赞
踩
在web3.0中,大家对空投肯定不陌生。如果项目方要大规模给用户进行空投,所需gas将是一笔不小的开支,特别是在以太坊链上,其次是大规模连续转账交易所涉及的事物原子性等问题也值得考虑。接下来将介绍一种相对低成本、安全性高的空投实现方式:通过默克尔树验证用户信息有效性并发放空投。
默克尔树(Merkle Tree)是一种二叉树数据结构,它使用哈希函数将大量数据块组织成树状结构,用于快速验证大型数据集的完整性。默克尔树的主要原理包括以下几个关键点:
哈希函数: 默克尔树使用哈希函数对数据块进行哈希运算。常用的哈希函数如 SHA-256,SHA-3 等。
叶子节点: 待存储的原始数据被分割成固定大小的块,每个块作为树的叶子节点,通过哈希函数生成一个哈希值。
构建树结构: 叶子节点按顺序两两配对,将它们的哈希值拼接后再次进行哈希运算,生成一个新的哈希值。这个过程一直重复,直到树的根节点生成。
根节点: 默克尔树的根节点是最终的哈希值,称为 Merkle 根。它是树中所有数据块的紧凑表示。
证明: 如果需要验证某个特定数据块是否在树中,可以提供一组哈希值,称为默克尔证明(Merkle Proof)。这个证明包括沿着树路径的一系列哈希值,从叶子节点到根节点。通过检查证明的有效性,可以快速验证数据块的完整性。
Merkle Tree允许对大型数据结构的内容进行有效和安全的验证(Merkle Proof)。对于有N个叶子结点的Merkle Tree,在已知root根值的情况下,验证某个数据是否有效(属于Merkle Tree叶子结点)只需要log(N)个数据(也叫proof),非常高效。如果数据有误,或者给的proof错误,则无法还原出root根植。 下面的例子中,叶子L1的Merkle proof为Hash 0-1和Hash 1:知道这两个值,就能验证L1的值是不是在Merkle Tree的叶子中。 因为通过叶子L1我们就可以算出Hash 0-0,我们又知道了Hash 0-1,那么Hash 0-0和Hash 0-1就可以联合算出Hash 0,然后我们又知道Hash 1,Hash 0和Hash 1就可以联合算出Top Hash,也就是root节点的hash。
合约代码如下:
- // SPDX-License-Identifier: MIT
- pragma solidity ^0.8.20;
- import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
- import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
- import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
- import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
-
- contract AirDrop is ReentrancyGuardUpgradeable{
-
- uint256 private index;
- using SafeERC20 for IERC20;
- mapping (bytes32 => mapping (address => uint256)) private indexs;
- mapping(uint256 => mapping (address => bool)) private isClaimed;
-
- //设置根、空投token
- function setRootAndToken (bytes32 _root,address _address) external {
- indexs[_root][_address] = ++index;
- }
-
- function hasClaimed(bytes32 _root,address token,address _address) external view returns (bool) {
- uint256 _index = indexs[_root][token];
- return isClaimed[_index][_address];
- }
-
- function claim(
- address account,
- uint256 amount,
- bytes32[] calldata merkleProof,
- bytes32 _root,
- address token
- ) external nonReentrant{
- uint256 _index = indexs[_root][token];
- require(!isClaimed[_index][account], 'Already claimed.');
-
- bytes32 _leaf = keccak256(abi.encodePacked(account, amount));
- bool isValidProof = MerkleProof.verifyCalldata(merkleProof,_root,_leaf);
- require(isValidProof, 'Invalid proof.');
-
- isClaimed[_index][account] = true;
- IERC20 dropToken = IERC20(token);
- uint256 bal = dropToken.balanceOf(address(this));
- require(
- bal >= amount,
- "Insufficient balance"
- );
-
- dropToken.safeTransfer(account, amount);
- }
- }
这里通过hardhat的Mocha+chai.js来做测试用例:
- import {
- time,
- loadFixture,
- } from "@nomicfoundation/hardhat-toolbox/network-helpers";
- import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
- import { expect } from "chai";
- import { ethers } from "hardhat";
- import { MerkleTree } from 'merkletreejs'
-
- // Same as `abi.encodePacked` in Solidity
- function encodePacked(address:any, spots:any) {
- return ethers.solidityPacked(["address", "uint256"],[address, spots])
- }
-
- describe("AirDrop",function(){
- async function deployTokenFixture() {
- const [owner,account1,account2,account3] = await ethers.getSigners();
- const token = await ethers.getContractFactory("AirDrop")
- const _token = await token.deploy()
- await _token.waitForDeployment();
-
- const myToken = await ethers.getContractFactory("MyToken")
- const _myToken = await myToken.deploy(owner.address,"My Token","MTK",100);
- await _myToken.waitForDeployment();
-
- console.log("AirDrop: "+await _token.getAddress());
- console.log("MyToken: "+await _myToken.getAddress());
-
- return {_token,_myToken,owner,account1,account2,account3}
- }
-
- describe("test",function(){
- it("",async function () {
- const { _token,_myToken, owner,account1,account2,account3 } = await loadFixture(deployTokenFixture);
- let tokenAdd = await _token.getAddress()
- let myTokenAdd = await _myToken.getAddress()
-
- let tx = await _myToken.transfer(tokenAdd,2);
- await tx.wait();
- console.log("AirDrop bal = ",await _myToken.balanceOf(tokenAdd))
- let list = [
- encodePacked(owner.address, 2),
- encodePacked(account1.address, 2),
- ];
-
- console.log("list = ",list);
-
- //构造树
- let tree = new MerkleTree(list, ethers.keccak256, { hashLeaves: true,sortPairs: true });
- // 获取树根
- let root = tree.getHexRoot();
- console.log("tree = ",tree.toString());
- console.log("root = ",root);
- await _token.setRootAndToken(root,myTokenAdd)
-
- //获取树叶
- let leaf = ethers.keccak256(list[0])
- //获取证明
- let proof = tree.getHexProof(leaf);
- console.log("leaf = ",leaf);
- console.log("proof = ",proof);
-
- console.log("hasClaimed = ",await _token.hasClaimed(root,myTokenAdd,owner.address))
- await _token.claim(owner.address, 2,proof,root,myTokenAdd)
- console.log("hasClaimed = ",await _token.hasClaimed(root,myTokenAdd,owner.address))
- })
- })
- })
这是验证结果:
- AirDrop
- test
- AirDrop: 0x5FbDB2315678afecb367f032d93F642f64180aa3
- MyToken: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
- AirDrop bal = 2n
- list = [
- '0xf39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000002',
- '0x70997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000000000000000002'
- ]
- tree = └─ 1a7a429f400d85c26a5848cca75bdd5058aa94d7ff5b42d936d8b1c2d8ef9743
- ├─ f23ba99f1ec426341536c572dc91ecc790ff86f74ea7aa03df4846a680dd4e73
- └─ 274996539fafc4b0887fdcfbe1c73bc1147c223b1ebedc6e4e8462a80707d2c7
-
- root = 0x1a7a429f400d85c26a5848cca75bdd5058aa94d7ff5b42d936d8b1c2d8ef9743
- leaf = 0xf23ba99f1ec426341536c572dc91ecc790ff86f74ea7aa03df4846a680dd4e73
- proof = [
- '0x274996539fafc4b0887fdcfbe1c73bc1147c223b1ebedc6e4e8462a80707d2c7'
- ]
- hasClaimed = false
- hasClaimed = true
- ✔ (2099ms)
-
-
- 1 passing (2s)
欢迎大家批评指导,共同学习。(太困了!!!)
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。