SolidityFoundry Merkle Airdrop

devtools/2024/9/24 14:39:05/

Merkle airdrop

   Merkle Tree,也叫默克尔树或哈希树,是区块链的底层加密技术,被比特币和以太坊区块链广泛采用。Merkle Tree允许对大型数据结构的内容进行有效和安全的验证(Merkle Proof)。对于有N个叶子结点的Merkle Tree,在已知root根值的情况下,验证某个数据是否有效(属于Merkle Tree叶子结点)只需要ceil(log₂N)个数据(也叫proof),非常高效。如果数据有误,或者给的proof错误,则无法还原出root根植。 忘记的同学可以参考下方。

Merkle树-CSDN博客文章浏览阅读1.1k次,点赞2次,收藏6次。 merkle树 区块链中的每个区块都包含了产生于该区块的所有交易,且以Merkle树表示。 默克尔树(又叫哈希树)是一种二叉树,由一个根节点、一组中间节点和一组叶节点组成。最下面的叶节点包含存储数据或其哈希值,每个中间节点是它的两个孩子节点内容的哈希值,根节点也是由它的两个子节点内容的哈希值组成。默克尔树的特点是,底层数据的任何变动,都会传递到其父亲节点,一直到树...https://blog.csdn.net/xq723310/article/details/80153072

        所以我们可以利用Merkel的特性。在链下,创建以账户地址和数量为叶子(addr, amount)的Merkel数,并计算出root hash。然后将roothash放到链上,这样就不需要在链上记录大量address和amount,节省gas。当空投开始后,不需要项目方花费gas,去给每一位用户空投,用户可以自行调用合约领取;

        有用户想要领取空投时,可以进行调用合约进行claim,其实就是验证merkel的roothash;由于链上已经保存了一份roothash,只要在链上使用用户提供的信息生成的roothash与之前保存的一致,就可以证明该用户享有领取空投的权利。

准备工作

利用openzeppelin/merkle-tree,生成拥有空投资格用户的Merkel树.

import * as fs from 'fs'
import {StandardMerkleTree} from '@openzeppelin/merkle-tree'// 1. build a tree
const elements = [['0x0000000000000000000000000000000000000001', 1],['0x0000000000000000000000000000000000000002', 2],['0x0000000000000000000000000000000000000003', 3],['0x0000000000000000000000000000000000000004', 4],['0x0000000000000000000000000000000000000005', 5],['0x0000000000000000000000000000000000000006', 6],['0x0000000000000000000000000000000000000007', 7],['0x0000000000000000000000000000000000000008', 8],
]let merkleTree = StandardMerkleTree.of(elements, ['address', 'uint256'])
const root = merkleTree.root
const tree = merkleTree.dump()
console.log(merkleTree.render());
fs.writeFileSync('tree.json', JSON.stringify(tree))
fs.writeFileSync('root.json', JSON.stringify({root:root}))// get proof
const proofs = []
const mtree = StandardMerkleTree.load(JSON.parse(fs.readFileSync("tree.json", "utf8")));
for (const [i, v] of mtree.entries()) {proofs.push({'account':v[0], 'amount':v[1],'proof':mtree.getProof(i)})if (v[0] === '0x0000000000000000000000000000000000000001') {const proof = mtree.getProof(i);console.log('Value:', v);console.log('Proof:', proof);}
}
fs.writeFileSync('proofs.json', JSON.stringify(proofs))

 首先我们使用StandardMerkleTree.of生成了一个八个账户的merkel树并且记录了roothash;然后我们使用,getProof给每个用户都生成他自己的验证proof。用户想要领取空投的时候,需要提供自己proof——其实就是mekle的验证路径,这个一般都是由项目方保存就行了,保存在链下就可以了。这里还输出了三个json文件,这个三个文件,后边测试的时候,需要用到;

  • tree.json:merkle tree的信息;
  • root.json:merkle的roothash
  • proofs.json:所有用户的proof数据

链上合约

merkle 合约,这个我们使用openzeppelin的MerkelProof库,主要是把验证函数实现一下,就可以了。验证的时候,需要提供用户的proof,address,amount,就可以了;

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import "openzeppelin-contracts/contracts/utils/cryptography/MerkleProof.sol";contract MerkleAirdrop {using MerkleProof for bytes32[];bytes32 private _root;constructor(bytes32 root) {_root = root;}function verify(bytes32[] memory proof,address account,uint amount) public view returns (bool) {bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));return proof.verify(_root, leaf);}function verifyCalldata(bytes32[] calldata proof,address account,uint amount) public view returns (bool) {bytes32 leaf = keccak256(bytes.concat(keccak256(abi.encode(account, amount))));return proof.verifyCalldata(_root, leaf);}}

airdrop合约,主要实现了claim,享有空投资格的用户,调用之后,而就可以领取空投了;

