diff --git a/contract/soroban/contracts/escrow/src/lib.rs b/contract/soroban/contracts/escrow/src/lib.rs index f812004..09241e5 100644 --- a/contract/soroban/contracts/escrow/src/lib.rs +++ b/contract/soroban/contracts/escrow/src/lib.rs @@ -1,22 +1,34 @@ #![no_std] -use soroban_sdk::{contract, contractimpl, vec, Env, String, Vec}; +use soroban_sdk::{ + contract, contracterror, contractimpl, contracttype, panic_with_error, Address, Env, +}; + +#[contracttype] +pub enum DataKey { + Admin, +} + +#[contracterror] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum SettlementError { + AlreadyInitialized = 1, +} #[contract] -pub struct Contract; +pub struct SettlementContract; -// This is a sample contract. Replace this placeholder with your own contract logic. -// A corresponding test example is available in `test.rs`. -// -// For comprehensive examples, visit . -// The repository includes use cases for the Stellar ecosystem, such as data storage on -// the blockchain, token swaps, liquidity pools, and more. -// -// Refer to the official documentation: -// . #[contractimpl] -impl Contract { - pub fn hello(env: Env, to: String) -> Vec { - vec![&env, String::from_str(&env, "Hello"), to] +impl SettlementContract { + pub fn initialize(env: Env, admin: Address) { + if env.storage().instance().has(&DataKey::Admin) { + panic_with_error!(&env, SettlementError::AlreadyInitialized); + } + admin.require_auth(); + env.storage().instance().set(&DataKey::Admin, &admin); + } + + pub fn get_admin(env: Env) -> Option
{ + env.storage().instance().get(&DataKey::Admin) } } diff --git a/contract/soroban/contracts/escrow/src/test.rs b/contract/soroban/contracts/escrow/src/test.rs index 0bdcba0..fa5939d 100644 --- a/contract/soroban/contracts/escrow/src/test.rs +++ b/contract/soroban/contracts/escrow/src/test.rs @@ -1,21 +1,48 @@ #![cfg(test)] use super::*; -use soroban_sdk::{vec, Env, String}; +use soroban_sdk::testutils::Address as _; +use soroban_sdk::Env; #[test] -fn test() { +fn initialize_sets_admin() { let env = Env::default(); - let contract_id = env.register(Contract, ()); - let client = ContractClient::new(&env, &contract_id); + env.mock_all_auths(); - let words = client.hello(&String::from_str(&env, "Dev")); + let contract_id = env.register(SettlementContract, ()); + let client = SettlementContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.initialize(&admin); + + assert_eq!(client.get_admin(), Some(admin)); +} + +#[test] +fn double_initialize_fails_with_already_initialized() { + let env = Env::default(); + env.mock_all_auths(); + + let contract_id = env.register(SettlementContract, ()); + let client = SettlementContractClient::new(&env, &contract_id); + + let admin = Address::generate(&env); + client.initialize(&admin); + + let result = client.try_initialize(&admin); assert_eq!( - words, - vec![ - &env, - String::from_str(&env, "Hello"), - String::from_str(&env, "Dev"), - ] + result, + Err(Ok(soroban_sdk::Error::from_contract_error( + SettlementError::AlreadyInitialized as u32 + ))) ); } + +#[test] +fn get_admin_returns_none_before_initialize() { + let env = Env::default(); + let contract_id = env.register(SettlementContract, ()); + let client = SettlementContractClient::new(&env, &contract_id); + + assert_eq!(client.get_admin(), None); +} diff --git a/contract/soroban/contracts/escrow/test_snapshots/test/double_initialize_fails_with_already_initialized.1.json b/contract/soroban/contracts/escrow/test_snapshots/test/double_initialize_fails_with_already_initialized.1.json new file mode 100644 index 0000000..061af37 --- /dev/null +++ b/contract/soroban/contracts/escrow/test_snapshots/test/double_initialize_fails_with_already_initialized.1.json @@ -0,0 +1,142 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contract/soroban/contracts/escrow/test_snapshots/test/get_admin_returns_none_before_initialize.1.json b/contract/soroban/contracts/escrow/test_snapshots/test/get_admin_returns_none_before_initialize.1.json new file mode 100644 index 0000000..6a19fbf --- /dev/null +++ b/contract/soroban/contracts/escrow/test_snapshots/test/get_admin_returns_none_before_initialize.1.json @@ -0,0 +1,77 @@ +{ + "generators": { + "address": 1, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": null + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contract/soroban/contracts/escrow/test_snapshots/test/initialize_sets_admin.1.json b/contract/soroban/contracts/escrow/test_snapshots/test/initialize_sets_admin.1.json new file mode 100644 index 0000000..061af37 --- /dev/null +++ b/contract/soroban/contracts/escrow/test_snapshots/test/initialize_sets_admin.1.json @@ -0,0 +1,142 @@ +{ + "generators": { + "address": 2, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contract/soroban/contracts/fee-collector/src/lib.rs b/contract/soroban/contracts/fee-collector/src/lib.rs index 9d9302e..12376ee 100644 --- a/contract/soroban/contracts/fee-collector/src/lib.rs +++ b/contract/soroban/contracts/fee-collector/src/lib.rs @@ -1,6 +1,7 @@ #![no_std] use soroban_sdk::{ - contract, contractevent, contractimpl, contracttype, token, Address, Env, + contract, contracterror, contractevent, contractimpl, contracttype, panic_with_error, token, + Address, Env, }; #[contracttype] @@ -10,6 +11,12 @@ pub enum DataKey { Treasury, } +#[contracterror] +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum FeeCollectorError { + AlreadyInitialized = 1, +} + #[contractevent(data_format = "vec")] pub struct Initialized { #[topic] @@ -40,6 +47,9 @@ pub struct FeeCollectorContract; #[contractimpl] impl FeeCollectorContract { pub fn initialize(env: Env, admin: Address, fee_bps: u32, treasury: Address) { + if env.storage().instance().has(&DataKey::Admin) { + panic_with_error!(&env, FeeCollectorError::AlreadyInitialized); + } admin.require_auth(); env.storage().instance().set(&DataKey::Admin, &admin); env.storage().instance().set(&DataKey::FeeBps, &fee_bps); diff --git a/contract/soroban/contracts/fee-collector/src/test.rs b/contract/soroban/contracts/fee-collector/src/test.rs index 86ae0aa..a2ddede 100644 --- a/contract/soroban/contracts/fee-collector/src/test.rs +++ b/contract/soroban/contracts/fee-collector/src/test.rs @@ -79,6 +79,22 @@ fn set_fee_bps_above_limit_panics() { client.set_fee_bps(&201); } +#[test] +fn double_initialize_fails_with_already_initialized() { + let (env, contract_id, _token_address, admin, treasury) = setup(); + let client = FeeCollectorContractClient::new(&env, &contract_id); + + client.initialize(&admin, &75, &treasury); + + let result = client.try_initialize(&admin, &75, &treasury); + assert_eq!( + result, + Err(Ok(soroban_sdk::Error::from_contract_error( + FeeCollectorError::AlreadyInitialized as u32 + ))) + ); +} + #[test] #[should_panic] fn deduct_before_initialize_panics() { diff --git a/contract/soroban/contracts/fee-collector/test_snapshots/test/double_initialize_fails_with_already_initialized.1.json b/contract/soroban/contracts/fee-collector/test_snapshots/test/double_initialize_fails_with_already_initialized.1.json new file mode 100644 index 0000000..198ef54 --- /dev/null +++ b/contract/soroban/contracts/fee-collector/test_snapshots/test/double_initialize_fails_with_already_initialized.1.json @@ -0,0 +1,364 @@ +{ + "generators": { + "address": 5, + "nonce": 0, + "mux_id": 0 + }, + "auth": [ + [], + [ + [ + "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + { + "function": { + "contract_fn": { + "contract_address": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "function_name": "set_admin", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "function_name": "initialize", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + }, + { + "u32": 75 + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 23, + "sequence_number": 0, + "timestamp": 0, + "network_id": "0000000000000000000000000000000000000000000000000000000000000000", + "base_reserve": 0, + "min_persistent_entry_ttl": 4096, + "min_temp_entry_ttl": 16, + "max_entry_ttl": 6312000, + "ledger_entries": [ + [ + { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "account": { + "account_id": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "balance": "0", + "seq_num": "0", + "num_sub_entries": 0, + "inflation_dest": null, + "flags": 0, + "home_domain": "", + "thresholds": "01010101", + "signers": [], + "ext": "v0" + } + }, + "ext": "v0" + }, + null + ] + ], + [ + { + "contract_data": { + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V", + "key": { + "ledger_key_nonce": { + "nonce": "801925984706572462" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "FeeBps" + } + ] + }, + "val": { + "u32": 75 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Treasury" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAK3IM" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4095 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "ledger_key_nonce": { + "nonce": "5541220902715666415" + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6311999 + ] + ], + [ + { + "contract_data": { + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CBUSYNQKASUYFWYC3M2GUEDMX4AIVWPALDBYJPNK6554BREHTGZ2IUNF", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": "stellar_asset", + "storage": [ + { + "key": { + "symbol": "METADATA" + }, + "val": { + "map": [ + { + "key": { + "symbol": "decimal" + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "symbol": "name" + }, + "val": { + "string": "aaa:GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAGO6V" + } + }, + { + "key": { + "symbol": "symbol" + }, + "val": { + "string": "aaa" + } + } + ] + } + }, + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "AssetInfo" + } + ] + }, + "val": { + "vec": [ + { + "symbol": "AlphaNum4" + }, + { + "map": [ + { + "key": { + "symbol": "asset_code" + }, + "val": { + "string": "aaa\\0" + } + }, + { + "key": { + "symbol": "issuer" + }, + "val": { + "bytes": "0000000000000000000000000000000000000000000000000000000000000003" + } + } + ] + } + ] + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 120960 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4095 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file