ERC20-Permit(EIP-2612)下,如何避免 使用进行两步交易:授权+ transferFrom!
快进至EIP-2612
从那时起,DAI和Uniswap一直在朝着名为EIP-2612的新标准的方向发展,该标准可以取消 approve + transferFrom,同时还允许无 gas 通证转账。 DAI是第一个为其ERC-20通证添加新的permit功能的公司。它允许用户在链下签署授权的交易,生成任何人都可以使用并提交给区块链的签名。这是解决gas 支付问题的基本的第一步,并且消除了用户不友好的两步过程:发送approve和之后的 transferFrom。
让我们详细研究一下EIP。
原始的错误方法
总体而言,该过程非常简单。用户不在发起授权(approve)交易,而是对approve(spender, amount)签名。签名结果可以被任何人传递到 permit函数,在 permit函数我们只需使用 ecrecover来检索签名者地址,接着用 approve(signer,spender,amount)。
这种方式可用于让其他人为交易支付 gas 费用,也可以删除掉常见的授权(approve)+ transferFrom模式:
之前方法:
- 用户提交token.approve(myContract.address, amount)交易。
- 等待交易确认。
- 用户提交第二个 myContract.doSomething()交易,该交易内部使用 token.transferFrom。
现在:
- 用户进行授权签名:签名信息signature=(myContract.address,amount)。
- 用户向 myContract.doSomething(signature)提交签名。
- myContract使用 token.permit增加配额,并调用 token.transferFrom 获取代币。
之前需要两笔交易,现在只需要一笔!
Permit 细节:防止滥用和重播
我们面临的主要问题是签名可能会多次使用或在原本不打算使用的其他地方使用。为防止这种情况,我们添加了几个参数。在底层,我们使用的是已经存在的,广泛使用的EIP-712标准。
1. EIP-712 域哈希(Domain Hash)
使用EIP-712,我们为ERC-20定义了一个域分隔符
bytes32 eip712DomainHash = keccak256(
abi.encode(
keccak256(
"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"
),
keccak256(bytes(name())), // ERC-20 Name
keccak256(bytes("1")), // Version
chainid(),
address(this)
)
);
这样可以确保仅在正确的链ID上将签名用于我们给定的通证合约地址。chainID是在以太坊经典分叉之后引入(以太坊经典network id 依旧为 1), 用来精确识别在哪一个网络。 可以在此处查看现有chain ID的列表。
2. Permit 哈希结构
现在我们可以创建一个Permit的签名:
bytes32 hashStruct = keccak256(
abi.encode(
keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
owner,
spender,
amount,
nonces[owner],
deadline
)
);
此hashStruct将确保签名只能用于
- Permit 函数
- 从owner授权
- 授权spender
- 授权给定的value (金额)
- 仅在给定的deadline之前有效
- 仅对给定的 nonce有效
nonce可确保某人无法重播签名,即在同一合约上多次使用该签名。
3. 最终哈希
现在我们可以用兼容 EIP-191的712哈希构建(以0x1901开头)最终签名:
bytes32 hash = keccak256(
abi.encodePacked(uint16(0x1901), eip712DomainHash, hashStruct)
);
4. 验证签名
在此哈希上,我们可以使用ecrecover 获得该函数的签名者:
address signer = ecrecover(hash, v, r, s);
require(signer == owner, "ERC20Permit: invalid signature");
require(signer != address(0), "ECDSA: invalid signature");
无效的签名将产生一个空地址,这就是最后一次检查的目的。
5. 增加Nonce 和 授权
现在,最后我们只需要增加所有者的Nonce并调用授权函数即可:
nonces[owner]++;
_approve(owner, spender, amount);
你可以在此处看到完整的实现示例。
已有的ERC20-Permit 实现
DAI ERC20-Permit
DAI是最早引入 permit的通证之一,如此处所述。实现与EIP-2612略有不同:
- 没有使用 value,而只使用一个bool allowed,并将allowance 设置为0或MAX_UINT256
- deadline参数称为expiry
Uniswap ERC20-Permit
Uniswap实现与当前的EIP-2612保持一致,请参见这里。它允许你调用removeLiquidityWithPermit,从而省去了额外的授权步骤。
如果你想体验一下该过程,请转到https://app.uniswap.org/#/pool 并切换到Kovan网络。不用增加资金的流动性。现在尝试将其删除。单击“Approve”后,你会注意到此MetaMask弹出窗口如下图所示。
这不会提交交易,而只会创建具有给定参数的签名。你可以对其进行签名,并在第二步中使用生成的签名调用removeLiquidityWithPermit。总而言之:只需提交一份交易。
ERC20-Permit 代码库
我已经创建了可以导入的ERC-20-Permit代码库。你可以在https://github.com/soliditylabs/ERC20-Permit 中找到它
其使用:
- OpenZeppelin ERC20 Permit
- 0x-inspired节省gas的 汇编代码。
- eth-permit 前端库,用于测试
你可以通过npm安装来简单地使用它:
$ npm install @soliditylabs/erc20-permit --save-dev
像这样将其导入到你的ERC-20合约中:
// SPDX-License-Identifier: MIT
pragma solidity ^0.7.0;
import {ERC20, ERC20Permit} from "@soliditylabs/erc20-permit/contracts/ERC20Permit.sol";
contract ERC20PermitToken is ERC20Permit {
constructor (uint256 initialSupply) ERC20("ERC20Permit-Token", "EPT") {
_mint(msg.sender, initialSupply);
}
}
前端使用
你可以在我的测试中代码在这里看到如何使用eth-permit库创建有效的签名。它会自动获取正确的随机数,并根据当前标准设置参数。它还支持DAI样式的许可证签名创建。完整文档可在https://github.com/dmihal/eth-permit 获得
关于调试的一句话:这可能很痛苦。关闭任何单个参数都将导致revert: Invalid signature。祝你好运找出原因。
在撰写本文时,似乎仍然有一个已知 issue,它可能会或也可能不会影响你,具体取决于你的Web3提供程序。如果确实对你有影响,请使用通过patch-package安装这里的补丁
无需gas代币解决方案
现在回想起我在悉尼的经历,单靠这个标准并不能解决问题,但这是解决该问题的第一个基本模块。现在,你可以为其创建加油站网络,例如Open GSN。部署合约,该网络只需通过permit + transferFrom即可进行通证转账。 GSN内部运行的节点将获取许可签名并提交。
谁支付 gas 费?这将取决于特定的场景。也许Dapp公司支付这些费用作为其客户获取成本(CAC)的一部分。也许用转移的通证支付了GSN节点费用。要弄清所有细节,我们还有很长的路要走。
一如既往的小心使用
请注意,该标准尚未最终确定。当前与Uniswap实现相同,但将来可能会有所变化。如果标准再次更改,我将保持库的更新。我的图书馆代码也未经审核,使用后果自负。
本翻译由 Cell Network 赞助支持。
转载自:https://learnblockchain.cn/article/1790
版权属于:区块链中文技术社区 / 转载原创者
本文链接:https://bcskill.com/index.php/archives/1622.html
相关技术文章仅限于相关区块链底层技术研究,禁止用于非法用途,后果自负!本站严格遵守一切相关法律政策!