-
-
Save Amxx/5721b4d76f261720dd3a68956d50d895 to your computer and use it in GitHub Desktop.
Using addresses as leaf
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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