can someone help me out, I'm testing my buySingle function, by adding random values inside my first 3 inputs and an array in the last one. when I try executing I keep getting invalid proof. What I am supposed to be adding in the proof??
Currently, this is my _proof input:
[
"0x4406d4944cd8815a388db585f5a7a0b70d21ea3dd912414d4313136a2eac33a0",
"0x79fd2a4dca72d9577193fcc3d1ffd96ecf8127dab0bd295a48fcefa78c1ff948",
"0x6ec8df7eec81d5a31e79f9d3101bc2468ecf58af264968ba019766f12b2c6a65",
"0xb350ca80e5d4284caedb2d59caa85729952df9902c07f2d86054b05a4193551c",
"0xba0b16b6e0abae3d2a33a072dc7a0a59d77b1de708c5477e50634319f1496529",
"0x6c9b99e2e6f4aaaad0694375728a68e44160888e9fada56d440f4fbe3cbf33ea",
"0x17fb11285cd87a16370a061e2359f85d288f377f69248e7172c43475b7035806",
"0x33a63c21e96c9b7cc2081da21c9f2a0e8ff0904569a3acb17fa76129443dd047",
"0x266505b770c75759218c46c22f531ef5bd216c97246bf226567ddb753bfc93db",
"0x77e2eac49c0478daeac4f6023434ade425b8c45d4150d0d5a2c8cea8555027d2",
"0xe28a06c8328b5534e8de2adff61ee2916b040307a2165adab2cfd4b8eaa8fbfc",
"0x19a35f620df55ae0a87e54a8a1911bf9a40a5f41b54a1031f18a714c49e4ca17",
"0xd24be13d5d23f5102e312d8e5afd1b7dc5573b44e614c242d68d80899e10821f"
]
input:
price:
start:
end:
proof:
Output:
"message": "execution reverted: invalid proof"
and here is my code:
pragma solidity 0.8.10;
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
import "@openzeppelin/contracts/interfaces/IERC165.sol";
import "@openzeppelin/contracts/interfaces/IERC721.sol";
import "./IMintableERC721.sol";
contract MintablePresale is Ownable {
// Use Zeppelin MerkleProof Library to verify Merkle proofs
using MerkleProof for bytes32[];
uint32 public nextId = 1;
uint32 public finalId;
uint32 public batchLimit;
uint32 public mintLimit;
uint32 public soldCounter;
/**
* @notice Merkle tree root to validate (address, cost, startDate, endDate)
* tuples
*/
bytes32 public root;
uint256 public constant UID = 0x96156164645b31243986cb90a4oio09d99f140aa39d43ffa58a6af6d5e90bdbf4;
address public immutable tokenContract;
address public immutable developerAddress;
mapping(address => uint32) public mints;
/**
* @dev Fired in initialize()
*
* @param _by an address which executed the initialization
* @param _nextId next ID of the token to mint
* @param _finalId final ID of the token to mint
* @param _batchLimit how many tokens is allowed to buy in a single transaction
* @param _root merkle tree root
*/
event Initialized(
address indexed _by,
uint32 _nextId,
uint32 _finalId,
uint32 _batchLimit,
uint32 _limit,
bytes32 _root
);
event Bought(address indexed _by, address indexed _to, uint256 _amount, uint256 _value);
constructor(address _tokenContract, address _developerAddress) {
// verify the input is set
require(_tokenContract != address(0), "token contract is not set");
// verify input is valid smart contract of the expected interfaces
require(
IERC165(_tokenContract).supportsInterface(type(IMintableERC721).interfaceId)
&& IERC165(_tokenContract).supportsInterface(type(IMintableERC721).interfaceId),
"unexpected token contract type"
);
// assign the addresses
tokenContract = _tokenContract;
developerAddress = _developerAddress;
}
function itemsOnSale() public view returns(uint32) {
// calculate items left on sale, taking into account that
// finalId is on sale (inclusive bound)
return finalId > nextId? finalId + 1 - nextId: 0;
}
function itemsAvailable() public view returns(uint32) {
// delegate to itemsOnSale() if sale is active, return zero otherwise
return isActive() ? itemsOnSale(): 0;
}
function isActive() public view virtual returns(bool) {
// evaluate sale state based on the internal state variables and return
return nextId <= finalId;
}
/**
* @dev Restricted access function to set up sale parameters, all at once,
* or any subset of them
*
* @dev To skip parameter initialization, set it to `-1`,
* that is a maximum value for unsigned integer of the corresponding type;
* `_aliSource` and `_aliValue` must both be either set or skipped
*
* @dev Example: following initialization will update only _itemPrice and _batchLimit,
* leaving the rest of the fields unchanged
* initialize(
* 0xFFFFFFFF,
* 0xFFFFFFFF,
* 10,
* 0xFFFFFFFF
* )
*
* @dev Requires next ID to be greater than zero (strict): `_nextId > 0`
*
* @dev Requires transaction sender to have `ROLE_SALE_MANAGER` role
*
* @param _nextId next ID of the token to mint, will be increased
* in smart contract storage after every successful buy
* @param _finalId final ID of the token to mint; sale is capable of producing
* `_finalId - _nextId + 1` tokens
* when current time is within _saleStart (inclusive) and _saleEnd (exclusive)
* @param _batchLimit how many tokens is allowed to buy in a single transaction,
* set to zero to disable the limit
* @param _mintLimit how many tokens is allowed to buy for the duration of the sale,
* set to zero to disable the limit
* @param _root merkle tree root used to verify whether an address can mint
*/
function initialize(
uint32 _nextId, // <<<--- keep type in sync with the body type(uint32).max !!!
uint32 _finalId, // <<<--- keep type in sync with the body type(uint32).max !!!
uint32 _batchLimit, // <<<--- keep type in sync with the body type(uint32).max !!!
uint32 _mintLimit, // <<<--- keep type in sync with the body type(uint32).max !!!
bytes32 _root // <<<--- keep type in sync with the 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF !!!
) public onlyOwner {
// verify the inputs
require(_nextId > 0, "zero nextId");
// no need to verify extra parameters - "incorrect" values will deactivate the sale
// initialize contract state based on the values supplied
// take into account our convention that value `-1` means "do not set"
// 0xFFFFFFFFFFFFFFFF, 64 bits
// 0xFFFFFFFF, 32 bits
if(_nextId != type(uint32).max) {
nextId = _nextId;
}
// 0xFFFFFFFF, 32 bits
if(_finalId != type(uint32).max) {
finalId = _finalId;
}
// 0xFFFFFFFF, 32 bits
if(_batchLimit != type(uint32).max) {
batchLimit = _batchLimit;
}
// 0xFFFFFFFF, 32 bits
if(_mintLimit != type(uint32).max) {
mintLimit = _mintLimit;
}
// 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF, 256 bits
if(_root != 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF) {
root = _root;
}
// emit an event - read values from the storage since not all of them might be set
emit Initialized(
msg.sender,
nextId,
finalId,
batchLimit,
mintLimit,
root
);
}
/**
* @notice Buys several (at least two) tokens in a batch.
* Accepts ETH as payment and mints a token
*
* @param _amount amount of tokens to create, two or more
*/
function buy(uint256 _price, uint256 _start, uint256 _end, bytes32[] memory _proof, uint32 _amount) public payable {
// delegate to `buyTo` with the transaction sender set to be a recipient
buyTo(msg.sender, _price, _start, _end, _proof, _amount);
}
/**
* @notice Buys several (at least two) tokens in a batch to an address specified.
* Accepts ETH as payment and mints tokens
*
* @param _to address to mint tokens to
* @param _amount amount of tokens to create, two or more
*/
function buyTo(address _to, uint256 _price, uint256 _start, uint256 _end, bytes32[] memory _proof, uint32 _amount) public payable {
// construct Merkle tree leaf from the inputs supplied
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, _price, _start, _end));
// verify proof
require(_proof.verify(root, leaf), "invalid proof");
// verify the inputs
require(_to != address(0), "recipient not set");
require(_amount > 1 && (batchLimit == 0 || _amount <= batchLimit), "incorrect amount");
require(block.timestamp >= _start, "sale not yet started");
require(block.timestamp <= _end, "sale ended");
// verify mint limit
if(mintLimit != 0) {
require(mints[msg.sender] + _amount <= mintLimit, "mint limit reached");
}
// verify there is enough items available to buy the amount
// verifies sale is in active state under the hood
require(itemsAvailable() >= _amount, "inactive sale or not enough items available");
// calculate the total price required and validate the transaction value
uint256 totalPrice = _price * _amount;
require(msg.value >= totalPrice, "not enough funds");
// mint token to to the recipient
IMintableERC721(tokenContract).mintBatch(_to, nextId, _amount);
// increment `nextId`
nextId += _amount;
// increment `soldCounter`
soldCounter += _amount;
// increment sender mints
mints[msg.sender] += _amount;
// if ETH amount supplied exceeds the price
if(msg.value > totalPrice) {
// send excess amount back to sender
payable(msg.sender).transfer(msg.value - totalPrice);
}
// emit en event
emit Bought(msg.sender, _to, _amount, totalPrice);
}
/**
* @notice Buys single token.
* Accepts ETH as payment and mints a token
*/
function buySingle(uint256 _price, uint256 _start, uint256 _end, bytes32[] memory _proof) public payable {
// delegate to `buySingleTo` with the transaction sender set to be a recipient
buySingleTo(msg.sender, _price, _start, _end, _proof);
}
/**
* @notice Buys single token to an address specified.
* Accepts ETH as payment and mints a token
*
* @param _to address to mint token to
*/
function buySingleTo(address _to, uint256 _price, uint256 _start, uint256 _end, bytes32[] memory _proof) public payable {
// construct Merkle tree leaf from the inputs supplied
bytes32 leaf = keccak256(abi.encodePacked(msg.sender, _price, _start, _end));
// verify proof
require(_proof.verify(root, leaf), "invalid proof");
// verify the inputs and transaction value
require(_to != address(0), "recipient not set");
require(msg.value >= _price, "not enough funds");
require(block.timestamp >= _start, "sale not yet started");
require(block.timestamp <= _end, "sale ended");
// verify mint limit
if(mintLimit != 0) {
require(mints[msg.sender] + 1 <= mintLimit, "mint limit reached");
}
// verify sale is in active state
require(isActive(), "inactive sale");
// mint token to the recipient
IMintableERC721(tokenContract).mint(_to, nextId);
// increment `nextId`
nextId++;
// increment `soldCounter`
soldCounter++;
// increment sender mints
mints[msg.sender]++;
// emit en event
emit Bought(msg.sender, _to, 1, _price);
}
}
}
I found this on the open zeppelin website
but still do not understand how to get the hashes from the leaf to the root.
Here is my IMintableERC721 as requested in the comments
// SPDX-License-Identifier: MIT
pragma solidity 0.8.10;
interface IMintableERC721 {
/**
* @notice Checks if specified token exists
*
* @dev Returns whether the specified token ID has an ownership
* information associated with it
*
* @param _tokenId ID of the token to query existence for
* @return whether the token exists (true - exists, false - doesn't exist)
*/
function exists(uint256 _tokenId) external view returns(bool);
/**
* @dev Creates new token with token ID specified
* and assigns an ownership `_to` for this token
*
* @dev Unsafe: doesn't execute `onERC721Received` on the receiver.
* Prefer the use of `saveMint` instead of `mint`.
*
* @dev Should have a restricted access handled by the implementation
*
* @param _to an address to mint token to
* @param _tokenId ID of the token to mint
*/
function mint(address _to, uint256 _tokenId) external;
/**
* @dev Creates new tokens starting with token ID specified
* and assigns an ownership `_to` for these tokens
*
* @dev Token IDs to be minted: [_tokenId, _tokenId + n)
*
* @dev n must be greater or equal 2: `n > 1`
*
* @dev Unsafe: doesn't execute `onERC721Received` on the receiver.
* Prefer the use of `saveMintBatch` instead of `mintBatch`.
*
* @dev Should have a restricted access handled by the implementation
*
* @param _to an address to mint tokens to
* @param _tokenId ID of the first token to mint
* @param n how many tokens to mint, sequentially increasing the _tokenId
*/
function mintBatch(address _to, uint256 _tokenId, uint256 n) external;
/**
* @dev Creates new token with token ID specified
* and assigns an ownership `_to` for this token
*
* @dev Checks if `_to` is a smart contract (code size > 0). If so, it calls
* `onERC721Received` on `_to` and throws if the return value is not
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
*
* @dev Should have a restricted access handled by the implementation
*
* @param _to an address to mint token to
* @param _tokenId ID of the token to mint
*/
function safeMint(address _to, uint256 _tokenId) external;
/**
* @dev Creates new token with token ID specified
* and assigns an ownership `_to` for this token
*
* @dev Checks if `_to` is a smart contract (code size > 0). If so, it calls
* `onERC721Received` on `_to` and throws if the return value is not
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
*
* @dev Should have a restricted access handled by the implementation
*
* @param _to an address to mint token to
* @param _tokenId ID of the token to mint
* @param _data additional data with no specified format, sent in call to `_to`
*/
function safeMint(address _to, uint256 _tokenId, bytes memory _data) external;
/**
* @dev Creates new tokens starting with token ID specified
* and assigns an ownership `_to` for these tokens
*
* @dev Token IDs to be minted: [_tokenId, _tokenId + n)
*
* @dev n must be greater or equal 2: `n > 1`
*
* @dev Checks if `_to` is a smart contract (code size > 0). If so, it calls
* `onERC721Received` on `_to` and throws if the return value is not
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
*
* @dev Should have a restricted access handled by the implementation
*
* @param _to an address to mint token to
* @param _tokenId ID of the token to mint
* @param n how many tokens to mint, sequentially increasing the _tokenId
*/
function safeMintBatch(address _to, uint256 _tokenId, uint256 n) external;
/**
* @dev Creates new tokens starting with token ID specified
* and assigns an ownership `_to` for these tokens
*
* @dev Token IDs to be minted: [_tokenId, _tokenId + n)
*
* @dev n must be greater or equal 2: `n > 1`
*
* @dev Checks if `_to` is a smart contract (code size > 0). If so, it calls
* `onERC721Received` on `_to` and throws if the return value is not
* `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`.
*
* @dev Should have a restricted access handled by the implementation
*
* @param _to an address to mint token to
* @param _tokenId ID of the token to mint
* @param n how many tokens to mint, sequentially increasing the _tokenId
* @param _data additional data with no specified format, sent in call to `_to`
*/
function safeMintBatch(address _to, uint256 _tokenId, uint256 n, bytes memory _data) external;
}