上周,本体宣布支持 EVM 的测试网正式部署并向全球开发者开放 EVM 兼容公测。同时,与知名代码审计机构慢雾科技合作发布《本体安全漏洞与威胁情报赏金计划》(https://slowmist.io/en/ontology/)正式启动,上报单个有效漏洞奖励最高可达12,000美金。
上一期,我们介绍了在本体上开发和部署 EVM 合约的工具,以及如何使用 MetaMask 插件数据客户端来管理以太坊数据客户端。这一期,我们将为您带来本体 EVM 合约开发流程演示。
第四部分 EVM 合约开发流程演示
下面我们将使用 Hardhat 工具来演示在本体网络中开发部署和测试 EVM 合约的完整流程。
4.1 环境准备
4.2 合约设计
4.2.1 合约逻辑
我们将以一个红包合约为例,该合约主要提供以下功能:
每次发红包需要指定红包金额和该红包数量。
例如,红包总金额是100个通证,红包的数量是10,即有10个不同的地址领取红包。为了简单起见,我们设置每个红包金额相等, 也就是每个地址可以领10个通证。
根据以上的逻辑我们可以设置如下的存储结构:
EIP20Interface public Token; // support Token address uint public nextPacketId; // the next redpacket ID // packetId -> Packet, store all the redpacket mapping(uint => Packet) public packets; //packetId -> address -> bool, store receive redpacket record mapping(uint => mapping(address => bool)) public receiveRecords; struct Packet { uint[] assetAmounts;// Number of Tokens per copy uint receivedIndex; // Number of red packets received }
4.2.2 定义合约事件
在合约执行的过程中,我们可以通过添加事件来追溯合约执行流程。
在本例中我们设计以下两个事件:
event SendRedPacket(uint packetId, uint amount); event ReceiveRedPacket(uint packetId, uint amount);
4.2.3 定义函数
sendRedPacket
1. 发红包。任何人都可以调用该接口,将一定量的通证打给该合约地址,从而其他的地址可以从该合约地址领取红包。
注意: 在调用该方法之前,需要先授权该合约地址能够从用户的地址把通证转移走,所以需要先调用该通证的 approve 方法。
function sendRedPacket(uint amount, uint packetNum) public payable returns (uint) { require(amount >= packetNum, "amount >= packetNum"); require(packetNum > 0 && packetNum < 100, "packetNum>0 && packetNum < 100"); uint before = Token.universalBalanceOf(address(this)); Token.universalTransferFrom(address(msg.sender), address(this), amount); uint afterValue = Token.universalBalanceOf(address(this)); uint delta = afterValue - before; uint id = nextPacketId; uint[] memory assetAmounts = new uint[](packetNum); for (uint i = 0; i < packetNum; i++) { assetAmounts[i] = delta / packetNum; } packets[id] = Packet({assetAmounts : assetAmounts, receivedIndex : 0}); nextPacketId = id + 1; emit SendRedPacket(id, amount); return id; }
receivePacket
2. 领取红包。任何地址都可以通过调用该接口领取红包,调用该接口的时候需要指定红包的 ID,即指定要领取的红包。
function receivePacket(uint packetId) public payable returns (bool) { require(packetId < nextPacketId, "not the redpacket"); Packet memory p = packets[packetId]; if (p.assetAmounts.length < 1) { return false; } require(p.receivedIndex < p.assetAmounts.length - 1, "It's over"); require(receiveRecords[packetId][address(msg.sender)] == false, "has received"); p.receivedIndex = p.receivedIndex + 1; bool res = Token.universalTransfer(msg.sender, p.assetAmounts[p.receivedIndex]); require(res, "Token transfer failed"); packets[packetId] = p; receiveRecords[packetId][address(msg.sender)] == true; emit ReceiveRedPacket(packetId, p.assetAmounts[p.receivedIndex]); return true; }
合约完整的代码请参考此链接:(https://github.com/ontio/ontology/tree/master/docs/specifications/evm_refernce/contract-demo/hardhatdemo/contracts)。
4.3 使用 Hardhat 编译和测试合约
4.3.1 创建 Hardhat 项目
mkdir hardhatdemo cd hardhatdemo npm init npm install --save-dev hardhat npx hardhat
4.3.2 修改 hardhat.config.js 文件
添加测试网节点配置信息
module.exports = {
defaultNetwork: "ontology_testnet",
networks: {
hardhat: {},
ontology_testnet: {
url: "http://polaris2.ont.io:20339",
chainId: 5851,
gasPrice:500,
gas:2000000,
timeout:10000000,
accounts: ["用户私钥1",
"用户私钥2"]
}
},
solidity: {
version: "0.8.0",
settings: {
optimizer: {
enabled: true,
runs: 200
}
}
},
};
accounts 字段指定的私钥数组,对应的地址需要有测试网的 ONG 来支付交易的手续费,可以在这里(https://developer.ont.io/)领取测试网 ONG。
4.3.3 文件准备
把红包合约代码文件放到 contracts 文件夹下,为了支持 ERC-20 数字资产的转账,我们还需要 EIP20Interface.sol,UniversalERC20.sol 和 TokenDemo.sol 文件,可以从此处(https://github.com/ontio/ontology/tree/master/docs/specifications/evm_refernce/contract-demo/hardhatdemo)下载相关文件。
4.3.4 在 test 文件夹下添加测试代码
describe("RedPacket", function () {
let TokenDemo, redPacket, owner, acct1, assetAmount, packetAmount; beforeEach(async function () { const TokenDemo = await ethers.getContractFactory("TokenDemo"); TokenDemo = await TokenDemo.deploy(10000000, "L Token", 18, "LT"); await TokenDemo.deployed(); const RedPacket = await ethers.getContractFactory("RedPacket"); redPacket = await RedPacket.deploy(TokenDemo.address); await redPacket.deployed(); [owner, acct1] = await ethers.getSigners(); assetAmount = 1000; packetAmount = 10; }); it("Token", async function () { expect(await redPacket.Token()).to.equal(TokenDemo.address); }); it("sendRedPacket", async function () { const approveTx = await TokenDemo.approve(redPacket.address, assetAmount); await approveTx.wait(); const sendRedPacketTx = await redPacket.sendRedPacket(assetAmount, packetAmount); await sendRedPacketTx.wait(); let balance = await TokenDemo.balanceOf(redPacket.address); expect(balance.toString()).to.equal(assetAmount.toString()); res = await redPacket.nextPacketId(); expect(res.toString()).to.equal("1"); await redPacket.connect(acct1).receivePacket(0); balance = await TokenDemo.balanceOf(acct1.address); expect(balance.toString()).to.equal((assetAmount / packetAmount).toString()); }); });
4.3.5 编译合约
在项目根目录执行如下命令编译合约
$ npx hardhat compile Compiling 5 files with 0.8.0 Compilation finished successfully
该命令执行完成后会生成如下的文件夹
├── artifacts ├── cache ├── contracts ├── hardhat.config.js ├── node_modules ├── package-lock.json ├── package.json ├── scripts └── test
4.3.6 测试合约
$ npx hardhat test
执行结果如下
$ npx hardhat test RedPacket ✓ Token ✓ sendRedPacket (16159ms) 2 passing (41s)
以上便是本体 EVM 合约开发的完整演示流程,希望我们提供的教程能够帮助到您。下期,我们将为您提供 Web3 API 参考,并教您如何使用 Ontology Bridge 本体跨链桥实现本体数字资产与以太坊数字资产的一键互跨,敬请期待。