Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(protocol): added test case for ERC721Airdrop #16025

Merged
merged 5 commits into from
Feb 25, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/protocol/test/TaikoTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,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
171 changes: 171 additions & 0 deletions packages/protocol/test/team/airdrop/ERC721Airdrop.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import "../../TaikoTest.sol";
import "./LibDelegationSigUtil.sol";
arularmstrong marked this conversation as resolved.
Show resolved Hide resolved
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
arularmstrong marked this conversation as resolved.
Show resolved Hide resolved

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 OwnableUpgradeable {
/// @notice Initializes the vault.
function init() external initializer {
__Ownable_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);
}
}