Skip to content

Commit

Permalink
feat(axelar-std)!: add pausable interface (#204)
Browse files Browse the repository at this point in the history
  • Loading branch information
milapsheth authored Jan 24, 2025
1 parent 9c1fec1 commit 0d4af95
Show file tree
Hide file tree
Showing 21 changed files with 319 additions and 82 deletions.
6 changes: 3 additions & 3 deletions contracts/stellar-example/src/tests/test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,7 +201,7 @@ fn its_example() {

let trusted_chain_set_event = events::fmt_last_emitted_event::<TrustedChainSetEvent>(&env);

let data = Some(Address::generate(&env).to_string_bytes());
let data = Address::generate(&env).to_string_bytes();

let msg = stellar_interchain_token_service::types::HubMessage::ReceiveFromHub {
source_chain: original_source_chain,
Expand All @@ -211,7 +211,7 @@ fn its_example() {
source_address: user,
destination_address: example_app.address.to_string_bytes(),
amount,
data: data.clone(),
data: Some(data.clone()),
},
),
};
Expand Down Expand Up @@ -249,6 +249,6 @@ fn its_example() {
let token = token::TokenClient::new(&env, &its_client.token_address(&token_id));
assert_eq!(token.balance(&example_app.address), 0);

let recipient = Address::from_string_bytes(&data.unwrap());
let recipient = Address::from_string_bytes(&data);
assert_eq!(token.balance(&recipient), amount);
}
36 changes: 9 additions & 27 deletions contracts/stellar-interchain-token-service/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@ 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, Upgradable};
use stellar_axelar_std::{ensure, interfaces, Operatable, Ownable, Pausable, Upgradable};
use stellar_interchain_token::InterchainTokenClient;

use crate::error::ContractError;
use crate::event::{
InterchainTokenDeployedEvent, InterchainTokenDeploymentStartedEvent,
InterchainTokenIdClaimedEvent, InterchainTransferReceivedEvent, InterchainTransferSentEvent,
PauseStatusSetEvent, TrustedChainRemovedEvent, TrustedChainSetEvent,
TrustedChainRemovedEvent, TrustedChainSetEvent,
};
use crate::flow_limit::FlowDirection;
use crate::interface::InterchainTokenServiceInterface;
Expand All @@ -36,7 +36,7 @@ const PREFIX_CANONICAL_TOKEN_SALT: &str = "canonical-token-salt";
const EXECUTE_WITH_TOKEN: &str = "execute_with_interchain_token";

#[contract]
#[derive(Operatable, Ownable, Upgradable)]
#[derive(Operatable, Ownable, Pausable, Upgradable)]
pub struct InterchainTokenService;

#[contractimpl]
Expand Down Expand Up @@ -195,10 +195,6 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
.token_manager_type
}

fn is_paused(env: &Env) -> bool {
env.storage().instance().has(&DataKey::Paused)
}

fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option<i128> {
flow_limit::flow_limit(env, token_id)
}
Expand All @@ -221,20 +217,6 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
flow_limit::set_flow_limit(env, token_id, flow_limit)
}

fn set_pause_status(env: &Env, paused: bool) -> Result<(), ContractError> {
Self::owner(env).require_auth();

if paused {
env.storage().instance().set(&DataKey::Paused, &());
} else {
env.storage().instance().remove(&DataKey::Paused);
}

PauseStatusSetEvent { paused }.emit(env);

Ok(())
}

