当前位置:   article > 正文

solidity——默克尔树实现零成本空投_基于merkle树,验证did身份,获取erc20代币空投

基于merkle树,验证did身份,获取erc20代币空投

在web3.0中,大家对空投肯定不陌生。如果项目方要大规模给用户进行空投,所需gas将是一笔不小的开支,特别是在以太坊链上,其次是大规模连续转账交易所涉及的事物原子性等问题也值得考虑。接下来将介绍一种相对低成本、安全性高的空投实现方式:通过默克尔树验证用户信息有效性并发放空投。

一、默克尔树介绍

默克尔树(Merkle Tree)是一种二叉树数据结构,它使用哈希函数将大量数据块组织成树状结构,用于快速验证大型数据集的完整性。默克尔树的主要原理包括以下几个关键点:

  1. 哈希函数: 默克尔树使用哈希函数对数据块进行哈希运算。常用的哈希函数如 SHA-256,SHA-3 等。

  2. 叶子节点: 待存储的原始数据被分割成固定大小的块,每个块作为树的叶子节点,通过哈希函数生成一个哈希值。

  3. 构建树结构: 叶子节点按顺序两两配对,将它们的哈希值拼接后再次进行哈希运算,生成一个新的哈希值。这个过程一直重复,直到树的根节点生成。

  4. 根节点: 默克尔树的根节点是最终的哈希值,称为 Merkle 根。它是树中所有数据块的紧凑表示。

  5. 证明: 如果需要验证某个特定数据块是否在树中,可以提供一组哈希值,称为默克尔证明(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。

二、实现方式

合约代码如下:

  1. // SPDX-License-Identifier: MIT
  2. pragma solidity ^0.8.20;
  3. import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
  4. import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
  5. import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
  6. import "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
  7. contract AirDrop is ReentrancyGuardUpgradeable{
  8. uint256 private index;
  9. using SafeERC20 for IERC20;
  10. mapping (bytes32 => mapping (address => uint256)) private indexs;
  11. mapping(uint256 => mapping (address => bool)) private isClaimed;
  12. //设置根、空投token
  13. function setRootAndToken (bytes32 _root,address _address) external {
  14. indexs[_root][_address] = ++index;
  15. }
  16. function hasClaimed(bytes32 _root,address token,address _address) external view returns (bool) {
  17. uint256 _index = indexs[_root][token];
  18. return isClaimed[_index][_address];
  19. }
  20. function claim(
  21. address account,
  22. uint256 amount,
  23. bytes32[] calldata merkleProof,
  24. bytes32 _root,
  25. address token
  26. ) external nonReentrant{
  27. uint256 _index = indexs[_root][token];
  28. require(!isClaimed[_index][account], 'Already claimed.');
  29. bytes32 _leaf = keccak256(abi.encodePacked(account, amount));
  30. bool isValidProof = MerkleProof.verifyCalldata(merkleProof,_root,_leaf);
  31. require(isValidProof, 'Invalid proof.');
  32. isClaimed[_index][account] = true;
  33. IERC20 dropToken = IERC20(token);
  34. uint256 bal = dropToken.balanceOf(address(this));
  35. require(
  36. bal >= amount,
  37. "Insufficient balance"
  38. );
  39. dropToken.safeTransfer(account, amount);
  40. }
  41. }
  • 后端导入空投白名单,对数据进行持久化,通常包括空投用户地址、空投数量,并计算出默克尔树root。
  • 通过调用合约中的setRootAndToken(bytes32 _root,address _address)函数,提前将root和空投token存储在合约状态变量中,并向合约账户转入本次空投总共花费的token数量。
  • 用户登陆前端页面后,从后端获取自己的空投数量、root、空投token等调用claim(address account,uint256 amount,bytes32[] calldata merkleProof,bytes32 _root,address token)所需参数,需要用户进行主动领取(其实是将成本转换到用户身上,目前主流的方式)。

三、验证 

这里通过hardhat的Mocha+chai.js来做测试用例:

  1. import {
  2. time,
  3. loadFixture,
  4. } from "@nomicfoundation/hardhat-toolbox/network-helpers";
  5. import { anyValue } from "@nomicfoundation/hardhat-chai-matchers/withArgs";
  6. import { expect } from "chai";
  7. import { ethers } from "hardhat";
  8. import { MerkleTree } from 'merkletreejs'
  9. // Same as `abi.encodePacked` in Solidity
  10. function encodePacked(address:any, spots:any) {
  11. return ethers.solidityPacked(["address", "uint256"],[address, spots])
  12. }
  13. describe("AirDrop",function(){
  14. async function deployTokenFixture() {
  15. const [owner,account1,account2,account3] = await ethers.getSigners();
  16. const token = await ethers.getContractFactory("AirDrop")
  17. const _token = await token.deploy()
  18. await _token.waitForDeployment();
  19. const myToken = await ethers.getContractFactory("MyToken")
  20. const _myToken = await myToken.deploy(owner.address,"My Token","MTK",100);
  21. await _myToken.waitForDeployment();
  22. console.log("AirDrop: "+await _token.getAddress());
  23. console.log("MyToken: "+await _myToken.getAddress());
  24. return {_token,_myToken,owner,account1,account2,account3}
  25. }
  26. describe("test",function(){
  27. it("",async function () {
  28. const { _token,_myToken, owner,account1,account2,account3 } = await loadFixture(deployTokenFixture);
  29. let tokenAdd = await _token.getAddress()
  30. let myTokenAdd = await _myToken.getAddress()
  31. let tx = await _myToken.transfer(tokenAdd,2);
  32. await tx.wait();
  33. console.log("AirDrop bal = ",await _myToken.balanceOf(tokenAdd))
  34. let list = [
  35. encodePacked(owner.address, 2),
  36. encodePacked(account1.address, 2),
  37. ];
  38. console.log("list = ",list);
  39. //构造树
  40. let tree = new MerkleTree(list, ethers.keccak256, { hashLeaves: true,sortPairs: true });
  41. // 获取树根
  42. let root = tree.getHexRoot();
  43. console.log("tree = ",tree.toString());
  44. console.log("root = ",root);
  45. await _token.setRootAndToken(root,myTokenAdd)
  46. //获取树叶
  47. let leaf = ethers.keccak256(list[0])
  48. //获取证明
  49. let proof = tree.getHexProof(leaf);
  50. console.log("leaf = ",leaf);
  51. console.log("proof = ",proof);
  52. console.log("hasClaimed = ",await _token.hasClaimed(root,myTokenAdd,owner.address))
  53. await _token.claim(owner.address, 2,proof,root,myTokenAdd)
  54. console.log("hasClaimed = ",await _token.hasClaimed(root,myTokenAdd,owner.address))
  55. })
  56. })
  57. })

这是验证结果:

  1. AirDrop
  2. test
  3. AirDrop: 0x5FbDB2315678afecb367f032d93F642f64180aa3
  4. MyToken: 0xe7f1725E7734CE288F8367e1Bb143E90bb3F0512
  5. AirDrop bal = 2n
  6. list = [
  7. '0xf39fd6e51aad88f6f4ce6ab8827279cfffb922660000000000000000000000000000000000000000000000000000000000000002',
  8. '0x70997970c51812dc3a010c7d01b50e0d17dc79c80000000000000000000000000000000000000000000000000000000000000002'
  9. ]
  10. tree = └─ 1a7a429f400d85c26a5848cca75bdd5058aa94d7ff5b42d936d8b1c2d8ef9743
  11. ├─ f23ba99f1ec426341536c572dc91ecc790ff86f74ea7aa03df4846a680dd4e73
  12. └─ 274996539fafc4b0887fdcfbe1c73bc1147c223b1ebedc6e4e8462a80707d2c7
  13. root = 0x1a7a429f400d85c26a5848cca75bdd5058aa94d7ff5b42d936d8b1c2d8ef9743
  14. leaf = 0xf23ba99f1ec426341536c572dc91ecc790ff86f74ea7aa03df4846a680dd4e73
  15. proof = [
  16. '0x274996539fafc4b0887fdcfbe1c73bc1147c223b1ebedc6e4e8462a80707d2c7'
  17. ]
  18. hasClaimed = false
  19. hasClaimed = true
  20. ✔ (2099ms)
  21. 1 passing (2s)

 欢迎大家批评指导,共同学习。(太困了!!!)

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/小蓝xlanll/article/detail/432757
推荐阅读
相关标签
  

闽ICP备14008679号