EIP-712 使用详解
之前的文章我们介绍过如何对数据进行签名,利用签名技术我们可以实现一些功能例如白名单校验等。但是这种签名技术的应用场景比较简单,一般就是给一串字符串,或者一串哈希签名,如果我们想为更复杂的数据签名就无法实现了。 EIP-712 的出现就是为了解决这个问题,利用 EIP-712,我们可以对更大的数据集,例如对结构体进行签名。那么这种签名格式有什么实际的应用场景呢。使用过 Uniswap,PancakeSwap 等 DEX 的朋友应该有印象,在移除 LP 流动性的时候,我们需要先签名,然后再发送一笔交易移除流动性。正常情况下,其实应该我们先调用 LP 代币的授权方法,授权 DEX 合约可以转移我们的 LP,然后再去移除流动性。而这种二合一的实现正是应用了 EIP-712。它帮助我们仅仅签名一次,就可以将两步交易合并为一步交易,从而节省 Gas 费用。这篇文章我们就来看看 EIP-712 到底是怎么使用的。基本结构EIP712Domain顾名思义,是一个与域相关的结构体,总共包含五个字段:name,合约或者协议的名称version,合约的版本chainId,合约部署的链 Id,一般使用 ...
流动性挖矿-合约原理详解
流动性挖矿应该是上个牛市最火热的内容,基本上整个 DeFi 都是在围绕着流动性挖矿展开的,今天我们就来看看它到底是什么以及合约代码层面是怎么实现的。流动性挖矿简介首先我们先从用户的角度来理解一下流动性挖矿是什么,实际上就是用户通过在合约中质押一个 token 从而赚取另一个 token 的过程。例如,SushiSwap 最初推出的 DEX 流动性挖矿,用户可以通过将 SushiSwap 的 LP token 质押到合约中赚取 Sushi token。那么这个奖励具体是怎么发放以及如何实现的呢?我们今天就来研究一下这部分内容。 先来看几个例子: 一:假设有一个流动性挖矿的合约,可以质押 A token 赚取 B token。它在 0 秒时开始活动,每秒奖励 R 个 B token。此时有用户 Alice 在第 3 秒时质押了 2 个 A token,并且之后没有其他人参与,在第 8 秒时取出 token,图示:那么他在此时获得的收益就是:5R = (2 / 2) * (8 - 3) * R 其中,第一个 2 是用户 A 质押的数量,第二个 2 是合约中质押的总量,(8-3)是用户 ...
CREATE2 操作码使用方法详解
CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相...
Smart Contract Developer
EIP-712 使用详解
之前的文章我们介绍过如何对数据进行签名,利用签名技术我们可以实现一些功能例如白名单校验等。但是这种签名技术的应用场景比较简单,一般就是给一串字符串,或者一串哈希签名,如果我们想为更复杂的数据签名就无法实现了。 EIP-712 的出现就是为了解决这个问题,利用 EIP-712,我们可以对更大的数据集,例如对结构体进行签名。那么这种签名格式有什么实际的应用场景呢。使用过 Uniswap,PancakeSwap 等 DEX 的朋友应该有印象,在移除 LP 流动性的时候,我们需要先签名,然后再发送一笔交易移除流动性。正常情况下,其实应该我们先调用 LP 代币的授权方法,授权 DEX 合约可以转移我们的 LP,然后再去移除流动性。而这种二合一的实现正是应用了 EIP-712。它帮助我们仅仅签名一次,就可以将两步交易合并为一步交易,从而节省 Gas 费用。这篇文章我们就来看看 EIP-712 到底是怎么使用的。基本结构EIP712Domain顾名思义,是一个与域相关的结构体,总共包含五个字段:name,合约或者协议的名称version,合约的版本chainId,合约部署的链 Id,一般使用 ...
流动性挖矿-合约原理详解
流动性挖矿应该是上个牛市最火热的内容,基本上整个 DeFi 都是在围绕着流动性挖矿展开的,今天我们就来看看它到底是什么以及合约代码层面是怎么实现的。流动性挖矿简介首先我们先从用户的角度来理解一下流动性挖矿是什么,实际上就是用户通过在合约中质押一个 token 从而赚取另一个 token 的过程。例如,SushiSwap 最初推出的 DEX 流动性挖矿,用户可以通过将 SushiSwap 的 LP token 质押到合约中赚取 Sushi token。那么这个奖励具体是怎么发放以及如何实现的呢?我们今天就来研究一下这部分内容。 先来看几个例子: 一:假设有一个流动性挖矿的合约,可以质押 A token 赚取 B token。它在 0 秒时开始活动,每秒奖励 R 个 B token。此时有用户 Alice 在第 3 秒时质押了 2 个 A token,并且之后没有其他人参与,在第 8 秒时取出 token,图示:那么他在此时获得的收益就是:5R = (2 / 2) * (8 - 3) * R 其中,第一个 2 是用户 A 质押的数量,第二个 2 是合约中质押的总量,(8-3)是用户 ...
CREATE2 操作码使用方法详解
CREATE2 是一个可以在合约中创建合约的操作码。我们先来举个例子看看它能干什么:这段代码是 Uniswap v2-core 里面的工厂合约代码,使用 create2 操作码创建了 pair 合约,返回值是 pair 的地址,这样就可以逻辑中直接使用其地址进行接下来的操作。 那么 create2 到底是怎么使用呢,根据官方 EIP 文档,create2 一共接收四个参数,分别是:endowment(创建合约时往合约中打的 ETH 数量)memory_start(代码在内存中的起始位置,一般固定为 add(bytecode, 0x20) )memory_length(代码长度,一般固定为 mload(bytecode) )salt(随机数盐)这里要注意的是第一个参数如果大于 0 的话,需要待部署合约的构造方法带有 payable。随机数盐是由用户自定,须为 bytes32 格式,例如在上面 Uniswap 的例子中,salt 为:bytes32 salt = keccak256(abi.encodePacked(token0, token1)); create2 还有一个优点,相...
Smart Contract Developer