contract Airdrop is MerkleAirdrop{event Claim(address to, uint256 amount);MockIToken public token;constructor(address _token, bytes32 _root) MerkleAirdrop(_root){token = MockIToken(_token);}function claim(bytes32[] memory proof, address account, uint256 amount)external returns (bool){verify(proof, account, amount);token.mint(account, amount);emit Claim(account, amount);}
}interface MockIToken {function mint(address to, uint256 amount) external;
}contract MockToken is ERC20 {constructor(string memory name, string memory symbol)ERC20(name, symbol) {}function mint(address account, uint amount) external {_mint(account, amount);}
}

 foundry测试

        使用foundry进行,测试部分比较简单,就是测试了,merkelproof的verify函数以及airdrop的cliam函数;这次测试比较有趣的部分是foundry的json解析部分。也不是特别难,大家可以自行搜索foundry的文档进行学习与联系。

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.21;import "forge-std/Test.sol";
import {stdJson} from "forge-std/StdJson.sol";
import "../src/MerkleAirdrop.sol";
import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";contract MerkleAirdropTest is Test {using stdJson for string;struct Proof {address account;uint amount;bytes32[] proof;}string private _jsonTree = vm.readFile("test/data/tree.json");string private _jsonRoot = vm.readFile("test/data/root.json");string private _jsonProofs = vm.readFile("test/data/proofs.json");bytes32 private _rootHash = _jsonRoot.readBytes32(".root");MerkleAirdrop private _testing;Airdrop private airdrop;MockToken private token;function setUp() public {_testing = new MerkleAirdrop(_rootHash);token = new MockToken("test", "TEST");airdrop = new Airdrop(address(token), _rootHash);}function test_verify() external {Proof[] memory proofs = abi.decode(_jsonProofs.parseRaw(""), (Proof[]));for (uint i = 0; i < proofs.length; ++i) {assertTrue(_testing.verify(proofs[i].proof,proofs[i].account,proofs[i].amount));}}function test_claim() external {Proof[] memory proofs = abi.decode(vm.parseJson(_jsonProofs), (Proof[]));for (uint i = 0; i < proofs.length; ++i) {vm.expectEmit();emit Airdrop.Claim(proofs[i].account, proofs[i].amount);airdrop.claim(proofs[i].proof,proofs[i].account,proofs[i].amount);assertEq(token.balanceOf(proofs[i].account), proofs[i].amount);}}
}

所有代码点这里;


http://www.ppmy.cn/devtools/98696.html

相关文章

在node.js环境中使用web服务器http-server运行html静态文件

http-server http-server是一个超轻量级web服务器&#xff0c;它可以将任何一个文件夹当作服务器的目录供自己使用。 当我们想要在服务器运行一些代码&#xff0c;但是又不会配置服务器的时候&#xff0c;就可以使用http-server就可以搞定了。 使用方法 因为http-server需要…

JAVA之MAC详解以及子线程MDC传递

MDC简介 MDC(Mapped Diagnostic Context)是用于分布式系统中跟踪和诊断日志的重要概念。是一个在Java项目中用于日志跟踪的工具&#xff0c;它允许你在多线程环境下关联和传递特定的上下文信息。 MDC是一个线程本地的、可维护的、可传递的上下文环境。在Java中&#xff0c;MDC…

9. ((type *)0) 和 (type *0)区别

((type *)0) 和 (type *0) 在 C 和 C 中有不同的含义和用途&#xff0c;主要区别在于括号的位置和解析方式。 1. ((type *)0) 的含义 type 是一个数据类型&#xff08;例如 int、char、float 等&#xff09;&#xff0c;0 是一个整数常量。type * 表示一个指向 type 类型的指…

竞争激烈的音频市场中,如何开发脱颖而出的有声听书软件

近年来&#xff0c;有声书和在线音频市场迅速发展&#xff0c;吸引了众多企业纷纷进入这一领域。然而&#xff0c;随着市场参与者的增加&#xff0c;行业竞争日益激烈&#xff0c;利润率逐渐下降&#xff0c;市场份额争夺也愈发激烈。对于那些资源有限的小型企业而言&#xff0…

NSSCTF练习记录:[SWPUCTF 2021 新生赛]crypto10

题目&#xff1a; AFFPGS{pbatenghyngvbaf!!!},建议直接秒了根据提交flag格式&#xff0c;NSSCTF{XXXX}判断目标字符串进行了字母偏移&#xff0c;偏移量为13&#xff0c;用工具直接解码得到答案。

ReTagList标签列表(API)

组件实现基于 Vue3 + Element Plus + Typescript,同时引用 vueUse + lodash-es + tailwindCss (不影响功能,可忽略) 基于ElTag实现的Tag列表,支持Tag列表多选,动态Tag列表 ReTagList标签列表 基础 简单展示Tag列表,可通过size指定尺寸 查看 /demo/tag-list/basic.md …

顶顶通呼叫中心中间件-一句话语音识别安装步骤

顶顶通呼叫中心中间件-一句话语音识别安装步骤&#xff0c;对接mod_vad。一句话识别&#xff08;http接口提交录音文件识别&#xff09; 一、安装asrproxy 1、将下载软件压缩包上传到需要安装的服务器中 2、SSH终端依次执行以下命令&#xff1a; mkdir -p /ddt/asrproxysud…

Axios介绍;前后端分离开发的介绍;YAPI的使用;Vue项目简介、入门;Elementui的使用;nginx介绍

1 Ajax 1.1 Ajax介绍 1.1.1 Ajax概述 我们前端页面中的数据&#xff0c;如下图所示的表格中的学生信息&#xff0c;应该来自于后台&#xff0c;那么我们的后台和前端是互不影响的2个程序&#xff0c;那么我们前端应该如何从后台获取数据呢&#xff1f;因为是2个程序&#xf…