diff --git a/Cargo.lock b/Cargo.lock index 7db9ceca..0394b1ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1993,6 +1993,7 @@ dependencies = [ name = "stellar-axelar-operators" version = "0.2.2" dependencies = [ + "cfg-if", "goldie", "paste", "soroban-sdk", @@ -2091,6 +2092,7 @@ dependencies = [ name = "stellar-upgrader" version = "0.2.2" dependencies = [ + "cfg-if", "soroban-sdk", "stellar-axelar-std", ] diff --git a/contracts/stellar-axelar-gas-service/src/interface.rs b/contracts/stellar-axelar-gas-service/src/interface.rs index 4083d9f3..c7a97a6a 100644 --- a/contracts/stellar-axelar-gas-service/src/interface.rs +++ b/contracts/stellar-axelar-gas-service/src/interface.rs @@ -9,10 +9,23 @@ pub trait AxelarGasServiceInterface: OperatableInterface { /// Pay for gas using a token for sending a message on a destination chain. /// /// This function is called on the source chain before calling the gateway to send a message. - /// - /// `sender` refers to the address that sent the cross-chain message via the `axelar_gateway`. /// The `spender` pays the gas but might differ from the `sender`, /// e.g. the `sender` is a contract, but the `spender` can be the user signing the transaction. + /// + /// # Arguments + /// * `sender` - The address initiating the gas payment. It's the address that sent the cross-chain message via the `axelar_gateway`. + /// * `destination_chain` - The destination chain for the message. + /// * `destination_address` - The destination contract address for the message. + /// * `payload` - The payload data associated with the message. + /// * `spender` - The address of the spender paying for the gas. Might differ from the `sender`. Excess gas will be refunded to this address. + /// * `token` - The token used to pay for the gas, including the address and amount. + /// * `metadata` - Additional metadata associated with the gas payment. + /// + /// # Errors + /// - [`ContractError::InvalidAmount`]: If the token amount is zero or negative. + /// + /// # Authorization + /// - The `spender` address must authorize the token transfer to the gas service. fn pay_gas( env: Env, sender: Address, @@ -26,9 +39,20 @@ pub trait AxelarGasServiceInterface: OperatableInterface { /// Adds additional gas payment after initiating a cross-chain message. /// - /// `sender` refers to the address that sent the cross-chain message via the `axelar_gateway`. /// The `spender` pays the gas but might differ from the `sender`, /// e.g. the `sender` is a contract, but the `spender` can be the user signing the transaction. + /// + /// # Arguments + /// * `sender` - The address that sent the cross-chain message. + /// * `message_id` - The identifier of the message for which gas is being added. + /// * `spender` - The address of the spender paying for the gas. + /// * `token` - The token used to pay for the gas, including the address and amount. + /// + /// # Errors + /// - [`ContractError::InvalidAmount`]: If the token amount is zero or negative. + /// + /// # Authorization + /// - The `spender` address must authorize. fn add_gas( env: Env, sender: Address, @@ -37,13 +61,30 @@ pub trait AxelarGasServiceInterface: OperatableInterface { token: Token, ) -> Result<(), ContractError>; + /// Collects gas fees and transfers them to a specified receiver. + /// /// Allows the `gas_collector` to collect accumulated fees from the contract. /// - /// Only callable by the `operator`. + /// # Arguments + /// * `receiver` - The address that will receive the collected feeds. + /// * `token` - The token address and amount for the fee collection. + /// + /// # Errors + /// - [`ContractError::InvalidAmount`]: If the token amount is zero or negative. + /// - [`ContractError::InsufficientBalance`]: If the contract's token balance is insufficient to cover the transfer. + /// + /// # Authorization + /// - [`Self::operator`] must authorize. fn collect_fees(env: Env, receiver: Address, token: Token) -> Result<(), ContractError>; - /// Refunds gas payment to the receiver in relation to a specific cross-chain message. + /// Refunds gas payment to the specified receiver in relation to a specific cross-chain message. + /// + /// # Arguments + /// * `message_id` - The identifier of the cross-chain message for which the gas fees are being refunded. + /// * `receiver` - The address of the receiver to whom the gas fees will be refunded. + /// * `token` - The token used for the refund, including the address and amount. /// - /// Only callable by the `operator`. + /// # Authorization + /// - [`Self::operator`] must authorize. fn refund(env: Env, message_id: String, receiver: Address, token: Token); } diff --git a/contracts/stellar-axelar-gateway/src/interface.rs b/contracts/stellar-axelar-gateway/src/interface.rs index baa6e2b1..faa0ced5 100644 --- a/contracts/stellar-axelar-gateway/src/interface.rs +++ b/contracts/stellar-axelar-gateway/src/interface.rs @@ -18,14 +18,40 @@ pub trait AxelarGatewayInterface: /// Returns the minimum delay between rotations. fn minimum_rotation_delay(env: &Env) -> u64; - /// Approves a collection of messages. + /// Approves a collection of messages with the provided proof. + /// + /// This function allows the approval of multiple messages using a cryptographic proof. + /// It ensures that the messages are not empty and prevents replay attacks by checking + /// if the messages have already been approved or executed. + /// + /// # Arguments + /// * `messages` - A vector of messages to be approved. + /// * `proof` - The cryptographic proof used to validate the approval. + /// + /// # Errors + /// - [`ContractError::EmptyMessages`]: If the provided messages vector is empty. + /// - Any error from `auth::validate_proof` due to an invalid proof. fn approve_messages( env: &Env, messages: Vec, proof: Proof, ) -> Result<(), ContractError>; - /// Rotates the signers. + /// Rotates to `signers` if the `proof` is valid. + /// + /// If `bypass_rotation_delay` is set to true, the `operator` must authorize the rotation. + /// + /// # Arguments + /// * `signers` - The new set of weighted signers to be rotated in. + /// * `proof` - The cryptographic proof used to validate the rotation. + /// * `bypass_rotation_delay` - A boolean indicating whether to bypass the rotation delay. + /// + /// # Errors + /// - [`ContractError::NotLatestSigners`]: If the provided signers are not the latest and `bypass_rotation_delay` is false. + /// - Any error from `auth::validate_proof` due to invalid proof. + /// + /// # Authorization + /// - The `operator` must authorize if `bypass_rotation_delay` is true. fn rotate_signers( env: &Env, signers: WeightedSigners, @@ -42,7 +68,8 @@ pub trait AxelarGatewayInterface: /// Returns the signers hash by epoch. fn signers_hash_by_epoch(env: &Env, epoch: u64) -> Result, ContractError>; - /// Validate the `proof` for `data_hash` created by the signers. Returns a boolean indicating if the proof was created by the latest signers. + /// Validate the `proof` for `data_hash` created by the signers. + /// Returns a boolean indicating if the proof was created by the latest signers. fn validate_proof( env: &Env, data_hash: BytesN<32>, diff --git a/contracts/stellar-axelar-operators/Cargo.toml b/contracts/stellar-axelar-operators/Cargo.toml index 3c78d9de..d4e00058 100644 --- a/contracts/stellar-axelar-operators/Cargo.toml +++ b/contracts/stellar-axelar-operators/Cargo.toml @@ -10,9 +10,11 @@ publish = true crate-type = ["cdylib", "rlib"] [features] +library = [] # Only export the contract interface testutils = ["stellar-axelar-std/testutils"] [dependencies] +cfg-if = { workspace = true } soroban-sdk = { workspace = true } stellar-axelar-std = { workspace = true } diff --git a/contracts/stellar-axelar-operators/src/contract.rs b/contracts/stellar-axelar-operators/src/contract.rs index 172dc10f..b04a8ed7 100644 --- a/contracts/stellar-axelar-operators/src/contract.rs +++ b/contracts/stellar-axelar-operators/src/contract.rs @@ -5,6 +5,7 @@ use stellar_axelar_std::{ensure, interfaces, Ownable, Upgradable}; use crate::error::ContractError; use crate::event::{OperatorAddedEvent, OperatorRemovedEvent}; +use crate::interface::AxelarOperatorsInterface; use crate::storage_types::DataKey; #[contract] @@ -16,18 +17,17 @@ impl AxelarOperators { pub fn __constructor(env: Env, owner: Address) { interfaces::set_owner(&env, &owner); } +} - /// Return true if the account is an operator. - pub fn is_operator(env: Env, account: Address) -> bool { +#[contractimpl] +impl AxelarOperatorsInterface for AxelarOperators { + fn is_operator(env: Env, account: Address) -> bool { let key = DataKey::Operators(account); env.storage().instance().has(&key) } - /// Add an address as an operator. - /// - /// Only callable by the contract owner. - pub fn add_operator(env: Env, account: Address) -> Result<(), ContractError> { + fn add_operator(env: Env, account: Address) -> Result<(), ContractError> { Self::owner(&env).require_auth(); let key = DataKey::Operators(account.clone()); @@ -46,10 +46,7 @@ impl AxelarOperators { Ok(()) } - /// Remove an address as an operator. - /// - /// Only callable by the contract owner. - pub fn remove_operator(env: Env, account: Address) -> Result<(), ContractError> { + fn remove_operator(env: Env, account: Address) -> Result<(), ContractError> { Self::owner(&env).require_auth(); let key = DataKey::Operators(account.clone()); @@ -66,8 +63,7 @@ impl AxelarOperators { Ok(()) } - /// Execute a function on a contract as an operator. - pub fn execute( + fn execute( env: Env, operator: Address, contract: Address, diff --git a/contracts/stellar-axelar-operators/src/interface.rs b/contracts/stellar-axelar-operators/src/interface.rs new file mode 100644 index 00000000..a2caa1aa --- /dev/null +++ b/contracts/stellar-axelar-operators/src/interface.rs @@ -0,0 +1,65 @@ +use soroban_sdk::{contractclient, Address, Env, Symbol, Val, Vec}; +use stellar_axelar_std::interfaces::OwnableInterface; + +use crate::error::ContractError; + +#[allow(dead_code)] +#[contractclient(name = "AxelarOperatorsClient")] +pub trait AxelarOperatorsInterface: OwnableInterface { + /// Return whether specified account is an operator. + fn is_operator(env: Env, account: Address) -> bool; + + /// Add an address as an operator. + /// + /// The operator is authorized to execute any third party contract via this contract. + /// An app can give a privileged role to this contract, which can then allow multiple operators + /// to call it, e.g. `refund` on the gas service. + /// + /// # Arguments + /// * `account` - The address to be added as an operator. + /// + /// # Errors + /// - [`ContractError::OperatorAlreadyAdded`]: If the specified account is already an operator. + /// + /// # Authorization + /// - [`Self::owner`] must authorize. + fn add_operator(env: Env, account: Address) -> Result<(), ContractError>; + + /// Remove an address as an operator. + /// + /// The address is no longer authorized to execute apps via this contract. + /// + /// # Arguments + /// * `account` - The address to be removed as an operator. + /// + /// # Errors + /// - [`ContractError::NotAnOperator`]: If the specified account is not an operator. + /// + /// # Authorization + /// - [`Self::owner`] must authorize. + fn remove_operator(env: Env, account: Address) -> Result<(), ContractError>; + + /// Execute a function on any contract as the operators contract. + /// + /// # Arguments + /// * `operator` - The address of the operator executing the function. + /// * `contract` - The address of the contract on which the function will be executed. + /// * `func` - The symbol representing the function to be executed. + /// * `args` - The arguments to be passed to the function. + /// + /// # Returns + /// - `Ok(Val)`: Returns the result of the function execution. + /// + /// # Errors + /// - [`ContractError::NotAnOperator`]: If the specified operator is not authorized. + /// + /// # Authorization + /// - An `operator` must authorize. + fn execute( + env: Env, + operator: Address, + contract: Address, + func: Symbol, + args: Vec, + ) -> Result; +} diff --git a/contracts/stellar-axelar-operators/src/lib.rs b/contracts/stellar-axelar-operators/src/lib.rs index d7bb592d..d6994a1b 100644 --- a/contracts/stellar-axelar-operators/src/lib.rs +++ b/contracts/stellar-axelar-operators/src/lib.rs @@ -3,13 +3,21 @@ #[cfg(any(test, feature = "testutils"))] extern crate std; -pub mod event; -mod storage_types; - -mod contract; pub mod error; -pub use contract::{AxelarOperators, AxelarOperatorsClient}; +mod interface; #[cfg(test)] mod tests; + +cfg_if::cfg_if! { + if #[cfg(all(feature = "library", not(feature = "testutils")))] { + pub use interface::{AxelarOperatorsClient, AxelarOperatorsInterface}; + } else { + pub mod event; + mod storage_types; + mod contract; + + pub use contract::{AxelarOperators, AxelarOperatorsClient}; + } +} diff --git a/contracts/stellar-example/src/contract.rs b/contracts/stellar-example/src/contract.rs index e98208f2..98ff8da1 100644 --- a/contracts/stellar-example/src/contract.rs +++ b/contracts/stellar-example/src/contract.rs @@ -11,6 +11,7 @@ use stellar_interchain_token_service::executable::CustomInterchainTokenExecutabl use stellar_interchain_token_service::InterchainTokenServiceClient; use crate::event::{ExecutedEvent, TokenReceivedEvent, TokenSentEvent}; +use crate::interface::ExampleInterface; use crate::storage_types::DataKey; #[contract] @@ -122,15 +123,18 @@ impl Example { .instance() .set(&DataKey::InterchainTokenService, &interchain_token_service); } +} - pub fn gas_service(env: &Env) -> Address { +#[contractimpl] +impl ExampleInterface for Example { + fn gas_service(env: &Env) -> Address { env.storage() .instance() .get(&DataKey::GasService) .expect("gas service not found") } - pub fn send( + fn send( env: &Env, caller: Address, destination_chain: String, @@ -161,7 +165,7 @@ impl Example { ); } - pub fn send_token( + fn send_token( env: &Env, caller: Address, token_id: BytesN<32>, diff --git a/contracts/stellar-example/src/interface.rs b/contracts/stellar-example/src/interface.rs new file mode 100644 index 00000000..58f6a2a7 --- /dev/null +++ b/contracts/stellar-example/src/interface.rs @@ -0,0 +1,58 @@ +use soroban_sdk::{Address, Bytes, BytesN, Env, String}; +use stellar_axelar_gateway::executable::AxelarExecutableInterface; +use stellar_axelar_std::types::Token; + +use crate::contract::ExampleError; + +pub trait ExampleInterface: AxelarExecutableInterface { + /// Retrieves the address of the gas service. + fn gas_service(env: &Env) -> Address; + + /// Sends a message to a specified destination chain. + /// + /// The function also handles the payment of gas for the cross-chain transaction. + /// + /// # Arguments + /// * `caller` - The address of the caller initiating the message. + /// * `destination_chain` - The name of the destination chain where the message will be sent. + /// * `destination_address` - The address on the destination chain where the message will be sent. + /// * `message` - The message to be sent. + /// * `gas_token` - The token used to pay for gas during the transaction. + /// + /// # Authorization + /// - The `caller` must authorize. + fn send( + env: &Env, + caller: Address, + destination_chain: String, + destination_address: String, + message: Bytes, + gas_token: Token, + ); + + /// Sends a token to a specified destination chain. + /// + /// The function also emits an event upon successful transfer. + /// + /// # Arguments + /// * `caller` - The address of the caller initiating the token transfer. + /// * `token_id` - The ID of the token to be transferred. + /// * `destination_chain` - The name of the destination chain where the token will be sent. + /// * `destination_app_contract` - The address of the application contract on the destination chain. + /// * `amount` - The amount of the token to be transferred. + /// * `recipient` - An optional recipient address on the destination chain. + /// * `gas_token` - The token used to pay for gas during the transaction. + /// + /// # Authorization + /// - The `caller` must authorize. + fn send_token( + env: &Env, + caller: Address, + token_id: BytesN<32>, + destination_chain: String, + destination_app_contract: Bytes, + amount: i128, + recipient: Option, + gas_token: Token, + ) -> Result<(), ExampleError>; +} diff --git a/contracts/stellar-example/src/lib.rs b/contracts/stellar-example/src/lib.rs index be4dd51c..c2942d33 100644 --- a/contracts/stellar-example/src/lib.rs +++ b/contracts/stellar-example/src/lib.rs @@ -5,6 +5,7 @@ extern crate std; mod contract; pub mod event; +pub mod interface; mod storage_types; pub use contract::{Example, ExampleClient}; diff --git a/contracts/stellar-interchain-token-service/src/interface.rs b/contracts/stellar-interchain-token-service/src/interface.rs index 775c1b7c..86f95284 100644 --- a/contracts/stellar-interchain-token-service/src/interface.rs +++ b/contracts/stellar-interchain-token-service/src/interface.rs @@ -44,12 +44,12 @@ pub trait InterchainTokenServiceInterface: /// Sets the specified chain as trusted for cross-chain messaging. /// # Authorization - /// - Must be called by [`Self::owner`]. + /// - [`Self::owner`] must authorize. fn set_trusted_chain(env: &Env, chain: String) -> Result<(), ContractError>; /// Removes the specified chain from trusted chains. /// # Authorization - /// - Must be called by the [`Self::owner`]. + /// - [`Self::owner`] must authorize. fn remove_trusted_chain(env: &Env, chain: String) -> Result<(), ContractError>; /// Computes the unique identifier for an interchain token. @@ -125,10 +125,10 @@ pub trait InterchainTokenServiceInterface: /// - `flow_limit`: The new flow limit value. Must be positive if Some. /// /// # Errors - /// - `ContractError::InvalidFlowLimit`: If the provided flow limit is not positive. + /// - [`ContractError::InvalidFlowLimit`]: If the provided flow limit is not positive. /// /// # Authorization - /// - Must be called by [`Self::operator`]. + /// - [`Self::operator`] must authorize. fn set_flow_limit( env: &Env, token_id: BytesN<32>, @@ -150,10 +150,10 @@ pub trait InterchainTokenServiceInterface: /// - `Ok(BytesN<32>)`: Returns the token ID. /// /// # Errors - /// - `ContractError::InvalidMinter`: If the minter address is invalid. + /// - [`ContractError::InvalidMinter`]: If the minter address is invalid. /// /// # Authorization - /// - The `deployer` must authenticate. + /// - The `deployer` must authorize. fn deploy_interchain_token( env: &Env, deployer: Address, @@ -175,11 +175,11 @@ pub trait InterchainTokenServiceInterface: /// - `Ok(BytesN<32>)`: Returns the token ID. /// /// # Errors - /// - `ContractError::InvalidTokenId`: If the token ID does not exist in the persistent storage. + /// - [`ContractError::InvalidTokenId`]: If the token ID does not exist in the persistent storage. /// - Any error propagated from `pay_gas_and_call_contract`. /// /// # Authorization - /// - The `caller` must authenticate. + /// - The `caller` must authorize. fn deploy_remote_interchain_token( env: &Env, caller: Address, @@ -197,7 +197,7 @@ pub trait InterchainTokenServiceInterface: /// - `Ok(BytesN<32>)`: Returns the token ID. /// /// # Errors - /// - `ContractError::TokenAlreadyRegistered`: If the token ID is already registered. + /// - [`ContractError::TokenAlreadyRegistered`]: If the token ID is already registered. fn register_canonical_token( env: &Env, token_address: Address, @@ -219,7 +219,7 @@ pub trait InterchainTokenServiceInterface: /// - `Ok(BytesN<32>)`: Returns the token ID. /// /// # Errors - /// - `ContractError::InvalidTokenId`: If the token ID does not exist in the persistent storage. + /// - [`ContractError::InvalidTokenId`]: If the token ID does not exist in the persistent storage. /// - Any error propagated from `pay_gas_and_call_contract`. /// /// # Authorization @@ -250,16 +250,13 @@ pub trait InterchainTokenServiceInterface: /// - `data`: Optional data to be handled by the destination address if it's a contract. /// - `gas_token`: The token used to pay for cross-chain message execution. /// - /// # Returns - /// - `Ok(())` - /// /// # Errors - /// - `ContractError::InvalidAmount`: If amount is not greater than 0. - /// - `ContractError::FlowLimitExceeded`: If transfer would exceed flow limits. + /// - [`ContractError::InvalidAmount`]: If amount is not greater than 0. + /// - [`ContractError::FlowLimitExceeded`]: If transfer would exceed flow limits. /// - Any error propagated from `pay_gas_and_call_contract`. /// /// # Authorization - /// - The `caller` must authenticate. + /// - The `caller` must authorize. fn interchain_transfer( env: &Env, caller: Address, diff --git a/contracts/stellar-interchain-token/src/interface.rs b/contracts/stellar-interchain-token/src/interface.rs index 8cf95507..2fb0a984 100644 --- a/contracts/stellar-interchain-token/src/interface.rs +++ b/contracts/stellar-interchain-token/src/interface.rs @@ -6,15 +6,47 @@ use crate::error::ContractError; #[allow(dead_code)] #[contractclient(name = "InterchainTokenClient")] pub trait InterchainTokenInterface: token::Interface + StellarAssetInterface { + /// Returns the Interchain Token ID fn token_id(env: &Env) -> BytesN<32>; + /// Returns if the specified address is a minter. fn is_minter(env: &Env, minter: Address) -> bool; + + /// Mints new tokens from a specified minter to a specified address. + /// + /// # Arguments + /// * `minter` - The address of the minter. + /// * `to` - The address to which the tokens will be minted. + /// * `amount` - The amount of tokens to be minted. + /// + /// # Errors + /// - [`ContractError::NotMinter`]: If the specified minter is not authorized to mint tokens. + /// - [`ContractError::InvalidAmount`]: If the specified amount is invalid (e.g. negative). + /// + /// # Authorization + /// - The `minter` must authorize. fn mint_from( env: &Env, minter: Address, to: Address, amount: i128, ) -> Result<(), ContractError>; + + /// Adds a new minter to the Interchain Token contract. + /// + /// # Arguments + /// * `minter` - The address to be added as a minter. + /// + /// # Authorization + /// - [`Self::owner`] must authorize. fn add_minter(env: &Env, minter: Address); + + /// Removes a new minter from the Interchain Token contract. + /// + /// # Arguments + /// * `minter` - The address to be added as a minter. + /// + /// # Authorization + /// - [`Self::owner`] must authorize. fn remove_minter(env: &Env, minter: Address); } diff --git a/contracts/stellar-upgrader/Cargo.toml b/contracts/stellar-upgrader/Cargo.toml index 1fd0ed35..eb5bd119 100644 --- a/contracts/stellar-upgrader/Cargo.toml +++ b/contracts/stellar-upgrader/Cargo.toml @@ -10,11 +10,16 @@ publish = true crate-type = ["cdylib", "rlib"] [dependencies] +cfg-if = { workspace = true } soroban-sdk = { workspace = true } stellar-axelar-std = { workspace = true } [dev-dependencies] soroban-sdk = { workspace = true, features = ["testutils"] } +[features] +library = [] # Only export the contract interface +testutils = ["soroban-sdk/testutils", "stellar-axelar-std/testutils"] + [lints] workspace = true diff --git a/contracts/stellar-upgrader/src/contract.rs b/contracts/stellar-upgrader/src/contract.rs index ea1c0b72..558a6831 100644 --- a/contracts/stellar-upgrader/src/contract.rs +++ b/contracts/stellar-upgrader/src/contract.rs @@ -5,6 +5,7 @@ use stellar_axelar_std::ensure; use stellar_axelar_std::interfaces::UpgradableClient; use crate::error::ContractError; +use crate::interface::UpgraderInterface; const MIGRATE: Symbol = symbol_short!("migrate"); @@ -14,8 +15,11 @@ pub struct Upgrader; #[contractimpl] impl Upgrader { pub fn __constructor(_env: Env) {} +} - pub fn upgrade( +#[contractimpl] +impl UpgraderInterface for Upgrader { + fn upgrade( env: Env, contract_address: Address, new_version: String, diff --git a/contracts/stellar-upgrader/src/interface.rs b/contracts/stellar-upgrader/src/interface.rs new file mode 100644 index 00000000..eafd185e --- /dev/null +++ b/contracts/stellar-upgrader/src/interface.rs @@ -0,0 +1,24 @@ +use soroban_sdk::{Address, BytesN, Env, String, Val}; + +use crate::error::ContractError; + +pub trait UpgraderInterface { + /// Upgrades and migrates a contract atomically to a new version using the provided WASM hash and migration data. + /// + /// # Arguments + /// * `contract_address` - The address of the contract to be upgraded. + /// * `new_version` - The new version string for the contract. + /// * `new_wasm_hash` - The hash of the new WASM code for the contract. + /// * `migration_data` - The data to be used during the migration process. + /// + /// # Errors + /// - [`ContractError::SameVersion`]: If the new version is the same as the current version. + /// - [`ContractError::UnexpectedNewVersion`]: If the contract version after the upgrade does not match the expected new version. + fn upgrade( + env: Env, + contract_address: Address, + new_version: String, + new_wasm_hash: BytesN<32>, + migration_data: soroban_sdk::Vec, + ) -> Result<(), ContractError>; +} diff --git a/contracts/stellar-upgrader/src/lib.rs b/contracts/stellar-upgrader/src/lib.rs index 95280c21..8e581eed 100644 --- a/contracts/stellar-upgrader/src/lib.rs +++ b/contracts/stellar-upgrader/src/lib.rs @@ -2,10 +2,19 @@ #[cfg(test)] extern crate alloc; -mod contract; pub mod error; -pub use contract::{Upgrader, UpgraderClient}; +mod interface; #[cfg(test)] mod tests; + +cfg_if::cfg_if! { + if #[cfg(all(feature = "library", not(feature = "testutils")))] { + pub use interface::{UpgraderClient, UpgraderInterface}; + } else { + mod contract; + + pub use contract::{Upgrader, UpgraderClient}; + } +}