From b830bf8c4a72b7e0bf0fd057c05ff3e35996474d Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Wed, 10 May 2023 16:04:46 +0200 Subject: [PATCH 1/2] delete(multi-airdrop) --- .gas-snapshot | 13 +- Makefile | 4 - foundry.toml | 5 +- scripts/WorldIDMultiAirdrop.s.sol | 40 ------ scripts/deploy.js | 93 ------------ src/WorldIDMultiAirdrop.sol | 161 --------------------- src/test/WorldIDMultiAirdrop.t.sol | 222 ----------------------------- 7 files changed, 2 insertions(+), 536 deletions(-) delete mode 100644 scripts/WorldIDMultiAirdrop.s.sol delete mode 100644 src/WorldIDMultiAirdrop.sol delete mode 100644 src/test/WorldIDMultiAirdrop.t.sol diff --git a/.gas-snapshot b/.gas-snapshot index 5fcbd44..ff4560b 100644 --- a/.gas-snapshot +++ b/.gas-snapshot @@ -6,15 +6,4 @@ SemaphoreAirdropTest:testCannotClaimWithInvalidProof() (gas: 8797746687696282396 SemaphoreAirdropTest:testCannotClaimWithInvalidSignal() (gas: 2948678) SemaphoreAirdropTest:testCannotDoubleClaim() (gas: 3004607) SemaphoreAirdropTest:testCannotUpdateAirdropAmountIfNotManager() (gas: 16865) -SemaphoreAirdropTest:testUpdateAirdropAmount() (gas: 19308) -SemaphoreMultiAirdropTest:testCanClaim() (gas: 3122714) -SemaphoreMultiAirdropTest:testCanClaimAfterNewMemberAdded() (gas: 3789635) -SemaphoreMultiAirdropTest:testCanCreateAirdrop() (gas: 141458) -SemaphoreMultiAirdropTest:testCanUpdateAirdropDetails() (gas: 149676) -SemaphoreMultiAirdropTest:testCannotClaimHoursAfterNewMemberAdded() (gas: 3513319) -SemaphoreMultiAirdropTest:testCannotClaimIfNotMember() (gas: 3067076) -SemaphoreMultiAirdropTest:testCannotClaimNonExistantAirdrop() (gas: 2731400) -SemaphoreMultiAirdropTest:testCannotClaimWithInvalidProof() (gas: 8797746687696288053) -SemaphoreMultiAirdropTest:testCannotClaimWithInvalidSignal() (gas: 3069668) -SemaphoreMultiAirdropTest:testCannotDoubleClaim() (gas: 3125373) -SemaphoreMultiAirdropTest:testNonOwnerCannotUpdateAirdropDetails() (gas: 143499) +SemaphoreAirdropTest:testUpdateAirdropAmount() (gas: 19308) \ No newline at end of file diff --git a/Makefile b/Makefile index 1417e08..123f127 100644 --- a/Makefile +++ b/Makefile @@ -22,12 +22,8 @@ snapshot:; FOUNDRY_PROFILE=bench forge snapshot # Deploy contracts deploy-airdrop: install build; node --no-warnings scripts/deploy.js deploy-airdrop -deploy-multi-airdrop: install build; node --no-warnings scripts/deploy.js deploy-multi-airdrop - mock-airdrop: install build; node --no-warnings scripts/deploy.js mock-airdrop -mock-multi-airdrop: install build; node --no-warnings scripts/deploy.js mock-multi-airdrop - # ===== Utility Rules ================================================================================================= # Format the solidity code. diff --git a/foundry.toml b/foundry.toml index cf23154..f778688 100644 --- a/foundry.toml +++ b/foundry.toml @@ -70,10 +70,7 @@ optimizer_runs = 20000 verbosity = 3 # We can specify the contracts to track gas data for by tracing. -gas_reports = [ - "WolrdIDAirdrop", - "WorldIDMultiAirdrop", -] +gas_reports = ["WolrdIDAirdrop"] # === Production Profile ====================================================== diff --git a/scripts/WorldIDMultiAirdrop.s.sol b/scripts/WorldIDMultiAirdrop.s.sol deleted file mode 100644 index 6c93b86..0000000 --- a/scripts/WorldIDMultiAirdrop.s.sol +++ /dev/null @@ -1,40 +0,0 @@ -pragma solidity ^0.8.19; - -import {Script} from "forge-std/Script.sol"; - -import {IWorldIDGroups} from "world-id-contracts/interfaces/IWorldIDGroups.sol"; -import {WorldIDMultiAirdrop} from "src/WorldIDMultiAirdrop.sol"; - -/// @title WorldIDMultiAirdrop deployment script -/// @author Worldcoin -/// @notice Deploys the WorldIDMultiAirdrop contracts -/// @dev You need to have the necessary values in scripts/.deploy-config.json in order for it to work. -/// Can be run by executing `make deploy-multi-airdrop` (assumes a deployment of world-id-contracts or a mock) -/// or `make mock-multi-airdrop` (local testing with Foundry's anvil) in the shell. -contract DeployWorldIDMultiAirdrop is Script { - - WorldIDMultiAirdrop public worldIDMultiAirdrop; - - /////////////////////////////////////////////////////////////////// - /// CONFIG /// - /////////////////////////////////////////////////////////////////// - string public root = vm.projectRoot(); - string public path = string.concat(root, "/scripts/.deploy-config.json"); - string public json = vm.readFile(path); - - uint256 public privateKey = abi.decode(vm.parseJson(json, ".privateKey"), (uint256)); - address public worldIDRouterAddress = abi.decode(vm.parseJson(json, ".worldIDRouterAddress"), (address)); - - - IWorldIDGroups worldIdRouter = IWorldIDGroups(worldIDRouterAddress); - - - function run() external { - vm.startBroadcast(privateKey); - - worldIDMultiAirdrop = new WorldIDMultiAirdrop(worldIdRouter); - - vm.stopBroadcast(); - } - -} \ No newline at end of file diff --git a/scripts/deploy.js b/scripts/deploy.js index 29814eb..34ad096 100644 --- a/scripts/deploy.js +++ b/scripts/deploy.js @@ -236,75 +236,6 @@ async function deployMockAirdrop(config) { } } -async function deployMockMultiAirdrop(config) { - dotenv.config(); - - await getPrivateKey(config); - await getEthereumRpcUrl(config); - await getEtherscanApiKey(config); - await deployWorldIDIdentityManagerRouterMock(config); - await getWorldIDIdentityManagerRouterAddress(config); - await saveConfiguration(config); - await getAirdropParameters(config); - - const spinner = ora(`Deploying WorldIDAirdrop contract...`).start(); - - try { - const data = execSync( - `forge script scripts/WorldIDMultiAirdrop.s.sol:DeployWorldIDMultiAirdrop --fork-url ${config.ethereumRpcUrl} \ - --etherscan-api-key ${config.ethereumEtherscanApiKey} --broadcast -vvvv` - ); - console.log(data.toString()); - spinner.succeed('Deployed WorldIDMultiAirdrop contract successfully!'); - } catch (err) { - console.error(err); - spinner.fail('Deployment of WorldIDMultiAirdrop has failed.'); - } -} - -// TODO: Couldn't get it to work :( -// You can use the following instead -// To deploy Multi Airdrop: -// ``` -// forge create --private-key $PRIVATE_KEY --rpc-url $RPC_URL src/WorldIDMultiAirdrop.sol:WorldIDMultiAirdrop --constructor-args $ADDRESS_OF_WORLD_ID_ROUTER -// ``` -// -// To encode args for verification: -// ``` -// cast abi-encode 'constructor(address)' $ADDRESS_OF_WORLD_ID_ROUTER -// ``` -// -// To verify: -// -// ``` -// forge verify-contract $ADDRESS_OF_MULTI_AIRDROP src/WorldIDMultiAirdrop.sol:WorldIDMultiAirdrop --etherscan-api-key $API_KEY --constructor-args $CONSTRUCTOR_ARGS_FROM_CAST_ENCODE --chain 137 -// ``` -async function deployMultiAirdrop(config) { - dotenv.config(); - - await getPrivateKey(config); - await getEthereumRpcUrl(config); - await getEtherscanApiKey(config); - await getWorldIDIdentityManagerRouterAddress(config); - await saveConfiguration(config); - await getAirdropParameters(config); - - const spinner = ora(`Deploying WorldIDAirdrop contract...`).start(); - - try { - console.log('x'); - const data = execSync( - `forge script scripts/WorldIDMultiAirdrop.s.sol:DeployWorldIDMultiAirdrop --legacy --fork-url ${config.ethereumRpcUrl} --broadcast -vvvv` - ); - console.log('x'); - console.log(data.toString()); - spinner.succeed('Deployed WorldIDMultiAirdrop contract successfully!'); - } catch (err) { - console.error(err); - spinner.fail('Deployment of WorldIDMultiAirdrop has failed.'); - } -} - async function setAllowance(config) { await getErc20Address(config); await getHolderAddress(config); @@ -342,17 +273,6 @@ async function main() { await saveConfiguration(config); }); - program - .name('deploy-multi-aidrop') - .command('deploy-multi-airdrop') - .description('Interactively deploys the WorldIDMultiAirdrop contracts on Ethereum mainnet.') - .action(async () => { - const options = program.opts(); - let config = await loadConfiguration(options.config); - await deployMultiAirdrop(config); - await saveConfiguration(config); - }); - program .name('mock-airdrop') .command('mock-airdrop') @@ -366,19 +286,6 @@ async function main() { await saveConfiguration(config); }); - program - .name('mock-multi-airdrop') - .command('mock-multi-airdrop') - .description( - 'Interactively deploys WorldIDIdentityManagerMock alongside with WorldIDMultiAirdrop for testing.' - ) - .action(async () => { - const options = program.opts(); - let config = await loadConfiguration(options.config); - await deployMockMultiAirdrop(config); - await saveConfiguration(config); - }); - program .name('set-allowance') .command('set-allowance') diff --git a/src/WorldIDMultiAirdrop.sol b/src/WorldIDMultiAirdrop.sol deleted file mode 100644 index 3d553ce..0000000 --- a/src/WorldIDMultiAirdrop.sol +++ /dev/null @@ -1,161 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {ERC20} from "solmate/tokens/ERC20.sol"; -import {SafeTransferLib} from "solmate/utils/SafeTransferLib.sol"; -import {IWorldIDGroups} from "world-id-contracts/interfaces/IWorldIDGroups.sol"; -import {ByteHasher} from "world-id-contracts/libraries/ByteHasher.sol"; -import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol"; - -/// @title World ID Multiple Airdrop Manager -/// @author Worldcoin -/// @notice Template contract for managing multiple airdrops to World ID members. -contract WorldIDMultiAirdrop is Ownable { - using ByteHasher for bytes; - - /////////////////////////////////////////////////////////////////////////////// - /// ERRORS /// - ////////////////////////////////////////////////////////////////////////////// - - /// @notice Thrown when trying to create or update airdrop details without being the manager - error Unauthorized(); - - /// @notice Thrown when attempting to reuse a nullifier - error InvalidNullifier(); - - /// @notice Thrown when attempting to claim a non-existent airdrop - error InvalidAirdrop(); - - /////////////////////////////////////////////////////////////////////////////// - /// EVENTS /// - ////////////////////////////////////////////////////////////////////////////// - - /// @notice Emitted when an airdrop is created - /// @param airdropId The id of the airdrop - /// @param airdrop The airdrop details - event AirdropCreated(uint256 airdropId, Airdrop airdrop); - - /// @notice Emitted when an airdrop is successfully claimed - /// @param receiver The address that received the airdrop - event AirdropClaimed(uint256 indexed airdropId, address receiver); - - /// @notice Emitted when the airdropped amount is changed - /// @param airdropId The id of the airdrop getting updated - /// @param airdrop The new details for the airdrop - event AirdropUpdated(uint256 indexed airdropId, Airdrop airdrop); - - /////////////////////////////////////////////////////////////////////////////// - /// STRUCTS /// - ////////////////////////////////////////////////////////////////////////////// - - /// @notice Stores the details for a specific airdrop - /// @param groupId The ID of the WorldIDRouter group that will be eligible to claim this airdrop - /// @param token The ERC20 token that will be airdropped to eligible participants - /// @param manager The address that manages this airdrop, which is allowed to update the airdrop details. - /// @param holder The address holding the tokens that will be airdropped - /// @param amount The amount of tokens that each participant will receive upon claiming - struct Airdrop { - uint256 groupId; - ERC20 token; - address manager; - address holder; - uint256 amount; - } - - /////////////////////////////////////////////////////////////////////////////// - /// CONFIG STORAGE /// - ////////////////////////////////////////////////////////////////////////////// - - /// @dev The WorldID router instance that will be used for managing groups and verifying proofs - IWorldIDGroups internal immutable worldIdRouter; - - /// @dev Whether a nullifier hash has been used already. Used to prevent double-signaling - mapping(uint256 => bool) internal nullifierHashes; - - uint256 internal nextAirdropId = 1; - mapping(uint256 => Airdrop) public getAirdrop; - - /////////////////////////////////////////////////////////////////////////////// - /// CONSTRUCTOR /// - ////////////////////////////////////////////////////////////////////////////// - - /// @notice Deploys a WorldIDAirdrop instance - /// @param _worldIdRouter The WorldID router instance that will manage groups and verify proofs - constructor(IWorldIDGroups _worldIdRouter) { - worldIdRouter = _worldIdRouter; - } - - /// @notice Create a new airdrop - /// @param groupId The ID of the WorldIDRouter group that will be eligible to claim this airdrop - /// @param token The ERC20 token that will be airdropped to eligible participants - /// @param holder The address holding the tokens that will be airdropped - /// @param amount The amount of tokens that each participant will receive upon claiming - function createAirdrop(uint256 groupId, ERC20 token, address holder, uint256 amount) public onlyOwner { - - Airdrop memory airdrop = Airdrop({ - groupId: groupId, - token: token, - manager: msg.sender, - holder: holder, - amount: amount - }); - - getAirdrop[nextAirdropId] = airdrop; - emit AirdropCreated(nextAirdropId, airdrop); - - ++nextAirdropId; - } - - /////////////////////////////////////////////////////////////////////////////// - /// CLAIM LOGIC /// - ////////////////////////////////////////////////////////////////////////////// - - /// @notice Claim a given airdrop - /// @param airdropId The id of the airdrop getting claimed - /// @param receiver The address that will receive the tokens - /// @param root The of the Merkle tree - /// @param nullifierHash The nullifier for this proof, preventing double signaling - /// @param proof The zero knowledge proof that demostrates the claimer is part of the WorldID group - /// @dev hashToField function docs are in lib/world-id-contracts/src/libraries/ByteHasher.sol - function claim( - uint256 airdropId, - address receiver, - uint256 root, - uint256 nullifierHash, - uint256[8] calldata proof - ) public { - if (nullifierHashes[nullifierHash]) revert InvalidNullifier(); - - Airdrop memory airdrop = getAirdrop[airdropId]; - if (airdropId == 0 || airdropId >= nextAirdropId) revert InvalidAirdrop(); - - worldIdRouter.verifyProof( - airdrop.groupId, - root, - abi.encodePacked(receiver).hashToField(), - nullifierHash, - abi.encodePacked(address(this), airdropId).hashToField(), - proof - ); - - nullifierHashes[nullifierHash] = true; - emit AirdropClaimed(airdropId, receiver); - - SafeTransferLib.safeTransferFrom(airdrop.token, airdrop.holder, receiver, airdrop.amount); - } - - /////////////////////////////////////////////////////////////////////////////// - /// CONFIG LOGIC /// - ////////////////////////////////////////////////////////////////////////////// - - /// @notice Update the details for a given airdrop, for addresses that haven't claimed already. Can only be called by the airdrop creator - /// @param airdropId The id of the airdrop to update - /// @param airdrop The new details for the airdrop - function updateDetails(uint256 airdropId, Airdrop calldata airdrop) public { - if (getAirdrop[airdropId].manager != msg.sender) revert Unauthorized(); - - getAirdrop[airdropId] = airdrop; - - emit AirdropUpdated(airdropId, airdrop); - } -} diff --git a/src/test/WorldIDMultiAirdrop.t.sol b/src/test/WorldIDMultiAirdrop.t.sol deleted file mode 100644 index 4359946..0000000 --- a/src/test/WorldIDMultiAirdrop.t.sol +++ /dev/null @@ -1,222 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.8.19; - -import {PRBTest} from "@prb/test/PRBTest.sol"; -import {WorldIDIdentityManagerRouterMock} from "src/test/mock/WorldIDIdentityManagerRouterMock.sol"; -import {TestERC20, ERC20} from "src/test/mock/TestERC20.sol"; -import {WorldIDMultiAirdrop} from "src/WorldIDMultiAirdrop.sol"; - -/// @title World ID Multi Airdrop tests -/// @notice Contains tests for the aidrop contracts of various tokens to the World ID users -/// @author Worldcoin -/// @dev Tests that multiple airdrops can be created and later claimed by World ID users that submit -/// valid World ID proofs. -contract WorldIDMultiAirdropTest is PRBTest { - /////////////////////////////////////////////////////////////////// - /// EVENTS /// - /////////////////////////////////////////////////////////////////// - - event AirdropClaimed(uint256 indexed airdropId, address receiver); - event AirdropCreated(uint256 airdropId, WorldIDMultiAirdrop.Airdrop airdrop); - event AirdropUpdated(uint256 indexed airdropId, WorldIDMultiAirdrop.Airdrop airdrop); - - /////////////////////////////////////////////////////////////////// - /// CONFIG STORAGE /// - /////////////////////////////////////////////////////////////////// - - address public user; - address public airdropOwner; - uint256 internal groupId; - TestERC20 internal token; - uint256[8] internal proof; - WorldIDIdentityManagerRouterMock internal worldIDIdentityManagerRouterMock; - WorldIDMultiAirdrop internal airdrop; - - function setUp() public { - groupId = 1; - user = address(0x2); - token = new TestERC20(); - airdropOwner = address(0x1); - worldIDIdentityManagerRouterMock = new WorldIDIdentityManagerRouterMock(); - vm.prank(airdropOwner); - airdrop = new WorldIDMultiAirdrop(worldIDIdentityManagerRouterMock); - proof = [0, 0, 0, 0, 0, 0, 0, 0]; - - vm.label(address(this), "Sender"); - vm.label(user, "Holder"); - vm.label(airdropOwner, "Airdrop Owner"); - vm.label(address(token), "Token"); - vm.label(address(worldIDIdentityManagerRouterMock), "WorldIDIdentityManagerRouterMock"); - vm.label(address(airdrop), "WorldIDMultiAirdrop"); - - // Issue some tokens to the user address, to be airdropped from the contract - token.issue(address(user), 10 ether); - - // Approve spending from the airdrop contract - vm.prank(address(user)); - token.approve(address(airdrop), type(uint256).max); - } - - /// @notice Tests that you can create an airdrop - function testCanCreateAirdrop() public { - - vm.expectEmit(false, false, false, true); - emit AirdropCreated( - 1, - WorldIDMultiAirdrop.Airdrop({ - groupId: groupId, - token: token, - manager: airdropOwner, - holder: address(user), - amount: 1 ether - }) - ); - - vm.prank(airdropOwner); - airdrop.createAirdrop(groupId, token, address(user), 1 ether); - - (uint256 _groupId, ERC20 _token, address manager, address _holder, uint256 amount) = - airdrop.getAirdrop(1); - - assertEq(_groupId, groupId); - assertEq(address(_token), address(token)); - assertEq(manager, airdropOwner); - assertEq(_holder, address(user)); - assertEq(amount, 1 ether); - } - - /// @notice Tests that a user can claim a specific airdrop if they provide a valid World ID proof - /// @dev mocks verifyProof inside airdrop.claim(), always goes through - function testCanClaim(uint256 worldIDRoot, uint256 nullifierHash) public { - vm.assume(worldIDRoot != 0 && nullifierHash != 0); - - assertEq(token.balanceOf(address(this)), 0); - - vm.prank(airdropOwner); - airdrop.createAirdrop(groupId, token, address(user), 1 ether); - - vm.expectEmit(true, false, false, true); - emit AirdropClaimed(1, address(this)); - airdrop.claim(1, address(this), worldIDRoot, nullifierHash, proof); - - assertEq(token.balanceOf(address(this)), 1 ether); - } - - /// @notice Tests that a user can't claim an airdrop that hasn't been defined in the contract. - /// @dev mocks verifyProof inside airdrop.claim(), always goes through - function testCannotClaimNonExistantAirdrop(uint256 worldIDRoot, uint256 nullifierHash) public { - vm.assume(worldIDRoot != 0 && nullifierHash != 0); - - assertEq(token.balanceOf(address(this)), 0); - - vm.expectRevert(WorldIDMultiAirdrop.InvalidAirdrop.selector); - airdrop.claim(1, address(this), worldIDRoot, nullifierHash, proof); - - assertEq(token.balanceOf(address(this)), 0); - } - - /// @notice Tests that a user can't claim an airdrop twice (consume the same nullifier hash twice). - function testCannotDoubleClaim(uint256 worldIDRoot, uint256 nullifierHash) public { - vm.assume(worldIDRoot != 0 && nullifierHash != 0); - - assertEq(token.balanceOf(address(this)), 0); - - vm.prank(airdropOwner); - airdrop.createAirdrop(groupId, token, address(user), 1 ether); - - airdrop.claim(1, address(this), worldIDRoot, nullifierHash, proof); - - assertEq(token.balanceOf(address(this)), 1 ether); - - vm.expectRevert(WorldIDMultiAirdrop.InvalidNullifier.selector); - airdrop.claim(1, address(this), worldIDRoot, nullifierHash, proof); - - assertEq(token.balanceOf(address(this)), 1 ether); - } - - /// @notice Tests that the creator of the airdrop can update the details of the airdrop. - function testCanUpdateAirdropDetails() public { - vm.prank(airdropOwner); - airdrop.createAirdrop(groupId, token, address(user), 1 ether); - - ( - uint256 oldGroupId, - ERC20 oldToken, - address oldManager, - address oldHolder, - uint256 oldAmount - ) = airdrop.getAirdrop(1); - - assertEq(oldGroupId, groupId); - assertEq(address(oldToken), address(token)); - assertEq(oldManager, airdropOwner); - assertEq(oldHolder, address(user)); - assertEq(oldAmount, 1 ether); - - WorldIDMultiAirdrop.Airdrop memory newDetails = WorldIDMultiAirdrop.Airdrop({ - groupId: groupId + 1, - token: token, - manager: address(user), - holder: address(this), - amount: 2 ether - }); - - vm.expectEmit(true, false, false, true); - emit AirdropUpdated(1, newDetails); - vm.prank(airdropOwner); - airdrop.updateDetails(1, newDetails); - - (uint256 _groupId, ERC20 _token, address manager, address _holder, uint256 amount) = - airdrop.getAirdrop(1); - - assertEq(_groupId, newDetails.groupId); - assertEq(address(_token), address(newDetails.token)); - assertEq(manager, newDetails.manager); - assertEq(_holder, newDetails.holder); - assertEq(amount, newDetails.amount); - } - - /// @notice Tests that a non owner can't update details of an existing airdrop. - function testNonOwnerCannotUpdateAirdropDetails(address notAirdropOwner) public { - vm.assume(notAirdropOwner != airdropOwner && notAirdropOwner != address(0)); - - vm.prank(airdropOwner); - airdrop.createAirdrop(groupId, token, address(user), 1 ether); - - ( - uint256 oldGroupId, - ERC20 oldToken, - address oldManager, - address oldHolder, - uint256 oldAmount - ) = airdrop.getAirdrop(1); - - assertEq(oldGroupId, groupId); - assertEq(address(oldToken), address(token)); - assertEq(oldManager, airdropOwner); - assertEq(oldHolder, address(user)); - assertEq(oldAmount, 1 ether); - - vm.prank(notAirdropOwner); - vm.expectRevert(WorldIDMultiAirdrop.Unauthorized.selector); - airdrop.updateDetails( - 1, - WorldIDMultiAirdrop.Airdrop({ - groupId: groupId + 1, - token: token, - manager: address(user), - holder: address(this), - amount: 2 ether - }) - ); - - (uint256 _groupId, ERC20 _token, address manager, address _holder, uint256 amount) = - airdrop.getAirdrop(1); - - assertEq(_groupId, groupId); - assertEq(address(_token), address(token)); - assertEq(manager, airdropOwner); - assertEq(_holder, address(user)); - assertEq(amount, 1 ether); - } -} From f7d60fea284507d6a08ec7afd11b7b2bfea065c3 Mon Sep 17 00:00:00 2001 From: "dcbuilder.eth" Date: Wed, 10 May 2023 16:21:31 +0200 Subject: [PATCH 2/2] add(event) --- src/WorldIDAirdrop.sol | 2 ++ src/test/WorldIDAirdrop.t.sol | 11 ++++++++--- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/WorldIDAirdrop.sol b/src/WorldIDAirdrop.sol index 0a75875..0e7238c 100644 --- a/src/WorldIDAirdrop.sol +++ b/src/WorldIDAirdrop.sol @@ -118,6 +118,8 @@ contract WorldIDAirdrop { nullifierHashes[nullifierHash] = true; SafeTransferLib.safeTransferFrom(token, holder, receiver, airdropAmount); + + emit AirdropClaimed(receiver); } /////////////////////////////////////////////////////////////////////////////// diff --git a/src/test/WorldIDAirdrop.t.sol b/src/test/WorldIDAirdrop.t.sol index f720f38..04da3ff 100644 --- a/src/test/WorldIDAirdrop.t.sol +++ b/src/test/WorldIDAirdrop.t.sol @@ -13,6 +13,7 @@ import {WorldIDAirdrop} from "../WorldIDAirdrop.sol"; /// functionality for a single airdrop. contract WorldIDAirdropTest is PRBTest { event AmountUpdated(uint256 amount); + event AirdropClaimed(address receiver); address public user; uint256 internal groupId; @@ -56,14 +57,18 @@ contract WorldIDAirdropTest is PRBTest { } /// @notice Tests that the user is able to claim tokens if the World ID proof is valid - function testCanClaim(uint256 worldIDRoot, uint256 nullifierHash) public { + function testCanClaim(uint256 worldIDRoot, uint256 nullifierHash, address receiver) public { vm.assume(worldIDRoot != 0 && nullifierHash != 0); assertEq(token.balanceOf(address(this)), 0); - airdrop.claim(address(this), worldIDRoot, nullifierHash, proof); + vm.expectEmit(true, true, true, true); + emit AirdropClaimed(receiver); - assertEq(token.balanceOf(address(this)), airdrop.airdropAmount()); + vm.prank(receiver); + airdrop.claim(receiver, worldIDRoot, nullifierHash, proof); + + assertEq(token.balanceOf(receiver), airdrop.airdropAmount()); } /// @notice Tests that nullifier hash for the same action cannot be consumed twice