if __name__ == '__main__': gitbook_url = 'https://docs.zerolend.xyz'# Replace with the GitBook URL output_file = 'documentation.md'# Desired output markdown file
main(gitbook_url, output_file)
Replace above official document url to whatever you need.After modify it to your own url, run the following python script.
1
python3 gitbook_scraper.py
After successlly execute the python script you’ll see the documentation.md on your root directory.
Feed the markdown file to AI
Let’s take claude.ai as example.
Copy the generated flowchart to online mermaid website.
view the flowchart online
Jump to website https://mermaid.live/ , paste the copyed flowchart code to it.
Why we need this ?
As we know, Web3 security competitions are time-limited. Participants who grasp the entire system quickly gain an advantage in identifying more vulnerabilities. With the help of AI we can generate flowchart in minitues. Furthermore, we have a summary of the entire project. I’m not entirely convinced that AI will replace auditors in the near future, but I am certain that those who don’t utilize AI will be left behind.
1.SettleLongPremium is incorrectly implemented: premium should be deducted instead of added
summary
1 2 3 4 5
// current available assets belonging to PLPs (updated after settlement) excluding any premium paid int256 updatedAssets = int256(uint256(s_poolAssets)) - swappedAmount;
// add premium to be paid/collected on position close > int256 tokenToPay = -realizedPremium;
In the code, the int type is used for numbers, and the sign of the subtrahend is used to control whether the final result is an increase or a decrease.
root cause
As a result, an error in the sign of the input parameters leads to the final operation being contrary to the expectation.
learned
When we use the int type, we need to pay special attention to the sign of the values
When deployNewPool is called it uses the spot price of the pool, which can be manipulated through a flashloan and thus could return a highly inaccurate result.
root cause
uses spot price
learned
spot price can be manipulated through a flashloan, try to use TWAP price instead.
2._validatePositionList() does not check for duplicate tokenIds, allowing attackers to bypass solvency checks
summary
The user can pass in an array of tokenIds, but there is no check for duplicates.
root cause
no check for duplicates
learned
Think about if pass in array need to be checked for duplicates.
3.Removed liquidity can overflow when calling SemiFungiblePositionManager.mintTokenizedPosition function
summary
1 2 3 4 5 6
if (!isBurn) { // we can't remove more liquidity than we add in the first place, so this can't overflow unchecked { removedLiquidity += chunkLiquidity; } }
options can be repeatly which can lead to removedLiquidity sum up mutiple times.
root cause
unchecked block overflow
learned
need to double check the unchecked block make sure it can not be overflow
4.Wrong leg chunkKey calculation in haircutPremia function
summary
when loop tokenId , uses always the index 0 when calculating the leg chunkKey instead of using the actual leg index
root cause
logic error
learned
pay more attention to the logic.
5.Panoptic pool can be non-profitable by specific Uniswap governance
summary
The developers only considered the current range of Uniswap fees, but these fees may change through governance, affecting the existing logic.
root cause
fee may changes in the future.
learned
It is necessary to consider the impact of governance on the current fees.
1.DAO unable to withdraw their funds due to Convex admin action
summary
The administrator can cause a DoS (Denial of Service) in the protocol by passing malicious parameters
root cause
according to docs : admin’s action is RESTRICTED
learned
When a role’s behavior is RESTRICTED, it is necessary to examine the consequences of any suspicious actions
2.Inadequate Allowance Handling in convertAndForward Function of OCT_DAO & OCT_YDL
summary
stricted allowance assertion check lead to transaction failed
root cause
protocol suffers from inadequate handling of token allowances for the 1inch router,however they are not reset afterward.
learned
take care of the allowance assertion check
3.cannot forward extra rewards from both OCY_Convex to OCT_YDL
summary
1 2
- if (rewardAmount > 0) { IERC20(rewardContract).safeTransfer(OCT_YDL, rewardAmount); } + if (rewardAmount > 0) { IBaseRewardPool_OCY_Convex_C(rewardContract).rewardToken().safeTransfer(OCT_YDL, rewardAmount); }
root cause
use safeTransfer in a none erc20 contract
learned
4.ZivoeYDL::earningsTrancheuse() always assumes that daysBetweenDistributions have passed, which might not be the case
summary
The protocol relies on keepers to call distributeYield. However, there is no guarantee that the keeper will make the call immediately.
root cause
The calculation of the APY depends on block.timestamp.
learned
When the calculation of APY depends on the timestamp, ensure it is called immediately
5.ZivoeYDL::distributeYield yield distribution is flash-loan manipulatable
summary
distributeYield is depends on totalSupply, however totalSupply can be manipulable through a flashloan.A 1-transaction inflated staked amount allows to inflate stakers distribution at the loss of vesters distribution
root cause
distributeYield amount is calculated with totalSupply
6.distributeYield() calls earningsTrancheuse() with outdated emaSTT & emaJTT while calculating senior & junior tranche yield distributions
summary
The earningsTrancheuse function uses emaSTT and emaJTT to calculate earnings, and then updates the latest emaSTT and emaJTT. It is recommended to update the latest values first and then use them to calculate earnings.
root cause
the value is not lastest need to be updated.
7.User cannot withdraw stakingToken due to incorrect calculation of _totalSupply
summary
When the user revokes a stake, the total amount is subtracted instead of the currently withdrawable amount, leading to an overflow DoS.
Refer to the definetion of merkle tree on wikipedia:https://en.wikipedia.org/wiki/Merkle_tree In cryptography and computer science, a hash tree or Merkle tree is a tree in which every “leaf” node is labelled with the cryptographic hash of a data block, and every node that is not a leaf (called a branch, inner node, or inode) is labelled with the cryptographic hash of the labels of its child nodes. A hash tree allows efficient and secure verification of the contents of a large data structure. A hash tree is a generalization of a hash list and a hash chain. As we know, an airdrop may contain numerous eligible addresses. Storing all these addresses would incur significant gas fees. Instead, we can simply store the Merkle root, which is a 32-byte value. If you’re not a Solidity developer, grasping the basics of Merkle trees can be challenging. That’s why we’ve developed a tool called oneclicktoken. It simplifies the process of generating Merkle tree roots and, furthermore, enables the creation of ERC20 tokens with just a click. You won’t need any prior knowledge of web3, Solidity, or the ERC20 standard At this early stage of our project, we’d like to highlight some of the features and supported chains
1.1 supported chains:
base
sepolia(test)
More Coming…
1.2 features:
generate merkle root from google spread sheet
generate ERC20 token
Premint
Mintable
Pauseable
VerifyContract(if you need)
2.How to generate merkle tree root from an online google spread
To extract the unique ID from a Google Sheets URL, you can locate the string of characters following the /d/ and preceding the /edit in the URL. For example, in the given URL, the unique ID is 1jVX3j-TlLtI3VJ-27DuOuZuCM3x6_zf9W5tI6k67TvQ.
Next, to identify the specific sheet within the spreadsheet, navigate to the bottom left corner of the Google Sheets interface, where a list of sheets is displayed. From there, you can determine the name of the sheet you need to access. In the provided URL, the sheet name is sheet1.
Once you have obtained the unique ID and sheet name, you can proceed to our tool website, oneclicktoken . There, input the required parameters and click the Calculate button. This will generate the Merkle tree root and provide a download URL for the Merkle tree JSON file.
3.How to generate ERC20 with airdrop using a single click?
token name
token symbol
premint amount(1 == 1 ether token)
contract owner(deployer is the default owner)
merkle root(if you need an airdrop) Once you have obtained above params, navigate to our webiste oneclicktoken input the required parameters and click the deploy button , then you can deploy your custom ERC20 token to target chains. If you need to test your token you can deploy it to sepolia testnet first it’s free.
Our project is in the very early stages, so you might encounter some issues while using it. If you need assistance, please don’t hesitate to reach out to me through the following channels. Additionally, if you require a custom airdrop website or need assistance building a custom Solidity contract, feel free to contact me.
1.getBunniTokenPrice wrongly returns the total price of all tokens
The function getBunniTokenPrice() is supposed to return the price of 1 Bunni token (1 share) like all other feeds, but it doesn't. It returns the total price of all minted tokens/shares for a specific pool (total value of position's reserves) which is wrong.
2.Possible incorrect price for tokens in Balancer stable pool due to amplification parameter update
3.Incorrect StablePool BPT price calculation
`getProtocolOwnedLiquidityOhm` is in scope , but any user can invoke deposit to add assets, deposit is not in scope.need to check those out of scope functions.
4.getReservesByCategory() when useSubmodules =true and submoduleReservesSelector=bytes4(0) will revert
when add not check parameters lead to some category can't get Reserves
5.Price calculation can be manipulated by intentionally reverting some of price feeds
when use uniswap and balancer to calculate price, both price feed can revert due to re-enter lead to user can manipulate price.
use selfdestruct can send eth to any contract,even contract not implement receive{}
1.Bidder can use donations to get VerbsToken from auction that already ended.
protocol use address(this).balance to check if bid amount is bigger than current eth value in contract , attacker can send eth to protocol to bypass above check. need to take care of address(this).balance check.
2.Incorrect amounts of ETH are transferred to the DAO treasury in ERC20TokenEmitter::buyToken(), causing a value leak in every transaction
need to track eth
3.VerbsToken.tokenURI() is vulnerable to JSON injection attacks
4.encodedData argument of hashStruct is not calculated perfectly for EIP712 singed messages in CultureIndex.sol
1.Attacker can reenter to mint all the collection supply
when mint ERC721 token,protocol invoke minter’s onERC721Received function can lead to re-entrancy issue.
2.Attacker can drain all ETH from AuctionDemo when block.timestamp == auctionEndTime
3.Adversary can block claimAuction() due to push-strategy to transfer assets to multiple bidders
claimAuction() implements a push-strategy instead of a pull-strategy for returning the bidders funds. protocol invoke user’s address to send eth,user can implement revert on receive{} function to brick the process.
4.Multiple mints can brick any form of salesOption 3 mintings
1.Weighted pool spot price calculation is incorrect
Notional calculate the spot price of Weighted pool using the balances of token. Which need to be upscaled according to the balancer but Notional doesn’t.
Not farmilar with balancer wighted pool.
2.Single-sided instead of proportional exit is performed during emergency exit
Single-sided instead of proportional exit is performed during emergency exit, which could lead to a loss of assets during emergency exit and vault restoration.
Comment should use proportionally withdraw but used single token out , pay more attention on comments.
3.Native ETH not received when removing liquidity from Curve V2 pools
Not farmilar with Curve V2 pool removing LP.
4.Different spot prices used during the comparison
5.Incorrect invariant used for Balancer’s composable pools
Not farmilar with balancer need to check balancer contract next time.
6.Fewer than expected LP tokens if the pool is imbalanced during vault restoration
The vault restoration function intends to perform a proportional deposit
OHM aims to fill the gap between fiat stablecoins and volatile crypto assets, providing relative stability and scalability in a fully autonomous way with no off-chain dependencies OHM:0x64aa3364f17a4d01c6f1751fd97c2bd3d7e7f1d5
he Treasury represents all assets owned and controlled by the protocol
Protocol Owned Liquidity
Dex
Uniswap v3 pool OHM/wETH
RBS
Provide OHM/DAI LP
Range Bound Stability
Cooler Loans
Cooler Loans is a decentralized lending facility that allows OHM (Olympus) token holders to borrow DAI by using their gOHM (governance OHM) tokens as collateral.
1.FULL_RESTRICTED Stakers can bypass restriction through approvals
The openzeppelin ERC4626 contract allows approved address to withdraw and redeem on behalf of another address so far there is an approval。
Encountering an unfamiliar EIP requires a thorough understanding of its code before proceeding with the subsequent audit.
2.Soft Restricted Staker Role can withdraw stUSDe for USDe
The code does not satisfy that condition, when a holder has the SOFT_RESTRICTED_STAKER_ROLE, they can exchange their stUSDe for USDe using StakedUSDeV2。
When faced with strictly limiting conditions, it is essential to consider whether there are any possible ways to bypass them through various means.
3.users still forced to follow previously set cooldownDuration even when cooldown is off (set to zero) before unstaking
The main issue with this question is that when setting global parameters, the impact on previous users was not taken into consideration
4.Malicious users can front-run to cause a denial of service (DoS) for StakedUSDe due to MinShares checks
This issue arises from the fact that the first user of the vault can have a certain impact on subsequent users. Therefore, it is important to be mindful of the effects of the initial configuration when handling shares.
<address>.codehash != bytes32(0) check is insufficient to determine if an address has existing code
An account is considered empty when it has no code and zero nonce and zero balance.
if anyone transfers 1 wei to an address, .codehash will return keccak256("") instead of bytes32(0)
Fix for this is easier than suggested - just change from x.codehash != bytes32(0) to x.code.length != 0
2.Borrower has no way to update maxTotalSupply of market or close market
Developer define a function but no way to interact with it
3.When withdrawalBatchDuration is set to zero lenders can withdraw more then allocated to a batch
wirte a full test
4.Any address can withdraw from markets contrary
function access control
5.Borrower can drain all funds of a sanctioned lender
1.protocol use CREATE3 that’s not avaliable in the Zksync Era.
2.two modifer not cover block.timestamp == allocationEndTime.
1 2 3 4 5 6 7 8 9 10 11 12 13
function _checkOnlyActiveAllocation() internal view { if (allocationStartTime > block.timestamp || block.timestamp > allocationEndTime) { revert ALLOCATION_NOT_ACTIVE(); } }
/// @notice Checks if the allocation has ended and reverts if not. /// @dev This will revert if the allocation has not ended. function _checkOnlyAfterAllocation() internal view { if (block.timestamp < allocationEndTime) { revert ALLOCATION_NOT_ENDED(); } }
When block.timestamp == allocationEndTime none of above modifers revert.
3.Manager vote every recipient to update his state once reacted the threshold
Since we have serval states protocol not check if user have already reacted the threshold.
4._distribute amount not correctly calculated.
5.missing access modifer
6.Can not create a pool by cloning strategies on zkSync network
Can not create pool by cloning strategies on zkSync network because of different behaviors from EVM instructions between zkSync and Ethereum
7.RFPSimpleStrategy milestones can be set multiple times
1
if (upcomingMilestone != 0) revert MILESTONES_ALREADY_SET();
the value of upcomingMilestone not be updated after set milestones lead to manager can set milestones mutiple times.
8.fundPool does not work with fee-on-transfer token
1.Chain support chain cannot be removed or cleared in bridge contracts.
Contracts provide a way to add support for a chain; however, they do not provide a way to delete it. When a contract offers an addition method, it’s important to consider whether it should also provide a deletion method
2.Contract use msg.sender as remote chain receiver when bridge token.
AA wallet like safe has different wallet address on different chains. This vulnerability requires us to be familiar with some commonly used third-party tools in order to discover it
3.Two different transactions can result in the same txnHash value, thus breaking the approval process of transaction minting
Consider whether the keys in similar mappings are always unique?
4.Admin can’t burn tokens from blocklisted addresses because of a check in _beforeTokenTransfer
The burn operation prematurely calls the beforeTransfer function, but beforeTransfer needs to check if the address is blocked, which hinders the successful execution of the burn
Percentage of xETH in the Curve pool is between REBALANCE_UP_THRESHOLD and REBALANCE_DOWN_THRESHOLD. Rebalance operations are allowed When percentage of xETH is out of range.But the amount of xETH not checked which could lead to Percentage out of range after rebalance.
2.Invalid Validation
The value of the check does not match the expectation
3.ERC20
zero amount token transfer can cause DOS.
4.ERC20
check allowance == 0 should only use in initial or resetting to zero.
5.ERC20
when totalSupply() == 0
6.ERC20
bc of xETH.balanceOf(address(this)),staker can inflate the exchange rate by transferring tokens directly to the contract
7.MEV bot
uncorrect calculate slippage value.
8.Context
an array has add but no remove function.
9.ERC20
transfer token to target contract but can’t withdraw
The recipient of the flash loan will burn a certain amount of tokens as a fee. The recipient can be set to an arbitrary user who impelement receiver.onFlashLoan function.
In certain token-consuming operations, it’s necessary to consider whether only oneself is able to perform this action
2.Context
3.Common
Incorrectly implemented modifiers.
Always check modifiers revert?
4.Upgradable
Logic contact constructor should not update storage variable,cuz it will not be reflected in the proxy’s state.Instead we need use initializer function.
5.Precision Lost
user share can be burned cuz _EUSDAmount.mul(_totalShares).div(totalMintedEUSD) to zero.
6.Context
When using conditional statements, verify whether the values on the left and right sides of the equation meet the expected criteria.
7.Context
threshold value if different from the explanation in the document.
8.Dos
use IVault(pool).vaultType() vaultType can’t be a internel variable.
9.Math(M4)
when calculate token per second use wrong total amount.
As we know , blockchains are self-contained worlds.Smart contract can’t get information by invoking api like we do in web2.At the same time,centralized data providers are also not very reliable.For example,we need to know the price ETH/USDC when we swaping on defi like uniswap.Can we trust one single centralized data provider?The answer is obvious.So we need some decentralized data providers.Oracel act as an message bridge between blockchain and off-chain data provider.
why do SMART CONTRACTS need oracle?
To achieve deterministic execution, blockchains limit nodes to reaching consensus on simple binary (true/false) questions using only data stored on the blockchain itself.If blockchain get information from external data provider it would be impossible to achieve determinism.Different blockchain node maybe return different executes result.To solve those problems we create oracles.Oracle smart contract taking information from off-chain sources and then store the information on the blockchain.Since information stored on-chain is unalterable and publicly available, Ethereum nodes can safely use the oracle imported off-chain data to compute state changes without breaking consensus
how to use chainlink data feed oracle?
Let’s dive into an example,assume that we need to get the BTC/USD price on our smart contract. Chainlink provide data feed server on several networks.
Ethereum Mainnet
Sepolia Testnet
Gogerli Testnet
We gonna test the data feed server on goerli testnet by foundry tool.
First we need to import chainlink contract interface @chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol.
Secondly,let’s init the smart contract using a specify target smart contract address which we copy from chainlink official document website.
Lastly,we just need invoke latestRoundData function which return the current price about BTC/USD.
function getLatestData() public view returns (int) { // prettier-ignore ( /* uint80 roundID */, int answer, /*uint startedAt*/, /*uint timeStamp*/, /*uint80 answeredInRound*/ ) = dataFeed.latestRoundData(); return answer; } }
we can use forge create command to deploy the contract.And then we invoke the getLatestData function via cast call command.Trust me you’ll get some return like this:
If you are using Chainlink Data Feeds on L2 networks like Arbitrum, Optimism, and Metis, you must also check the latest answer from the L2 Sequencer Uptime Feed to ensure that the data is accurate in the event of an L2 sequencer outage
Basiclly,access control means “who is allowed to do this thing”.It’s quite important for smart contract to specify a particular address who can totally control the whole system.Therefor it’s critical to fully understand how to use access control before using it in your project or just copy some example code from somewhere. In openzeppelin there are mainly two ways to implementing access control.
and the we use owner address to create the smart contract,it’s quite simple you don’t even need to pass any parameters.openzeppelin Ownable would set msg.sender as it’s owner as default.we use foundry for testing,if you’re not familiar with foundry,go and learn it first before proceeding further.
Firstly,we use owner address create our smart contract above in foundry test contract setUp function.
1 2 3 4 5
//let's set address owner to msg.sender. vm.prank(owner);
//create the contract. myContract = new MyContract();
And then we use the arbitrary address to invoke the specialThing function which is protected by onlyOwner modifier.As expect the invoke revert because of access control.Here goes the code:
1 2 3 4
//use arbitrary address invoke specialThing() function. vm.prank(arbitrary); vm.expectRevert("Ownable: caller is not the owner"); myContract.specialThing();
Finally,we use our owner who master the contract invoke the specialThing function.As you see the owne succeed.
1 2 3
//switch to owner. vm.prank(owner); myContract.specialThing();
contract MyToken2 is ERC20, AccessControl { // Create a new role identifier for the minter role bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
constructor(address minter) ERC20("MyToken", "TKN") { _grantRole(DEFAULT_ADMIN_ROLE, minter); // Grant the minter role to a specified account _grantRole(MINTER_ROLE, minter); }
function mint(address to, uint256 amount) public { // Check that the calling account has the minter role require(hasRole(MINTER_ROLE, msg.sender), "Caller is not a minter"); _mint(to, amount); } }
We can create whatever role we want in Role-Based Access Control (RBAC) contract.Let’s say we want create a minter role who can mint some ERC20 token.We can use a special constant bytes32 as the name of the role like this:
1 2
// Create a new role identifier for the minter role bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
Firstly,we use owner address as our minter to create our access control contract.
1
myToken = new MyToken2(owner);
And then we use the arbitrary user to mint some ERC20 token:
1 2 3 4
//use arbitrary address invoke mint() function. vm.prank(arbitrary); vm.expectRevert("Caller is not a minter"); myToken.mint(address(this), 1);
the above invoke would be revert because of arbitrary don’t hold the minter role.
1 2 3
//switch to owner. vm.prank(owner); myToken.mint(address(this), 1);
If we switch msg.sender to owner the call would successed.
What’s more? we can use owner address to grant a minter role whoever we want.Let’s say we grant the minter role to the arbitrary address:
There are 2 major ways to deploy smart contract CREATE and CREATE2. They look quite similar, but there are still some differences.CREATE2 and CREATE are one of solidity opcode which give us the ability to predict the address where a contract will be deployed before we deploy a smart contract.You might be curious about what opcode is.Opcodes are the fundamental building blocks of EVM bytecode, and each opcode represents a specific operation that the EVM can perform. For example, there are opcodes for arithmetic operations, logical operations, storage access, memory manipulation, conditional branching, and more.
What is CREATE
Smart contracts can be created both by other contracts and regular EOA.They both compute the new address the same way:
1
new_address = keccak256(sender, nonce)
So each created address is associated with a nonce value.Nonce increased on every transaction.It is related to the number of transactions we make and is unpredictable. That’s why we need CREATE2.
What is CREATE2 ?
The whole idea behind this opcode is to make the resulting address independent of future events. Regardless of what may happen on the blockchain, it will always be possible to deploy the contract at the precomputed address.There are four parameters in CREATE2 function:
Imagine a scenario where you need to deploy a contract to multiple networks, and precisely at that moment, you need to store the addresses of the yet-to-be-deployed contracts as storage parameters within the currently deployed contract. In such cases, you would require knowing the future addresses of the contracts to be deployed in advance.
function deploy( bytes memory bytecode, uint _salt ) public payable returns (address) { address addr;
assembly { addr := create2( callvalue(), // wei sent with current call // Actual code starts after skipping the first 32 bytes add(bytecode, 0x20), mload(bytecode), // Load the size of code contained in the first 32 bytes _salt // Salt from function arguments )
Merkle trees, named after Ralph Merkle, are a fundamental data structure used in cryptography and computer science. They are commonly employed in blockchain systems to ensure data integrity and enable efficient verification.
The principle behind Merkle trees is based on the concept of hash functions. A hash function is a mathematical algorithm that takes an input (data) and produces a fixed-size output, known as a hash value or digest. The key properties of a hash function are collision resistance and the avalanche effect.
how to generate merkle tree data via solidity
we can calculate hash value through solidity keccak256 function:
1 2 3 4 5 6 7 8
keccak256(abi.encodePacked(toHashValue)
e.g.: before hash 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2
after hash 0x999bf57501565dbd2fdcea36efa2b9aef8340a8901e3459f4a4c926275d36cdb
After performing the hash operation on the values of the leaf nodes, the adjacent nodes are then hashed together until only a single root node remains.
Suppose there are two adjacent nodes, A and B. The order of the hash operation, whether it is hash(A+B) or hash(B+A), is determined by the sizes of A and B. In the corresponding Merkle code in OpenZeppelin, we can find the following code snippet:
1 2 3
function _hashPair(bytes32 a, bytes32 b) private pure returns (bytes32) { return a < b ? _efficientHash(a, b) : _efficientHash(b, a); }
In summary, the smaller value is typically placed in front to determine the order for computing the hash values. This may cause some confusion when performing the calculations manually.
In practical projects, it is common to store only the final result, the root hash value, in the contract. Storing a large number of addresses in the contract would consume a significant amount of gas fees. By using a Merkle tree calculation, the amount of data that needs to be stored is greatly reduced.
Let’s demonstrate how to calculate and store the root hash value using a setup example from Foundry:
bytes32 public root; bytes32[] public leafs; bytes32[] public l2;
function setUp() public { address[] memory addrss = new address[](4); addrss[0] = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2; addrss[1] = 0x2d886570A0dA04885bfD6eb48eD8b8ff01A0eb7e; addrss[2] = 0xed857ac80A9cc7ca07a1C213e79683A1883df07B; addrss[3] = 0x690B9A9E9aa1C9dB991C7721a92d351Db4FaC990;
//Calculating the hash values of leaf nodes from a list of addresses. leafs.push(keccak256(abi.encodePacked(addrss[0]))); leafs.push(keccak256(abi.encodePacked(addrss[1]))); leafs.push(keccak256(abi.encodePacked(addrss[2]))); leafs.push(keccak256(abi.encodePacked(addrss[3])));
//Calculating the hash values of the second level. l2.push(keccak256(abi.encodePacked(leafs[0], leafs[1]))); l2.push(keccak256(abi.encodePacked(leafs[2], leafs[3])));
For demonstration purposes, we have provided only four addresses. In actual projects, the number of addresses can be significantly larger.
how to proof merkle data
Once we have stored the root hash value in the contract, how do we verify if a client-provided address is a valid address or belongs to a whitelist?
Firstly, we need to hash the address as the third parameter. Then, we pass the hash value of the address, along with the adjacent hash values, as the proof to the verification function.
The proof list corresponds to the red-marked area in the image below.
test proof function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
function testVerify() public { address proofAddress = 0xAb8483F64d9C6d1EcF9b849Ae677dD3315835cb2;
function parentHash(bytes32 a, bytes32 b) public pure returns (bytes32) { if (a < b) { return keccak256(abi.encode(a, b)); } else { return keccak256(abi.encode(b, a)); } }
The statement “abi.encode(address, uint)” will produce 64 bytes. Since “abi.encode(bytes32, bytes32)” also yields 64 bytes, hash collisions may potentially occur between leaf nodes and parent nodes.
function parentHash(bytes32 a, bytes32 b) public pure returns (bytes32) { if (a < b) { return keccak256(abi.encode(a, b)); } else { return keccak256(abi.encode(b, a)); } }