Navigation Menu

Skip to content

Instantly share code, notes, and snippets.

@Amxx
Created March 29, 2021 14:58
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Amxx/5721b4d76f261720dd3a68956d50d895 to your computer and use it in GitHub Desktop.
Save Amxx/5721b4d76f261720dd3a68956d50d895 to your computer and use it in GitHub Desktop.
Using addresses as leaf
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/cryptography/MerkleProof.sol";
contract ERC721MerkleDrop is ERC721 {
bytes32 immutable public root;
constructor(string memory name, string memory symbol, bytes32 merkleroot)
ERC721(name, symbol)
{
root = merkleroot;
}
function redeem(address account, uint256 tokenId, bytes32[] calldata proof)
external
{
require(_verify(_leaf(account, tokenId), proof), "Invalid merkle proof");
_mint(account, tokenId);
}
function _leaf(address account, uint256 tokenId)
internal pure returns (bytes32)
{
// Showcase addresses as leaf
return bytes32(uint256(uint160(account)));
// return keccak256(abi.encodePacked(tokenId, account));
}
function _verify(bytes32 leaf, bytes32[] memory proof)
internal view returns (bool)
{
return MerkleProof.verify(proof, root, leaf);
}
}
const { ethers } = require('hardhat');
const { MerkleTree } = require('merkletreejs');
const keccak256 = require('keccak256');
const { expect } = require('chai');
async function deploy(name, ...params) {
const Contract = await ethers.getContractFactory(name);
return await Contract.deploy(...params).then(f => f.deployed());
}
function hashToken(tokenId, account) {
// Showcase addresses as leaf
return Buffer.from(account.slice(2).padStart(64, '0'), 'hex')
// return Buffer.from(ethers.utils.solidityKeccak256(['uint256', 'address'], [tokenId, account]).slice(2), 'hex')
}
const tokens = {
'56660740342816081431743222872731117427526580551422435935884080137676694505177': '0xa111C225A0aFd5aD64221B1bc1D5d817e5D3Ca15',
'77779554305765060701256245686273608138749709626967147417222804954719217104111': '0x8de806462823aD25056eE8104101F9367E208C14',
'36416698825062681237969338228395867809712571178321010467907965453371102606457': '0x801EfbcFfc2Cf572D4C30De9CEE2a0AFeBfa1Ce1',
'108148532074091611222173342315291145352157620481598067018426825547456519926282': '0x7DdC72Eb160F6A325A5927299b2715Abd0bEA55B',
'13988174029436432540570381205111963270292016538206553609831089847857228090619': '0x5aB21736841D90fc48D6aA003A7791E62f52692f',
'66006320280895314860647153115820051089242351094955318545993434402895457320087': '0x940Da56126BE133Ddaa53Fe0FE52B4F42d3f761F',
'16131328929436688815488112444319900503393907293977987766579886433194904646533': '0x7De3e7f2821ef6310B353c00628D02B57ad661D8',
'70363901684546190131494672691202596480010865506265940013889525778922121326126': '0x6B831bf30f30701D5595DedDA9873F9F93495FBb',
'64308966187936641647974943581975166543063707479192358820456738763585086852921': '0x3EE9626735Bc3585D2B4b3B6F604769aB3450593',
'25852044952998253150599325372353410768171410249341626326102359644632315835678': '0xb0874963aCcA3FD8C3740fC13A9948b89D34B19A',
'109270421435016155674138011152358860652282160046602257314270248009729450888616': '0x4Ea6D5DF8291E973289C726b2a36836BF232C0D1',
'60951507906621244943905836719336971568787306218150314963160274193630911924084': '0xa702766f94C9C515722251a8A9FE7Ea9141eE73B',
'49530906133062864957479951054142751172241371576265182872288195323037610453263': '0x346229540D5849203f84A727636AE4Eb6eA265fD',
'100710336746605700073221831260512041319095295422384736038717106200796860021250': '0x8Ee432c17f59e5989069B77164a68fbAAcF46A2B',
'37235810039334701263914709571720867451357683795820563395225347574888394987520': '0xeAE26C61ab4fF2B8e0d38CE3C44b3D115cfB0FDE',
'89637433112024637295546101206613645952665816288525094814507166297376938891111': '0x30e2326ee1bf319EFA6117E6E6C8Df334243E76d',
'86830284497346071256477650027501261388928562487022930095462971204284911067107': '0xBcfF64A3f8EE87d8D82B8c9b6D35b150761b430D',
'6705805767844207723261592881831509603729469983418291212309451818559986173218': '0x0EbDa2F020DA73192F6Ab99c31224b8b1Aa32df8',
'56826771101205779187075327440225035725608966408941262027534145212461501880746': '0x0904b2214f21bdEc6f567469b57cc9444D4f4DdB',
'57495477825540507962551692981047197416807307031606449655753227875319265312884': '0x7d33793CFdb315882cfF375096A4c0EF7d3fC0f0',
}
const merkleTree = new MerkleTree(Object.entries(tokens).map(token => hashToken(...token)), keccak256, { sortPairs: true });
describe('ERC721MerkleDrop', function () {
before(async function() {
this.accounts = await ethers.getSigners();
});
describe('Mint all elements', function () {
before(async function() {
this.registry = await deploy('ERC721MerkleDrop', 'Name', 'Symbol', merkleTree.getHexRoot());
});
for (const [tokenId, account] of Object.entries(tokens)) {
it('element', async function () {
const proof = merkleTree.getHexProof(hashToken(tokenId, account));
await expect(this.registry.redeem(account, tokenId, proof))
.to.emit(this.registry, 'Transfer')
.withArgs(ethers.constants.AddressZero, account, tokenId);
});
}
});
describe('Duplicate mint', function () {
before(async function() {
this.registry = await deploy('ERC721MerkleDrop', 'Name', 'Symbol', merkleTree.getHexRoot());
this.token = {};
[ this.token.tokenId, this.token.account ] = Object.entries(tokens).find(Boolean);
this.token.proof = merkleTree.getHexProof(hashToken(this.token.tokenId, this.token.account));
});
it('mint once - success', async function () {
await expect(this.registry.redeem(this.token.account, this.token.tokenId, this.token.proof))
.to.emit(this.registry, 'Transfer')
.withArgs(ethers.constants.AddressZero, this.token.account, this.token.tokenId);
});
it('mint twice - failure', async function () {
await expect(this.registry.redeem(this.token.account, this.token.tokenId, this.token.proof))
.to.be.revertedWith('ERC721: token already minted');
});
});
describe('Frontrun', function () {
before(async function() {
this.registry = await deploy('ERC721MerkleDrop', 'Name', 'Symbol', merkleTree.getHexRoot());
this.token = {};
[ this.token.tokenId, this.token.account ] = Object.entries(tokens).find(Boolean);
this.token.proof = merkleTree.getHexProof(hashToken(this.token.tokenId, this.token.account));
});
it('Change owner - success', async function () {
await expect(this.registry.redeem(this.accounts[0].address, this.token.tokenId, this.token.proof))
.to.be.revertedWith('Invalid merkle proof');
});
})
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment