主页 > imtoken钱包官网版最新 > 【知识】免GAS以太坊交易实现原理及源码

【知识】免GAS以太坊交易实现原理及源码

imtoken钱包官网版最新 2023-03-11 07:52:13

每个人都在谈论无燃料的以太坊交易,因为没有人喜欢支付燃料费。 但以太坊网络之所以有效,恰恰是因为交易需要费用。 那么如何实现无gas交易呢? 让我们一起学习无气体以太坊交易的神奇之处!

在本文中,我们将学习如何实现无燃料交易模式。 你会发现,虽然以太坊上没有免费的午餐,但有一些有趣的方法可以转移 gas 成本。 使用本文中学到的知识,您的 DApp 用户可以节省汽油,获得更好的用户体验,或在您的智能合约中构建新颖的代理模型。

源代码:

英文原文:

1.一些背景知识

我不得不承认,虽然我了解如何在智能合约中实现无燃料交易,但我对它背后的密码学知之甚少。 但对我来说,这并不是什么大障碍,所以如果你对密码学不熟悉,相信也不会影响你实现无gas以太坊交易。

据我所知,我的私钥用于签署发送到以太坊网络的交易。 在这个过程中,使用了一些密码学技术来识别我的身份,并将其存储在变量 msg.sender 中,在以太坊中访问。 控制的基石。

无气体交易背后的神奇之处在于,我们可以使用我们的私钥为我们希望执行的合约交易进行签名。

签名是在不消耗任何气体的情况下在链下生成的。 签名完成后,我们可以将交易发送给其他人为我们执行,并为我们支付燃料费。

使用签名的合约函数通常是一个普通函数,但它支持传入额外的签名参数。 例如,在 dai.sol 中,我们可以看到如下的 approve 函数:

function approve(address usr, uint wad) external returns (bool)

复制

还可以看到 permit 函数,它的作用和 approve 一样,但是支持额外的签名参数:

function permit(address holder, address spender, uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s) external

复制

如果您不了解这些附加参数,请不要担心,它们将在下面进行解释。 我们需要注意的是上面两个函数是如何处理allowance mapping的:

function approve(address usr, uint wad) external returns (bool)
{
  allowance[msg.sender][usr] = wad;
  …
}
function permit(
  address holder, address spender,

sitebitett.com 以太坊怎么交易_新加坡以太坊交易网站_以太坊交易费

uint256 nonce, uint256 expiry, bool allowed, uint8 v, bytes32 r, bytes32 s ) external { … allowance[holder][spender] = wad; … }

复制

如果调用了approve方法,则表示允许spender账户操作不超过你持有的wad代币。

如果你给另一个人一个有效的签名,那么那个人可以通过调用 permit 方法允许消费者账户操作不超过你持有的 wad 代币。 是一样的吗?

所以基本上,无气体交易背后的模型是做一个签名,其他人可以使用你的身份安全地执行特定的交易,就像你授权别人执行一个方法一样。

这实际上是一种代理模型。

2、免gas交易规范

如果您和我一样,您可能会立即深入研究代码。 我立即注意到一个注释:

// — — EIP712 niceties — -

复制

它看起来像以太坊规范,所以我研究了一下,但当时没看懂。 现在我明白了,可以用通俗易懂的话来解释了。

EIP712() 描述了为合约方法生成签名的通用方法。 其他 EIP 描述了如何在特定用例中使用 EIP712。 例如 EIP2612() 描述了如何将 EIP712 签名用于 permit 方法,它与我们之前看到的 ERC20 令牌中的 approve 方法做同样的事情。

如果你只是想实现一个定义好的签名方法,比如给你的MetaCoin合约添加一个支持签名的approve方法,那么阅读EIP2612就足够了。 更简单的方法是直接继承一个实现了 EIP2612 的合约。

在本文中,我们将研究 dai.sol 中的 gasless 交易实现,这将有助于我们更清楚地了解其内部机制。 dai.sol 的 gas-free 实现是在 EIP2612 之前完成的,所以有一些差异。 但这不是什么大问题。

3.签名的构成

EIP712 的早期实现可以在 dai.sol 中看到,它允许 dai 持有者在链下计算签名并让支出者执行 approve 方法,而不是直接由 dai 持有者调用 approve 方法。

整个实现包括4个部分:

新加坡以太坊交易网站_sitebitett.com 以太坊怎么交易_以太坊交易费

DOMAIN_SEPARATOR PERMIT_TYPEHASH 随机数变量许可函数

这是 DOMAIN_SEPARATOR 和相关变量:

string  public constant name     = "Dai Stablecoin";
string  public constant version  = "1";
bytes32 public DOMAIN_SEPARATOR;
constructor(uint256 chainId_) public {
  ...
  DOMAIN_SEPARATOR = keccak256(abi.encode(
    keccak256(
      "EIP712Domain(string name,string version," + 
      "uint256 chainId,address verifyingContract)"
    ),
    keccak256(bytes(name)),
    keccak256(bytes(version)),
    chainId_,
    address(this)
  ));
}

复制

DOMAIN_SEPARATOR 是用于唯一标识智能合约的哈希值,它是使用标记 EIP712 域(合约名称、版本、链 ID、部署地址)的字符串构造的。

所有这些信息都在构造函数中进行哈希处理并存储在 DOMAIN_SEPARATOR 变量中,dai 持有者在生成签名时需要使用这个变量值,在执行 permit 方法时需要匹配。 DOMAIN_SEPARATOR 可以确保一个签名只对单个合约有效。

许可类型哈希:

sitebitett.com 以太坊怎么交易_以太坊交易费_新加坡以太坊交易网站

PERMIT_TYPEHASH是函数名(首字母大写)和所有参数(包括类型和参数名)的哈希值,其目的是明确定义签名的适用方法。

