智能合约高级开发

智能合约高级开发

本章将介绍智能合约开发中的高级主题,包括合约升级、代理模式、闪电贷、跨合约调用等高级特性。

合约升级模式

1. 代理模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
// 代理合约
contract Proxy {
    address public implementation;
    address public admin;

    constructor(address _implementation) {
        implementation = _implementation;
        admin = msg.sender;
    }

    modifier onlyAdmin() {
        require(msg.sender == admin, "Not admin");
        _;
    }

    function upgrade(address newImplementation) external onlyAdmin {
        implementation = newImplementation;
    }

    fallback() external payable {
        address _implementation = implementation;
        assembly {
            // 复制调用数据
            calldatacopy(0, 0, calldatasize())

            // 调用实现合约
            let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)

            // 复制返回数据
            returndatacopy(0, 0, returndatasize())

            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }

    receive() external payable {}
}

// 实现合约V1
contract ImplementationV1 {
    uint256 public value;

    function setValue(uint256 _value) external {
        value = _value;
    }
}

// 实现合约V2
contract ImplementationV2 {
    uint256 public value;

    function setValue(uint256 _value) external {
        value = _value * 2;
    }

    function getValue() external view returns (uint256) {
        return value;
    }
}

2. 透明代理模式

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
contract TransparentProxy {
    address public implementation;
    address public admin;

    constructor(address _implementation) {
        implementation = _implementation;
        admin = msg.sender;
    }

    modifier ifAdmin() {
        if (msg.sender == admin) {
            _;
        } else {
            _fallback();
        }
    }

    function upgradeTo(address newImplementation) external ifAdmin {
        implementation = newImplementation;
    }

    function _fallback() internal {
        _delegate(implementation);
    }

    function _delegate(address _implementation) internal {
        assembly {
            calldatacopy(0, 0, calldatasize())
            let result := delegatecall(gas(), _implementation, 0, calldatasize(), 0, 0)
            returndatacopy(0, 0, returndatasize())
            switch result
            case 0 { revert(0, returndatasize()) }
            default { return(0, returndatasize()) }
        }
    }

    fallback() external payable {
        _fallback();
    }

    receive() external payable {
        _fallback();
    }
}

闪电贷实现

1. 基础闪电贷

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
interface IFlashLoanReceiver {
    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external returns (bool);
}

contract FlashLoan {
    mapping(address => uint256) public balances;
    uint256 public constant FLASH_LOAN_FEE = 9; // 0.09%

    event FlashLoan(
        address indexed borrower,
        address indexed token,
        uint256 amount,
        uint256 fee
    );

    function executeFlashLoan(
        address receiver,
        address token,
        uint256 amount,
        bytes calldata params
    ) external {
        uint256 balanceBefore = IERC20(token).balanceOf(address(this));
        require(balanceBefore >= amount, "Not enough balance");

        // 计算费用
        uint256 fee = (amount * FLASH_LOAN_FEE) / 10000;

        // 转账给接收者
        IERC20(token).transfer(receiver, amount);

        // 调用接收者的回调函数
        require(
            IFlashLoanReceiver(receiver).executeOperation(
                [token],
                [amount],
                [fee],
                msg.sender,
                params
            ),
            "Flash loan failed"
        );

        // 检查是否归还了本金和费用
        uint256 balanceAfter = IERC20(token).balanceOf(address(this));
        require(
            balanceAfter >= balanceBefore + fee,
            "Flash loan not repaid"
        );

        emit FlashLoan(msg.sender, token, amount, fee);
    }
}

2. 闪电贷套利示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
contract FlashLoanArbitrage is IFlashLoanReceiver {
    address public owner;

    constructor() {
        owner = msg.sender;
    }

    function executeOperation(
        address[] calldata assets,
        uint256[] calldata amounts,
        uint256[] calldata premiums,
        address initiator,
        bytes calldata params
    ) external override returns (bool) {
        // 执行套利逻辑
        _executeArbitrage(assets[0], amounts[0]);

        // 归还闪电贷
        uint256 amountToRepay = amounts[0] + premiums[0];
        IERC20(assets[0]).approve(msg.sender, amountToRepay);

        return true;
    }

    function _executeArbitrage(address token, uint256 amount) internal {
        // 在DEX A上卖出
        uint256 received = _swapOnDexA(token, amount);

        // 在DEX B上买入
        uint256 bought = _swapOnDexB(token, received);

        require(bought > amount, "No profit");
    }

    function _swapOnDexA(address token, uint256 amount) internal returns (uint256) {
        // DEX A的交换逻辑
        return amount;
    }

    function _swapOnDexB(address token, uint256 amount) internal returns (uint256) {
        // DEX B的交换逻辑
        return amount;
    }
}

