GnosisSafe

GitHub 合约代码库 (https://github.com/safe-global/safe-contracts)
官网 https://gnosis-safe.io/
GnosisSafe 是一个多签钱包。(Gnosis Safe is the most trusted platform to manage digital assets.)
代码结构

1652926849(1).png

主要合约是 GnosisSafe,GnosisSafeL2 继承至GnosisSafe,与之比较就是加了两个日志

 event SafeMultiSigTransaction(
        address to,
        uint256 value,
        bytes data,
        Enum.Operation operation,
        uint256 safeTxGas,
        uint256 baseGas,
        uint256 gasPrice,
        address gasToken,
        address payable refundReceiver,
        bytes signatures,
        // We combine nonce, sender and threshold into one to avoid stack too deep
        // Dev note: additionalInfo should not contain `bytes`, as this complicates decoding
        bytes additionalInfo
    );

    event SafeModuleTransaction(address module, address to, uint256 value, bytes data, Enum.Operation operation);

分别在方法 execTransaction 和 execTransactionFromModule 弹出,应该是handle链上数据的需要添加的 对合约功能没有影响。
目录 accessor 访问器
此目录下只有一份合约 SimulateTxAccessor 继承至 Executor 模拟访问器

contract SimulateTxAccessor is Executor {
    address private immutable accessorSingleton;

    constructor() {
        accessorSingleton = address(this);
    }
     
   //只能代理调用 执行的合约不能是此合约 
    modifier onlyDelegateCall() {
        require(address(this) != accessorSingleton, "SimulateTxAccessor should only be called via delegatecall");
        _;
    }

   //模拟执行
    function simulate(
        address to,
        uint256 value,
        bytes calldata data,
        Enum.Operation operation
    )
        external
        onlyDelegateCall()
        returns (
            uint256 estimate,
            bool success,
            bytes memory returnData
        )
    {
        uint256 startGas = gasleft();
        success = execute(to, value, data, operation, gasleft());
        estimate = startGas - gasleft();
        // 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
        }
    }
}

此合约应该是测试用的与合约主线没有关联。
external目录下面也只有一份合约 GnosisSafeMath

interface目录 ERC721TokenReceiver,ERC777TokensRecipient,ERC1155TokenReceiver,IERC165,ISignatureValidator,ViewStorageAccessible。

libraries目录 创建,执行GnosisSafe的辅助功能性合约 CreateCall 利用creationCode创建合约,GnosisSafeStorage GnosisSafe的数据存储布局,由于GnosisSafe继承至多份合约所以数据存储布局看起来没有那么清晰,GnosisSafeStorage相当于做一个清晰的展示。MultiSend,MultiSendCallOnly,多笔交易同时调用,SignMessageLib。

handler目录回调的处理,这部分是可以自定义的。 HandlerContext 提取调用上下文,DefaultCallbackHandler,CompatibilityFallbackHandler 三个文件。

examples里面又分为两个目录一个是libraries -> Migration 一个升级的工具或者示例。另一个是gards(卫士)里面有三个合约 DebugTransactionGuard,DelegateCallTransactionGuard,ReentrancyTransactionGuard 不同功能的gards示例 ,这部分也可以自己定义。
proxies
-> 1.GnosisSafeProxy GnosisSafe的代理合约
->2.IProxyCreationCallback 一个创建多签钱包后的回调接口,具体实现有用户在创建时自己填入
-> 3.GnosisSafeProxyFactory 代理工厂 创建GnosisSafeProxy ,创建多签钱包。
GnosisSafe 是多签钱包的具体实现(逻辑) GnosisSafeProxy 是多签钱包的代理(存储)
剩余目录合约基本上和GnosisSafe 都存在继承关系

他们的继承关系图如下
image.png

方法调用如
1652938685(1).png
contract GnosisSafe is
    EtherPaymentFallback,
    Singleton,
    ModuleManager,
    OwnerManager,
    SignatureDecoder,
    SecuredTokenTransfer,
    ISignatureValidatorConstants,
    FallbackManager,
    StorageAccessible,
    GuardManager

EtherPaymentFallback -> EtherPaymentFallback 收到 eth时弹出 SafeReceived 日志
Singleton -> 一个私有变量singleton 对应 GnosisSafeProxy 的singleton 相当于一个占位符
SelfAuthorized -> 主要限制一些函数只能自己调用目的是限制只能多签验证后才能调用。
Executor -> 一个执行方法 execute
ModuleManager -> ModuleManager is SelfAuthorized, Executor,模块管理器可以添加和删除模块 模块以链表形式保存。模块的作用是被添加的模块合约可以直接调用GnosisSafe去执行一些操作,而无需验证多签

 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
        }
    }

OwnerManager -> OwnerManager is SelfAuthorized 管理多签成员与前面阈值。成员的保存依然使用的链表。
SignatureDecoder -> signatureSplit 签名解码
SecuredTokenTransfer -> SecuredTokenTransfer 安全转账
ISignatureValidatorConstants -> 签名验证通过后返回验证函数的签名
ISignatureValidator -> ISignatureValidator is ISignatureValidatorConstants -> isValidSignature 验证签名的合法性实现在 GnosisSafe
ISignatureValidator
FallbackManager -> FallbackManager is SelfAuthorized 管理fallback()函数执行时具体的实现合约 -> setFallbackHandler
StorageAccessible
1 -> getStorageAt 获取合约的指定卡槽的指定长度的内容。
2 -> simulateAndRevert 模拟执行调用一个合约获取返回值。但是链状态回退(不发生改变)。返回值放入revert里面。有点像uniswap v3的预兑换,获取输入对应的输出。
Guard - > Guard is IERC165 两个方法 checkTransaction 多签后方法执行前调用 checkAfterExecution 多签后方法执行后调用
GuardManager -> GuardManager is SelfAuthorized 安全卫士管理器,多签可以自己设定这个卫士合约(setGuard)。上面有提过示例。
最后就是 GnosisSafe
首先 签名是使用 EIP712 签名标志

DOMAIN_SEPARATOR_TYPEHASH =  keccak256( "EIP712Domain(uint256 chainId,address verifyingContract)" );
SAFE_TX_TYPEHASH  =  keccak256("SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,addressrefundReceiver,uint256 nonce)" );
   这是实现合约,创建的多签钱包是代理合约 也就是意味着创建多签钱包不会执行。
    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;
    }

初始化

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);
      // 设置fallback的实现合约
        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);
    }

执行多签事务

  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 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);
    }

//检查签名
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) { //链上手动approve
                // 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 { //正常的eip712签名
                // 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;
        }
    }

预先计算执行事务需要的gas 也是执行后revert 把结果放在revert里面。

    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)));
    }

根据他的测试用例他解析revert的内容的步骤 

1.部署一个 decoder 合约

 contract Decoder {
                function decode(address to, bytes memory data) public returns (bytes memory) {
                    (bool success, bytes memory data) = to.call(data);
                    require(!success, "Shit happens");
                    return data;
                }
            } 
2 ether.js解析
           const { safe, decoder } = await setupTests()
            const data = safe.interface.encodeFunctionData("requiredTxGas", [safe.address, 0, "0x", 0])
            const result = await decoder.callStatic.decode(safe.address, data)
            BigNumber.from("0x" + result.slice(result.length - 32)).toNumber()

在GnosisSafeProxyFactory的calculateCreateProxyWithNonceAddress中也使用了把结果放在revert里面 前端调用时用staiccall。

在create2里面creationCode + args,后面的args好像只能是 uint256类型的。

你可能感兴趣的:(GnosisSafe)