签名需要在permit方法中处理。 如果适用的 PERMIT_TYPEHASH 不适用于此方法,则事务将被回滚。 这确保签名只能与特定方法一起使用。

这是随机数映射:

mapping (address => uint) public nonces;

复制

Nonces 用于注册特定 dai 持有者使用的签名数量。 创建签名时,您需要包含一个随机数值。 在执行 permit 方法时,nonce 必须与持有者使用的签名数量相匹配。 此措施用于确保签名仅使用一次。

PERMIT_TYPEHASH、DOMAIN_SEPARATOR和nonce这三者的组合,可以保证一个签名只能用于特定的合约,特定的方法,并且只能使用一次。

现在让我们看看智能合约中是如何处理签名的。

4.许可方式

permit 方法是 dai.sol 中实现的一个函数,它允许使用签名来实现与 approve 相同的功能。

// --- Approve by signature ---
function permit(
  address holder, address spender,
  uint256 nonce, uint256 expiry, bool allowed,
  uint8 v, bytes32 r, bytes32 s
) external

复制

如您所见,permit 方法包含许多参数。 这些参数就是计算签名所需要的数据,以及签名数据v、r、s。

传入参数来创建签名似乎很愚蠢,但这是必要的。 因为唯一可以从签名中恢复的是签名创建者的地址。 我们需要所有这些参数和恢复的创建者地址来确保签名的有效性。

首先,我们使用这些参数计算汇总数据。 Dai 持有者需要在链下执行相同的计算,这是生成签名的必要部分:

bytes32 digest =
  keccak256(abi.encodePacked(
    "\x19\x01",

新加坡以太坊交易网站_以太坊交易费_sitebitett.com 以太坊怎么交易

DOMAIN_SEPARATOR, keccak256(abi.encode( PERMIT_TYPEHASH, holder, spender, nonce, expiry, allowed )) ));

复制

使用带有 v、r 和 s 的 ecrecover 方法,我们可以从签名中恢复地址。 如果这是 dai 持有者的地址,那么我们就知道参数是正确的,即 DOMAIN_SEPARATOR、PERMIT_TYPEHASH、nonce、holder、spender、expiry、allowed 都是正确的。 如果匹配不上,直接拒绝这个签名:

require(holder == ecrecover(digest, v, r, s), "Dai/invalid-permit");

复制

这个地方需要注意。 签名涉及的参数很多以太坊交易费,有些参数比较隐晦,比如链ID(DOMAIN_SEPARATOR的一部分)。 不匹配这些参数中的任何一个都将导致签名被拒绝,这使得链下签名的调试变得非常困难。

现在我们引导持有者授权这个方法调用。 接下来我们需要验证签名没有被滥用。

首先检查当前时间是否在到期之前,这使得授权只能在某个时间点之前有效。

require(expiry == 0 || now <= expiry, "Dai/permit-expired");

复制

我们还可以检查具有此随机数的签名是否尚未使用,从而确保签名只能使用一次。

require(nonce == nonces[holder]++, "Dai/invalid-nonce");

复制

现在通过了! dai.sol 更新配额,触发事件,仅此而已。

以太坊交易费_新加坡以太坊交易网站_sitebitett.com 以太坊怎么交易

uint wad = allowed ? uint(-1) : 0;
allowance[holder][spender] = wad;
emit Approval(holder, spender, wad);

复制

dai.sol 合约使用二进制方式处理配额,我们提供的代码使用更传统的方式来处理配额。

5. 创建链下签名

创建签名不适合胆小的人,但只要稍加练习和耐心,其实很容易掌握。 我们分三步复制智能合约的 permit 方法中的逻辑:

生成 DOMAIN_SEPARATOR 生成摘要 生成交易签名

参考项目的“signatures.ts”文件。

下面的函数将创建 DOMAIN_SEPARATOR。 它与 dai.sol 构造函数中的代码执行相同的操作,但使用 javascript,以及 ethers.js 中的 keccak256、defaultAbiCoder 和 toUtfBytes。 此函数采用代币名称、部署地址和链 ID,并假设代币版本为“1”:

在此处插入图像描述

下面的函数将为特定的许可调用创建摘要。 请注意,holder、spender、nonce 和 expiry 都是作为参数传入的。 还要传入一个 approve.allowed 参数,尽管您始终可以将其设置为 true。 注意这里的PERMIT_TYPEHASH是直接从dai.sol复制过来的。

在此处插入图像描述

一旦我们有了摘要以太坊交易费,签名就相对容易了。 我们使用 ethereumjs-util 中的 ecsign 删除 0x 前缀对摘要数据进行签名。 请注意,对于此步骤,我们需要用户私钥。

[图片上传失败...(image-87bb30-1633403813266)]

上述js函数的调用方法如下:

在此处插入图像描述

请注意我们在调用 permit 时如何使用我们之前创建的摘要中的那些参数。 只有这样签名才有效。

另一点需要注意的是,在此代码片段中,user2 仅调用了两个事务。 user1 代表 dai 持有者,他是创建摘要并对其签名的帐户。 但是 user1 不需要消耗任何气体。

user1会签名给user2,user2使用这个签名执行permit方法和transferFrom方法。

从user1的角度来看,这是一笔无gas交易,他不需要消耗任何wei。

六,结论

本文展示了如何使用 gasless 交易,阐明 gasless 实际上意味着将 gas 成本转嫁给其他人。 为此,我们需要智能合约中可以处理预签名交易的方法。

然而,使用这种模型有显着的好处,因此无燃料交易已被广泛使用。 签名允许交易的 gas 成本从用户转移到服务提供商,从而在许多场景中消除了用户进入的障碍。 无气体交易还支持更高级代码模式的实施,这通常会显着改善用户体验。