• GnosisSafe.sol 学习 (一)


    GnosisSafe是以太坊区块链上最流行的多签钱包!它的最初版本叫 MultiSigWallet,现在新的钱包叫Gnosis Safe,意味着它不仅仅是钱包了。它自己的介绍为:以太坊上的最可信的数字资产管理平台(The most trusted platform to manage digital assets on Ethereum)。

    学习完代理合约GnosisSafeProxy之后我们来学习实现合约,先从GnosisSafe.sol学习起。

    一 合约源码

    第一件事上源码:

    // SPDX-License-Identifier: LGPL-3.0-only
    pragma solidity >=0.7.0 <0.9.0;
    
    import "./base/ModuleManager.sol";
    import "./base/OwnerManager.sol";
    import "./base/FallbackManager.sol";
    import "./base/GuardManager.sol";
    import "./common/EtherPaymentFallback.sol";
    import "./common/Singleton.sol";
    import "./common/SignatureDecoder.sol";
    import "./common/SecuredTokenTransfer.sol";
    import "./common/StorageAccessible.sol";
    import "./interfaces/ISignatureValidator.sol";
    import "./external/GnosisSafeMath.sol";
    
    /// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191.
    /// @author Stefan George - 
    /// @author Richard Meissner - 
    contract GnosisSafe is
        EtherPaymentFallback,
        Singleton,
        ModuleManager,
        OwnerManager,
        SignatureDecoder,
        SecuredTokenTransfer,
        ISignatureValidatorConstants,
        FallbackManager,
        StorageAccessible,
        GuardManager
    {
        using GnosisSafeMath for uint256;
    
        string public constant VERSION = "1.3.0";
    
        // keccak256(
        //     "EIP712Domain(uint256 chainId,address verifyingContract)"
        // );
        bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218;
    
        // keccak256(
        //     "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)"
        // );
        bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8;
    
        event SafeSetup(address indexed initiator, address[] owners, uint256 threshold, address initializer, address fallbackHandler);
        event ApproveHash(bytes32 indexed approvedHash, address indexed owner);
        event SignMsg(bytes32 indexed msgHash);
        event ExecutionFailure(bytes32 txHash, uint256 payment);
        event ExecutionSuccess(bytes32 txHash, uint256 payment);
    
        uint256 public nonce;
        bytes32 private _deprecatedDomainSeparator;
        // Mapping to keep track of all message hashes that have been approve by ALL REQUIRED owners
        mapping(bytes32 => uint256) public signedMessages;
        // Mapping to keep track of all hashes (message or transaction) that have been approve by ANY owners
        mapping(address => mapping(bytes32 => uint256)) public approvedHashes;
    
        // This constructor ensures that this contract can only be used as a master copy for Proxy contracts
        constructor() {
            // By setting the threshold it is not possible to call setup anymore,
            // so we create a Safe with 0 owners and threshold 1.
            // This is an unusable Safe, perfect for the singleton
            threshold = 1;
        }
    
        /// @dev Setup function sets initial storage of contract.
        /// @param _owners List of Safe owners.
        /// @param _threshold Number of required confirmations for a Safe transaction.
        /// @param to Contract address for optional delegate call.
        /// @param data Data payload for optional delegate call.
        /// @param fallbackHandler Handler for fallback calls to this contract
        /// @param paymentToken Token that should be used for the payment (0 is ETH)
        /// @param payment Value that should be paid
        /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin)
        function setup(
            address[] calldata _owners,
            uint256 _threshold,
            address to,
            bytes calldata data,
            address fallbackHandler,
            address paymentToken,
            uint256 payment,
            address payable paymentReceiver
        ) external {
            // setupOwners checks if the Threshold is already set, therefore preventing that this method is called twice
            setupOwners(_owners, _threshold);
            if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler);
            // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules
            setupModules(to, data);
    
            if (payment > 0) {
                // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself)
                // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment
                handlePayment(payment, 0, 1, paymentToken, paymentReceiver);
            }
            emit SafeSetup(msg.sender, _owners, _threshold, to, fallbackHandler);
        }
    
        /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction.
        ///      Note: The fees are always transferred, even if the user transaction fails.
        /// @param to Destination address of Safe transaction.
        /// @param value Ether value of Safe transaction.
        /// @param data Data payload of Safe transaction.
        /// @param operation Operation type of Safe transaction.
        /// @param safeTxGas Gas that should be used for the Safe transaction.
        /// @param baseGas Gas costs that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)
        /// @param gasPrice Gas price that should be used for the payment calculation.
        /// @param gasToken Token address (or 0 if ETH) that is used for the payment.
        /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).
        /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v})
        function execTransaction(
            address to,
            uint256 value,
            bytes calldata data,
            Enum.Operation operation,
            uint256 safeTxGas,
            uint256 baseGas,
            uint256 gasPrice,
            address gasToken,
            address payable refundReceiver,
            bytes memory signatures
        ) public payable virtual returns (bool success) {
            bytes32 txHash;
            // Use scope here to limit variable lifetime and prevent `stack too deep` errors
            {
                bytes memory txHashData =
                    encodeTransactionData(
                        // Transaction info
                        to,
                        value,
                        data,
                        operation,
                        safeTxGas,
                        // Payment info
                        baseGas,
                        gasPrice,
                        gasToken,
                        refundReceiver,
                        // Signature info
                        nonce
                    );
                // Increase nonce and execute transaction.
                nonce++;
                txHash = keccak256(txHashData);
                checkSignatures(txHash, txHashData, signatures);
            }
            address guard = getGuard();
            {
                if (guard != address(0)) {
                    Guard(guard).checkTransaction(
                        // Transaction info
                        to,
                        value,
                        data,
                        operation,
                        safeTxGas,
                        // Payment info
                        baseGas,
                        gasPrice,
                        gasToken,
                        refundReceiver,
                        // Signature info
                        signatures,
                        msg.sender
                    );
                }
            }
            // We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500)
            // We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150
            require(gasleft() >= ((safeTxGas * 64) / 63).max(safeTxGas + 2500) + 500, "GS010");
            // Use scope here to limit variable lifetime and prevent `stack too deep` errors
            {
                uint256 gasUsed = gasleft();
                // If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas)
                // We only substract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas
                success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas);
                gasUsed = gasUsed.sub(gasleft());
                // If no safeTxGas and no gasPrice was set (e.g. both are 0), then the internal tx is required to be successful
                // This makes it possible to use `estimateGas` without issues, as it searches for the minimum gas where the tx doesn't revert
                require(success || safeTxGas != 0 || gasPrice != 0, "GS013");
                // We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls
                uint256 payment = 0;
                if (gasPrice > 0) {
                    payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver);
                }
                if (success) emit ExecutionSuccess(txHash, payment);
                else emit ExecutionFailure(txHash, payment);
            }
            {
                if (guard != address(0)) {
                    Guard(guard).checkAfterExecution(txHash, success);
                }
            }
        }
    
        function handlePayment(
            uint256 gasUsed,
            uint256 baseGas,
            uint256 gasPrice,
            address gasToken,
            address payable refundReceiver
        ) private returns (uint256 payment) {
            // solhint-disable-next-line avoid-tx-origin
            address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver;
            if (gasToken == address(0)) {
                // For ETH we will only adjust the gas price to not be higher than the actual used gas price
                payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice);
                require(receiver.send(payment), "GS011");
            } else {
                payment = gasUsed.add(baseGas).mul(gasPrice);
                require(transferToken(gasToken, receiver, payment), "GS012");
            }
        }
    
        /**
         * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.
         * @param dataHash Hash of the data (could be either a message hash or transaction hash)
         * @param data That should be signed (this is passed to an external validator contract)
         * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash.
         */
        function checkSignatures(
            bytes32 dataHash,
            bytes memory data,
            bytes memory signatures
        ) public view {
            // Load threshold to avoid multiple storage loads
            uint256 _threshold = threshold;
            // Check that a threshold is set
            require(_threshold > 0, "GS001");
            checkNSignatures(dataHash, data, signatures, _threshold);
        }
    
        /**
         * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise.
         * @param dataHash Hash of the data (could be either a message hash or transaction hash)
         * @param data That should be signed (this is passed to an external validator contract)
         * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash.
         * @param requiredSignatures Amount of required valid signatures.
         */
        function checkNSignatures(
            bytes32 dataHash,
            bytes memory data,
            bytes memory signatures,
            uint256 requiredSignatures
        ) public view {
            // Check that the provided signature data is not too short
            require(signatures.length >= requiredSignatures.mul(65), "GS020");
            // There cannot be an owner with address 0.
            address lastOwner = address(0);
            address currentOwner;
            uint8 v;
            bytes32 r;
            bytes32 s;
            uint256 i;
            for (i = 0; i < requiredSignatures; i++) {
                (v, r, s) = signatureSplit(signatures, i);
                if (v == 0) {
                    // If v is 0 then it is a contract signature
                    // When handling contract signatures the address of the contract is encoded into r
                    currentOwner = address(uint160(uint256(r)));
    
                    // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes
                    // This check is not completely accurate, since it is possible that more signatures than the threshold are send.
                    // Here we only check that the pointer is not pointing inside the part that is being processed
                    require(uint256(s) >= requiredSignatures.mul(65), "GS021");
    
                    // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes)
                    require(uint256(s).add(32) <= signatures.length, "GS022");
    
                    // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length
                    uint256 contractSignatureLen;
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        contractSignatureLen := mload(add(add(signatures, s), 0x20))
                    }
                    require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "GS023");
    
                    // Check signature
                    bytes memory contractSignature;
                    // solhint-disable-next-line no-inline-assembly
                    assembly {
                        // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s
                        contractSignature := add(add(signatures, s), 0x20)
                    }
                    require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "GS024");
                } else if (v == 1) {
                    // If v is 1 then it is an approved hash
                    // When handling approved hashes the address of the approver is encoded into r
                    currentOwner = address(uint160(uint256(r)));
                    // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction
                    require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "GS025");
                } else if (v > 30) {
                    // If v > 30 then default va (27,28) has been adjusted for eth_sign flow
                    // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover
                    currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v - 4, r, s);
                } else {
                    // Default is the ecrecover flow with the provided data hash
                    // Use ecrecover with the messageHash for EOA signatures
                    currentOwner = ecrecover(dataHash, v, r, s);
                }
                require(currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS, "GS026");
                lastOwner = currentOwner;
            }
        }
    
        /// @dev Allows to estimate a Safe transaction.
        ///      This method is only meant for estimation purpose, therefore the call will always revert and encode the result in the revert data.
        ///      Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction`
        /// @param to Destination address of Safe transaction.
        /// @param value Ether value of Safe transaction.
        /// @param data Data payload of Safe transaction.
        /// @param operation Operation type of Safe transaction.
        /// @return Estimate without refunds and overhead fees (base transaction and payload data gas costs).
        /// @notice Deprecated in favor of common/StorageAccessible.sol and will be removed in next version.
        function requiredTxGas(
            address to,
            uint256 value,
            bytes calldata data,
            Enum.Operation operation
        ) external returns (uint256) {
            uint256 startGas = gasleft();
            // We don't provide an error message here, as we use it to return the estimate
            require(execute(to, value, data, operation, gasleft()));
            uint256 requiredGas = startGas - gasleft();
            // Convert response to string and return via error message
            revert(string(abi.encodePacked(requiredGas)));
        }
    
        /**
         * @dev Marks a hash as approved. This can be used to validate a hash that is used by a signature.
         * @param hashToApprove The hash that should be marked as approved for signatures that are verified by this contract.
         */
        function approveHash(bytes32 hashToApprove) external {
            require(owners[msg.sender] != address(0), "GS030");
            approvedHashes[msg.sender][hashToApprove] = 1;
            emit ApproveHash(hashToApprove, msg.sender);
        }
    
        /// @dev Returns the chain id used by this contract.
        function getChainId() public view returns (uint256) {
            uint256 id;
            // solhint-disable-next-line no-inline-assembly
            assembly {
                id := chainid()
            }
            return id;
        }
    
        function domainSeparator() public view returns (bytes32) {
            return keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, getChainId(), this));
        }
    
        /// @dev Returns the bytes that are hashed to be signed by owners.
        /// @param to Destination address.
        /// @param value Ether value.
        /// @param data Data payload.
        /// @param operation Operation type.
        /// @param safeTxGas Gas that should be used for the safe transaction.
        /// @param baseGas Gas costs for that are independent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund)
        /// @param gasPrice Maximum gas price that should be used for this transaction.
        /// @param gasToken Token address (or 0 if ETH) that is used for the payment.
        /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).
        /// @param _nonce Transaction nonce.
        /// @return Transaction hash bytes.
        function encodeTransactionData(
            address to,
            uint256 value,
            bytes calldata data,
            Enum.Operation operation,
            uint256 safeTxGas,
            uint256 baseGas,
            uint256 gasPrice,
            address gasToken,
            address refundReceiver,
            uint256 _nonce
        ) public view returns (bytes memory) {
            bytes32 safeTxHash =
                keccak256(
                    abi.encode(
                        SAFE_TX_TYPEHASH,
                        to,
                        value,
                        keccak256(data),
                        operation,
                        safeTxGas,
                        baseGas,
                        gasPrice,
                        gasToken,
                        refundReceiver,
                        _nonce
                    )
                );
            return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator(), safeTxHash);
        }
    
        /// @dev Returns hash to be signed by owners.
        /// @param to Destination address.
        /// @param value Ether value.
        /// @param data Data payload.
        /// @param operation Operation type.
        /// @param safeTxGas Fas that should be used for the safe transaction.
        /// @param baseGas Gas costs for data used to trigger the safe transaction.
        /// @param gasPrice Maximum gas price that should be used for this transaction.
        /// @param gasToken Token address (or 0 if ETH) that is used for the payment.
        /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin).
        /// @param _nonce Transaction nonce.
        /// @return Transaction hash.
        function getTransactionHash(
            address to,
            uint256 value,
            bytes calldata data,
            Enum.Operation operation,
            uint256 safeTxGas,
            uint256 baseGas,
            uint256 gasPrice,
            address gasToken,
            address refundReceiver,
            uint256 _nonce
        ) public view returns (bytes32) {
            return keccak256(encodeTransactionData(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce));
        }
    }
    
    • 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
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133
    • 134
    • 135
    • 136
    • 137
    • 138
    • 139
    • 140
    • 141
    • 142
    • 143
    • 144
    • 145
    • 146
    • 147
    • 148
    • 149
    • 150
    • 151
    • 152
    • 153
    • 154
    • 155
    • 156
    • 157
    • 158
    • 159
    • 160
    • 161
    • 162
    • 163
    • 164
    • 165
    • 166
    • 167
    • 168
    • 169
    • 170
    • 171
    • 172
    • 173
    • 174
    • 175
    • 176
    • 177
    • 178
    • 179
    • 180
    • 181
    • 182
    • 183
    • 184
    • 185
    • 186
    • 187
    • 188
    • 189
    • 190
    • 191
    • 192
    • 193
    • 194
    • 195
    • 196
    • 197
    • 198
    • 199
    • 200
    • 201
    • 202
    • 203
    • 204
    • 205
    • 206
    • 207
    • 208
    • 209
    • 210
    • 211
    • 212
    • 213
    • 214
    • 215
    • 216
    • 217
    • 218
    • 219
    • 220
    • 221
    • 222
    • 223
    • 224
    • 225
    • 226
    • 227
    • 228
    • 229
    • 230
    • 231
    • 232
    • 233
    • 234
    • 235
    • 236
    • 237
    • 238
    • 239
    • 240
    • 241
    • 242
    • 243
    • 244
    • 245
    • 246
    • 247
    • 248
    • 249
    • 250
    • 251
    • 252
    • 253
    • 254
    • 255
    • 256
    • 257
    • 258
    • 259
    • 260
    • 261
    • 262
    • 263
    • 264
    • 265
    • 266
    • 267
    • 268
    • 269
    • 270
    • 271
    • 272
    • 273
    • 274
    • 275
    • 276
    • 277
    • 278
    • 279
    • 280
    • 281
    • 282
    • 283
    • 284
    • 285
    • 286
    • 287
    • 288
    • 289
    • 290
    • 291
    • 292
    • 293
    • 294
    • 295
    • 296
    • 297
    • 298
    • 299
    • 300
    • 301
    • 302
    • 303
    • 304
    • 305
    • 306
    • 307
    • 308
    • 309
    • 310
    • 311
    • 312
    • 313
    • 314
    • 315
    • 316
    • 317
    • 318
    • 319
    • 320
    • 321
    • 322
    • 323
    • 324
    • 325
    • 326
    • 327
    • 328
    • 329
    • 330
    • 331
    • 332
    • 333
    • 334
    • 335
    • 336
    • 337
    • 338
    • 339
    • 340
    • 341
    • 342
    • 343
    • 344
    • 345
    • 346
    • 347
    • 348
    • 349
    • 350
    • 351
    • 352
    • 353
    • 354
    • 355
    • 356
    • 357
    • 358
    • 359
    • 360
    • 361
    • 362
    • 363
    • 364
    • 365
    • 366
    • 367
    • 368
    • 369
    • 370
    • 371
    • 372
    • 373
    • 374
    • 375
    • 376
    • 377
    • 378
    • 379
    • 380
    • 381
    • 382
    • 383
    • 384
    • 385
    • 386
    • 387
    • 388
    • 389
    • 390
    • 391
    • 392
    • 393
    • 394
    • 395
    • 396
    • 397
    • 398
    • 399
    • 400
    • 401
    • 402
    • 403
    • 404
    • 405
    • 406
    • 407
    • 408
    • 409
    • 410
    • 411
    • 412
    • 413
    • 414
    • 415
    • 416
    • 417
    • 418
    • 419
    • 420
    • 421
    • 422

    从源码头部可以看到,它导入并继承了很多合约,因此我们分开来学习。今天先学习
    EtherPaymentFallback,SingletonModuleManager

    二 EtherPaymentFallback.sol

    源码为:

    // SPDX-License-Identifier: LGPL-3.0-only
    pragma solidity >=0.7.0 <0.9.0;
    
    /// @title EtherPaymentFallback - A contract that has a fallback to accept ether payments
    /// @author Richard Meissner - 
    contract EtherPaymentFallback {
        event SafeReceived(address indexed sender, uint256 value);
    
        /// @dev Fallback function accepts Ether transactions.
        receive() external payable {
            emit SafeReceived(msg.sender, msg.value);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    源码很简单,在接收ETH时触发一个SafeReceived事件方便客户端进行追踪。

    吐槽一下,这个CSDN的在线编辑器不支持Solidity的代码块,但Typora支持。所以这里只能使用Javascript代码块,源代码看上去比较丑。大家可以自行和下图使用Solidity代码块的效果对比一下

    在这里插入图片描述
    希望CSDN能早一点支持Solidity.

    三 Singleton.sol

    源码为:

    // SPDX-License-Identifier: LGPL-3.0-only
    pragma solidity >=0.7.0 <0.9.0;
    
    /// @title Singleton - Base for singleton contracts (should always be first super contract)
    ///         This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`)
    /// @author Richard Meissner - 
    contract Singleton {
        // singleton always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract.
        // It should also always be ensured that the address is stored alone (uses a full word)
        address private singleton;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    这个合约也很简单,只有一个私有的状态变量singleton.我们前面提到过,代理合约的第一个状态变量是singleton,地址类型,为了保持插槽一致,所以实现合约的第一个状态变量也必须是相同数据类型的singleton,虽然实现合约本身用不到它,但是逻辑上需要它。

    通常,Singleton 是第一个应该继承的合约,否则 singleton 便有可能排不了第一个,但是由于它前面的EtherPaymentFallback并没有定义状态变量,因此它还是排在第一个。

    四 ModuleManager.sol

    顾名思义,模块管理器,虽然现在还不知道模块是什么。源代码如下:

    // SPDX-License-Identifier: LGPL-3.0-only
    pragma solidity >=0.7.0 <0.9.0;
    import "../common/Enum.sol";
    import "../common/SelfAuthorized.sol";
    import "./Executor.sol";
    
    /// @title Module Manager - A contract that manages modules that can execute transactions via this contract
    /// @author Stefan George - 
    /// @author Richard Meissner - 
    contract ModuleManager is SelfAuthorized, Executor {
        event EnabledModule(address module);
        event DisabledModule(address module);
        event ExecutionFromModuleSuccess(address indexed module);
        event ExecutionFromModuleFailure(address indexed module);
    
        address internal constant SENTINEL_MODULES = address(0x1);
    
        mapping(address => address) internal modules;
    
        function setupModules(address to, bytes memory data) internal {
            require(modules[SENTINEL_MODULES] == address(0), "GS100");
            modules[SENTINEL_MODULES] = SENTINEL_MODULES;
            if (to != address(0))
                // Setup has to complete successfully or transaction fails.
                require(execute(to, 0, data, Enum.Operation.DelegateCall, gasleft()), "GS000");
        }
    
        /// @dev Allows to add a module to the whitelist.
        ///      This can only be done via a Safe transaction.
        /// @notice Enables the module `module` for the Safe.
        /// @param module Module to be whitelisted.
        function enableModule(address module) public authorized {
            // Module address cannot be null or sentinel.
            require(module != address(0) && module != SENTINEL_MODULES, "GS101");
            // Module cannot be added twice.
            require(modules[module] == address(0), "GS102");
            modules[module] = modules[SENTINEL_MODULES];
            modules[SENTINEL_MODULES] = module;
            emit EnabledModule(module);
        }
    
        /// @dev Allows to remove a module from the whitelist.
        ///      This can only be done via a Safe transaction.
        /// @notice Disables the module `module` for the Safe.
        /// @param prevModule Module that pointed to the module to be removed in the linked list
        /// @param module Module to be removed.
        function disableModule(address prevModule, address module) public authorized {
            // Validate module address and check that it corresponds to module index.
            require(module != address(0) && module != SENTINEL_MODULES, "GS101");
            require(modules[prevModule] == module, "GS103");
            modules[prevModule] = modules[module];
            modules[module] = address(0);
            emit DisabledModule(module);
        }
    
        /// @dev Allows a Module to execute a Safe transaction without any further confirmations.
        /// @param to Destination address of module transaction.
        /// @param value Ether value of module transaction.
        /// @param data Data payload of module transaction.
        /// @param operation Operation type of module transaction.
        function execTransactionFromModule(
            address to,
            uint256 value,
            bytes memory data,
            Enum.Operation operation
        ) public virtual returns (bool success) {
            // Only whitelisted modules are allowed.
            require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "GS104");
            // Execute transaction without further confirmations.
            success = execute(to, value, data, operation, gasleft());
            if (success) emit ExecutionFromModuleSuccess(msg.sender);
            else emit ExecutionFromModuleFailure(msg.sender);
        }
    
        /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data
        /// @param to Destination address of module transaction.
        /// @param value Ether value of module transaction.
        /// @param data Data payload of module transaction.
        /// @param operation Operation type of module transaction.
        function execTransactionFromModuleReturnData(
            address to,
            uint256 value,
            bytes memory data,
            Enum.Operation operation
        ) public returns (bool success, bytes memory returnData) {
            success = execTransactionFromModule(to, value, data, operation);
            // solhint-disable-next-line no-inline-assembly
            assembly {
                // Load free memory location
                let ptr := mload(0x40)
                // We allocate memory for the return data by setting the free memory location to
                // current free memory location + data size + 32 bytes for data size value
                mstore(0x40, add(ptr, add(returndatasize(), 0x20)))
                // Store the size
                mstore(ptr, returndatasize())
                // Store the data
                returndatacopy(add(ptr, 0x20), 0, returndatasize())
                // Point the return data to the correct memory location
                returnData := ptr
            }
        }
    
        /// @dev Returns if an module is enabled
        /// @return True if the module is enabled
        function isModuleEnabled(address module) public view returns (bool) {
            return SENTINEL_MODULES != module && modules[module] != address(0);
        }
    
        /// @dev Returns array of modules.
        /// @param start Start of the page.
        /// @param pageSize Maximum number of modules that should be returned.
        /// @return array Array of modules.
        /// @return next Start of the next page.
        function getModulesPaginated(address start, uint256 pageSize) external view returns (address[] memory array, address next) {
            // Init array with max page size
            array = new address[](pageSize);
    
            // Populate return array
            uint256 moduleCount = 0;
            address currentModule = modules[start];
            while (currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) {
                array[moduleCount] = currentModule;
                currentModule = modules[currentModule];
                moduleCount++;
            }
            next = currentModule;
            // Set correct size of returned array
            // solhint-disable-next-line no-inline-assembly
            assembly {
                mstore(array, moduleCount)
            }
        }
    }
    
    • 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
    • 124
    • 125
    • 126
    • 127
    • 128
    • 129
    • 130
    • 131
    • 132
    • 133

    它也导入了三个合约并继承了两个,我们先学习导入的三个合约。

    五 Enum.sol

    代码为:

    // SPDX-License-Identifier: LGPL-3.0-only
    pragma solidity >=0.7.0 <0.9.0;
    
    /// @title Enum - Collection of enums
    /// @author Richard Meissner - 
    contract Enum {
        enum Operation {Call, DelegateCall}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    代码很简单,定义了一个枚举Operation,它有两个值,CallDelegateCall。这样我们就可以使用这个枚举了。注意:枚举定义没有可见性,是默认可见的。

    六 SelfAuthorized.sol

    顾名思义,自认证(其实是自调用验证),代码为:

    // SPDX-License-Identifier: LGPL-3.0-only
    pragma solidity >=0.7.0 <0.9.0;
    
    /// @title SelfAuthorized - authorizes current contract to perform actions
    /// @author Richard Meissner - 
    contract SelfAuthorized {
        function requireSelfCall() private view {
            require(msg.sender == address(this), "GS031");
        }
    
        modifier authorized() {
            // This is a function call as it minimized the bytecode size
            requireSelfCall();
            _;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    这个合约主要内容是定义了一个修饰符:authorized 它内部调用了一个函数用来验证是不是自己调用自己。
    自己调用自己的场景在日常开发中并不多见,主要用于多签钱包和DAO自管理,因为多签钱包所有操作都最终由多签钱包发出,所以msg.sender是多签钱包,又因为是自管理,是操作多签钱包内部的状态,所以to也是多签钱包,此时就是自调用。

    我们平常在开发智能合约时,比如合约内部调用本合约的一个public函数f(),此时如果然我们将f()修改为this.f(),那么它就变成了一个合约自调用了。当然其msg.sender变成合约自己了,所以大家就不要修改了。this.f()和正常的contract.function()调用是一样的(例如erc20.decimals()),this代表一个合约,f()代表调用函数。

    七 Executor.sol

    代码为:

    // SPDX-License-Identifier: LGPL-3.0-only
    pragma solidity >=0.7.0 <0.9.0;
    import "../common/Enum.sol";
    
    /// @title Executor - A contract that can execute transactions
    /// @author Richard Meissner - 
    contract Executor {
        function execute(
            address to,
            uint256 value,
            bytes memory data,
            Enum.Operation operation,
            uint256 txGas
        ) internal returns (bool success) {
            if (operation == Enum.Operation.DelegateCall) {
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0)
                }
            } else 
                // solhint-disable-next-line no-inline-assembly
                assembly {
                    success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0)
                }
            }
        }
    }
    
    • 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

    这个合约也很简单,定义了一个执行器,注意它执行两种操作,calldelegatecall。这两种操作的含义相信大家都很清楚,在内嵌汇编中使用它们的方法也是类似的,只是delegatecall并没有value参数。

    注意,这里并没有对返回值做处理,因此不知道是外部调用成功还是失败。它只是一个内部函数,结果由调用它的函数处理。

    需要提一下的是如果上面的execute函数的参数to为本合约地址的话,那么便是自调用的情况了,刚好和SelfAuthorized提到的自调用检查相吻合。

    话说枚举可以定义在合约外部供多个合约共同使用的,只是此时可能所属权不够清晰。本例中定义在Enum合约中,使用时需要使用全称:Enum.Operation.

    八 ModuleManager 详解

    我们回过头来再来看ModuleManager合约。它继承了SelfAuth及orizedExecutor,因此有一个自调用检查和执行器。

    我们跳过事件定义,直接来到常量SENTINEL_MODULES(哨兵模块)。
    当然这个值address(1)是自定义的,我们后面可以看到,这个代表一个模块链的开始,所以使用1是很合适的。当然你也可以使用address(666),没有任何问题的。

    mapping(address => address) internal modules; 这里定义了一个map,注意它的key和value都是地址类型,这样可以前一个的value当成后一个的key,形成一个链条。

    8.1 setupModules 函数

    setupModules 函数用来初始化,为什么这样说呢? 显然下面的代码只能执行一次(初始化)

    require(modules[SENTINEL_MODULES] == address(0), "GS100");
    modules[SENTINEL_MODULES] = SENTINEL_MODULES;
    
    • 1
    • 2

    然后当to不为零地址时,执行的是委托调用Enum.Operation.DelegateCall,,那么委托调用改变的是自己的状态变量,所以整体看来,这个函数是一个初始化函数(仅能执行一次)。

    8.2 enableModule 函数

    注意中写的比较清楚,将一个模块加入白名单,我们查看这个函数的逻辑可以发现,它将原哨兵模块(SENTINEL_MODULES) 对应的地址赋值给最新的模块,然后将哨兵模块的值设置为最新添加的模块。我们模拟一下连续调用本函数的结果

    enableModule 0x3
    enableModule 0x4
    enableModule 0x5
    
    • 1
    • 2
    • 3

    得到的结果为:

    modules[0x03] = 0x01
    modules[0x04] = 0x03;
    modules[0x05] = 0x04;
    modules[0x01] = 0x05;
    
    • 1
    • 2
    • 3
    • 4

    可以看到哨兵模块指向了最新的模块0x05,然后0x05指向0x04,0x04指向0x03,0x03指向了哨兵模块。这样形成了一条模块链,最新的模块在最后面。

    注意:本函数有 authorized 限定符,看来是内部管理函数。

    8.3 disableModule 函数

    这个和 enableModule 刚好相反,移除相关模块。就是把模块链断开去掉一个节点,然后再把断开的两边连接上。

    我们模拟一下调用本函数的结果:

    disableModule 0x1 0x5
    
    • 1

    最后得到的模块链为

    modules[0x03] = 0x01
    modules[0x04] = 0x03;
    modules[0x01] = 0x04;
    
    • 1
    • 2
    • 3

    可以看到,最后添加的0x5被移除了。

    注意,同样对应的,它也有authorized修饰符。

    8.4 execTransactionFromModule 函数

    看注释,它允许添加的模块调用它来执行外部调用/自定义操作,因此,上面添加的模块是在这里使用的。
    这个函数很简单,注意的是它执行调用后的返回值处理

    success = execute(to, value, data, operation, gasleft());
    if (success) emit ExecutionFromModuleSuccess(msg.sender);
    else emit ExecutionFromModuleFailure(msg.sender);
    
    • 1
    • 2
    • 3

    注意,它执行失败后只是触发了一个事件,并没有重置交易,是因为这个合约本身并没有改变任何状态,所以无需重置整个交易。

    它将对外调用执行的结果bool success并进一步返回,因此调用模块需要处理这个调用失败(success为false)的情况。

    8.5 execTransactionFromModuleReturnData 函数

    同execTransactionFromModule 函数,但是多返回了一个返回值。
    外部调用很简单,同execTransactionFromModule,只是没有触发事件。
    我们看一下接下来的内嵌汇编:

    assembly {
         // Load free memory location
         let ptr := mload(0x40)
         // We allocate memory for the return data by setting the free memory location to
         // current free memory location + data size + 32 bytes for data size value
         mstore(0x40, add(ptr, add(returndatasize(), 0x20)))
         // Store the size
         mstore(ptr, returndatasize())
         // Store the data
         returndatacopy(add(ptr, 0x20), 0, returndatasize())
         // Point the return data to the correct memory location
         returnData := ptr
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    1. 得到自由内存指针地址(存在0x40)
    2. 将自由内存指针地址重新设定在当前地址+ 数据大小+ 长度前缀 的地址,多出来的空间(长度前缀 + 数据大小 )为是了构造 返回值returnData
    3. 写入长度前缀,从旧自由内存指针地址开始写入一个word(32字节),值为返回的数据大小。这里的返回数据哪来的呢?因为本函数调用了execTransactionFromModule函数,虽然execTransactionFromModule函数是个public函数,但是我们这里却只是内部跳转,并没有涉及到消息调用,因此你可以认为是execTransactionFromModule的代码直接复制了过来。而execTransactionFromModule又调用了execute,这同样是一个内部调用,因此返回值来源于execute的执行结果。 注意,这里只有消息调用(合约之间或者EOA与合约之间)才会有returndata,它并不是普通函数之间相互调用的返回值(函数返回值是Solidity语言)。
    4. 将returndata的内容复制到内存中,returndatacopy操作码我们已经多次见到,为什么从add(ptr, 0x20)开始呢,因为ptr开始的32字节我们在上一步存入了长度前缀。
    5. 最后设定返回Solidity返回数据returnData的内存地址,从prt开始分别为它的长度前缀和实际数据

    8.6 isModuleEnabled 函数

    很简单,判断模块是否白名单

    8.7 getModulesPaginated 函数

    分页获取白名单模块,返回一个白名单数组。这里为什么要采用分页呢?因为理论上,可以注册个无数模块,因此返回的数据可以无限大。然而却是有gasLimit的,所以数组过大会导致调用失败,因此采用了分页模式,可以调整返回的数组大小和起始位置。

    这里view类型的函数同样也会受到gas限制,同样也会gas超限。

    本函数的逻辑使用了一个while从后向前循环模块链,取出分页大小的模块。如果不足(没有注册或者为哨兵模块),则立刻终止。

    一个比较实用的技巧是最后的内嵌汇编,用来改变返回数组的大小。

     // Set correct size of returned array
    // solhint-disable-next-line no-inline-assembly
    assembly {
        mstore(array, moduleCount)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    我们知道,数组在汇编中的内存layout也是值为内存地址,开始32字节存的是数组长度,后面再接数据内容。

    由于我们分页获取的数组可能未填充满,比如取10个,我们只有4个。因此后面6个元素为空的。此时我们返回空元素的话会浪费空间。因此,可以直接修改返回的数据大小为4,这里就示例了一种直接修改方法。

    直接将数组地址开始的32字节(存储数组大小)赋值为实际数组大小。
    这是一个很实用的技巧,我们平常在遇到数组不能填充满时也可以使用此技巧。

    修改大小后本来返回10个元素的数组变成了返回4个元素的数组,而有效内容是相同的

  • 相关阅读:
    配置HBase和zookeeper
    ssm+vue的4S店预约保养管理系统(有报告)。Javaee项目,ssm vue前后端分离项目。
    使用 SSL 实现 2 路身份验证的最简单方法 - 提供源代码的示例
    CPU三级缓存原理与优化
    分布式系统大势所趋,银行运维如何与时俱进?
    RTP Tools
    为什么每个人都在谈论Art Gobblers
    通过VN1630/VN7640的I/O功能来确认电源设置电压的时间精确度
    警惕Faust勒索病毒的最新变种faust,您需要知道的预防和恢复方法。
    几行cmd命令,轻松将java文件打包成jar文件
  • 原文地址:https://blog.csdn.net/weixin_39430411/article/details/127134952