Subscribe to xyyme.eth

Subscribe to xyyme.eth
Share Dialog
Share Dialog
<100 subscribers
<100 subscribers
Events 是 Solidity 中记录事件的工具,可以简单理解为日志。Events 的优点在于,一是能够利用较少的 Gas 就能将数据记录在区块链上,二是可以方便链下对链上数据进行监听。
先来看一段简单的代码:
pragma solidity 0.8.10;
contract EventsDemo {
// 定义 Events
event Transfer(
address indexed from,
address indexed to,
uint256 amount
);
function transfer(address to, uint256 amount) external {
// 发送 Events
emit Transfer(msg.sender, to, amount);
}
}
在上述代码中,我们通过 event 关键字定义事件,通过 emit 关键字发送事件。这样,在调用 transfer 函数的时候,就会发送 Transfer 事件,将其数据记录在链上。
接下来我们实际部署一下,并调用 transfer 看看会发生什么。
调用 transfer 时的参数我们分别设为:
to → 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
amount → 123
调用成功后,查看 Etherscan 的 Logs 页面:

我们可以看到页面中有三部分,分别是:
Address
Topics
Data
其中 Address 是合约地址,这个很容易理解,那么剩下的两个字段分别代表什么呢?
Events 打印出的日志中包含两种数据结构,分别是 topic 和 data。每个日志中最多可以包含 4 个 topic,data 则没有限制。topic 的第一个字段默认是事件签名,在上例中,Topics[0] 中的 0xddf25…b3ef 哈希值就是 Transfer 事件的签名:
keccak256(bytes("Transfer(address,address,uint256)"))
图示即为(图片来自于这里):

