Skip to content

Commit 7273a3f

Browse files
authored
feat: Upgrade stellar crates and read events from specs (#371)
* chore: Upgrade stellar crates and read events from specs * fix: Moving helpers funtions to helpers file * fix: Fixing issue with compare_vec csv * fix: Removing unnecessary function * fix: Fixing issue decoding event identifier * test: Adding integration test case * fix: Removing print line * test: Extending test coverage * fix: Improving logic for extracting stellar values * test: Adding test cases for csv compare_vec * docs: Adding docs info for stellar events * docs: Adding docs info * docs: Fixing docs info * docs: Fixing docs link
1 parent 6b26bb6 commit 7273a3f

File tree

16 files changed

+1952
-136
lines changed

16 files changed

+1952
-136
lines changed

Cargo.lock

Lines changed: 41 additions & 15 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,10 +48,10 @@ rust_decimal = "1.37.1"
4848
serde = { version = "1.0", features = ["derive"] }
4949
serde_json = "1.0"
5050
sha2 = "0.10.0"
51-
soroban-spec = "22.0.7"
52-
stellar-rpc-client = "22.0.0"
51+
soroban-spec = "23.0.2"
52+
stellar-rpc-client = "23.0.1"
5353
stellar-strkey = "0.0.13"
54-
stellar-xdr = "22.1.0"
54+
stellar-xdr = "23.0.0"
5555
sysinfo = "0.34.2"
5656
thiserror = "2.0.12"
5757
tokio = { version = "1.0", features = ["full"] }

docs/modules/ROOT/pages/index.adoc

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1075,6 +1075,9 @@ The monitor uses a structured JSON format with nested objects for template varia
10751075
| `*events.[index].args.[position]*`
10761076
| Event parameters by position
10771077

1078+
| `*events.[index].args.[param]*`
1079+
| Event parameters by name (only in case the contract supports event parameters name)
1080+
10781081
| `*functions.[index].args.[param]*`
10791082
| Function parameters by name
10801083
|===
@@ -1603,7 +1606,23 @@ When a Stellar parameter has `kind: "vec"`, its value can be either a CSV string
16031606

16041607
*Event Parameter Access (Stellar)*
16051608

1606-
Stellar event parameters are typically accessed by their numeric index as the base variable name (e.g., `0`, `1`, `2`). If an indexed event parameter is itself a complex type (like an array or map, represented as a JSON string), you can then apply the respective access methods:
1609+
Starting with Stellar Protocol 23, Soroban contracts can include event definitions in their contract specifications. This enables two ways to access event parameters:
1610+
1611+
*Access by Name (Protocol 23+):*
1612+
When a contract specification includes event definitions, you can access event parameters by their defined names:
1613+
1614+
* If an event has a parameter named `recipient` (kind: "Address"):
1615+
** `recipient == 'GBBD...ABCD'`
1616+
* If an event has a parameter named `amount` (kind: "U128"):
1617+
** `amount > 1000000`
1618+
* If an event has a parameter named `metadata` (kind: "Map") containing `'{"id": 123, "name": "Test"}'`:
1619+
** `metadata.id == 123`
1620+
** `metadata.name contains 'test'` (case-insensitive)
1621+
* If an event has a parameter named `items` (kind: "array") containing `'["alpha", "beta"]'`:
1622+
** `items contains 'beta'` (case-insensitive deep search)
1623+
1624+
*Access by Index (Legacy):*
1625+
For contracts without event definitions in their specification, or when working with older contracts, event parameters can be accessed by their numeric index (e.g., `0`, `1`, `2`):
16071626

16081627
* If event parameter `0` (kind: "Map") is `'{"id": 123, "name": "Test"}'`:
16091628
** `0.id == 123`
@@ -1613,6 +1632,11 @@ Stellar event parameters are typically accessed by their numeric index as the ba
16131632
** `1[1].val == 'Beta'` (case-insensitive)
16141633
** `1 contains 'beta'` (case-insensitive deep search)
16151634

1635+
[TIP]
1636+
====
1637+
Named access is recommended when available as it makes expressions more readable and maintainable. The monitor will automatically use named parameters if they're available in the contract specification, otherwise it falls back to indexed access.
1638+
====
1639+
16161640
===== EVM Examples
16171641

16181642
These examples assume common EVM event parameters or transaction fields.
@@ -1738,6 +1762,16 @@ Assume event parameter `0` is `12345` (u64), `1` (kind: "array") is `'["Val1", "
17381762
"2.keyB < 1000"
17391763
----
17401764

1765+
Now, after Stellar Protocol 23 we can access to Event parameters by name (first, make sure the contract supports the feature)
1766+
1767+
----
1768+
"0 > 10000"
1769+
"param_name[0] == 'val1'"
1770+
"param_name contains 'val2'"
1771+
"param_name.keyA == 'DATAX'"
1772+
"param_name.keyB < 1000"
1773+
----
1774+
17411775
[NOTE]
17421776
====
17431777
With SEP-48 support, Stellar functions can now reference parameters by name (e.g., `amount > 1000`) instead of position (e.g., `2 > 1000`). Events still use indexed parameters until SEP-48 support is added for events.

docs/modules/ROOT/pages/quickstart.adoc

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,7 @@ cp examples/config/filters/stellar_filter_block_number.sh config/filters/stellar
602602
* The `contract_spec` field is optional for Stellar contracts. If not provided, the monitor automatically fetches the contract's SEP-48 interface from the chain
603603
* You can explore Stellar contract interfaces using the link:https://lab.stellar.org/smart-contracts/contract-explorer[Stellar Contract Explorer^]
604604
* The expression `"out_min > 1000000000"` monitors swaps with minimum output over 1 billion tokens
605+
* Now, you can also filter by parameters event's name (Stellar Protocol 23 has introduced this new feature). See this xref:index.adoc#stellar_specifics[section] for more details
605606
====
606607

607608
==== Step 3: Notification Setup
@@ -636,6 +637,12 @@ cp examples/config/triggers/slack_notifications.json config/triggers/slack_notif
636637
}
637638
----
638639

640+
* If you want to include event details in the notification message body, you can also access event parameters by name. Here's an example:
641+
[source,bash]
642+
----
643+
"body": "${monitor.name} triggered because of a large swap from ${events.0.args.from} tokens | https://stellar.expert/explorer/public/tx/${transaction.hash}"
644+
----
645+
639646
==== Step 4: Run the Monitor
640647

641648
**Local Deployment:**

src/models/blockchain/stellar/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ mod transaction;
1212
pub use block::{Block as StellarBlock, LedgerInfo as StellarLedgerInfo};
1313
pub use event::Event as StellarEvent;
1414
pub use monitor::{
15+
ContractEvent as StellarContractEvent, ContractEventParam as StellarContractEventParam,
1516
ContractFunction as StellarContractFunction, ContractInput as StellarContractInput,
1617
ContractSpec as StellarContractSpec, DecodedParamEntry as StellarDecodedParamEntry,
18+
EventParamLocation as StellarEventParamLocation,
1719
FormattedContractSpec as StellarFormattedContractSpec, MatchArguments as StellarMatchArguments,
1820
MatchParamEntry as StellarMatchParamEntry, MatchParamsMap as StellarMatchParamsMap,
1921
MonitorMatch as StellarMonitorMatch, ParsedOperationResult as StellarParsedOperationResult,

src/models/blockchain/stellar/monitor.rs

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@ use stellar_xdr::curr::ScSpecEntry;
77
use crate::{
88
models::{MatchConditions, Monitor, StellarBlock, StellarTransaction},
99
services::filter::stellar_helpers::{
10-
get_contract_spec_functions, get_contract_spec_with_function_input_parameters,
10+
get_contract_spec_events, get_contract_spec_functions,
11+
get_contract_spec_with_event_parameters, get_contract_spec_with_function_input_parameters,
1112
},
1213
};
1314

@@ -172,14 +173,21 @@ impl std::ops::Deref for ContractSpec {
172173
pub struct FormattedContractSpec {
173174
/// List of callable functions defined in the contract
174175
pub functions: Vec<ContractFunction>,
176+
177+
/// List of events defined in the contract
178+
pub events: Vec<ContractEvent>,
175179
}
176180

177181
impl From<ContractSpec> for FormattedContractSpec {
178182
fn from(spec: ContractSpec) -> Self {
179-
let functions =
180-
get_contract_spec_with_function_input_parameters(get_contract_spec_functions(spec.0));
183+
let functions = get_contract_spec_with_function_input_parameters(
184+
get_contract_spec_functions(spec.0.clone()),
185+
);
181186

182-
FormattedContractSpec { functions }
187+
let events =
188+
get_contract_spec_with_event_parameters(get_contract_spec_events(spec.0.clone()));
189+
190+
FormattedContractSpec { functions, events }
183191
}
184192
}
185193

@@ -233,6 +241,53 @@ pub struct ContractInput {
233241
pub kind: String,
234242
}
235243

244+
/// Event parameter location (indexed or data)
245+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
246+
pub enum EventParamLocation {
247+
/// Parameter is indexed (in topics)
248+
#[default]
249+
Indexed,
250+
/// Parameter is in event data
251+
Data,
252+
}
253+
254+
/// Event parameter specification for a Stellar contract event
255+
///
256+
/// Describes a single parameter in a contract event, including its name,
257+
/// type, and whether it's indexed or in the event data.
258+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
259+
pub struct ContractEventParam {
260+
/// Parameter name as defined in the contract
261+
pub name: String,
262+
263+
/// Parameter type in Stellar's type system format
264+
pub kind: String,
265+
266+
/// Whether this parameter is indexed or in data
267+
pub location: EventParamLocation,
268+
}
269+
270+
/// Event definition within a Stellar contract specification
271+
///
272+
/// Represents an event that can be emitted by a Stellar smart contract,
273+
/// including its name and parameters. This is parsed from the contract's
274+
/// ScSpecEventV0 entries.
275+
#[derive(Debug, Clone, Deserialize, Serialize, PartialEq, Default)]
276+
pub struct ContractEvent {
277+
/// Name of the event as defined in the contract
278+
pub name: String,
279+
280+
/// Prefix topics that are emitted before indexed parameters
281+
/// These are metadata topics used to identify the event
282+
pub prefix_topics: Vec<String>,
283+
284+
/// Ordered list of parameters in the event
285+
pub params: Vec<ContractEventParam>,
286+
287+
/// Signature of the event
288+
pub signature: String,
289+
}
290+
236291
#[cfg(test)]
237292
mod tests {
238293
use super::*;

src/models/mod.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,11 @@ pub use blockchain::evm::{
2424
};
2525

2626
pub use blockchain::stellar::{
27-
StellarBlock, StellarContractFunction, StellarContractInput, StellarContractSpec,
28-
StellarDecodedParamEntry, StellarDecodedTransaction, StellarEvent,
29-
StellarFormattedContractSpec, StellarLedgerInfo, StellarMatchArguments, StellarMatchParamEntry,
30-
StellarMatchParamsMap, StellarMonitorMatch, StellarParsedOperationResult, StellarTransaction,
31-
StellarTransactionInfo,
27+
StellarBlock, StellarContractEvent, StellarContractEventParam, StellarContractFunction,
28+
StellarContractInput, StellarContractSpec, StellarDecodedParamEntry, StellarDecodedTransaction,
29+
StellarEvent, StellarEventParamLocation, StellarFormattedContractSpec, StellarLedgerInfo,
30+
StellarMatchArguments, StellarMatchParamEntry, StellarMatchParamsMap, StellarMonitorMatch,
31+
StellarParsedOperationResult, StellarTransaction, StellarTransactionInfo,
3232
};
3333

3434
// Re-export core types

src/services/blockchain/clients/stellar/client.rs

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
66
use anyhow::Context;
77
use async_trait::async_trait;
8+
use base64::{engine::general_purpose::STANDARD as BASE64_STANDARD, Engine as _};
89
use serde_json::json;
910
use std::marker::PhantomData;
1011
use stellar_xdr::curr::{Limits, WriteXdr};
@@ -642,14 +643,16 @@ impl<T: Send + Sync + Clone + BlockchainTransport> BlockChainClient for StellarC
642643
let contract_instance_ledger_key = get_contract_instance_ledger_key(contract_id)
643644
.map_err(|e| anyhow::anyhow!("Failed to get contract instance ledger key: {}", e))?;
644645

645-
let contract_instance_ledger_key_xdr = contract_instance_ledger_key
646-
.to_xdr_base64(Limits::none())
646+
let contract_instance_ledger_key_xdr_bytes = contract_instance_ledger_key
647+
.to_xdr(Limits::none())
647648
.map_err(|e| {
648649
anyhow::anyhow!(
649650
"Failed to convert contract instance ledger key to XDR: {}",
650651
e
651652
)
652653
})?;
654+
let contract_instance_ledger_key_xdr =
655+
BASE64_STANDARD.encode(&contract_instance_ledger_key_xdr_bytes);
653656

654657
let params = json!({
655658
"keys": [contract_instance_ledger_key_xdr],
@@ -675,11 +678,13 @@ impl<T: Send + Sync + Clone + BlockchainTransport> BlockChainClient for StellarC
675678
let contract_code_ledger_key = get_contract_code_ledger_key(wasm_hash.as_str())
676679
.map_err(|e| anyhow::anyhow!("Failed to get contract code ledger key: {}", e))?;
677680

678-
let contract_code_ledger_key_xdr = contract_code_ledger_key
679-
.to_xdr_base64(Limits::none())
681+
let contract_code_ledger_key_xdr_bytes = contract_code_ledger_key
682+
.to_xdr(Limits::none())
680683
.map_err(|e| {
681-
anyhow::anyhow!("Failed to convert contract code ledger key to XDR: {}", e)
682-
})?;
684+
anyhow::anyhow!("Failed to convert contract code ledger key to XDR: {}", e)
685+
})?;
686+
let contract_code_ledger_key_xdr =
687+
BASE64_STANDARD.encode(&contract_code_ledger_key_xdr_bytes);
683688

684689
let params = json!({
685690
"keys": [contract_code_ledger_key_xdr],

0 commit comments

Comments
 (0)