跨合约调用

1. 低级调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
contract LowLevelCalls {
    function executeCall(
        address target,
        bytes memory data,
        uint256 value
    ) external returns (bool, bytes memory) {
        // call方式
        (bool success, bytes memory result) = target.call{value: value}(data);
        require(success, "Call failed");
        return (success, result);
    }

    function executeStaticCall(
        address target,
        bytes memory data
    ) external view returns (bool, bytes memory) {
        // staticcall方式(只读调用)
        (bool success, bytes memory result) = target.staticcall(data);
        require(success, "Static call failed");
        return (success, result);
    }

    function executeDelegateCall(
        address target,
        bytes memory data
    ) external returns (bool, bytes memory) {
        // delegatecall方式(使用目标合约代码,但在当前上下文执行)
        (bool success, bytes memory result) = target.delegatecall(data);
        require(success, "Delegate call failed");
        return (success, result);
    }
}

2. 接口调用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
interface IExternalContract {
    function doSomething(uint256 value) external returns (bool);
    function getValue() external view returns (uint256);
}

contract InterfaceCalls {
    function executeInterface(
        address target,
        uint256 value
    ) external returns (bool) {
        return IExternalContract(target).doSomething(value);
    }

    function queryInterface(
        address target
    ) external view returns (uint256) {
        return IExternalContract(target).getValue();
    }
}

多签名实现

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
contract MultiSigWallet {
    event Deposit(address indexed sender, uint amount);
    event Submit(uint indexed txId);
    event Approve(address indexed owner, uint indexed txId);
    event Revoke(address indexed owner, uint indexed txId);
    event Execute(uint indexed txId);

    struct Transaction {
        address to;
        uint value;
        bytes data;
        bool executed;
    }

    address[] public owners;
    mapping(address => bool) public isOwner;
    uint public required;

    Transaction[] public transactions;
    mapping(uint => mapping(address => bool)) public approved;

    modifier onlyOwner() {
        require(isOwner[msg.sender], "Not owner");
        _;
    }

    modifier txExists(uint _txId) {
        require(_txId < transactions.length, "Tx does not exist");
        _;
    }

    modifier notApproved(uint _txId) {
        require(!approved[_txId][msg.sender], "Tx already approved");
        _;
    }

    modifier notExecuted(uint _txId) {
        require(!transactions[_txId].executed, "Tx already executed");
        _;
    }

    constructor(address[] memory _owners, uint _required) {
        require(_owners.length > 0, "Owners required");
        require(
            _required > 0 && _required <= _owners.length,
            "Invalid required number"
        );

        for (uint i = 0; i < _owners.length; i++) {
            address owner = _owners[i];
            require(owner != address(0), "Invalid owner");
            require(!isOwner[owner], "Owner not unique");

            isOwner[owner] = true;
            owners.push(owner);
        }

        required = _required;
    }

    receive() external payable {
        emit Deposit(msg.sender, msg.value);
    }

    function submit(
        address _to,
        uint _value,
        bytes calldata _data
    ) external onlyOwner {
        transactions.push(Transaction({
            to: _to,
            value: _value,
            data: _data,
            executed: false
        }));

        emit Submit(transactions.length - 1);
    }

    function approve(
        uint _txId
    ) external onlyOwner txExists(_txId) notApproved(_txId) notExecuted(_txId) {
        approved[_txId][msg.sender] = true;
        emit Approve(msg.sender, _txId);
    }

    function _getApprovalCount(uint _txId) private view returns (uint count) {
        for (uint i = 0; i < owners.length; i++) {
            if (approved[_txId][owners[i]]) {
                count += 1;
            }
        }
    }

    function execute(
        uint _txId
    ) external txExists(_txId) notExecuted(_txId) {
        require(
            _getApprovalCount(_txId) >= required,
            "Approvals < required"
        );

        Transaction storage transaction = transactions[_txId];
        transaction.executed = true;

        (bool success, ) = transaction.to.call{value: transaction.value}(
            transaction.data
        );
        require(success, "Tx failed");

        emit Execute(_txId);
    }

    function revoke(
        uint _txId
    ) external onlyOwner txExists(_txId) notExecuted(_txId) {
        require(approved[_txId][msg.sender], "Tx not approved");
        approved[_txId][msg.sender] = false;
        emit Revoke(msg.sender, _txId);
    }
}

