diff --git a/src/components/minter/interface.cairo b/src/components/minter/interface.cairo index 664deca..c8b6882 100644 --- a/src/components/minter/interface.cairo +++ b/src/components/minter/interface.cairo @@ -21,6 +21,6 @@ trait IMint { recipient: ContractAddress, amount: u256 ); - fn public_buy(ref self: TContractState, money_amount: u256, force: bool) -> Span; + fn public_buy(ref self: TContractState, money_amount: u256); fn cancel_mint(ref self: TContractState, should_cancel: bool); } diff --git a/src/components/minter/mint.cairo b/src/components/minter/mint.cairo index 703bc8d..21c5c97 100644 --- a/src/components/minter/mint.cairo +++ b/src/components/minter/mint.cairo @@ -46,35 +46,65 @@ mod MintComponent { SoldOut: SoldOut, Buy: Buy, MintCanceled: MintCanceled, + UnitPriceUpdated: UnitPriceUpdated, + Withdraw: Withdraw, + AmountRetrieved: AmountRetrieved, + MinMoneyAmountPerTxUpdated: MinMoneyAmountPerTxUpdated, } #[derive(Drop, starknet::Event)] struct PublicSaleOpen { - time: u64 + old_value: bool, + new_value: bool } #[derive(Drop, starknet::Event)] struct PublicSaleClose { - time: u64 + old_value: bool, + new_value: bool } #[derive(Drop, starknet::Event)] - struct SoldOut { - time: u64 - } + struct SoldOut {} #[derive(Drop, starknet::Event)] struct Buy { #[key] address: ContractAddress, + money_amount: u256, vintages: Span, - shares: u256, } #[derive(Drop, starknet::Event)] struct MintCanceled { - is_canceled: bool, - time: u64 + old_value: bool, + is_canceled: bool + } + + #[derive(Drop, starknet::Event)] + struct UnitPriceUpdated { + old_price: u256, + new_price: u256, + } + + #[derive(Drop, starknet::Event)] + struct Withdraw { + recipient: ContractAddress, + amount: u256, + } + + + #[derive(Drop, starknet::Event)] + struct AmountRetrieved { + token_address: ContractAddress, + recipient: ContractAddress, + amount: u256, + } + + #[derive(Drop, starknet::Event)] + struct MinMoneyAmountPerTxUpdated { + old_amount: u256, + new_amount: u256, } mod Errors { @@ -118,21 +148,16 @@ mod MintComponent { fn cancel_mint(ref self: ComponentState, should_cancel: bool) { let project_address = self.Mint_carbonable_project_address.read(); let project = IProjectDispatcher { contract_address: project_address }; - // [Check] Caller is not zero + let caller_address = get_caller_address(); assert(!caller_address.is_zero(), 'Invalid caller'); - // [Check] Caller is owner let isOwner = project.only_owner(caller_address); assert(isOwner, 'Caller is not the owner'); - // [Effect] Cancel the mint + let old_value: bool = self.Mint_cancel.read(); self.Mint_cancel.write(should_cancel); - // Get the current timestamp - let current_time = get_block_timestamp(); - - // [Event] Emit cancel event - self.emit(MintCanceled { is_canceled: should_cancel, time: current_time }); + self.emit(MintCanceled { old_value: old_value, is_canceled: should_cancel }); } fn is_canceled(self: @ComponentState) -> bool { @@ -142,61 +167,52 @@ mod MintComponent { fn set_public_sale_open(ref self: ComponentState, public_sale_open: bool) { let project_address = self.Mint_carbonable_project_address.read(); let project = IProjectDispatcher { contract_address: project_address }; - // [Check] Caller is not zero + let caller_address = get_caller_address(); assert(!caller_address.is_zero(), 'Invalid caller'); - // [Check] Caller is owner let isOwner = project.only_owner(caller_address); assert(isOwner, 'Caller is not the owner'); - // [Effect] Update storage + let old_value = self.Mint_public_sale_open.read(); self.Mint_public_sale_open.write(public_sale_open); - // [Event] Emit event - let current_time = get_block_timestamp(); - if public_sale_open { - self.emit(PublicSaleOpen { time: current_time }); - } else { - self.emit(PublicSaleClose { time: current_time }); - }; + self.emit(PublicSaleOpen { old_value: old_value, new_value: public_sale_open }); } fn set_unit_price(ref self: ComponentState, unit_price: u256) { let project_address = self.Mint_carbonable_project_address.read(); let project = IProjectDispatcher { contract_address: project_address }; - // [Check] Caller is not zero let caller_address = get_caller_address(); assert(!caller_address.is_zero(), 'Invalid caller'); - - // [Check] Caller is owner let isOwner = project.only_owner(caller_address); assert(isOwner, 'Caller is not the owner'); - - // [Check] Value not null assert(unit_price > 0, 'Invalid unit price'); - // [Effect] Store value + + let old_price = self.Mint_unit_price.read(); self.Mint_unit_price.write(unit_price); + + self.emit(UnitPriceUpdated { old_price: old_price, new_price: unit_price }); } fn withdraw(ref self: ComponentState) { let project_address = self.Mint_carbonable_project_address.read(); let project = IProjectDispatcher { contract_address: project_address }; - // [Check] Caller is not zero + let caller_address = get_caller_address(); assert(!caller_address.is_zero(), 'Invalid caller'); - // [Check] Caller is owner let isOwner = project.only_owner(caller_address); assert(isOwner, 'Caller is not the owner'); - // [Compute] Balance to withdraw + let token_address = self.Mint_payment_token_address.read(); let erc20 = IERC20Dispatcher { contract_address: token_address }; let contract_address = get_contract_address(); let balance = erc20.balance_of(contract_address); - // [Interaction] Transfer tokens let success = erc20.transfer(caller_address, balance); assert(success, 'Transfer failed'); + + self.emit(Withdraw { recipient: caller_address, amount: balance }); } fn retrieve_amount( @@ -207,25 +223,29 @@ mod MintComponent { ) { let project_address = self.Mint_carbonable_project_address.read(); let project = IProjectDispatcher { contract_address: project_address }; + let caller_address = get_caller_address(); assert(!caller_address.is_zero(), 'Invalid caller'); - // [Check] Caller is owner let isOwner = project.only_owner(caller_address); assert(isOwner, 'Caller is not the owner'); let erc20 = IERC20Dispatcher { contract_address: token_address }; let success = erc20.transfer(recipient, amount); assert(success, 'Transfer failed'); + + self + .emit( + AmountRetrieved { + token_address: token_address, recipient: recipient, amount: amount + } + ); } - fn public_buy( - ref self: ComponentState, money_amount: u256, force: bool - ) -> Span { - // [Check] Public sale is open + fn public_buy(ref self: ComponentState, money_amount: u256) { let public_sale_open = self.Mint_public_sale_open.read(); assert(public_sale_open, 'Sale is closed'); - // [Interaction] Buy - self._buy(money_amount, force) + + self._buy(money_amount); } fn set_min_money_amount_per_tx( @@ -234,22 +254,25 @@ mod MintComponent { let project_address = self.Mint_carbonable_project_address.read(); let project = IProjectDispatcher { contract_address: project_address }; - // [Check] Caller is not zero let caller_address = get_caller_address(); assert(!caller_address.is_zero(), 'Invalid caller'); - - // [Check] Caller is owner let isOwner = project.only_owner(caller_address); assert(isOwner, 'Caller is not the owner'); - // [Check] Value in range - let max_money_amount_per_tx = self.Mint_max_money_amount.read(); + let max_money_amount_per_tx = self.get_max_money_amount(); assert( max_money_amount_per_tx >= min_money_amount_per_tx, 'Invalid min money amount per tx' ); - // [Effect] Store value + + let old_amount = self.Mint_min_money_amount_per_tx.read(); self.Mint_min_money_amount_per_tx.write(min_money_amount_per_tx); + self + .emit( + MinMoneyAmountPerTxUpdated { + old_amount: old_amount, new_amount: min_money_amount_per_tx, + } + ); } fn get_max_money_amount(self: @ComponentState) -> u256 { @@ -269,50 +292,36 @@ mod MintComponent { max_money_amount: u256, unit_price: u256, ) { - // [Check] Input consistency assert(unit_price > 0, 'Invalid unit price'); - // [Effect] Update storage self.Mint_carbonable_project_address.write(carbonable_project_address); - - // [Effect] Update storage self.Mint_payment_token_address.write(payment_token_address); self.Mint_unit_price.write(unit_price); self.Mint_remaining_money_amount.write(max_money_amount); self.Mint_max_money_amount.write(max_money_amount); - - // [Effect] Use dedicated function to emit corresponding events self.Mint_public_sale_open.write(public_sale_open); } - fn _buy( - ref self: ComponentState, money_amount: u256, force: bool - ) -> Span { - // [Check] Value not null + fn _buy(ref self: ComponentState, money_amount: u256) { assert(money_amount > 0, 'Invalid amount of money'); - // [Check] Caller is not zero let caller_address = get_caller_address(); assert(!caller_address.is_zero(), 'Invalid caller'); - // [Check] Allowed value let min_money_amount_per_tx = self.Mint_min_money_amount_per_tx.read(); assert(money_amount >= min_money_amount_per_tx, 'Value too low'); - // [Check] Allowed enough remaining_money let remaining_money_amount = self.Mint_remaining_money_amount.read(); - assert(money_amount <= remaining_money_amount, 'Not enough remaining money'); - // [Interaction] Compute share of the amount of project - let max_money_amount = self.Mint_max_money_amount.read(); + + let max_money_amount = self.get_max_money_amount(); let share = money_amount * CC_DECIMALS_MULTIPLIER / max_money_amount; - // [Interaction] Compute the amount of cc for each vintage let project_address = self.Mint_carbonable_project_address.read(); let vintages = IVintageDispatcher { contract_address: project_address }; let num_vintages: usize = vintages.get_num_vintages(); - // Initially, share is the same for all the vintages + // User mints the same amount of tokens for all the vintages let mut cc_shares: Array = Default::default(); let mut tokens: Array = Default::default(); let mut index = 0; @@ -327,40 +336,37 @@ mod MintComponent { let cc_shares = cc_shares.span(); let token_ids = tokens.span(); - // [Interaction] Pay let token_address = self.Mint_payment_token_address.read(); let erc20 = IERC20Dispatcher { contract_address: token_address }; let minter_address = get_contract_address(); let success = erc20.transfer_from(caller_address, minter_address, money_amount); - // [Check] Transfer successful assert(success, 'Transfer failed'); - // [Interaction] Update remaining money amount self.Mint_remaining_money_amount.write(remaining_money_amount - money_amount); - // [Interaction] Mint let project = IProjectDispatcher { contract_address: project_address }; project.batch_mint(caller_address, token_ids, cc_shares); - // [Event] Emit event - let current_time = get_block_timestamp(); self .emit( - Event::Buy(Buy { address: caller_address, vintages: token_ids, shares: share }) + Event::Buy( + Buy { + address: caller_address, money_amount: money_amount, vintages: token_ids + } + ) ); - // [Effect] Close the sale if sold out if self.is_sold_out() { - // [Effect] Close public sale self.Mint_public_sale_open.write(false); - - // [Event] Emit sold out event - self.emit(Event::SoldOut(SoldOut { time: current_time })); + self + .emit( + Event::PublicSaleClose( + PublicSaleClose { old_value: true, new_value: false } + ) + ); + self.emit(Event::SoldOut(SoldOut {})); }; - - // [Return] cc shares - cc_shares } } } diff --git a/src/components/offsetter/interface.cairo b/src/components/offsetter/interface.cairo index e9e87f8..c468942 100644 --- a/src/components/offsetter/interface.cairo +++ b/src/components/offsetter/interface.cairo @@ -8,7 +8,7 @@ trait IOffsetHandler { /// Retire carbon credits from the list of carbon credits. /// Behaviour is : - /// - If one of the carbon values is not enough or vintage status is not righ, + /// - If one of the carbon values is not enough or vintage status is not right, /// the function will fail and no carbon will be retired and the function will revert. fn retire_list_carbon_credits( ref self: TContractState, vintages: Span, carbon_values: Span diff --git a/src/components/offsetter/offset_handler.cairo b/src/components/offsetter/offset_handler.cairo index f560cf3..ab832c2 100644 --- a/src/components/offsetter/offset_handler.cairo +++ b/src/components/offsetter/offset_handler.cairo @@ -43,6 +43,7 @@ mod OffsetComponent { enum Event { RequestedRetirement: RequestedRetirement, Retired: Retired, + PendingRetirementRemoved: PendingRetirementRemoved, } #[derive(Drop, starknet::Event)] @@ -53,7 +54,8 @@ mod OffsetComponent { project: ContractAddress, #[key] vintage: u256, - amount: u256, + old_amount: u256, + new_amount: u256 } #[derive(Drop, starknet::Event)] @@ -64,7 +66,18 @@ mod OffsetComponent { project: ContractAddress, #[key] vintage: u256, - amount: u256, + old_amount: u256, + new_amount: u256 + } + + #[derive(Drop, starknet::Event)] + struct PendingRetirementRemoved { + #[key] + from: ContractAddress, + #[key] + vintage: u256, + old_amount: u256, + new_amount: u256 } mod Errors { @@ -78,11 +91,10 @@ mod OffsetComponent { fn retire_carbon_credits( ref self: ComponentState, vintage: u256, cc_value: u256 ) { // TODO use token_id instead of vintage - // [Setup] Setup variable and contract interaction let caller_address: ContractAddress = get_caller_address(); let project_address: ContractAddress = self.Offsetter_carbonable_project_address.read(); - // [Check] Vintage have the right status + // [Check] Vintage got the right status let vintages = IVintageDispatcher { contract_address: project_address }; let stored_vintage: CarbonVintage = vintages .get_carbon_vintage(vintage.try_into().expect('Invalid vintage year')); @@ -90,14 +102,12 @@ mod OffsetComponent { stored_vintage.status == CarbonVintageType::Audited, 'Vintage status is not audited' ); - // [Check] caller owns the carbon credits for the vintage let erc1155 = IERC1155Dispatcher { contract_address: project_address }; let caller_balance = erc1155.balance_of(caller_address, vintage); assert(caller_balance >= cc_value, 'Not own enough carbon credits'); - // [Effect] Add pending retirement + self._add_pending_retirement(caller_address, vintage, cc_value); - // [Effect] Offset carbon credits self._offset_carbon_credit(caller_address, vintage, cc_value); } @@ -151,7 +161,6 @@ mod OffsetComponent { fn initializer( ref self: ComponentState, carbonable_project_address: ContractAddress ) { - // [Effect] Update storage self.Offsetter_carbonable_project_address.write(carbonable_project_address); } @@ -164,17 +173,18 @@ mod OffsetComponent { let current_pending_retirement = self .Offsetter_carbon_pending_retirement .read((vintage, from)); + let new_pending_retirement = current_pending_retirement + amount; self.Offsetter_carbon_pending_retirement.write((vintage, from), new_pending_retirement); - // [Event] Emit event self .emit( RequestedRetirement { from: from, project: self.Offsetter_carbonable_project_address.read(), vintage: vintage, - amount: amount + old_amount: current_pending_retirement, + new_amount: new_pending_retirement } ); } @@ -189,8 +199,19 @@ mod OffsetComponent { .Offsetter_carbon_pending_retirement .read((vintage, from)); assert(current_pending_retirement >= amount, 'Not enough pending retirement'); + let new_pending_retirement = current_pending_retirement - amount; self.Offsetter_carbon_pending_retirement.write((vintage, from), new_pending_retirement); + + self + .emit( + PendingRetirementRemoved { + from: from, + vintage: vintage, + old_amount: current_pending_retirement, + new_amount: new_pending_retirement + } + ); } fn _offset_carbon_credit( @@ -199,10 +220,8 @@ mod OffsetComponent { vintage: u256, amount: u256 ) { - // [Effect] Remove pending retirement self._remove_pending_retirement(from, vintage, amount); - // [Effect] Update storage let project = IProjectDispatcher { contract_address: self.Offsetter_carbonable_project_address.read() }; @@ -211,14 +230,14 @@ mod OffsetComponent { let new_retirement = current_retirement + amount; self.Offsetter_carbon_retired.write((vintage, from), new_retirement); - // [Event] Emit event self .emit( Retired { from: from, project: self.Offsetter_carbonable_project_address.read(), vintage: vintage, - amount: amount + old_amount: current_retirement, + new_amount: new_retirement } ); } diff --git a/src/components/vintage/vintage.cairo b/src/components/vintage/vintage.cairo index 7877cf1..6f7c599 100644 --- a/src/components/vintage/vintage.cairo +++ b/src/components/vintage/vintage.cairo @@ -24,12 +24,31 @@ mod VintageComponent { #[event] #[derive(Drop, PartialEq, starknet::Event)] enum Event { - ProjectCarbonUpdate: ProjectCarbonUpdate, + ProjectCarbonUpdated: ProjectCarbonUpdated, VintageUpdate: VintageUpdate, + VintageRebased: VintageRebased, + VintageStatusUpdated: VintageStatusUpdated, + VintageSet: VintageSet, } #[derive(Drop, PartialEq, starknet::Event)] - struct ProjectCarbonUpdate { + struct VintageRebased { + #[key] + token_id: u256, + old_supply: u128, + new_supply: u128, + } + + #[derive(Drop, PartialEq, starknet::Event)] + struct VintageStatusUpdated { + #[key] + token_id: u256, + old_status: CarbonVintageType, + new_status: CarbonVintageType, + } + + #[derive(Drop, PartialEq, starknet::Event)] + struct ProjectCarbonUpdated { old_carbon: u128, new_carbon: u128, } @@ -39,7 +58,16 @@ mod VintageComponent { struct VintageUpdate { #[key] token_id: u256, - vintage: CarbonVintage, + old_vintage: CarbonVintage, + new_vintage: CarbonVintage, + } + + #[derive(Drop, PartialEq, starknet::Event)] + struct VintageSet { + #[key] + token_id: u256, + old_vintage: CarbonVintage, + new_vintage: CarbonVintage, } mod Errors { @@ -128,7 +156,6 @@ mod VintageComponent { fn rebase_vintage( ref self: ComponentState, token_id: u256, new_cc_supply: u128 ) { - // [Check] Caller is owner self.assert_only_role(OWNER_ROLE); let mut vintage: CarbonVintage = self.Vintage_vintages.read(token_id); @@ -151,23 +178,36 @@ mod VintageComponent { } vintage.supply = new_cc_supply; self.Vintage_vintages.write(token_id, vintage); + + self + .emit( + VintageRebased { + token_id: token_id, old_supply: old_supply, new_supply: new_cc_supply, + } + ); } fn update_vintage_status( ref self: ComponentState, token_id: u256, status: u8 ) { - // [Check] Caller is owner self.assert_only_role(OWNER_ROLE); let new_status: CarbonVintageType = status.try_into().expect('Invalid status'); let mut vintage: CarbonVintage = self.Vintage_vintages.read(token_id); + let old_status = vintage.status; vintage.status = new_status; self.Vintage_vintages.write(token_id, vintage); + + self + .emit( + VintageStatusUpdated { + token_id: token_id, old_status: old_status, new_status: new_status, + } + ); } fn set_project_carbon(ref self: ComponentState, new_carbon: u128) { - // [Check] Caller is owner self.assert_only_role(OWNER_ROLE); // [Check] Project carbon is not 0 @@ -176,7 +216,7 @@ mod VintageComponent { let old_carbon = self.Vintage_project_carbon.read(); self.Vintage_project_carbon.write(new_carbon); // [Event] Emit event - self.emit(ProjectCarbonUpdate { old_carbon, new_carbon, }); + self.emit(ProjectCarbonUpdated { old_carbon, new_carbon, }); } fn set_vintages( @@ -184,10 +224,8 @@ mod VintageComponent { yearly_absorptions: Span, start_year: u32 ) { - // [Check] Caller is owner self.assert_only_role(OWNER_ROLE); - // [Check] Vintages length is not 0 assert(yearly_absorptions.len() > 0, 'Vintages length is 0'); let vintages_num = yearly_absorptions.len(); @@ -200,6 +238,7 @@ mod VintageComponent { } let supply = *yearly_absorptions.at(index); + let old_vintage = self.Vintage_vintages.read(index.into()); let vintage = CarbonVintage { year: (start_year + index).into(), supply: supply, @@ -208,7 +247,12 @@ mod VintageComponent { status: CarbonVintageType::Projected, }; self.Vintage_vintages.write(index.into(), vintage); - self.emit(VintageUpdate { token_id: index.into(), vintage: vintage, }); + self + .emit( + VintageUpdate { + token_id: index.into(), old_vintage: old_vintage, new_vintage: vintage + } + ); index += 1; }; } @@ -238,7 +282,7 @@ mod VintageComponent { created: 0, status: CarbonVintageType::Projected, }; - // [Effect] Store values + self.Vintage_vintages.write(index.into(), vintage); index += 1; }; diff --git a/src/contracts/project.cairo b/src/contracts/project.cairo index da7c6ac..c0d5007 100644 --- a/src/contracts/project.cairo +++ b/src/contracts/project.cairo @@ -160,6 +160,16 @@ mod Project { MetadataEvent: MetadataComponent::Event, } + #[derive(Drop, starknet::Event)] + struct MinterRoleGranted { + account: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + struct MinterRoleRevoked { + account: ContractAddress, + } + mod Errors { const UNEQUAL_ARRAYS_URI: felt252 = 'URI Array len do not match'; const INVALID_ARRAY_LENGTH: felt252 = 'ERC1155: no equal array length'; diff --git a/tests/test_mint.cairo b/tests/test_mint.cairo index 2d74b1a..067401e 100644 --- a/tests/test_mint.cairo +++ b/tests/test_mint.cairo @@ -1,3 +1,4 @@ +use snforge_std::cheatcodes::events::EventSpyAssertionsTrait; // TODO: // - check if is_setup is needed // - refactor project setup into helper function? @@ -10,22 +11,24 @@ use starknet::{ContractAddress, contract_address_const}; use openzeppelin::utils::serde::SerializedAppend; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use openzeppelin::token::erc1155::ERC1155Component; use snforge_std as snf; use snforge_std::{ ContractClassTrait, test_address, spy_events, EventSpy, CheatSpan, start_cheat_caller_address, - stop_cheat_caller_address, + stop_cheat_caller_address }; // Components use carbon_v3::components::vintage::interface::{IVintageDispatcher, IVintageDispatcherTrait}; use carbon_v3::components::vintage::VintageComponent; -use carbon_v3::components::vintage::VintageComponent::{Event, ProjectCarbonUpdate}; +use carbon_v3::components::vintage::VintageComponent::{Event}; use carbon_v3::models::carbon_vintage::{CarbonVintage, CarbonVintageType}; use carbon_v3::components::minter::interface::{IMintDispatcher, IMintDispatcherTrait}; +use carbon_v3::components::minter::MintComponent; // Contracts @@ -40,7 +43,8 @@ use carbon_v3::mock::usdcarb::USDCarb; use super::tests_lib::{ get_mock_absorptions, equals_with_error, deploy_project, setup_project, - default_setup_and_deploy, deploy_offsetter, deploy_erc20, deploy_minter, buy_utils + default_setup_and_deploy, deploy_offsetter, deploy_erc20, deploy_minter, buy_utils, + helper_get_token_ids }; // Constants @@ -69,10 +73,10 @@ struct Contracts { // is_public_sale_open #[test] -fn test_is_public_sale_open() { - let (project_address, _) = deploy_project(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); +fn test_is_public_sale_open_default_value() { + let project_address = deploy_project(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); let minter = IMintDispatcher { contract_address: minter_address }; let sale_open = minter.is_public_sale_open(); @@ -82,9 +86,9 @@ fn test_is_public_sale_open() { #[test] #[should_panic(expected: 'Caller is not the owner')] fn test_set_public_sale_open_without_owner_role() { - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); let minter = IMintDispatcher { contract_address: minter_address }; @@ -94,16 +98,20 @@ fn test_set_public_sale_open_without_owner_role() { #[test] fn test_set_public_sale_open_with_owner_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + let mut spy = spy_events(); - // [Prank] Use owner as caller to Minter contract start_cheat_caller_address(minter_address, owner_address); let minter = IMintDispatcher { contract_address: minter_address }; minter.set_public_sale_open(true); + let expected_event = MintComponent::Event::PublicSaleOpen( + MintComponent::PublicSaleOpen { old_value: true, new_value: true } + ); + spy.assert_emitted(@array![(minter_address, expected_event)]); } // public_buy @@ -112,9 +120,10 @@ fn test_set_public_sale_open_with_owner_role() { fn test_public_buy() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = deploy_project(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = deploy_project(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + let mut spy = spy_events(); start_cheat_caller_address(project_address, owner_address); let project_contract = IProjectDispatcher { contract_address: project_address }; @@ -140,8 +149,46 @@ fn test_public_buy() { start_cheat_caller_address(minter_address, user_address); start_cheat_caller_address(erc20_address, minter_address); - let tokenized_cc: Span = minter.public_buy(amount_to_buy, false); - assert(tokenized_cc.len() == 19, 'should be 19 vintages'); + + minter.public_buy(amount_to_buy); + + // Build the expected events + let vintages = IVintageDispatcher { contract_address: project_address }; + let number_of_vintages = vintages.get_num_vintages(); + let max_money_amount = minter.get_max_money_amount(); + let share = amount_to_buy * CC_DECIMALS_MULTIPLIER / max_money_amount; + let mut cc_shares: Array = Default::default(); + let mut tokens: Array = Default::default(); + let mut index = 0; + loop { + if index >= number_of_vintages { + break; + } + cc_shares.append(share); + index += 1; + tokens.append(index.into()) + }; + let cc_shares = cc_shares.span(); + let token_ids = tokens.span(); + + // Assert the emitted events + let expected_event_buy = MintComponent::Event::Buy( + MintComponent::Buy { + address: user_address, money_amount: amount_to_buy, vintages: token_ids + } + ); + spy.assert_emitted(@array![(minter_address, expected_event_buy)]); + + let expected_event_1155_transfer = ERC1155Component::Event::TransferBatch( + ERC1155Component::TransferBatch { + operator: minter_address, + from: Zeroable::zero(), + to: user_address, + ids: token_ids, + values: cc_shares + } + ); + spy.assert_emitted(@array![(project_address, expected_event_1155_transfer)]); } // get_available_money_amount @@ -150,9 +197,9 @@ fn test_public_buy() { fn test_get_available_money_amount() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = deploy_project(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = deploy_project(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); let project_contract = IProjectDispatcher { contract_address: project_address }; @@ -180,7 +227,7 @@ fn test_get_available_money_amount() { start_cheat_caller_address(minter_address, user_address); start_cheat_caller_address(erc20_address, minter_address); - minter.public_buy(amount_to_buy, false); + minter.public_buy(amount_to_buy); stop_cheat_caller_address(minter_address); stop_cheat_caller_address(erc20_address); @@ -198,7 +245,7 @@ fn test_get_available_money_amount() { erc20.approve(minter_address, remaining_money_to_buy); start_cheat_caller_address(minter_address, user_address); start_cheat_caller_address(erc20_address, minter_address); - minter.public_buy(remaining_money_to_buy, false); + minter.public_buy(remaining_money_to_buy); let remaining_money_after_buying_all = minter.get_available_money_amount(); assert(remaining_money_after_buying_all == 0, 'remaining money wrong value'); @@ -209,12 +256,12 @@ fn test_get_available_money_amount() { #[test] fn test_cancel_mint() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = deploy_project(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = deploy_project(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + let mut spy = spy_events(); let minter = IMintDispatcher { contract_address: minter_address }; - start_cheat_caller_address(minter_address, owner_address); // Ensure the mint is not canceled initially @@ -223,6 +270,10 @@ fn test_cancel_mint() { // Cancel the mint minter.cancel_mint(true); + let expected_event = MintComponent::Event::MintCanceled( + MintComponent::MintCanceled { old_value: false, is_canceled: true } + ); + spy.assert_emitted(@array![(minter_address, expected_event)]); // Verify that the mint is canceled let is_canceled_after = minter.is_canceled(); @@ -230,44 +281,22 @@ fn test_cancel_mint() { // Reopen the mint minter.cancel_mint(false); + let expected_event_reopen = MintComponent::Event::MintCanceled( + MintComponent::MintCanceled { old_value: true, is_canceled: false } + ); + spy.assert_emitted(@array![(minter_address, expected_event_reopen)]); // Verify that the mint is reopened let is_canceled_reopened = minter.is_canceled(); assert(!is_canceled_reopened, 'mint should be reopened') } -#[test] -#[should_panic(expected: 'Caller is not the owner')] -fn test_set_unit_price_without_owner_role() { - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); - - let minter = IMintDispatcher { contract_address: minter_address }; - let price: u256 = 100; - minter.set_unit_price(price); -} - -#[test] -fn test_set_unit_price_with_owner_role() { - let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); - - start_cheat_caller_address(minter_address, owner_address); - - let minter = IMintDispatcher { contract_address: minter_address }; - let price: u256 = 100; - minter.set_unit_price(price); -} - #[test] fn test_get_max_money_amount() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); let project_contract = IProjectDispatcher { contract_address: project_address }; @@ -289,7 +318,7 @@ fn test_get_max_money_amount() { start_cheat_caller_address(minter_address, owner_address); start_cheat_caller_address(erc20_address, minter_address); - minter.public_buy(amount_to_buy, false); + minter.public_buy(amount_to_buy); stop_cheat_caller_address(minter_address); stop_cheat_caller_address(erc20_address); @@ -302,9 +331,9 @@ fn test_get_max_money_amount() { fn test_get_min_money_amount_per_tx() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); start_cheat_caller_address(minter_address, owner_address); let project_contract = IProjectDispatcher { contract_address: project_address }; @@ -327,7 +356,7 @@ fn test_get_min_money_amount_per_tx() { start_cheat_caller_address(minter_address, user_address); start_cheat_caller_address(erc20_address, minter_address); - minter.public_buy(amount_to_buy, false); + minter.public_buy(amount_to_buy); // Verify the min money amount per transaction remains unchanged let min_money_per_tx_after_buy = minter.get_min_money_amount_per_tx(); @@ -341,9 +370,10 @@ fn test_get_min_money_amount_per_tx() { fn test_is_sold_out() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + let mut spy = spy_events(); start_cheat_caller_address(project_address, owner_address); let project_contract = IProjectDispatcher { contract_address: project_address }; @@ -364,22 +394,30 @@ fn test_is_sold_out() { start_cheat_caller_address(minter_address, user_address); start_cheat_caller_address(erc20_address, minter_address); - minter.public_buy(remaining_money, false); + minter.public_buy(remaining_money); let remaining_money_after_buying_all = minter.get_available_money_amount(); assert(remaining_money_after_buying_all == 0, 'remaining money wrong'); let is_sold_out_after = minter.is_sold_out(); assert(is_sold_out_after, 'should be sold out'); + + let expected_event_sale_close = MintComponent::Event::PublicSaleClose( + MintComponent::PublicSaleClose { old_value: true, new_value: false } + ); + let expected_event_sold_out = MintComponent::Event::SoldOut(MintComponent::SoldOut {}); + spy.assert_emitted(@array![(minter_address, expected_event_sale_close)]); + spy.assert_emitted(@array![(minter_address, expected_event_sold_out)]); } #[test] fn test_set_min_money_amount_per_tx() { // Deploy required contracts let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + let mut spy = spy_events(); start_cheat_caller_address(project_address, owner_address); start_cheat_caller_address(minter_address, owner_address); @@ -393,14 +431,21 @@ fn test_set_min_money_amount_per_tx() { minter.set_min_money_amount_per_tx(new_min_money_amount); let updated_min_money_amount_per_tx = minter.get_min_money_amount_per_tx(); assert(updated_min_money_amount_per_tx == new_min_money_amount, 'Updated min money incorrect'); + + let expected_event = MintComponent::Event::MinMoneyAmountPerTxUpdated( + MintComponent::MinMoneyAmountPerTxUpdated { + old_amount: 0, new_amount: new_min_money_amount + } + ); + spy.assert_emitted(@array![(minter_address, expected_event)]); } #[test] #[should_panic(expected: 'Caller is not the owner')] fn test_set_min_money_amount_per_tx_without_owner_role() { - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); let minter = IMintDispatcher { contract_address: minter_address }; let amount: u256 = 100; @@ -412,9 +457,9 @@ fn test_set_min_money_amount_per_tx_without_owner_role() { fn test_set_min_money_amount_per_tx_panic() { // Deploy required contracts let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); start_cheat_caller_address(minter_address, owner_address); let project_contract = IProjectDispatcher { contract_address: project_address }; @@ -435,9 +480,9 @@ fn test_set_min_money_amount_per_tx_panic() { #[test] fn test_get_carbonable_project_address() { let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(minter_address, user_address); let minter = IMintDispatcher { contract_address: minter_address }; @@ -450,9 +495,9 @@ fn test_get_carbonable_project_address() { #[test] fn test_get_payment_token_address() { let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(minter_address, user_address); let minter = IMintDispatcher { contract_address: minter_address }; @@ -464,13 +509,45 @@ fn test_get_payment_token_address() { // set_unit_price #[test] -fn test_set_unit_price() { +#[should_panic(expected: 'Caller is not the owner')] +fn test_set_unit_price_without_owner_role() { + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + + let minter = IMintDispatcher { contract_address: minter_address }; + let price: u256 = 100; + minter.set_unit_price(price); +} + +#[test] +fn test_set_unit_price_with_owner_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + let mut spy = spy_events(); + + start_cheat_caller_address(minter_address, owner_address); + + let minter = IMintDispatcher { contract_address: minter_address }; + let price: u256 = 100; + minter.set_unit_price(price); + + let expected_event = MintComponent::Event::UnitPriceUpdated( + MintComponent::UnitPriceUpdated { old_price: 11, new_price: price } + ); + spy.assert_emitted(@array![(minter_address, expected_event)]); +} + +#[test] +#[should_panic(expected: 'Invalid unit price')] +fn test_set_unit_price_to_zero_panic() { + let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); - start_cheat_caller_address(erc20_address, owner_address); start_cheat_caller_address(minter_address, owner_address); let minter = IMintDispatcher { contract_address: minter_address }; @@ -478,24 +555,9 @@ fn test_set_unit_price() { let unit_price = minter.get_unit_price(); assert(unit_price == 11, 'unit price should be 11'); - // Set the unit price - let new_unit_price: u256 = 1000; - start_cheat_caller_address(minter_address, owner_address); + // Set the unit price to 0 and it should panic + let new_unit_price: u256 = 0; minter.set_unit_price(new_unit_price); - stop_cheat_caller_address(minter_address); - - let unit_price_after = minter.get_unit_price(); - assert(unit_price_after == new_unit_price, 'unit price wrong value'); - - // Set the unit price to a large value - let new_unit_price_large: u256 = 1000000000; - start_cheat_caller_address(minter_address, owner_address); - minter.set_unit_price(new_unit_price_large); - stop_cheat_caller_address(minter_address); - - // Verify that the unit price is set correctly - let unit_price_after_large = minter.get_unit_price(); - assert(unit_price_after_large == new_unit_price_large, 'unit price wrong value'); } // get_unit_price @@ -504,9 +566,9 @@ fn test_set_unit_price() { fn test_get_unit_price() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(minter_address, user_address); let minter = IMintDispatcher { contract_address: minter_address }; @@ -526,35 +588,14 @@ fn test_get_unit_price() { assert(unit_price_after == new_unit_price, 'unit price wrong value'); } -// set_unit_price_to_zero_panic - -#[test] -#[should_panic(expected: 'Invalid unit price')] -fn test_set_unit_price_to_zero_panic() { - let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); - - start_cheat_caller_address(minter_address, owner_address); - let minter = IMintDispatcher { contract_address: minter_address }; - - // Ensure the unit price is not set initially - let unit_price = minter.get_unit_price(); - assert(unit_price == 11, 'unit price should be 11'); - - // Set the unit price to 0 and it should panic - let new_unit_price: u256 = 0; - minter.set_unit_price(new_unit_price); -} - #[test] fn test_withdraw() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = deploy_project(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = deploy_project(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + let mut spy = spy_events(); start_cheat_caller_address(erc20_address, owner_address); start_cheat_caller_address(minter_address, owner_address); @@ -582,6 +623,11 @@ fn test_withdraw() { balance_owner_after == balance_owner_before_withdraw + balance_to_withdraw, 'balance should be the same' ); + + let expected_event = MintComponent::Event::Withdraw( + MintComponent::Withdraw { recipient: owner_address, amount: balance_to_withdraw } + ); + spy.assert_emitted(@array![(minter_address, expected_event)]); } #[test] @@ -589,9 +635,9 @@ fn test_withdraw() { fn test_withdraw_without_owner_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = deploy_project(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = deploy_project(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(erc20_address, owner_address); start_cheat_caller_address(minter_address, owner_address); @@ -614,14 +660,17 @@ fn test_withdraw_without_owner_role() { fn test_retrieve_amount() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); + // Deploy erc20 used for the minter, and a second erc20 that isn't used let contract = snf::declare("USDCarb").expect('Failed to declare contract'); let mut calldata: Array = array![user_address.into(), user_address.into()]; let (erc20_address, _) = contract.deploy(@calldata).expect('Failed to deploy contract'); calldata = array![user_address.into(), user_address.into()]; let (second_erc20_address, _) = contract.deploy(@calldata).expect('Failed to deploy contract'); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let minter_address = deploy_minter(project_address, erc20_address); + + let mut spy = spy_events(); start_cheat_caller_address(second_erc20_address, user_address); start_cheat_caller_address(minter_address, owner_address); @@ -634,6 +683,13 @@ fn test_retrieve_amount() { minter.retrieve_amount(second_erc20_address, owner_address, 1000); let balance_after = second_er20.balance_of(owner_address); assert(balance_after == 1000, 'balance should be the same'); + + let expected_event = MintComponent::Event::AmountRetrieved( + MintComponent::AmountRetrieved { + token_address: second_erc20_address, recipient: owner_address, amount: 1000 + } + ); + spy.assert_emitted(@array![(minter_address, expected_event)]); } #[test] @@ -641,14 +697,14 @@ fn test_retrieve_amount() { fn test_retrieve_amount_without_owner_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); // Deploy erc20 used for the minter, and a second erc20 that isn't used let contract = snf::declare("USDCarb").expect('Failed to declare contract'); let mut calldata: Array = array![user_address.into(), user_address.into()]; let (erc20_address, _) = contract.deploy(@calldata).expect('Failed to deploy contract'); calldata = array![user_address.into(), user_address.into()]; let (second_erc20_address, _) = contract.deploy(@calldata).expect('Failed to deploy contract'); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(second_erc20_address, user_address); start_cheat_caller_address(minter_address, owner_address); diff --git a/tests/test_offsetter.cairo b/tests/test_offsetter.cairo index 62a6040..98c94bd 100644 --- a/tests/test_offsetter.cairo +++ b/tests/test_offsetter.cairo @@ -29,7 +29,7 @@ use snforge_std::{ // Components use carbon_v3::components::vintage::interface::{IVintageDispatcher, IVintageDispatcherTrait}; -use carbon_v3::components::vintage::vintage::VintageComponent::{Event, ProjectCarbonUpdate}; +use carbon_v3::components::vintage::vintage::VintageComponent::{Event}; use carbon_v3::models::carbon_vintage::{CarbonVintage, CarbonVintageType}; use carbon_v3::models::constants::CC_DECIMALS_MULTIPLIER; use carbon_v3::components::vintage::VintageComponent; @@ -78,8 +78,8 @@ struct Contracts { #[test] fn test_offsetter_init() { let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); let offsetter = IOffsetHandlerDispatcher { contract_address: offsetter_address }; start_cheat_caller_address(offsetter_address, user_address); @@ -97,10 +97,10 @@ fn test_offsetter_init() { fn test_offsetter_retire_carbon_credits() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); let token_id: u256 = 1; start_cheat_caller_address(offsetter_address, user_address); @@ -136,10 +136,10 @@ fn test_offsetter_retire_carbon_credits() { fn test_offsetter_wrong_status() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(offsetter_address, user_address); start_cheat_caller_address(project_address, owner_address); @@ -168,10 +168,10 @@ fn test_offsetter_wrong_status() { fn test_retire_carbon_credits_insufficient_credits() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(offsetter_address, user_address); start_cheat_caller_address(project_address, owner_address); @@ -199,10 +199,10 @@ fn test_retire_carbon_credits_insufficient_credits() { fn test_retire_carbon_credits_exact_balance() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(offsetter_address, user_address); start_cheat_caller_address(project_address, owner_address); @@ -237,10 +237,10 @@ fn test_retire_carbon_credits_exact_balance() { fn test_retire_carbon_credits_multiple_retirements() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(offsetter_address, user_address); start_cheat_caller_address(project_address, owner_address); @@ -277,10 +277,10 @@ fn test_retire_carbon_credits_multiple_retirements() { fn test_retire_list_carbon_credits_valid_inputs() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(offsetter_address, user_address); start_cheat_caller_address(project_address, owner_address); @@ -323,8 +323,8 @@ fn test_retire_list_carbon_credits_valid_inputs() { #[should_panic(expected: 'Inputs cannot be empty')] fn test_retire_list_carbon_credits_empty_inputs() { let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); start_cheat_caller_address(offsetter_address, user_address); @@ -336,8 +336,8 @@ fn test_retire_list_carbon_credits_empty_inputs() { #[should_panic(expected: 'Vintages and Values mismatch')] fn test_retire_list_carbon_credits_mismatched_lengths() { let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); let token_id: u256 = 1; start_cheat_caller_address(offsetter_address, user_address); @@ -353,10 +353,10 @@ fn test_retire_list_carbon_credits_mismatched_lengths() { fn test_retire_list_carbon_credits_partial_valid_inputs() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(offsetter_address, user_address); start_cheat_caller_address(project_address, owner_address); @@ -386,10 +386,10 @@ fn test_retire_list_carbon_credits_partial_valid_inputs() { fn test_retire_list_carbon_credits_multiple_same_vintage() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(offsetter_address, user_address); start_cheat_caller_address(project_address, owner_address); @@ -426,8 +426,8 @@ fn test_retire_list_carbon_credits_multiple_same_vintage() { #[test] fn test_get_pending_retirement_no_pending() { let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); start_cheat_caller_address(offsetter_address, user_address); let offsetter = IOffsetHandlerDispatcher { contract_address: offsetter_address }; @@ -443,8 +443,8 @@ fn test_get_pending_retirement_no_pending() { #[test] fn test_get_carbon_retired_no_retired() { let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); start_cheat_caller_address(offsetter_address, user_address); let offsetter = IOffsetHandlerDispatcher { contract_address: offsetter_address }; diff --git a/tests/test_project.cairo b/tests/test_project.cairo index 271558c..8914b22 100644 --- a/tests/test_project.cairo +++ b/tests/test_project.cairo @@ -1,3 +1,6 @@ +use snforge_std::cheatcodes::events::EventsFilterTrait; +use snforge_std::cheatcodes::events::EventSpyTrait; +use snforge_std::cheatcodes::events::EventSpyAssertionsTrait; // TODO: use token_ids instead of years as vintage // Starknet deps @@ -6,6 +9,7 @@ use starknet::{ContractAddress, contract_address_const, get_caller_address, Clas // External deps use openzeppelin::utils::serde::SerializedAppend; +use openzeppelin::token::erc1155::ERC1155Component; use snforge_std as snf; use snforge_std::{ ContractClassTrait, EventSpy, start_cheat_caller_address, stop_cheat_caller_address, spy_events @@ -41,43 +45,49 @@ use super::tests_lib::{ perform_fuzzed_transfer, buy_utils, deploy_erc20, deploy_minter, deploy_offsetter }; -#[test] -fn test_constructor_ok() { - let (_project_address, _spy) = deploy_project(); -} - #[test] fn test_project_mint() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let user_address: ContractAddress = contract_address_const::<'USER'>(); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); let vintages = IVintageDispatcher { contract_address: project_address }; start_cheat_caller_address(project_address, owner_address); let project_contract = IProjectDispatcher { contract_address: project_address }; project_contract.grant_minter_role(minter_address); - stop_cheat_caller_address(project_address); start_cheat_caller_address(project_address, minter_address); let share: u256 = 10 * CC_DECIMALS_MULTIPLIER / 100; // 10% of the total supply let token_id: u256 = 1; - project_contract.mint(owner_address, token_id, share); + let mut spy: EventSpy = spy_events(); + project_contract.mint(user_address, token_id, share); let supply_vintage_token_id = vintages.get_carbon_vintage(token_id).supply; let expected_balance = supply_vintage_token_id.into() * share / CC_DECIMALS_MULTIPLIER; - let balance = project_contract.balance_of(owner_address, token_id); + let balance = project_contract.balance_of(user_address, token_id); assert(equals_with_error(balance, expected_balance, 10), 'Error of balance'); + let expected_event_1155_transfer_single = ERC1155Component::Event::TransferSingle( + ERC1155Component::TransferSingle { + operator: minter_address, + from: Zeroable::zero(), + to: user_address, + id: token_id, + value: share + } + ); + spy.assert_emitted(@array![(project_address, expected_event_1155_transfer_single)]); } #[test] #[should_panic(expected: 'Only Minter can mint')] fn test_project_mint_without_minter_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, minter_address); let project_contract = IProjectDispatcher { contract_address: project_address }; @@ -91,7 +101,7 @@ fn test_project_mint_without_minter_role() { #[should_panic(expected: 'Only Minter can batch mint')] fn test_project_batch_mint_without_minter_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; start_cheat_caller_address(project_address, owner_address); @@ -126,10 +136,12 @@ fn test_project_batch_mint_without_minter_role() { #[test] fn test_project_batch_mint_with_minter_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let user_address: ContractAddress = contract_address_const::<'USER'>(); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); let vintages = IVintageDispatcher { contract_address: project_address }; + let mut spy = spy_events(); start_cheat_caller_address(project_address, owner_address); let project_contract = IProjectDispatcher { contract_address: project_address }; @@ -139,7 +151,7 @@ fn test_project_batch_mint_with_minter_role() { let share: u256 = 10 * CC_DECIMALS_MULTIPLIER / 100; // 10% of the total supply let num_vintages = vintages.get_num_vintages(); - let mut cc_distribution: Array = Default::default(); + let mut cc_shares: Array = Default::default(); let mut tokens: Array = Default::default(); let mut index = 0; loop { @@ -147,30 +159,41 @@ fn test_project_batch_mint_with_minter_role() { break; }; - cc_distribution.append(share); + cc_shares.append(share); index += 1; - tokens.append(index.into()) + tokens.append(index.into()); }; - let cc_distribution = cc_distribution.span(); + let cc_shares = cc_shares.span(); let token_ids = tokens.span(); - project_contract.batch_mint(owner_address, token_ids, cc_distribution); + project_contract.batch_mint(user_address, token_ids, cc_shares); let token_id: u256 = 1; let supply_vintage_token_id = vintages.get_carbon_vintage(token_id).supply; let expected_balance = supply_vintage_token_id.into() * share / CC_DECIMALS_MULTIPLIER; - let balance = project_contract.balance_of(owner_address, token_id); + let balance = project_contract.balance_of(user_address, token_id); assert(equals_with_error(balance, expected_balance, 10), 'Error of balance'); + + let expected_event_1155_transfer = ERC1155Component::Event::TransferBatch( + ERC1155Component::TransferBatch { + operator: minter_address, + from: Zeroable::zero(), + to: user_address, + ids: token_ids, + values: cc_shares + } + ); + spy.assert_emitted(@array![(project_address, expected_event_1155_transfer)]); } #[test] fn test_project_offset_with_offsetter_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); start_cheat_caller_address(offsetter_address, user_address); @@ -190,9 +213,22 @@ fn test_project_offset_with_offsetter_role() { let token_id: u256 = 1; vintages.update_vintage_status(token_id, CarbonVintageType::Audited.into()); stop_cheat_caller_address(project_address); + let balance = project.balance_of(user_address, token_id); + let mut spy = spy_events(); + let share_value = vintages.cc_to_share(balance, token_id); start_cheat_caller_address(project_address, offsetter_address); - project.offset(user_address, token_id, 100); + project.offset(user_address, token_id, balance); + let expected_event_1155_transfer_single = ERC1155Component::Event::TransferSingle( + ERC1155Component::TransferSingle { + operator: offsetter_address, + from: user_address, + to: Zeroable::zero(), + id: token_id, + value: share_value + } + ); + spy.assert_emitted(@array![(project_address, expected_event_1155_transfer_single)]); } #[test] @@ -200,10 +236,10 @@ fn test_project_offset_with_offsetter_role() { fn test_project_offset_without_offsetter_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); start_cheat_caller_address(offsetter_address, user_address); @@ -229,10 +265,10 @@ fn test_project_offset_without_offsetter_role() { fn test_project_batch_offset_with_offsetter_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); start_cheat_caller_address(offsetter_address, user_address); @@ -270,8 +306,20 @@ fn test_project_batch_offset_with_offsetter_role() { let cc_distribution = cc_distribution.span(); let token_ids = tokens.span(); + let mut spy = spy_events(); + start_cheat_caller_address(project_address, offsetter_address); project.batch_offset(user_address, token_ids, cc_distribution); + let expected_event_1155_transfer = ERC1155Component::Event::TransferBatch( + ERC1155Component::TransferBatch { + operator: offsetter_address, + from: user_address, + to: Zeroable::zero(), + ids: token_ids, + values: cc_distribution + } + ); + spy.assert_emitted(@array![(project_address, expected_event_1155_transfer)]); } #[test] @@ -279,10 +327,10 @@ fn test_project_batch_offset_with_offsetter_role() { fn test_project_batch_offset_without_offsetter_role() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (offsetter_address, _) = deploy_offsetter(project_address); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let offsetter_address = deploy_offsetter(project_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); start_cheat_caller_address(offsetter_address, user_address); @@ -323,29 +371,16 @@ fn test_project_batch_offset_without_offsetter_role() { project.batch_offset(user_address, token_ids, cc_distribution); } -#[test] -fn test_project_set_vintage_status() { - let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let vintages = IVintageDispatcher { contract_address: project_address }; - - start_cheat_caller_address(project_address, owner_address); - let token_id: u256 = 1; - vintages.update_vintage_status(token_id, 3); - let vintage: CarbonVintage = vintages.get_carbon_vintage(token_id); - assert(vintage.status == CarbonVintageType::Audited, 'Error of status'); -} - /// Test balance_of #[test] fn test_project_balance_of() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let project_contract = IProjectDispatcher { contract_address: project_address }; - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); project_contract.grant_minter_role(minter_address); @@ -365,11 +400,12 @@ fn test_project_balance_of() { fn test_transfer_without_loss() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let project_contract = IProjectDispatcher { contract_address: project_address }; - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); + start_cheat_caller_address(project_address, owner_address); project_contract.grant_minter_role(minter_address); @@ -383,16 +419,28 @@ fn test_transfer_without_loss() { let expected_balance = supply_vintage_token_id.into() * share / CC_DECIMALS_MULTIPLIER; let balance = project_contract.balance_of(user_address, token_id); - assert(equals_with_error(balance, expected_balance, 10), 'Error balance owner 1'); + assert(balance == expected_balance, 'Error balance owner 1'); let receiver_address: ContractAddress = contract_address_const::<'receiver'>(); let receiver_balance = project_contract.balance_of(receiver_address, token_id); assert(equals_with_error(receiver_balance, 0, 10), 'Error of receiver balance 1'); + let mut spy = spy_events(); + let value = vintages.cc_to_share(balance, token_id); project_contract .safe_transfer_from( user_address, receiver_address, token_id, balance.into(), array![].span() ); + let expected_event_1155_transfer_single = ERC1155Component::Event::TransferSingle( + ERC1155Component::TransferSingle { + operator: user_address, + from: user_address, + to: receiver_address, + id: token_id, + value: value + } + ); + spy.assert_emitted(@array![(project_address, expected_event_1155_transfer_single)]); let balance = project_contract.balance_of(user_address, token_id); assert(equals_with_error(balance, 0, 10), 'Error balance owner 2'); @@ -409,12 +457,12 @@ fn test_consecutive_transfers_and_rebases( ) { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let project_contract = IProjectDispatcher { contract_address: project_address }; let vintages = IVintageDispatcher { contract_address: project_address }; - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); start_cheat_caller_address(project_address, owner_address); project_contract.grant_minter_role(minter_address); @@ -637,33 +685,34 @@ fn fuzz_test_transfer_high_supply_high_amount( ); } -#[test] -fn test_project_metadata_update() { - let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); - let project_contract = IProjectDispatcher { contract_address: project_address }; - let metadata = IMetadataHandlerDispatcher { contract_address: project_address }; - let base_uri: ClassHash = 0.try_into().unwrap(); - let mut new_uri: ClassHash = 'new/uri'.try_into().unwrap(); - - start_cheat_caller_address(project_address, owner_address); +// #[test] +// fn test_project_metadata_update() { +// let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); +// let project_address = default_setup_and_deploy(); +// let project_contract = IProjectDispatcher { contract_address: project_address }; +// let erc1155_meta = IERC1155MetadataURIDispatcher { contract_address: project_address }; +// let mut spy = spy_events(); +// let base_uri: ByteArray = format!("{}", 'uri'); +// let mut new_uri: ByteArray = format!("{}", 'new/uri'); - assert(metadata.get_uri() == base_uri, 'Wrong base token URI'); +// start_cheat_caller_address(project_address, owner_address); - project_contract.set_uri(new_uri); +// let vintage = 1; +// assert(erc1155_meta.uri(vintage) == base_uri, 'Wrong base token URI'); - assert(metadata.get_uri() == new_uri, 'Wrong updated token URI'); -//check event emitted -// let expected_batch_metadata_update = BatchMetadataUpdate { -// from_token_id: 0, to_token_id: num_vintages.into() -// }; +// project_contract.set_uri(new_uri.clone()); +// assert(erc1155_meta.uri(vintage) == new_uri.clone(), 'Wrong updated token URI'); -//todo: check if the event is emitted, do all the events assertions -} +// let expected_batch_metadata_update = BatchMetadataUpdate { +// from_token_id: 0, +// to_token_id: 1 +// }; +// spy.assert_emitted(@array![(project_address, expected_batch_metadata_update)]); +// } fn test_set_uri() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let project_contract = IProjectDispatcher { contract_address: project_address }; start_cheat_caller_address(project_address, owner_address); @@ -674,7 +723,7 @@ fn test_set_uri() { #[test] fn test_decimals() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let project_contract = IProjectDispatcher { contract_address: project_address }; let project_decimals = project_contract.decimals(); @@ -683,7 +732,7 @@ fn test_decimals() { #[test] fn test_shares_of() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let project_contract = IProjectDispatcher { contract_address: project_address }; let token_id: u256 = 1; @@ -694,7 +743,7 @@ fn test_shares_of() { #[test] fn test_is_approved_for_all() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let project_contract = IProjectDispatcher { contract_address: project_address }; let owner = get_caller_address(); @@ -707,15 +756,22 @@ fn test_is_approved_for_all() { #[test] fn test_set_approval_for_all() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let project_contract = IProjectDispatcher { contract_address: project_address }; + let mut spy = spy_events(); start_cheat_caller_address(project_address, owner_address); - let owner = get_caller_address(); let approval: bool = false; project_contract.set_approval_for_all(project_address, approval); - let status_now = project_contract.is_approved_for_all(owner, project_address); + let status_now = project_contract.is_approved_for_all(owner_address, project_address); assert_eq!(status_now, false); + + let expected_event = ERC1155Component::Event::ApprovalForAll( + ERC1155Component::ApprovalForAll { + owner: owner_address, operator: project_address, approved: approval + } + ); + spy.assert_emitted(@array![(project_address, expected_event)]); } diff --git a/tests/test_vintage.cairo b/tests/test_vintage.cairo index fcd0587..a4c639d 100644 --- a/tests/test_vintage.cairo +++ b/tests/test_vintage.cairo @@ -16,7 +16,7 @@ use snforge_std::{ // Components use carbon_v3::components::vintage::interface::{IVintageDispatcher, IVintageDispatcherTrait}; -use carbon_v3::components::vintage::VintageComponent::{Event, ProjectCarbonUpdate}; +use carbon_v3::components::vintage::VintageComponent::{Event}; use carbon_v3::models::carbon_vintage::{CarbonVintage, CarbonVintageType}; use carbon_v3::models::constants::CC_DECIMALS_MULTIPLIER; use carbon_v3::components::vintage::VintageComponent; @@ -62,41 +62,26 @@ struct Contracts { #[test] fn test_set_project_carbon() { - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); let vintages = IVintageDispatcher { contract_address: project_address }; let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); start_cheat_caller_address(project_address, owner_address); vintages.set_project_carbon(PROJECT_CARBON); let fetched_value = vintages.get_project_carbon(); assert(fetched_value == PROJECT_CARBON.into(), 'project_carbon wrong value'); -// spy -// .assert_emitted( -// @array![ -// ( -// project_address, -// VintageComponent::Event::ProjectCarbonUpdate( -// VintageComponent::ProjectCarbonUpdate { -// old_carbon: 0, new_carbon: fetched_value -// } -// ) -// ) -// ] -// ); -// // found events are removed from the spy after assertion, so the length should be 0 -// assert(spy.events.len() == 0, 'number of events should be 0'); } #[test] #[should_panic(expected: 'Caller does not have role')] fn test_set_project_carbon_without_owner_role() { - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); let vintages = IVintageDispatcher { contract_address: project_address }; vintages.set_project_carbon(PROJECT_CARBON.into()); } #[test] fn test_get_project_carbon_not_set() { - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); let vintages = IVintageDispatcher { contract_address: project_address }; // [Assert] default project_carbon is 0 let fetched_value = vintages.get_project_carbon(); @@ -105,7 +90,7 @@ fn test_get_project_carbon_not_set() { #[test] fn test_set_project_carbon_twice() { - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); let vintages = IVintageDispatcher { contract_address: project_address }; let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); start_cheat_caller_address(project_address, owner_address); @@ -122,7 +107,7 @@ fn test_set_project_carbon_twice() { #[test] fn test_share_to_cc_zero_share() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1.into(); let share: u256 = 0.into(); @@ -133,7 +118,7 @@ fn test_share_to_cc_zero_share() { #[test] #[should_panic(expected: 'CC value exceeds vintage supply')] fn test_share_to_cc_revert_exceeds_supply() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let share: u256 = 2 * CC_DECIMALS_MULTIPLIER; // share is greater than 100% let token_id = 1; @@ -142,7 +127,7 @@ fn test_share_to_cc_revert_exceeds_supply() { #[test] fn test_share_to_cc_equal_to_multiplier() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let share: u256 = CC_DECIMALS_MULTIPLIER.into(); let token_id = 1; @@ -154,7 +139,7 @@ fn test_share_to_cc_equal_to_multiplier() { #[test] fn test_share_to_cc_half_supply() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let share: u256 = 50 * CC_DECIMALS_MULTIPLIER / 100; // 50% @@ -167,7 +152,7 @@ fn test_share_to_cc_half_supply() { #[test] fn test_share_to_cc_non_existent_token_id() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 999.into(); // Assuming 999 does not exist let share: u256 = 50 * CC_DECIMALS_MULTIPLIER / 100; // 50% @@ -177,7 +162,7 @@ fn test_share_to_cc_non_existent_token_id() { #[test] fn test_share_to_cc_zero_cc_supply() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1; let share: u256 = 1000.into(); @@ -189,7 +174,7 @@ fn test_share_to_cc_zero_cc_supply() { #[test] fn test_cc_to_share_zero_cc_value() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1; let cc_value: u256 = 0.into(); @@ -199,7 +184,7 @@ fn test_cc_to_share_zero_cc_value() { #[test] fn test_cc_to_share_equal_to_supply() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1; @@ -212,7 +197,7 @@ fn test_cc_to_share_equal_to_supply() { #[test] fn test_cc_to_share_half_supply() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1; @@ -226,7 +211,7 @@ fn test_cc_to_share_half_supply() { #[test] #[should_panic(expected: 'CC supply of vintage is 0')] fn test_cc_to_share_zero_cc_supply() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1; @@ -242,7 +227,7 @@ fn test_cc_to_share_zero_cc_supply() { #[test] #[should_panic(expected: 'CC supply of vintage is 0')] fn test_cc_to_share_non_existent_token_id() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let cc_value: u256 = 100000.into(); vintages.cc_to_share(cc_value, 999); @@ -251,7 +236,7 @@ fn test_cc_to_share_non_existent_token_id() { #[test] #[should_panic(expected: 'Share value exceeds 100%')] fn test_cc_to_share_revert_exceeds_supply() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1; @@ -265,7 +250,7 @@ fn test_cc_to_share_revert_exceeds_supply() { fn test_set_vintages() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); let yearly_absorptions = get_mock_absorptions(); start_cheat_caller_address(project_address, owner_address); @@ -312,7 +297,7 @@ fn test_set_vintages() { #[test] #[should_panic(expected: 'Caller does not have role')] fn test_set_vintages_without_owner_role() { - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); let yearly_absorptions = get_mock_absorptions(); let starting_year = 2024; let vintages = IVintageDispatcher { contract_address: project_address }; @@ -323,7 +308,7 @@ fn test_set_vintages_without_owner_role() { #[test] fn test_get_carbon_vintage() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let cc_vintages = vintages.get_cc_vintages(); @@ -343,7 +328,7 @@ fn test_get_carbon_vintage() { /// get_initial_cc_supply #[test] fn test_get_initial_cc_supply() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; // initial supply should be equal to supply before any rebases @@ -381,7 +366,7 @@ fn test_get_initial_cc_supply() { #[test] fn test_get_carbon_vintage_non_existent_token_id() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 999.into(); // Assuming 999 does not exist @@ -396,7 +381,7 @@ fn test_get_carbon_vintage_non_existent_token_id() { #[test] fn test_get_cc_decimals() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let cc_decimals = vintages.get_cc_decimals(); @@ -407,7 +392,7 @@ fn test_get_cc_decimals() { #[test] fn test_update_vintage_status_valid() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1; @@ -428,7 +413,7 @@ fn test_update_vintage_status_valid() { #[test] #[should_panic(expected: 'Invalid status')] fn test_update_vintage_status_invalid() { - let (project_address, _) = default_setup_and_deploy(); + let project_address = default_setup_and_deploy(); let vintages = IVintageDispatcher { contract_address: project_address }; let token_id: u256 = 1; @@ -439,7 +424,7 @@ fn test_update_vintage_status_invalid() { #[test] #[should_panic(expected: 'Caller does not have role')] fn test_update_vintage_status_without_owner_role() { - let (project_address, _) = deploy_project(); + let project_address = deploy_project(); let user_address: ContractAddress = contract_address_const::<'USER'>(); let vintages = IVintageDispatcher { contract_address: project_address }; @@ -451,7 +436,7 @@ fn test_update_vintage_status_without_owner_role() { // #[test] todo, what do we expect here? // fn test_update_vintage_status_non_existent_token_id() { -// let (project_address, _) = default_setup_and_deploy(); +// let project_address = default_setup_and_deploy(); // // let token_id: u64 = 999; // Assuming 999 does not exist // let new_status: u8 = 2; @@ -464,9 +449,9 @@ fn test_update_vintage_status_without_owner_role() { fn test_rebase_half_supply() { let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); - let (project_address, _) = default_setup_and_deploy(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); + let project_address = default_setup_and_deploy(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); let vintages = IVintageDispatcher { contract_address: project_address }; let project = IProjectDispatcher { contract_address: project_address }; diff --git a/tests/tests_lib.cairo b/tests/tests_lib.cairo index 5aec33c..f7ed3d0 100644 --- a/tests/tests_lib.cairo +++ b/tests/tests_lib.cairo @@ -99,7 +99,7 @@ fn share_to_buy_amount(minter_address: ContractAddress, share: u256) -> u256 { /// Deploy and setup functions /// -fn deploy_project() -> (ContractAddress, EventSpy) { +fn deploy_project() -> ContractAddress { let contract = snf::declare("Project").expect('Declaration failed'); let starting_year: u64 = 2024; let number_of_years: u64 = 20; @@ -107,9 +107,8 @@ fn deploy_project() -> (ContractAddress, EventSpy) { contract_address_const::<'OWNER'>().into(), starting_year.into(), number_of_years.into() ]; let (contract_address, _) = contract.deploy(@calldata).expect('Deployment failed'); - let mut spy = spy_events(); - (contract_address, spy) + contract_address } fn setup_project( @@ -124,15 +123,15 @@ fn setup_project( vintages.set_project_carbon(project_carbon); } -fn default_setup_and_deploy() -> (ContractAddress, EventSpy) { - let (project_address, spy) = deploy_project(); +fn default_setup_and_deploy() -> ContractAddress { + let project_address = deploy_project(); let yearly_absorptions: Span = get_mock_absorptions(); setup_project(project_address, 8000000000, yearly_absorptions); - (project_address, spy) + project_address } /// Deploys the offsetter contract. -fn deploy_offsetter(project_address: ContractAddress) -> (ContractAddress, EventSpy) { +fn deploy_offsetter(project_address: ContractAddress) -> ContractAddress { let contract = snf::declare("Offsetter").expect('Declaration failed'); let owner: ContractAddress = contract_address_const::<'OWNER'>(); let mut calldata: Array = array![]; @@ -141,15 +140,13 @@ fn deploy_offsetter(project_address: ContractAddress) -> (ContractAddress, Event let (contract_address, _) = contract.deploy(@calldata).expect('Deployment failed'); - let mut spy = spy_events(); - - (contract_address, spy) + contract_address } /// Deploys a minter contract. fn deploy_minter( project_address: ContractAddress, payment_address: ContractAddress -) -> (ContractAddress, EventSpy) { +) -> ContractAddress { let contract = snf::declare("Minter").expect('Declaration failed'); let owner: ContractAddress = contract_address_const::<'OWNER'>(); let public_sale: bool = true; @@ -167,12 +164,11 @@ fn deploy_minter( ]; let (contract_address, _) = contract.deploy(@calldata).expect('Deployment failed'); - let mut spy = spy_events(); - (contract_address, spy) + contract_address } /// Deploy erc20 contract. -fn deploy_erc20() -> (ContractAddress, EventSpy) { +fn deploy_erc20() -> ContractAddress { let contract = snf::declare("USDCarb").expect('Declaration failed'); let owner: ContractAddress = contract_address_const::<'OWNER'>(); let mut calldata: Array = array![]; @@ -180,15 +176,13 @@ fn deploy_erc20() -> (ContractAddress, EventSpy) { calldata.append(owner.into()); let (contract_address, _) = contract.deploy(@calldata).expect('Deployment failed'); - let mut spy = spy_events(); - - (contract_address, spy) + contract_address } -fn fuzzing_setup(cc_supply: u128) -> (ContractAddress, ContractAddress, ContractAddress, EventSpy) { - let (project_address, spy) = deploy_project(); - let (erc20_address, _) = deploy_erc20(); - let (minter_address, _) = deploy_minter(project_address, erc20_address); +fn fuzzing_setup(cc_supply: u128) -> (ContractAddress, ContractAddress, ContractAddress) { + let project_address = deploy_project(); + let erc20_address = deploy_erc20(); + let minter_address = deploy_minter(project_address, erc20_address); // Tests are done on a single vintage, thus the absorptions are the same let yearly_absorptions: Span = array![ @@ -215,7 +209,7 @@ fn fuzzing_setup(cc_supply: u128) -> (ContractAddress, ContractAddress, Contract ] .span(); setup_project(project_address, 8000000000, yearly_absorptions); - (project_address, minter_address, erc20_address, spy) + (project_address, minter_address, erc20_address) } /// Utility function to buy a share of the total supply. @@ -246,7 +240,7 @@ fn buy_utils( start_cheat_caller_address(erc20_address, minter_address); // [Prank] Use caller (usually user) as caller for the Minter contract start_cheat_caller_address(minter_address, caller_address); - minter.public_buy(amount_to_buy, false); + minter.public_buy(amount_to_buy); stop_cheat_caller_address(minter_address); stop_cheat_caller_address(erc20_address); @@ -280,7 +274,7 @@ fn perform_fuzzed_transfer( let owner_address: ContractAddress = contract_address_const::<'OWNER'>(); let user_address: ContractAddress = contract_address_const::<'USER'>(); let receiver_address: ContractAddress = contract_address_const::<'receiver'>(); - let (project_address, minter_address, _, _) = fuzzing_setup(supply); + let (project_address, minter_address, _) = fuzzing_setup(supply); let project = IProjectDispatcher { contract_address: project_address }; // Setup Roles for the contracts start_cheat_caller_address(project_address, owner_address); @@ -314,3 +308,18 @@ fn perform_fuzzed_transfer( let balance_receiver = project.balance_of(receiver_address, token_id); assert(equals_with_error(balance_receiver, 0, 10), 'Error balance receiver 2'); } + +fn helper_get_token_ids(project_address: ContractAddress) -> Span { + let vintages = IVintageDispatcher { contract_address: project_address }; + let num_vintages: usize = vintages.get_num_vintages(); + let mut tokens: Array = Default::default(); + let mut index = 0; + loop { + if index >= num_vintages { + break; + } + index += 1; + tokens.append(index.into()) + }; + tokens.span() +}