diff --git a/airdrop/cairo_project.toml b/airdrop/cairo_project.toml index 3041433..fbf7e1b 100644 --- a/airdrop/cairo_project.toml +++ b/airdrop/cairo_project.toml @@ -1,3 +1,3 @@ [crate_roots] -governance = "src" +airdrop = "src" tests = "tests" diff --git a/airdrop/src/airdrop.cairo b/airdrop/src/airdrop.cairo index 01343f6..0c25300 100644 --- a/airdrop/src/airdrop.cairo +++ b/airdrop/src/airdrop.cairo @@ -1,39 +1,40 @@ -use core::array::{Array, Span}; -use governance::interfaces::erc20::{IERC20Dispatcher}; +use core::array::Span; + use starknet::{ContractAddress}; -#[derive(Copy, Drop, Serde, Hash, PartialEq, Debug)] -pub struct Claim { - // the unique ID of the claim - pub id: u64, - // the address that will receive the token - pub claimee: ContractAddress, - // the amount of token the address is entitled to - pub amount: u128, -} +use airdrop::interfaces::erc20::{IERC20Dispatcher}; + #[starknet::interface] -pub trait IAirdrop { +pub trait IAirdrop { // Return the root of the airdrop - fn get_root(self: @TStorage) -> felt252; + fn get_root(self: @TContractState) -> felt252; // Return the token being dropped - fn get_token(self: @TStorage) -> IERC20Dispatcher; - - // Claims the given allotment of tokens. - // Because this method is idempotent, it does not revert in case of a second submission of the same claim. - // This makes it simpler to batch many claims together in a single transaction. - // Returns true iff the claim was processed. Returns false if the claim was already claimed. - // Panics if the proof is invalid. - fn claim(ref self: TStorage, claim: Claim, proof: Span) -> bool; - - // Claims the batch of up to 128 claims that must be aligned with a single bitmap, i.e. the id of the first must be a multiple of 128 - // and the claims should be sequentially in order. The proof verification is optimized in this method. - // Returns the number of claims that were executed - fn claim_128(ref self: TStorage, claims: Span, remaining_proof: Span) -> u8; - - // Return whether the claim with the given ID has been claimed - fn is_claimed(self: @TStorage, claim_id: u64) -> bool; + fn get_token(self: @TContractState) -> IERC20Dispatcher; + + // Return the claiming start time + fn get_start_time(self: @TContractState) -> u64; + + // Return the vesting peroid duration in seconds + fn get_vesting_duration(self: @TContractState) -> u64; + + // Return the claimed amount of a recipient + fn get_claimed_amount(self: @TContractState, recipient: ContractAddress) -> u128; + + // Calculates the total claimable amount based on timestamp alone + fn calculate_total_claimable(self: @TContractState, total_amount: u128) -> u128; + + // Claim the airdrop. + // The `total_amount` sent in here is the total allocation amount, which is subject to vesting. + // Therefore, users will likely receive smaller amounts depending on when they claim. + // Returns true iif amount _actually_ claimed is larger than 0. + fn claim( + ref self: TContractState, + recipient: ContractAddress, + total_amount: u128, + proof: Span + ) -> bool; } #[starknet::contract] @@ -42,102 +43,49 @@ pub mod Airdrop { use core::hash::{LegacyHash}; use core::num::traits::one::{One}; use core::num::traits::zero::{Zero}; - use governance::interfaces::erc20::{IERC20DispatcherTrait}; - use governance::utils::exp2::{exp2}; - use super::{IAirdrop, ContractAddress, Claim, IERC20Dispatcher}; + use core::poseidon::hades_permutation; + use starknet::{ContractAddress, get_block_timestamp}; - pub(crate) fn hash_function(a: felt252, b: felt252) -> felt252 { - let a_u256: u256 = a.into(); - if a_u256 < b.into() { - core::pedersen::pedersen(a, b) - } else { - core::pedersen::pedersen(b, a) - } - } + use airdrop::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; - // Compute the pedersen root of a merkle tree by combining the current node with each sibling up the tree - pub(crate) fn compute_pedersen_root(current: felt252, mut proof: Span) -> felt252 { - match proof.pop_front() { - Option::Some(proof_element) => { - compute_pedersen_root(hash_function(current, *proof_element), proof) - }, - Option::None => { current }, - } - } + use super::{IAirdrop}; #[storage] struct Storage { root: felt252, token: IERC20Dispatcher, - claimed_bitmap: LegacyMap, - } - - #[derive(Drop, starknet::Event)] - pub(crate) struct Claimed { - pub claim: Claim + start_time: u64, + vesting_duration: u64, + claimed_amounts: LegacyMap, } - #[derive(starknet::Event, Drop)] #[event] + #[derive(Drop, starknet::Event)] enum Event { Claimed: Claimed, } - #[constructor] - fn constructor(ref self: ContractState, token: IERC20Dispatcher, root: felt252) { - self.root.write(root); - self.token.write(token); - } - - const BITMAP_SIZE: NonZero = 128; - - fn claim_id_to_bitmap_index(claim_id: u64) -> (u64, u8) { - let (word, index) = DivRem::div_rem(claim_id, BITMAP_SIZE); - (word, index.try_into().unwrap()) - } - - pub fn hash_claim(claim: Claim) -> felt252 { - LegacyHash::hash(selector!("ekubo::governance::airdrop::Claim"), claim) + #[derive(Drop, starknet::Event)] + struct Claimed { + recipient: ContractAddress, + amount: u128, + total_amount: u128 } - pub fn compute_root_of_group(mut claims: Span) -> felt252 { - assert(!claims.is_empty(), 'NO_CLAIMS'); - let mut claim_hashes: Array = ArrayTrait::new(); - - let mut last_claim_id: Option = Option::None; - - while let Option::Some(claim) = claims - .pop_front() { - if let Option::Some(last_id) = last_claim_id { - assert(last_id == (*claim.id - 1), 'SEQUENTIAL'); - }; - - claim_hashes.append(hash_claim(*claim)); - last_claim_id = Option::Some(*claim.id); - }; - - // will eventually contain an array of length 1 - let mut current_layer: Span = claim_hashes.span(); - - while current_layer - .len() - .is_non_one() { - let mut next_layer: Array = ArrayTrait::new(); - - while let Option::Some(hash) = current_layer - .pop_front() { - next_layer - .append( - hash_function(*hash, *current_layer.pop_front().unwrap_or(hash)) - ); - }; - - current_layer = next_layer.span(); - }; - - *current_layer.pop_front().unwrap() + #[constructor] + fn constructor( + ref self: ContractState, + token: IERC20Dispatcher, + root: felt252, + start_time: u64, + vesting_duration: u64 + ) { + self.root.write(root); + self.token.write(token); + self.start_time.write(start_time); + self.vesting_duration.write(vesting_duration); } #[abi(embed_v0)] @@ -150,84 +98,110 @@ pub mod Airdrop { self.token.read() } - fn claim(ref self: ContractState, claim: Claim, proof: Span) -> bool { - let leaf = hash_claim(claim); - assert(self.root.read() == compute_pedersen_root(leaf, proof), 'INVALID_PROOF'); - - // this is copied in from is_claimed because we only want to read the bitmap once - let (word, index) = claim_id_to_bitmap_index(claim.id); - let bitmap = self.claimed_bitmap.read(word); - let already_claimed = Zeroable::is_non_zero(bitmap & exp2(index)); - - if already_claimed { - false - } else { - self.claimed_bitmap.write(word, bitmap | exp2(index.try_into().unwrap())); + fn get_start_time(self: @ContractState) -> u64 { + self.start_time.read() + } - self.token.read().transfer(claim.claimee, claim.amount.into()); + fn get_vesting_duration(self: @ContractState) -> u64 { + self.vesting_duration.read() + } - self.emit(Claimed { claim }); + fn get_claimed_amount(self: @ContractState, recipient: ContractAddress) -> u128 { + self.claimed_amounts.read(recipient) + } - true - } + fn calculate_total_claimable(self: @ContractState, total_amount: u128) -> u128 { + calculate_claimable_amount(self, total_amount) } - fn claim_128( - ref self: ContractState, mut claims: Span, remaining_proof: Span - ) -> u8 { - assert(claims.len() < 129, 'TOO_MANY_CLAIMS'); - assert(!claims.is_empty(), 'CLAIMS_EMPTY'); + fn claim( + ref self: ContractState, + recipient: ContractAddress, + total_amount: u128, + proof: Span + ) -> bool { + assert(Zeroable::is_non_zero(total_amount), 'ZERO_TOTAL_AMOUNT'); + assert(get_block_timestamp() >= self.start_time.read(), 'AIRDROP_NOT_STARTED'); - // groups that cross bitmap boundaries should just make multiple calls - // this code already reduces the number of pedersens in the verification by a factor of ~7 - let (word, index_u64) = DivRem::div_rem(*claims.at(0).id, BITMAP_SIZE); - assert(index_u64 == 0, 'FIRST_CLAIM_MUST_BE_MULT_128'); + let (leaf, _, _) = hades_permutation(recipient.into(), total_amount.into(), 2); + assert(self.root.read() == compute_pedersen_root(leaf, proof), 'INVALID_PROOF'); - let root_of_group = compute_root_of_group(claims); + let total_claimable_amount = calculate_claimable_amount(@self, total_amount); + let already_claimed_amount = self.claimed_amounts.read(recipient); - assert( - self.root.read() == compute_pedersen_root(root_of_group, remaining_proof), - 'INVALID_PROOF' - ); + if total_claimable_amount > already_claimed_amount { + let current_amount_claimed = total_claimable_amount - already_claimed_amount; + self.claimed_amounts.write(recipient, total_claimable_amount); - let mut bitmap = self.claimed_bitmap.read(word); + self + .emit( + Event::Claimed( + Claimed { + recipient: recipient, + amount: current_amount_claimed, + total_amount: total_amount + } + ) + ); - let mut index: u8 = 0; - let mut unclaimed: Array = ArrayTrait::new(); + self.token.read().transfer(recipient, current_amount_claimed.into()); - while let Option::Some(claim) = claims - .pop_front() { - let already_claimed = Zeroable::is_non_zero(bitmap & exp2(index)); + true + } else { + self + .emit( + Event::Claimed( + Claimed { recipient: recipient, amount: 0, total_amount: total_amount } + ) + ); - if !already_claimed { - bitmap = bitmap | exp2(index); - unclaimed.append(*claim); - } + false + } + } + } - index += 1; - }; + fn compute_pedersen_root(current: felt252, mut proof: Span) -> felt252 { + match proof.pop_front() { + Option::Some(proof_element) => { + compute_pedersen_root(hash_function(current, *proof_element), proof) + }, + Option::None => { current }, + } + } - self.claimed_bitmap.write(word, bitmap); + fn hash_function(a: felt252, b: felt252) -> felt252 { + let a_u256: u256 = a.into(); + if a_u256 < b.into() { + core::pedersen::pedersen(a, b) + } else { + core::pedersen::pedersen(b, a) + } + } - let num_claimed = unclaimed.len(); + fn calculate_claimable_amount(self: @ContractState, total_amount: u128) -> u128 { + let current_time = get_block_timestamp(); + let start_time = self.start_time.read(); - // the event emittance and transfers are separated from the above to prevent re-entrance - let token = self.token.read(); + if current_time < start_time { + 0 + } else { + let duration_elapsed = current_time - start_time; + let vesting_duration = self.vesting_duration.read(); - while let Option::Some(claim) = unclaimed - .pop_front() { - token.transfer(claim.claimee, claim.amount.into()); - self.emit(Claimed { claim }); - }; + if duration_elapsed >= vesting_duration { + // All vested already + total_amount + } else { + // Hard-coded to be 25% of total amount + let unlocked_amount = total_amount / 4; + let vesting_amount = total_amount - unlocked_amount; - // never fails because we assert claims length at the beginning so we know it's less than 128 - num_claimed.try_into().unwrap() - } + let vested_amount = vesting_amount + * duration_elapsed.into() + / vesting_duration.into(); - fn is_claimed(self: @ContractState, claim_id: u64) -> bool { - let (word, index) = claim_id_to_bitmap_index(claim_id); - let bitmap = self.claimed_bitmap.read(word); - Zeroable::is_non_zero(bitmap & exp2(index)) + unlocked_amount + vested_amount + } } } } diff --git a/airdrop/src/lib.cairo b/airdrop/src/lib.cairo index 2b096f7..a484b02 100644 --- a/airdrop/src/lib.cairo +++ b/airdrop/src/lib.cairo @@ -1,5 +1,3 @@ mod airdrop; mod interfaces; - -mod utils; diff --git a/airdrop/src/utils.cairo b/airdrop/src/utils.cairo deleted file mode 100644 index 44aa120..0000000 --- a/airdrop/src/utils.cairo +++ /dev/null @@ -1 +0,0 @@ -mod exp2; diff --git a/airdrop/src/utils/exp2.cairo b/airdrop/src/utils/exp2.cairo deleted file mode 100644 index a08db59..0000000 --- a/airdrop/src/utils/exp2.cairo +++ /dev/null @@ -1,136 +0,0 @@ -// Returns 2^n -pub fn exp2(n: u8) -> u128 { - match n { - 0 => { 0x1 }, - 1 => { 0x2 }, - 2 => { 0x4 }, - 3 => { 0x8 }, - 4 => { 0x10 }, - 5 => { 0x20 }, - 6 => { 0x40 }, - 7 => { 0x80 }, - 8 => { 0x100 }, - 9 => { 0x200 }, - 10 => { 0x400 }, - 11 => { 0x800 }, - 12 => { 0x1000 }, - 13 => { 0x2000 }, - 14 => { 0x4000 }, - 15 => { 0x8000 }, - 16 => { 0x10000 }, - 17 => { 0x20000 }, - 18 => { 0x40000 }, - 19 => { 0x80000 }, - 20 => { 0x100000 }, - 21 => { 0x200000 }, - 22 => { 0x400000 }, - 23 => { 0x800000 }, - 24 => { 0x1000000 }, - 25 => { 0x2000000 }, - 26 => { 0x4000000 }, - 27 => { 0x8000000 }, - 28 => { 0x10000000 }, - 29 => { 0x20000000 }, - 30 => { 0x40000000 }, - 31 => { 0x80000000 }, - 32 => { 0x100000000 }, - 33 => { 0x200000000 }, - 34 => { 0x400000000 }, - 35 => { 0x800000000 }, - 36 => { 0x1000000000 }, - 37 => { 0x2000000000 }, - 38 => { 0x4000000000 }, - 39 => { 0x8000000000 }, - 40 => { 0x10000000000 }, - 41 => { 0x20000000000 }, - 42 => { 0x40000000000 }, - 43 => { 0x80000000000 }, - 44 => { 0x100000000000 }, - 45 => { 0x200000000000 }, - 46 => { 0x400000000000 }, - 47 => { 0x800000000000 }, - 48 => { 0x1000000000000 }, - 49 => { 0x2000000000000 }, - 50 => { 0x4000000000000 }, - 51 => { 0x8000000000000 }, - 52 => { 0x10000000000000 }, - 53 => { 0x20000000000000 }, - 54 => { 0x40000000000000 }, - 55 => { 0x80000000000000 }, - 56 => { 0x100000000000000 }, - 57 => { 0x200000000000000 }, - 58 => { 0x400000000000000 }, - 59 => { 0x800000000000000 }, - 60 => { 0x1000000000000000 }, - 61 => { 0x2000000000000000 }, - 62 => { 0x4000000000000000 }, - 63 => { 0x8000000000000000 }, - 64 => { 0x10000000000000000 }, - 65 => { 0x20000000000000000 }, - 66 => { 0x40000000000000000 }, - 67 => { 0x80000000000000000 }, - 68 => { 0x100000000000000000 }, - 69 => { 0x200000000000000000 }, - 70 => { 0x400000000000000000 }, - 71 => { 0x800000000000000000 }, - 72 => { 0x1000000000000000000 }, - 73 => { 0x2000000000000000000 }, - 74 => { 0x4000000000000000000 }, - 75 => { 0x8000000000000000000 }, - 76 => { 0x10000000000000000000 }, - 77 => { 0x20000000000000000000 }, - 78 => { 0x40000000000000000000 }, - 79 => { 0x80000000000000000000 }, - 80 => { 0x100000000000000000000 }, - 81 => { 0x200000000000000000000 }, - 82 => { 0x400000000000000000000 }, - 83 => { 0x800000000000000000000 }, - 84 => { 0x1000000000000000000000 }, - 85 => { 0x2000000000000000000000 }, - 86 => { 0x4000000000000000000000 }, - 87 => { 0x8000000000000000000000 }, - 88 => { 0x10000000000000000000000 }, - 89 => { 0x20000000000000000000000 }, - 90 => { 0x40000000000000000000000 }, - 91 => { 0x80000000000000000000000 }, - 92 => { 0x100000000000000000000000 }, - 93 => { 0x200000000000000000000000 }, - 94 => { 0x400000000000000000000000 }, - 95 => { 0x800000000000000000000000 }, - 96 => { 0x1000000000000000000000000 }, - 97 => { 0x2000000000000000000000000 }, - 98 => { 0x4000000000000000000000000 }, - 99 => { 0x8000000000000000000000000 }, - 100 => { 0x10000000000000000000000000 }, - 101 => { 0x20000000000000000000000000 }, - 102 => { 0x40000000000000000000000000 }, - 103 => { 0x80000000000000000000000000 }, - 104 => { 0x100000000000000000000000000 }, - 105 => { 0x200000000000000000000000000 }, - 106 => { 0x400000000000000000000000000 }, - 107 => { 0x800000000000000000000000000 }, - 108 => { 0x1000000000000000000000000000 }, - 109 => { 0x2000000000000000000000000000 }, - 110 => { 0x4000000000000000000000000000 }, - 111 => { 0x8000000000000000000000000000 }, - 112 => { 0x10000000000000000000000000000 }, - 113 => { 0x20000000000000000000000000000 }, - 114 => { 0x40000000000000000000000000000 }, - 115 => { 0x80000000000000000000000000000 }, - 116 => { 0x100000000000000000000000000000 }, - 117 => { 0x200000000000000000000000000000 }, - 118 => { 0x400000000000000000000000000000 }, - 119 => { 0x800000000000000000000000000000 }, - 120 => { 0x1000000000000000000000000000000 }, - 121 => { 0x2000000000000000000000000000000 }, - 122 => { 0x4000000000000000000000000000000 }, - 123 => { 0x8000000000000000000000000000000 }, - 124 => { 0x10000000000000000000000000000000 }, - 125 => { 0x20000000000000000000000000000000 }, - 126 => { 0x40000000000000000000000000000000 }, - _ => { - assert(n == 127, 'exp2'); - 0x80000000000000000000000000000000 - } - } -} diff --git a/airdrop/tests/airdrop.cairo b/airdrop/tests/airdrop.cairo index 33c0ac0..545c80b 100644 --- a/airdrop/tests/airdrop.cairo +++ b/airdrop/tests/airdrop.cairo @@ -1,14 +1,13 @@ +use core::poseidon::hades_permutation; + use starknet::testing::{pop_log}; use starknet::{ get_contract_address, syscalls::{deploy_syscall}, ClassHash, contract_address_const, ContractAddress }; -use governance::airdrop::{ - IAirdropDispatcher, IAirdropDispatcherTrait, Airdrop, - Airdrop::{compute_pedersen_root, hash_function, hash_claim, compute_root_of_group}, Claim -}; -use governance::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; +use airdrop::airdrop::{Airdrop, IAirdropDispatcher, IAirdropDispatcherTrait}; +use airdrop::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait}; use tests::deploy::{deploy_airdrop, deploy_erc20}; use tests::mock::erc20::ERC20::Transfer; @@ -17,792 +16,98 @@ fn deploy_token(name: felt252, symbol: felt252, initial_supply: u128) -> IERC20D deploy_erc20(name, symbol, 18, initial_supply.into(), get_contract_address()) } -#[test] -fn test_selector() { - assert_eq!( - selector!("ekubo::governance::airdrop::Claim"), - 0x01782c4dfd9b809591e597c7a90a503c5db310130ec93790567b00d95ac81da0 - ); -} - -#[test] -fn test_hash() { - assert_eq!( - hash_claim(Claim { id: 123, claimee: contract_address_const::<456>(), amount: 789 }), - 0x0760b337026a91a6f2af99a0654f7fdff5d5c8d4e565277e787b99e17b1742a3 - ); -} - -#[test] -fn test_compute_pedersen_root_example_lt() { - assert_eq!( - compute_pedersen_root(1234, array![1235].span()), - 0x24e78083d17aa2e76897f44cfdad51a09276dd00a3468adc7e635d76d432a3b - ); -} - -#[test] -fn test_compute_pedersen_root_example_gt() { - assert_eq!( - compute_pedersen_root(1234, array![1233].span()), - 0x2488766c14e4bfd8299750797eeb07b7045398df03ea13cf33f0c0c6645d5f9 - ); +fn hash_claim(recipient: ContractAddress, total_amount: u128) -> felt252 { + let (leaf, _, _) = hades_permutation(recipient.into(), total_amount.into(), 2); + leaf } -#[test] -fn test_compute_pedersen_root_example_eq() { - assert_eq!( - compute_pedersen_root(1234, array![1234].span()), - 0x7a7148565b76ae90576733160aa3194a41ce528ee1434a64a9da50dcbf6d3ca - ); -} - -#[test] -fn test_compute_pedersen_root_empty() { - assert_eq!(compute_pedersen_root(1234, array![].span()), 1234); -} - -#[test] -fn test_compute_pedersen_root_recursive() { - assert_eq!( - compute_pedersen_root(1234, array![1234, 1234].span()), - 0xc92a4f7aa8979b0202770b378e46de07bebe0836f8ceece5a47ccf3929c6b0 - ); -} #[test] fn test_claim_single_recipient() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + starknet::testing::set_block_timestamp(10000); - let claim = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; + let token = deploy_token('AIRDROP', 'AD', 100); + let recipient = contract_address_const::<2345>(); - let leaf = hash_claim(claim); + let leaf = hash_claim(recipient, 100); - let airdrop = deploy_airdrop(token.contract_address, leaf); + let airdrop = deploy_airdrop(token.contract_address, leaf, 10000, 100); - token.transfer(airdrop.contract_address, 6789); + token.transfer(airdrop.contract_address, 100); - assert_eq!(airdrop.claim(claim, array![].span()), true); + assert_eq!(airdrop.claim(recipient, 100, array![].span()), true); let log = pop_log::(airdrop.contract_address).unwrap(); - assert_eq!(log.claim, claim); - - pop_log::(token.contract_address).unwrap(); - pop_log::(token.contract_address).unwrap(); - let log = pop_log::(token.contract_address).unwrap(); - assert_eq!(log.from, airdrop.contract_address); - assert_eq!(log.to, claim.claimee); - assert_eq!(log.value, claim.amount.into()); -} - -#[test] -fn test_claim_128_single_recipient_tree() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + assert_eq!(log.recipient, recipient); + assert_eq!(log.amount, 25); + assert_eq!(log.total_amount, 100); - let claim = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; + assert_eq!(token.balanceOf(recipient), 25); + assert_eq!(token.balanceOf(airdrop.contract_address), 75); - let leaf = hash_claim(claim); + // Claiming again on the same timestamp yields no more tokens - let airdrop = deploy_airdrop(token.contract_address, leaf); - - token.transfer(airdrop.contract_address, 6789); - - assert_eq!(airdrop.claim_128(array![claim].span(), array![].span()), 1); + assert_eq!(airdrop.claim(recipient, 100, array![].span()), false); let log = pop_log::(airdrop.contract_address).unwrap(); - assert_eq!(log.claim, claim); - - pop_log::(token.contract_address).unwrap(); - pop_log::(token.contract_address).unwrap(); - let log = pop_log::(token.contract_address).unwrap(); - assert_eq!(log.from, airdrop.contract_address); - assert_eq!(log.to, claim.claimee); - assert_eq!(log.value, claim.amount.into()); -} - -#[test] -fn test_double_claim() { - let token = deploy_token('AIRDROP', 'AD', 1234567); - - let claim = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; - - let leaf = hash_claim(claim); - - let airdrop = deploy_airdrop(token.contract_address, leaf); - - token.transfer(airdrop.contract_address, 6789); - assert_eq!(airdrop.claim(claim, array![].span()), true); - assert_eq!(airdrop.claim(claim, array![].span()), false); -} - -#[test] -fn test_double_claim_128_single_recipient_tree() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + assert_eq!(log.recipient, recipient); + assert_eq!(log.amount, 0); + assert_eq!(log.total_amount, 100); - let claim = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; - - let leaf = hash_claim(claim); - - let airdrop = deploy_airdrop(token.contract_address, leaf); - - token.transfer(airdrop.contract_address, 6789); - assert_eq!(airdrop.claim_128(array![claim].span(), array![].span()), 1); - assert_eq!(airdrop.claim_128(array![claim].span(), array![].span()), 0); -} - -#[test] -#[should_panic(expected: ('INVALID_PROOF', 'ENTRYPOINT_FAILED'))] -fn test_invalid_proof_single_entry() { - let token = deploy_token('AIRDROP', 'AD', 1234567); - - let claim = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; - - let leaf = hash_claim(claim); - - let airdrop = deploy_airdrop(token.contract_address, leaf); - - token.transfer(airdrop.contract_address, 6789); - airdrop.claim(claim, array![1].span()); -} - -#[test] -#[should_panic(expected: ('INVALID_PROOF', 'ENTRYPOINT_FAILED'))] -fn test_invalid_proof_fake_entry() { - let token = deploy_token('AIRDROP', 'AD', 1234567); - - let claim = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; - - let leaf = hash_claim(claim); - - let airdrop = deploy_airdrop(token.contract_address, leaf); - - token.transfer(airdrop.contract_address, 6789); - - airdrop - .claim( - Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789 + 1, }, - array![].span() - ); -} - -#[test] -#[should_panic(expected: ('NO_CLAIMS',))] -fn test_compute_root_of_group_empty() { - compute_root_of_group(array![].span()); -} - -#[test] -fn test_compute_root_of_group() { - assert_eq!( - compute_root_of_group( - array![Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789 }].span() - ), - 0x0336963eacdeee5da262a870ddfc7f8d12c6162ebdf58a805941c06d3baf8b40 - ); - assert_eq!( - compute_root_of_group( - array![ - Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789 }, - Claim { id: 1, claimee: contract_address_const::<3456>(), amount: 789 } - ] - .span() - ), - 0x0526f232ab9be3fef7ac6e1f8fd57f45232f9287ce58073c0436b135e1c77ea7 - ); - assert_eq!( - compute_root_of_group( - array![ - Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789 }, - Claim { id: 1, claimee: contract_address_const::<3456>(), amount: 789 }, - Claim { id: 2, claimee: contract_address_const::<4567>(), amount: 89 } - ] - .span() - ), - 0x06a2f92ce1d9514d0270addf05923a6aeb568ec3fb962a40ddc62c86d0bd3846 - ); -} - - -#[test] -fn test_compute_root_of_group_large() { - let mut arr: Array = array![]; - - let mut i: u64 = 64; - while i < 256 { - arr - .append( - Claim { id: i, claimee: contract_address_const::<2345>(), amount: (i + 1).into() } - ); - i += 1; - }; - - assert_eq!( - compute_root_of_group(arr.span()), - 0x0570d1767033fda8e16a754fccc383a47bc79a60d1b97c905b354adda64355d4 - ); -} - -#[test] -fn test_compute_root_of_group_large_odd() { - let mut arr: Array = array![]; - - let mut i: u64 = 64; - while i < 257 { - arr - .append( - Claim { id: i, claimee: contract_address_const::<2345>(), amount: (i + 1).into() } - ); - i += 1; - }; - - assert_eq!( - compute_root_of_group(arr.span()), - 0x360de0739531ee0f159a2d940ff6b83066a4269da0ce1e2ecad27feebf81d4 - ); -} - - -#[test] -fn test_claim_two_claims() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + assert_eq!(token.balanceOf(recipient), 25); + assert_eq!(token.balanceOf(airdrop.contract_address), 75); - let claim_a = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; - let claim_b = Claim { id: 1, claimee: contract_address_const::<3456>(), amount: 789, }; + // 1/5 time passsed - let leaf_a = hash_claim(claim_a); - let leaf_b = hash_claim(claim_b); + starknet::testing::set_block_timestamp(10020); - let root = hash_function(leaf_a, leaf_b); + assert_eq!(airdrop.claim(recipient, 100, array![].span()), true); - let airdrop = deploy_airdrop(token.contract_address, root); - token.transfer(airdrop.contract_address, 6789 + 789 + 1); - - airdrop.claim(claim_a, array![leaf_b].span()); - assert_eq!(token.balanceOf(airdrop.contract_address), (789 + 1)); - assert_eq!(token.balanceOf(claim_a.claimee), 6789); - - airdrop.claim(claim_b, array![leaf_a].span()); - assert_eq!(token.balanceOf(airdrop.contract_address), 1); - assert_eq!(token.balanceOf(claim_b.claimee), 789); -} - -#[test] -fn test_claim_two_claims_via_claim_128() { - let token = deploy_token('AIRDROP', 'AD', 1234567); - - let claim_a = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; - let claim_b = Claim { id: 1, claimee: contract_address_const::<3456>(), amount: 789, }; - - let leaf_a = hash_claim(claim_a); - let leaf_b = hash_claim(claim_b); - - let root = hash_function(leaf_a, leaf_b); - - let airdrop = deploy_airdrop(token.contract_address, root); - token.transfer(airdrop.contract_address, 6789 + 789); - - assert_eq!(airdrop.claim_128(array![claim_a, claim_b].span(), array![].span()), 2); - - let claim_a_log = pop_log::(airdrop.contract_address).unwrap(); - assert_eq!(claim_a_log.claim, claim_a); - let claim_b_log = pop_log::(airdrop.contract_address).unwrap(); - assert_eq!(claim_b_log.claim, claim_b); - - // pops the initial supply transfer from 0 log - pop_log::(token.contract_address).unwrap(); - // pops the transfer from deployer to airdrop - pop_log::(token.contract_address).unwrap(); - - let transfer_claim_a_log = pop_log::(token.contract_address).unwrap(); - assert_eq!(transfer_claim_a_log.from, airdrop.contract_address); - assert_eq!(transfer_claim_a_log.to, claim_a.claimee); - assert_eq!(transfer_claim_a_log.value, claim_a.amount.into()); - - let transfer_claim_b_log = pop_log::(token.contract_address).unwrap(); - assert_eq!(transfer_claim_b_log.from, airdrop.contract_address); - assert_eq!(transfer_claim_b_log.to, claim_b.claimee); - assert_eq!(transfer_claim_b_log.value, claim_b.amount.into()); - - assert_eq!(airdrop.claim_128(array![claim_a, claim_b].span(), array![].span()), 0); -} - -#[test] -#[should_panic(expected: ('INVALID_PROOF', 'ENTRYPOINT_FAILED'))] -fn test_claim_three_claims_one_invalid_via_claim_128() { - let token = deploy_token('AIRDROP', 'AD', 1234567); - - let claim_a = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; - let claim_b = Claim { id: 1, claimee: contract_address_const::<3456>(), amount: 789, }; - let claim_b_2 = Claim { id: 2, claimee: contract_address_const::<3456>(), amount: 789, }; - - let leaf_a = hash_claim(claim_a); - let leaf_b = hash_claim(claim_b); - - let root = hash_function(leaf_a, leaf_b); - - let airdrop = deploy_airdrop(token.contract_address, root); - token.transfer(airdrop.contract_address, 6789 + 789 + 789); - - assert_eq!(airdrop.claim_128(array![claim_a, claim_b, claim_b_2].span(), array![].span()), 3); -} - -fn test_claim_is_valid(root: felt252, claim: Claim, proof: Array) { - let pspan = proof.span(); - let token = deploy_token('AIRDROP', 'AD', claim.amount); - let airdrop = deploy_airdrop(token.contract_address, root); - token.transfer(airdrop.contract_address, claim.amount.into()); - - assert_eq!(airdrop.claim(claim, pspan), true); - assert_eq!(airdrop.claim(claim, pspan), false); - - let claim_log = pop_log::(airdrop.contract_address).unwrap(); - assert_eq!(claim_log.claim, claim); - - // pops the initial supply transfer from 0 log - pop_log::(token.contract_address).unwrap(); - // pops the transfer from deployer to airdrop - pop_log::(token.contract_address).unwrap(); - let transfer_log = pop_log::(token.contract_address).unwrap(); - assert_eq!(transfer_log.from, airdrop.contract_address); - assert_eq!(transfer_log.to, claim.claimee); - assert_eq!(transfer_log.value, claim.amount.into()); -} - -#[test] -fn test_claim_from_end_of_tree() { - test_claim_is_valid( - root: 2413984000256568988735068618807996871735886303454043475744972321149068137869, - claim: Claim { - id: 3592, - claimee: contract_address_const::< - 827929506653898309809051765272831150759947744606852950844797791651878826782 - >(), - amount: 1001271836113844608, - }, - proof: array![ - 999107061513787509684635393322981468422914789854841379477747793466442449935, - 515922882550246450639433632126072568380885235399474989388432279023063245887, - 2183670702902438880162847431850472734321860550216187087562069279528995144858, - 15651848759914294392773788266993460012436498803878911309497344547864396458, - 681329051542701608410442131965439826537794833969063315276363661924591621130, - 3136244998269470531984442468315391698901695607768566301585234761964804893655, - 2778542412084971505948237227833424078439670112778918680530473881654242267636, - 1664390236282514480745387082230901158164058685736963812907939026964512035529, - 2315326196699957769855383121961607281382192717308836542377578681714910420282, - 2382716371051479826678099165037038065721763275238547296230775213540032250366, - 775413931716626428851693665000522046203123080573891636225659041253540837203, - 1844857354889111805724320956769488995432351795269595216315100679068515517971 - ] - ); -} - -#[test] -fn test_claim_from_end_of_tree_large() { - test_claim_is_valid( - root: 405011783278363798212920545986279540950667137059008708904434915300742585819, - claim: Claim { - id: 16605, - claimee: contract_address_const::< - 284836135682475739559347904100664354678769084599508066858400818369306251115 - >(), - amount: 1000080194694973312, - }, - proof: array![ - 3584994958232786110573847189435462679736813679574169859276708512901684459908, - 3456560767569876651615908793256283805842107509530334958473406784224175394481, - 576973065814431626081993410573073322558132970018343395866696785754848554185, - 736107990262848904714315972898063881018050115073309434649053444309959183221, - 2021163002815443628933626434693228945243297172164470801936592396663555877826, - 2901589040842703364427753471773264798075947637844829636061501293319979431640, - 3293774020270833566793904790401762833702670186284119531755070600268368741925, - 160674685665120746028095836066282924500059590244854318435384160229157963763, - 2839570568016896630097863196252956147067067637781804601680059249176605149835, - 1870088898022793000041170914738822183912185184028239464557428700062425279227, - 271505888377476822812366281446524851149674339669641575685919848919662124896, - 3391878612706733042690751883383139274310601469785669990192514358124091696985, - 1858283206563877188634011031115620633400912073664087333553401439891983671978, - 653009678825348308131020658113913238736663469737876248844258093567627009338, - 1776702285563761589028945262957253286857459730675857906935919165166876058497 - ] - ); -} - -#[test] -fn test_claim_from_end_of_tree_middle_of_bitmap() { - test_claim_is_valid( - root: 405011783278363798212920545986279540950667137059008708904434915300742585819, - claim: Claim { - id: 16567, - claimee: contract_address_const::< - 1748616718994798723044863281884565737514860606804556124091102474369748521947 - >(), - amount: 1005026355664803840, - }, - proof: array![ - 577779429737926850673034182197562601348556455795160762160509490274702911309, - 3531956498125196032888119207616455741869865921010213747115240525082947964487, - 2515825962787606228786524382243188502433378049561987247415362987154981448571, - 3316670161889032026226037747433331224604549957491601814857297557140704540764, - 211583343697216472970992442436522301103449739328892936330405180665115266222, - 2016634616917323403993677865627397960725479662042496998096798462521905866406, - 567154639474675754849449940276760355068200176296841726121206582800434130638, - 160674685665120746028095836066282924500059590244854318435384160229157963763, - 2839570568016896630097863196252956147067067637781804601680059249176605149835, - 1870088898022793000041170914738822183912185184028239464557428700062425279227, - 271505888377476822812366281446524851149674339669641575685919848919662124896, - 3391878612706733042690751883383139274310601469785669990192514358124091696985, - 1858283206563877188634011031115620633400912073664087333553401439891983671978, - 653009678825348308131020658113913238736663469737876248844258093567627009338, - 1776702285563761589028945262957253286857459730675857906935919165166876058497 - ] - ); -} - -#[test] -fn test_double_claim_from_generated_tree() { - test_claim_is_valid( - root: 2413984000256568988735068618807996871735886303454043475744972321149068137869, - claim: Claim { - id: 0, - claimee: contract_address_const::< - 1257981684727298919953780547925609938727371268283996697135018561811391002099 - >(), - amount: 845608158412629999616, - }, - proof: array![ - 390013443931943946052075510188945600544108471539235465760564815348896073043, - 2591818886036301641799899841447556295494184204908229358406473782788431853617, - 3433559452610196359109559589502585411529094342760420711041457728474879804685, - 119111708719532621104568211251857481136318454621898627733025381039107349350, - 1550418626007763899979956501892881046988353701960212721885621375458028218469, - 218302537176435686946721821062002958322614343556723420712784506426080342216, - 1753580693918376168416443301945093568141375497403576624304615426611458701443, - 284161108154264923299661757093898525322488115499630822539338320558723810310, - 3378969471732886394431481313236934101872088301949153794471811360320074526103, - 2691963575009292057768595613759919396863463394980592564921927341908988940473, - 22944591007266013337629529054088070826740344136663051917181912077498206093, - 2846046884061389749777735515205600989814522753032574962636562486677935396074 - ] - ); -} - -#[test] -fn test_double_claim_after_other_claim() { - let claim_0 = Claim { - id: 0, - claimee: contract_address_const::< - 1257981684727298919953780547925609938727371268283996697135018561811391002099 - >(), - amount: 845608158412629999616, - }; - - let claim_1 = Claim { - id: 1, - claimee: contract_address_const::< - 2446484730111463702450186103350698828806903266085688038950964576824849476058 - >(), - amount: 758639984742607224832, - }; - - let token = deploy_token('AIRDROP', 'AD', claim_0.amount.into() + claim_1.amount.into()); - - let root = 2413984000256568988735068618807996871735886303454043475744972321149068137869; - let airdrop = deploy_airdrop(token.contract_address, root); - - token.transfer(airdrop.contract_address, claim_0.amount.into() + claim_1.amount.into()); - - assert_eq!( - airdrop - .claim( - claim_1, - array![ - 2879705852068751339326970574743249357626496859246711485336045655175496222574, - 2591818886036301641799899841447556295494184204908229358406473782788431853617, - 3433559452610196359109559589502585411529094342760420711041457728474879804685, - 119111708719532621104568211251857481136318454621898627733025381039107349350, - 1550418626007763899979956501892881046988353701960212721885621375458028218469, - 218302537176435686946721821062002958322614343556723420712784506426080342216, - 1753580693918376168416443301945093568141375497403576624304615426611458701443, - 284161108154264923299661757093898525322488115499630822539338320558723810310, - 3378969471732886394431481313236934101872088301949153794471811360320074526103, - 2691963575009292057768595613759919396863463394980592564921927341908988940473, - 22944591007266013337629529054088070826740344136663051917181912077498206093, - 2846046884061389749777735515205600989814522753032574962636562486677935396074 - ] - .span() - ), - true - ); - - assert_eq!( - airdrop - .claim( - claim_0, - array![ - 390013443931943946052075510188945600544108471539235465760564815348896073043, - 2591818886036301641799899841447556295494184204908229358406473782788431853617, - 3433559452610196359109559589502585411529094342760420711041457728474879804685, - 119111708719532621104568211251857481136318454621898627733025381039107349350, - 1550418626007763899979956501892881046988353701960212721885621375458028218469, - 218302537176435686946721821062002958322614343556723420712784506426080342216, - 1753580693918376168416443301945093568141375497403576624304615426611458701443, - 284161108154264923299661757093898525322488115499630822539338320558723810310, - 3378969471732886394431481313236934101872088301949153794471811360320074526103, - 2691963575009292057768595613759919396863463394980592564921927341908988940473, - 22944591007266013337629529054088070826740344136663051917181912077498206093, - 2846046884061389749777735515205600989814522753032574962636562486677935396074 - ] - .span() - ), - true - ); - - // double claim of claim id 1 - assert_eq!( - airdrop - .claim( - claim_1, - array![ - 2879705852068751339326970574743249357626496859246711485336045655175496222574, - 2591818886036301641799899841447556295494184204908229358406473782788431853617, - 3433559452610196359109559589502585411529094342760420711041457728474879804685, - 119111708719532621104568211251857481136318454621898627733025381039107349350, - 1550418626007763899979956501892881046988353701960212721885621375458028218469, - 218302537176435686946721821062002958322614343556723420712784506426080342216, - 1753580693918376168416443301945093568141375497403576624304615426611458701443, - 284161108154264923299661757093898525322488115499630822539338320558723810310, - 3378969471732886394431481313236934101872088301949153794471811360320074526103, - 2691963575009292057768595613759919396863463394980592564921927341908988940473, - 22944591007266013337629529054088070826740344136663051917181912077498206093, - 2846046884061389749777735515205600989814522753032574962636562486677935396074 - ] - .span() - ), - false - ); -} - -#[test] -#[should_panic( - expected: ('TRANSFER_INSUFFICIENT_BALANCE', 'ENTRYPOINT_FAILED', 'ENTRYPOINT_FAILED') -)] -fn test_claim_before_funded() { - let claim_0 = Claim { - id: 0, - claimee: contract_address_const::< - 1257981684727298919953780547925609938727371268283996697135018561811391002099 - >(), - amount: 845608158412629999616, - }; - - let token = deploy_token('AIRDROP', 'AD', 0); - - let root = 2413984000256568988735068618807996871735886303454043475744972321149068137869; - let airdrop = deploy_airdrop(token.contract_address, root); - - airdrop - .claim( - claim_0, - array![ - 390013443931943946052075510188945600544108471539235465760564815348896073043, - 2591818886036301641799899841447556295494184204908229358406473782788431853617, - 3433559452610196359109559589502585411529094342760420711041457728474879804685, - 119111708719532621104568211251857481136318454621898627733025381039107349350, - 1550418626007763899979956501892881046988353701960212721885621375458028218469, - 218302537176435686946721821062002958322614343556723420712784506426080342216, - 1753580693918376168416443301945093568141375497403576624304615426611458701443, - 284161108154264923299661757093898525322488115499630822539338320558723810310, - 3378969471732886394431481313236934101872088301949153794471811360320074526103, - 2691963575009292057768595613759919396863463394980592564921927341908988940473, - 22944591007266013337629529054088070826740344136663051917181912077498206093, - 2846046884061389749777735515205600989814522753032574962636562486677935396074 - ] - .span() - ); -} - -#[test] -fn test_multiple_claims_from_generated_tree() { - let claim_0 = Claim { - id: 0, - claimee: contract_address_const::< - 1257981684727298919953780547925609938727371268283996697135018561811391002099 - >(), - amount: 845608158412629999616, - }; - - let claim_1 = Claim { - id: 1, - claimee: contract_address_const::< - 2446484730111463702450186103350698828806903266085688038950964576824849476058 - >(), - amount: 758639984742607224832, - }; - - let token = deploy_token('AIRDROP', 'AD', claim_0.amount.into() + claim_1.amount.into()); - - let root = 2413984000256568988735068618807996871735886303454043475744972321149068137869; - let airdrop = deploy_airdrop(token.contract_address, root); - - token.transfer(airdrop.contract_address, claim_0.amount.into() + claim_1.amount.into()); - - airdrop - .claim( - claim_1, - array![ - 2879705852068751339326970574743249357626496859246711485336045655175496222574, - 2591818886036301641799899841447556295494184204908229358406473782788431853617, - 3433559452610196359109559589502585411529094342760420711041457728474879804685, - 119111708719532621104568211251857481136318454621898627733025381039107349350, - 1550418626007763899979956501892881046988353701960212721885621375458028218469, - 218302537176435686946721821062002958322614343556723420712784506426080342216, - 1753580693918376168416443301945093568141375497403576624304615426611458701443, - 284161108154264923299661757093898525322488115499630822539338320558723810310, - 3378969471732886394431481313236934101872088301949153794471811360320074526103, - 2691963575009292057768595613759919396863463394980592564921927341908988940473, - 22944591007266013337629529054088070826740344136663051917181912077498206093, - 2846046884061389749777735515205600989814522753032574962636562486677935396074 - ] - .span() - ); - - let log = pop_log::(airdrop.contract_address).unwrap(); - assert_eq!(log.claim, claim_1); - - airdrop - .claim( - claim_0, - array![ - 390013443931943946052075510188945600544108471539235465760564815348896073043, - 2591818886036301641799899841447556295494184204908229358406473782788431853617, - 3433559452610196359109559589502585411529094342760420711041457728474879804685, - 119111708719532621104568211251857481136318454621898627733025381039107349350, - 1550418626007763899979956501892881046988353701960212721885621375458028218469, - 218302537176435686946721821062002958322614343556723420712784506426080342216, - 1753580693918376168416443301945093568141375497403576624304615426611458701443, - 284161108154264923299661757093898525322488115499630822539338320558723810310, - 3378969471732886394431481313236934101872088301949153794471811360320074526103, - 2691963575009292057768595613759919396863463394980592564921927341908988940473, - 22944591007266013337629529054088070826740344136663051917181912077498206093, - 2846046884061389749777735515205600989814522753032574962636562486677935396074 - ] - .span() - ); let log = pop_log::(airdrop.contract_address).unwrap(); - assert_eq!(log.claim, claim_0); -} - - -#[test] -#[should_panic(expected: ('FIRST_CLAIM_MUST_BE_MULT_128', 'ENTRYPOINT_FAILED'))] -fn test_claim_128_fails_if_not_id_aligned() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + assert_eq!(log.recipient, recipient); + assert_eq!(log.amount, 15); + assert_eq!(log.total_amount, 100); - let claim_a = Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }; - let claim_b = Claim { id: 1, claimee: contract_address_const::<3456>(), amount: 789, }; + assert_eq!(token.balanceOf(recipient), 40); + assert_eq!(token.balanceOf(airdrop.contract_address), 60); - let leaf_a = hash_claim(claim_a); - let leaf_b = hash_claim(claim_b); + // Again, claiming again on the same timestamp yields no more tokens - let root = hash_function(leaf_a, leaf_b); + assert_eq!(airdrop.claim(recipient, 100, array![].span()), false); - let airdrop = deploy_airdrop(token.contract_address, root); - - airdrop.claim_128(array![claim_b, claim_a].span(), array![].span()); -} - - -#[test] -#[should_panic(expected: ('CLAIMS_EMPTY', 'ENTRYPOINT_FAILED'))] -fn test_claim_128_empty() { - let token = deploy_token('AIRDROP', 'AD', 1234567); - - let airdrop = deploy_airdrop(token.contract_address, 0); - - airdrop.claim_128(array![].span(), array![].span()); -} - -#[test] -#[should_panic(expected: ('TOO_MANY_CLAIMS', 'ENTRYPOINT_FAILED'))] -fn test_claim_128_too_many_claims() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + let log = pop_log::(airdrop.contract_address).unwrap(); + assert_eq!(log.recipient, recipient); + assert_eq!(log.amount, 0); + assert_eq!(log.total_amount, 100); - let airdrop = deploy_airdrop(token.contract_address, 0); + assert_eq!(token.balanceOf(recipient), 40); + assert_eq!(token.balanceOf(airdrop.contract_address), 60); - let mut claims: Array = array![]; - let mut i: u64 = 0; - while i < 129 { - claims.append(Claim { id: 0, claimee: contract_address_const::<2345>(), amount: 6789, }); - i += 1; - }; + // Another 1/5 time passsed - airdrop.claim_128(claims.span(), array![].span()); -} + starknet::testing::set_block_timestamp(10040); -#[test] -fn test_claim_128_large_tree() { - let mut i: u64 = 0; + assert_eq!(airdrop.claim(recipient, 100, array![].span()), true); - let mut claims: Array = array![]; + let log = pop_log::(airdrop.contract_address).unwrap(); + assert_eq!(log.recipient, recipient); + assert_eq!(log.amount, 15); + assert_eq!(log.total_amount, 100); - while (i < 320) { - claims.append(Claim { id: i, amount: 3, claimee: contract_address_const::<0xcdee>() }); - i += 1; - }; + assert_eq!(token.balanceOf(recipient), 55); + assert_eq!(token.balanceOf(airdrop.contract_address), 45); - let s1 = compute_root_of_group(claims.span().slice(0, 128)); - let s2 = compute_root_of_group(claims.span().slice(128, 128)); - let s3 = compute_root_of_group(claims.span().slice(256, 64)); + // Passed the entire vesting duration - let rl = hash_function(s1, s2); - let rr = hash_function(s3, s3); - let root = hash_function(rl, rr); + starknet::testing::set_block_timestamp(20000); - let token = deploy_token('AIRDROP', 'AD', 960); - let airdrop = deploy_airdrop(token.contract_address, root); - token.transfer(airdrop.contract_address, 960); + assert_eq!(airdrop.claim(recipient, 100, array![].span()), true); - assert_eq!(airdrop.claim_128(claims.span().slice(0, 128), array![s2, rr].span()), 128); - assert_eq!(airdrop.claim_128(claims.span().slice(128, 128), array![s1, rr].span()), 128); - assert_eq!(airdrop.claim_128(claims.span().slice(256, 64), array![s3, rl].span()), 64); -} + let log = pop_log::(airdrop.contract_address).unwrap(); + assert_eq!(log.recipient, recipient); + assert_eq!(log.amount, 45); + assert_eq!(log.total_amount, 100); -#[test] -fn test_claim_128_double_claim() { - let mut i: u64 = 0; - - let mut claims: Array = array![]; - - while (i < 320) { - claims.append(Claim { id: i, amount: 3, claimee: contract_address_const::<0xcdee>() }); - i += 1; - }; - - let s1 = compute_root_of_group(claims.span().slice(0, 128)); - let s2 = compute_root_of_group(claims.span().slice(128, 128)); - let s3 = compute_root_of_group(claims.span().slice(256, 64)); - - let rl = hash_function(s1, s2); - let rr = hash_function(s3, s3); - let root = hash_function(rl, rr); - - let token = deploy_token('AIRDROP', 'AD', 960); - let airdrop = deploy_airdrop(token.contract_address, root); - token.transfer(airdrop.contract_address, 960); - - assert_eq!(airdrop.claim_128(claims.span().slice(0, 128), array![s2, rr].span()), 128); - let mut i: u64 = 0; - while let Option::Some(claimed) = - pop_log::< - Airdrop::Claimed - >(airdrop.contract_address) { - assert_eq!( - claimed.claim, - Claim { id: i, amount: 3, claimee: contract_address_const::<0xcdee>() } - ); - i += 1; - }; - - assert_eq!(airdrop.claim_128(claims.span().slice(0, 128), array![s2, rr].span()), 0); - assert_eq!(pop_log::(airdrop.contract_address).is_none(), true); + assert_eq!(token.balanceOf(recipient), 100); + assert_eq!(token.balanceOf(airdrop.contract_address), 0); } diff --git a/airdrop/tests/deploy.cairo b/airdrop/tests/deploy.cairo index 73d1b6f..4df87b3 100644 --- a/airdrop/tests/deploy.cairo +++ b/airdrop/tests/deploy.cairo @@ -6,14 +6,19 @@ use traits::{Into, TryInto}; use starknet::ContractAddress; use starknet::syscalls::deploy_syscall; -use governance::{airdrop::{Airdrop, IAirdropDispatcher}, interfaces::erc20::{IERC20Dispatcher}}; +use airdrop::{airdrop::{Airdrop, IAirdropDispatcher}, interfaces::erc20::{IERC20Dispatcher}}; use tests::mock; -fn deploy_airdrop(token: ContractAddress, root: felt252) -> IAirdropDispatcher { +fn deploy_airdrop( + token: ContractAddress, root: felt252, start_time: u64, vesting_duration: u64 +) -> IAirdropDispatcher { let (contract_address, _) = deploy_syscall( - Airdrop::TEST_CLASS_HASH.try_into().unwrap(), 0, array![token.into(), root].span(), false + Airdrop::TEST_CLASS_HASH.try_into().unwrap(), + 0, + array![token.into(), root, start_time.into(), vesting_duration.into()].span(), + false ) .unwrap(); diff --git a/airdrop/tests/mock/erc20.cairo b/airdrop/tests/mock/erc20.cairo index 0af8eeb..6de2159 100644 --- a/airdrop/tests/mock/erc20.cairo +++ b/airdrop/tests/mock/erc20.cairo @@ -3,7 +3,7 @@ mod ERC20 { use starknet::{ContractAddress, contract_address_const, get_caller_address}; - use governance::interfaces::erc20::{IERC20}; + use airdrop::interfaces::erc20::{IERC20}; #[storage] struct Storage {