时间锁合约

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
contract TimeLock {
    event QueueTransaction(
        bytes32 indexed txHash,
        address indexed target,
        uint value,
        string signature,
        bytes data,
        uint eta
    );
    event ExecuteTransaction(
        bytes32 indexed txHash,
        address indexed target,
        uint value,
        string signature,
        bytes data,
        uint eta
    );
    event CancelTransaction(
        bytes32 indexed txHash,
        address indexed target,
        uint value,
        string signature,
        bytes data,
        uint eta
    );

    uint public constant GRACE_PERIOD = 14 days;
    uint public constant MINIMUM_DELAY = 2 days;
    uint public constant MAXIMUM_DELAY = 30 days;

    address public admin;
    uint public delay;

    mapping(bytes32 => bool) public queuedTransactions;

    constructor(uint _delay) {
        require(
            _delay >= MINIMUM_DELAY && _delay <= MAXIMUM_DELAY,
            "Delay not in range"
        );
        admin = msg.sender;
        delay = _delay;
    }

    function queueTransaction(
        address target,
        uint value,
        string memory signature,
        bytes memory data,
        uint eta
    ) public returns (bytes32) {
        require(msg.sender == admin, "Not admin");
        require(
            eta >= block.timestamp + delay,
            "Eta too soon"
        );

        bytes32 txHash = keccak256(
            abi.encode(target, value, signature, data, eta)
        );
        queuedTransactions[txHash] = true;

        emit QueueTransaction(txHash, target, value, signature, data, eta);
        return txHash;
    }

    function executeTransaction(
        address target,
        uint value,
        string memory signature,
        bytes memory data,
        uint eta
    ) public payable returns (bytes memory) {
        require(msg.sender == admin, "Not admin");

        bytes32 txHash = keccak256(
            abi.encode(target, value, signature, data, eta)
        );
        require(queuedTransactions[txHash], "Tx not queued");
        require(block.timestamp >= eta, "Too early");
        require(
            block.timestamp <= eta + GRACE_PERIOD,
            "Tx stale"
        );

        queuedTransactions[txHash] = false;

        bytes memory callData;
        if (bytes(signature).length == 0) {
            callData = data;
        } else {
            callData = abi.encodePacked(
                bytes4(keccak256(bytes(signature))),
                data
            );
        }

        (bool success, bytes memory returnData) = target.call{value: value}(
            callData
        );
        require(success, "Tx failed");

        emit ExecuteTransaction(txHash, target, value, signature, data, eta);
        return returnData;
    }

    function cancelTransaction(
        address target,
        uint value,
        string memory signature,
        bytes memory data,
        uint eta
    ) public {
        require(msg.sender == admin, "Not admin");

        bytes32 txHash = keccak256(
            abi.encode(target, value, signature, data, eta)
        );
        queuedTransactions[txHash] = false;

        emit CancelTransaction(txHash, target, value, signature, data, eta);
    }
}

练习题

  1. 实现一个可升级的ERC20代币合约:
1
2
3
4
// 练习:实现可升级代币
contract UpgradeableToken {
    // 你的代码
}
  1. 创建一个闪电贷套利合约:
1
2
3
4
// 练习:实现闪电贷套利
contract FlashLoanArbitrage {
    // 你的代码
}
  1. 实现一个带有时间锁的治理合约:
1
2
3
4
// 练习:实现时间锁治理
contract TimeLockGovernance {
    // 你的代码
}
  1. 创建一个跨合约调用的示例:
1
2
3
4
// 练习:实现跨合约调用
contract CrossContractCaller {
    // 你的代码
}
  1. 实现一个多签名钱包的变体:
1
2
3
4
// 练习:实现多签钱包变体
contract CustomMultiSig {
    // 你的代码
}

参考资源

  1. OpenZeppelin Upgrades
  2. Aave闪电贷文档
  3. Gnosis Safe多签钱包
  4. Compound时间锁
  5. 智能合约安全指南