Skip to content

Commit a60b7e8

Browse files
Simplify contract event testing (#1638)
### What Adds a testutils struct `ContractEvents` to be returned by `env.events().all()` that now returns the XDR variants of the emitted contract events. To simplify testing, Events defined by `contractevents` can be converted to the XDR variant with `to_contract_event`. Additional partial eq impl were added to maintain backwards compatibility with users who test with existing methods. This will impact tests where users directly check the length of the events array, via `all().len()`, who will now need to write `all().events.len()`. Lastly, this also allows the debug information when testing events to display actual values, making it easier to determine what went wrong during test failures: Before: ``` left: Vec(Ok((Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM), Vec(Ok(Symbol(transfer)), Ok(Address(obj#35)), Ok(Address(obj#37))), Map(obj#43)))) right: Vec(Ok((Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM), Vec(Ok(Symbol(transfer)), Ok(Address(obj#11)), Ok(Address(obj#29))), Map(obj#53)))) ``` After: ``` left: [ContractEvent { ext: V0, contract_id: Some(ContractId(Hash(0000000000000000000000000000000000000000000000000000000000000001))), type_: Contract, body: V0(ContractEventV0 { topics: VecM([Symbol(ScSymbol(StringM(transfer))), Address(Contract(ContractId(Hash(0000000000000000000000000000000000000000000000000000000000000002)))), Address(Account(AccountId(PublicKeyTypeEd25519(Uint256(0000000000000000000000000000000000000000000000000000000000000003)))))]), data: Map(Some(ScMap(VecM([ScMapEntry { key: Symbol(ScSymbol(StringM(amount))), val: I128(Int128Parts { hi: 0, lo: 1 }) }, ScMapEntry { key: Symbol(ScSymbol(StringM(to_muxed_id))), val: U64(1) }])))) }) }] right: [ContractEvent { ext: V0, contract_id: Some(ContractId(Hash(0000000000000000000000000000000000000000000000000000000000000001))), type_: Contract, body: V0(ContractEventV0 { topics: VecM([Symbol(ScSymbol(StringM(transfer))), Address(Contract(ContractId(Hash(0000000000000000000000000000000000000000000000000000000000000002)))), Address(Account(AccountId(PublicKeyTypeEd25519(Uint256(0000000000000000000000000000000000000000000000000000000000000003)))))]), data: Map(Some(ScMap(VecM([ScMapEntry { key: Symbol(ScSymbol(StringM(amount))), val: I128(Int128Parts { hi: 0, lo: 0 }) }, ScMapEntry { key: Symbol(ScSymbol(StringM(to_muxed_id))), val: U64(1) }])))) }) }] ``` An example test: ```rust #[test] fn test_event() { let env = Env::default(); let contract_id = env.register(Contract, ()); let client = ContractClient::new(&env, &contract_id); let from = Address::generate(&env); let to = MuxedAddress::generate(&env); let amount = 1i128; client.transfer(&from, &to, &amount); assert_eq!( env.events().all(), std::vec![Transfer { from: from.clone(), to: to.address(), amount, to_muxed_id: to.id(), } .to_contract_event(&env, &contract_id)], ); } ``` ### Why Solves #1566. Makes it easier to validate events during tests. ### Known limitations None --------- Co-authored-by: Leigh <351529+leighmcculloch@users.noreply.github.com>
1 parent d89ee3b commit a60b7e8

File tree

9 files changed

+979
-333
lines changed

9 files changed

+979
-333
lines changed

soroban-sdk/src/events.rs

Lines changed: 31 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,31 @@ impl Debug for Events {
5858
}
5959
}
6060

61+
#[cfg(any(test, feature = "testutils"))]
62+
use crate::{testutils, xdr, FromVal};
63+
6164
pub trait Event {
6265
fn topics(&self, env: &Env) -> Vec<Val>;
6366
fn data(&self, env: &Env) -> Val;
6467

6568
fn publish(&self, env: &Env) {
6669
env.events().publish_event(self);
6770
}
71+
72+
/// Convert this event and the given contract_id into a [`xdr::ContractEvent`] object.
73+
/// Used to compare Events to emitted events in tests.
74+
#[cfg(any(test, feature = "testutils"))]
75+
fn to_xdr(&self, env: &Env, contract_id: &crate::Address) -> xdr::ContractEvent {
76+
xdr::ContractEvent {
77+
ext: xdr::ExtensionPoint::V0,
78+
type_: xdr::ContractEventType::Contract,
79+
contract_id: Some(contract_id.contract_id()),
80+
body: xdr::ContractEventBody::V0(xdr::ContractEventV0 {
81+
topics: self.topics(env).into(),
82+
data: xdr::ScVal::from_val(env, &self.data(env)),
83+
}),
84+
}
85+
}
6886
}
6987

7088
pub trait Topics: IntoVal<Env, Vec<Val>> {}
@@ -116,39 +134,29 @@ impl Events {
116134
}
117135
}
118136

119-
#[cfg(any(test, feature = "testutils"))]
120-
use crate::{testutils, xdr, Address, TryIntoVal};
121-
122137
#[cfg(any(test, feature = "testutils"))]
123138
#[cfg_attr(feature = "docs", doc(cfg(feature = "testutils")))]
124139
impl testutils::Events for Events {
125-
fn all(&self) -> Vec<(crate::Address, Vec<Val>, Val)> {
140+
fn all(&self) -> testutils::ContractEvents {
126141
let env = self.env();
127-
let mut vec = Vec::new(env);
128-
self.env()
142+
let vec: std::vec::Vec<xdr::ContractEvent> = self
143+
.env()
129144
.host()
130145
.get_events()
131146
.unwrap()
132147
.0
133148
.into_iter()
134-
.for_each(|e| {
135-
if e.failed_call {
136-
return;
137-
}
138-
if let xdr::ContractEvent {
139-
type_: xdr::ContractEventType::Contract,
140-
contract_id: Some(contract_id),
141-
body: xdr::ContractEventBody::V0(xdr::ContractEventV0 { topics, data }),
142-
..
143-
} = e.event
149+
.filter_map(|e| {
150+
if !e.failed_call
151+
&& e.event.type_ == xdr::ContractEventType::Contract
152+
&& e.event.contract_id.is_some()
144153
{
145-
vec.push_back((
146-
Address::from_contract_id(env, contract_id.0 .0),
147-
topics.try_into_val(env).unwrap(),
148-
data.try_into_val(env).unwrap(),
149-
))
154+
Some(e.event)
155+
} else {
156+
None
150157
}
151-
});
152-
vec
158+
})
159+
.collect();
160+
testutils::ContractEvents::new(&env, vec)
153161
}
154162
}

0 commit comments

Comments
 (0)