Uniswap Labs发布了两个新的智能合约Permit2和UniversalRouter, Permit2 确实可以让链上交易体验上一层楼。

前几天,Uniswap Labs发布了两个新的智能合约 Permit2 和 Universal Router :

  1. Permit2 允许代币授权在不同的应用程序中共享和管理,创造一个更统一、更具成本效益、更安全的用户体验。
  2. Universal Router 将ERC20和NFT兑换统一到一个单一的兑换路由器。与Permit2整合后,用户可以在一次兑换交易中兑换多个代币和NFT,同时节省Gas费。

Uniswap最初构思Permit2和Universal Router是为了改进Uniswap自己的产品,优化Gas成本,简化用户交易流程,并加强安全性。在构思的过程中,Unswap觉得其他应用可以从整合这些合约中大大受益。Uniswap 本身致力于建设公共基础设施,因此设计了这些合约,提供整个开发者生态系统使用,包括广泛的文档、SDK。

Permit2是一个代币授权合约,可以在不同的智能合约中安全地共享和管理代币授权。随着越来越多的项目与Permit2集成,可以在所有应用程序中对代币授权进行标准化。反过来,Permit2将通过降低交易成本来改善用户体验,同时提高智能合约的安全性。

典型授权模式

以下是EIP-20中定义的典型代币授权(Approve)方法图示:

  1. Alice在一个ERC20上调用approve(),向一个合约授予支出授权。
  2. Alice在合约上调用一个交互函数,该函数又在ERC20代币合约上调用transferFrom(),转账她的代币。

显然,这种模式是可行的(它无处不在),并且最终可以相当灵活,因为协议通常会最终不间断地长期访问用户的代币。但它有两个众所周知的现实世界的问题。

  • 糟糕的用户体验, 用户必须对他们打算使用的每个代币授权给每个新的协议,这导致了混乱的用户体验,同时这几乎总是一个单独的交易,浪费了Gas和时间。
  • 糟糕的安全性,应用程序通常要求用户授权最大限额,以避免重复上述用户体验问题。这意味着,如果协议被利用,每个用户授权协议使用的代币都有可能被直接从他们的钱包里拿走(因为应用程序可以无限期地访问钱包的整个代币余额)。

授权签名(EIP-2612)模型

EIP-2612 对代币的授权进行了迭代。用户可以通过在他们的交易中附加一个授权签名(Permit)信息来与应用合约交互,而不需要事先授权。
让我们看看ERC20的EIP-2612扩展所启用的方法,它通常是这样的:

  1. Alice签署一个链外的 "permit(签名授权)" 信息,表示她希望授予一个合约一个(EIP-2612)代币的使用权。
  2. Alice提交签署的消息,作为她与所述合约交互的一部分。
  3. 合约调用代币上的 "permit()" 方法,它会使用签名授权信息和签名,同时授予合约一个授权。
  4. 合约现在有了授权,所以它可以在代币上调用transferFrom(),转账由Alice持有的代币。

这解决了典型ERC20 授权方法的两个问题:

  • 用户永远不需要提交一个单独的approve()交易。
  • 不再有悬空授权的必要之恶,因为许可消息授予的是即时授权,通常会立即花费。因此也可以选择一个更合理的授权额度,更重要的是,在签名授权消息可以被使用代币的时间上有一个到期时间。

虽然EIP-2612使代币授权更加安全,但在EIP-2612之前推出的代币并不支持签名授权功能,而且并非所有较新的代币都采用该功能,这就是悲催的现实。因此大多数时候,这种方法不可行。

关于EIP-2612, 我那个登链社区上还有一些文章探讨,可参考这里

Permit2模式

最后,让我们深入探讨Permit2的方法,Permit2 结合了这两种模式,将EIP-2612的用户体验和安全优势扩展到也涵盖了普通的ERC20代币!
为了说明Permit2的革命性,在一个常见的场景中,协议需要转账 Alice持有的代币。

  1. Alice在一个ERC20上调用approve(),典型的方式为的Permit2合约授予一个无限的授权。
  2. Alice签署一个链下 permit2 消息,该消息表明协议合约被允许代表她转账代币。
  3. Alice在协议合约上调用一个交互函数,将签署的 permit2 消息作为参数传入。
  4. 协议合约在Permit2合约上调用 permitTransferFrom(),而Permit2合约又使用其授权(在1中授予)在ERC20合约上调用 "transferFrom()",转账Alice持有的代币。

