前言ERC20Permit是对扩展了ERC20标准的扩展,添加了一个permit函数,允许用户通过EIP-712签名修改授权,而不是通过msg.sender;ERC20Permit定义:ERC20Permit是对扩展了ERC20标准的扩展,添加了一个permit函数,
ERC20Permit是对扩展了 ERC20 标准的扩展,添加了一个
permit
函数,允许用户通过 EIP-712 签名修改授权,而不是通过msg.sender
;ERC20Permit
定义:ERC20Permit是对扩展了 ERC20 标准的扩展,添加了一个
permit
函数,允许用户通过 EIP-712 签名修改授权特点、优势
降低交易成本
- 减少交易数量:在标准的ERC20代币中,用户需要先执行
approve
交易授权一定数量的代币给接收者,然后再由接收者执行transferFrom
交易来转移代币,这需要两次上链交易。而使用ERC20Permit,可以将这两个步骤合并为一个交易,从而节省了gas费用。例如,标准ERC20流程中,approve
交易,transferFrom
交易;而使用ERC20Permit的transferWithPermit
交易,包括permit和transferFrom操作在内。- 支持批量处理:接收者可以将多个permit和transferFrom操作批量在一个交易中执行,进一步降低了gas消耗,这对于需要频繁进行代币授权和转移的场景非常有利,能够有效减少用户的交易成本和区块链网络的负载。
提升用户体验
- 简化操作流程:用户无需再进行单独的
approve
交易来授权代币转移,只需生成一个签名(这一操作是在链下进行的,不产生gas费用),然后由接收者调用transferWithPermit
函数即可完成代币的转移,简化了用户的操作步骤,使代币交互过程更加顺畅。- 避免重复授权:在一些应用场景中,用户可能需要频繁地授权不同数量的代币给不同的接收者。使用ERC20Permit,用户只需生成一次签名,就可以授权多个不同的转移操作,无需像传统方式那样每次都需要进行一次
approve
交易,避免了重复授权的繁琐操作,提升了用户体验。
说明:借助openzeppelin库实现;
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC20Permit} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "hardhat/console.sol";
contract MyTokenPermit is ERC20Permit {
constructor() ERC20("MyTokenPermit", "MTKP") ERC20Permit("MyTokenPermit") {
_mint(msg.sender, 1000000 * 10 ** decimals());
}
}
# 编译指令
# npx hardhat compile
测试说明:主要测试 允许并批准使用签名、截止日期过期、 签名无效 、过期随机数场景
const { ethers } = require("hardhat");
const { expect } = require("chai");
describe("MyTokenPermit", function () {
let MyTokenPermit;
let myTokenPermit;
let owner;
let spender;
let addr2;
beforeEach(async function () {
await deployments.fixture(["MyTokenPermit"]);
[owner, spender, addr2] = await ethers.getSigners();
[owner,addr1,addr2]=await ethers.getSigners();
const MyTokenPermitDeployment = await deployments.get("MyTokenPermit");
myTokenPermit = await ethers.getContractAt("MyTokenPermit",MyTokenPermitDeployment.address);//已经部署的合约交互
//同上代码
// const MyTokenPermitFactory = await ethers.getContractFactory("MyTokenPermit");
// myTokenPermit = await MyTokenPermitFactory.deploy();
// await myTokenPermit.waitForDeployment();
});
describe("Basic Token Operations", function () {
it("查看代币的 name 和 symbol", async function () {
expect(await myTokenPermit.name()).to.equal("MyTokenPermit");
expect(await myTokenPermit.symbol()).to.equal("MTKP");
});
it("查看代币的总量", async function () {
const ownerBalance = await myTokenPermit.balanceOf(owner.address);
expect(await myTokenPermit.totalSupply()).to.equal(ownerBalance);
});
});
describe("Permit", function () {
it("允许并批准使用签名", async function () {
const chainId = (await ethers.provider.getNetwork()).chainId;
// 构建域分隔符
const domain = {
name: "MyTokenPermit",
version: "1",
chainId: chainId,
verifyingContract: await myTokenPermit.getAddress()
};
// EIP2612 标准的 Permit 类型
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
const value = ethers.parseEther("100");
const deadline = Math.floor(Date.now() / 1000) + 60 * 60; // 1 hour from now
const nonce = await myTokenPermit.nonces(owner.address);
// 准备签名数据
const message = {
owner: owner.address,
spender: spender.address,
value: value,
nonce: nonce,
deadline: deadline
};
// 签名
const signature = await owner.signTypedData(domain, types, message);
const { v, r, s } = ethers.Signature.from(signature);
// 使用 permit
await myTokenPermit.permit(
owner.address,
spender.address,
value,
deadline,
v,
r,
s
);
// 验证授权额度
expect(await myTokenPermit.allowance(owner.address, spender.address)).to.equal(value);
// 测试转账
await myTokenPermit.connect(spender).transferFrom(owner.address, addr2.address, value);
expect(await myTokenPermit.balanceOf(addr2.address)).to.equal(value);
console.log("转账成功")
});
it("截止日期过期 失败", async function () {
const chainId = (await ethers.provider.getNetwork()).chainId;
const domain = {
name: "MyTokenPermit",
version: "1",
chainId: chainId,
verifyingContract: await myTokenPermit.getAddress()
};
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
const value = ethers.parseEther("100");
const deadline = Math.floor(Date.now() / 1000) - 60; // 1 minute ago
const nonce = await myTokenPermit.nonces(owner.address);
const message = {
owner: owner.address,
spender: spender.address,
value: value,
nonce: nonce,
deadline: deadline
};
const signature = await owner.signTypedData(domain, types, message);
const { v, r, s } = ethers.Signature.from(signature);
await expect(
myTokenPermit.permit(
owner.address,
spender.address,
value,
deadline,
v,
r,
s
)
).to.be.revertedWith("ERC20Permit: expired deadline");
});
it("签名无效 失败", async function () {
const chainId = (await ethers.provider.getNetwork()).chainId;
const domain = {
name: "MyTokenPermit",
version: "1",
chainId: chainId,
verifyingContract: await myTokenPermit.getAddress()
};
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
const value = ethers.parseEther("100");
const deadline = Math.floor(Date.now() / 1000) + 60 * 60;
const nonce = await myTokenPermit.nonces(owner.address);
const message = {
owner: owner.address,
spender: spender.address,
value: value,
nonce: nonce,
deadline: deadline
};
// 使用错误的签名者
const signature = await spender.signTypedData(domain, types, message);
const { v, r, s } = ethers.Signature.from(signature);
await expect(
myTokenPermit.permit(
owner.address,
spender.address,
value,
deadline,
v,
r,
s
)
).to.be.revertedWith("ERC20Permit: invalid signature");
});
it("过期随机数 失败", async function () {
const chainId = (await ethers.provider.getNetwork()).chainId;
const domain = {
name: "MyTokenPermit",
version: "1",
chainId: chainId,
verifyingContract: await myTokenPermit.getAddress()
};
const types = {
Permit: [
{ name: "owner", type: "address" },
{ name: "spender", type: "address" },
{ name: "value", type: "uint256" },
{ name: "nonce", type: "uint256" },
{ name: "deadline", type: "uint256" }
]
};
const value = ethers.parseEther("100");
const deadline = Math.floor(Date.now() / 1000) + 60 * 60;
const nonce = await myTokenPermit.nonces(owner.address);
const message = {
owner: owner.address,
spender: spender.address,
value: value,
nonce: nonce,
deadline: deadline
};
const signature = await owner.signTypedData(domain, types, message);
const { v, r, s } = ethers.Signature.from(signature);
// 第一次使用permit应该成功
await myTokenPermit.permit(
owner.address,
spender.address,
value,
deadline,
v,
r,
s
);
// 使用相同的签名再次调用permit应该失败
await expect(
myTokenPermit.permit(
owner.address,
spender.address,
value,
deadline,
v,
r,
s
)
).to.be.revertedWith("ERC20Permit: invalid signature");
});
});
});
# 测试指令
# npx hardhat test ./test/xxx.js
module.exports = async function ({getNamedAccounts,deployments}) {
const firstAccount= (await getNamedAccounts()).firstAccount;
const secondAccount= (await getNamedAccounts()).secondAccount;
const {deploy,log}=deployments
const MyTokenPermit=await deploy("MyTokenPermit",{
from:firstAccount,
args: [],//参数 代币名字,代币符号
log: true,
})
console.log('MyTokenPermit合约地址',MyTokenPermit.address)
}
module.exports.tags = ["all", "MyTokenPermit"];
# 部署指令
# npx hardhat deploy
以上就是基于EIP-712 签名修改授权标准,实现一个ERC20代币的增强版的合约,涵盖了开发、测试、部署全部流程以及对该标准使用场景和优点等方面的相关介绍。
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!