Skip to content

Latest commit

 

History

History
128 lines (103 loc) · 6.54 KB

File metadata and controls

128 lines (103 loc) · 6.54 KB

五十二课 EIP712 类型化数据签名

当支持 EIP712 的 Dapp 请求签名时,钱包会展示签名消息的原始数据,用户可以在验证数据符合预期之后签名。如果我们关心的只是字节串,那么对数据进行签名就是一个已解决的问题。不幸的是,在现实世界中,我们关心的是复杂而有意义的消息。此 EIP 旨在提高链下消息签名在链上使用的可用性。

链下签名

  1. EIP712 签名必须包含一个 EIP712Domain 部分,它包含了合约的 name,version(一般约定为 “1”),chainId,和 verifyingContract(验证签名的合约地址)。
    EIP712Domain: [
        { name: "name", type: "string" },
        { name: "version", type: "string" },
        { name: "chainId", type: "uint256" },
        { name: "verifyingContract", type: "address" },
    ]

// 定义domain的数据:
const domain = {
    name: "EIP712Storage",
    version: "1",
    chainId: "1",
    verifyingContract: "0xC3916677350056A30D29D4dE42262704F0f77f8e",
};
  1. 你需要根据使用场景自定义一个签名的数据类型,他要与合约匹配。在 EIP712Storage 例子中,我们定义了一个 Storage 类型,它有两个成员: address 类型的 spender,指定了可以修改变量的调用者;uint256 类型的 number,指定了变量修改后的值。
const types = {
    Storage: [
        { name: "spender", type: "address" },
        { name: "number", type: "uint256" },
    ],
};

// 要被签名的结构化数据
const message = {
    spender: "0x5B38Da6a701c568545dCfcB03FcB875f56beddC4",
    number: "100",
};
  1. 调用钱包的signTypeData方法,传入前面步骤中的 domain,types,和 message 变量进行签名(这里使用 ethersjs v6)。
    // 获得provider
    const provider = new ethers.BrowserProvider(window.ethereum)
    // 获得signer后调用signTypedData方法进行eip712签名
    const signature = await signer.signTypedData(domain, types, message);
    console.log("Signature:", signature);

五十三课 ERC20Permit

在ERC20的可以直接转账或者通过授权转账,但是都避免不了需要支付gas。在ERC20Permit中的一个拓展是:使用签名进行授权。达到改善用户体验的目的。

好处

  1. 授权这步仅需用户在链下签名,减少一笔交易。
  2. 签名后,用户可以委托第三方进行后续交易,不需要持有 ETH:用户 A 可以将签名发送给 拥有gas的第三方 B,委托 B 来执行后续交易。

ERC20Permit的使用

它定义了 3 个函数:

  1. permit(): 根据 owner 的签名, 将 owenr 的ERC20代币余额授权给 spender,数量为 value。要求:

    • spender 不能是零地址。
    • deadline 必须是未来的时间戳。
    • v,r 和 s 必须是 owner 对 EIP712 格式的函数参数的有效 keccak256 签名。
    • 签名必须使用 owner 当前的 nonce。
  2. nonces(): 返回 owner 的当前 nonce。每次为 permit() 函数生成签名时,都必须包括此值。每次成功调用 permit() 函数都会将 owner 的 nonce 增加 1,防止多次使用同一个签名。

  3. DOMAIN_SEPARATOR(): 返回用于编码 permit() 函数的签名的域分隔符(domain separator),如 EIP712 所定义。

五十四课 跨链桥

跨链桥是一种区块链协议,它允许在两个或多个区块链之间移动数字资产和信息。例如,一个在以太坊主网上运行的ERC20代币,可以通过跨链桥转移到其他兼容以太坊的侧链或独立链。

跨链桥的种类

  1. Burn/Mint:在源链上销毁(burn)代币,然后在目标链上创建(mint)同等数量的代币。此方法好处是代币的总供应量保持不变,但是需要跨链桥拥有代币的铸造权限,适合项目方搭建自己的跨链桥。
  2. Stake/Mint:在源链上锁定(stake)代币,然后在目标链上创建(mint)同等数量的代币(凭证)。源链上的代币被锁定,当代币从目标链移回源链时再解锁。这是一般跨链桥使用的方案,不需要任何权限,但是风险也较大,当源链的资产被黑客攻击时,目标链上的凭证将变为空气。
  3. Stake/Unstake:在源链上锁定(stake)代币,然后在目标链上释放(unstake)同等数量的代币,在目标链上的代币可以随时兑换回源链的代币。这个方法需要跨链桥在两条链都有锁定的代币,门槛较高,一般需要激励用户在跨链桥锁仓。

五十五课 多重调用

MultiCall 多重调用合约,它的设计目的在于一次交易中执行多个函数调用,这样可以显著降低交易费用并提高效率。

多重调用的优点

  • 方便性:MultiCall能让你在一次交易中对不同合约的不同函数进行调用,同时这些调用还可以使用不同的参数。比如你可以一次性查询多个地址的ERC20代币余额。

  • 节省gas:MultiCall能将多个交易合并成一次交易中的多个调用,从而节省gas。

  • 原子性:MultiCall能让用户在一笔交易中执行所有操作,保证所有操作要么全部成功,要么全部失败,这样就保持了原子性。比如,你可以按照特定的顺序进行一系列的代币交易。

代码示例

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract Multicall {
    // Call结构体,包含目标合约target,是否允许调用失败allowFailure,和call data
    struct Call {
        address target;
        bool allowFailure;
        bytes callData;
    }

    // Result结构体,包含调用是否成功和return data
    struct Result {
        bool success;
        bytes returnData;
    }

    /// @notice 将多个调用(支持不同合约/不同方法/不同参数)合并到一次调用
    /// @param calls Call结构体组成的数组
    /// @return returnData Result结构体组成的数组
    function multicall(Call[] calldata calls) public returns (Result[] memory returnData) {
        uint256 length = calls.length;
        returnData = new Result[](length);
        Call calldata calli;
        
        // 在循环中依次调用
        for (uint256 i = 0; i < length; i++) {
            Result memory result = returnData[i];
            calli = calls[i];
            (result.success, result.returnData) = calli.target.call(calli.callData);
            // 如果 calli.allowFailure 和 result.success 均为 false,则 revert
            if (!(calli.allowFailure || result.success)){
                revert("Multicall: call failed");
            }
        }
    }
}