要求用户首先授予一个明确的授权交易,这似乎是一种倒退。但是,用户不是直接授予协议,而是将其授予规范的Permit2合约。这意味着,如果用户之前已经这样做了,比如说与另一个集成了Permit2的协议进行交互,那么其他每一个协议都可以跳过这个步骤。

这太棒了。

协议不会直接调用ERC20代币上的transferFrom()来执行转账,而是调用规范的Permit2合约上的permitTransferFrom()。Permit2 位于协议和ERC20代币之间,跟踪和验证permit2消息,然后最终使用其授权直接在ERC20上执行transferFrom()调用。这种间接性使得Permit2可以将类似于EIP-2612的好处扩展到每一个现有的ERC20代币上。

同时,像EIP-2612 签名授权信息一样,Permit2 信息也会过期,以限制漏洞的攻击窗口。

集成 Permit2

对于集成Permit2的前端来说,它需要获取一个用户签名,并将其传递到交易中。这些签名签署的Permit2消息结构(PermitTransferFrom)必须符合EIP-712标准(社区有一些相关文章),使用这里这里定义的Permit2域和类型散列。请注意,EIP-712 Permit2对象的 spender 字段需要被设置为将要消费它的合约地址。

智能合约的整合实际上是相当容易的! 任何需要转账用户持有的代币的函数只需要接受任何的许可信息细节和相应的EIP-712用户签名。为了实际转账代币,我们将在规范的Permit2合约上调用permitTransferFrom()。该函数的声明为:

function permitTransferFrom(
    PermitTransferFrom calldata permit,
    SignatureTransferDetails calldata transferDetails,
    address owner,
    bytes calldata signature
) external;

这个函数的参数是:

  • permit - permit2 消息的详情, 有下面的信息。
    • permitted 一个TokenPermissions结构,有以下字段:
      • token - 要转账的代币的地址。
      • amount - 此签名信息可转移的最大金额。
    • nonce - 一个独特的数字,用来防止重用签名许可。一旦签名许可被使用,任何使用该nonce的其他签名许可将无效。
    • deadline - 该签名许可有效的截止时间。
  • transferDetails - 一个包含转账接收人和转账金额的结构,可以小于用户签名的金额。
  • owner - 签署许可的人,也是持有代币。通常,在简单的使用场景中,调用者和用户是同一个人,这应该被设置为调用者(msg.sender)。但在更奇特的集成中,你可能需要更复杂的检查。
  • signature - permit2信息对应的EIP-712签名,由owner签名。如果从签名验证中还原的地址与 owner 不一致,调用将失败。

注意,PermitTransferFrom结构不包括签名信息 EIP-712 typehash 定义中的spender字段。在处理过程中,它将被填入我们的合约地址(permitTransferFrom()的直接调用者)。这就是为什么用户签署的EIP-712对象的spender字段必须是这个合约的地址。

高级集成

前面涵盖了 Permit2 提供的基本功能,但你还可以用它做更多的事情!

  • 自定义见证数据 - 你可以将自定义数据附加到permit2的信息中,这意味着Permit2的签名验证也将扩展到验证这些数据。
  • 批量转账 - 一个用于执行多个转账的批量 permit2 消息,由一个签名来保证。
  • Smart Nonces - 在底层,nonces实际上被写成存储槽中的位字段,并以上面的248位为索引。你可以通过仔细选择重用存储槽的nonce值来节省大量的Gas。
  • 回调签名 - Permit2支持EIP-1271回调签名,它允许智能合约也签署permit2消息。
  • Permit2 Allowances - 对于需要更多灵活性的协议,Permit2支持一个更传统的授权模型,得到了过期时间的额外好处。

The Demo

这里提供的示例代码是一个简单的金库,用户可以使用Permit2将ERC20代币存入其中,随后可以提取。因为它是多用户的,它需要启动转账,以便可靠地记入哪个账户拥有哪个余额。通常情况下,这需要给金库合约授予授权,然后让金库对代币本身执行transferFrom(),但Permit2让我们跳过了这个麻烦!

Test用例部署了一个本地的、字节码的主网Permit2合约的分叉,以测试金库的一个实例。EIP-712 Hash 和签名生成也是用solidity/foundry编写的,但通常应该在链外用你选择的语言在前端或后端执行。

参考资源

转载自:https://learnblockchain.cn/article/5161