Skip to main content

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}