{"content":{"title":"Sudoswap是如何节省gas的","body":"# Sudoswap是如何节省gas的\r\n## Background\r\nsudoswap的一大特色就是非常[节省gas](https:\/\/learnblockchain.cn\/article\/567)，在它的twitter上也和seaport消耗的gas进行了对比，所以这里就想学习下sudoswap是如何节省gas的。\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/XeHgPnwc63148aa4510ee.png)\r\n### 创建pair合约\r\n通过factory创建pair合约的主要操作都是在LSSVMPairCloner这个lib里完成，可以看到这里有着非常多的assembly代码甚至是手写的Opcode。\r\n比如其`cloneETHPair`方法，里面就充斥了一堆手写的[opcode](https:\/\/github.com\/sudoswap\/lssvm\/blob\/c8b228b2a3b51664c433f487fb76db42d3509dd4\/src\/lib\/LSSVMPairCloner.sol#L21-L101)。\r\n\r\n![image.png](https:\/\/img.learnblockchain.cn\/attachments\/2022\/09\/ZS8YA9bH63148acc928ec.png)\r\n1. 明白这个函数在做什么：\r\n这个函数实际上就是完成了EIP-1167，最小化proxy合约的创建，具体可参考[这个链接](https:\/\/learnblockchain.cn\/article\/2663)。与标准的创建EIP-1167合约不同的一点在于，他将函数的参数，如`factory,bondingCurve,nft,poolType`这四个参数全部硬编码到了部署得到的proxy合约代码里面，如这个[地址所示](https:\/\/etherscan.io\/address\/0xb79f94e6f2460d3cb8b8970de4aa873f78589e34#code)：\r\n```js\r\n0x3d3d3d3d363d3d37603d6035363936603d013d73cd80c916b1194beb48abf007d0b79a7238436d565af43d3d93803e603357fd5bf3b16c1342e617a5b6e4b631eb114483fdb289c0a4432f962d8209781da23fb37b6b59ee15de7d984164ad353bc90a04361c4810ae7b3701f3beb48d7e02\r\n```\r\n其中，implement合约地址是：`0xCd80C916B1194beB48aBF007D0b79a7238436D56`。\r\n携带的4个参数分别是：\r\n```js\r\nfactory: 0xb16c1342e617a5b6e4b631eb114483fdb289c0a4\r\nbondingCurve: 0x432f962d8209781da23fb37b6b59ee15de7d9841\r\nnft:0x64ad353bc90a04361c4810ae7b3701f3beb48d7e\r\npoolType: 0x02\r\n```\r\n2. 然后当有任何的调用到这个proxy上时，这个proxy会直接将所有的函数调用通过delegatecall的方式，调用到其实现合约上，并且与EIP-1167不同的一点是，其调用delegatecall的时候，会在calldata后面硬编码上proxy创建时带的这4个参数:`factory,bondingCurve,nft,poolType`\r\n即，比如最简单的调用`swapTokenForAnyNFTs`方法，其调用的calldata为：\r\n```js\r\n首先是用户直接调用proxy的swapTokenForAnyNFTs方法：\r\nproxy.call(abi.encodeWithSelector(proxy.swapTokenForAnyNFTs.selector, numNFTs, maxExpectedTokenInput, nftRecipient, isRouter, routerCaller))\r\n然后是proxy对该方法调用进行delegatecall转发：\r\nimpl.delegatecall(\r\n    abi.encodePacked(\r\n        abi.encodeWithSelector(\r\n            proxy.swapTokenForAnyNFTs.selector, \r\n            numNFTs, \r\n            maxExpectedTokenInput, \r\n            nftRecipient, \r\n            isRouter, \r\n            routerCaller),\r\n        abi.encodePacked(\r\n            factory,\r\n            bondingCurve,\r\n            nft,\r\n            poolType\r\n        )))\r\n```\r\n这样做有很多好处，一个好处就是可以极大的省gas。\r\n3. 现在我们需要来分析一下，我们自己应该如何才能实现同样的功能，有哪些值得学习的地方。\r\n可以看到proxy合约要实现的功能主要有两个：\r\n第一：通过delegatecall对函数调用进行转发，并将结果返回给我。\r\n第二：函数调用转发时，带上4个参数。这4个参数需要硬编码到proxy合约里。\r\n为了完成这个功能，我们需要用到[etk](https:\/\/github.com\/quilt\/etk)这个工具来辅助我们手写opcode。\r\n首先是op.etk文件：\r\n```js\r\ninitstart:\r\n    push1 runtimeEnd-runtimeStart       #runtimesize\r\n    returndatasize                      #0 runtimesize\r\n    dup2                                #runtimesize 0 runtimesize\r\n    push1 runtimeStart-initstart        #runtimeoffset runtimesize 0 runtimesize\r\n    returndatasize                      #0 runtimeoffset runtimesize 0 runtimesize\r\n    codecopy                            #0 runtimesize\r\n    return\r\ninitend:\r\nruntimeStart:\r\n    returndatasize                      #0\r\n    returndatasize                      #0 0\r\n    returndatasize                      #0 0 0\r\n    returndatasize                      #0 0 0 0\r\n    calldatasize                        #calldatasize 0 0 0 0\r\n    returndatasize                      #0 calldatasize 0 0 0 0\r\n    returndatasize                      #0 0 calldatasize 0 0 0 0\r\n    calldatacopy                        #0 0 0 0 \r\n    push1 extradataEnd-extradataStart   #extradatasize 0 0 0 0\r\n    push1 extradataStart-runtimeStart   #extradataoffset extradatasize 0 0 0 0\r\n    calldatasize                        #calldatasize extradataoffset extradatasize 0 0 0 0\r\n    codecopy                            #0 0 0 0\r\n    push1 extradataEnd-extradataStart   #extradatasize 0 0 0 0\r\n    calldatasize                        #calldatasize extradatasize 0 0  0 0\r\n    add                                 #inputSize 0 0 0 0\r\n    returndatasize                      #0 inputSize 0 0 0 0\r\n    push20 0x0000000000000000000000000000000000000000                       #addr 0 inputSize 0 0 0 0\r\n    gas                                 #gas addr 0 inputSize 0 0 0 0\r\n    delegatecall                        #success 0 0\r\n    returndatasize                      #returndatasize success 0 0\r\n    swap3                               #0 success 0 returndatasize\r\n    dup4                                #returndatasize 0 success 0 returndatasize\r\n    swap1                               #0 returndatasize success 0 returndatasize\r\n    dup1                                #0 0 returndatasize success 0 returndatasize\r\n    returndatacopy                      #success 0 returndatasize\r\n    push1 labelreturn                   #labelreturn success 0 returndatasize\r\n    jumpi                               #0 returndatasize\r\n    revert                              #\r\nlabelreturn:    \r\n    jumpdest                            #0 returndatasize\r\n    return                              #\r\n\r\n\r\n\r\nextradataStart:\r\n%include_hex(\"extra.etk\")\r\nextradataEnd:\r\nruntimeEnd:\r\n```\r\n然后是extra.etk文件，这里面放着的是需要硬编码到proxy合约里的字节码：\r\n```js\r\nb16c1342e617a5b6e4b631eb114483fdb289c0a4432f962d8209781da23fb37b6b59ee15de7d984164ad353bc90a04361c4810ae7b3701f3beb48d7e02 \r\n```\r\n通过etk工具对op.etk文件进行编码，得到的字节码段为：\r\n```js\r\n60733d8160093d39f33d3d3d3d363d3d37603d60363639603d36013d7300000000000000000000000000000000000000005af43d928390803e603d57fd5bf3b16c1342e617a5b6e4b631eb114483fdb289c0a4432f962d8209781da23fb37b6b59ee15de7d984164ad353bc90a04361c4810ae7b3701f3beb48d7e02\r\n```\r\n那么我们为什么需要花费这么大的力气来手写这些编码呢？最大的一个好处是可以利用etk工具帮我们计算出对应的offset和length的数据，否则需要自己来手动计算，就很容易出错。并且我们可以在`evm.codes`这个网站上在线调试一下，我们手写的这部分字节码是不是正确的。\r\n![](2022-09-04-19-00-24.png)\r\n当我们拿到需要的字节码之后，我们就可以开始编写相对应的assembly部分了。编写assembly的核心是把得到的字节码当成一串字符，然后存储到memory里，最后通过create方法创建出来。\r\n编写assembly之前，通过字节码，要算出对应的长度：\r\n```python\r\n>>> s= \"60733d8160093d39f33d3d3d3d363d3d37603d60363639603d36013d7300000000000000000000000000000000000000005af43d928390803e603d57fd5bf3b16c1342e617a5b6e4b631eb114483fdb289c0a4432f962d8209781da23fb37b6b59ee15de7d984164ad353bc90a04361c4810ae7b3701f3beb48d7e02\"\r\n>>> s[:0x1d*2]\r\n'60733d8160093d39f33d3d3d3d363d3d37603d60363639603d36013d73'\r\n>>> s[0x1d*2:0x1d*2+40]\r\n'0000000000000000000000000000000000000000'\r\n>>> s[0x31*2:0x3f*2]\r\n'5af43d928390803e603d57fd5bf3'\r\n>>> s[0x3f*2:0x53*2]\r\n'b16c1342e617a5b6e4b631eb114483fdb289c0a4'\r\n>>> s[0x53*2:0x67*2]\r\n'432f962d8209781da23fb37b6b59ee15de7d9841'\r\n>>> s[0x67*2:0x7b*2]\r\n'64ad353bc90a04361c4810ae7b3701f3beb48d7e'\r\n>>> s[0x7b*2:0x7c*2]\r\n'02'\r\n```\r\n```js\r\nfunction cloneETHPair(\r\n    address implementation,\r\n    ILSSVMPairFactoryLike factory,\r\n    ICurve bondingCurve,\r\n    IERC721 nft,\r\n    uint8 poolType\r\n) internal returns (address instance) {\r\n    assembly {\r\n        let ptr := mload(0x40)\r\n        mstore(ptr,           0x60733d8160093d39f33d3d3d3d363d3d37603d60363639603d36013d73000000)\r\n        mstore(add(ptr,0x1d), shl(0x60,implementation))\r\n        mstore(add(ptr,0x31), 0x5af43d928390803e603d57fd5bf3000000000000000000000000000000000000)\r\n        mstore(add(ptr,0x3f), shl(0x60,factory))\r\n        mstore(add(ptr,0x53), shl(0x60,bondingCurve))\r\n        mstore(add(ptr,0x67), shl(0x60, nft))\r\n        mstore8(add(ptr,0x7b), poolType)\r\n        instance := create(0, ptr, 0x7c)\r\n    }\r\n}\r\n```"},"author":{"user":"https:\/\/learnblockchain.cn\/people\/3295","address":null},"history":"QmUpi914eKR2q4EZabTNG11jowD46UXjGJUQeMZgJxSN4g","timestamp":1663033516,"version":1}