第二章:智能合约开发与变现

第二章:智能合约开发与变现

智能合约是可以自动执行的商业逻辑。当你把"合约即代码"和"代码即法律"结合起来,你得到的是一个不需要中间人就可以收取费用的商业系统。


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

小结

智能合约的变现核心是:把协议费嵌入到合约逻辑中,让每次用户交互都自动触发收费

关键原则:

  1. CEI 模式(Check-Effects-Interactions):先检查,再修改状态,再外部交互
  2. 可升级合约:使用代理模式,为未来的迭代保留灵活性
  3. 费率管控:设置上限(如最高 5%),保持用户信任
  4. 充分测试:在主网部署前,Foundry 模糊测试是必须的

下一章,讲 DeFi 协议的商业模型——已经有数十亿美元通过这些模型流转。