Skip to content

Commit fbd5feb

Browse files
committed
added test + addressed most pr comments
1 parent 5bda25e commit fbd5feb

File tree

9 files changed

+200
-24
lines changed

9 files changed

+200
-24
lines changed

contracts/stellar-multicall/Cargo.toml

+3
Original file line numberDiff line numberDiff line change
@@ -15,5 +15,8 @@ stellar-axelar-std = { workspace = true }
1515
goldie = { workspace = true }
1616
soroban-sdk = { workspace = true, features = ["testutils"] }
1717

18+
[features]
19+
library = []
20+
1821
[lints]
1922
workspace = true

contracts/stellar-multicall/src/contract.rs

+16-16
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,31 @@
11
use soroban_sdk::{contract, contractimpl, Env, Val, Vec};
2-
use stellar_axelar_std::assert_some;
32

43
use crate::error::ContractError;
54
use crate::interface::MulticallInterface;
6-
use crate::types::MulticallData;
5+
use crate::types::FunctionCall;
76

87
#[contract]
98
pub struct Multicall;
109

1110
#[contractimpl]
12-
impl MulticallInterface for Multicall {
13-
fn multicall(env: Env, params: Vec<MulticallData>) -> Result<Vec<Val>, ContractError> {
14-
let mut results = Vec::new(&env);
11+
impl Multicall {
12+
pub fn __constructor(_env: &Env) {}
13+
}
1514

16-
for i in 0..params.len() {
17-
let param = assert_some!(params.get(i));
18-
let contract_address = &param.contract_address;
19-
let function = &param.function;
20-
let args = &param.args;
15+
#[contractimpl]
16+
impl MulticallInterface for Multicall {
17+
fn multicall(env: &Env, params: Vec<FunctionCall>) -> Result<Vec<Val>, ContractError> {
18+
let mut results = Vec::new(env);
2119

22-
let result: Result<Val, ContractError> =
23-
env.invoke_contract(contract_address, function, args.clone());
20+
for FunctionCall {
21+
contract,
22+
function,
23+
args,
24+
} in params.into_iter()
25+
{
26+
let result: Val = env.invoke_contract(&contract, &function, args.clone());
2427

25-
match result {
26-
Ok(val) => results.push_back(val),
27-
Err(_) => return Err(ContractError::MulticallFailed),
28-
}
28+
results.push_back(result);
2929
}
3030

3131
Ok(results)

contracts/stellar-multicall/src/error.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,5 @@ use soroban_sdk::contracterror;
44
#[derive(Debug, Eq, PartialEq)]
55
#[repr(u32)]
66
pub enum ContractError {
7-
MulticallFailed = 1,
7+
FunctionCallFailed = 1,
88
}

contracts/stellar-multicall/src/interface.rs

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
use soroban_sdk::{Env, Val, Vec};
1+
use soroban_sdk::{contractclient, Env, Val, Vec};
22

33
use crate::error::ContractError;
4-
use crate::types::MulticallData;
4+
use crate::types::FunctionCall;
55

6+
#[contractclient(name = "MulticallClient")]
67
pub trait MulticallInterface {
78
/// Executes an arbitrary list of contract calls and returns the results of all the calls.
89
///
@@ -14,5 +15,5 @@ pub trait MulticallInterface {
1415
///
1516
/// # Errors
1617
/// - [`ContractError::MulticallFailed`]: If any of the contract calls fail.
17-
fn multicall(env: Env, params: Vec<MulticallData>) -> Result<Vec<Val>, ContractError>;
18+
fn multicall(env: &Env, function_calls: Vec<FunctionCall>) -> Result<Vec<Val>, ContractError>;
1819
}
+16-2
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,23 @@
11
#![no_std]
22

3+
#[cfg(test)]
4+
extern crate std;
5+
36
pub mod error;
47

58
pub mod interface;
69

7-
pub mod contract;
8-
910
pub mod types;
11+
12+
#[cfg(test)]
13+
mod tests;
14+
15+
cfg_if::cfg_if! {
16+
if #[cfg(all(feature = "library", not(test)))] {
17+
pub use interface::{MulticallClientClient, MulticallInterface};
18+
} else {
19+
mod contract;
20+
21+
pub use contract::{Multicall, MulticallClient};
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mod test;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#![cfg(test)]
2+
extern crate std;
3+
4+
use soroban_sdk::testutils::Address as _;
5+
use soroban_sdk::{
6+
contract, contractimpl, symbol_short, vec, Address, Env, IntoVal,
7+
Symbol, Val, Vec,
8+
};
9+
use stellar_axelar_std::events::{fmt_last_emitted_event, Event};
10+
use stellar_axelar_std::interfaces::OwnableInterface;
11+
use stellar_axelar_std::{
12+
assert_contract_err, interfaces, IntoEvent,
13+
};
14+
15+
use crate::error::ContractError;
16+
use crate::types::FunctionCall;
17+
use crate::{Multicall, MulticallClient};
18+
19+
#[contract]
20+
pub struct TestTarget;
21+
22+
#[derive(Debug, PartialEq, Eq, IntoEvent)]
23+
pub struct ExecutedEvent {
24+
pub value: u32,
25+
}
26+
27+
#[contractimpl]
28+
impl OwnableInterface for TestTarget {
29+
fn owner(env: &Env) -> Address {
30+
interfaces::owner(env)
31+
}
32+
33+
fn transfer_ownership(env: &Env, new_owner: Address) {
34+
interfaces::transfer_ownership::<Self>(env, new_owner);
35+
}
36+
}
37+
38+
#[contractimpl]
39+
impl TestTarget {
40+
pub fn __constructor(env: Env, owner: Address) {
41+
interfaces::set_owner(&env, &owner);
42+
}
43+
44+
pub fn method(env: &Env, value: u32) {
45+
ExecutedEvent { value }.emit(env);
46+
}
47+
48+
pub fn failing(_env: &Env) {
49+
panic!("This method should fail");
50+
}
51+
52+
pub fn failing_with_error(_env: &Env) -> Result<Val, ContractError> {
53+
Err(ContractError::FunctionCallFailed)
54+
}
55+
}
56+
57+
fn setup<'a>() -> (Env, MulticallClient<'a>, Address, Address) {
58+
let env = Env::default();
59+
60+
let owner = Address::generate(&env);
61+
let contract_id = env.register(Multicall, ());
62+
let client = MulticallClient::new(&env, &contract_id);
63+
64+
let target_id = env.register(TestTarget, (owner.clone(),));
65+
66+
(env, client, target_id, owner)
67+
}
68+
69+
#[test]
70+
fn multicall_succeeds() {
71+
let (env, client, target, _) = setup();
72+
73+
let params = vec![
74+
&env,
75+
FunctionCall {
76+
contract: target.clone(),
77+
function: symbol_short!("method"),
78+
args: vec![&env, IntoVal::<_, Val>::into_val(&42u32, &env)],
79+
},
80+
];
81+
82+
client.multicall(&params);
83+
goldie::assert!(fmt_last_emitted_event::<ExecutedEvent>(&env));
84+
}
85+
86+
#[test]
87+
#[should_panic(expected = "HostError: Error(WasmVm, InvalidAction)")]
88+
fn multicall_fails_when_target_panics() {
89+
let (env, client, target, _) = setup();
90+
91+
let params = vec![
92+
&env,
93+
FunctionCall {
94+
contract: target,
95+
function: symbol_short!("failing"),
96+
args: Vec::<Val>::new(&env),
97+
},
98+
];
99+
100+
client.multicall(&params);
101+
}
102+
103+
#[test]
104+
fn multicall_fails_when_target_returns_error() {
105+
let (env, client, target, _) = setup();
106+
107+
let params = vec![
108+
&env,
109+
FunctionCall {
110+
contract: target,
111+
function: Symbol::new(&env, "failing_with_error"),
112+
args: Vec::<Val>::new(&env),
113+
},
114+
];
115+
116+
assert_contract_err!(
117+
client.try_multicall(&params),
118+
ContractError::FunctionCallFailed
119+
);
120+
}
121+
122+
#[test]
123+
fn multicall_fails_when_some_calls_returns_error() {
124+
let (env, client, target, _) = setup();
125+
126+
let params = vec![
127+
&env,
128+
FunctionCall {
129+
contract: target.clone(),
130+
function: symbol_short!("method"),
131+
args: vec![&env, IntoVal::<_, Val>::into_val(&42u32, &env)],
132+
},
133+
FunctionCall {
134+
contract: target.clone(),
135+
function: Symbol::new(&env, "failing_with_error"),
136+
args: Vec::<Val>::new(&env),
137+
},
138+
FunctionCall {
139+
contract: target.clone(),
140+
function: symbol_short!("method"),
141+
args: vec![&env, IntoVal::<_, Val>::into_val(&0u32, &env)],
142+
},
143+
];
144+
assert_contract_err!(
145+
client.try_multicall(&params),
146+
ContractError::FunctionCallFailed
147+
);
148+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
ExecutedEvent {
2+
value: 42,
3+
}
4+
5+
Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M)
6+
7+
executed {
8+
#[topic] value: u32,
9+
}

contracts/stellar-multicall/src/types.rs

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,8 @@ use soroban_sdk::{contracttype, Address, Symbol, Val, Vec};
22

33
#[contracttype]
44
#[derive(Clone, Debug, PartialEq, Eq)]
5-
pub struct MulticallData {
6-
pub contract_address: Address,
5+
pub struct FunctionCall {
6+
pub contract: Address,
77
pub function: Symbol,
88
pub args: Vec<Val>,
99
}

0 commit comments

Comments
 (0)