soliditywhitelistmerkle-treekeccak

Failing to validate merkle proof on smart contract


I'm using a copy of Invisible Friends NFT contract (https://etherscan.io/address/0x59468516a8259058bad1ca5f8f4bff190d30e066#code) and I'm currently in a process of creating a minting page. Everything is going well except validating merkle proofs for whitelist minting.

First, I created an array of 2 ETH addresses, put together merkle tree, calculate root hash and update it on the contract:

let leafNodes;
let merkleTree;
let merkleProof;
let merkleRoot;
const whitelist = ["0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266", "0x70997970C51812dc3A010C7d01b50e0d17dc79C8"];
leafNodes = whitelist.map(addr => keccak256(addr.concat("5")));
merkleTree = new MerkleTree(leafNodes, keccak256, { sortPairs: true });
merkleRoot = merkleTree.getRoot();
await contract.setMerkleProof(merkleRoot);

Now, when I create merkle proof and try to verify it on my front-end, it works:

merkleProof = merkleTree.getHexProof(keccak256(owner.address.concat("5")));
console.log(merkleTree.verify(merkleProof, keccak256(owner.address.concat("5")), merkleRoot)) => returns "true"

However, when I try to mint using the generated proof, it fails:

await contract.mintListed(1, merkleProof, 5, { value: ethers.utils.parseEther("0.25") });
=> "Error: VM Exception while processing transaction: reverted with reason string 'Invalid proof'"

Contract side which is responsible for merkle proof verification:

function _verify(
    bytes32[] calldata merkleProof,
    address sender,
    uint256 maxAmount
) private view returns (bool) {
    bytes32 leaf = keccak256(
        abi.encodePacked(sender, maxAmount.toString())
    );
    return MerkleProof.verify(merkleProof, merkleRoot, leaf);
}

This returns false, therefore failing the mint. I can't figure out why it's doing this, since I'm doing the same thing on my front-end and it's working well.

And mintListed function itself:

function mintListed(
    uint256 amount,
    bytes32[] calldata merkleProof,
    uint256 maxAmount
) public payable nonReentrant {
    address sender = _msgSender();

    require(isActive, "Sale is closed");
    require(
        amount <= maxAmount - _alreadyMinted[sender],
        "Insufficient mints left"
    );
    require(_verify(merkleProof, sender, maxAmount), "Invalid proof");
    require(msg.value == price * amount, "Incorrect payable amount");

    _alreadyMinted[sender] += amount;
    _internalMint(sender, amount);
}

When I remove the "maxAmount" from merkle tree and using only keccak256 hashed addresses, it works, but I do need it.

I tried different ways to concatenate strings on JS side, but nothing works.


Solution

  • I managed to fix the issue.

    So instead of

    keccak256(addr.concat("5"))); 
    

    I used

    keccak256(ethers.utils.solidityPack(["address", "string"], [addr, "5"]))
    

    Now it works fine!