diff --git a/Cargo.toml b/Cargo.toml index 28aae39e03..c93fdfe8d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -145,6 +145,7 @@ members = [ "contracts/feature-tests/composability/local-esdt-and-nft/meta", "contracts/feature-tests/composability/promises-features", "contracts/feature-tests/composability/promises-features/meta", + "contracts/feature-tests/composability/promises-features/interactor", "contracts/feature-tests/composability/proxy-test-first", "contracts/feature-tests/composability/proxy-test-first/meta", "contracts/feature-tests/composability/proxy-test-second", diff --git a/contracts/feature-tests/composability/promises-features/interactor/.gitignore b/contracts/feature-tests/composability/promises-features/interactor/.gitignore new file mode 100644 index 0000000000..39198ce2e0 --- /dev/null +++ b/contracts/feature-tests/composability/promises-features/interactor/.gitignore @@ -0,0 +1,3 @@ +# Pem files are used for interactions, but shouldn't be committed +*.pem +state.toml diff --git a/contracts/feature-tests/composability/promises-features/interactor/Cargo.toml b/contracts/feature-tests/composability/promises-features/interactor/Cargo.toml new file mode 100644 index 0000000000..254f760509 --- /dev/null +++ b/contracts/feature-tests/composability/promises-features/interactor/Cargo.toml @@ -0,0 +1,28 @@ +[package] +name = "rust-interact" +version = "0.0.0" +authors = ["you"] +edition = "2021" +publish = false + +[[bin]] +name = "rust-interact" +path = "src/interactor_main.rs" + +[lib] +path = "src/interact.rs" + +[dependencies.promises-features] +path = ".." + +[dependencies.multiversx-sc-snippets] +version = "0.56.0" +path = "../../../../../framework/snippets" + +[dependencies] +clap = { version = "4.4.7", features = ["derive"] } +serde = { version = "1.0", features = ["derive"] } +toml = "0.8.6" + +[features] +chain-simulator-tests = [] diff --git a/contracts/feature-tests/composability/promises-features/interactor/config.toml b/contracts/feature-tests/composability/promises-features/interactor/config.toml new file mode 100644 index 0000000000..c6f9a97a1d --- /dev/null +++ b/contracts/feature-tests/composability/promises-features/interactor/config.toml @@ -0,0 +1,5 @@ +# chain_type = 'simulator' +# gateway_uri = 'http://localhost:8085' + +chain_type = 'real' +gateway_uri = 'https://devnet-gateway.multiversx.com' diff --git a/contracts/feature-tests/composability/promises-features/interactor/src/config.rs b/contracts/feature-tests/composability/promises-features/interactor/src/config.rs new file mode 100644 index 0000000000..2d072b4bfb --- /dev/null +++ b/contracts/feature-tests/composability/promises-features/interactor/src/config.rs @@ -0,0 +1,51 @@ +#![allow(unused)] + +use serde::Deserialize; +use std::io::Read; + +/// Config file +const CONFIG_FILE: &str = "config.toml"; + +#[derive(Debug, Deserialize)] +#[serde(rename_all = "lowercase")] +pub enum ChainType { + Real, + Simulator, +} + +/// Contract Interact configuration +#[derive(Debug, Deserialize)] +pub struct Config { + pub gateway_uri: String, + pub chain_type: ChainType, +} + +impl Config { + // Deserializes config from file + pub fn new() -> Self { + let mut file = std::fs::File::open(CONFIG_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } + + pub fn chain_simulator_config() -> Self { + Config { + gateway_uri: "http://localhost:8085".to_owned(), + chain_type: ChainType::Simulator, + } + } + + // Returns the gateway URI + pub fn gateway_uri(&self) -> &str { + &self.gateway_uri + } + + // Returns if chain type is chain simulator + pub fn use_chain_simulator(&self) -> bool { + match self.chain_type { + ChainType::Real => false, + ChainType::Simulator => true, + } + } +} diff --git a/contracts/feature-tests/composability/promises-features/interactor/src/interact.rs b/contracts/feature-tests/composability/promises-features/interactor/src/interact.rs new file mode 100644 index 0000000000..ad3a1ced3b --- /dev/null +++ b/contracts/feature-tests/composability/promises-features/interactor/src/interact.rs @@ -0,0 +1,502 @@ +pub mod config; +mod interactor_cli; +mod interactor_state; + +use clap::Parser; +use config::Config; +use interactor_state::State; +use multiversx_sc_snippets::imports::*; +use promises_features::promises_feature_proxy; + +const CODE_PATH: MxscPath = MxscPath::new("output/promises-features.mxsc.json"); + +pub async fn promises_features_cli() { + env_logger::init(); + + let config = Config::new(); + let mut interact = ContractInteract::new(config).await; + + let cli = interactor_cli::InteractCli::parse(); + match &cli.command { + Some(interactor_cli::InteractCliCommand::Deploy) => interact.deploy().await, + Some(interactor_cli::InteractCliCommand::CallbackData) => interact.callback_data().await, + Some(interactor_cli::InteractCliCommand::CallbackDataAtIndex(args)) => { + interact.callback_data_at_index(args.index).await + }, + Some(interactor_cli::InteractCliCommand::ClearCallbackData) => { + interact.clear_callback_data().await + }, + Some(interactor_cli::InteractCliCommand::ForwardPromiseAcceptFunds(args)) => { + interact + .forward_promise_accept_funds( + &args.token_id, + args.token_nonce, + args.token_amount, + &Bech32Address::from_bech32_string(args.to.clone()), + ) + .await + }, + Some(interactor_cli::InteractCliCommand::ForwardPromiseRetrieveFunds(args)) => { + interact + .forward_promise_retrieve_funds( + &args.token_id, + args.token_nonce, + args.token_amount, + &Bech32Address::from_bech32_string(args.to.clone()), + ) + .await + }, + Some(interactor_cli::InteractCliCommand::ForwardPaymentCallback(args)) => { + interact + .forward_payment_callback( + &args.token_id, + args.token_nonce, + args.token_amount, + &Bech32Address::from_bech32_string(args.to.clone()), + ) + .await + }, + Some(interactor_cli::InteractCliCommand::PromiseRawSingleToken(args)) => { + let promise_args_vec: Vec<&str> = args.args.split(",").collect(); + + interact + .promise_raw_single_token( + &args.token_id, + args.token_nonce, + args.token_amount, + &Bech32Address::from_bech32_string(args.to.clone()), + &args.endpoint_name, + args.gas_limit, + args.extra_gas_for_callback, + &promise_args_vec, + ) + .await + }, + Some(interactor_cli::InteractCliCommand::PromiseRawMultiTransfer(args)) => { + let payment: EsdtTokenPaymentMultiValue = + EsdtTokenPaymentMultiValue::from(EsdtTokenPayment::new( + TokenIdentifier::from_esdt_bytes(&b""[..]), + 0u64, + BigUint::::from(0u128), + )); + + let mut token_payment_args = MultiValueEncoded::new(); + token_payment_args.push(payment); + + interact + .promise_raw_multi_transfer( + &Bech32Address::from_bech32_string(args.to.clone()), + &args.endpoint_name, + args.extra_gas_for_callback, + token_payment_args, + ) + .await + }, + Some(interactor_cli::InteractCliCommand::ForwardSyncRetrieveFundsBt(args)) => { + interact + .forward_sync_retrieve_funds_bt( + &Bech32Address::from_bech32_string(args.to.clone()), + &args.token_id, + args.token_nonce, + args.token_amount, + ) + .await + }, + Some(interactor_cli::InteractCliCommand::ForwardSyncRetrieveFundsBtTwice(args)) => { + interact + .forward_sync_retrieve_funds_bt_twice( + &Bech32Address::from_bech32_string(args.to.clone()), + &args.token_id, + args.token_nonce, + args.token_amount, + ) + .await + }, + Some(interactor_cli::InteractCliCommand::ForwardPromiseRetrieveFundsBackTransfers( + args, + )) => { + interact + .forward_promise_retrieve_funds_back_transfers( + &Bech32Address::from_bech32_string(args.to.clone()), + &args.token_id, + args.token_nonce, + args.token_amount, + ) + .await + }, + _ => {}, + } +} + +pub struct ContractInteract { + pub interactor: Interactor, + pub wallet_address: Address, + pub state: State, +} + +impl ContractInteract { + pub async fn new(config: Config) -> Self { + let mut interactor = Interactor::new(config.gateway_uri()) + .await + .use_chain_simulator(config.use_chain_simulator()); + + interactor.set_current_dir_from_workspace( + "contracts/feature-tests/composability/promises-features", + ); + let wallet_address = interactor.register_wallet(test_wallets::alice()).await; + + // Useful in the chain simulator setting + // generate blocks until ESDTSystemSCAddress is enabled + interactor.generate_blocks_until_epoch(1).await.unwrap(); + + ContractInteract { + interactor, + wallet_address, + state: State::load_state(), + } + } + + pub async fn deploy(&mut self) { + let new_address = self + .interactor + .tx() + .from(&self.wallet_address) + .gas(51_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .init() + .code(CODE_PATH) + .returns(ReturnsNewAddress) + .run() + .await; + + let new_address_bech32 = bech32::encode(&new_address); + self.state + .set_promises_features_address(Bech32Address::from_bech32_string( + new_address_bech32.clone(), + )); + + println!("new address: {new_address_bech32}"); + } + + pub async fn callback_data(&mut self) { + let response = self + .interactor + .query() + .to(self.state.current_promises_features_address()) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .callback_data() + .returns(ReturnsHandledOrError::new().returns(ReturnsResultUnmanaged)) + .run() + .await; + + match response { + Ok(result) => { + println!("Callbacks stored"); + for r in result.into_vec() { + let args_str: Vec = r + .args + .into_vec() + .iter() + .map(|arg| arg.to_string()) + .collect(); + + println!( + "{} | {} | {} | {} | {}", + r.callback_name, + r.token_identifier.into_name(), + r.token_nonce, + r.token_amount + .to_u64() + .unwrap_or_else(|| panic!("unable to parse token amount")), + args_str.join(", ") + ); + } + }, + Err(_) => panic!("Cannot retrieve CallbackData storage!"), + } + } + + pub async fn callback_data_at_index(&mut self, index: u32) { + let response = self + .interactor + .query() + .to(self.state.current_promises_features_address()) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .callback_data_at_index(index) + .returns(ReturnsHandledOrError::new().returns(ReturnsResultUnmanaged)) + .run() + .await; + + match response { + Ok(result) => { + let (callback_name, token_identifier, token_nonce, token_amount, args) = + result.into_tuple(); + let args_str: Vec = args + .0 + .iter() + .map(|arg| String::from_utf8(arg.to_vec()).unwrap()) + .collect(); + println!( + "{} | {} | {} | {} | {}", + String::from_utf8(callback_name).unwrap(), + token_identifier.into_name(), + token_nonce, + token_amount, + args_str.join(", ") + ) + }, + Err(_) => panic!("Cannot retrieve CallbackData at index {index} storage!"), + } + } + + pub async fn clear_callback_data(&mut self) { + self.interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .clear_callback_data() + .run() + .await; + + println!("DONE cleared callback data"); + } + + pub async fn forward_promise_accept_funds( + &mut self, + token_id: &str, + token_nonce: u64, + token_amount: u64, + to: &Bech32Address, + ) { + let payment: EsdtTokenPayment = EsdtTokenPayment::new( + TokenIdentifier::from(token_id), + token_nonce, + token_amount.into(), + ); + + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .forward_promise_accept_funds(to) + .payment(payment) + .returns(ReturnsHandledOrError::new()) + .run() + .await; + + match response { + Ok(_) => { + println!("forward_promise_accept_funds done successfully. Params used: {token_id} | {token_nonce} | {token_amount} | {}", to.to_bech32_expr()) + }, + Err(err) => panic!("FAILED: forward_promise_accept_funds. Reason: {}. Params used: {token_id} | {token_nonce} | {token_amount} | {}", err.message, to.to_bech32_expr()), + } + } + + pub async fn forward_promise_retrieve_funds( + &mut self, + token: &str, + token_nonce: u64, + amount: u64, + to: &Bech32Address, + ) { + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .forward_promise_retrieve_funds(to, token, token_nonce, amount) + .returns(ReturnsHandledOrError::new()) + .run() + .await; + + match response { + Ok(_) => { + println!("forward_promise_retrieve_funds done successfully. Params used: {token} | {token_nonce} | {amount} | {}", to.to_bech32_expr()) + }, + Err(err) => panic!("FAILED: forward_promise_retrieve_funds. Reason: {}. Params used: {token} | {token_nonce} | {amount} | {}", err.message, to.to_bech32_expr()), + } + } + + pub async fn forward_payment_callback( + &mut self, + token_id: &str, + token_nonce: u64, + token_amount: u64, + to: &Bech32Address, + ) { + let payment: EsdtTokenPayment = EsdtTokenPayment::new( + TokenIdentifier::from(token_id), + token_nonce, + token_amount.into(), + ); + + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .forward_payment_callback(to) + .payment(payment) + .returns(ReturnsHandledOrError::new()) + .run() + .await; + + match response { + Ok(_) => println!("forward_payment_callback done successfully. Params used: {token_id} | {token_nonce} | {token_amount} | {}", to.to_bech32_expr()), + Err(err) => panic!("FAILED: forward_payment_callback. Reason: {}. Params used: {token_id} | {token_nonce} | {token_amount} | {}", err.message, to.to_bech32_expr()), + } + } + + pub async fn promise_raw_single_token( + &mut self, + token_id: &str, + token_nonce: u64, + token_amount: u64, + to: &Bech32Address, + endpoint_name: &str, + gas_limit: u64, + extra_gas_for_callback: u64, + args: &Vec<&str>, + ) { + let payment: EsdtTokenPayment = EsdtTokenPayment::new( + TokenIdentifier::from(token_id), + token_nonce, + token_amount.into(), + ); + + let promise_args: MultiValueEncoded> = args + .into_iter() + .map(|arg| ManagedBuffer::from(arg.as_bytes())) + .collect(); + + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .promise_raw_single_token( + to, + endpoint_name, + gas_limit, + extra_gas_for_callback, + promise_args, + ) + .payment(payment) + .returns(ReturnsHandledOrError::new()) + .run() + .await; + + match response { + Ok(_) => println!("promise_raw_single_token done successfully. Params used: {token_id} | {token_nonce} | {token_amount} | {} | {endpoint_name} | {gas_limit} | {extra_gas_for_callback} | {}", to.to_bech32_expr(), args.join(",")), + Err(err) => panic!("FAILED: promise_raw_single_token. Reason: {}. Params used: {token_id} | {token_nonce} | {token_amount} | {} | {endpoint_name} | {gas_limit} | {extra_gas_for_callback} | {}", err.message, to.to_bech32_expr(), args.join(",")), + } + } + + pub async fn promise_raw_multi_transfer( + &mut self, + to: &Bech32Address, + endpoint_name: &str, + extra_gas_for_callback: u64, + payment: MultiValueEncoded>, + ) { + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .promise_raw_multi_transfer(to, endpoint_name, extra_gas_for_callback, payment) + .returns(ReturnsHandledOrError::new()) + .run() + .await; + + match response { + Ok(_) => println!("promise_raw_multi_transfer done successfully. Params used: {} | {endpoint_name} | {extra_gas_for_callback}", to.to_bech32_expr()), + Err(err) => panic!("FAILED: promise_raw_multi_transfer. Reason: {}. Params used: {} | {endpoint_name} | {extra_gas_for_callback}", err.message, to.to_bech32_expr()), + } + } + + pub async fn forward_sync_retrieve_funds_bt( + &mut self, + to: &Bech32Address, + token: &str, + token_nonce: u64, + amount: u64, + ) { + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .forward_sync_retrieve_funds_bt(to, token, token_nonce, amount) + .returns(ReturnsHandledOrError::new()) + .run() + .await; + + match response { + Ok(_) => println!("forward_sync_retrieve_funds_bt done successfully. Params used: {} | {token} | {token_nonce} | {amount}", to.to_bech32_expr()), + Err(err) => panic!("FAILED: forward_sync_retrieve_funds_bt. Reason: {}. Params used: {} | {token} | {token_nonce} | {amount}", err.message, to.to_bech32_expr()), + } + } + + pub async fn forward_sync_retrieve_funds_bt_twice( + &mut self, + to: &Bech32Address, + token: &str, + token_nonce: u64, + amount: u64, + ) { + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .forward_sync_retrieve_funds_bt_twice(to, token, token_nonce, amount) + .returns(ReturnsHandledOrError::new()) + .run() + .await; + + match response { + Ok(_) => println!("forward_sync_retrieve_funds_bt_twice done successfully. Params used: {} | {token} | {token_nonce} | {amount}", to.to_bech32_expr()), + Err(err) => panic!("FAILED: forward_sync_retrieve_funds_bt_twice. Reason: {}. Params used: {} | {token} | {token_nonce} | {amount}", err.message, to.to_bech32_expr()), + } + } + + pub async fn forward_promise_retrieve_funds_back_transfers( + &mut self, + to: &Bech32Address, + token: &str, + token_nonce: u64, + amount: u64, + ) { + let response = self + .interactor + .tx() + .from(&self.wallet_address) + .to(self.state.current_promises_features_address()) + .gas(30_000_000u64) + .typed(promises_feature_proxy::PromisesFeaturesProxy) + .forward_promise_retrieve_funds_back_transfers(to, token, token_nonce, amount) + .returns(ReturnsResultUnmanaged) + .run() + .await; + + println!("Result: {response:?}"); + } +} diff --git a/contracts/feature-tests/composability/promises-features/interactor/src/interactor_cli.rs b/contracts/feature-tests/composability/promises-features/interactor/src/interactor_cli.rs new file mode 100644 index 0000000000..640cbb67c4 --- /dev/null +++ b/contracts/feature-tests/composability/promises-features/interactor/src/interactor_cli.rs @@ -0,0 +1,105 @@ +use clap::{Args, Parser, Subcommand}; + +/// Adder Interact CLI +#[derive(Default, PartialEq, Eq, Debug, Parser)] +#[command(version, about)] +#[command(propagate_version = true)] +pub struct InteractCli { + #[command(subcommand)] + pub command: Option, +} + +/// Adder Interact CLI Commands +#[derive(Clone, PartialEq, Eq, Debug, Subcommand)] +pub enum InteractCliCommand { + #[command(name = "deploy", about = "Deploy contract")] + Deploy, + #[command(name = "callback-data", about = "Callback data")] + CallbackData, + #[command(name = "callback-data-at-index", about = "Callback data at index")] + CallbackDataAtIndex(CallbackDataAtIndexArgs), + #[command(name = "clear_callback_data", about = "Clear callback data")] + ClearCallbackData, + #[command( + name = "forward-promise-accept-funds", + about = "Forward promise accept funds" + )] + ForwardPromiseAcceptFunds(ForwardPromiseFundsArgs), + #[command( + name = "forward-promise-retrieve-funds", + about = "Forward promise retrieve funds" + )] + ForwardPromiseRetrieveFunds(ForwardPromiseFundsArgs), + #[command(name = "forward-payment-callback", about = "Forward payment callback")] + ForwardPaymentCallback(ForwardPromiseFundsArgs), + #[command(name = "promise-raw-single-token", about = "Promise raw single token")] + PromiseRawSingleToken(PromiseRawSingleTokenArgs), + #[command( + name = "promise-raw-multi-transfer", + about = "Promise raw multi transfer" + )] + PromiseRawMultiTransfer(PromiseRawMultiTransferArgs), + #[command( + name = "forward-sync-retrieve-funds-bt", + about = "Forward sync retrieve funds bt" + )] + ForwardSyncRetrieveFundsBt(ForwardPromiseFundsArgs), + #[command( + name = "forward-sync-retrieve-funds-bt-twice", + about = "Forward sync retrieve funds bt twice" + )] + ForwardSyncRetrieveFundsBtTwice(ForwardPromiseFundsArgs), + #[command( + name = "forward-promise-retrieve-funds-back-transfers", + about = "Forward sync retrieve funds bt twice" + )] + ForwardPromiseRetrieveFundsBackTransfers(ForwardPromiseFundsArgs), +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Args)] +pub struct CallbackDataAtIndexArgs { + #[arg(short = 'i', long = "index")] + pub index: u32, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Args)] +pub struct ForwardPromiseFundsArgs { + #[arg(short = 'i', long = "token-id")] + pub token_id: String, + #[arg(short = 'n', long = "token-nonce")] + pub token_nonce: u64, + #[arg(short = 'a', long = "token-amount")] + pub token_amount: u64, + #[arg(short = 't', long = "to")] + pub to: String, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Args)] +pub struct PromiseRawSingleTokenArgs { + #[arg(short = 'i', long = "token-id")] + pub token_id: String, + #[arg(short = 'n', long = "token-nonce")] + pub token_nonce: u64, + #[arg(short = 'a', long = "token-amount")] + pub token_amount: u64, + #[arg(short = 't', long = "to")] + pub to: String, + #[arg(short = 'e', long = "endpoint-name")] + pub endpoint_name: String, + #[arg(short = 'g', long = "gas-limit")] + pub gas_limit: u64, + #[arg(short = 'x', long = "extra-gas-for-callback")] + pub extra_gas_for_callback: u64, + #[arg(short = 'a', long = "args")] + pub args: String, +} + +#[derive(Default, Clone, PartialEq, Eq, Debug, Args)] +pub struct PromiseRawMultiTransferArgs { + #[arg(short = 't', long = "to")] + pub to: String, + #[arg(short = 'e', long = "endpoint-name")] + pub endpoint_name: String, + #[arg(short = 'x', long = "extra-gas-for-callback")] + pub extra_gas_for_callback: u64, +} diff --git a/contracts/feature-tests/composability/promises-features/interactor/src/interactor_main.rs b/contracts/feature-tests/composability/promises-features/interactor/src/interactor_main.rs new file mode 100644 index 0000000000..f29b22c6db --- /dev/null +++ b/contracts/feature-tests/composability/promises-features/interactor/src/interactor_main.rs @@ -0,0 +1,7 @@ +use multiversx_sc_snippets::imports::*; +use rust_interact::promises_features_cli; + +#[tokio::main] +async fn main() { + promises_features_cli().await; +} diff --git a/contracts/feature-tests/composability/promises-features/interactor/src/interactor_state.rs b/contracts/feature-tests/composability/promises-features/interactor/src/interactor_state.rs new file mode 100644 index 0000000000..8139dfdef1 --- /dev/null +++ b/contracts/feature-tests/composability/promises-features/interactor/src/interactor_state.rs @@ -0,0 +1,50 @@ +use multiversx_sc_snippets::imports::*; +use serde::{Deserialize, Serialize}; +use std::{ + io::{Read, Write}, + path::Path, +}; + +/// State file +const STATE_FILE: &str = "state.toml"; + +/// Promise-Features Interact state +#[derive(Debug, Default, Serialize, Deserialize)] +pub struct State { + promises_features_address: Option, +} + +impl State { + // Deserializes state from file + pub fn load_state() -> Self { + if Path::new(STATE_FILE).exists() { + let mut file = std::fs::File::open(STATE_FILE).unwrap(); + let mut content = String::new(); + file.read_to_string(&mut content).unwrap(); + toml::from_str(&content).unwrap() + } else { + Self::default() + } + } + + /// Sets the promise features address + pub fn set_promises_features_address(&mut self, address: Bech32Address) { + self.promises_features_address = Some(address); + } + + /// Returns the promise features contract + pub fn current_promises_features_address(&self) -> &Bech32Address { + self.promises_features_address + .as_ref() + .expect("no known promises features contract, deploy first") + } +} + +impl Drop for State { + // Serializes state to file + fn drop(&mut self) { + let mut file = std::fs::File::create(STATE_FILE).unwrap(); + file.write_all(toml::to_string(self).unwrap().as_bytes()) + .unwrap(); + } +}