第二章:智能合约开发与变现
第二章:智能合约开发与变现
智能合约是可以自动执行的商业逻辑。当你把"合约即代码"和"代码即法律"结合起来,你得到的是一个不需要中间人就可以收取费用的商业系统。
2.1 Solidity 核心模式
Solidity 是以太坊生态的主要智能合约语言,语法类似 JavaScript。
基础合约结构:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
/**
* @title SimpleProtocol
* @dev 一个基础的协议合约,展示核心模式
*/
contract SimpleProtocol is Ownable, ReentrancyGuard {
// 状态变量
uint256 public protocolFee = 30; // 0.3% (基数 10000)
uint256 public totalFeesCollected;
mapping(address => uint256) public userBalances;
// 事件(链上日志,前端监听用)
event Deposited(address indexed user, uint256 amount);
event FeeCollected(address indexed user, uint256 feeAmount);
event FeeUpdated(uint256 oldFee, uint256 newFee);
// 错误(比 require 字符串更 Gas 高效)
error InvalidAmount();
error InsufficientBalance();
error FeeOutOfRange();
constructor() Ownable(msg.sender) {}
// 存款函数(payable = 接收 ETH)
function deposit() external payable nonReentrant {
if (msg.value == 0) revert InvalidAmount();
// 计算协议费
uint256 fee = (msg.value * protocolFee) / 10000;
uint256 netAmount = msg.value - fee;
userBalances[msg.sender] += netAmount;
totalFeesCollected += fee;
emit Deposited(msg.sender, netAmount);
emit FeeCollected(msg.sender, fee);
}
// 取款函数
function withdraw(uint256 amount) external nonReentrant {
if (amount == 0) revert InvalidAmount();
if (userBalances[msg.sender] < amount) revert InsufficientBalance();
userBalances[msg.sender] -= amount;
// CEI 模式:先修改状态,再转账(防止重入攻击)
(bool success, ) = payable(msg.sender).call{value: amount}("");
require(success, "Transfer failed");
}
// 管理员功能:更新费率
function setProtocolFee(uint256 newFee) external onlyOwner {
if (newFee > 500) revert FeeOutOfRange(); // 最高 5%
emit FeeUpdated(protocolFee, newFee);
protocolFee = newFee;
}
// 管理员功能:提取累积的协议费
function withdrawFees() external onlyOwner {
uint256 fees = totalFeesCollected;
totalFeesCollected = 0;
(bool success, ) = payable(owner()).call{value: fees}("");
require(success, "Fee withdrawal failed");
}
// 查看函数(不消耗 Gas)
function getBalance(address user) external view returns (uint256) {
return userBalances[user];
}
}
2.2 合约升级模式
智能合约一旦部署就不可修改。但通过代理模式(Proxy Pattern),可以实现"可升级"合约:
// 使用 OpenZeppelin UUPS 代理模式
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
contract ProtocolV1 is Initializable, OwnableUpgradeable, UUPSUpgradeable {
uint256 public protocolFee;
mapping(address => uint256) public userBalances;
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers(); // 防止直接部署被初始化
}
// 用 initialize 替代 constructor(代理模式要求)
function initialize(address initialOwner) public initializer {
__Ownable_init(initialOwner);
__UUPSUpgradeable_init();
protocolFee = 30; // 0.3%
}
// V1 的逻辑...
// 升级授权(只有 owner 可以升级)
function _authorizeUpgrade(address newImplementation)
internal
override
onlyOwner
{}
}
// V2 添加新功能,不破坏现有存储
contract ProtocolV2 is ProtocolV1 {
// 新增状态变量必须放在现有变量之后
uint256 public referralFee; // 新功能
function setReferralFee(uint256 fee) external onlyOwner {
referralFee = fee;
}
// 覆盖现有函数,添加推荐逻辑
function depositWithReferral(address referrer) external payable {
// 新的存款逻辑,包含推荐奖励
}
}
升级模式的商业意义:
可升级合约 = 软件产品,而不是固定的一次性合约
你可以:
- 修复 Bug(不需要用户迁移资产)
- 添加新功能
- 调整费率参数
代价:
- 中心化风险(owner 可以升级到恶意版本)
- 解决方案:Timelock(升级需要等待 48 小时,给用户反应时间)
Multisig(需要多个签名者同意才能升级)
2.3 链上协议费收取机制
协议费是 Web3 商业的核心变现方式,有几种常见模式:
模式一:直接百分比费(最常见)
// 每次交易收取 0.3% 费用,累积在合约中
uint256 constant BASIS_POINTS = 10000;
uint256 public feeBps = 30; // 30/10000 = 0.3%
function _deductFee(uint256 amount) internal returns (uint256 netAmount) {
uint256 fee = (amount * feeBps) / BASIS_POINTS;
accumulatedFees += fee;
return amount - fee;
}
模式二:订阅模式
// 用户支付月费获取访问权
mapping(address => uint256) public subscriptionExpiry;
uint256 public monthlyFeeUSD = 10e18; // $10 in wei
// 需要 Chainlink Price Feed 获取 ETH/USD 价格
function subscribe(uint8 months) external payable {
uint256 requiredETH = getETHPrice(monthlyFeeUSD * months);
require(msg.value >= requiredETH, "Insufficient payment");
uint256 newExpiry = max(subscriptionExpiry[msg.sender], block.timestamp);
subscriptionExpiry[msg.sender] = newExpiry + (30 days * months);
}
modifier onlySubscribed() {
require(subscriptionExpiry[msg.sender] > block.timestamp, "Subscription expired");
_;
}
模式三:Token 持有权限
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
IERC20 public accessToken;
uint256 public minTokenBalance = 100 * 1e18; // 持有 100 个 Token
modifier onlyTokenHolder() {
require(
accessToken.balanceOf(msg.sender) >= minTokenBalance,
"Insufficient token balance"
);
_;
}
2.4 前端与合约交互
// 使用 viem(现代以太坊前端库,比 ethers.js 更新)
import { createPublicClient, createWalletClient, http, parseEther } from 'viem'
import { arbitrum } from 'viem/chains'
import { PROTOCOL_ABI, PROTOCOL_ADDRESS } from './constants'
const publicClient = createPublicClient({
chain: arbitrum,
transport: http('https://arb1.arbitrum.io/rpc'),
})
// 读取合约状态(免费)
async function getBalance(userAddress: `0x${string}`) {
const balance = await publicClient.readContract({
address: PROTOCOL_ADDRESS,
abi: PROTOCOL_ABI,
functionName: 'getBalance',
args: [userAddress],
})
return balance
}
// 发送交易(需要用户签名)
async function deposit(walletClient: any, amount: string) {
const { request } = await publicClient.simulateContract({
account: walletClient.account,
address: PROTOCOL_ADDRESS,
abi: PROTOCOL_ABI,
functionName: 'deposit',
value: parseEther(amount),
})
// 先 simulate 确认不会 revert,再发送
const hash = await walletClient.writeContract(request)
// 等待确认
const receipt = await publicClient.waitForTransactionReceipt({ hash })
return receipt
}
2.5 合约测试(Foundry)
// test/Protocol.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Test.sol";
import "../src/SimpleProtocol.sol";
contract SimpleProtocolTest is Test {
SimpleProtocol protocol;
address alice = makeAddr("alice");
address bob = makeAddr("bob");
function setUp() public {
protocol = new SimpleProtocol();
// 给测试账户发 ETH
deal(alice, 10 ether);
deal(bob, 10 ether);
}
function test_Deposit() public {
vm.prank(alice); // 模拟 alice 发起交易
protocol.deposit{value: 1 ether}();
// 0.3% 费用后,alice 的余额应该是 0.997 ETH
assertEq(protocol.getBalance(alice), 0.997 ether);
}
function test_WithdrawFeesOnlyOwner() public {
vm.prank(alice);
vm.expectRevert(); // 期望 revert
protocol.withdrawFees();
}
function testFuzz_DepositAmount(uint256 amount) public {
// 模糊测试:用随机金额测试
amount = bound(amount, 0.001 ether, 10 ether);
vm.prank(alice);
protocol.deposit{value: amount}();
uint256 expectedFee = (amount * 30) / 10000;
assertEq(protocol.totalFeesCollected(), expectedFee);
}
}
# 运行测试
forge test -vvv
# 运行模糊测试(默认 256 次随机输入)
forge test --match-test testFuzz -vv
# Gas 报告
forge test --gas-report
小结
智能合约的变现核心是:把协议费嵌入到合约逻辑中,让每次用户交互都自动触发收费。
关键原则:
- CEI 模式(Check-Effects-Interactions):先检查,再修改状态,再外部交互
- 可升级合约:使用代理模式,为未来的迭代保留灵活性
- 费率管控:设置上限(如最高 5%),保持用户信任
- 充分测试:在主网部署前,Foundry 模糊测试是必须的
下一章,讲 DeFi 协议的商业模型——已经有数十亿美元通过这些模型流转。