diff --git a/crates/contracts/core/src/master_account/src/Cargo.toml b/crates/contracts/core/src/master_account/src/Cargo.toml new file mode 100644 index 0000000..e69de29 diff --git a/crates/contracts/core/src/master_account/src/errors.rs b/crates/contracts/core/src/master_account/src/errors.rs new file mode 100644 index 0000000..6def752 --- /dev/null +++ b/crates/contracts/core/src/master_account/src/errors.rs @@ -0,0 +1,10 @@ +use soroban_sdk::contracterror; + +#[contracterror] +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[repr(u32)] +pub enum ContractError { + NotAuthorized = 1, + InvalidThreshold = 2, + SignerNotFound = 3, +} \ No newline at end of file diff --git a/crates/contracts/core/src/master_account/src/events.rs b/crates/contracts/core/src/master_account/src/events.rs new file mode 100644 index 0000000..9ef6ed2 --- /dev/null +++ b/crates/contracts/core/src/master_account/src/events.rs @@ -0,0 +1,15 @@ +use soroban_sdk::{Address, Env, symbol_short}; + +pub fn admin_rotated(env: &Env, new_admin: Address) { + env.events().publish( + (symbol_short!("admin_rotated"),), + new_admin, + ); +} + +pub fn signer_added(env: &Env, signer: Address) { + env.events().publish( + (symbol_short!("signer_added"),), + signer, + ); +} \ No newline at end of file diff --git a/crates/contracts/core/src/master_account/src/lib.rs b/crates/contracts/core/src/master_account/src/lib.rs new file mode 100644 index 0000000..f9689b5 --- /dev/null +++ b/crates/contracts/core/src/master_account/src/lib.rs @@ -0,0 +1,85 @@ +#![no_std] + +use soroban_sdk::{ + contract, contractimpl, Address, Env, Vec +}; + +mod storage; +mod errors; +mod events; + +use storage::DataKey; +use errors::ContractError; + +#[contract] +pub struct MasterAccountContract; + +#[contractimpl] +impl MasterAccountContract { + + // Initialize contract + pub fn initialize( + env: Env, + admin: Address, + threshold: u32, + ) { + if env.storage().has(&DataKey::Admin) { + panic!("Already initialized"); + } + + admin.require_auth(); + + env.storage().set(&DataKey::Admin, &admin); + env.storage().set(&DataKey::Signers, &Vec::
::new(&env)); + env.storage().set(&DataKey::Threshold, &threshold); + } + + // Rotate admin + pub fn rotate_admin(env: Env, new_admin: Address) { + let admin: Address = env.storage().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + env.storage().set(&DataKey::Admin, &new_admin); + events::admin_rotated(&env, new_admin); + } + + // Add signer (for multisig) + pub fn add_signer(env: Env, signer: Address) { + let admin: Address = env.storage().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + let mut signers: Vec
= + env.storage().get(&DataKey::Signers).unwrap(); + + signers.push_back(signer.clone()); + + env.storage().set(&DataKey::Signers, &signers); + + events::signer_added(&env, signer); + } + + // Update threshold + pub fn set_threshold(env: Env, threshold: u32) { + let admin: Address = env.storage().get(&DataKey::Admin).unwrap(); + admin.require_auth(); + + if threshold == 0 { + panic_with_error!(&env, ContractError::InvalidThreshold); + } + + env.storage().set(&DataKey::Threshold, &threshold); + } + + // Getters + pub fn get_admin(env: Env) -> Address { + env.storage().get(&DataKey::Admin).unwrap() + } + + pub fn get_threshold(env: Env) -> u32 { + env.storage().get(&DataKey::Threshold).unwrap() + } + + pub fn get_signers(env: Env) -> Vec
{ + env.storage().get(&DataKey::Signers).unwrap() + } +} \ No newline at end of file diff --git a/crates/contracts/core/src/master_account/src/storage.rs b/crates/contracts/core/src/master_account/src/storage.rs new file mode 100644 index 0000000..00af868 --- /dev/null +++ b/crates/contracts/core/src/master_account/src/storage.rs @@ -0,0 +1,8 @@ +use soroban_sdk::{contracttype, Address}; + +#[contracttype] +pub enum DataKey { + Admin, + Signers, + Threshold, +} \ No newline at end of file diff --git a/crates/contracts/tests/master_account_tests.rs b/crates/contracts/tests/master_account_tests.rs new file mode 100644 index 0000000..bf21c48 --- /dev/null +++ b/crates/contracts/tests/master_account_tests.rs @@ -0,0 +1,19 @@ +use soroban_sdk::{ + testutils::{Address as _}, + Address, Env +}; + +use master_account::MasterAccountContract; + +#[test] +fn test_initialize() { + let env = Env::default(); + let admin = Address::generate(&env); + + let contract_id = env.register_contract(None, MasterAccountContract); + let client = MasterAccountContractClient::new(&env, &contract_id); + + client.initialize(&admin, &1); + + assert_eq!(client.get_admin(), admin); +} \ No newline at end of file