Skip to content

Commit

Permalink
feat(protocol): added test case for ERC721Airdrop (taikoxyz#16025)
Browse files Browse the repository at this point in the history
Co-authored-by: Daniel Wang <99078276+dantaik@users.noreply.github.com>
  • Loading branch information
arularmstrong and dantaik authored Feb 25, 2024
1 parent f3d7d82 commit c8f6e9b
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 0 deletions.
1 change: 1 addition & 0 deletions packages/protocol/test/TaikoTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import "../contracts/L2/TaikoL2.sol";
import "../contracts/team/TimelockTokenPool.sol";
import "../contracts/team/airdrop/ERC20Airdrop.sol";
import "../contracts/team/airdrop/ERC20Airdrop2.sol";
import "../contracts/team/airdrop/ERC721Airdrop.sol";

import "../test/common/erc20/FreeMintERC20.sol";

Expand Down
170 changes: 170 additions & 0 deletions packages/protocol/test/team/airdrop/ERC721Airdrop.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "../../TaikoTest.sol";
import "../../../contracts/common/OwnerUUPSUpgradable.sol";

contract MockERC721Airdrop is ERC721Airdrop {
function _verifyMerkleProof(
bytes32[] calldata, /*proof*/
bytes32, /*merkleRoot*/
bytes32 /*value*/
)
internal
pure
override
returns (bool)
{
return true;
}
}

// Simple mock - so that we do not need to deploy AddressManager (for these tests). With this
// contract we mock an ERC721Vault which mints tokens into a Vault (which holds the TKOP).
contract MockAddressManager {
address mockERC721Vault;

constructor(address _mockERC721Vault) {
mockERC721Vault = _mockERC721Vault;
}

function getAddress(uint64, /*chainId*/ bytes32 /*name*/ ) public view returns (address) {
return mockERC721Vault;
}
}

// It does nothing but:
// - stores the tokens for the airdrop
// - owner can call setApprovalForAll() on token, and approving the AirdropERC721.sol contract so
// it acts on
// behalf
// - funds can later be withdrawn by the user
contract SimpleERC721Vault is OwnerUUPSUpgradable {
/// @notice Initializes the vault.
function init() external initializer {
__OwnerUUPSUpgradable_init();
}

function approveAirdropContract(address token, address approvedActor) public onlyOwner {
BridgedERC721(token).setApprovalForAll(approvedActor, true);
}

function onERC721Received(
address,
address,
uint256,
bytes calldata
)
external
pure
returns (bytes4)
{
return SimpleERC721Vault.onERC721Received.selector;
}
}

contract TestERC721Airdrop is TaikoTest {
address public owner = randAddress();

uint256 public mintSupply = 5;

bytes32 public constant merkleRoot = bytes32(uint256(1));
bytes32[] public merkleProof;
uint64 public claimStart;
uint64 public claimEnd;

BridgedERC721 token;
MockERC721Airdrop airdrop;
MockAddressManager addressManager;
SimpleERC721Vault vault;

function setUp() public {
vm.startPrank(owner);

// 1. We need to have a vault
vault = SimpleERC721Vault(
deployProxy({
name: "vault",
impl: address(new SimpleERC721Vault()),
data: abi.encodeCall(SimpleERC721Vault.init, ())
})
);

// 2. Need to add it to the AddressManager (below here i'm just mocking it) so that we can
// mint TKOP. Basically this step only required in this test. Only thing we need to be sure
// on testnet/mainnet. Vault (which Airdrop transfers from) HAVE tokens.
addressManager = new MockAddressManager(address(vault));

// 3. Deploy a bridged TKOP token (but on mainnet it will be just a bridged token from L1 to
// L2) - not necessary step on mainnet.
token = BridgedERC721(
deployProxy({
name: "tkop",
impl: address(new BridgedERC721()),
data: abi.encodeCall(
BridgedERC721.init,
(address(addressManager), randAddress(), 100, "TKOP", "Taiko Points Token")
)
})
);

vm.stopPrank();

vm.startPrank(address(vault));
// 5. Mint 5 NFTs token ids from 0 - 4 to the vault. This step on mainnet will be done by
// Taiko Labs. For
// testing on A6 the imporatnt thing is: HAVE tokens in this vault!
for (uint256 i; i != mintSupply; ++i) {
BridgedERC721(token).mint(address(vault), i);
}

vm.stopPrank();

// 6. Deploy the airdrop contract, and set the claimStart, claimEnd and merkleRoot -> On
// mainnet it will be separated into 2 tasks obviously, because first we deploy, then we set
// those variables. On testnet (e.g. A6) it shall also be 2 steps easily. Deploy a contract,
// then set merkle.
claimStart = uint64(block.timestamp + 10);
claimEnd = uint64(block.timestamp + 10_000);
merkleProof = new bytes32[](3);

vm.startPrank(owner);
airdrop = MockERC721Airdrop(
deployProxy({
name: "MockERC721Airdrop",
impl: address(new MockERC721Airdrop()),
data: abi.encodeCall(
ERC721Airdrop.init,
(claimStart, claimEnd, merkleRoot, address(token), address(vault))
)
})
);

vm.stopPrank();

// 7. Approval (Vault approves Airdrop contract to be the spender!) Has to be done on
// testnet and mainnet too, obviously.
vm.prank(address(vault), owner);
BridgedERC721(token).setApprovalForAll(address(airdrop), true);

// Vault shall have the balance
assertEq(BridgedERC721(token).balanceOf(address(vault)), mintSupply);

vm.roll(block.number + 1);
}

function test_claim() public {
vm.warp(claimStart);
vm.prank(Alice, Alice);
uint256[] memory tokenIds = new uint256[](5);
// Airdrop all the minted tokens
for (uint256 i; i != mintSupply; ++i) {
tokenIds[i] = i;
}

airdrop.claim(Alice, tokenIds, merkleProof);

// Check Alice balance
assertEq(BridgedERC721(token).balanceOf(Alice), mintSupply);
}
}

0 comments on commit c8f6e9b

Please sign in to comment.