GnosisSafeProxyFactory合约学习

这篇具有很好参考价值的文章主要介绍了GnosisSafeProxyFactory合约学习。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

所谓Factory,顾名思义,就是能够快捷创建某类合约的合约,通过合约创建合约的方式而非直接部署一个新的合约。因此GnosisSafeProxyFactory 就是用来快速创建GnosisSafeProxy的合约。

1.1 GnosisSafeProxyFactory 源码

GnosisSafeProxyFactory 的源码并不复杂,核心为 deployProxyWithNonce函数,用来创建一个GnosisSafeProxy,其它的只是一些辅助函数和包装函数而已。

// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;

import "./GnosisSafeProxy.sol";
import "./IProxyCreationCallback.sol";

/// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
/// @author Stefan George - <stefan@gnosis.pm>
contract GnosisSafeProxyFactory {
    event ProxyCreation(GnosisSafeProxy proxy, address singleton);

    /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
    /// @param singleton Address of singleton contract.
    /// @param data Payload for message call sent to new proxy contract.
    function createProxy(address singleton, bytes memory data) public returns (GnosisSafeProxy proxy) {
        proxy = new GnosisSafeProxy(singleton);
        if (data.length > 0)
            // solhint-disable-next-line no-inline-assembly
            assembly {
                if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) {
                    revert(0, 0)
                }
            }
        emit ProxyCreation(proxy, singleton);
    }

    /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed.
    function proxyRuntimeCode() public pure returns (bytes memory) {
        return type(GnosisSafeProxy).runtimeCode;
    }

    /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address.
    function proxyCreationCode() public pure returns (bytes memory) {
        return type(GnosisSafeProxy).creationCode;
    }

    /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer.
    ///      This method is only meant as an utility to be called from other methods
    /// @param _singleton Address of singleton contract.
    /// @param initializer Payload for message call sent to new proxy contract.
    /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
    function deployProxyWithNonce(
        address _singleton,
        bytes memory initializer,
        uint256 saltNonce
    ) internal returns (GnosisSafeProxy proxy) {
        // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it
        bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
        bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton)));
        // solhint-disable-next-line no-inline-assembly
        assembly {
            proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
        }
        require(address(proxy) != address(0), "Create2 call failed");
    }

    /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction.
    /// @param _singleton Address of singleton contract.
    /// @param initializer Payload for message call sent to new proxy contract.
    /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
    function createProxyWithNonce(
        address _singleton,
        bytes memory initializer,
        uint256 saltNonce
    ) public returns (GnosisSafeProxy proxy) {
        proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);
        if (initializer.length > 0)
            // solhint-disable-next-line no-inline-assembly
            assembly {
                if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) {
                    revert(0, 0)
                }
            }
        emit ProxyCreation(proxy, _singleton);
    }

    /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction
    /// @param _singleton Address of singleton contract.
    /// @param initializer Payload for message call sent to new proxy contract.
    /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
    /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized.
    function createProxyWithCallback(
        address _singleton,
        bytes memory initializer,
        uint256 saltNonce,
        IProxyCreationCallback callback
    ) public returns (GnosisSafeProxy proxy) {
        uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback)));
        proxy = createProxyWithNonce(_singleton, initializer, saltNonceWithCallback);
        if (address(callback) != address(0)) callback.proxyCreated(proxy, _singleton, initializer, saltNonce);
    }

    /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce`
    ///      This method is only meant for address calculation purpose when you use an initializer that would revert,
    ///      therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory.
    /// @param _singleton Address of singleton contract.
    /// @param initializer Payload for message call sent to new proxy contract.
    /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract.
    function calculateCreateProxyWithNonceAddress(
        address _singleton,
        bytes calldata initializer,
        uint256 saltNonce
    ) external returns (GnosisSafeProxy proxy) {
        proxy = deployProxyWithNonce(_singleton, initializer, saltNonce);
        revert(string(abi.encodePacked(proxy)));
    }
}

1.2 源码学习

我们跳过导入语句和pragma声明部分,由于本合约未继承任何合约,我们直接从函数createProxy开始学习。

  • createProxy 函数。 用来创建一个代理合约。 它使用了Solidity 的 new关键字进行了创建,同时指定构造器参数为singleton。第二个参数data一般用于创建或者升级后进行初始化,我们着重来看这个部分。

    if (data.length > 0)
      // solhint-disable-next-line no-inline-assembly
      assembly {
          if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) {
              revert(0, 0)
          }
      }
    

    这里if语句未使用花括号,虽然是个人风格的问题,但是还是推荐使用花括号。

    如果data的长度为0,那么data就是用来进行初始化的payload(包含了函数选择器和参数编码)。这时需要调用proxy合约进行初始化。

    这里直接调用了内嵌汇编的call函数进行proxy合约的调用,参数分别为剩余gas,proxy合约地址,发送的eth数量,内存中起始位置,调用数据的大小,output的位置 和大小。

    这里需要注意的是data的数据类型为bytes memory,为动态类型。因此它在solidity中的直接值其实代表的是包含了长度前缀的起始地址,因为长度前缀为一个word,所以add(data, 0x20) 得到了真正payload的起始地址(去除了长度前缀)。mload(data) 则是读取长度前缀的值,也就是该payload的大小。

    最后将执行的结果和0比较(0代表失败),如果失败了,revert,且无提示信息。

    函数的最后将创建的proxy地址及其实现合约地址通过事件的形式分发出去,方便客户端监听。

    这里还有一个细节,就是函数的可见性为public,通常来讲,一般为external(只要内部没有调用),这样部分数据类型更能节省gas。但是external 可见性的函数会导致其bytes类型参数datacalldata类型,也就是不存在memory中,这样还需要额外的操作将data从calldata复制到memory中,所以这里使用publicmemory反而更能节省gas

  • proxyRuntimeCode 函数,用来返回proxy合约的运行时代码,注释中提到可以用来检查proxy合约是否部署,这里暂时不是很明白检查的意义。

  • proxyCreationCode函数,用来返回proxy合约的创建时代码,注意,创建时代码运行后会得到运行时代码。部署合约时使用的是创建时代码,该代码执行后产生的是运行时代码,也就是合约的线上代码。注释中提到可以用来计算地址,这里主要是create2时使用。

  • deployProxyWithNonce 函数,用一个saltNonce来控制产生的proxy合约的地址。本函数的核心是使用了create2函数而非new来创建合约,create2中有一个可自定义的salt,从而可以控制生成的合约地址。

    根据注释,不同的initializer需要导致不行的地址,因此把它和输入参数中的saltNonce一起编码作来salt。这里,一般我们创建proxyinitializer是固定的,而通过不断改变saltNonce的值来得到不同的地址。那么这里问题来了,如果initializer_singletonsaltNonce都相同,那我们能在相同的地址部署两次么?我们晚点针对这个问题进行测试。

    这里拓展一下,为什么要使用create2呢?Solidity中有一段话:

    When creating a contract, the address of the contract is computed from the address of the creating contract and a counter that is increased with each contract creation.

    If you specify the option salt (a bytes32 value), then contract creation will use a different mechanism to come up with the address of the new contract:

    意思是,如果你直接创建合约,例如通过new或者 直接部署,生成的合约地址和创建它的地址及一个不断增加的计数器相关,例如外部账号EOA的nonce。当你定义一个salt时,它使用不同的机制来计算新合约的地址。

    create2的定义为:

    create2(v, p, n, s)
    
    create new contract with code mem[p…(p+n)) at address keccak256(0xff . this . s . keccak256(mem[p…(p+n))) and send v wei and return the new address, where 0xff is a 1 byte value, this is the current contract’s address as a 20 byte value and s is a big-endian 256-bit value; returns 0 on error
    

    我们对照实际代码:

    // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it
      bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce));
      bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_singleton)));
      // solhint-disable-next-line no-inline-assembly
      assembly {
          proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt)
      }
      require(address(proxy) != address(0), "Create2 call failed");
    

    这里会得到 v为0(也就是不给新合约ETH),p是内存起始位置,所以为add(0x20, deploymentData),这里为什么要加add 0x20,上面也提到过,是要去除长度前缀。 当然大小就是 mload(deploymentData)了,同deployProxyWithNonce中一样,这里的salt 是将initializersaltNonce组合得到的一个值。注释中提到,哈希比直接连接便宜,这里我想是因为initializerbytes类型,不知道具体长度大小,所以连接操作不方便。而哈希之后就是固定长度的bytes32,所以操作更便宜一些。

    注意,这里keccak256(initializer)initializer的值并不是它在内存中的起始位置 ,它就是实际的bytes数据,只有在操作内存时才代表其内存地址(一般在内嵌汇编中使用)。

​ 这里可以看到最后计算的地址和本合约地址(固定的),deploymentData 及 salt 相关,而 deploymentData 又和合约的创建时代码(固定的)及 构造器参数_singleton 相关。所以最终,我们任意改变_singletoninitializer或者saltNonce的值,就可以得到一个不同的proxy地址,当然这个地址也可以线下计算出来。

​ 从上面的代码中还可以看出来,我们构造器参数其实是附在创建时代码后面的,这和不使用create2例如正常外部账号部署合约时是一致的。

​ 最后一点要注意的是,正如注释中所说,它并没有调用新创建proxy合约的initializer,因此它是功能不完整的,只是创建功能的抽象,所以它只是个内部函数,供其它函数调用 。

  • createProxyWithNonce 说曹操,曹操到。弄清楚了上面的函数,这个函数就很简单的。第一步,调用上面的内部函数创建proxy合约,第二步,如果initializer不为空,则调用proxyinitializer对应的函数进行初始化(注意并不是调用initializer函数,initializer的含义是指初始化一次。具体调用哪个函数要看initializer前8位的函数选择器。

    这里的汇编使用就很简单了,和前面的用法 一样,在操作内存相关时,记住initializer代表了包含长度前缀的payload的起始地址就可以了。

  • createProxyWithCallback函数,在上面createProxyWithNonce函数的基础上加了一个回调函数用来通知其它合约创建并初始化成功了。实际使用的场景并不多, 主要用于调用合约和回调合约不是同一个合约的场景,类似ERC20中的ApproveAndCall

  • calculateCreateProxyWithNonceAddress 函数,看注释是用来得到一个新的proxy合约的地址,它仅用于计算目的。当然我们可以线下计算,线下计算只是让这个Factory合约更完善。不过有一点却是要注意的,该函数不是一个view函数,它的结果是使用revert返回的,并不是那么容易直接获取的。并且如果直接调用,非view函数就是发送交易,哪怕是revert并未实际改写数据,也是要花费gas费用的,我们需要一点技巧来避免此项开销,就好比UniswapV3的价格查询合约Quoter一样。大家其实可以参考Uniswap V3: Quoter合约的,地址为:https://etherscan.io/address/0xb27308f9f90d607463bb33ea1bebb41c27ce5ab6#code

    当然,最好的办法是线下计算,利用上面提到的create2的定义,参考UniswapV2中Pair地址的计算方法:

    // calculates the CREATE2 address for a pair without making any external calls
    function pairFor(address factory, address tokenA, address tokenB) internal pure returns (address pair) {
        (address token0, address token1) = sortTokens(tokenA, tokenB);
        pair = address(uint(keccak256(abi.encodePacked(
                hex'ff',
                factory,
                keccak256(abi.encodePacked(token0, token1)),
                hex'96e8ac4277198ff8b6f785478aa9a39f403cb768dd02cbee326c3e7da348845f' // init code hash
            ))));
    }
    

    这里的initCode 如果使用hardhat可以通过如下方式获取:

    let UniswapV2Pair = await ethers.getContractFactory("UniswapV2Pair");
    let InitCode = ethers.utils.keccak256(UniswapV2Pair.bytecode)
    

    更为直接的是直接参考Solidity官方文档示例:

    // SPDX-License-Identifier: GPL-3.0
    pragma solidity >=0.7.0 <0.9.0;
    contract D {
        uint public x;
        constructor(uint a) {
            x = a;
        }
    }
    
    contract C {
        function createDSalted(bytes32 salt, uint arg) public {
            // This complicated expression just tells you how the address
            // can be pre-computed. It is just there for illustration.
            // You actually only need ``new D{salt: salt}(arg)``.
            address predictedAddress = address(uint160(uint(keccak256(abi.encodePacked(
                bytes1(0xff),
                address(this),
                salt,
                keccak256(abi.encodePacked(
                    type(D).creationCode,
                    arg
                ))
            )))));
    
            D d = new D{salt: salt}(arg);
            require(address(d) == predictedAddress);
        }
    }
    

    这里可以看到,使用new D{salt: salt}(arg) 可以达到和create2相同的效果,但Gnosis Safe为什么选择了create2函数呢,我想可能是使用内嵌汇编更节省gas

    在我们自己的实际应用中,使用new D{salt: salt}(arg)即可。

    这里我们可以实际测试一下,使用remix.ethereum.org在线快速编辑测试,使用相同的saltarg调用createDSalted两次。结果为:第一次会创建一个合约,第二次会出错重置(因为你不能在同一地址创建合约两次,除非原合约自杀了)。

    通过Remix我们也可以看到,合约D的x状态变量的值正是我们调用createDSaltedarg的值,这是一致的。文章来源地址https://www.toymoban.com/news/detail-788028.html

到了这里,关于GnosisSafeProxyFactory合约学习的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • web3Js(干货)(多签的流程原理)看完这一篇就懂了(波场网络-请勿用于除学习外其他用途)

    连接波场网络: 其中APIKEY可以在官网获取; 可以使用tronWeb.isConnected()判断是否连接成功 创建离线波场地址: 该地址未激活,如果需要激活, 通常需要一定数量的 TRX(TRON 的本地代币)用于支付激活费用; 等待区块确定 就可以查看激活信息; 创建随机助记词与私钥: 如何让

    2024年02月04日
    浏览(63)
  • Web3知识科普:什么是多签钱包?

    我们日常使用的HD(身份)钱包或多链钱包,通常只可以通过一个公钥进行存储。这意味着,无论是谁,只要获悉了与该公钥匹配的私钥,就能够支配该公钥链上所持有的资产。所以为了解决密钥的问题,多重签名技术应运而生。 今天我们就来讲讲多重签名机制的含义、作用

    2024年02月11日
    浏览(32)
  • 区块链智能合约开发学习

    最近正在肝区块链知识学习,入手学习智能合约的开发,由于网上资料实在是太少了,好不容易东拼西凑完成了智能合约的开发、编译、部署、web3js调用(网页页面)和web3j调用(java调用),赶紧趁热把重点提炼出来。 先上图,是我最近学习知识点的一个概括总结,此外还包

    2023年04月18日
    浏览(30)
  • Solidity,智能合约的学习(1)

    Solidity是一种智能合约高级语言,运行在Ethereum虚拟机(EVM)上,Solidity是面向对象的高级语言,用于实现智能合约。智能合约是管理以太坊状态内的账户行为的程序。 BitPen认为,作为Web3的链上玩家,那么能够看懂Solidity代码将会是一项必备的技能,因为大多区块链项目都是在

    2024年01月23日
    浏览(43)
  • 智能合约学习笔记——僵尸工厂(一)

    根据CryptoZombies学solidity Lesson 1 搭建僵尸工厂 生成僵尸的工厂,首先建立一个批量生成僵尸的合约 javascript实现 当合约完成之后,就需要写一段 JavaScript 前端代码来调用这个合约。 以太坊有一个 JavaScript 库,名为Web3.js。 在后面的课程里,我们会进一步地教你如何安装一个合

    2024年02月02日
    浏览(20)
  • 智能合约学习笔记--随机数攻击复现

    智能合约中的随机数 在智能合约中随机数经常被用到,但是我们知道,这些生成的随机数都是伪随机数,当生成的随机数不是足够安全的时候就会产生漏洞。随机数攻击,就是针对智能合约的随机数生成算法进行攻击,预测智能合约的随机数。 目前来说常见的随机数获取有

    2023年04月12日
    浏览(34)
  • solidity学习-如何在智能合约中打印Log日志

    在写合约的过程中经常会遇到一些错误。这个时候想要查看合约运行过程中的一些数据,那么就可以用以下方法: 在合约中创建一个Event,起名为Log 在想要打印日志的地方调用事件 emit Log(...),就可以查看运行过程中的数据了 如下图: 点击deploy,在控制台就可以查看打印出

    2024年02月11日
    浏览(33)
  • 【】BSC链只涨不跌的合约学习【完整源码】

    要想做到BSC链如何做到只涨不跌,那首先我们就来了解下薄饼交易所(pancakeswap)价格产生原理; 市场价格 =池子里DAI的数量/池子里BNB的数量(P市场=X/Y)。假设市场数量趋近于无穷大,兑换价格无限趋近于X/Y 兑换价格 =支付DAI的数量/获得BNB的数量(P兑换=△x /△y) 总结: 池

    2024年02月16日
    浏览(27)
  • 学区块链智能合约?来自培训学校内部的学习计划

    大家好,我是赛联区块链教育张群。每天都有来问的,张老师我钱不够,想自己学,怎么学?有学习大纲吗?好吧!谁让张老师是个信仰者和布道者呢。计划来了,拿去不谢!假如你每天能够投入2个小时的学习时间,我们可以将智能合约开发的学习设定为渐进形式。整个学习

    2024年02月22日
    浏览(28)
  • 智能合约学习笔记一 、——{Solidity语言详解——(1—2)小练习}

    1.根据提示,在指定位置写出编译版本,要求使用^符号,版本要求在0.6.0及以上。 2.根据提示,在指定位置写出所定义的合约名称。 3.为了查看程序的效果,我们使用在线 Solidity 开发工具 Remix IDE 编译和运行 Solidity 程序。中文在线版:在浏览器打开下方链接: Remix - 中文版

    2024年02月02日
    浏览(32)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包