From 67553a9da78cb39aa7be73689ac04b3d94988bf1 Mon Sep 17 00:00:00 2001 From: tinkerer-shubh Date: Sat, 15 Feb 2025 23:48:48 +0530 Subject: [PATCH 1/4] feat(test): Add event testing utilities for fungible token --- .../token/fungible/src/test/event_utils.rs | 108 ++++++++++++++++++ 1 file changed, 108 insertions(+) create mode 100644 contracts/token/fungible/src/test/event_utils.rs 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..45b7a4d --- /dev/null +++ b/contracts/token/fungible/src/test/event_utils.rs @@ -0,0 +1,108 @@ +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() + ); + } +} \ No newline at end of file From 890d936f5135245258c6dadb4e3311bfaf3267f1 Mon Sep 17 00:00:00 2001 From: tinkerer-shubh Date: Sat, 15 Feb 2025 23:48:54 +0530 Subject: [PATCH 2/4] test: Add event emission assertions for fungible token tests --- contracts/token/fungible/src/test.rs | 48 +++++++- .../test_snapshots/test/burn_works.1.json | 104 +++++++++++++++++- .../test_snapshots/test/mint_works.1.json | 29 ++++- 3 files changed, 176 insertions(+), 5 deletions(-) diff --git a/contracts/token/fungible/src/test.rs b/contracts/token/fungible/src/test.rs index 54a7638..62c7d68 100644 --- a/contracts/token/fungible/src/test.rs +++ b/contracts/token/fungible/src/test.rs @@ -13,12 +13,16 @@ use soroban_sdk::{ use crate::{ extensions::mintable::mint, + extensions::burnable::burn, storage::{ allowance, approve, balance, set_allowance, spend_allowance, total_supply, transfer, transfer_from, update, StorageKey, BALANCE_EXTEND_AMOUNT, INSTANCE_EXTEND_AMOUNT, }, }; +mod event_utils; +use event_utils::EventAssertion; + #[contract] struct MockContract; @@ -208,8 +212,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); }); } @@ -374,3 +380,41 @@ fn update_with_insufficient_balance_panics() { update(&e, Some(&from), Some(&to), 100); }); } + +#[test] +fn mint_works() { + let e = Env::default(); + e.mock_all_auths(); + let address = e.register(MockContract, ()); + let to = Address::generate(&e); + + e.as_contract(&address, || { + mint(&e, &to, 100); + assert_eq!(balance(&e, &to), 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(&to, 100); + }); +} + +#[test] +fn burn_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); + 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); + }); +} 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 From bda1bf108e55e7aa2c627b6c36ce3fe71006c98c Mon Sep 17 00:00:00 2001 From: tinkerer-shubh Date: Tue, 18 Feb 2025 01:16:25 +0530 Subject: [PATCH 3/4] feat(token): improve event testing utilities and remove test duplication --- .../fungible/src/extensions/burnable/test.rs | 12 +++++ .../fungible/src/extensions/mintable/test.rs | 9 ++++ contracts/token/fungible/src/test.rs | 47 +++---------------- .../token/fungible/src/test/event_utils.rs | 29 ++++++++++++ 4 files changed, 57 insertions(+), 40 deletions(-) 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..e254c6f 100644 --- a/contracts/token/fungible/src/extensions/mintable/test.rs +++ b/contracts/token/fungible/src/extensions/mintable/test.rs @@ -7,19 +7,28 @@ use soroban_sdk::{contract, testutils::Address as _, Address, Env}; use crate::{ extensions::mintable::storage::mint, storage::{balance, total_supply}, + test::event_utils::EventAssertion, }; #[contract] struct MockContract; +// Note: Basic mint_works test is covered in the main fungible token tests +// This file is reserved for mintable extension-specific tests + #[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 62c7d68..965be0e 100644 --- a/contracts/token/fungible/src/test.rs +++ b/contracts/token/fungible/src/test.rs @@ -13,14 +13,13 @@ use soroban_sdk::{ use crate::{ extensions::mintable::mint, - extensions::burnable::burn, storage::{ allowance, approve, balance, set_allowance, spend_allowance, total_supply, transfer, transfer_from, update, StorageKey, BALANCE_EXTEND_AMOUNT, INSTANCE_EXTEND_AMOUNT, }, }; -mod event_utils; +pub mod event_utils; use event_utils::EventAssertion; #[contract] @@ -264,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); }); } @@ -380,41 +385,3 @@ fn update_with_insufficient_balance_panics() { update(&e, Some(&from), Some(&to), 100); }); } - -#[test] -fn mint_works() { - let e = Env::default(); - e.mock_all_auths(); - let address = e.register(MockContract, ()); - let to = Address::generate(&e); - - e.as_contract(&address, || { - mint(&e, &to, 100); - assert_eq!(balance(&e, &to), 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(&to, 100); - }); -} - -#[test] -fn burn_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); - 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); - }); -} diff --git a/contracts/token/fungible/src/test/event_utils.rs b/contracts/token/fungible/src/test/event_utils.rs index 45b7a4d..1ecbda7 100644 --- a/contracts/token/fungible/src/test/event_utils.rs +++ b/contracts/token/fungible/src/test/event_utils.rs @@ -105,4 +105,33 @@ impl<'a> EventAssertion<'a> { 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"); + } } \ No newline at end of file From c20556b330759f4e2e144ddb98c4da77abd39b1d Mon Sep 17 00:00:00 2001 From: tinkerer-shubh Date: Wed, 19 Feb 2025 20:54:58 +0530 Subject: [PATCH 4/4] fix: formatting updates --- Cargo.lock | 18 ++++++++++--- .../fungible/src/extensions/mintable/test.rs | 3 --- .../token/fungible/src/test/event_utils.rs | 27 ++++++++++--------- 3 files changed, 28 insertions(+), 20 deletions(-) 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/mintable/test.rs b/contracts/token/fungible/src/extensions/mintable/test.rs index e254c6f..c2870f6 100644 --- a/contracts/token/fungible/src/extensions/mintable/test.rs +++ b/contracts/token/fungible/src/extensions/mintable/test.rs @@ -13,9 +13,6 @@ use crate::{ #[contract] struct MockContract; -// Note: Basic mint_works test is covered in the main fungible token tests -// This file is reserved for mintable extension-specific tests - #[test] fn mint_works() { let e = Env::default(); diff --git a/contracts/token/fungible/src/test/event_utils.rs b/contracts/token/fungible/src/test/event_utils.rs index 1ecbda7..3a68957 100644 --- a/contracts/token/fungible/src/test/event_utils.rs +++ b/contracts/token/fungible/src/test/event_utils.rs @@ -1,6 +1,4 @@ -use soroban_sdk::{ - symbol_short, testutils::Events, Address, Env, IntoVal, Symbol, Val, Vec, -}; +use soroban_sdk::{symbol_short, testutils::Events, Address, Env, IntoVal, Symbol, Val, Vec}; pub struct EventAssertion<'a> { env: &'a Env, @@ -20,17 +18,14 @@ impl<'a> EventAssertion<'a> { topic_symbol == symbol_short!("transfer") }); - assert!( - transfer_event.is_some(), - "Transfer event not found in event log" - ); + 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")); @@ -58,7 +53,7 @@ impl<'a> EventAssertion<'a> { 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")); @@ -84,7 +79,7 @@ impl<'a> EventAssertion<'a> { 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")); @@ -106,7 +101,13 @@ impl<'a> EventAssertion<'a> { ); } - pub fn assert_approve(&self, owner: &Address, spender: &Address, amount: i128, live_until_ledger: u32) { + 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(); @@ -121,7 +122,7 @@ impl<'a> EventAssertion<'a> { 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")); @@ -134,4 +135,4 @@ impl<'a> EventAssertion<'a> { 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"); } -} \ No newline at end of file +}