我们注意到,前面定义的 Transfer 事件中,from 和 to 字段都带有一个 indexed 关键字,它的作用就是将该字段列为 topic。由于最多限制 4 个 topic,且第一个 topic 默认是事件签名,因此在合约中定义事件的时候,最多只能有 3 个字段可以使用 indexed。那么 topic 到底有什么用呢?
将字段列为 topic 可以方便检索。例如,我们想要在链下监听 Transfer 事件,但是并不想监听每一笔事件,只想监听 from 是我的地址的事件,那么就必须将 from 设置为 topic,否则 data 类型的日志是没有办法做到的。
在上例中,我们将 from 和 to 设置为 indexed,因此我们可以在上图中看到,有三个 topic,分别是
事件签名
from,这里是调用合约的地址
to
那么剩下的 data 部分就是 amount 字段的数据了。
data 的内容是将数据经过 abi-encoded 的结果。
我们在 Etherscan 上传代码,刷新页面:

这时 Etherscan 就可以识别出事件的 name,并且可以将 topic 和 data 数据解码。
前面我们说到,一个事件日志中,最多只能有 4 个 topic,也就是说最多有 3 个 indexed。同时,topic 本身也有长度限制,每个 topic 只能容纳 32 个字节的数据。那么像 string 或者 bytes 这种非定长的数据能否设置为 topic 呢?
答案是可以的,但是需要对其内容进行哈希。我们来看一段代码:
pragma solidity 0.8.10;
contract EventsDemo {
event Message(
address indexed from,
address indexed to,
string message
);
function sendMessage(address to, string memory message) public {
emit Message(msg.sender, to, message);
}
}
首先,我们先将 Message 事件中的 message 字段设置非 indexed,部署并调用,调用的参数分别是:
to → 0x5b38da6a701c568545dcfcb03fcb875f56beddc4
message → Hello World
结果为:

message 字段位于 data 中,可以被正常解码。
接下来,我们给 message 加上 indexed:
event Message(
address indexed from,
address indexed to,
string indexed message
);
部署并调用,使用同样的参数,结果为:

此时,data 中内容为空,而 topic[3] 的值就是 message 的哈希值:
keccak256(bytes("Hello World"))
根据以太坊黄皮书的内容,日志的基础费用是 375 Gas。另外每个 topic 同样需要花费 375 Gas,data 中每个字节需要花费 8 Gas。


因此,我们前面的 Transfer 事件花费的 Gas 为:
1756 = 375(基础费用)+ 375 * 3(3 个
topic)+ 32 * 8(data中共 32 字节)
Solidity 老版本中还存在一种底层调用,例如:
pragma solidity >=0.4.10 <0.7.0;
contract C {
function f() public payable {
uint256 _id = 0x420042;
log2(
bytes32(msg.value),
bytes32(uint256(msg.sender)),
bytes32(_id)
);
}
}
包括 log0、log1、log2、log3、log4,不过新版文档中已经去除了这部分内容,因此我们也不再介绍,了解其存在即可,感兴趣的朋友可以查看这里。
链下监听事件有很多种方法,几乎所有主流语言都有对应的 web3 库可以实现。我前面写的一篇 ethers 使用教程的文章中包含了这部分内容,感兴趣的朋友可以前往查看,这里就不再赘述了。
Solidity 中 Events 是一个很实用的日志工具,花费 Gas 少,利于链下来监听链上交易。
欢迎和我交流
https://docs.soliditylang.org/en/latest/contracts.html#events
Events 是 Solidity 中记录事件的工具,可以简单理解为日志。Events 的优点在于,一是能够利用较少的 Gas 就能将数据记录在区块链上,二是可以方便链下对链上数据进行监听。
先来看一段简单的代码:
pragma solidity 0.8.10;
contract EventsDemo {
// 定义 Events
event Transfer(
address indexed from,
address indexed to,
uint256 amount
);
function transfer(address to, uint256 amount) external {
// 发送 Events
emit Transfer(msg.sender, to, amount);
}
}
在上述代码中,我们通过 event 关键字定义事件,通过 emit 关键字发送事件。这样,在调用 transfer 函数的时候,就会发送 Transfer 事件,将其数据记录在链上。
接下来我们实际部署一下,并调用 transfer 看看会发生什么。
调用 transfer 时的参数我们分别设为:
to → 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
amount → 123
调用成功后,查看 Etherscan 的 Logs 页面:

