Skip to content

Commit

Permalink
feat(axelar-std-derive): add macro to execute when contract is not pa…
Browse files Browse the repository at this point in the history
…used (#214)
  • Loading branch information
milapsheth authored Jan 25, 2025
1 parent c986ce8 commit 03d1a48
Show file tree
Hide file tree
Showing 7 changed files with 129 additions and 33 deletions.
24 changes: 12 additions & 12 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,8 @@ hex = { version = "0.4", default-features = false }
paste = { version = "1.0", default-features = false }
proc-macro2 = { version = "1.0", default-features = false }
rand = { version = "0.8.5", default-features = false }
soroban-sdk = { version = "22.0.5" }
soroban-token-sdk = { version = "22.0.5" }
soroban-sdk = { version = "22.0.6" }
soroban-token-sdk = { version = "22.0.6" }
stellar-axelar-gas-service = { version = "^0.2.2", path = "contracts/stellar-axelar-gas-service" }
stellar-axelar-gateway = { version = "^0.2.2", path = "contracts/stellar-axelar-gateway" }
stellar-axelar-operators = { version = "^0.2.2", path = "contracts/stellar-axelar-operators" }
Expand Down
7 changes: 4 additions & 3 deletions contracts/stellar-axelar-gateway/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use soroban_sdk::xdr::ToXdr;
use soroban_sdk::{contract, contractimpl, Address, Bytes, BytesN, Env, String, Vec};
use stellar_axelar_std::events::Event;
use stellar_axelar_std::ttl::extend_instance_ttl;
use stellar_axelar_std::{ensure, interfaces, Operatable, Ownable, Pausable, Upgradable};
use stellar_axelar_std::{
ensure, interfaces, when_not_paused, Operatable, Ownable, Pausable, Upgradable,
};

use crate::auth;
use crate::error::ContractError;
Expand Down Expand Up @@ -146,13 +148,12 @@ impl AxelarGatewayInterface for AxelarGateway {
auth::previous_signers_retention(env)
}

#[when_not_paused]
fn approve_messages(
env: &Env,
messages: Vec<Message>,
proof: Proof,
) -> Result<(), ContractError> {
ensure!(!Self::paused(env), ContractError::ContractPaused);

let data_hash: BytesN<32> = env
.crypto()
.keccak256(&(CommandType::ApproveMessages, messages.clone()).to_xdr(env))
Expand Down
22 changes: 9 additions & 13 deletions contracts/stellar-interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@ use stellar_axelar_std::address::AddressExt;
use stellar_axelar_std::events::Event;
use stellar_axelar_std::ttl::{extend_instance_ttl, extend_persistent_ttl};
use stellar_axelar_std::types::Token;
use stellar_axelar_std::{ensure, interfaces, Operatable, Ownable, Pausable, Upgradable};
use stellar_axelar_std::{
ensure, interfaces, when_not_paused, Operatable, Ownable, Pausable, Upgradable,
};
use stellar_interchain_token::InterchainTokenClient;

use crate::error::ContractError;
Expand Down Expand Up @@ -217,6 +219,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
flow_limit::set_flow_limit(env, token_id, flow_limit)
}

#[when_not_paused]
fn deploy_interchain_token(
env: &Env,
caller: Address,
Expand All @@ -225,8 +228,6 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
initial_supply: i128,
minter: Option<Address>,
) -> Result<BytesN<32>, ContractError> {
ensure!(!Self::paused(env), ContractError::ContractPaused);

caller.require_auth();

let initial_minter = if initial_supply > 0 {
Expand Down Expand Up @@ -275,28 +276,26 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
Ok(token_id)
}

#[when_not_paused]
fn deploy_remote_interchain_token(
env: &Env,
caller: Address,
salt: BytesN<32>,
destination_chain: String,
gas_token: Token,
) -> Result<BytesN<32>, ContractError> {
ensure!(!Self::paused(env), ContractError::ContractPaused);

caller.require_auth();

let deploy_salt = Self::interchain_token_deploy_salt(env, caller.clone(), salt);

Self::deploy_remote_token(env, caller, deploy_salt, destination_chain, gas_token)
}

#[when_not_paused]
fn register_canonical_token(
env: &Env,
token_address: Address,
) -> Result<BytesN<32>, ContractError> {
ensure!(!Self::paused(env), ContractError::ContractPaused);

let deploy_salt = Self::canonical_token_deploy_salt(env, token_address.clone());
let token_id = Self::interchain_token_id(env, Address::zero(env), deploy_salt.clone());

Expand Down Expand Up @@ -326,15 +325,14 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
Ok(token_id)
}

#[when_not_paused]
fn deploy_remote_canonical_token(
env: &Env,
token_address: Address,
destination_chain: String,
spender: Address,
gas_token: Token,
) -> Result<BytesN<32>, ContractError> {
ensure!(!Self::paused(env), ContractError::ContractPaused);

let deploy_salt = Self::canonical_token_deploy_salt(env, token_address);

let token_id =
Expand All @@ -343,6 +341,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
Ok(token_id)
}

#[when_not_paused]
fn interchain_transfer(
env: &Env,
caller: Address,
Expand All @@ -353,8 +352,6 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
data: Option<Bytes>,
gas_token: Token,
) -> Result<(), ContractError> {
ensure!(!Self::paused(env), ContractError::ContractPaused);

ensure!(amount > 0, ContractError::InvalidAmount);

ensure!(
Expand Down Expand Up @@ -477,15 +474,14 @@ impl InterchainTokenService {
Ok(())
}

#[when_not_paused]
fn execute_message(
env: &Env,
source_chain: String,
message_id: String,
source_address: String,
payload: Bytes,
) -> Result<(), ContractError> {
ensure!(!Self::paused(env), ContractError::ContractPaused);

let (source_chain, message) =
Self::get_execute_params(env, source_chain, source_address, payload)?;

Expand Down
37 changes: 36 additions & 1 deletion packages/stellar-axelar-std-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ mod pausable;
mod upgradable;

use proc_macro::TokenStream;
use syn::{parse_macro_input, DeriveInput};
use syn::{parse_macro_input, DeriveInput, ItemFn};
use upgradable::MigrationArgs;

/// Implements the Operatable interface for a Soroban contract.
Expand Down Expand Up @@ -90,6 +90,41 @@ pub fn derive_pausable(input: TokenStream) -> TokenStream {
pausable::pausable(name).into()
}

/// Ensure that the Stellar contract is not paused before executing the function.
///
/// The first argument to the function must be `env`, and a `ContractError` error type must be defined in scope,
/// with a `ContractPaused` variant.
///
/// # Example
/// ```rust,ignore
/// # mod test {
/// # use soroban_sdk::{contract, contractimpl, Address, Env};
/// use stellar_axelar_std_derive::when_not_paused;
///
/// #[contracttype]
/// pub enum ContractError {
/// ContractPaused = 1,
/// }
///
/// #[contract]
/// #[derive(Pausable)]
/// pub struct Contract;
///
/// #[contractimpl]
/// impl Contract {
/// #[when_not_paused]
/// pub fn transfer(env: &Env, to: Address, amount: String) {
/// // ... transfer logic ...
/// }
/// }
/// ```
#[proc_macro_attribute]
pub fn when_not_paused(_attr: TokenStream, item: TokenStream) -> TokenStream {
let input_fn = parse_macro_input!(item as ItemFn);

pausable::when_not_paused_impl(input_fn).into()
}

/// Implements the Upgradable and Migratable interfaces for a Soroban contract.
///
/// A `ContractError` error type must be defined in scope, and have a `MigrationNotAllowed` variant.
Expand Down
34 changes: 34 additions & 0 deletions packages/stellar-axelar-std-derive/src/pausable.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use proc_macro2::{Ident, TokenStream as TokenStream2};
use quote::quote;
use syn::ItemFn;

pub fn pausable(name: &Ident) -> TokenStream2 {
quote! {
Expand All @@ -21,3 +22,36 @@ pub fn pausable(name: &Ident) -> TokenStream2 {
}
}
}

pub fn when_not_paused_impl(input_fn: ItemFn) -> TokenStream2 {
let fn_vis = &input_fn.vis;
let fn_sig = &input_fn.sig;
let fn_name = &fn_sig.ident;
let fn_generics = &fn_sig.generics;
let fn_inputs = &fn_sig.inputs;
let fn_output = &fn_sig.output;
let fn_body = &input_fn.block;
let fn_attrs = &input_fn.attrs;

// Check that env is the first parameter
let Some(syn::FnArg::Typed(pat_type)) = fn_inputs.first() else {
panic!("First parameter must be a typed parameter")
};
let syn::Pat::Ident(pat_ident) = &*pat_type.pat else {
panic!("First parameter must be a simple identifier")
};
assert!(
pat_ident.ident == "env",
"First parameter must be named 'env'"
);

// Ensure the function is not paused
quote! {
#(#fn_attrs)*
#fn_vis fn #fn_name #fn_generics(#fn_inputs) #fn_output {
stellar_axelar_std::ensure!(!Self::paused(env), ContractError::ContractPaused);

#fn_body
}
}
}
34 changes: 32 additions & 2 deletions packages/stellar-axelar-std/src/interfaces/pausable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,11 +57,19 @@ pub struct UnpausedEvent {}
#[cfg(test)]
mod test {
use soroban_sdk::testutils::Address as _;
use soroban_sdk::{contract, contractimpl, Address, Env};
use soroban_sdk::{contract, contracterror, contractimpl, Address, Env};

use super::{PausedEvent, UnpausedEvent};
use crate as stellar_axelar_std;
use crate::interfaces::{OwnableInterface, PausableInterface};
use crate::{assert_auth, assert_auth_err, events};
use crate::{assert_auth, assert_auth_err, assert_contract_err, events, when_not_paused};

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
#[repr(u32)]
pub enum ContractError {
ContractPaused = 1,
}

#[contract]
pub struct Contract;
Expand Down Expand Up @@ -99,6 +107,14 @@ mod test {
}
}

#[contractimpl]
impl Contract {
#[when_not_paused]
pub fn test(env: &Env) -> Result<u32, ContractError> {
Ok(42)
}
}

fn setup<'a>() -> (Env, ContractClient<'a>) {
let env = Env::default();
let owner = Address::generate(&env);
Expand Down Expand Up @@ -168,4 +184,18 @@ mod test {

assert_auth_err!(Address::generate(&env), client.unpause());
}

#[test]
fn test_succeeds_when_not_paused() {
let (_, client) = setup();
assert_eq!(client.test(), 42);
}

#[test]
fn test_fails_when_paused() {
let (_, client) = setup();

client.mock_all_auths().pause();
assert_contract_err!(client.try_test(), ContractError::ContractPaused);
}
}

0 comments on commit 03d1a48

Please sign in to comment.