diff --git a/Cargo.lock b/Cargo.lock index d38cfba..f6338b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -543,7 +543,17 @@ checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "fungible-pausable-example" -version = "0.0.0" +version = "0.1.0" +dependencies = [ + "openzeppelin-fungible-token", + "openzeppelin-pausable", + "openzeppelin-pausable-macros", + "soroban-sdk", +] + +[[package]] +name = "fungible-token-interface-example" +version = "0.1.0" dependencies = [ "openzeppelin-fungible-token", "openzeppelin-pausable", @@ -811,14 +821,14 @@ checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" [[package]] name = "openzeppelin-fungible-token" -version = "0.0.0" +version = "0.1.0" dependencies = [ "soroban-sdk", ] [[package]] name = "openzeppelin-pausable" -version = "0.0.0" +version = "0.1.0" dependencies = [ "soroban-sdk", ] @@ -852,7 +862,7 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pausable-example" -version = "0.0.0" +version = "0.1.0" dependencies = [ "openzeppelin-pausable", "openzeppelin-pausable-macros", diff --git a/contracts/token/fungible/src/extensions/burnable/test.rs b/contracts/token/fungible/src/extensions/burnable/test.rs index 18d417b..35b9776 100644 --- a/contracts/token/fungible/src/extensions/burnable/test.rs +++ b/contracts/token/fungible/src/extensions/burnable/test.rs @@ -10,6 +10,7 @@ use crate::{ mintable::mint, }, storage::{allowance, approve, balance, total_supply}, + test::event_utils::EventAssertion, }; #[contract] @@ -26,6 +27,11 @@ fn burn_works() { burn(&e, &account, 50); assert_eq!(balance(&e, &account), 50); assert_eq!(total_supply(&e), 50); + + let event_assert = EventAssertion::new(&e, address.clone()); + event_assert.assert_event_count(2); + event_assert.assert_mint(&account, 100); + event_assert.assert_burn(&account, 50); }); } @@ -43,6 +49,12 @@ fn burn_with_allowance_works() { assert_eq!(balance(&e, &owner), 70); assert_eq!(balance(&e, &spender), 0); assert_eq!(total_supply(&e), 70); + + let event_assert = EventAssertion::new(&e, address.clone()); + event_assert.assert_event_count(3); + event_assert.assert_mint(&owner, 100); + event_assert.assert_approve(&owner, &spender, 30, 1000); + event_assert.assert_burn(&owner, 30); }); } diff --git a/contracts/token/fungible/src/extensions/mintable/test.rs b/contracts/token/fungible/src/extensions/mintable/test.rs index 79c0aee..c2870f6 100644 --- a/contracts/token/fungible/src/extensions/mintable/test.rs +++ b/contracts/token/fungible/src/extensions/mintable/test.rs @@ -7,6 +7,7 @@ use soroban_sdk::{contract, testutils::Address as _, Address, Env}; use crate::{ extensions::mintable::storage::mint, storage::{balance, total_supply}, + test::event_utils::EventAssertion, }; #[contract] @@ -15,11 +16,16 @@ struct MockContract; #[test] fn mint_works() { let e = Env::default(); + e.mock_all_auths(); let address = e.register(MockContract, ()); let account = Address::generate(&e); e.as_contract(&address, || { mint(&e, &account, 100); assert_eq!(balance(&e, &account), 100); assert_eq!(total_supply(&e), 100); + + let event_assert = EventAssertion::new(&e, address.clone()); + event_assert.assert_event_count(1); + event_assert.assert_mint(&account, 100); }); } diff --git a/contracts/token/fungible/src/test.rs b/contracts/token/fungible/src/test.rs index 54a7638..965be0e 100644 --- a/contracts/token/fungible/src/test.rs +++ b/contracts/token/fungible/src/test.rs @@ -19,6 +19,9 @@ use crate::{ }, }; +pub mod event_utils; +use event_utils::EventAssertion; + #[contract] struct MockContract; @@ -208,8 +211,10 @@ fn transfer_works() { assert_eq!(balance(&e, &from), 50); assert_eq!(balance(&e, &recipient), 50); - let events = e.events().all(); - assert_eq!(events.len(), 2); + let event_assert = EventAssertion::new(&e, address.clone()); + event_assert.assert_event_count(2); + event_assert.assert_mint(&from, 100); + event_assert.assert_transfer(&from, &recipient, 50); }); } @@ -258,6 +263,12 @@ fn approve_and_transfer_from() { let updated_allowance = allowance(&e, &owner, &spender); assert_eq!(updated_allowance, 20); + + let event_assert = EventAssertion::new(&e, address.clone()); + event_assert.assert_event_count(3); + event_assert.assert_mint(&owner, 100); + event_assert.assert_approve(&owner, &spender, 50, 1000); + event_assert.assert_transfer(&owner, &recipient, 30); }); } diff --git a/contracts/token/fungible/src/test/event_utils.rs b/contracts/token/fungible/src/test/event_utils.rs new file mode 100644 index 0000000..3a68957 --- /dev/null +++ b/contracts/token/fungible/src/test/event_utils.rs @@ -0,0 +1,138 @@ +use soroban_sdk::{symbol_short, testutils::Events, Address, Env, IntoVal, Symbol, Val, Vec}; + +pub struct EventAssertion<'a> { + env: &'a Env, + contract: Address, +} + +impl<'a> EventAssertion<'a> { + pub fn new(env: &'a Env, contract: Address) -> Self { + Self { env, contract } + } + + pub fn assert_transfer(&self, from: &Address, to: &Address, amount: i128) { + let events = self.env.events().all(); + let transfer_event = events.iter().find(|e| { + let topics: Vec = e.1.clone(); + let topic_symbol: Symbol = topics.first().unwrap().into_val(self.env); + topic_symbol == symbol_short!("transfer") + }); + + assert!(transfer_event.is_some(), "Transfer event not found in event log"); + + let (contract, topics, data) = transfer_event.unwrap(); + assert_eq!(contract, self.contract, "Event from wrong contract"); + + let topics: Vec = topics.clone(); + assert_eq!(topics.len(), 3, "Transfer event should have 3 topics"); + + let topic_symbol: Symbol = topics.get_unchecked(0).into_val(self.env); + assert_eq!(topic_symbol, symbol_short!("transfer")); + + let event_from: Address = topics.get_unchecked(1).into_val(self.env); + let event_to: Address = topics.get_unchecked(2).into_val(self.env); + let event_amount: i128 = data.into_val(self.env); + + assert_eq!(&event_from, from, "Transfer event has wrong from address"); + assert_eq!(&event_to, to, "Transfer event has wrong to address"); + assert_eq!(event_amount, amount, "Transfer event has wrong amount"); + } + + pub fn assert_mint(&self, to: &Address, amount: i128) { + let events = self.env.events().all(); + let mint_event = events.iter().find(|e| { + let topics: Vec = e.1.clone(); + let topic_symbol: Symbol = topics.first().unwrap().into_val(self.env); + topic_symbol == symbol_short!("mint") + }); + + assert!(mint_event.is_some(), "Mint event not found in event log"); + + let (contract, topics, data) = mint_event.unwrap(); + assert_eq!(contract, self.contract, "Event from wrong contract"); + + let topics: Vec = topics.clone(); + assert_eq!(topics.len(), 2, "Mint event should have 2 topics"); + + let topic_symbol: Symbol = topics.get_unchecked(0).into_val(self.env); + assert_eq!(topic_symbol, symbol_short!("mint")); + + let event_to: Address = topics.get_unchecked(1).into_val(self.env); + let event_amount: i128 = data.into_val(self.env); + + assert_eq!(&event_to, to, "Mint event has wrong to address"); + assert_eq!(event_amount, amount, "Mint event has wrong amount"); + } + + pub fn assert_burn(&self, from: &Address, amount: i128) { + let events = self.env.events().all(); + let burn_event = events.iter().find(|e| { + let topics: Vec = e.1.clone(); + let topic_symbol: Symbol = topics.first().unwrap().into_val(self.env); + topic_symbol == symbol_short!("burn") + }); + + assert!(burn_event.is_some(), "Burn event not found in event log"); + + let (contract, topics, data) = burn_event.unwrap(); + assert_eq!(contract, self.contract, "Event from wrong contract"); + + let topics: Vec = topics.clone(); + assert_eq!(topics.len(), 2, "Burn event should have 2 topics"); + + let topic_symbol: Symbol = topics.get_unchecked(0).into_val(self.env); + assert_eq!(topic_symbol, symbol_short!("burn")); + + let event_from: Address = topics.get_unchecked(1).into_val(self.env); + let event_amount: i128 = data.into_val(self.env); + + assert_eq!(&event_from, from, "Burn event has wrong from address"); + assert_eq!(event_amount, amount, "Burn event has wrong amount"); + } + + pub fn assert_event_count(&self, expected: usize) { + let events = self.env.events().all(); + assert_eq!( + events.len() as usize, + expected, + "Expected {} events, found {}", + expected, + events.len() + ); + } + + pub fn assert_approve( + &self, + owner: &Address, + spender: &Address, + amount: i128, + live_until_ledger: u32, + ) { + let events = self.env.events().all(); + let approve_event = events.iter().find(|e| { + let topics: Vec = e.1.clone(); + let topic_symbol: Symbol = topics.first().unwrap().into_val(self.env); + topic_symbol == symbol_short!("approve") + }); + + assert!(approve_event.is_some(), "Approve event not found in event log"); + + let (contract, topics, data) = approve_event.unwrap(); + assert_eq!(contract, self.contract, "Event from wrong contract"); + + let topics: Vec = topics.clone(); + assert_eq!(topics.len(), 3, "Approve event should have 3 topics"); + + let topic_symbol: Symbol = topics.get_unchecked(0).into_val(self.env); + assert_eq!(topic_symbol, symbol_short!("approve")); + + let event_owner: Address = topics.get_unchecked(1).into_val(self.env); + let event_spender: Address = topics.get_unchecked(2).into_val(self.env); + let event_data: (i128, u32) = data.into_val(self.env); + + assert_eq!(&event_owner, owner, "Approve event has wrong owner address"); + assert_eq!(&event_spender, spender, "Approve event has wrong spender address"); + assert_eq!(event_data.0, amount, "Approve event has wrong amount"); + assert_eq!(event_data.1, live_until_ledger, "Approve event has wrong live_until_ledger"); + } +} diff --git a/contracts/token/fungible/test_snapshots/test/burn_works.1.json b/contracts/token/fungible/test_snapshots/test/burn_works.1.json index b14185c..742e011 100644 --- a/contracts/token/fungible/test_snapshots/test/burn_works.1.json +++ b/contracts/token/fungible/test_snapshots/test/burn_works.1.json @@ -5,7 +5,21 @@ }, "auth": [ [], - [] + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "", + "args": [] + } + }, + "sub_invocations": [] + } + ] + ] ], "ledger": { "protocol_version": 22, @@ -113,6 +127,39 @@ 120960 ] ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], [ { "contract_code": { @@ -136,5 +183,58 @@ ] ] }, - "events": [] + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 100 + } + } + } + } + }, + "failed_call": false + }, + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "burn" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 50 + } + } + } + } + }, + "failed_call": false + } + ] } \ No newline at end of file diff --git a/contracts/token/fungible/test_snapshots/test/mint_works.1.json b/contracts/token/fungible/test_snapshots/test/mint_works.1.json index 7cea01f..5908fcf 100644 --- a/contracts/token/fungible/test_snapshots/test/mint_works.1.json +++ b/contracts/token/fungible/test_snapshots/test/mint_works.1.json @@ -136,5 +136,32 @@ ] ] }, - "events": [] + "events": [ + { + "event": { + "ext": "v0", + "contract_id": "0000000000000000000000000000000000000000000000000000000000000001", + "type_": "contract", + "body": { + "v0": { + "topics": [ + { + "symbol": "mint" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ], + "data": { + "i128": { + "hi": 0, + "lo": 100 + } + } + } + } + }, + "failed_call": false + } + ] } \ No newline at end of file