我们可以看到页面中有三部分,分别是:
Address
Topics
Data
其中 Address 是合约地址,这个很容易理解,那么剩下的两个字段分别代表什么呢?
Events 打印出的日志中包含两种数据结构,分别是 topic 和 data。每个日志中最多可以包含 4 个 topic,data 则没有限制。topic 的第一个字段默认是事件签名,在上例中,Topics[0] 中的 0xddf25…b3ef 哈希值就是 Transfer 事件的签名:
keccak256(bytes("Transfer(address,address,uint256)"))
图示即为(图片来自于这里):

我们注意到,前面定义的 Transfer 事件中,from 和 to 字段都带有一个 indexed 关键字,它的作用就是将该字段列为 topic。由于最多限制 4 个 topic,且第一个 topic 默认是事件签名,因此在合约中定义事件的时候,最多只能有 3 个字段可以使用 indexed。那么 topic 到底有什么用呢?
将字段列为 topic 可以方便检索。例如,我们想要在链下监听 Transfer 事件,但是并不想监听每一笔事件,只想监听 from 是我的地址的事件,那么就必须将 from 设置为 topic,否则 data 类型的日志是没有办法做到的。
在上例中,我们将 from 和 to 设置为 indexed,因此我们可以在上图中看到,有三个 topic,分别是
事件签名
from,这里是调用合约的地址
to
那么剩下的 data 部分就是 amount 字段的数据了。
data 的内容是将数据经过 abi-encoded 的结果。
我们在 Etherscan 上传代码,刷新页面:

这时 Etherscan 就可以识别出事件的 name,并且可以将 topic 和 data 数据解码。
前面我们说到,一个事件日志中,最多只能有 4 个 topic,也就是说最多有 3 个 indexed。同时,topic 本身也有长度限制,每个 topic 只能容纳 32 个字节的数据。那么像 string 或者 bytes 这种非定长的数据能否设置为 topic 呢?
答案是可以的,但是需要对其内容进行哈希。我们来看一段代码:
pragma solidity 0.8.10;
contract EventsDemo {
event Message(
address indexed from,
address indexed to,
string message
);
function sendMessage(address to, string memory message) public {
emit Message(msg.sender, to, message);
}
}
首先,我们先将 Message 事件中的 message 字段设置非 indexed,部署并调用,调用的参数分别是:
to → 0x5b38da6a701c568545dcfcb03fcb875f56beddc4
message → Hello World
结果为:

message 字段位于 data 中,可以被正常解码。
接下来,我们给 message 加上 indexed:
event Message(
address indexed from,
address indexed to,
string indexed message
);
部署并调用,使用同样的参数,结果为:

此时,data 中内容为空,而 topic[3] 的值就是 message 的哈希值:
keccak256(bytes("Hello World"))
根据以太坊黄皮书的内容,日志的基础费用是 375 Gas。另外每个 topic 同样需要花费 375 Gas,data 中每个字节需要花费 8 Gas。


因此,我们前面的 Transfer 事件花费的 Gas 为:
1756 = 375(基础费用)+ 375 * 3(3 个
topic)+ 32 * 8(data中共 32 字节)
Solidity 老版本中还存在一种底层调用,例如:
pragma solidity >=0.4.10 <0.7.0;
contract C {
function f() public payable {
uint256 _id = 0x420042;
log2(
bytes32(msg.value),
bytes32(uint256(msg.sender)),
bytes32(_id)
);
}
}
包括 log0、log1、log2、log3、log4,不过新版文档中已经去除了这部分内容,因此我们也不再介绍,了解其存在即可,感兴趣的朋友可以查看这里。
链下监听事件有很多种方法,几乎所有主流语言都有对应的 web3 库可以实现。我前面写的一篇 ethers 使用教程的文章中包含了这部分内容,感兴趣的朋友可以前往查看,这里就不再赘述了。
Solidity 中 Events 是一个很实用的日志工具,花费 Gas 少,利于链下来监听链上交易。
欢迎和我交流
https://docs.soliditylang.org/en/latest/contracts.html#events
No activity yet