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 还有一个优点,相...
Share Dialog
Share Dialog
Smart Contract Developer

Subscribe to xyyme.eth

Subscribe to xyyme.eth
<100 subscribers
<100 subscribers
Hardhat fork 可以使我们将主网的区块 fork 到本地,这样我们就可以在本地与真实的链上数据进行交互,同时也可以模拟任意账户,方便我们进行一些测试,速度快,并且不用花费 Gas。
首先,我们先创建一个 Hardhat 项目,不熟悉的朋友可以看看这里。创建完毕之后,就可以在本地 node 中 fork 区块了。使用命令(替换自己的 key):
npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/
这个命令会将当前最新的一个区块数据 fork 到本地,也就是说现在本地 node 链中存在的就是这个区块的数据。当然也可以指定区块号进行 fork:
npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/ --fork-block-number 14913700
每次 fork 都使用命令行指定这一堆参数很麻烦,因此可以将其放在 Hardhat 的配置文件
hardhat.config.js中:module.exports = { solidity: "0.8.4", networks: { hardhat: { // 添加 forking 内容 forking: { url: "https://eth-mainnet.alchemyapi.io/v2/<key>", // 如果不指定区块,则默认 fork 当前最新区块 blockNumber: 14913700 } } } };这样配置之后,就可以直接使用
npx hardhat node
此时本地 node 的数据就是 fork 的区块数据。
需要注意的一点是,在执行
npx hardhat test运行单元测试时,测试内容运行的链不是本地node,而是沙盒模式。因此如果想要在单元测试的沙盒模式中使用 fork 的数据,需要在配置文件中显式配置 forking 内容。如果希望单元测试在本地 node 中执行,需要指定网络:npx hardhat test --network localhost
接下来,我们利用单元测试来实际验证一下我们的 fork 是否成功。在
test文件夹下创建fork.test.js文件,编写代码:const { ethers } = require("hardhat"); describe("Fork", function () { it("Testing fork data", async function () { console.log((await ethers.provider.getBlockNumber()).toString()); }); });执行测试,输出
14913700,说明 fork 成功。现在我们来试试,实际调用主网的数据。我们以 USDT 合约为例,调用它的数据:
// 需要将 usdt 的 abi 保存在本地 const USDT_ABI = require("./usdt_abi.json"); // usdt 合约的主网地址 const USDT_ADDRESS = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; const { ethers } = require("hardhat"); describe("Fork", function () { it("Testing fork data", async function () { const provider = ethers.provider; // 构造 usdt 合约对象 const USDT = new ethers.Contract(USDT_ADDRESS, USDT_ABI, provider); // 调用 usdt 的 totalSupply let totalSupply = await USDT.totalSupply(); console.log(totalSupply.toString()); }); });结果为:
再去查看 USDT 的实际发行量:
验证数据正确。
fork 功能还有一个很有用的功能是,可以本地模拟任意账户,那么就意味着可以在本地拥有该地址的任意资产,这对我们做一些测试很有帮助。在测试文件中编写如下代码:
const mockAddress = "0x4c8CFE078a5B989CeA4B330197246ceD82764c63"; await network.provider.request({ method: "hardhat_impersonateAccount", params: [mockAddress], }); const signer = await ethers.provider.getSigner(mockAddress);这样就可以模拟地址
0x4c8CFE078a5B989CeA4B330197246ceD82764c63,此时signer就是该地址的信息。先来查看该地址的账户信息:let ETHBalance = await signer.getBalance(); console.log(`ETH balance is ${ETHBalance.toString() / 1e18}`); let USDTBalance = await USDT.balanceOf(signer.getAddress()) / 1e6; console.log(`USDT balance is ${USDTBalance.toString()}`);执行结果为:
主网查询其资产为:
验证数据正确。我们再来看看如何模拟该账户发送交易,假设需要向其他地址发送一万 USDT,代码为:
// 打印转账前的账户余额 let USDTBalanceA = await USDT.balanceOf(signer.getAddress()) / 1e6; console.log(`USDT balance before transfer is ${USDTBalanceA.toString()}`); const recipient = "0x652361ED2a8FB7E9b15Fe073AAb9fE2cFacb0B52"; let USDTBalanceB = await USDT.balanceOf(recipient) / 1e6; console.log(`USDT balance of recipient before transfer is ${USDTBalanceB.toString()}`); console.log("========Transfering========"); // 转账操作 await USDT.connect(signer).transfer( "0x652361ED2a8FB7E9b15Fe073AAb9fE2cFacb0B52", ethers.utils.parseUnits("10000", 6) ); // 打印转账后的账户余额 USDTBalanceA = await USDT.balanceOf(signer.getAddress()) / 1e6; console.log(`USDT balance after transfer is ${USDTBalanceA.toString()}`); USDTBalanceB = await USDT.balanceOf(recipient) / 1e6; console.log(`USDT balance of recipient after transfer is ${USDTBalanceB.toString()}`);结果为:
验证转账成功。
fork 下还有一些功能我们这里没有介绍,例如
// 设置账户余额 await network.provider.send("hardhat_setBalance", [ "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B", "0x1000", ]); // 设置账户 nonce await network.provider.send("hardhat_setNonce", [ "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B", "0x21", ]);感兴趣的朋友可以查看文档自行了解。
Hardhat fork 在平时的测试中非常方便,例如我们想要测试闪电贷,套利合约等,如果在主网测试,一是很慢,二是会花费 Gas。不过这个功能也不是 Hardhat 的专属,Ganache 和 Foundry 也有 fork 功能,感兴趣的朋友可以了解一下。
欢迎和我交流
https://hardhat.org/hardhat-network/guides/mainnet-forking
https://hardhat.org/hardhat-network/reference
https://stackoverflow.com/questions/71106843/check-balance-of-erc20-token-in-hardhat-using-ethers-js
Hardhat fork 可以使我们将主网的区块 fork 到本地,这样我们就可以在本地与真实的链上数据进行交互,同时也可以模拟任意账户,方便我们进行一些测试,速度快,并且不用花费 Gas。
首先,我们先创建一个 Hardhat 项目,不熟悉的朋友可以看看这里。创建完毕之后,就可以在本地 node 中 fork 区块了。使用命令(替换自己的 key):
npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/
这个命令会将当前最新的一个区块数据 fork 到本地,也就是说现在本地 node 链中存在的就是这个区块的数据。当然也可以指定区块号进行 fork:
npx hardhat node --fork https://eth-mainnet.alchemyapi.io/v2/ --fork-block-number 14913700
每次 fork 都使用命令行指定这一堆参数很麻烦,因此可以将其放在 Hardhat 的配置文件
hardhat.config.js中:module.exports = { solidity: "0.8.4", networks: { hardhat: { // 添加 forking 内容 forking: { url: "https://eth-mainnet.alchemyapi.io/v2/<key>", // 如果不指定区块,则默认 fork 当前最新区块 blockNumber: 14913700 } } } };这样配置之后,就可以直接使用
npx hardhat node
此时本地 node 的数据就是 fork 的区块数据。
需要注意的一点是,在执行
npx hardhat test运行单元测试时,测试内容运行的链不是本地node,而是沙盒模式。因此如果想要在单元测试的沙盒模式中使用 fork 的数据,需要在配置文件中显式配置 forking 内容。如果希望单元测试在本地 node 中执行,需要指定网络:npx hardhat test --network localhost
接下来,我们利用单元测试来实际验证一下我们的 fork 是否成功。在
test文件夹下创建fork.test.js文件,编写代码:const { ethers } = require("hardhat"); describe("Fork", function () { it("Testing fork data", async function () { console.log((await ethers.provider.getBlockNumber()).toString()); }); });执行测试,输出
14913700,说明 fork 成功。现在我们来试试,实际调用主网的数据。我们以 USDT 合约为例,调用它的数据:
// 需要将 usdt 的 abi 保存在本地 const USDT_ABI = require("./usdt_abi.json"); // usdt 合约的主网地址 const USDT_ADDRESS = "0xdAC17F958D2ee523a2206206994597C13D831ec7"; const { ethers } = require("hardhat"); describe("Fork", function () { it("Testing fork data", async function () { const provider = ethers.provider; // 构造 usdt 合约对象 const USDT = new ethers.Contract(USDT_ADDRESS, USDT_ABI, provider); // 调用 usdt 的 totalSupply let totalSupply = await USDT.totalSupply(); console.log(totalSupply.toString()); }); });结果为:
再去查看 USDT 的实际发行量:
验证数据正确。
fork 功能还有一个很有用的功能是,可以本地模拟任意账户,那么就意味着可以在本地拥有该地址的任意资产,这对我们做一些测试很有帮助。在测试文件中编写如下代码:
const mockAddress = "0x4c8CFE078a5B989CeA4B330197246ceD82764c63"; await network.provider.request({ method: "hardhat_impersonateAccount", params: [mockAddress], }); const signer = await ethers.provider.getSigner(mockAddress);这样就可以模拟地址
0x4c8CFE078a5B989CeA4B330197246ceD82764c63,此时signer就是该地址的信息。先来查看该地址的账户信息:let ETHBalance = await signer.getBalance(); console.log(`ETH balance is ${ETHBalance.toString() / 1e18}`); let USDTBalance = await USDT.balanceOf(signer.getAddress()) / 1e6; console.log(`USDT balance is ${USDTBalance.toString()}`);执行结果为:
主网查询其资产为:
验证数据正确。我们再来看看如何模拟该账户发送交易,假设需要向其他地址发送一万 USDT,代码为:
// 打印转账前的账户余额 let USDTBalanceA = await USDT.balanceOf(signer.getAddress()) / 1e6; console.log(`USDT balance before transfer is ${USDTBalanceA.toString()}`); const recipient = "0x652361ED2a8FB7E9b15Fe073AAb9fE2cFacb0B52"; let USDTBalanceB = await USDT.balanceOf(recipient) / 1e6; console.log(`USDT balance of recipient before transfer is ${USDTBalanceB.toString()}`); console.log("========Transfering========"); // 转账操作 await USDT.connect(signer).transfer( "0x652361ED2a8FB7E9b15Fe073AAb9fE2cFacb0B52", ethers.utils.parseUnits("10000", 6) ); // 打印转账后的账户余额 USDTBalanceA = await USDT.balanceOf(signer.getAddress()) / 1e6; console.log(`USDT balance after transfer is ${USDTBalanceA.toString()}`); USDTBalanceB = await USDT.balanceOf(recipient) / 1e6; console.log(`USDT balance of recipient after transfer is ${USDTBalanceB.toString()}`);结果为:
验证转账成功。
fork 下还有一些功能我们这里没有介绍,例如
// 设置账户余额 await network.provider.send("hardhat_setBalance", [ "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B", "0x1000", ]); // 设置账户 nonce await network.provider.send("hardhat_setNonce", [ "0x0d2026b3EE6eC71FC6746ADb6311F6d3Ba1C000B", "0x21", ]);感兴趣的朋友可以查看文档自行了解。
Hardhat fork 在平时的测试中非常方便,例如我们想要测试闪电贷,套利合约等,如果在主网测试,一是很慢,二是会花费 Gas。不过这个功能也不是 Hardhat 的专属,Ganache 和 Foundry 也有 fork 功能,感兴趣的朋友可以了解一下。
欢迎和我交流
https://hardhat.org/hardhat-network/guides/mainnet-forking
https://hardhat.org/hardhat-network/reference
https://stackoverflow.com/questions/71106843/check-balance-of-erc20-token-in-hardhat-using-ethers-js
No activity yet