fn deploy_interchain_token(
env: &Env,
caller: Address,
Expand All @@ -243,7 +225,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
initial_supply: i128,
minter: Option<Address>,
) -> Result<BytesN<32>, ContractError> {
ensure!(!Self::is_paused(env), ContractError::ContractPaused);
ensure!(!Self::paused(env), ContractError::ContractPaused);

caller.require_auth();

Expand Down Expand Up @@ -300,7 +282,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
destination_chain: String,
gas_token: Token,
) -> Result<BytesN<32>, ContractError> {
ensure!(!Self::is_paused(env), ContractError::ContractPaused);
ensure!(!Self::paused(env), ContractError::ContractPaused);

caller.require_auth();

Expand All @@ -313,7 +295,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
env: &Env,
token_address: Address,
) -> Result<BytesN<32>, ContractError> {
ensure!(!Self::is_paused(env), ContractError::ContractPaused);
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 @@ -351,7 +333,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
spender: Address,
gas_token: Token,
) -> Result<BytesN<32>, ContractError> {
ensure!(!Self::is_paused(env), ContractError::ContractPaused);
ensure!(!Self::paused(env), ContractError::ContractPaused);

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

Expand All @@ -371,7 +353,7 @@ impl InterchainTokenServiceInterface for InterchainTokenService {
data: Option<Bytes>,
gas_token: Token,
) -> Result<(), ContractError> {
ensure!(!Self::is_paused(env), ContractError::ContractPaused);
ensure!(!Self::paused(env), ContractError::ContractPaused);

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

Expand Down Expand Up @@ -502,7 +484,7 @@ impl InterchainTokenService {
source_address: String,
payload: Bytes,
) -> Result<(), ContractError> {
ensure!(!Self::is_paused(env), ContractError::ContractPaused);
ensure!(!Self::paused(env), ContractError::ContractPaused);

let (source_chain, message) =
Self::get_execute_params(env, source_chain, source_address, payload)?;
Expand Down
5 changes: 0 additions & 5 deletions contracts/stellar-interchain-token-service/src/event.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,6 @@ pub struct FlowLimitSetEvent {
pub flow_limit: Option<i128>,
}

#[derive(Debug, PartialEq, Eq, IntoEvent)]
pub struct PauseStatusSetEvent {
pub paused: bool,
}

#[derive(Debug, PartialEq, Eq, IntoEvent)]
pub struct InterchainTokenDeployedEvent {
pub token_id: BytesN<32>,
Expand Down
19 changes: 10 additions & 9 deletions contracts/stellar-interchain-token-service/src/interface.rs
Original file line number Diff line number Diff line change
@@ -1,14 +1,23 @@
use soroban_sdk::{contractclient, Address, Bytes, BytesN, Env, String};
use soroban_token_sdk::metadata::TokenMetadata;
use stellar_axelar_gateway::executable::AxelarExecutableInterface;
use stellar_axelar_std::interfaces::{
OperatableInterface, OwnableInterface, PausableInterface, UpgradableInterface,
};
use stellar_axelar_std::types::Token;

use crate::error::ContractError;
use crate::types::TokenManagerType;

#[allow(dead_code)]
#[contractclient(name = "InterchainTokenServiceClient")]
pub trait InterchainTokenServiceInterface: AxelarExecutableInterface {
pub trait InterchainTokenServiceInterface:
AxelarExecutableInterface
+ OwnableInterface
+ OperatableInterface
+ PausableInterface
+ UpgradableInterface
{
/// Returns the address of the Gas Service contract.
fn gas_service(env: &Env) -> Address;

Expand Down Expand Up @@ -81,9 +90,6 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface {
/// Returns the type of the token manager associated with the specified token ID.
fn token_manager_type(env: &Env, token_id: BytesN<32>) -> TokenManagerType;

/// Returns whether the contract is paused.
fn is_paused(env: &Env) -> bool;

/// Returns the flow limit for the token associated with the specified token ID.
/// Returns `None` if no limit is set.
fn flow_limit(env: &Env, token_id: BytesN<32>) -> Option<i128>;
Expand Down Expand Up @@ -117,11 +123,6 @@ pub trait InterchainTokenServiceInterface: AxelarExecutableInterface {
flow_limit: Option<i128>,
) -> Result<(), ContractError>;

/// Sets the pause status of the contract.
/// # Authorization
/// - Must be called by [`Self::owner`].
fn set_pause_status(env: &Env, paused: bool) -> Result<(), ContractError>;

/// Deploys a new interchain token on the current chain with specified metadata and optional
/// initial supply. If initial supply is provided, it is minted to the caller. The
/// caller can also specify an optional minter address for the interchain token.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ pub enum DataKey {
ItsHubAddress,
NativeTokenAddress,
InterchainTokenWasmHash,
Paused,
TrustedChain(String),
TokenIdConfig(BytesN<32>),
FlowLimit(BytesN<32>),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ fn deploy_interchain_token_succeeds() {
fn deploy_interchain_token_fails_when_paused() {
let (env, client, _, _, _) = setup_env();

client.mock_all_auths().set_pause_status(&true);
client.mock_all_auths().pause();

assert_contract_err!(
client.try_deploy_interchain_token(
Expand Down Expand Up @@ -117,21 +117,15 @@ fn deploy_interchain_token_check_token_id_and_token_manager_type() {
let (env, client, _, _, _) = setup_env();

let (sender, salt, token_metadata) = dummy_token_params(&env);
let minter = Address::generate(&env);
let minter = Some(Address::generate(&env));
let initial_supply = 100;

let deploy_salt = client.interchain_token_deploy_salt(&sender, &salt);
let expected_token_id = client.interchain_token_id(&Address::zero(&env), &deploy_salt);

let token_id = assert_auth!(
&sender,
client.deploy_interchain_token(
&sender,
&salt,
&token_metadata,
&initial_supply,
&Some(minter.clone()),
)
client.deploy_interchain_token(&sender, &salt, &token_metadata, &initial_supply, &minter,)
);

goldie::assert!(events::fmt_emitted_event_at_idx::<
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,7 @@ fn deploy_remote_interchain_token_succeeds() {
fn deploy_remote_interchain_token_fails_when_paused() {
let (env, client, _, _, _) = setup_env();

client.mock_all_auths().set_pause_status(&true);
client.mock_all_auths().pause();

assert_contract_err!(
client.try_deploy_remote_interchain_token(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ fn execute_fails_when_paused() {
];
approve_gateway_messages(&env, &gateway_client, signers, messages);

client.mock_all_auths().set_pause_status(&true);
client.mock_all_auths().pause();

assert_contract_err!(
client.try_execute(&source_chain, &message_id, &source_address, &payload,),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ fn interchain_transfer_send_succeeds() {
fn interchain_transfer_send_fails_when_paused() {
let (env, client, _, _, _) = setup_env();

client.mock_all_auths().set_pause_status(&true);
client.mock_all_auths().pause();

assert_contract_err!(
client.try_interchain_transfer(
Expand Down
23 changes: 11 additions & 12 deletions contracts/stellar-interchain-token-service/src/tests/pause.rs
Original file line number Diff line number Diff line change
@@ -1,40 +1,39 @@
use soroban_sdk::testutils::Address as _;
use soroban_sdk::Address;
use stellar_axelar_std::interfaces::{PausedEvent, UnpausedEvent};
use stellar_axelar_std::{assert_auth, assert_auth_err, events};

use super::utils::setup_env;
use crate::event::PauseStatusSetEvent;

#[test]
fn pause_succeeds() {
let (env, client, _, _, _) = setup_env();

assert!(!client.is_paused());
assert!(!client.paused());

assert_auth!(client.owner(), client.set_pause_status(&true));
goldie::assert!(events::fmt_last_emitted_event::<PauseStatusSetEvent>(&env));
assert_auth!(client.owner(), client.pause());
goldie::assert!(events::fmt_last_emitted_event::<PausedEvent>(&env));

assert!(client.is_paused());
assert!(client.paused());
}

#[test]
fn unpause_succeeds() {
let (env, client, _, _, _) = setup_env();

assert_auth!(client.owner(), client.set_pause_status(&true));
assert_auth!(client.owner(), client.pause());

assert!(client.is_paused());
assert_auth!(client.owner(), client.set_pause_status(&false));
assert!(client.paused());
assert_auth!(client.owner(), client.unpause());

goldie::assert!(events::fmt_last_emitted_event::<PauseStatusSetEvent>(&env));
goldie::assert!(events::fmt_last_emitted_event::<UnpausedEvent>(&env));

assert!(!client.is_paused());
assert!(!client.paused());
}

#[test]
fn pause_fails_with_invalid_auth() {
let (env, client, _, _, _) = setup_env();

let user = Address::generate(&env);
assert_auth_err!(user, client.set_pause_status(&true));
assert_auth_err!(Address::generate(&env), client.pause());
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ fn register_canonical_token_succeeds() {
fn register_canonical_token_fails_when_paused() {
let (env, client, _, _, _) = setup_env();

client.mock_all_auths().set_pause_status(&true);
client.mock_all_auths().pause();

assert_contract_err!(
client.try_register_canonical_token(&Address::generate(&env)),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
PauseStatusSetEvent {
paused: true,
}
PausedEvent

Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5)

pause_status_set {
#[topic] paused: bool,
paused {
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
PauseStatusSetEvent {
paused: false,
}
UnpausedEvent

Contract(CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAVAX5)

pause_status_set {
#[topic] paused: bool,
unpaused {
}
22 changes: 22 additions & 0 deletions packages/stellar-axelar-std-derive/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ mod event;
mod its_executable;
mod operatable;
mod ownable;
mod pausable;
mod upgradable;

use proc_macro::TokenStream;
Expand Down Expand Up @@ -68,6 +69,27 @@ pub fn derive_ownable(input: TokenStream) -> TokenStream {
ownable::ownable(name).into()
}

/// Implements the Pausable interface for a Soroban contract.
///
/// # Example
/// ```rust,ignore
/// # mod test {
/// # use soroban_sdk::{contract, contractimpl, Address, Env};
/// use stellar_axelar_std_derive::Pausable;
///
/// #[contract]
/// #[derive(Pausable)]
/// pub struct Contract;
/// # }
/// ```
#[proc_macro_derive(Pausable)]
pub fn derive_pausable(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as DeriveInput);
let name = &input.ident;

pausable::pausable(name).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
Loading

0 comments on commit 0d4af95

Please sign in to comment.