Uniswap v3 无常损失分析
Sep 17, 202228 min. readUniswap v3 无常损失分析目标对 Uniswap v3 无常损失的定量分析;如何使用策略让 Uniswap v3 LP 获得更大的收益。Uniswap 概览基于恒定乘积的自动化做市商(AMM),去中心化交易所。 v1 版本:2018年11月解决了什么问题:传统交易所 order book 买卖双方不活跃导致的长时间挂单,交易效率低下功能:ETH ←→ ERC20 token 兑换带来的问题:token1 与 token2 之间的兑换需要借助 ETHUSDT → ETH → USDCv2 版本:2020年5月新功能自由组合交易对:token1 ←→ token2token1-token2 交易池LPers 提供流动性并赚取费用价格预言机(时间加权平均价格,TWAP)、闪电贷、最优化交易路径等带来的问题资金利用率低:在 x*y=k 的情况下,做市的价格区间在 (0, +∞) 的分布,当用户交易时,交易的量相比我们的流动性来说是很小的假设 ETH/DAI 交易对的实时价格为 1500 DAI/ETH,交易对的流动性池中共有资金:4...
Tornado Cash 基本原理
假设地址 A 发送了 100 ETH 给地址 B,由于在区块链上所有的数据都是公开的,所以全世界都知道地址 A 和地址 B 进行了一次交易,如果地址A和地址 B 属于同一个用户 Alice,则大家知道Alice仍然拥有 100 ETH,如果地址B属于用户 Bob,则大家知道 Bob 现在有 100ETH 了。一个问题就是:如何在交易的过程中保持隐蔽呢,或者说隐藏发送用户与接收用户之前的练习?那就要用到 Tornado Cash。 用户将资金存入Tornado Cash,然后将资金提取到另一个地址中,在区块链上记录上,这两个地址之间的联系就大概率断开了。那 Tornado Cash 是如何做到的呢?存款(deposit)过程首先我们看一下存款过程。用户在存款时需要生产两个随机数 secret 和 nullifier,并计算这两个数的一个哈希 commitment = hash(secret, nullifier),然后用户将需要混币的金额(比如 1 ETH)和 commitment 发送给 TC 合约的 deposit 函数,TC合约将保存这两个数据,commitment之后会用于...
Sui 数据类型讲解
这篇文章中,我们将介绍 Sui 中常见的数据结构,这些结构包含 Sui Move 和 Sui Framework 中提供的基础类型和数据结构,理解和熟悉这些数据结构对于 Sui Move 的理解和应用大有裨益。 首先,我们先快速复习一下 Sui Move 中使用到的基础类型。无符号整型(Integer)Move 包含六种无符号整型:u8,u16 u32,u64,u128和 u256。值的范围从 0 到 与类型大小相关的最大值。 这些类型的字面值为数字序列(例如 112)或十六进制文字,例如 0xFF。 字面值的类型可以选择添加为后缀,例如 112u8。 如果未指定类型,编译器将尝试从使用文字的上下文中推断类型。 如果无法推断类型,则假定为 u64。 对无符号整型支持的运算包括:算数运算: + - * % /位运算: & | ^ >> <<比较运算: > < >= <= == !=类型转换: as注意,类型转换不会截断,因此如果结果对于指定类型而言太大,转换将中止。简单示例:let a: u64 = 4; let b = 2u64; let hex_u64: u64 = 0xCAF...
<100 subscribers
Uniswap v3 无常损失分析
Sep 17, 202228 min. readUniswap v3 无常损失分析目标对 Uniswap v3 无常损失的定量分析;如何使用策略让 Uniswap v3 LP 获得更大的收益。Uniswap 概览基于恒定乘积的自动化做市商(AMM),去中心化交易所。 v1 版本:2018年11月解决了什么问题:传统交易所 order book 买卖双方不活跃导致的长时间挂单,交易效率低下功能:ETH ←→ ERC20 token 兑换带来的问题:token1 与 token2 之间的兑换需要借助 ETHUSDT → ETH → USDCv2 版本:2020年5月新功能自由组合交易对:token1 ←→ token2token1-token2 交易池LPers 提供流动性并赚取费用价格预言机(时间加权平均价格,TWAP)、闪电贷、最优化交易路径等带来的问题资金利用率低:在 x*y=k 的情况下,做市的价格区间在 (0, +∞) 的分布,当用户交易时,交易的量相比我们的流动性来说是很小的假设 ETH/DAI 交易对的实时价格为 1500 DAI/ETH,交易对的流动性池中共有资金:4...
Tornado Cash 基本原理
假设地址 A 发送了 100 ETH 给地址 B,由于在区块链上所有的数据都是公开的,所以全世界都知道地址 A 和地址 B 进行了一次交易,如果地址A和地址 B 属于同一个用户 Alice,则大家知道Alice仍然拥有 100 ETH,如果地址B属于用户 Bob,则大家知道 Bob 现在有 100ETH 了。一个问题就是:如何在交易的过程中保持隐蔽呢,或者说隐藏发送用户与接收用户之前的练习?那就要用到 Tornado Cash。 用户将资金存入Tornado Cash,然后将资金提取到另一个地址中,在区块链上记录上,这两个地址之间的联系就大概率断开了。那 Tornado Cash 是如何做到的呢?存款(deposit)过程首先我们看一下存款过程。用户在存款时需要生产两个随机数 secret 和 nullifier,并计算这两个数的一个哈希 commitment = hash(secret, nullifier),然后用户将需要混币的金额(比如 1 ETH)和 commitment 发送给 TC 合约的 deposit 函数,TC合约将保存这两个数据,commitment之后会用于...
Sui 数据类型讲解
这篇文章中,我们将介绍 Sui 中常见的数据结构,这些结构包含 Sui Move 和 Sui Framework 中提供的基础类型和数据结构,理解和熟悉这些数据结构对于 Sui Move 的理解和应用大有裨益。 首先,我们先快速复习一下 Sui Move 中使用到的基础类型。无符号整型(Integer)Move 包含六种无符号整型:u8,u16 u32,u64,u128和 u256。值的范围从 0 到 与类型大小相关的最大值。 这些类型的字面值为数字序列(例如 112)或十六进制文字,例如 0xFF。 字面值的类型可以选择添加为后缀,例如 112u8。 如果未指定类型,编译器将尝试从使用文字的上下文中推断类型。 如果无法推断类型,则假定为 u64。 对无符号整型支持的运算包括:算数运算: + - * % /位运算: & | ^ >> <<比较运算: > < >= <= == !=类型转换: as注意,类型转换不会截断,因此如果结果对于指定类型而言太大,转换将中止。简单示例:let a: u64 = 4; let b = 2u64; let hex_u64: u64 = 0xCAF...
Share Dialog
Share Dialog
Merkle 树现在普遍用来做线上数据验证。这篇文章主要解释和实现使用 Merkle 树做 NFT 白名单验证。
使用 Merkle 树做 NFT 白名单验证,简单来说就是将所有的白名单钱包地址做为 Merkle 树的叶节点生成一棵 Merkle 树,在部署的NFT 合约中只存储 Merkle 树的 root hash,这样避免了在合约中存储所有白名单地址带来的高额 gas 费用。在 mint 时,前端生成钱包地址的 Merkle proof,调用合约进行验证即可。
一次验证过程前端和合约运行过程如图:
![图片来自 [3]](https://img.paragraph.com/cdn-cgi/image/format=auto,width=3840,quality=85/https://storage.googleapis.com/papyrus_images/c6fa3e523e9070f98dfb6d3af6ff2c88b56bc698025c7a5754bb264c18d54469.png)
详情请参见:https://en.wikipedia.org/wiki/Merkle_tree
![图片来自 [1]](https://img.paragraph.com/cdn-cgi/image/format=auto,width=3840,quality=85/https://storage.googleapis.com/papyrus_images/7427cd473bbef06b0cb9c166765fcb97638624646285a2eb2bce5ccb126e0ae1.png)
比如,以水果单词作为叶节点,生成 Merkle 树的结构如下:
![图片来自 [2]](https://img.paragraph.com/cdn-cgi/image/format=auto,width=3840,quality=85/https://storage.googleapis.com/papyrus_images/0ad1e8f8c9424d00cc7a35cfe7971ae29662369981e2f95b194fda253783a032.png)
我们简单实现 Merkle 验证的过程,此合约包含以下功能:
设置 Merkle 根哈希: setSaleMerkleRoot
验证 Merkle proof: isValidMerkleProof
mint 并记录是否已经mint: mint
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
contract Merkle is Ownable {
bytes32 public saleMerkleRoot;
mapping(address => bool) public claimed;
function setSaleMerkleRoot(bytes32 merkleRoot) external onlyOwner {
saleMerkleRoot = merkleRoot;
}
modifier isValidMerkleProof(bytes32[] calldata merkleProof, bytes32 root) {
require(
MerkleProof.verify(
merkleProof,
root,
keccak256(abi.encodePacked(msg.sender))
),
"Address does not exist in list"
);
_;
}
function mint(bytes32[] calldata merkleProof)
external
isValidMerkleProof(merkleProof, saleMerkleRoot)
{
require(!claimed[msg.sender], "Address already claimed");
claimed[msg.sender] = true;
}
}
调用合约验证的 Merkle proof 需要在前端生成。生成过程需要用到 merkletreejs和 keccak256 两个库,前者用于创建 Merkle 树,后者用于生成哈希。
npm install --save merkletreejs keccak256
第一步,生成白名单地址的 Merkle 树:
const { MerkleTree } = require('merkletreejs');
const keccak256 = require('keccak256');
let whitelistAddresses = [
'0x169841AA3024cfa570024Eb7Dd6Bf5f774092088',
'0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33',
'0x0a290c8cE7C35c40F4F94070a9Ed592fC85c62B9',
'0x43Be076d3Cd709a38D2f83Cd032297a194196517',
'0xC7FaB03eecA24CcaB940932559C5565a4cE9cFFb',
'0xE4336D25e9Ca0703b574a6fd1b342A4d0327bcfa',
'0xeDcB8a28161f966C5863b8291E80dDFD1eB78491',
'0x77cbd0fa30F83a249da282e9fE90A86d7936FdE7',
'0xc39F9406284CcAeB426D0039a3F6ADe14573BaFe',
'0x16Beb6b55F145E4269279B82c040B7435f1088Ee',
'0x900b2909127Dff529f8b4DB3d83b957E6aE964c2',
'0xeA2A799793cE3D2eC6BcD066563f385F25401e95',
];
let leafNodes = whitelistAddresses.map(address => keccak256(address));
let tree = new MerkleTree(leafNodes, keccak256, { sortPairs: true });
console.log('Tree: ', tree.toString());
// const root = tree.getRoot();
// console.log('Root hash is: ', root.toString('hex'));
// Output:
//
// Tree: └─ c7ec7ffb250de2b95a1c690751b2826ec9d2999dd9f5c6f8816655b1590ca544
// ├─ 25f76dfbdd295dd14932a7aae9350055e72e9e317cd389c62d525884cc0d0f17
// │ ├─ 0613ec9d9455eaa91ffd480afaa50db8952ccf3cf1f04375f08f848dca194a86
// │ │ ├─ e0c3820340c8c58fa46f9ff9c8da5037a8f544f839abe168b76aff3fa391e177
// │ │ │ ├─ 1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4
// │ │ │ └─ 6abf3666623175adbce354196686c5e9853334b8eeb8324726a8ca89290c26d1
// │ │ └─ 6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b
// │ │ ├─ 4d313ef5510345a10724e131139b4556d77adaa109ba87087a600ea00bf92d18
// │ │ └─ 83260aa668bd8b075be8e34c6f6609ad5be3eee1470f7b30f46e85650097cb98
// │ └─ b0d6f760008340e3f60414d84b305702faa6418f44f31de07b10e05bf369eb3b
// │ ├─ f1e3a4717b4179aecf418fc3a0c92c728828ee399700d9bcb512c6424f86cb7b
// │ │ ├─ e00eb5681327801ed923ce4913468e70f833de797cfbc3df1e68dd13000f1fa6
// │ │ └─ d71c2d63734c3ca3c4257d118442c5796796234f77bb325759973b90e130dc62
// │ └─ 07ff91a64cd06c27a059056430bddfdf2d54e8833c0ccaa4642b39ed3b22579f
// │ ├─ 74b490baa6a881c8934d0aacc7fd778d1bac1e259f17856fccea372b6978bad6
// │ └─ 3845f80821bbaa15e35bfe9ace50761f9adeebf25b8472fae6e4ff0db394b2da
// └─ 4c880bf401add28c4e51270dfe16b28c3ca1b3d263ff7c5863fc8214b4046364
// └─ 4c880bf401add28c4e51270dfe16b28c3ca1b3d263ff7c5863fc8214b4046364
// ├─ 52a3b2fbc6bb6ee25b925ac9767246ceb24fd99c64a7dbc72847e6dc8dc52b81
// │ ├─ a61d6c75021de68e08a03f83d25738ac77e5e5cce1a63b4d48c2c819254b4375
// │ └─ 85c68207164ed77f53351eac1a14074cf5cd5b0fb1a664709adcd0ee4aa4ea8d
// └─ 1689b05d03db07df6c1f227c6f2ad46646a3edf11684c8081b821abbaf45a6dc
// ├─ 93b5a65af2ac0633f9f90c6e05c89c30e1d4aba0b6f98d2c2b9bda4118538d9f
// └─ 159859f50ff6cca7ef1060dcbc1a8daf59820817ea262f3f6107b431024eb9c4
我们可以看到根哈希值为 0xc7ec7ffb250de2b95a1c690751b2826ec9d2999dd9f5c6f8816655b1590ca544 ,这个值在调用合约函数 setSaleMerkleRoot 时需要用到,会保存在合约中。生成的 Merkle 证明需要存储在页面中,也可以存在 IPFS 中,在使用时加载使用。
第二步,需要生成参与 mint 地址的 Merkle 证明,假设使用 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33 地址进行 mint 操作:
let leaf = keccak256('0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33');
let proof = tree.getHexProof(leaf);
console.log('Proof of 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33: ', proof);
对应生成的证明为
Proof of 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33: [ '0x1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4', '0x6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b', '0xb0d6f760008340e3f60414d84b305702faa6418f44f31de07b10e05bf369eb3b', '0x4c880bf401add28c4e51270dfe16b28c3ca1b3d263ff7c5863fc8214b4046364' ]
同时我们将生成一个假的证明:
// another proof, for example
let anotherWhitelistAddresses = [
'0x169841AA3024cfa570024Eb7Dd6Bf5f774092088',
'0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33',
'0x0a290c8cE7C35c40F4F94070a9Ed592fC85c62B9',
'0x43Be076d3Cd709a38D2f83Cd032297a194196517',
];
let anotherLeafNodes = anotherWhitelistAddresses.map(address => keccak256(address));
let badTree = new MerkleTree(anotherLeafNodes, keccak256, { sortPairs: true });
let badLeaf = keccak256('0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33');
let badProof = badTree.getHexProof(badLeaf);
console.log('Bad proof of 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33: ', badProof);
// Bad proof of 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33: [
// '0x1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4',
// '0x6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b'
// ]
此过程将使用 Remix IDE 进行部署和测试:
使用 Remix 将合约部署到以太坊测试网 Rinkeby 中,得到合约地址为: 0xb3E2409199855ea9676dc5CFc9DefFd4A1b93eFe ;
调用 setSaleMerkleRoot 设置 Merkle 根哈希为 0xc7ec7ffb250de2b95a1c690751b2826ec9d2999dd9f5c6f8816655b1590ca544 ;
调用 mint ,传入非法 Merkle 证明:["0x1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4","0x6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b"] ,可以看到交易失败,显示 Fail with error 'Address does not exist in list ;
验证 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33 地址对应 mint 状态是否为 false;
调用 mint,传入合法的 Merkle 证明:
["0x1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4","0x6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b","0xb0d6f760008340e3f60414d84b305702faa6418f44f31de07b10e05bf369eb3b","0x4c880bf401add28c4e51270dfe16b28c3ca1b3d263ff7c5863fc8214b4046364"],可以看到交易成功;
验证 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33 地址对应 mint 状态是否为 true。
。
Merkle 树现在普遍用来做线上数据验证。这篇文章主要解释和实现使用 Merkle 树做 NFT 白名单验证。
使用 Merkle 树做 NFT 白名单验证,简单来说就是将所有的白名单钱包地址做为 Merkle 树的叶节点生成一棵 Merkle 树,在部署的NFT 合约中只存储 Merkle 树的 root hash,这样避免了在合约中存储所有白名单地址带来的高额 gas 费用。在 mint 时,前端生成钱包地址的 Merkle proof,调用合约进行验证即可。
一次验证过程前端和合约运行过程如图:
![图片来自 [3]](https://img.paragraph.com/cdn-cgi/image/format=auto,width=3840,quality=85/https://storage.googleapis.com/papyrus_images/c6fa3e523e9070f98dfb6d3af6ff2c88b56bc698025c7a5754bb264c18d54469.png)
详情请参见:https://en.wikipedia.org/wiki/Merkle_tree
![图片来自 [1]](https://img.paragraph.com/cdn-cgi/image/format=auto,width=3840,quality=85/https://storage.googleapis.com/papyrus_images/7427cd473bbef06b0cb9c166765fcb97638624646285a2eb2bce5ccb126e0ae1.png)
比如,以水果单词作为叶节点,生成 Merkle 树的结构如下:
![图片来自 [2]](https://img.paragraph.com/cdn-cgi/image/format=auto,width=3840,quality=85/https://storage.googleapis.com/papyrus_images/0ad1e8f8c9424d00cc7a35cfe7971ae29662369981e2f95b194fda253783a032.png)
我们简单实现 Merkle 验证的过程,此合约包含以下功能:
设置 Merkle 根哈希: setSaleMerkleRoot
验证 Merkle proof: isValidMerkleProof
mint 并记录是否已经mint: mint
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
contract Merkle is Ownable {
bytes32 public saleMerkleRoot;
mapping(address => bool) public claimed;
function setSaleMerkleRoot(bytes32 merkleRoot) external onlyOwner {
saleMerkleRoot = merkleRoot;
}
modifier isValidMerkleProof(bytes32[] calldata merkleProof, bytes32 root) {
require(
MerkleProof.verify(
merkleProof,
root,
keccak256(abi.encodePacked(msg.sender))
),
"Address does not exist in list"
);
_;
}
function mint(bytes32[] calldata merkleProof)
external
isValidMerkleProof(merkleProof, saleMerkleRoot)
{
require(!claimed[msg.sender], "Address already claimed");
claimed[msg.sender] = true;
}
}
调用合约验证的 Merkle proof 需要在前端生成。生成过程需要用到 merkletreejs和 keccak256 两个库,前者用于创建 Merkle 树,后者用于生成哈希。
npm install --save merkletreejs keccak256
第一步,生成白名单地址的 Merkle 树:
const { MerkleTree } = require('merkletreejs');
const keccak256 = require('keccak256');
let whitelistAddresses = [
'0x169841AA3024cfa570024Eb7Dd6Bf5f774092088',
'0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33',
'0x0a290c8cE7C35c40F4F94070a9Ed592fC85c62B9',
'0x43Be076d3Cd709a38D2f83Cd032297a194196517',
'0xC7FaB03eecA24CcaB940932559C5565a4cE9cFFb',
'0xE4336D25e9Ca0703b574a6fd1b342A4d0327bcfa',
'0xeDcB8a28161f966C5863b8291E80dDFD1eB78491',
'0x77cbd0fa30F83a249da282e9fE90A86d7936FdE7',
'0xc39F9406284CcAeB426D0039a3F6ADe14573BaFe',
'0x16Beb6b55F145E4269279B82c040B7435f1088Ee',
'0x900b2909127Dff529f8b4DB3d83b957E6aE964c2',
'0xeA2A799793cE3D2eC6BcD066563f385F25401e95',
];
let leafNodes = whitelistAddresses.map(address => keccak256(address));
let tree = new MerkleTree(leafNodes, keccak256, { sortPairs: true });
console.log('Tree: ', tree.toString());
// const root = tree.getRoot();
// console.log('Root hash is: ', root.toString('hex'));
// Output:
//
// Tree: └─ c7ec7ffb250de2b95a1c690751b2826ec9d2999dd9f5c6f8816655b1590ca544
// ├─ 25f76dfbdd295dd14932a7aae9350055e72e9e317cd389c62d525884cc0d0f17
// │ ├─ 0613ec9d9455eaa91ffd480afaa50db8952ccf3cf1f04375f08f848dca194a86
// │ │ ├─ e0c3820340c8c58fa46f9ff9c8da5037a8f544f839abe168b76aff3fa391e177
// │ │ │ ├─ 1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4
// │ │ │ └─ 6abf3666623175adbce354196686c5e9853334b8eeb8324726a8ca89290c26d1
// │ │ └─ 6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b
// │ │ ├─ 4d313ef5510345a10724e131139b4556d77adaa109ba87087a600ea00bf92d18
// │ │ └─ 83260aa668bd8b075be8e34c6f6609ad5be3eee1470f7b30f46e85650097cb98
// │ └─ b0d6f760008340e3f60414d84b305702faa6418f44f31de07b10e05bf369eb3b
// │ ├─ f1e3a4717b4179aecf418fc3a0c92c728828ee399700d9bcb512c6424f86cb7b
// │ │ ├─ e00eb5681327801ed923ce4913468e70f833de797cfbc3df1e68dd13000f1fa6
// │ │ └─ d71c2d63734c3ca3c4257d118442c5796796234f77bb325759973b90e130dc62
// │ └─ 07ff91a64cd06c27a059056430bddfdf2d54e8833c0ccaa4642b39ed3b22579f
// │ ├─ 74b490baa6a881c8934d0aacc7fd778d1bac1e259f17856fccea372b6978bad6
// │ └─ 3845f80821bbaa15e35bfe9ace50761f9adeebf25b8472fae6e4ff0db394b2da
// └─ 4c880bf401add28c4e51270dfe16b28c3ca1b3d263ff7c5863fc8214b4046364
// └─ 4c880bf401add28c4e51270dfe16b28c3ca1b3d263ff7c5863fc8214b4046364
// ├─ 52a3b2fbc6bb6ee25b925ac9767246ceb24fd99c64a7dbc72847e6dc8dc52b81
// │ ├─ a61d6c75021de68e08a03f83d25738ac77e5e5cce1a63b4d48c2c819254b4375
// │ └─ 85c68207164ed77f53351eac1a14074cf5cd5b0fb1a664709adcd0ee4aa4ea8d
// └─ 1689b05d03db07df6c1f227c6f2ad46646a3edf11684c8081b821abbaf45a6dc
// ├─ 93b5a65af2ac0633f9f90c6e05c89c30e1d4aba0b6f98d2c2b9bda4118538d9f
// └─ 159859f50ff6cca7ef1060dcbc1a8daf59820817ea262f3f6107b431024eb9c4
我们可以看到根哈希值为 0xc7ec7ffb250de2b95a1c690751b2826ec9d2999dd9f5c6f8816655b1590ca544 ,这个值在调用合约函数 setSaleMerkleRoot 时需要用到,会保存在合约中。生成的 Merkle 证明需要存储在页面中,也可以存在 IPFS 中,在使用时加载使用。
第二步,需要生成参与 mint 地址的 Merkle 证明,假设使用 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33 地址进行 mint 操作:
let leaf = keccak256('0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33');
let proof = tree.getHexProof(leaf);
console.log('Proof of 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33: ', proof);
对应生成的证明为
Proof of 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33: [ '0x1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4', '0x6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b', '0xb0d6f760008340e3f60414d84b305702faa6418f44f31de07b10e05bf369eb3b', '0x4c880bf401add28c4e51270dfe16b28c3ca1b3d263ff7c5863fc8214b4046364' ]
同时我们将生成一个假的证明:
// another proof, for example
let anotherWhitelistAddresses = [
'0x169841AA3024cfa570024Eb7Dd6Bf5f774092088',
'0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33',
'0x0a290c8cE7C35c40F4F94070a9Ed592fC85c62B9',
'0x43Be076d3Cd709a38D2f83Cd032297a194196517',
];
let anotherLeafNodes = anotherWhitelistAddresses.map(address => keccak256(address));
let badTree = new MerkleTree(anotherLeafNodes, keccak256, { sortPairs: true });
let badLeaf = keccak256('0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33');
let badProof = badTree.getHexProof(badLeaf);
console.log('Bad proof of 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33: ', badProof);
// Bad proof of 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33: [
// '0x1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4',
// '0x6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b'
// ]
此过程将使用 Remix IDE 进行部署和测试:
使用 Remix 将合约部署到以太坊测试网 Rinkeby 中,得到合约地址为: 0xb3E2409199855ea9676dc5CFc9DefFd4A1b93eFe ;
调用 setSaleMerkleRoot 设置 Merkle 根哈希为 0xc7ec7ffb250de2b95a1c690751b2826ec9d2999dd9f5c6f8816655b1590ca544 ;
调用 mint ,传入非法 Merkle 证明:["0x1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4","0x6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b"] ,可以看到交易失败,显示 Fail with error 'Address does not exist in list ;
验证 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33 地址对应 mint 状态是否为 false;
调用 mint,传入合法的 Merkle 证明:
["0x1575cc1dded49f942913392f94716824d29b8fa45876b2db6295d16a606533a4","0x6c42c6099e51e28eef8f19f71765bb42c571d5c7177996f177606138f65c0c2b","0xb0d6f760008340e3f60414d84b305702faa6418f44f31de07b10e05bf369eb3b","0x4c880bf401add28c4e51270dfe16b28c3ca1b3d263ff7c5863fc8214b4046364"],可以看到交易成功;
验证 0xc12ae5Ba30Da6eB11978939379D383beb5Df9b33 地址对应 mint 状态是否为 true。
。
No comments yet