stellar_axelar_std/error.rs
1/// Return with an error if a condition is not met.
2///
3/// Simplifies the pattern of checking for a condition and returning with an error.
4#[macro_export]
5macro_rules! ensure {
6 ($cond:expr, $e:expr $(,)?) => {
7 if !$cond {
8 return Err($e);
9 }
10 };
11}
12
13// The following definitions are mostly intended to serve as pseudo-documentation within tests
14// and help with convenience/clarity.
15
16/// Assert that a [`Result`] is [`Ok`]
17///
18/// If the provided expresion evaulates to [`Ok`], then the
19/// macro returns the value contained within the [`Ok`]. If
20/// the [`Result`] is an [`Err`] then the macro will [`panic`]
21/// with a message that includes the expression and the error.
22///
23/// This function was vendored from [assert_ok](https://docs.rs/assert_ok/1.0.2/assert_ok/).
24#[macro_export]
25macro_rules! assert_ok {
26 ( $x:expr ) => {
27 match $x {
28 Ok(v) => v,
29 Err(e) => {
30 panic!("Error calling {}: {:?}", stringify!($x), e);
31 }
32 }
33 };
34}
35
36/// Assert that a [`Result`] is [`Err`] and matches an error variant
37#[macro_export]
38macro_rules! assert_err {
39 ( $x:expr, $expected:pat ) => {
40 match $x {
41 Err(e) => {
42 if !matches!(e, $expected) {
43 panic!("Expected error {}: {:?}", stringify!($expected), e);
44 }
45 }
46 Ok(v) => {
47 panic!("Expected error {}, found {:?}", stringify!($expected), v)
48 }
49 }
50 };
51}
52
53/// Assert that a [`Result`] from a contract call is [`Err`] and matches an error variant
54///
55/// `given` corresponds to the return type from `try_*` functions in Soroban.
56/// For the assert to succeed, the function needs to fail and successfully pass
57/// down the intended error type. So, the parameters would be in the form:
58///
59/// given: `Err(Ok(ContractError))`
60/// expected: `ContractError`
61///
62/// Putting it together in a function call:
63///
64/// `assert_contract_err(client.try_fun(...), ContractError);`
65#[macro_export]
66macro_rules! assert_contract_err {
67 ($given:expr, $expected:expr) => {
68 match $given {
69 Ok(v) => panic!(
70 "Expected error {:?}, got {:?} instead",
71 stringify!($expected),
72 v
73 ),
74 Err(e) => match e {
75 Err(e) => panic!("Unexpected error {e:?}"),
76 Ok(v) if v != $expected => {
77 panic!("Expected error {:?}, got {:?} instead", $expected, v)
78 }
79 _ => (),
80 },
81 }
82 };
83}
84
85/// Assert that an [`Option`] is [`Some`]
86///
87/// If the provided expresion evaulates to [`Some`], then the
88/// macro returns the value contained within the [`Some`]. If
89/// the [`Option`] is [`None`] then the macro will [`panic`]
90/// with a message that includes the expression
91#[macro_export]
92macro_rules! assert_some {
93 ( $x:expr ) => {
94 match $x {
95 core::option::Option::Some(s) => s,
96 core::option::Option::None => {
97 panic!("Expected value when calling {}, got None", stringify!($x));
98 }
99 }
100 };
101}
102
103/// Assert that a contract call with authentication succeeds
104///
105/// This macro is used to test contract calls that require authentication. It mocks the authentication
106/// for the specified caller and executes the contract call. If the call fails or doesn't require the authentication,
107/// the macro will panic with an error message.
108///
109/// # Example
110///
111/// ```rust, ignore
112/// # use stellar_axelar_std::{Address, Env, contract, contractimpl};
113/// # use stellar_axelar_std::testutils::Address as _;
114/// # use stellar_axelar_std::assert_auth;
115///
116/// #[contract]
117/// pub struct Contract;
118///
119/// #[contractimpl]
120/// impl Contract {
121/// pub fn set_value(env: &Env, caller: Address, value: u32) {
122/// caller.require_auth();
123/// }
124/// }
125///
126/// # let env = Env::default();
127/// # let caller = Address::generate(&env);
128/// # let contract_id = env.register(Contract, ());
129/// # let client = ContractClient::new(&env, &contract_id);
130///
131/// assert_auth!(caller, client.set_value(&caller, &42));
132/// ```
133#[macro_export]
134macro_rules! assert_auth {
135 ($caller:expr, $client:ident . $method:ident ( $($arg:expr),* $(,)? )) => {{
136 use stellar_axelar_std::IntoVal;
137
138 // Evaluate the expression before the method call.
139 // If the expression itself called the contract, e.g. client.owner(),
140 // then this will prevent events from being reset when checking the auth after the call.
141 let caller = $caller.clone();
142
143 // Paste is used to concatenate the method name with the `try_` prefix
144 paste::paste! {
145 let result = $client
146 .mock_auths(&[$crate::mock_auth!(
147 caller,
148 $client.$method($($arg),*)
149 )])
150 .[<try_ $method>]($($arg),*);
151 }
152
153 let result = match result {
154 Ok(outer) => {
155 match outer {
156 Ok(inner) => inner,
157 Err(err) => panic!("Expected Ok result, but got an error {:?}", err),
158 }
159 }
160 Err(err) => panic!("Expected Ok result, but got an error {:?}", err),
161 };
162
163 assert_eq!(
164 $client.env.auths(),
165 std::vec![(
166 caller,
167 stellar_axelar_std::testutils::AuthorizedInvocation {
168 function: stellar_axelar_std::testutils::AuthorizedFunction::Contract((
169 $client.address.clone(),
170 stellar_axelar_std::Symbol::new(&$client.env, stringify!($method)),
171 ($($arg.clone(),)*).into_val(&$client.env)
172 )),
173 sub_invocations: std::vec![]
174 }
175 )]
176 );
177
178 result
179 }};
180}
181
182#[macro_export]
183macro_rules! assert_auth_err {
184 ($caller:expr, $client:ident . $method:ident ( $($arg:expr),* $(,)? )) => {{
185 use stellar_axelar_std::xdr::{ScError, ScErrorCode, ScVal};
186
187 let caller = $caller.clone();
188
189 paste::paste! {
190 let call_result = $client
191 .mock_auths(&[$crate::mock_auth!(
192 caller,
193 $client.$method($($arg),*)
194 )])
195 .[<try_ $method>]($($arg),*);
196 }
197 match call_result {
198 Err(_) => {
199 let val = ScVal::Error(ScError::Context(ScErrorCode::InvalidAction));
200 match ScError::try_from(val) {
201 Ok(ScError::Context(ScErrorCode::InvalidAction)) => {}
202 _ => panic!("Expected ScErrorCode::InvalidAction"),
203 }
204 }
205 Ok(_) => panic!("Expected error, but got Ok result."),
206 }
207 }};
208}
209
210#[macro_export]
211macro_rules! mock_auth {
212 (
213 $caller:expr,
214 $client:ident . $method:ident ( $($arg:expr),* $(,)? ),
215 $sub_invokes:expr
216 ) => {{
217 use stellar_axelar_std::IntoVal;
218
219 stellar_axelar_std::testutils::MockAuth {
220 address: &$caller,
221 invoke: &stellar_axelar_std::testutils::MockAuthInvoke {
222 contract: &$client.address,
223 fn_name: &stringify!($method).replace("try_", ""),
224 args: ($($arg.clone(),)*).into_val(&$client.env),
225 sub_invokes: $sub_invokes,
226 },
227 }
228 }};
229
230 (
231 $caller:expr,
232 $client:ident . $method:ident ( $($arg:expr),* $(,)? )
233 ) => {{
234 $crate::mock_auth!($caller, $client.$method($($arg),*), &[])
235 }};
236}