diff --git a/README.md b/README.md index 844c61f..ae08359 100644 --- a/README.md +++ b/README.md @@ -23,11 +23,11 @@ The structure is as follows: - Timelock ownership may be transferred to a new governance contract in future, e.g. to migrate to a volition-based voting contract - None of the proposal metadata is stored in governor, simply the number of votes - Proposals can be canceled at any time if the voting weight of the proposer falls below the threshold -- `Token` is an ERC20 token meant for voting in contracts like `Governor` +- `GovernanceToken` is an ERC20 token meant for voting in contracts like `Governor` - Users must delegate their tokens to vote, and may delegate to themselves - Allows other contracts to get the average voting weight for *any* historical period - Average votes are used to compute voting weight in the `Governor`, over a configurable period of time -- `Airdrop` can be used to distribute Token +- `Airdrop` can be used to distribute GovernanceToken - Compute a merkle root by computing a list of amounts and recipients, hashing them, and arranging them into a merkle binary tree - Deploy the airdrop with the root and the token address - Transfer the total amount of tokens to the `Airdrop` contract diff --git a/src/airdrop.cairo b/src/airdrop.cairo index 30bb431..4c8abee 100644 --- a/src/airdrop.cairo +++ b/src/airdrop.cairo @@ -7,8 +7,9 @@ struct Claim { amount: u128, } +// The only method required by Airdrop is transfer, so we use a simplified interface #[starknet::interface] -trait IERC20 { +trait ITransferrableERC20 { fn transfer(ref self: TStorage, recipient: ContractAddress, amount: u256); } @@ -19,7 +20,10 @@ trait IAirdrop { #[starknet::contract] mod Airdrop { - use super::{IAirdrop, ContractAddress, Claim, IERC20Dispatcher, IERC20DispatcherTrait}; + use super::{ + IAirdrop, ContractAddress, Claim, ITransferrableERC20Dispatcher, + ITransferrableERC20DispatcherTrait + }; use array::{ArrayTrait, SpanTrait}; use hash::{pedersen}; use traits::{Into, TryInto}; @@ -61,7 +65,7 @@ mod Airdrop { #[storage] struct Storage { root: felt252, - token: IERC20Dispatcher, + token: ITransferrableERC20Dispatcher, claimed: LegacyMap, } @@ -77,9 +81,9 @@ mod Airdrop { } #[constructor] - fn constructor(ref self: ContractState, token: IERC20Dispatcher, root: felt252) { + fn constructor(ref self: ContractState, token: ContractAddress, root: felt252) { self.root.write(root); - self.token.write(token); + self.token.write(ITransferrableERC20Dispatcher { contract_address: token }); } #[external(v0)] diff --git a/src/token.cairo b/src/governance_token.cairo similarity index 92% rename from src/token.cairo rename to src/governance_token.cairo index 379eb72..6b3fa68 100644 --- a/src/token.cairo +++ b/src/governance_token.cairo @@ -2,24 +2,27 @@ use core::traits::TryInto; use starknet::ContractAddress; #[starknet::interface] -trait IToken { - // ERC20 methods +trait IERC20 { fn name(self: @TStorage) -> felt252; fn symbol(self: @TStorage) -> felt252; fn decimals(self: @TStorage) -> u8; fn total_supply(self: @TStorage) -> u256; + fn totalSupply(self: @TStorage) -> u256; fn balance_of(self: @TStorage, account: ContractAddress) -> u256; + fn balanceOf(self: @TStorage, account: ContractAddress) -> u256; fn allowance(self: @TStorage, owner: ContractAddress, spender: ContractAddress) -> u256; fn transfer(ref self: TStorage, recipient: ContractAddress, amount: u256) -> bool; fn transfer_from( ref self: TStorage, sender: ContractAddress, recipient: ContractAddress, amount: u256 ) -> bool; - fn approve(ref self: TStorage, spender: ContractAddress, amount: u256) -> bool; - fn increase_allowance(ref self: TStorage, spender: ContractAddress, added_value: u256) -> bool; - fn decrease_allowance( - ref self: TStorage, spender: ContractAddress, subtracted_value: u256 + fn transferFrom( + ref self: TStorage, sender: ContractAddress, recipient: ContractAddress, amount: u256 ) -> bool; + fn approve(ref self: TStorage, spender: ContractAddress, amount: u256) -> bool; +} +#[starknet::interface] +trait IGovernanceToken { // Delegate tokens from the caller to the given delegate address fn delegate(ref self: TStorage, to: ContractAddress); @@ -43,8 +46,8 @@ trait IToken { } #[starknet::contract] -mod Token { - use super::{IToken, ContractAddress}; +mod GovernanceToken { + use super::{IERC20, IGovernanceToken, ContractAddress}; use traits::{Into, TryInto}; use option::{OptionTrait}; use starknet::{get_caller_address, get_block_timestamp}; @@ -211,7 +214,7 @@ mod Token { } #[external(v0)] - impl TokenImpl of IToken { + impl ERC20Impl of IERC20 { fn name(self: @ContractState) -> felt252 { self.name.read() } @@ -224,9 +227,15 @@ mod Token { fn total_supply(self: @ContractState) -> u256 { self.total_supply.read().into() } + fn totalSupply(self: @ContractState) -> u256 { + self.total_supply() + } fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { self.balances.read(account).into() } + fn balanceOf(self: @ContractState, account: ContractAddress) -> u256 { + self.balance_of(account) + } fn allowance( self: @ContractState, owner: ContractAddress, spender: ContractAddress ) -> u256 { @@ -269,6 +278,14 @@ mod Token { self.emit(Transfer { from: sender, to: recipient, value: amount }); true } + fn transferFrom( + ref self: ContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: u256 + ) -> bool { + self.transfer_from(sender, recipient, amount) + } fn approve(ref self: ContractState, spender: ContractAddress, amount: u256) -> bool { let owner = get_caller_address(); self @@ -277,18 +294,10 @@ mod Token { self.emit(Approval { owner, spender, value: amount }); true } + } - fn increase_allowance( - ref self: ContractState, spender: ContractAddress, added_value: u256 - ) -> bool { - self.approve(spender, self.allowance(get_caller_address(), spender) + added_value) - } - fn decrease_allowance( - ref self: ContractState, spender: ContractAddress, subtracted_value: u256 - ) -> bool { - self.approve(spender, self.allowance(get_caller_address(), spender) - subtracted_value) - } - + #[external(v0)] + impl TokenImpl of IGovernanceToken { fn delegate(ref self: ContractState, to: ContractAddress) { let caller = get_caller_address(); let old = self.delegates.read(caller); diff --git a/src/governor.cairo b/src/governor.cairo index e48e352..06bb87e 100644 --- a/src/governor.cairo +++ b/src/governor.cairo @@ -1,26 +1,51 @@ -use governance::token::ITokenDispatcherTrait; -use starknet::{ContractAddress}; +use governance::governance_token::IGovernanceTokenDispatcherTrait; +use starknet::{ContractAddress, StorePacking}; use array::{Array}; -use governance::token::{ITokenDispatcher}; +use governance::governance_token::{IGovernanceTokenDispatcher}; use starknet::account::{Call}; +use option::{Option, OptionTrait}; +use integer::{u128_safe_divmod, u128_as_non_zero}; +use traits::{Into, TryInto}; + +#[derive(Copy, Drop, Serde, PartialEq)] +struct ProposalTimestamps { + // the timestamp when the proposal was created + creation: u64, + // the timestamp when the proposal was executed + executed: u64, +} + +const TWO_POW_64: u128 = 0x10000000000000000_u128; + +impl ProposalTimestampsStorePacking of StorePacking { + fn pack(value: ProposalTimestamps) -> u128 { + value.creation.into() + (value.executed.into() * TWO_POW_64) + } + + fn unpack(value: u128) -> ProposalTimestamps { + let (executed, creation) = u128_safe_divmod(value, u128_as_non_zero(TWO_POW_64)); + ProposalTimestamps { + creation: creation.try_into().unwrap(), executed: executed.try_into().unwrap() + } + } +} #[derive(Copy, Drop, Serde, starknet::Store, PartialEq)] struct ProposalInfo { + // the address of the proposer proposer: ContractAddress, - // when the proposal was created - creation_timestamp: u64, - // how many yes votes has been collected + // the relevant timestamps + timestamps: ProposalTimestamps, + // how many yes votes have been collected yes: u128, - // how many no votes has been collected + // how many no votes have been collected no: u128, - // whether the proposal has been executed - executed: bool } #[derive(Copy, Drop, Serde, starknet::Store)] struct Config { // the token used for voting - voting_token: ITokenDispatcher, + voting_token: IGovernanceTokenDispatcher, // how long after a proposal is created does voting start voting_start_delay: u64, // the period during which votes are collected @@ -56,10 +81,13 @@ trait IGovernor { #[starknet::contract] mod Governor { - use super::{ContractAddress, Array, IGovernor, ITokenDispatcher, Config, ProposalInfo, Call}; + use super::{ + ContractAddress, Array, IGovernor, IGovernanceTokenDispatcher, Config, ProposalInfo, Call, + ProposalTimestamps + }; use starknet::{get_block_timestamp, get_caller_address, contract_address_const}; use governance::call_trait::{CallTrait}; - use governance::token::{ITokenDispatcherTrait}; + use governance::governance_token::{IGovernanceTokenDispatcherTrait}; use zeroable::{Zeroable}; #[storage] @@ -102,11 +130,9 @@ mod Governor { .write( id, ProposalInfo { - proposer, - creation_timestamp: timestamp_current, - yes: 0, - no: 0, - executed: false + proposer, timestamps: ProposalTimestamps { + creation: timestamp_current, executed: 0 + }, yes: 0, no: 0 } ); @@ -119,7 +145,7 @@ mod Governor { assert(proposal.proposer.is_non_zero(), 'DOES_NOT_EXIST'); let timestamp_current = get_block_timestamp(); - let voting_start_time = (proposal.creation_timestamp + config.voting_start_delay); + let voting_start_time = (proposal.timestamps.creation + config.voting_start_delay); let voter = get_caller_address(); let voted = self.voted.read((voter, id)); @@ -166,18 +192,16 @@ mod Governor { } assert( - timestamp_current < (proposal.creation_timestamp + timestamp_current < (proposal.timestamps.creation + config.voting_start_delay + config.voting_period), 'VOTING_ENDED' ); proposal = ProposalInfo { - proposer: contract_address_const::<0>(), - creation_timestamp: 0, - yes: 0, - no: 0, - executed: false + proposer: contract_address_const::<0>(), timestamps: ProposalTimestamps { + creation: 0, executed: 0 + }, yes: 0, no: 0 }; self.proposals.write(id, proposal); @@ -190,12 +214,15 @@ mod Governor { let mut proposal = self.proposals.read(id); assert(proposal.proposer.is_non_zero(), 'DOES_NOT_EXIST'); - assert(!proposal.executed, 'ALREADY_EXECUTED'); + assert(proposal.timestamps.executed.is_zero(), 'ALREADY_EXECUTED'); let timestamp_current = get_block_timestamp(); + // we cannot tell if a proposal is executed if it is executed at timestamp 0 + // this can only happen in testing, but it makes this method locally correct + assert(timestamp_current.is_non_zero(), 'TIMESTAMP_ZERO'); assert( - timestamp_current >= (proposal.creation_timestamp + timestamp_current >= (proposal.timestamps.creation + config.voting_start_delay + config.voting_period), 'VOTING_NOT_ENDED' @@ -204,7 +231,9 @@ mod Governor { assert((proposal.yes + proposal.no) >= config.quorum, 'QUORUM_NOT_MET'); assert(proposal.yes >= proposal.no, 'NO_MAJORITY'); - proposal.executed = true; + proposal.timestamps = ProposalTimestamps { + creation: proposal.timestamps.creation, executed: timestamp_current + }; self.proposals.write(id, proposal); diff --git a/src/lib.cairo b/src/lib.cairo index 10dbd43..a7321db 100644 --- a/src/lib.cairo +++ b/src/lib.cairo @@ -1,6 +1,6 @@ mod airdrop; mod governor; -mod token; +mod governance_token; mod timelock; mod call_trait; diff --git a/src/tests.cairo b/src/tests.cairo index 1a0de80..91004bf 100644 --- a/src/tests.cairo +++ b/src/tests.cairo @@ -3,7 +3,7 @@ mod airdrop_test; #[cfg(test)] mod governor_test; #[cfg(test)] -mod token_test; +mod governance_token_test; #[cfg(test)] mod timelock_test; #[cfg(test)] diff --git a/src/tests/airdrop_test.cairo b/src/tests/airdrop_test.cairo index 9c5e739..a54e52f 100644 --- a/src/tests/airdrop_test.cairo +++ b/src/tests/airdrop_test.cairo @@ -1,23 +1,24 @@ use governance::airdrop::Airdrop::ClaimToLeafTrait; -use governance::token::ITokenDispatcherTrait; +use governance::governance_token::IGovernanceTokenDispatcherTrait; use array::{ArrayTrait}; use debug::PrintTrait; use governance::airdrop::{ IAirdropDispatcher, IAirdropDispatcherTrait, Airdrop, Airdrop::compute_pedersen_root, Claim, - Airdrop::ClaimToLeaf, Airdrop::felt252_lt, IERC20Dispatcher, IERC20DispatcherTrait + Airdrop::ClaimToLeaf, Airdrop::felt252_lt }; +use governance::governance_token::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ get_contract_address, deploy_syscall, ClassHash, contract_address_const, ContractAddress }; -use governance::token::{Token, ITokenDispatcher}; -use governance::tests::token_test::{deploy as deploy_token}; +use governance::governance_token::{GovernanceToken, IGovernanceTokenDispatcher}; +use governance::tests::governance_token_test::{deploy as deploy_token}; use starknet::class_hash::Felt252TryIntoClassHash; use traits::{TryInto, Into}; use result::{Result, ResultTrait}; use option::{OptionTrait}; -fn deploy(token: IERC20Dispatcher, root: felt252) -> IAirdropDispatcher { +fn deploy(token: ContractAddress, root: felt252) -> IAirdropDispatcher { let mut constructor_args: Array = ArrayTrait::new(); Serde::serialize(@token, ref constructor_args); Serde::serialize(@root, ref constructor_args); @@ -92,13 +93,13 @@ fn test_compute_pedersen_root_recursive() { #[test] #[available_gas(3000000)] fn test_claim_single_recipient() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + let (_, token) = deploy_token('AIRDROP', 'AD', 1234567); let claim = Claim { claimee: contract_address_const::<2345>(), amount: 6789, }; let leaf = claim.to_leaf(); - let airdrop = deploy(IERC20Dispatcher { contract_address: token.contract_address }, leaf); + let airdrop = deploy(token.contract_address, leaf); token.transfer(airdrop.contract_address, 6789); let proof = ArrayTrait::new(); @@ -111,13 +112,13 @@ fn test_claim_single_recipient() { #[available_gas(4000000)] #[should_panic(expected: ('ALREADY_CLAIMED', 'ENTRYPOINT_FAILED'))] fn test_double_claim() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + let (_, token) = deploy_token('AIRDROP', 'AD', 1234567); let claim = Claim { claimee: contract_address_const::<2345>(), amount: 6789, }; let leaf = claim.to_leaf(); - let airdrop = deploy(IERC20Dispatcher { contract_address: token.contract_address }, leaf); + let airdrop = deploy(token.contract_address, leaf); token.transfer(airdrop.contract_address, 6789); let mut proof = ArrayTrait::new(); @@ -131,13 +132,13 @@ fn test_double_claim() { #[available_gas(3000000)] #[should_panic(expected: ('INVALID_PROOF', 'ENTRYPOINT_FAILED'))] fn test_invalid_proof_single_entry() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + let (_, token) = deploy_token('AIRDROP', 'AD', 1234567); let claim = Claim { claimee: contract_address_const::<2345>(), amount: 6789, }; let leaf = claim.to_leaf(); - let airdrop = deploy(IERC20Dispatcher { contract_address: token.contract_address }, leaf); + let airdrop = deploy(token.contract_address, leaf); token.transfer(airdrop.contract_address, 6789); let mut proof = ArrayTrait::new(); @@ -150,13 +151,13 @@ fn test_invalid_proof_single_entry() { #[available_gas(3000000)] #[should_panic(expected: ('INVALID_PROOF', 'ENTRYPOINT_FAILED'))] fn test_invalid_proof_fake_entry() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + let (_, token) = deploy_token('AIRDROP', 'AD', 1234567); let claim = Claim { claimee: contract_address_const::<2345>(), amount: 6789, }; let leaf = claim.to_leaf(); - let airdrop = deploy(IERC20Dispatcher { contract_address: token.contract_address }, leaf); + let airdrop = deploy(token.contract_address, leaf); token.transfer(airdrop.contract_address, 6789); let proof = ArrayTrait::new(); @@ -168,7 +169,7 @@ fn test_invalid_proof_fake_entry() { #[test] #[available_gas(30000000)] fn test_claim_two_claims() { - let token = deploy_token('AIRDROP', 'AD', 1234567); + let (_, token) = deploy_token('AIRDROP', 'AD', 1234567); let claim_a = Claim { claimee: contract_address_const::<2345>(), amount: 6789, }; let claim_b = Claim { claimee: contract_address_const::<3456>(), amount: 789, }; @@ -182,7 +183,7 @@ fn test_claim_two_claims() { pedersen(leaf_b, leaf_a) }; - let airdrop = deploy(IERC20Dispatcher { contract_address: token.contract_address }, root); + let airdrop = deploy(token.contract_address, root); token.transfer(airdrop.contract_address, 6789 + 789 + 1); let mut proof_a = ArrayTrait::new(); diff --git a/src/tests/call_trait_test.cairo b/src/tests/call_trait_test.cairo index ea81957..5c35356 100644 --- a/src/tests/call_trait_test.cairo +++ b/src/tests/call_trait_test.cairo @@ -2,7 +2,7 @@ use debug::PrintTrait; use governance::call_trait::{CallTrait}; use starknet::{contract_address_const, account::{Call}}; use array::{Array, ArrayTrait}; -use governance::tests::token_test::{deploy as deploy_token}; +use governance::tests::governance_token_test::{deploy as deploy_token}; use serde::{Serde}; #[test] @@ -71,7 +71,7 @@ fn test_execute_contract_not_deployed() { #[available_gas(300000000)] #[should_panic(expected: ('ENTRYPOINT_NOT_FOUND', ))] fn test_execute_invalid_entry_point() { - let token = deploy_token('TIMELOCK', 'TL', 1); + let (token, _) = deploy_token('TIMELOCK', 'TL', 1); let mut calldata: Array = ArrayTrait::new(); let call = Call { to: token.contract_address, selector: 0, calldata: calldata }; @@ -84,7 +84,7 @@ fn test_execute_invalid_entry_point() { #[available_gas(300000000)] #[should_panic(expected: ('Input too short for arguments', 'ENTRYPOINT_FAILED'))] fn test_execute_invalid_call_data_too_short() { - let token = deploy_token('TIMELOCK', 'TL', 1); + let (token, _) = deploy_token('TIMELOCK', 'TL', 1); let mut calldata: Array = ArrayTrait::new(); let call = Call { @@ -101,7 +101,7 @@ fn test_execute_invalid_call_data_too_short() { #[test] #[available_gas(300000000)] fn test_execute_valid_call_data() { - let token = deploy_token('TIMELOCK', 'TL', 1); + let (token, _) = deploy_token('TIMELOCK', 'TL', 1); let mut calldata: Array = ArrayTrait::new(); Serde::serialize(@contract_address_const::<1>(), ref calldata); diff --git a/src/tests/token_test.cairo b/src/tests/governance_token_test.cairo similarity index 68% rename from src/tests/token_test.cairo rename to src/tests/governance_token_test.cairo index 48bea17..acfa6c8 100644 --- a/src/tests/token_test.cairo +++ b/src/tests/governance_token_test.cairo @@ -1,6 +1,9 @@ use array::{ArrayTrait}; use debug::PrintTrait; -use governance::token::{ITokenDispatcher, ITokenDispatcherTrait, Token}; +use governance::governance_token::{ + IGovernanceTokenDispatcher, IGovernanceTokenDispatcherTrait, IERC20Dispatcher, + IERC20DispatcherTrait, GovernanceToken +}; use starknet::{ get_contract_address, deploy_syscall, ClassHash, contract_address_const, ContractAddress, }; @@ -11,77 +14,90 @@ use traits::{TryInto}; use result::{Result, ResultTrait}; use option::{OptionTrait}; -fn deploy(name: felt252, symbol: felt252, supply: u128) -> ITokenDispatcher { +fn deploy( + name: felt252, symbol: felt252, supply: u128 +) -> (IGovernanceTokenDispatcher, IERC20Dispatcher) { let mut constructor_args: Array = ArrayTrait::new(); Serde::serialize(@name, ref constructor_args); Serde::serialize(@symbol, ref constructor_args); Serde::serialize(@supply, ref constructor_args); let (address, _) = deploy_syscall( - Token::TEST_CLASS_HASH.try_into().unwrap(), 0, constructor_args.span(), true + GovernanceToken::TEST_CLASS_HASH.try_into().unwrap(), 0, constructor_args.span(), true ) .expect('DEPLOY_TK_FAILED'); - return ITokenDispatcher { contract_address: address }; + return ( + IGovernanceTokenDispatcher { + contract_address: address + }, IERC20Dispatcher { + contract_address: address + } + ); } #[test] #[available_gas(3000000)] fn test_deploy_constructor() { - let token = deploy('Governor Token', 'GT', 12345); - assert(token.name() == 'Governor Token', 'name'); - assert(token.symbol() == 'GT', 'symbol'); - assert(token.balance_of(get_contract_address()) == 12345, 'deployer balance'); - assert(token.balance_of(contract_address_const::<1234512345>()) == 0, 'random balance'); - assert(token.total_supply() == 12345, 'total supply'); + let (_, erc20) = deploy('Governor Token', 'GT', 12345); + assert(erc20.name() == 'Governor Token', 'name'); + assert(erc20.symbol() == 'GT', 'symbol'); + assert(erc20.balance_of(get_contract_address()) == 12345, 'deployer balance'); + assert(erc20.balanceOf(get_contract_address()) == 12345, 'deployer balance'); + assert(erc20.balance_of(contract_address_const::<1234512345>()) == 0, 'random balance'); + assert(erc20.balanceOf(contract_address_const::<1234512345>()) == 0, 'random balance'); + assert(erc20.total_supply() == 12345, 'total supply'); + assert(erc20.totalSupply() == 12345, 'total supply'); } #[test] #[available_gas(3000000)] fn test_transfer_entire_balance() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let recipient = contract_address_const::<12345>(); - token.transfer(recipient, 12345); - assert(token.balance_of(get_contract_address()) == 0, 'zero after'); - assert(token.balance_of(recipient) == 12345, '12345 after'); + erc20.transfer(recipient, 12345); + assert(erc20.balance_of(get_contract_address()) == 0, 'zero after'); + assert(erc20.balance_of(recipient) == 12345, '12345 after'); } #[test] #[available_gas(3000000)] fn test_transfer_lt_total_balance() { - let token = deploy('Governor Token', 'GT', 12345); + let (_, erc20) = deploy('Governor Token', 'GT', 12345); let recipient = contract_address_const::<12345>(); - token.transfer(recipient, 45); - assert(token.balance_of(get_contract_address()) == 12300, 'remaining'); - assert(token.balance_of(recipient) == 45, '45 transferred'); + erc20.transfer(recipient, 45); + assert(erc20.balance_of(get_contract_address()) == 12300, 'remaining'); + assert(erc20.balanceOf(get_contract_address()) == 12300, 'remaining'); + assert(erc20.balance_of(recipient) == 45, '45 transferred'); + assert(erc20.balanceOf(recipient) == 45, '45 transferred'); } #[test] #[available_gas(3000000)] #[should_panic(expected: ('TRANSFER_INSUFFICIENT_BALANCE', 'ENTRYPOINT_FAILED'))] fn test_transfer_gt_total_balance() { - let token = deploy('Governor Token', 'GT', 12345); + let (_, erc20) = deploy('Governor Token', 'GT', 12345); let recipient = contract_address_const::<12345>(); - token.transfer(recipient, 12346); + erc20.transfer(recipient, 12346); } #[test] #[available_gas(3000000)] #[should_panic(expected: ('TRANSFER_AMOUNT_OVERFLOW', 'ENTRYPOINT_FAILED'))] fn test_transfer_overflow() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let recipient = contract_address_const::<12345>(); - token.transfer(recipient, u256 { high: 1, low: 0 }); + erc20.transfer(recipient, u256 { high: 1, low: 0 }); } #[test] #[available_gas(3000000)] #[should_panic(expected: ('ORDER', 'ENTRYPOINT_FAILED'))] fn test_get_average_delegated_order_same() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); token.get_average_delegated(contract_address_const::<12345>(), 0, 0); } @@ -90,7 +106,7 @@ fn test_get_average_delegated_order_same() { #[available_gas(3000000)] #[should_panic(expected: ('ORDER', 'ENTRYPOINT_FAILED'))] fn test_get_average_delegated_order_backwards() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); token.get_average_delegated(contract_address_const::<12345>(), 1, 0); } @@ -99,7 +115,7 @@ fn test_get_average_delegated_order_backwards() { #[available_gas(3000000)] #[should_panic(expected: ('FUTURE', 'ENTRYPOINT_FAILED'))] fn test_get_average_delegated_future() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); token.get_average_delegated(contract_address_const::<12345>(), 0, 1); } @@ -108,7 +124,7 @@ fn test_get_average_delegated_future() { #[available_gas(3000000)] #[should_panic(expected: ('FUTURE', 'ENTRYPOINT_FAILED'))] fn test_get_average_delegated_future_non_zero() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); set_block_timestamp(5); @@ -118,59 +134,59 @@ fn test_get_average_delegated_future_non_zero() { #[test] #[available_gas(3000000)] fn test_approve_sets_allowance() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let spender = contract_address_const::<12345>(); - token.approve(spender, 5151); - assert(token.allowance(get_contract_address(), spender) == 5151, 'allowance'); + erc20.approve(spender, 5151); + assert(erc20.allowance(get_contract_address(), spender) == 5151, 'allowance'); } #[test] #[available_gas(3000000)] fn test_approve_allows_transfer_from() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let owner = get_contract_address(); let spender = contract_address_const::<12345>(); let recipient = contract_address_const::<12346>(); - token.approve(spender, 12345); + erc20.approve(spender, 12345); set_contract_address(spender); - assert(token.transfer_from(owner, recipient, 12345), 'transfer_from'); - assert(token.balance_of(owner) == 0, 'balance_of(from)'); - assert(token.balance_of(recipient) == 12345, 'balance_of(to)'); - assert(token.balance_of(spender) == 0, 'balance_of(spender)'); + assert(erc20.transfer_from(owner, recipient, 12345), 'transfer_from'); + assert(erc20.balance_of(owner) == 0, 'balance_of(from)'); + assert(erc20.balance_of(recipient) == 12345, 'balance_of(to)'); + assert(erc20.balance_of(spender) == 0, 'balance_of(spender)'); } #[test] #[available_gas(3000000)] #[should_panic(expected: ('TRANSFER_FROM_ALLOWANCE', 'ENTRYPOINT_FAILED'))] fn test_transfer_from_insufficient_allowance() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let owner = get_contract_address(); let spender = contract_address_const::<12345>(); let recipient = contract_address_const::<12346>(); - token.approve(spender, 12345); + erc20.approve(spender, 12345); set_contract_address(spender); - token.transfer_from(owner, recipient, 12346); + erc20.transfer_from(owner, recipient, 12346); } #[test] #[available_gas(3000000)] #[should_panic(expected: ('APPROVE_AMOUNT_OVERFLOW', 'ENTRYPOINT_FAILED'))] fn test_approve_overflow() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let spender = contract_address_const::<12345>(); let recipient = contract_address_const::<12346>(); - token.approve(spender, u256 { high: 1, low: 0 }); + erc20.approve(spender, u256 { high: 1, low: 0 }); } #[test] #[available_gas(30000000)] fn test_delegate_count_lags() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let delegatee = contract_address_const::<12345>(); set_block_timestamp(2); @@ -191,7 +207,7 @@ fn test_delegate_count_lags() { #[test] #[available_gas(30000000)] fn test_get_delegated_cumulative() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let delegatee = contract_address_const::<12345>(); set_block_timestamp(2); @@ -208,7 +224,7 @@ fn test_get_delegated_cumulative() { #[available_gas(30000000)] #[should_panic(expected: ('FUTURE', 'ENTRYPOINT_FAILED'))] fn test_get_delegated_cumulative_fails_future() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); token.get_delegated_cumulative(delegate: contract_address_const::<12345>(), timestamp: 1); } @@ -217,7 +233,7 @@ fn test_get_delegated_cumulative_fails_future() { #[available_gas(30000000)] #[should_panic(expected: ('FUTURE', 'ENTRYPOINT_FAILED'))] fn test_get_delegated_cumulative_fails_future_non_zero_ts() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); set_block_timestamp(5); @@ -228,7 +244,7 @@ fn test_get_delegated_cumulative_fails_future_non_zero_ts() { #[available_gas(30000000)] #[should_panic(expected: ('FUTURE', 'ENTRYPOINT_FAILED'))] fn test_get_delegated_at_fails_future() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); token.get_delegated_at(delegate: contract_address_const::<12345>(), timestamp: 1); } @@ -237,7 +253,7 @@ fn test_get_delegated_at_fails_future() { #[available_gas(30000000)] #[should_panic(expected: ('FUTURE', 'ENTRYPOINT_FAILED'))] fn test_get_delegated_at_fails_future_non_zero_ts() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); set_block_timestamp(5); @@ -247,7 +263,7 @@ fn test_get_delegated_at_fails_future_non_zero_ts() { #[test] #[available_gas(30000000)] fn test_get_average_delegated() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let delegatee = contract_address_const::<12345>(); set_block_timestamp(10); @@ -282,13 +298,13 @@ fn test_get_average_delegated() { #[test] #[available_gas(30000000)] fn test_transfer_delegates_moved() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let delegatee = contract_address_const::<12345>(); set_block_timestamp(2); token.delegate(delegatee); - token.transfer(contract_address_const::<3456>(), 500); + erc20.transfer(contract_address_const::<3456>(), 500); set_block_timestamp(5); assert(token.get_delegated(delegatee) == (12345 - 500), 'delegated'); @@ -302,7 +318,7 @@ fn test_transfer_delegates_moved() { #[test] #[available_gas(30000000)] fn test_delegate_undelegate() { - let token = deploy('Governor Token', 'GT', 12345); + let (token, erc20) = deploy('Governor Token', 'GT', 12345); let delegatee = contract_address_const::<12345>(); set_block_timestamp(2); diff --git a/src/tests/governor_test.cairo b/src/tests/governor_test.cairo index d6096a6..ae0ab39 100644 --- a/src/tests/governor_test.cairo +++ b/src/tests/governor_test.cairo @@ -2,9 +2,13 @@ use core::array::SpanTrait; use array::{ArrayTrait}; use debug::PrintTrait; use governance::governor::{ - IGovernorDispatcher, IGovernorDispatcherTrait, Governor, Config, ProposalInfo + IGovernorDispatcher, IGovernorDispatcherTrait, Governor, Config, ProposalInfo, + ProposalTimestamps +}; +use governance::governance_token::{ + IGovernanceTokenDispatcher, IGovernanceTokenDispatcherTrait, IERC20Dispatcher, + IERC20DispatcherTrait }; -use governance::token::{ITokenDispatcher, ITokenDispatcherTrait}; use governance::call_trait::{CallTrait}; use starknet::account::{Call}; use governance::tests::timelock_test::{single_call, transfer_call, deploy as deploy_timelock}; @@ -19,8 +23,9 @@ use traits::{TryInto}; use result::{Result, ResultTrait}; use option::{OptionTrait}; -use governance::tests::token_test::{deploy as deploy_token}; +use governance::tests::governance_token_test::{deploy as deploy_token}; use serde::Serde; +use zeroable::{Zeroable}; fn deploy(config: Config) -> IGovernorDispatcher { @@ -34,7 +39,7 @@ fn deploy(config: Config) -> IGovernorDispatcher { return IGovernorDispatcher { contract_address: address }; } -fn create_proposal(governance: IGovernorDispatcher, token: ITokenDispatcher) -> felt252 { +fn create_proposal(governance: IGovernorDispatcher, token: IGovernanceTokenDispatcher) -> felt252 { let recipient = utils::recipient(); let proposer = utils::proposer(); let start_time = utils::timestamp(); @@ -57,7 +62,7 @@ fn create_proposal(governance: IGovernorDispatcher, token: ITokenDispatcher) -> #[test] #[available_gas(3000000)] fn test_governance_deploy() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -84,7 +89,7 @@ fn test_governance_deploy() { #[test] #[available_gas(3000000)] fn test_propose() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -102,7 +107,9 @@ fn test_propose() { let start_time = utils::timestamp(); assert( proposal == ProposalInfo { - proposer, creation_timestamp: start_time, yes: 0, no: 0, executed: false + proposer, timestamps: ProposalTimestamps { + creation: start_time, executed: 0 + }, yes: 0, no: 0 }, 'proposal doesnt match' ); @@ -113,7 +120,7 @@ fn test_propose() { #[available_gas(5000000)] #[should_panic(expected: ('ALREADY_PROPOSED', 'ENTRYPOINT_FAILED'))] fn test_propose_already_exists_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -133,7 +140,7 @@ fn test_propose_already_exists_should_fail() { #[available_gas(3000000)] #[should_panic(expected: ('THRESHOLD', 'ENTRYPOINT_FAILED'))] fn test_propose_below_threshold_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -164,7 +171,7 @@ fn test_propose_below_threshold_should_fail() { #[test] #[available_gas(6000000)] fn test_vote_yes() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -201,7 +208,7 @@ fn test_vote_yes() { #[test] #[available_gas(6000000)] fn test_vote_no() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -240,7 +247,7 @@ fn test_vote_no() { #[should_panic(expected: ('VOTING_NOT_STARTED', 'ENTRYPOINT_FAILED'))] fn test_vote_before_voting_start_should_fail() { // Initial setup similar to propose test - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -267,7 +274,7 @@ fn test_vote_before_voting_start_should_fail() { #[available_gas(6000000)] #[should_panic(expected: ('ALREADY_VOTED', 'ENTRYPOINT_FAILED'))] fn test_vote_already_voted_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -300,7 +307,7 @@ fn test_vote_already_voted_should_fail() { #[available_gas(4000000)] #[should_panic(expected: ('VOTING_ENDED', 'ENTRYPOINT_FAILED'))] fn test_vote_after_voting_period_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -330,7 +337,7 @@ fn test_vote_after_voting_period_should_fail() { #[test] #[available_gas(4000000)] fn test_cancel_by_proposer() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -353,11 +360,9 @@ fn test_cancel_by_proposer() { let proposal = governance.get_proposal(id); assert( proposal == ProposalInfo { - proposer: contract_address_const::<0>(), - creation_timestamp: 0, - yes: 0, - no: 0, - executed: false + proposer: contract_address_const::<0>(), timestamps: ProposalTimestamps { + creation: 0, executed: 0 + }, yes: 0, no: 0, }, 'proposal not cancelled' ); @@ -366,7 +371,7 @@ fn test_cancel_by_proposer() { #[test] #[available_gas(6000000)] fn test_cancel_by_non_proposer() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -399,11 +404,9 @@ fn test_cancel_by_non_proposer() { let proposal = governance.get_proposal(id); assert( proposal == ProposalInfo { - proposer: contract_address_const::<0>(), - creation_timestamp: 0, - yes: 0, - no: 0, - executed: false + proposer: contract_address_const::<0>(), timestamps: ProposalTimestamps { + creation: 0, executed: 0 + }, yes: 0, no: 0, }, 'proposal not cancelled' ); @@ -413,7 +416,7 @@ fn test_cancel_by_non_proposer() { #[available_gas(4000000)] #[should_panic(expected: ('THRESHOLD_NOT_BREACHED', 'ENTRYPOINT_FAILED'))] fn test_cancel_by_non_proposer_threshold_not_breached_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -440,7 +443,7 @@ fn test_cancel_by_non_proposer_threshold_not_breached_should_fail() { #[available_gas(4000000)] #[should_panic(expected: ('VOTING_ENDED', 'ENTRYPOINT_FAILED'))] fn test_cancel_after_voting_end_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -470,7 +473,7 @@ fn test_cancel_after_voting_end_should_fail() { #[test] #[available_gas(8000000)] fn test_execute_valid_proposal() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, erc20) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -496,22 +499,22 @@ fn test_execute_valid_proposal() { // Send 100 tokens to the gov contract - this is because // the proposal calls transfer() which requires gov to have tokens. - token.transfer(governance.contract_address, 100); + erc20.transfer(governance.contract_address, 100); // set_caller_address(utils::zero_address()); let transfer_call = transfer_call(token: token, recipient: utils::recipient(), amount: 100); governance.execute(transfer_call); let proposal = governance.get_proposal(id); - assert(proposal.executed, 'execute failed'); - assert(token.balance_of(utils::recipient()) == 100, 'balance after execute'); + assert(proposal.timestamps.executed.is_non_zero(), 'execute failed'); + assert(erc20.balance_of(utils::recipient()) == 100, 'balance after execute'); } #[test] #[available_gas(4000000)] #[should_panic(expected: ('VOTING_NOT_ENDED', 'ENTRYPOINT_FAILED'))] fn test_execute_before_voting_ends_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -538,7 +541,7 @@ fn test_execute_before_voting_ends_should_fail() { #[available_gas(4000000)] #[should_panic(expected: ('QUORUM_NOT_MET', 'ENTRYPOINT_FAILED'))] fn test_execute_quorum_not_met_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, _) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -565,7 +568,7 @@ fn test_execute_quorum_not_met_should_fail() { #[available_gas(100000000)] #[should_panic(expected: ('NO_MAJORITY', 'ENTRYPOINT_FAILED'))] fn test_execute_no_majority_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, erc20) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -580,8 +583,8 @@ fn test_execute_no_majority_should_fail() { let voter = utils::voter(); let voter2 = utils::voter2(); let mut current_timestamp = utils::timestamp(); - token.transfer(voter, 49); - token.transfer(voter2, 51); + erc20.transfer(voter, 49); + erc20.transfer(voter2, 51); set_contract_address(voter); token.delegate(voter); set_contract_address(voter2); @@ -610,7 +613,7 @@ fn test_execute_no_majority_should_fail() { #[available_gas(100000000)] #[should_panic(expected: ('ALREADY_EXECUTED', 'ENTRYPOINT_FAILED'))] fn test_execute_already_executed_should_fail() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, erc20) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -636,7 +639,7 @@ fn test_execute_already_executed_should_fail() { // Send 100 tokens to the gov contract - this is because // the proposal calls transfer() which requires gov to have tokens. - token.transfer(governance.contract_address, 100); + erc20.transfer(governance.contract_address, 100); // set_caller_address(utils::zero_address()); let transfer_call = transfer_call(token: token, recipient: utils::recipient(), amount: 100); @@ -663,7 +666,7 @@ fn queue_with_timelock_call(timelock: ITimelockDispatcher, calls: Span) -> #[test] #[available_gas(300000000)] fn test_proposal_e2e() { - let token = deploy_token('Governor', 'GT', 1000); + let (token, erc20) = deploy_token('Governor', 'GT', 1000); let governance = deploy( Config { voting_token: token, @@ -686,7 +689,7 @@ fn test_proposal_e2e() { // so the average delegation is sufficient set_block_timestamp(start_time + 5); - token.transfer(timelock.contract_address, 200); + erc20.transfer(timelock.contract_address, 200); let recipient = utils::recipient(); let timelock_calls = single_call( call: transfer_call(token: token, recipient: recipient, amount: 100) @@ -701,9 +704,9 @@ fn test_proposal_e2e() { assert(result.len() == 1, '1 result'); let queued_call_id = result.pop_front(); set_block_timestamp(start_time + 5 + 3600 + 60 + 60); - assert(token.balance_of(timelock.contract_address) == 200, 'balance before t'); - assert(token.balance_of(recipient) == 0, 'balance before r'); + assert(erc20.balance_of(timelock.contract_address) == 200, 'balance before t'); + assert(erc20.balance_of(recipient) == 0, 'balance before r'); timelock.execute(timelock_calls); - assert(token.balance_of(timelock.contract_address) == 100, 'balance after t'); - assert(token.balance_of(recipient) == 100, 'balance before r'); + assert(erc20.balance_of(timelock.contract_address) == 100, 'balance after t'); + assert(erc20.balance_of(recipient) == 100, 'balance before r'); } diff --git a/src/tests/timelock_test.cairo b/src/tests/timelock_test.cairo index 5e42edc..1ec9a04 100644 --- a/src/tests/timelock_test.cairo +++ b/src/tests/timelock_test.cairo @@ -1,8 +1,11 @@ use array::{Array, ArrayTrait, SpanTrait}; use debug::PrintTrait; use governance::timelock::{ITimelockDispatcher, ITimelockDispatcherTrait, Timelock}; -use governance::tests::token_test::{deploy as deploy_token}; -use governance::token::{ITokenDispatcher, ITokenDispatcherTrait}; +use governance::tests::governance_token_test::{deploy as deploy_token}; +use governance::governance_token::{ + IGovernanceTokenDispatcher, IGovernanceTokenDispatcherTrait, IERC20Dispatcher, + IERC20DispatcherTrait +}; use starknet::{ get_contract_address, deploy_syscall, ClassHash, contract_address_const, ContractAddress, get_block_timestamp, testing::set_block_timestamp @@ -38,7 +41,9 @@ fn test_deploy() { assert(owner == contract_address_const::<2300>(), 'owner'); } -fn transfer_call(token: ITokenDispatcher, recipient: ContractAddress, amount: u256) -> Call { +fn transfer_call( + token: IGovernanceTokenDispatcher, recipient: ContractAddress, amount: u256 +) -> Call { let mut calldata: Array = ArrayTrait::new(); Serde::serialize(@recipient, ref calldata); Serde::serialize(@amount, ref calldata); @@ -63,8 +68,8 @@ fn test_queue_execute() { set_block_timestamp(1); let timelock = deploy(get_contract_address(), 86400, 3600); - let token = deploy_token('TIMELOCK', 'TL', 12345); - token.transfer(timelock.contract_address, 12345); + let (token, erc20) = deploy_token('TIMELOCK', 'TL', 12345); + erc20.transfer(timelock.contract_address, 12345); let recipient = contract_address_const::<12345>(); @@ -77,7 +82,7 @@ fn test_queue_execute() { set_block_timestamp(86401); timelock.execute(single_call(transfer_call(token, recipient, 500_u256))); - assert(token.balance_of(recipient) == 500_u256, 'balance'); + assert(erc20.balance_of(recipient) == 500_u256, 'balance'); } #[test] @@ -87,8 +92,8 @@ fn test_queue_cancel() { set_block_timestamp(1); let timelock = deploy(get_contract_address(), 86400, 3600); - let token = deploy_token('TIMELOCK', 'TL', 12345); - token.transfer(timelock.contract_address, 12345); + let (token, erc20) = deploy_token('TIMELOCK', 'TL', 12345); + erc20.transfer(timelock.contract_address, 12345); let recipient = contract_address_const::<12345>(); @@ -107,8 +112,8 @@ fn test_queue_execute_twice() { set_block_timestamp(1); let timelock = deploy(get_contract_address(), 86400, 3600); - let token = deploy_token('TIMELOCK', 'TL', 12345); - token.transfer(timelock.contract_address, 12345); + let (token, erc20) = deploy_token('TIMELOCK', 'TL', 12345); + erc20.transfer(timelock.contract_address, 12345); let recipient = contract_address_const::<12345>(); @@ -127,8 +132,8 @@ fn test_queue_executed_too_early() { set_block_timestamp(1); let timelock = deploy(get_contract_address(), 86400, 3600); - let token = deploy_token('TIMELOCK', 'TL', 12345); - token.transfer(timelock.contract_address, 12345); + let (token, erc20) = deploy_token('TIMELOCK', 'TL', 12345); + erc20.transfer(timelock.contract_address, 12345); let recipient = contract_address_const::<12345>(); @@ -146,8 +151,8 @@ fn test_queue_executed_too_late() { set_block_timestamp(1); let timelock = deploy(get_contract_address(), 86400, 3600); - let token = deploy_token('TIMELOCK', 'TL', 12345); - token.transfer(timelock.contract_address, 12345); + let (token, erc20) = deploy_token('TIMELOCK', 'TL', 12345); + erc20.transfer(timelock.contract_address, 12345); let recipient = contract_address_const::<12345>();