diff --git a/Backend/package-lock.json b/Backend/package-lock.json
index c80c7412..a9921e49 100644
--- a/Backend/package-lock.json
+++ b/Backend/package-lock.json
@@ -22,7 +22,6 @@
"@willsoto/nestjs-prometheus": "^6.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
- "ethers": "^6.15.0",
"iterare": "^1.2.1",
"joi": "^18.2.1",
"nest-winston": "^1.10.2",
@@ -63,12 +62,6 @@
"typescript-eslint": "^8.20.0"
}
},
- "node_modules/@adraffy/ens-normalize": {
- "version": "1.10.1",
- "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz",
- "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==",
- "license": "MIT"
- },
"node_modules/@ampproject/remapping": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
@@ -2876,30 +2869,6 @@
"typeorm": "^0.3.0"
}
},
- "node_modules/@noble/curves": {
- "version": "1.2.0",
- "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz",
- "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==",
- "license": "MIT",
- "dependencies": {
- "@noble/hashes": "1.3.2"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/@noble/curves/node_modules/@noble/hashes": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
- "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
"node_modules/@noble/ed25519": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@noble/ed25519/-/ed25519-3.1.0.tgz",
@@ -3250,7 +3219,7 @@
"version": "1.12.11",
"resolved": "https://registry.npmjs.org/@swc/core/-/core-1.12.11.tgz",
"integrity": "sha512-P3GM+0lqjFctcp5HhR9mOcvLSX3SptI9L1aux0Fuvgt8oH4f92rCUrkodAa0U2ktmdjcyIiG37xg2mb/dSCYSA==",
- "devOptional": true,
+ "dev": true,
"hasInstallScript": true,
"license": "Apache-2.0",
"dependencies": {
@@ -3292,6 +3261,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3308,6 +3278,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3324,6 +3295,7 @@
"cpu": [
"arm"
],
+ "dev": true,
"license": "Apache-2.0",
"optional": true,
"os": [
@@ -3340,6 +3312,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3356,6 +3329,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3372,6 +3346,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3388,6 +3363,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3404,6 +3380,7 @@
"cpu": [
"arm64"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3420,6 +3397,7 @@
"cpu": [
"ia32"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3436,6 +3414,7 @@
"cpu": [
"x64"
],
+ "dev": true,
"license": "Apache-2.0 AND MIT",
"optional": true,
"os": [
@@ -3449,14 +3428,14 @@
"version": "0.1.3",
"resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz",
"integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==",
- "devOptional": true,
+ "dev": true,
"license": "Apache-2.0"
},
"node_modules/@swc/types": {
"version": "0.1.23",
"resolved": "https://registry.npmjs.org/@swc/types/-/types-0.1.23.tgz",
"integrity": "sha512-u1iIVZV9Q0jxY+yM2vw/hZGDNudsN85bBpTqzAQ9rzkxW9D+e3aEM4Han+ow518gSewkXgjmEK0BD79ZcNVgPw==",
- "devOptional": true,
+ "dev": true,
"license": "Apache-2.0",
"dependencies": {
"@swc/counter": "^0.1.3"
@@ -4922,12 +4901,6 @@
"node": ">=0.4.0"
}
},
- "node_modules/aes-js": {
- "version": "4.0.0-beta.5",
- "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz",
- "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==",
- "license": "MIT"
- },
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
@@ -6893,67 +6866,6 @@
"node": ">= 0.6"
}
},
- "node_modules/ethers": {
- "version": "6.15.0",
- "resolved": "https://registry.npmjs.org/ethers/-/ethers-6.15.0.tgz",
- "integrity": "sha512-Kf/3ZW54L4UT0pZtsY/rf+EkBU7Qi5nnhonjUb8yTXcxH3cdcWrV2cRyk0Xk/4jK6OoHhxxZHriyhje20If2hQ==",
- "funding": [
- {
- "type": "individual",
- "url": "https://github.com/sponsors/ethers-io/"
- },
- {
- "type": "individual",
- "url": "https://www.buymeacoffee.com/ricmoo"
- }
- ],
- "license": "MIT",
- "dependencies": {
- "@adraffy/ens-normalize": "1.10.1",
- "@noble/curves": "1.2.0",
- "@noble/hashes": "1.3.2",
- "@types/node": "22.7.5",
- "aes-js": "4.0.0-beta.5",
- "tslib": "2.7.0",
- "ws": "8.17.1"
- },
- "engines": {
- "node": ">=14.0.0"
- }
- },
- "node_modules/ethers/node_modules/@noble/hashes": {
- "version": "1.3.2",
- "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz",
- "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==",
- "license": "MIT",
- "engines": {
- "node": ">= 16"
- },
- "funding": {
- "url": "https://paulmillr.com/funding/"
- }
- },
- "node_modules/ethers/node_modules/@types/node": {
- "version": "22.7.5",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz",
- "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==",
- "license": "MIT",
- "dependencies": {
- "undici-types": "~6.19.2"
- }
- },
- "node_modules/ethers/node_modules/tslib": {
- "version": "2.7.0",
- "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz",
- "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==",
- "license": "0BSD"
- },
- "node_modules/ethers/node_modules/undici-types": {
- "version": "6.19.8",
- "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
- "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
- "license": "MIT"
- },
"node_modules/events": {
"version": "3.3.0",
"resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz",
@@ -13258,27 +13170,6 @@
"dev": true,
"license": "ISC"
},
- "node_modules/ws": {
- "version": "8.17.1",
- "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz",
- "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==",
- "license": "MIT",
- "engines": {
- "node": ">=10.0.0"
- },
- "peerDependencies": {
- "bufferutil": "^4.0.1",
- "utf-8-validate": ">=5.0.2"
- },
- "peerDependenciesMeta": {
- "bufferutil": {
- "optional": true
- },
- "utf-8-validate": {
- "optional": true
- }
- }
- },
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
diff --git a/Backend/package.json b/Backend/package.json
index 143af2d4..c915fa2f 100644
--- a/Backend/package.json
+++ b/Backend/package.json
@@ -36,7 +36,6 @@
"@willsoto/nestjs-prometheus": "^6.1.0",
"class-transformer": "^0.5.1",
"class-validator": "^0.14.2",
- "ethers": "^6.15.0",
"iterare": "^1.2.1",
"joi": "^18.2.1",
"nest-winston": "^1.10.2",
diff --git a/contracts/src/multi.rs b/contracts/src/multi.rs
deleted file mode 100644
index 4c32b91d..00000000
--- a/contracts/src/multi.rs
+++ /dev/null
@@ -1,721 +0,0 @@
-#![no_std]
-use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, String, Vec};
-
-// ---------------------------------------------------------------------------
-// Data types
-// ---------------------------------------------------------------------------
-
-#[contracttype]
-#[derive(Clone)]
-pub struct Gist {
- pub gist_id: u64,
- pub author: Option
,
- pub location_cell: String,
- pub content_hash: String,
- pub created_at: u64,
-}
-
-// ---------------------------------------------------------------------------
-// Storage keys
-// ---------------------------------------------------------------------------
-
-const GIST_COUNT: soroban_sdk::Symbol = symbol_short!("GCOUNT");
-
-// ---------------------------------------------------------------------------
-// Contract
-// ---------------------------------------------------------------------------
-
-#[contract]
-pub struct GistRegistry;
-
-#[contractimpl]
-impl GistRegistry {
- /// Register a new gist on-chain. Returns the assigned gist_id.
- pub fn post_gist(
- env: Env,
- author: Option,
- location_cell: String,
- content_hash: String,
- ) -> u64 {
- let gist_id: u64 = env.storage().instance().get(&GIST_COUNT).unwrap_or(0) + 1;
-
- let gist = Gist {
- gist_id,
- author,
- location_cell: location_cell.clone(),
- content_hash,
- created_at: env.ledger().timestamp(),
- };
-
- env.storage().persistent().set(&gist_id, &gist);
- env.storage().instance().set(&GIST_COUNT, &gist_id);
-
- gist_id
- }
-
- /// Retrieve a gist record by its id.
- pub fn get_gist(env: Env, gist_id: u64) -> Option {
- env.storage().persistent().get(&gist_id)
- }
-
- /// List gists whose location_cell matches the given cell string.
- /// `cursor` is the gist_id to start scanning from (inclusive); `limit` caps results.
- pub fn list_gists_by_cell(
- env: Env,
- location_cell: String,
- cursor: u64,
- limit: u32,
- ) -> Vec {
- let total: u64 = env.storage().instance().get(&GIST_COUNT).unwrap_or(0);
- let mut results = Vec::new(&env);
- let mut count: u32 = 0;
-
- let start = if cursor == 0 { 1 } else { cursor };
-
- for id in start..=total {
- if count >= limit {
- break;
- }
- if let Some(gist) = env.storage().persistent().get::(&id) {
- if gist.location_cell == location_cell {
- results.push_back(gist);
- count += 1;
- }
- }
- }
-
- results
- }
-}
-
-// ---------------------------------------------------------------------------
-// Tests
-// ---------------------------------------------------------------------------
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use soroban_sdk::testutils::Ledger;
- use soroban_sdk::{Env, String};
-
- #[test]
- fn test_post_and_get_gist() {
- let env = Env::default();
- let contract_id = env.register(GistRegistry, ());
- let client = GistRegistryClient::new(&env, &contract_id);
-
- env.ledger().set_timestamp(1_000_000);
-
- let location = String::from_str(&env, "r3gx");
- let hash = String::from_str(&env, "QmTest123");
-
- let id = client.post_gist(&None, &location, &hash);
- assert_eq!(id, 1);
-
- let gist = client.get_gist(&id).expect("gist should exist");
- assert_eq!(gist.gist_id, 1);
- assert_eq!(gist.location_cell, location);
- assert_eq!(gist.content_hash, hash);
- assert_eq!(gist.created_at, 1_000_000);
- }
-
- #[test]
- fn test_list_gists_by_cell() {
- let env = Env::default();
- let contract_id = env.register(GistRegistry, ());
- let client = GistRegistryClient::new(&env, &contract_id);
-
- let cell_a = String::from_str(&env, "r3gx");
- let cell_b = String::from_str(&env, "u4ht");
- let hash = String::from_str(&env, "Qm000");
-
- client.post_gist(&None, &cell_a, &hash);
- client.post_gist(&None, &cell_b, &hash);
- client.post_gist(&None, &cell_a, &hash);
-
- let results = client.list_gists_by_cell(&cell_a, &0, &10);
- assert_eq!(results.len(), 2);
- }
-}
-use soroban_sdk::{
- contracterror, contracttype, panic_with_error, symbol_short, Address, Env, Symbol, Vec,
-};
-
-#[derive(Clone)]
-#[contracttype]
-pub enum DataKey {
- Admin,
- Signers,
- Threshold,
- HighValueThreshold,
- NextTxId,
- PendingTx(u64),
- Approval(u64, Address),
- ApprovalCount(u64),
- Balance(Address),
-}
-
-#[derive(Clone)]
-#[contracttype]
-pub struct PendingTx {
- pub id: u64,
- pub from: Address,
- pub to: Address,
- pub amount: i128,
- pub payload: Symbol,
- pub asset: Option,
- pub created_at: u64,
- pub executed: bool,
-}
-
-#[contracterror]
-#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
-#[repr(u32)]
-pub enum MultiSigError {
- NotInitialized = 1,
- AlreadyInitialized = 2,
- Unauthorized = 3,
- InvalidThreshold = 4,
- DuplicateSigner = 5,
- InvalidAmount = 6,
- PendingTxNotFound = 7,
- UnauthorizedSigner = 8,
- DuplicateApproval = 9,
- AlreadyExecuted = 10,
- InsufficientBalance = 11,
- MultisigNotConfigured = 12,
- Overflow = 13,
-}
-
-pub struct MultisigEvents;
-
-impl MultisigEvents {
- pub fn pending_created(env: &Env, tx: &PendingTx) {
- let topics = (symbol_short!("tx"), symbol_short!("pending"), tx.id);
- env.events().publish(
- topics,
- (tx.from.clone(), tx.to.clone(), tx.amount, tx.asset.clone()),
- );
- }
-
- pub fn approval_recorded(
- env: &Env,
- tx_id: u64,
- signer: &Address,
- approvals_count: u32,
- threshold: u32,
- ) {
- let topics = (symbol_short!("approve"), symbol_short!("record"), tx_id);
- env.events()
- .publish(topics, (signer.clone(), approvals_count, threshold));
- }
-
- pub fn transaction_executed(env: &Env, tx: &PendingTx, executor: &Address) {
- let topics = (symbol_short!("tx"), symbol_short!("executed"), tx.id);
- env.events().publish(
- topics,
- (
- executor.clone(),
- tx.from.clone(),
- tx.to.clone(),
- tx.amount,
- tx.asset.clone(),
- ),
- );
- }
-}
-
-pub fn initialize_state(env: &Env, admin: Address) {
- if env.storage().instance().has(&DataKey::Admin) {
- panic_with_error!(env, MultiSigError::AlreadyInitialized);
- }
-
- env.storage().instance().set(&DataKey::Admin, &admin);
- env.storage()
- .instance()
- .set(&DataKey::Signers, &Vec::::new(env));
- env.storage().instance().set(&DataKey::Threshold, &0u32);
- env.storage()
- .instance()
- .set(&DataKey::HighValueThreshold, &i128::MAX);
- env.storage().instance().set(&DataKey::NextTxId, &0u64);
-}
-
-pub fn get_admin(env: &Env) -> Address {
- env.storage()
- .instance()
- .get(&DataKey::Admin)
- .unwrap_or_else(|| panic_with_error!(env, MultiSigError::NotInitialized))
-}
-
-pub fn require_admin(env: &Env, caller: &Address) {
- caller.require_auth();
- let admin = get_admin(env);
- if admin != caller.clone() {
- panic_with_error!(env, MultiSigError::Unauthorized);
- }
-}
-
-pub fn set_signers(env: &Env, caller: Address, signers: Vec, threshold: u32) {
- require_admin(env, &caller);
- validate_signer_config(env, &signers, threshold);
-
- env.storage().instance().set(&DataKey::Signers, &signers);
- env.storage()
- .instance()
- .set(&DataKey::Threshold, &threshold);
-}
-
-pub fn set_high_value_threshold(env: &Env, caller: Address, amount: i128) {
- require_admin(env, &caller);
-
- if amount < 0 {
- panic_with_error!(env, MultiSigError::InvalidAmount);
- }
-
- env.storage()
- .instance()
- .set(&DataKey::HighValueThreshold, &amount);
-}
-
-pub fn get_signers(env: &Env) -> Vec {
- env.storage()
- .instance()
- .get(&DataKey::Signers)
- .unwrap_or_else(|| Vec::new(env))
-}
-
-pub fn get_threshold(env: &Env) -> u32 {
- env.storage()
- .instance()
- .get(&DataKey::Threshold)
- .unwrap_or(0)
-}
-
-pub fn get_high_value_threshold(env: &Env) -> i128 {
- env.storage()
- .instance()
- .get(&DataKey::HighValueThreshold)
- .unwrap_or(i128::MAX)
-}
-
-pub fn ensure_multisig_configured(env: &Env) {
- let signers = get_signers(env);
- let threshold = get_threshold(env);
-
- if signers.len() == 0 || threshold == 0 || threshold > signers.len() {
- panic_with_error!(env, MultiSigError::MultisigNotConfigured);
- }
-}
-
-pub fn require_signer(env: &Env, signer: &Address) {
- if !is_signer(env, signer) {
- panic_with_error!(env, MultiSigError::UnauthorizedSigner);
- }
-}
-
-pub fn is_signer(env: &Env, signer: &Address) -> bool {
- let signers = get_signers(env);
- for configured in signers.iter() {
- if configured == signer.clone() {
- return true;
- }
- }
- false
-}
-
-pub fn next_tx_id(env: &Env) -> u64 {
- let current: u64 = env
- .storage()
- .instance()
- .get(&DataKey::NextTxId)
- .unwrap_or(0);
- let next = current
- .checked_add(1)
- .unwrap_or_else(|| panic_with_error!(env, MultiSigError::Overflow));
-
- env.storage().instance().set(&DataKey::NextTxId, &next);
- next
-}
-
-pub fn has_approval(env: &Env, tx_id: u64, signer: &Address) -> bool {
- env.storage()
- .persistent()
- .has(&DataKey::Approval(tx_id, signer.clone()))
-}
-
-pub fn get_approval_count(env: &Env, tx_id: u64) -> u32 {
- env.storage()
- .persistent()
- .get(&DataKey::ApprovalCount(tx_id))
- .unwrap_or(0)
-}
-
-pub fn record_approval(env: &Env, tx_id: u64, signer: &Address) -> u32 {
- if has_approval(env, tx_id, signer) {
- panic_with_error!(env, MultiSigError::DuplicateApproval);
- }
-
- env.storage()
- .persistent()
- .set(&DataKey::Approval(tx_id, signer.clone()), &true);
-
- let current = get_approval_count(env, tx_id);
- let next = current
- .checked_add(1)
- .unwrap_or_else(|| panic_with_error!(env, MultiSigError::Overflow));
-
- env.storage()
- .persistent()
- .set(&DataKey::ApprovalCount(tx_id), &next);
-
- next
-}
-
-fn validate_signer_config(env: &Env, signers: &Vec, threshold: u32) {
- let signer_count = signers.len();
-
- if signer_count == 0 || threshold == 0 || threshold > signer_count {
- panic_with_error!(env, MultiSigError::InvalidThreshold);
- }
-
- for i in 0..signer_count {
- let signer_i = signers
- .get(i)
- .unwrap_or_else(|| panic_with_error!(env, MultiSigError::InvalidThreshold));
-
- for j in (i + 1)..signer_count {
- let signer_j = signers
- .get(j)
- .unwrap_or_else(|| panic_with_error!(env, MultiSigError::InvalidThreshold));
- if signer_i == signer_j {
- panic_with_error!(env, MultiSigError::DuplicateSigner);
- }
- }
- }
-}
-use soroban_sdk::{
- contract, contracterror, contractimpl, contracttype, panic_with_error, symbol_short, Address,
- Env, String,
-};
-
-#[derive(Clone)]
-#[contracttype]
-pub enum GovernanceDataKey {
- Admin, // To manage governance settings
- RequiredApprovals,
- ProposalCount,
- Proposal(u32),
- UserVote(u32, Address), // Proposal ID, User -> bool
- ConfigValue(String), // Stores the actual config data
-}
-
-#[derive(Clone)]
-#[contracttype]
-pub struct Proposal {
- pub id: u32,
- pub proposer: Address,
- pub config_key: String,
- pub config_value: String,
- pub approvals: u32,
- pub executed: bool,
- pub deadline: u64,
-}
-
-#[contracterror]
-#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
-#[repr(u32)]
-pub enum GovernanceError {
- NotInitialized = 1,
- AlreadyInitialized = 2,
- Unauthorized = 3,
- ProposalNotFound = 4,
- AlreadyVoted = 5,
- ProposalExpired = 6,
- AlreadyExecuted = 7,
- NotEnoughApprovals = 8,
- Overflow = 9,
- InvalidInput = 10,
-}
-
-pub struct GovernanceEvents;
-
-impl GovernanceEvents {
- pub fn admin_updated(env: &Env, previous_admin: &Address, new_admin: &Address) {
- let topics = (symbol_short!("gov"), symbol_short!("admin"));
- env.events().publish(
- topics,
- (
- previous_admin.clone(),
- new_admin.clone(),
- env.ledger().timestamp(),
- ),
- );
- }
-
- pub fn proposal_created(
- env: &Env,
- id: u32,
- proposer: &Address,
- config_key: &String,
- config_value: &String,
- ) {
- let topics = (symbol_short!("gov"), symbol_short!("created"));
- env.events().publish(
- topics,
- (
- id,
- proposer.clone(),
- config_key.clone(),
- config_value.clone(),
- env.ledger().timestamp(),
- ),
- );
- }
-
- pub fn voted(env: &Env, id: u32, voter: &Address) {
- let topics = (symbol_short!("gov"), symbol_short!("voted"));
- env.events()
- .publish(topics, (id, voter.clone(), env.ledger().timestamp()));
- }
-
- pub fn proposal_executed(env: &Env, id: u32, config_key: &String, config_value: &String) {
- let topics = (symbol_short!("gov"), symbol_short!("executed"));
- env.events().publish(
- topics,
- (
- id,
- config_key.clone(),
- config_value.clone(),
- env.ledger().timestamp(),
- ),
- );
- }
-}
-
-pub fn initialize_governance(env: &Env, admin: Address, required_approvals: u32) {
- if env.storage().instance().has(&GovernanceDataKey::Admin) {
- panic_with_error!(env, GovernanceError::AlreadyInitialized);
- }
- env.storage()
- .instance()
- .set(&GovernanceDataKey::Admin, &admin);
- env.storage()
- .instance()
- .set(&GovernanceDataKey::RequiredApprovals, &required_approvals);
- env.storage()
- .instance()
- .set(&GovernanceDataKey::ProposalCount, &0u32);
-}
-
-pub fn require_admin(env: &Env, caller: &Address) {
- caller.require_auth();
- let admin: Address = env
- .storage()
- .instance()
- .get(&GovernanceDataKey::Admin)
- .unwrap_or_else(|| panic_with_error!(env, GovernanceError::NotInitialized));
- if admin != *caller {
- panic_with_error!(env, GovernanceError::Unauthorized);
- }
-}
-
-pub fn update_admin(env: &Env, current_admin: Address, new_admin: Address) {
- require_admin(env, ¤t_admin);
- env.storage()
- .instance()
- .set(&GovernanceDataKey::Admin, &new_admin);
- GovernanceEvents::admin_updated(env, ¤t_admin, &new_admin);
-}
-
-/// Maximum length for config key and value strings
-const MAX_CONFIG_STRING_LENGTH: u32 = 256;
-
-pub fn create_proposal(
- env: &Env,
- proposer: Address,
- config_key: String,
- config_value: String,
- duration_seconds: u64,
-) -> u32 {
- proposer.require_auth();
-
- // Validate input string lengths
- if config_key.len() > MAX_CONFIG_STRING_LENGTH || config_key.len() == 0 {
- panic_with_error!(env, GovernanceError::InvalidInput);
- }
- if config_value.len() > MAX_CONFIG_STRING_LENGTH {
- panic_with_error!(env, GovernanceError::InvalidInput);
- }
- if duration_seconds == 0 {
- panic_with_error!(env, GovernanceError::InvalidInput);
- }
-
- let count: u32 = env
- .storage()
- .instance()
- .get(&GovernanceDataKey::ProposalCount)
- .unwrap_or_else(|| panic_with_error!(env, GovernanceError::NotInitialized));
-
- let new_id = count
- .checked_add(1)
- .unwrap_or_else(|| panic_with_error!(env, GovernanceError::Overflow));
- let current_time = env.ledger().timestamp();
- let deadline = current_time
- .checked_add(duration_seconds)
- .unwrap_or_else(|| panic_with_error!(env, GovernanceError::Overflow));
-
- let proposal = Proposal {
- id: new_id,
- proposer: proposer.clone(),
- config_key: config_key.clone(),
- config_value: config_value.clone(),
- approvals: 0,
- executed: false,
- deadline,
- };
-
- env.storage()
- .persistent()
- .set(&GovernanceDataKey::Proposal(new_id), &proposal);
- env.storage()
- .instance()
- .set(&GovernanceDataKey::ProposalCount, &new_id);
-
- GovernanceEvents::proposal_created(env, new_id, &proposer, &config_key, &config_value);
-
- new_id
-}
-
-pub fn vote_proposal(env: &Env, voter: Address, proposal_id: u32) {
- voter.require_auth();
-
- let mut proposal: Proposal = env
- .storage()
- .persistent()
- .get(&GovernanceDataKey::Proposal(proposal_id))
- .unwrap_or_else(|| panic_with_error!(env, GovernanceError::ProposalNotFound));
-
- if proposal.executed {
- panic_with_error!(env, GovernanceError::AlreadyExecuted);
- }
-
- if env.ledger().timestamp() > proposal.deadline {
- panic_with_error!(env, GovernanceError::ProposalExpired);
- }
-
- let vote_key = GovernanceDataKey::UserVote(proposal_id, voter.clone());
- let has_voted: bool = env.storage().persistent().get(&vote_key).unwrap_or(false);
-
- if has_voted {
- panic_with_error!(env, GovernanceError::AlreadyVoted);
- }
-
- env.storage().persistent().set(&vote_key, &true);
- proposal.approvals = proposal
- .approvals
- .checked_add(1)
- .unwrap_or_else(|| panic_with_error!(env, GovernanceError::Overflow));
- env.storage()
- .persistent()
- .set(&GovernanceDataKey::Proposal(proposal_id), &proposal);
-
- GovernanceEvents::voted(env, proposal_id, &voter);
-}
-
-pub fn execute_proposal(env: &Env, caller: Address, proposal_id: u32) {
- caller.require_auth(); // Anyone can trigger execution if conditions met, but auth required to trace
-
- let mut proposal: Proposal = env
- .storage()
- .persistent()
- .get(&GovernanceDataKey::Proposal(proposal_id))
- .unwrap_or_else(|| panic_with_error!(env, GovernanceError::ProposalNotFound));
-
- if proposal.executed {
- panic_with_error!(env, GovernanceError::AlreadyExecuted);
- }
-
- if env.ledger().timestamp() > proposal.deadline {
- panic_with_error!(env, GovernanceError::ProposalExpired);
- }
-
- let required_approvals: u32 = env
- .storage()
- .instance()
- .get(&GovernanceDataKey::RequiredApprovals)
- .unwrap_or_else(|| panic_with_error!(env, GovernanceError::NotInitialized));
-
- if proposal.approvals < required_approvals {
- panic_with_error!(env, GovernanceError::NotEnoughApprovals);
- }
-
- // Apply configuration changes
- env.storage().persistent().set(
- &GovernanceDataKey::ConfigValue(proposal.config_key.clone()),
- &proposal.config_value,
- );
-
- proposal.executed = true;
- env.storage()
- .persistent()
- .set(&GovernanceDataKey::Proposal(proposal_id), &proposal);
-
- GovernanceEvents::proposal_executed(
- env,
- proposal_id,
- &proposal.config_key,
- &proposal.config_value,
- );
-}
-
-#[contract]
-pub struct GovernanceContract;
-
-#[contractimpl]
-impl GovernanceContract {
- pub fn initialize(env: Env, admin: Address, required_approvals: u32) {
- initialize_governance(&env, admin, required_approvals);
- }
-
- pub fn update_admin(env: Env, current_admin: Address, new_admin: Address) {
- update_admin(&env, current_admin, new_admin);
- }
-
- pub fn get_admin(env: Env) -> Address {
- env.storage()
- .instance()
- .get(&GovernanceDataKey::Admin)
- .unwrap_or_else(|| panic_with_error!(&env, GovernanceError::NotInitialized))
- }
-
- pub fn create_proposal(
- env: Env,
- proposer: Address,
- config_key: String,
- config_value: String,
- duration_seconds: u64,
- ) -> u32 {
- create_proposal(&env, proposer, config_key, config_value, duration_seconds)
- }
-
- pub fn vote_proposal(env: Env, voter: Address, proposal_id: u32) {
- vote_proposal(&env, voter, proposal_id);
- }
-
- pub fn execute_proposal(env: Env, caller: Address, proposal_id: u32) {
- execute_proposal(&env, caller, proposal_id);
- }
-
- pub fn get_proposal(env: Env, proposal_id: u32) -> Option {
- env.storage()
- .persistent()
- .get(&GovernanceDataKey::Proposal(proposal_id))
- }
-
- pub fn get_config(env: Env, config_key: String) -> Option {
- env.storage()
- .persistent()
- .get(&GovernanceDataKey::ConfigValue(config_key))
- }
-}
diff --git a/contracts/src/ops.rs b/contracts/src/ops.rs
deleted file mode 100644
index c6a83838..00000000
--- a/contracts/src/ops.rs
+++ /dev/null
@@ -1,775 +0,0 @@
-//! Integration tests for the Batch Wallet Creation Contract.
-
-#![cfg(test)]
-
-use crate::{
- BatchCreateResult, BatchRecoveryResult, BatchWalletContract, BatchWalletContractClient,
- WalletCreateRequest, WalletCreateResult, WalletRecoveryRequest, WalletRecoveryResult,
-};
-use soroban_sdk::{
- testutils::{Address as _, Events as _, Ledger},
- Address, Env, Vec,
-};
-
-/// Creates a test environment with the contract deployed and initialized.
-fn setup_test_env() -> (Env, Address, BatchWalletContractClient<'static>) {
- let env = Env::default();
- env.mock_all_auths();
- env.ledger().with_mut(|li| {
- li.sequence_number = 12345;
- });
-
- // Deploy batch wallet contract
- let contract_id = env.register(BatchWalletContract, ());
- let client = BatchWalletContractClient::new(&env, &contract_id);
-
- let admin = Address::generate(&env);
- client.initialize(&admin);
-
- (env, admin, client)
-}
-
-/// Helper to create a wallet creation request.
-fn create_wallet_request(_env: &Env, owner: Address) -> WalletCreateRequest {
- WalletCreateRequest { owner }
-}
-
-fn create_recovery_request(
- _env: &Env,
- old_owner: Address,
- new_owner: Address,
-) -> WalletRecoveryRequest {
- WalletRecoveryRequest {
- old_owner,
- new_owner,
- }
-}
-
-// Initialization Tests
-
-#[test]
-fn test_initialize_contract() {
- let (_env, admin, client) = setup_test_env();
-
- assert_eq!(client.get_admin(), admin);
- assert_eq!(client.get_total_batches(), 0);
- assert_eq!(client.get_total_wallets_created(), 0);
-}
-
-#[test]
-#[should_panic(expected = "Contract already initialized")]
-fn test_cannot_initialize_twice() {
- let (env, admin, client) = setup_test_env();
-
- let new_admin = Address::generate(&env);
- client.initialize(&new_admin);
-}
-
-// Batch Wallet Creation Tests
-
-#[test]
-fn test_batch_create_wallets_single() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner.clone()));
-
- let result = client.batch_create_wallets(&admin, &requests);
-
- assert_eq!(result.total_requests, 1);
- assert_eq!(result.successful, 1);
- assert_eq!(result.failed, 0);
- assert_eq!(result.results.len(), 1);
-
- // Check wallet was created
- let wallet = client.get_wallet(&owner).unwrap();
- assert_eq!(wallet.owner, owner);
- assert_eq!(wallet.id, 1);
-}
-
-#[test]
-fn test_batch_create_wallets_multiple() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner1.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner3.clone()));
-
- let result = client.batch_create_wallets(&admin, &requests);
-
- assert_eq!(result.total_requests, 3);
- assert_eq!(result.successful, 3);
- assert_eq!(result.failed, 0);
-
- // Check wallets were created with sequential IDs
- let wallet1 = client.get_wallet(&owner1).unwrap();
- assert_eq!(wallet1.id, 1);
- let wallet2 = client.get_wallet(&owner2).unwrap();
- assert_eq!(wallet2.id, 2);
- let wallet3 = client.get_wallet(&owner3).unwrap();
- assert_eq!(wallet3.id, 3);
-}
-
-#[test]
-fn test_batch_create_wallets_partial_failures() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- // First batch: create wallets for owner1 and owner2
- let mut requests1: Vec = Vec::new(&env);
- requests1.push_back(create_wallet_request(&env, owner1.clone()));
- requests1.push_back(create_wallet_request(&env, owner2.clone()));
- client.batch_create_wallets(&admin, &requests1);
-
- // Second batch: try to create for owner1 (duplicate), owner2 (duplicate), owner3 (new)
- let mut requests2: Vec = Vec::new(&env);
- requests2.push_back(create_wallet_request(&env, owner1.clone())); // Duplicate
- requests2.push_back(create_wallet_request(&env, owner2.clone())); // Duplicate
- requests2.push_back(create_wallet_request(&env, owner3.clone())); // New
-
- let result = client.batch_create_wallets(&admin, &requests2);
-
- assert_eq!(result.total_requests, 3);
- assert_eq!(result.successful, 1);
- assert_eq!(result.failed, 2);
-
- // Check results
- match result.results.get(0).unwrap() {
- WalletCreateResult::Failure(addr, error_code) => {
- assert_eq!(addr, owner1);
- assert_eq!(error_code, 1); // Already exists
- }
- _ => panic!("Expected failure for duplicate"),
- }
- match result.results.get(1).unwrap() {
- WalletCreateResult::Failure(addr, error_code) => {
- assert_eq!(addr, owner2);
- assert_eq!(error_code, 1); // Already exists
- }
- _ => panic!("Expected failure for duplicate"),
- }
- match result.results.get(2).unwrap() {
- WalletCreateResult::Success(addr) => {
- assert_eq!(addr, owner3);
- }
- _ => panic!("Expected success for new wallet"),
- }
-
- // Check wallet3 was created
- let wallet3 = client.get_wallet(&owner3).unwrap();
- assert_eq!(wallet3.id, 3); // IDs continue from previous batch
-}
-
-#[test]
-fn test_batch_create_wallets_events_emitted() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner1.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
-
- client.batch_create_wallets(&admin, &requests);
-
- let events = env.events().all();
- // Should have: batch_started, wallet_created (2), batch_completed
- assert!(events.len() >= 4);
-}
-
-#[test]
-fn test_batch_create_wallets_accumulates_stats() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
-
- let mut requests1: Vec = Vec::new(&env);
- requests1.push_back(create_wallet_request(&env, owner1.clone()));
-
- let mut requests2: Vec = Vec::new(&env);
- requests2.push_back(create_wallet_request(&env, owner2.clone()));
-
- assert_eq!(client.get_total_batches(), 0);
- assert_eq!(client.get_total_wallets_created(), 0);
-
- client.batch_create_wallets(&admin, &requests1);
- assert_eq!(client.get_total_batches(), 1);
- assert_eq!(client.get_total_wallets_created(), 1);
-
- client.batch_create_wallets(&admin, &requests2);
- assert_eq!(client.get_total_batches(), 2);
- assert_eq!(client.get_total_wallets_created(), 2);
-}
-
-#[test]
-#[should_panic]
-fn test_batch_create_wallets_empty_batch() {
- let (env, admin, client) = setup_test_env();
-
- let requests: Vec = Vec::new(&env);
- client.batch_create_wallets(&admin, &requests);
-}
-
-#[test]
-#[should_panic]
-fn test_batch_create_wallets_unauthorized() {
- let (env, admin, client) = setup_test_env();
-
- let unauthorized = Address::generate(&env);
- let owner = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner));
-
- // This should panic due to unauthorized access
- client.batch_create_wallets(&unauthorized, &requests);
-}
-
-#[test]
-fn test_batch_create_wallets_large_batch() {
- let (env, admin, client) = setup_test_env();
-
- // Create a batch with 50 owners
- let mut requests: Vec = Vec::new(&env);
- let mut owners: Vec = Vec::new(&env);
-
- for _i in 0..50 {
- let owner = Address::generate(&env);
- owners.push_back(owner.clone());
- requests.push_back(create_wallet_request(&env, owner));
- }
-
- let result = client.batch_create_wallets(&admin, &requests);
-
- assert_eq!(result.total_requests, 50);
- assert_eq!(result.successful, 50);
- assert_eq!(result.failed, 0);
-
- // Check some wallets
- let wallet1 = client.get_wallet(&owners.get(0).unwrap()).unwrap();
- assert_eq!(wallet1.id, 1);
- let wallet50 = client.get_wallet(&owners.get(49).unwrap()).unwrap();
- assert_eq!(wallet50.id, 50);
-}
-
-// Admin Tests
-
-#[test]
-fn test_set_admin() {
- let (env, admin, client) = setup_test_env();
-
- let new_admin = Address::generate(&env);
- client.set_admin(&admin, &new_admin);
-
- assert_eq!(client.get_admin(), new_admin);
-}
-
-// Multiple Simultaneous Batch Creations
-
-#[test]
-fn test_multiple_simultaneous_batch_creations() {
- let (env, admin, client) = setup_test_env();
-
- // First batch: 3 owners
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- let mut batch1: Vec = Vec::new(&env);
- batch1.push_back(create_wallet_request(&env, owner1.clone()));
- batch1.push_back(create_wallet_request(&env, owner2.clone()));
- batch1.push_back(create_wallet_request(&env, owner3.clone()));
-
- let result1 = client.batch_create_wallets(&admin, &batch1);
- assert_eq!(result1.successful, 3);
-
- // Second batch: 2 owners (one new, one duplicate)
- let owner4 = Address::generate(&env);
-
- let mut batch2: Vec = Vec::new(&env);
- batch2.push_back(create_wallet_request(&env, owner1.clone())); // Duplicate
- batch2.push_back(create_wallet_request(&env, owner4.clone())); // New
-
- let result2 = client.batch_create_wallets(&admin, &batch2);
- assert_eq!(result2.successful, 1);
- assert_eq!(result2.failed, 1);
-
- // Verify contract stats
- assert_eq!(client.get_total_batches(), 2);
- assert_eq!(client.get_total_wallets_created(), 4); // 3 + 1
-}
-
-#[test]
-fn test_batch_recover_wallets_single_success() {
- let (env, admin, client) = setup_test_env();
-
- let original_owner = Address::generate(&env);
- let new_owner = Address::generate(&env);
-
- let mut create_requests: Vec = Vec::new(&env);
- create_requests.push_back(create_wallet_request(&env, original_owner.clone()));
- let create_result: BatchCreateResult = client.batch_create_wallets(&admin, &create_requests);
- assert_eq!(create_result.successful, 1);
-
- let mut recovery_requests: Vec = Vec::new(&env);
- recovery_requests.push_back(create_recovery_request(
- &env,
- original_owner.clone(),
- new_owner.clone(),
- ));
-
- let recover_result: BatchRecoveryResult =
- client.batch_recover_wallets(&admin, &recovery_requests);
-
- assert_eq!(recover_result.total_requests, 1);
- assert_eq!(recover_result.successful, 1);
- assert_eq!(recover_result.failed, 0);
- assert_eq!(recover_result.results.len(), 1);
-
- match recover_result.results.get(0).unwrap() {
- WalletRecoveryResult::Success(old, new_) => {
- assert_eq!(old, original_owner);
- assert_eq!(new_, new_owner);
- }
- _ => panic!("expected success result"),
- }
-
- let original_wallet = client.get_wallet(&original_owner);
- assert!(original_wallet.is_none());
-
- let recovered_wallet = client.get_wallet(&new_owner).unwrap();
- assert_eq!(recovered_wallet.owner, new_owner);
- assert_eq!(recovered_wallet.id, 1);
-}
-
-#[test]
-fn test_batch_recover_wallets_partial_failures() {
- let (env, admin, client) = setup_test_env();
-
- let existing_owner = Address::generate(&env);
- let other_existing_owner = Address::generate(&env);
- let non_existing_owner = Address::generate(&env);
- let recovery_target_1 = Address::generate(&env);
- let recovery_target_2 = Address::generate(&env);
-
- let mut create_requests: Vec = Vec::new(&env);
- create_requests.push_back(create_wallet_request(&env, existing_owner.clone()));
- create_requests.push_back(create_wallet_request(&env, other_existing_owner.clone()));
- client.batch_create_wallets(&admin, &create_requests);
-
- let mut recovery_requests: Vec = Vec::new(&env);
- recovery_requests.push_back(create_recovery_request(
- &env,
- non_existing_owner.clone(),
- recovery_target_1.clone(),
- ));
- recovery_requests.push_back(create_recovery_request(
- &env,
- existing_owner.clone(),
- existing_owner.clone(),
- ));
- recovery_requests.push_back(create_recovery_request(
- &env,
- existing_owner.clone(),
- recovery_target_2.clone(),
- ));
-
- let recover_result = client.batch_recover_wallets(&admin, &recovery_requests);
-
- assert_eq!(recover_result.total_requests, 3);
- assert_eq!(recover_result.successful, 1);
- assert_eq!(recover_result.failed, 2);
-
- match recover_result.results.get(0).unwrap() {
- WalletRecoveryResult::Failure(old, new_, code) => {
- assert_eq!(old, non_existing_owner);
- assert_eq!(new_, recovery_target_1);
- assert_eq!(code, 1);
- }
- _ => panic!("expected failure for non-existing source wallet"),
- }
-
- match recover_result.results.get(1).unwrap() {
- WalletRecoveryResult::Failure(old, new_, code) => {
- assert_eq!(old, existing_owner);
- assert_eq!(new_, existing_owner);
- assert_eq!(code, 2);
- }
- _ => panic!("expected failure for invalid destination wallet"),
- }
-
- match recover_result.results.get(2).unwrap() {
- WalletRecoveryResult::Success(old, new_) => {
- assert_eq!(old, existing_owner);
- assert_eq!(new_, recovery_target_2);
- }
- _ => panic!("expected success for valid recovery"),
- }
-
- let still_existing = client.get_wallet(&other_existing_owner).unwrap();
- assert_eq!(still_existing.owner, other_existing_owner);
-
- let recovered_wallet = client.get_wallet(&recovery_target_2).unwrap();
- assert_eq!(recovered_wallet.owner, recovery_target_2);
-}
-
-#[test]
-fn test_batch_recover_wallets_events_emitted() {
- let (env, admin, client) = setup_test_env();
-
- let original_owner = Address::generate(&env);
- let new_owner = Address::generate(&env);
-
- let mut create_requests: Vec = Vec::new(&env);
- create_requests.push_back(create_wallet_request(&env, original_owner.clone()));
- client.batch_create_wallets(&admin, &create_requests);
-
- let mut recovery_requests: Vec = Vec::new(&env);
- recovery_requests.push_back(create_recovery_request(
- &env,
- original_owner.clone(),
- new_owner.clone(),
- ));
-
- client.batch_recover_wallets(&admin, &recovery_requests);
-
- let events = env.events().all();
- assert!(events.len() >= 3);
-}
-
-#[test]
-#[should_panic]
-fn test_batch_recover_wallets_empty_batch() {
- let (env, admin, client) = setup_test_env();
-
- let recovery_requests: Vec = Vec::new(&env);
- client.batch_recover_wallets(&admin, &recovery_requests);
-}
-
-#[test]
-#[should_panic]
-fn test_batch_recover_wallets_unauthorized() {
- let (env, _admin, client) = setup_test_env();
-
- let original_owner = Address::generate(&env);
- let new_owner = Address::generate(&env);
-
- let mut recovery_requests: Vec = Vec::new(&env);
- recovery_requests.push_back(create_recovery_request(&env, original_owner, new_owner));
-
- let unauthorized = Address::generate(&env);
- client.batch_recover_wallets(&unauthorized, &recovery_requests);
-}
-
-// ─── Duplicate Wallet Detection Tests ──────────────────────────────────────
-
-#[test]
-#[should_panic(expected = "Error(Contract, #6)")]
-fn test_batch_create_wallets_duplicate_in_batch() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
- let owner2 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- // Add duplicate requests for same owner within single batch
- requests.push_back(create_wallet_request(&env, owner.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner.clone())); // Duplicate!
-
- // This should panic with DuplicateWallet error
- client.batch_create_wallets(&admin, &requests);
-}
-
-#[test]
-#[should_panic(expected = "Error(Contract, #6)")]
-fn test_batch_create_wallets_multiple_duplicates_in_batch() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner1.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner1.clone())); // Duplicate!
- requests.push_back(create_wallet_request(&env, owner3.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone())); // Another duplicate!
-
- // This should panic with DuplicateWallet error
- client.batch_create_wallets(&admin, &requests);
-}
-
-#[test]
-#[should_panic(expected = "Error(Contract, #6)")]
-fn test_batch_create_wallets_consecutive_duplicates() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner.clone()));
- requests.push_back(create_wallet_request(&env, owner.clone())); // Duplicate!
- requests.push_back(create_wallet_request(&env, owner.clone())); // Another duplicate!
-
- // This should panic with DuplicateWallet error
- client.batch_create_wallets(&admin, &requests);
-}
-
-#[test]
-fn test_batch_create_wallets_no_duplicates_succeeds() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner1.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner3.clone()));
-
- // This should succeed (no duplicates)
- let result = client.batch_create_wallets(&admin, &requests);
-
- assert_eq!(result.total_requests, 3);
- assert_eq!(result.successful, 3);
- assert_eq!(result.failed, 0);
-}
-
-#[test]
-fn test_batch_create_wallets_duplicate_event_emitted() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
- let owner2 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner.clone())); // Duplicate
-
- // Attempt batch create - should fail with duplicate error
- let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- client.batch_create_wallets(&admin, &requests);
- }));
-
- // Verify panic occurred
- assert!(result.is_err());
-
- // Verify duplicate event was published before panic
- let events = env.events().all();
- let mut duplicate_event_found = false;
- for (_, topics, _) in events.iter() {
- let topic_vec = topics.clone();
- if topic_vec.len() >= 2 {
- let topic1: soroban_sdk::Symbol =
- topic_vec.get(0).unwrap().into_val(&env).try_into().ok();
- let topic2: soroban_sdk::Symbol =
- topic_vec.get(1).unwrap().into_val(&env).try_into().ok();
- if topic1 == Some(soroban_sdk::Symbol::new(&env, "wallet"))
- && topic2 == Some(soroban_sdk::Symbol::new(&env, "duplicate"))
- {
- duplicate_event_found = true;
- break;
- }
- }
- }
- assert!(duplicate_event_found, "Duplicate event should be emitted");
-}
-
-#[test]
-fn test_batch_create_wallets_duplicate_error_identifies_entry() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- // First batch - success
- let mut requests1: Vec = Vec::new(&env);
- requests1.push_back(create_wallet_request(&env, owner.clone()));
- requests1.push_back(create_wallet_request(&env, owner2.clone()));
- client.batch_create_wallets(&admin, &requests1);
-
- // Second batch - with duplicate from first batch
- let mut requests2: Vec = Vec::new(&env);
- requests2.push_back(create_wallet_request(&env, owner.clone())); // Existed before
- requests2.push_back(create_wallet_request(&env, owner3.clone())); // New
-
- let result = client.batch_create_wallets(&admin, &requests2);
-
- // Should process individually since duplicates are within-batch check
- // Only existing wallets should fail, not in-batch duplicates
- assert_eq!(result.total_requests, 2);
- assert_eq!(result.successful, 1);
- assert_eq!(result.failed, 1);
-
- match result.results.get(0).unwrap() {
- WalletCreateResult::Failure(addr, _) => {
- assert_eq!(addr, owner); // First request failed (already exists)
- }
- _ => panic!("Expected failure for existing wallet"),
- }
-
- match result.results.get(1).unwrap() {
- WalletCreateResult::Success(addr) => {
- assert_eq!(addr, owner3); // New wallet created
- }
- _ => panic!("Expected success for new wallet"),
- }
-}
-
-
-#![no_std]
-use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, String, Vec};
-
-// ---------------------------------------------------------------------------
-// Data types
-// ---------------------------------------------------------------------------
-
-#[contracttype]
-#[derive(Clone)]
-pub struct Gist {
- pub gist_id: u64,
- pub author: Option,
- pub location_cell: String,
- pub content_hash: String,
- pub created_at: u64,
-}
-
-// ---------------------------------------------------------------------------
-// Storage keys
-// ---------------------------------------------------------------------------
-
-const GIST_COUNT: soroban_sdk::Symbol = symbol_short!("GCOUNT");
-
-// ---------------------------------------------------------------------------
-// Contract
-// ---------------------------------------------------------------------------
-
-#[contract]
-pub struct GistRegistry;
-
-#[contractimpl]
-impl GistRegistry {
- /// Register a new gist on-chain. Returns the assigned gist_id.
- pub fn post_gist(
- env: Env,
- author: Option,
- location_cell: String,
- content_hash: String,
- ) -> u64 {
- let gist_id: u64 = env.storage().instance().get(&GIST_COUNT).unwrap_or(0) + 1;
-
- let gist = Gist {
- gist_id,
- author,
- location_cell: location_cell.clone(),
- content_hash,
- created_at: env.ledger().timestamp(),
- };
-
- env.storage().persistent().set(&gist_id, &gist);
- env.storage().instance().set(&GIST_COUNT, &gist_id);
-
- gist_id
- }
-
- /// Retrieve a gist record by its id.
- pub fn get_gist(env: Env, gist_id: u64) -> Option {
- env.storage().persistent().get(&gist_id)
- }
-
- /// List gists whose location_cell matches the given cell string.
- /// `cursor` is the gist_id to start scanning from (inclusive); `limit` caps results.
- pub fn list_gists_by_cell(
- env: Env,
- location_cell: String,
- cursor: u64,
- limit: u32,
- ) -> Vec {
- let total: u64 = env.storage().instance().get(&GIST_COUNT).unwrap_or(0);
- let mut results = Vec::new(&env);
- let mut count: u32 = 0;
-
- let start = if cursor == 0 { 1 } else { cursor };
-
- for id in start..=total {
- if count >= limit {
- break;
- }
- if let Some(gist) = env.storage().persistent().get::(&id) {
- if gist.location_cell == location_cell {
- results.push_back(gist);
- count += 1;
- }
- }
- }
-
- results
- }
-}
-
-// ---------------------------------------------------------------------------
-// Tests
-// ---------------------------------------------------------------------------
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use soroban_sdk::testutils::Ledger;
- use soroban_sdk::{Env, String};
-
- #[test]
- fn test_post_and_get_gist() {
- let env = Env::default();
- let contract_id = env.register(GistRegistry, ());
- let client = GistRegistryClient::new(&env, &contract_id);
-
- env.ledger().set_timestamp(1_000_000);
-
- let location = String::from_str(&env, "r3gx");
- let hash = String::from_str(&env, "QmTest123");
-
- let id = client.post_gist(&None, &location, &hash);
- assert_eq!(id, 1);
-
- let gist = client.get_gist(&id).expect("gist should exist");
- assert_eq!(gist.gist_id, 1);
- assert_eq!(gist.location_cell, location);
- assert_eq!(gist.content_hash, hash);
- assert_eq!(gist.created_at, 1_000_000);
- }
-
- #[test]
- fn test_list_gists_by_cell() {
- let env = Env::default();
- let contract_id = env.register(GistRegistry, ());
- let client = GistRegistryClient::new(&env, &contract_id);
-
- let cell_a = String::from_str(&env, "r3gx");
- let cell_b = String::from_str(&env, "u4ht");
- let hash = String::from_str(&env, "Qm000");
-
- client.post_gist(&None, &cell_a, &hash);
- client.post_gist(&None, &cell_b, &hash);
- client.post_gist(&None, &cell_a, &hash);
-
- let results = client.list_gists_by_cell(&cell_a, &0, &10);
- assert_eq!(results.len(), 2);
- }
-}
diff --git a/contracts/src/rollback.rs b/contracts/src/rollback.rs
deleted file mode 100644
index b98ba82b..00000000
--- a/contracts/src/rollback.rs
+++ /dev/null
@@ -1,634 +0,0 @@
-//! Integration tests for the Batch Wallet Creation Contract.
-
-#![cfg(test)]
-
-use crate::{
- BatchCreateResult, BatchRecoveryResult, BatchWalletContract, BatchWalletContractClient,
- WalletCreateRequest, WalletCreateResult, WalletRecoveryRequest, WalletRecoveryResult,
-};
-use soroban_sdk::{
- testutils::{Address as _, Events as _, Ledger},
- Address, Env, Vec,
-};
-
-/// Creates a test environment with the contract deployed and initialized.
-fn setup_test_env() -> (Env, Address, BatchWalletContractClient<'static>) {
- let env = Env::default();
- env.mock_all_auths();
- env.ledger().with_mut(|li| {
- li.sequence_number = 12345;
- });
-
- // Deploy batch wallet contract
- let contract_id = env.register(BatchWalletContract, ());
- let client = BatchWalletContractClient::new(&env, &contract_id);
-
- let admin = Address::generate(&env);
- client.initialize(&admin);
-
- (env, admin, client)
-}
-
-/// Helper to create a wallet creation request.
-fn create_wallet_request(_env: &Env, owner: Address) -> WalletCreateRequest {
- WalletCreateRequest { owner }
-}
-
-fn create_recovery_request(
- _env: &Env,
- old_owner: Address,
- new_owner: Address,
-) -> WalletRecoveryRequest {
- WalletRecoveryRequest {
- old_owner,
- new_owner,
- }
-}
-
-// Initialization Tests
-
-#[test]
-fn test_initialize_contract() {
- let (_env, admin, client) = setup_test_env();
-
- assert_eq!(client.get_admin(), admin);
- assert_eq!(client.get_total_batches(), 0);
- assert_eq!(client.get_total_wallets_created(), 0);
-}
-
-#[test]
-#[should_panic(expected = "Contract already initialized")]
-fn test_cannot_initialize_twice() {
- let (env, admin, client) = setup_test_env();
-
- let new_admin = Address::generate(&env);
- client.initialize(&new_admin);
-}
-
-// Batch Wallet Creation Tests
-
-#[test]
-fn test_batch_create_wallets_single() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner.clone()));
-
- let result = client.batch_create_wallets(&admin, &requests);
-
- assert_eq!(result.total_requests, 1);
- assert_eq!(result.successful, 1);
- assert_eq!(result.failed, 0);
- assert_eq!(result.results.len(), 1);
-
- // Check wallet was created
- let wallet = client.get_wallet(&owner).unwrap();
- assert_eq!(wallet.owner, owner);
- assert_eq!(wallet.id, 1);
-}
-
-#[test]
-fn test_batch_create_wallets_multiple() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner1.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner3.clone()));
-
- let result = client.batch_create_wallets(&admin, &requests);
-
- assert_eq!(result.total_requests, 3);
- assert_eq!(result.successful, 3);
- assert_eq!(result.failed, 0);
-
- // Check wallets were created with sequential IDs
- let wallet1 = client.get_wallet(&owner1).unwrap();
- assert_eq!(wallet1.id, 1);
- let wallet2 = client.get_wallet(&owner2).unwrap();
- assert_eq!(wallet2.id, 2);
- let wallet3 = client.get_wallet(&owner3).unwrap();
- assert_eq!(wallet3.id, 3);
-}
-
-#[test]
-fn test_batch_create_wallets_partial_failures() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- // First batch: create wallets for owner1 and owner2
- let mut requests1: Vec = Vec::new(&env);
- requests1.push_back(create_wallet_request(&env, owner1.clone()));
- requests1.push_back(create_wallet_request(&env, owner2.clone()));
- client.batch_create_wallets(&admin, &requests1);
-
- // Second batch: try to create for owner1 (duplicate), owner2 (duplicate), owner3 (new)
- let mut requests2: Vec = Vec::new(&env);
- requests2.push_back(create_wallet_request(&env, owner1.clone())); // Duplicate
- requests2.push_back(create_wallet_request(&env, owner2.clone())); // Duplicate
- requests2.push_back(create_wallet_request(&env, owner3.clone())); // New
-
- let result = client.batch_create_wallets(&admin, &requests2);
-
- assert_eq!(result.total_requests, 3);
- assert_eq!(result.successful, 1);
- assert_eq!(result.failed, 2);
-
- // Check results
- match result.results.get(0).unwrap() {
- WalletCreateResult::Failure(addr, error_code) => {
- assert_eq!(addr, owner1);
- assert_eq!(error_code, 1); // Already exists
- }
- _ => panic!("Expected failure for duplicate"),
- }
- match result.results.get(1).unwrap() {
- WalletCreateResult::Failure(addr, error_code) => {
- assert_eq!(addr, owner2);
- assert_eq!(error_code, 1); // Already exists
- }
- _ => panic!("Expected failure for duplicate"),
- }
- match result.results.get(2).unwrap() {
- WalletCreateResult::Success(addr) => {
- assert_eq!(addr, owner3);
- }
- _ => panic!("Expected success for new wallet"),
- }
-
- // Check wallet3 was created
- let wallet3 = client.get_wallet(&owner3).unwrap();
- assert_eq!(wallet3.id, 3); // IDs continue from previous batch
-}
-
-#[test]
-fn test_batch_create_wallets_events_emitted() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner1.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
-
- client.batch_create_wallets(&admin, &requests);
-
- let events = env.events().all();
- // Should have: batch_started, wallet_created (2), batch_completed
- assert!(events.len() >= 4);
-}
-
-#[test]
-fn test_batch_create_wallets_accumulates_stats() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
-
- let mut requests1: Vec = Vec::new(&env);
- requests1.push_back(create_wallet_request(&env, owner1.clone()));
-
- let mut requests2: Vec = Vec::new(&env);
- requests2.push_back(create_wallet_request(&env, owner2.clone()));
-
- assert_eq!(client.get_total_batches(), 0);
- assert_eq!(client.get_total_wallets_created(), 0);
-
- client.batch_create_wallets(&admin, &requests1);
- assert_eq!(client.get_total_batches(), 1);
- assert_eq!(client.get_total_wallets_created(), 1);
-
- client.batch_create_wallets(&admin, &requests2);
- assert_eq!(client.get_total_batches(), 2);
- assert_eq!(client.get_total_wallets_created(), 2);
-}
-
-#[test]
-#[should_panic]
-fn test_batch_create_wallets_empty_batch() {
- let (env, admin, client) = setup_test_env();
-
- let requests: Vec = Vec::new(&env);
- client.batch_create_wallets(&admin, &requests);
-}
-
-#[test]
-#[should_panic]
-fn test_batch_create_wallets_unauthorized() {
- let (env, admin, client) = setup_test_env();
-
- let unauthorized = Address::generate(&env);
- let owner = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner));
-
- // This should panic due to unauthorized access
- client.batch_create_wallets(&unauthorized, &requests);
-}
-
-#[test]
-fn test_batch_create_wallets_large_batch() {
- let (env, admin, client) = setup_test_env();
-
- // Create a batch with 50 owners
- let mut requests: Vec = Vec::new(&env);
- let mut owners: Vec = Vec::new(&env);
-
- for _i in 0..50 {
- let owner = Address::generate(&env);
- owners.push_back(owner.clone());
- requests.push_back(create_wallet_request(&env, owner));
- }
-
- let result = client.batch_create_wallets(&admin, &requests);
-
- assert_eq!(result.total_requests, 50);
- assert_eq!(result.successful, 50);
- assert_eq!(result.failed, 0);
-
- // Check some wallets
- let wallet1 = client.get_wallet(&owners.get(0).unwrap()).unwrap();
- assert_eq!(wallet1.id, 1);
- let wallet50 = client.get_wallet(&owners.get(49).unwrap()).unwrap();
- assert_eq!(wallet50.id, 50);
-}
-
-// Admin Tests
-
-#[test]
-fn test_set_admin() {
- let (env, admin, client) = setup_test_env();
-
- let new_admin = Address::generate(&env);
- client.set_admin(&admin, &new_admin);
-
- assert_eq!(client.get_admin(), new_admin);
-}
-
-// Multiple Simultaneous Batch Creations
-
-#[test]
-fn test_multiple_simultaneous_batch_creations() {
- let (env, admin, client) = setup_test_env();
-
- // First batch: 3 owners
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- let mut batch1: Vec = Vec::new(&env);
- batch1.push_back(create_wallet_request(&env, owner1.clone()));
- batch1.push_back(create_wallet_request(&env, owner2.clone()));
- batch1.push_back(create_wallet_request(&env, owner3.clone()));
-
- let result1 = client.batch_create_wallets(&admin, &batch1);
- assert_eq!(result1.successful, 3);
-
- // Second batch: 2 owners (one new, one duplicate)
- let owner4 = Address::generate(&env);
-
- let mut batch2: Vec = Vec::new(&env);
- batch2.push_back(create_wallet_request(&env, owner1.clone())); // Duplicate
- batch2.push_back(create_wallet_request(&env, owner4.clone())); // New
-
- let result2 = client.batch_create_wallets(&admin, &batch2);
- assert_eq!(result2.successful, 1);
- assert_eq!(result2.failed, 1);
-
- // Verify contract stats
- assert_eq!(client.get_total_batches(), 2);
- assert_eq!(client.get_total_wallets_created(), 4); // 3 + 1
-}
-
-#[test]
-fn test_batch_recover_wallets_single_success() {
- let (env, admin, client) = setup_test_env();
-
- let original_owner = Address::generate(&env);
- let new_owner = Address::generate(&env);
-
- let mut create_requests: Vec = Vec::new(&env);
- create_requests.push_back(create_wallet_request(&env, original_owner.clone()));
- let create_result: BatchCreateResult = client.batch_create_wallets(&admin, &create_requests);
- assert_eq!(create_result.successful, 1);
-
- let mut recovery_requests: Vec = Vec::new(&env);
- recovery_requests.push_back(create_recovery_request(
- &env,
- original_owner.clone(),
- new_owner.clone(),
- ));
-
- let recover_result: BatchRecoveryResult =
- client.batch_recover_wallets(&admin, &recovery_requests);
-
- assert_eq!(recover_result.total_requests, 1);
- assert_eq!(recover_result.successful, 1);
- assert_eq!(recover_result.failed, 0);
- assert_eq!(recover_result.results.len(), 1);
-
- match recover_result.results.get(0).unwrap() {
- WalletRecoveryResult::Success(old, new_) => {
- assert_eq!(old, original_owner);
- assert_eq!(new_, new_owner);
- }
- _ => panic!("expected success result"),
- }
-
- let original_wallet = client.get_wallet(&original_owner);
- assert!(original_wallet.is_none());
-
- let recovered_wallet = client.get_wallet(&new_owner).unwrap();
- assert_eq!(recovered_wallet.owner, new_owner);
- assert_eq!(recovered_wallet.id, 1);
-}
-
-#[test]
-fn test_batch_recover_wallets_partial_failures() {
- let (env, admin, client) = setup_test_env();
-
- let existing_owner = Address::generate(&env);
- let other_existing_owner = Address::generate(&env);
- let non_existing_owner = Address::generate(&env);
- let recovery_target_1 = Address::generate(&env);
- let recovery_target_2 = Address::generate(&env);
-
- let mut create_requests: Vec = Vec::new(&env);
- create_requests.push_back(create_wallet_request(&env, existing_owner.clone()));
- create_requests.push_back(create_wallet_request(&env, other_existing_owner.clone()));
- client.batch_create_wallets(&admin, &create_requests);
-
- let mut recovery_requests: Vec = Vec::new(&env);
- recovery_requests.push_back(create_recovery_request(
- &env,
- non_existing_owner.clone(),
- recovery_target_1.clone(),
- ));
- recovery_requests.push_back(create_recovery_request(
- &env,
- existing_owner.clone(),
- existing_owner.clone(),
- ));
- recovery_requests.push_back(create_recovery_request(
- &env,
- existing_owner.clone(),
- recovery_target_2.clone(),
- ));
-
- let recover_result = client.batch_recover_wallets(&admin, &recovery_requests);
-
- assert_eq!(recover_result.total_requests, 3);
- assert_eq!(recover_result.successful, 1);
- assert_eq!(recover_result.failed, 2);
-
- match recover_result.results.get(0).unwrap() {
- WalletRecoveryResult::Failure(old, new_, code) => {
- assert_eq!(old, non_existing_owner);
- assert_eq!(new_, recovery_target_1);
- assert_eq!(code, 1);
- }
- _ => panic!("expected failure for non-existing source wallet"),
- }
-
- match recover_result.results.get(1).unwrap() {
- WalletRecoveryResult::Failure(old, new_, code) => {
- assert_eq!(old, existing_owner);
- assert_eq!(new_, existing_owner);
- assert_eq!(code, 2);
- }
- _ => panic!("expected failure for invalid destination wallet"),
- }
-
- match recover_result.results.get(2).unwrap() {
- WalletRecoveryResult::Success(old, new_) => {
- assert_eq!(old, existing_owner);
- assert_eq!(new_, recovery_target_2);
- }
- _ => panic!("expected success for valid recovery"),
- }
-
- let still_existing = client.get_wallet(&other_existing_owner).unwrap();
- assert_eq!(still_existing.owner, other_existing_owner);
-
- let recovered_wallet = client.get_wallet(&recovery_target_2).unwrap();
- assert_eq!(recovered_wallet.owner, recovery_target_2);
-}
-
-#[test]
-fn test_batch_recover_wallets_events_emitted() {
- let (env, admin, client) = setup_test_env();
-
- let original_owner = Address::generate(&env);
- let new_owner = Address::generate(&env);
-
- let mut create_requests: Vec = Vec::new(&env);
- create_requests.push_back(create_wallet_request(&env, original_owner.clone()));
- client.batch_create_wallets(&admin, &create_requests);
-
- let mut recovery_requests: Vec = Vec::new(&env);
- recovery_requests.push_back(create_recovery_request(
- &env,
- original_owner.clone(),
- new_owner.clone(),
- ));
-
- client.batch_recover_wallets(&admin, &recovery_requests);
-
- let events = env.events().all();
- assert!(events.len() >= 3);
-}
-
-#[test]
-#[should_panic]
-fn test_batch_recover_wallets_empty_batch() {
- let (env, admin, client) = setup_test_env();
-
- let recovery_requests: Vec = Vec::new(&env);
- client.batch_recover_wallets(&admin, &recovery_requests);
-}
-
-#[test]
-#[should_panic]
-fn test_batch_recover_wallets_unauthorized() {
- let (env, _admin, client) = setup_test_env();
-
- let original_owner = Address::generate(&env);
- let new_owner = Address::generate(&env);
-
- let mut recovery_requests: Vec = Vec::new(&env);
- recovery_requests.push_back(create_recovery_request(&env, original_owner, new_owner));
-
- let unauthorized = Address::generate(&env);
- client.batch_recover_wallets(&unauthorized, &recovery_requests);
-}
-
-// ─── Duplicate Wallet Detection Tests ──────────────────────────────────────
-
-#[test]
-#[should_panic(expected = "Error(Contract, #6)")]
-fn test_batch_create_wallets_duplicate_in_batch() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
- let owner2 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- // Add duplicate requests for same owner within single batch
- requests.push_back(create_wallet_request(&env, owner.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner.clone())); // Duplicate!
-
- // This should panic with DuplicateWallet error
- client.batch_create_wallets(&admin, &requests);
-}
-
-#[test]
-#[should_panic(expected = "Error(Contract, #6)")]
-fn test_batch_create_wallets_multiple_duplicates_in_batch() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner1.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner1.clone())); // Duplicate!
- requests.push_back(create_wallet_request(&env, owner3.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone())); // Another duplicate!
-
- // This should panic with DuplicateWallet error
- client.batch_create_wallets(&admin, &requests);
-}
-
-#[test]
-#[should_panic(expected = "Error(Contract, #6)")]
-fn test_batch_create_wallets_consecutive_duplicates() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner.clone()));
- requests.push_back(create_wallet_request(&env, owner.clone())); // Duplicate!
- requests.push_back(create_wallet_request(&env, owner.clone())); // Another duplicate!
-
- // This should panic with DuplicateWallet error
- client.batch_create_wallets(&admin, &requests);
-}
-
-#[test]
-fn test_batch_create_wallets_no_duplicates_succeeds() {
- let (env, admin, client) = setup_test_env();
-
- let owner1 = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner1.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner3.clone()));
-
- // This should succeed (no duplicates)
- let result = client.batch_create_wallets(&admin, &requests);
-
- assert_eq!(result.total_requests, 3);
- assert_eq!(result.successful, 3);
- assert_eq!(result.failed, 0);
-}
-
-#[test]
-fn test_batch_create_wallets_duplicate_event_emitted() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
- let owner2 = Address::generate(&env);
-
- let mut requests: Vec = Vec::new(&env);
- requests.push_back(create_wallet_request(&env, owner.clone()));
- requests.push_back(create_wallet_request(&env, owner2.clone()));
- requests.push_back(create_wallet_request(&env, owner.clone())); // Duplicate
-
- // Attempt batch create - should fail with duplicate error
- let result = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| {
- client.batch_create_wallets(&admin, &requests);
- }));
-
- // Verify panic occurred
- assert!(result.is_err());
-
- // Verify duplicate event was published before panic
- let events = env.events().all();
- let mut duplicate_event_found = false;
- for (_, topics, _) in events.iter() {
- let topic_vec = topics.clone();
- if topic_vec.len() >= 2 {
- let topic1: soroban_sdk::Symbol =
- topic_vec.get(0).unwrap().into_val(&env).try_into().ok();
- let topic2: soroban_sdk::Symbol =
- topic_vec.get(1).unwrap().into_val(&env).try_into().ok();
- if topic1 == Some(soroban_sdk::Symbol::new(&env, "wallet"))
- && topic2 == Some(soroban_sdk::Symbol::new(&env, "duplicate"))
- {
- duplicate_event_found = true;
- break;
- }
- }
- }
- assert!(duplicate_event_found, "Duplicate event should be emitted");
-}
-
-#[test]
-fn test_batch_create_wallets_duplicate_error_identifies_entry() {
- let (env, admin, client) = setup_test_env();
-
- let owner = Address::generate(&env);
- let owner2 = Address::generate(&env);
- let owner3 = Address::generate(&env);
-
- // First batch - success
- let mut requests1: Vec = Vec::new(&env);
- requests1.push_back(create_wallet_request(&env, owner.clone()));
- requests1.push_back(create_wallet_request(&env, owner2.clone()));
- client.batch_create_wallets(&admin, &requests1);
-
- // Second batch - with duplicate from first batch
- let mut requests2: Vec = Vec::new(&env);
- requests2.push_back(create_wallet_request(&env, owner.clone())); // Existed before
- requests2.push_back(create_wallet_request(&env, owner3.clone())); // New
-
- let result = client.batch_create_wallets(&admin, &requests2);
-
- // Should process individually since duplicates are within-batch check
- // Only existing wallets should fail, not in-batch duplicates
- assert_eq!(result.total_requests, 2);
- assert_eq!(result.successful, 1);
- assert_eq!(result.failed, 1);
-
- match result.results.get(0).unwrap() {
- WalletCreateResult::Failure(addr, _) => {
- assert_eq!(addr, owner); // First request failed (already exists)
- }
- _ => panic!("Expected failure for existing wallet"),
- }
-
- match result.results.get(1).unwrap() {
- WalletCreateResult::Success(addr) => {
- assert_eq!(addr, owner3); // New wallet created
- }
- _ => panic!("Expected success for new wallet"),
- }
-}
diff --git a/contracts/src/vault.rs b/contracts/src/vault.rs
deleted file mode 100644
index fff45df8..00000000
--- a/contracts/src/vault.rs
+++ /dev/null
@@ -1,139 +0,0 @@
-#![no_std]
-use soroban_sdk::{contract, contractimpl, contracttype, symbol_short, Address, Env, String, Vec};
-
-// ---------------------------------------------------------------------------
-// Data types
-// ---------------------------------------------------------------------------
-
-#[contracttype]
-#[derive(Clone)]
-pub struct Gist {
- pub gist_id: u64,
- pub author: Option,
- pub location_cell: String,
- pub content_hash: String,
- pub created_at: u64,
-}
-
-// ---------------------------------------------------------------------------
-// Storage keys
-// ---------------------------------------------------------------------------
-
-const GIST_COUNT: soroban_sdk::Symbol = symbol_short!("GCOUNT");
-
-// ---------------------------------------------------------------------------
-// Contract
-// ---------------------------------------------------------------------------
-
-#[contract]
-pub struct GistRegistry;
-
-#[contractimpl]
-impl GistRegistry {
- /// Register a new gist on-chain. Returns the assigned gist_id.
- pub fn post_gist(
- env: Env,
- author: Option,
- location_cell: String,
- content_hash: String,
- ) -> u64 {
- let gist_id: u64 = env.storage().instance().get(&GIST_COUNT).unwrap_or(0) + 1;
-
- let gist = Gist {
- gist_id,
- author,
- location_cell: location_cell.clone(),
- content_hash,
- created_at: env.ledger().timestamp(),
- };
-
- env.storage().persistent().set(&gist_id, &gist);
- env.storage().instance().set(&GIST_COUNT, &gist_id);
-
- gist_id
- }
-
- /// Retrieve a gist record by its id.
- pub fn get_gist(env: Env, gist_id: u64) -> Option {
- env.storage().persistent().get(&gist_id)
- }
-
- /// List gists whose location_cell matches the given cell string.
- /// `cursor` is the gist_id to start scanning from (inclusive); `limit` caps results.
- pub fn list_gists_by_cell(
- env: Env,
- location_cell: String,
- cursor: u64,
- limit: u32,
- ) -> Vec {
- let total: u64 = env.storage().instance().get(&GIST_COUNT).unwrap_or(0);
- let mut results = Vec::new(&env);
- let mut count: u32 = 0;
-
- let start = if cursor == 0 { 1 } else { cursor };
-
- for id in start..=total {
- if count >= limit {
- break;
- }
- if let Some(gist) = env.storage().persistent().get::(&id) {
- if gist.location_cell == location_cell {
- results.push_back(gist);
- count += 1;
- }
- }
- }
-
- results
- }
-}
-
-// ---------------------------------------------------------------------------
-// Tests
-// ---------------------------------------------------------------------------
-
-#[cfg(test)]
-mod tests {
- use super::*;
- use soroban_sdk::testutils::Ledger;
- use soroban_sdk::{Env, String};
-
- #[test]
- fn test_post_and_get_gist() {
- let env = Env::default();
- let contract_id = env.register(GistRegistry, ());
- let client = GistRegistryClient::new(&env, &contract_id);
-
- env.ledger().set_timestamp(1_000_000);
-
- let location = String::from_str(&env, "r3gx");
- let hash = String::from_str(&env, "QmTest123");
-
- let id = client.post_gist(&None, &location, &hash);
- assert_eq!(id, 1);
-
- let gist = client.get_gist(&id).expect("gist should exist");
- assert_eq!(gist.gist_id, 1);
- assert_eq!(gist.location_cell, location);
- assert_eq!(gist.content_hash, hash);
- assert_eq!(gist.created_at, 1_000_000);
- }
-
- #[test]
- fn test_list_gists_by_cell() {
- let env = Env::default();
- let contract_id = env.register(GistRegistry, ());
- let client = GistRegistryClient::new(&env, &contract_id);
-
- let cell_a = String::from_str(&env, "r3gx");
- let cell_b = String::from_str(&env, "u4ht");
- let hash = String::from_str(&env, "Qm000");
-
- client.post_gist(&None, &cell_a, &hash);
- client.post_gist(&None, &cell_b, &hash);
- client.post_gist(&None, &cell_a, &hash);
-
- let results = client.list_gists_by_cell(&cell_a, &0, &10);
- assert_eq!(results.len(), 2);
- }
-}