Merkleization – How to Verify a Leaf with Merkle Tree

merkleization

I'm trying to verify a leaf on a Merkle tree. I've generated the tree off-chain with merkletree.js using some "passwords" that I want to verify on-chain using OpenZeppelin MerkleProof library.

The problem is I got "No valid proof to claim an award" response even if the leaf is valid. My code:

The js script creating the tree and running the test

const assert = require('assert');
const ganache = require('ganache');
const { MerkleTree } = require('merkletreejs')
const keccak256 = require('keccak256');

const Web3 = require('web3');
const web3 = new Web3(ganache.provider());

const { abi, bytecode } = require('../build/contracts/LotteryNftAward.json');

let lottery;
let accounts;

const TOTAL_SUPPLY = 5;

const LEAVES = ["test", "merkletree", "lottery", "javascript", "blockchain", "factory"].map(p => keccak256(p));

const TREE = new MerkleTree(LEAVES, keccak256, { sortPairs: true });
const ROOT = TREE.getHexRoot();
const PROOF = TREE.getHexProof(LEAVES[1]);

beforeEach(async () => {
    accounts = await web3.eth.getAccounts();

    lottery = await new web3.eth.Contract(abi)
    .deploy({ data: bytecode, arguments: [TOTAL_SUPPLY, ROOT]})
    .send({ from: accounts[0], gas: 4000000 });    
});

describe('Lottery Contract', () => {

    it('Invalid Proof', async () => {
        try{
            await lottery.methods.claim(PROOF, "wrongLeaf").send({
                from: accounts[1]
            });
            assert(false);
        } catch (err){
            assert(err);
        }
    });

    it('Valid Proof', async () => { 
        await lottery.methods.claim(PROOF, keccak256("merkletree")).send({
            from: accounts[1]
        });
    });

});

The smart contract

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;

import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";


contract AciLotteryNftAward is ERC721, Ownable {
    using Counters for Counters.Counter;

    Counters.Counter private _tokenIdCounter;

    struct Player {
        address playerAddress;
        bool hasWon;
    }

    event Subscription(address newPlayer, uint256 totalSubscription);
    
    uint256 immutable TOTAL_SUPPLY;

    Player[] private players;
    
    bytes32 immutable private MERKLE_ROOT;

    mapping(address => bool) private claimTracker;

    constructor(uint256 totalSupply, bytes32 merkleRoot) ERC721("AciLotteryNftAward", "ALNA") {
        TOTAL_SUPPLY = totalSupply;
        MERKLE_ROOT = merkleRoot;
    }

.... other methods ....

    function claim(bytes32[] calldata proof, bytes32 leaf) external {
    //function claim() external {
        uint256 currentTokenId = _tokenIdCounter.current();

        require(currentTokenId < TOTAL_SUPPLY, "No more tokens to claim");
        require(!claimTracker[msg.sender], "This address has already claimed");

        require(MerkleProof.verifyCalldata(
                    proof,
                    MERKLE_ROOT,
                    keccak256(abi.encodePacked(leaf))
                ),
                "No valid proof to claim an award");
        
        _tokenIdCounter.increment();

        claimTracker[msg.sender] = true;
        
        _safeMint(msg.sender, _tokenIdCounter.current());
        
    }

}

Best Answer

You might need to use MerkleProof.verify() method, as you're not using calldata while creating the Merkle Tree.

Try This:

require(
    MerkleProof.verify(proof, MERKLE_ROOT, leaf),
    "No valid proof to claim an award"
);
Related Topic