{"author":{"address":"0x758e4E534AFBB044Dea64AB07e5d783fEc7e1541","user":"https://learnblockchain.cn/people/12931"},"content":{"body":"## 实战例子\r\n\r\n当时看到ARB空投泄漏的私钥第一反应就是看下ARB是否可以使用 `permit`函数，等黑客领到币之后直接转走。后面再推特上看到这个大佬使用该方法帮助粉丝抢回一定的币。\r\n\r\n1. [0xAA_Science 通过Arb代币的permit授权大战黑客](https://twitter.com/0xAA_Science/status/1639248965102362624)\r\n\r\n学会了个这个方法，再搭个私有节点 。下次有这种机会就可以battle一下了。\r\n\r\n## Tips\r\n\r\n1. 该方法属于ERC20的扩展，涉及的知识比较多，需要耐心一点一点理解其中的逻辑。建议先看[dai.sol](https://github.com/makerdao/dss/blob/master/src/dai.sol?ref=hackernoon.com)的实现。\r\n\r\n## Why ？\r\n\r\n这是一个 2020-04-13，Martin![]([](``)) Lundfall创建的[提案](https://eips.ethereum.org/EIPS/eip-2612)。属于ERC20代币功能的拓展，可以让用户A在没有ETH代币支付GAS费的情况下向用户B支付用户A所拥有的ERC代币。这是一个常见的情况，比如你的某个不常用的地址在ETH网络上收到100DAI，你要把这100DAI转移到常用的地址上，你首先需要向这个不常用的地址转一些ETH作为GAS费，才能把DAI转出来。如果DAI代币实现了ERC-2612这个扩展功能(确实实现了，而且在ERC-2612发布之前)，那么就可以不需要转移任何ETH到不常用的账户，就可以把该地址的DAI转移到常用账户。\r\n\r\n## What\r\n\r\n- 无GAS转账不是不需要GAS费就可以完成代币的转移，只不过是原本需要由用户A承担的GAS费可以转嫁用户B，或者服务的提供商身上。\r\n- 首先由用户A提供的批准用户B使用他的DAI代币的链下的签名，用户B拿到由用户A提供的签名就行授权，再完成转账。这样对于用户A来说，只用提供链下的签名，他不用付出任何GAS的成本，所以称为无GAS转账。用户B则需要支付2笔交易的gas的费来完成转账。\r\n- 本质来说，就是用户A把授权代币的操作通过链下签名的方式委托给了第三方。\r\n\r\n## How\r\n\r\n### 1. 用户A提供签名\r\n\r\n提供签名首先需要知道要对什么内容进行签名。\r\nERC2612按照ERC712的签名标准规定了对批准他人使用ERC代币的方法提供了签名的标准方法。一共规定了三个函数\r\n\r\n```\r\nfunction permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external\r\nfunction nonces(address owner) external view returns (uint)\r\nfunction DOMAIN_SEPARATOR() external view returns (bytes32)\r\n\r\n```\r\n\r\n其中\r\n\r\n```\r\nfunction permit(...) external\r\n\r\n```\r\n\r\n就是用户B拿着用户A签名可以调用的函数\r\n\r\n首先看下[op.sol](https://optimistic.etherscan.io/address/0x4200000000000000000000000000000000000042#code)中permit函数的实现来看链下签名需要哪些内容\r\n\r\n```\r\nfunction permit(\r\n        address owner,\r\n        address spender,\r\n        uint256 value,\r\n        uint256 deadline,\r\n        uint8 v,\r\n        bytes32 r,\r\n        bytes32 s\r\n    ) public virtual override {\r\n        require(block.timestamp \u003c= deadline, \"ERC20Permit: expired deadline\");\r\n\r\n        bytes32 structHash = keccak256(abi.encode(_PERMIT_TYPEHASH, owner, spender, value, _useNonce(owner), deadline));\r\n  \r\n  \r\n\t\t ////////////////// 这个这是hash的内容\r\n        bytes32 hash = _hashTypedDataV4(structHash);   \r\n         //////////////////\r\n\r\n        address signer = ECDSA.recover(hash, v, r, s);\r\n        require(signer == owner, \"ERC20Permit: invalid signature\");\r\n\r\n        _approve(owner, spender, value);\r\n    }\r\n```\r\n\r\n进一步查看\r\n\r\n```\r\n    function _hashTypedDataV4(bytes32 structHash) internal view virtual returns (bytes32) {\r\n        return ECDSA.toTypedDataHash(_domainSeparatorV4(), structHash);\r\n    }\r\n```\r\n\r\n发现使用的标准的EIP712 hashTypeData的签名方法。\r\n签名分为两个部分\r\n\r\n1. DOMAIN_SEPARATOR 第一部分用于确认该签名只能用于这一个合约\r\n2. structHash  第二部分用于确认该签名只能用于这一个函数\r\n\r\nERC2612规定了这两部分签名的内容\r\n\r\n```\r\nDOMAIN_SEPARATOR = keccak256(\r\n    abi.encode(\r\n        keccak256('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)'),\r\n        keccak256(bytes(name)),\r\n        keccak256(bytes(version)),\r\n        chainid,\r\n        address(this)\r\n));\r\n```\r\n\r\n```\r\n{\r\n  \"message\": {\r\n    \"owner\": owner,\r\n    \"spender\": spender,\r\n    \"value\": value,\r\n    \"nonce\": nonce,\r\n    \"deadline\": deadline\r\n  }}\r\n```\r\n\r\n把这个两部分的消息的hash再进行hashTypeData签名就可以得到需要签名的消息。\r\n\r\n然后我们就可以使用用户A的私钥对消息进行签名，得到最终可以使用的签名。\r\n\r\n### 2. 用户B使用签名完成授权，转账\r\n\r\n1. 用户B拿着用户A提供的签名调用op的 `permit`函数可以完成代币的授权。\r\n2. 用户B再调用op的 `transferFrom`函数就可以完成代币的转移\r\n\r\n### 3.实例\r\n\r\n\r\n![pic1.png](https://img.learnblockchain.cn/attachments/2024/07/C48djxAN668ddbda4ca01.png)\r\n\r\n1. 用户B拿到用户A的签名我们就可以调用OP的 `permit`函数，对用户A的TOKEN就行授权。\r\n2. 我向一个空地址 `0x7112DDf035f1fF30fd9D463164da6c80795bDb19`(用户A)转了1个OP\r\n3. 然后通过permit函数授权 `0x596De5582cbeDEac1880aac59035C80C4e113875`(用户B)转走了0.1*-18E个OP到 `0x758e4E534AFBB044Dea64AB07e5d783fEc7e1541`(我在用的一个地址)\r\n4. 在这个过程过用户A没有支付任何GAS，两笔交易费用都由用户B承担了。\r\n\r\n\r\n![pic2.png](https://img.learnblockchain.cn/attachments/2024/07/UhOGzsx9668ddbe97133c.png)\r\n\r\n### 4.代码\r\n\r\n提供了js代码，ethers.js提供了 `_signTypedData`函数非常便捷的可以完成permit的签名。\r\n需要自己配置下abi和私钥\r\n\r\n```javascript\r\nconst { ethers } = require(\"ethers\");\r\n\r\nrequire(\"dotenv\").config();\r\n\r\nconst provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);\r\n\r\nconst wallet = new ethers.Wallet(process.env.PRIVATE_KEY_OWNER, provider);\r\n\r\nconst wallet_spender = new ethers.Wallet(process.env.PRIVATE_KEY_SPENDER, provider);\r\n\r\nOP_NAME = \"Optimism\";\r\nOP_ADDRESS = \"0x4200000000000000000000000000000000000042\";\r\nOP_CHAIN_ID = \"10\";\r\n\r\n\r\nSPENDER = \"0x596De5582cbeDEac1880aac59035C80C4e113875\"; //使用代币的地址\r\nOWNER=\"0x7112ddf035f1ff30fd9d463164da6c80795bdb19\" //拥有代币的地址\r\n\r\n//通过abi连接合约\r\nconst abi = require(\"./op.json\");\r\nconst op = new ethers.Contract(\r\n  OP_ADDRESS,\r\n  abi,\r\n  wallet\r\n\r\n)\r\n\r\nasync function main_fuc(){\r\n\r\n\r\n  const domain = {\r\n    name: \"Optimism\",\r\n    version: \"1\",\r\n    chainId: OP_CHAIN_ID, \r\n    verifyingContract: OP_ADDRESS,\r\n  };\r\n\r\n  const types = {\r\n    Permit: [\r\n      { name: \"owner\", type: \"address\" },\r\n      { name: \"spender\", type: \"address\" },\r\n      { name: \"value\", type: \"uint256\" },\r\n      { name: \"nonce\", type: \"uint256\" },\r\n      { name: \"deadline\", type: \"uint256\" },\r\n    ],\r\n  };\r\n\r\nconst value = ethers.constants.MaxUint256; \r\n\r\nconst nonce = await op.nonces(wallet.address);\r\nconst deadline = ethers.constants.MaxUint256;\r\nconst message = {\r\n  owner: wallet.address,\r\n  spender: SPENDER,\r\n  value: value,\r\n  nonce: nonce,\r\n  deadline: deadline,\r\n};\r\n\r\n//使用钱包A生成链下的签名\r\nconst signature = await wallet._signTypedData(domain, types, message);\r\n\r\nconst { v, r, s } = ethers.utils.splitSignature(signature);\r\n\r\nconst op2 = new ethers.Contract(\r\n  OP_ADDRESS,\r\n  abi,\r\n  wallet_spender\r\n\r\n)\r\n//使用钱包B调用op的permit函数\r\nresut =await op2.permit(OWNER, SPENDER, value, deadline, v, r, s)\r\nconsole.log(resut)\r\n\r\n}\r\nmain_fuc()\r\n\r\n```\r\n\r\n## 参考资料\r\n\r\n1. Martin Lundfall ([@Mrchico](https://github.com/Mrchico)), \"ERC-2612: Permit Extension for EIP-20 Signed Approvals,\" *Ethereum Improvement Proposals*, no. 2612, April 2020. [Online serial]. Available: https://eips.ethereum.org/EIPS/eip-2612.\r\n2. https://hackernoon.com/how-to-code-gas-less-tokens-on-ethereum-43u3ew4\r\n3. https://wtf.academy/solidity-application/Signatur","title":"ERC-2612: 无GAS转账"},"history":null,"timestamp":1720573241,"version":1}