diff --git a/contracts/escrow/src/lib.rs b/contracts/escrow/src/lib.rs index 4a19474..4bbc08c 100644 --- a/contracts/escrow/src/lib.rs +++ b/contracts/escrow/src/lib.rs @@ -9,11 +9,14 @@ use shared::{ events::*, types::{Amount, EscrowInfo, Hash, Milestone, MilestoneStatus}, }; +use shared::upgradeable::*; +initialize_upgrade(&env, governance_address); use soroban_sdk::{contract, contractimpl, token::TokenClient, Address, BytesN, Env, Vec}; mod storage; mod validation; + #[cfg(test)] mod tests; diff --git a/contracts/project-launch/src/lib.rs b/contracts/project-launch/src/lib.rs index 5962c52..6272765 100644 --- a/contracts/project-launch/src/lib.rs +++ b/contracts/project-launch/src/lib.rs @@ -4,6 +4,8 @@ use soroban_sdk::{ contract, contracterror, contractimpl, contracttype, token::TokenClient, Address, Bytes, Env, }; +use shared::upgradeable::*; + use shared::{ constants::{MAX_PROJECT_DURATION, MIN_CONTRIBUTION, MIN_FUNDING_GOAL, MIN_PROJECT_DURATION}, errors::Error, @@ -52,6 +54,10 @@ pub struct ProjectLaunch; #[contractimpl] impl ProjectLaunch { + + pub fn initialize(env: Env, governance: Address, ...) { + initialize_upgrade(&env, governance); + /// Initialize the contract with an admin address pub fn initialize(env: Env, admin: Address) -> Result<(), Error> { if env.storage().instance().has(&DataKey::Admin) { diff --git a/contracts/project-launch/src/test_upgrade.rs b/contracts/project-launch/src/test_upgrade.rs new file mode 100644 index 0000000..c3d9555 --- /dev/null +++ b/contracts/project-launch/src/test_upgrade.rs @@ -0,0 +1,42 @@ +#[cfg(test)] +mod test { + use soroban_sdk::{Env, BytesN}; + use crate::*; + + #[test] + fn test_upgrade_timelock() { + let env = Env::default(); + + // mock addresses etc + + // 1. propose upgrade + // 2. attempt execute -> should panic + // 3. advance time + // 4. execute -> should succeed + } +} +#[test] +#[should_panic] +fn test_execute_before_timelock() { + // setup + // propose upgrade + // execute immediately -> should panic +} + +env.ledger().with_mut(|li| { + li.timestamp += 48 * 60 * 60; +}); + +#[test] +#[should_panic] +fn test_pause_blocks_function() { + // pause + // try fund +} + +#[test] +#[should_panic] +fn test_double_proposal_fails() { + // propose + // propose again -> panic +} \ No newline at end of file diff --git a/contracts/shared/Cargo.toml b/contracts/shared/Cargo.toml index facd724..de6c090 100644 --- a/contracts/shared/Cargo.toml +++ b/contracts/shared/Cargo.toml @@ -4,8 +4,11 @@ version.workspace = true edition.workspace = true license.workspace = true +[lib] +crate-type = ["rlib", "cdylib"] + [dependencies] -soroban-sdk = { workspace = true } +soroban-sdk = { workspace = true, default-features = false, features = ["alloc"] } -[lib] -crate-type = ["rlib"] +[features] +default = [] \ No newline at end of file diff --git a/contracts/shared/src/lib.rs b/contracts/shared/src/lib.rs index 989b890..3e834c8 100644 --- a/contracts/shared/src/lib.rs +++ b/contracts/shared/src/lib.rs @@ -5,12 +5,15 @@ pub mod errors; pub mod events; pub mod types; pub mod utils; +pub mod upgradeable; pub use constants::*; pub use errors::*; pub use events::*; pub use types::*; pub use utils::*; +pub use upgradeable::*; + pub fn calculate_percentage(amount: i128, percentage: u32, total_percentage: u32) -> i128 { // Calculate using i128 to avoid precision loss diff --git a/contracts/shared/src/upgradeable.rs b/contracts/shared/src/upgradeable.rs new file mode 100644 index 0000000..3a43108 --- /dev/null +++ b/contracts/shared/src/upgradeable.rs @@ -0,0 +1,56 @@ +pub fn execute_upgrade(env: &Env) { + let admin: Address = get_admin(env); + admin.require_auth(); + + // Ensure upgrade exists + if !env.storage().instance().has(&UpgradeKey::PendingWasmHash) { + panic!("No pending upgrade"); + } + + // Ensure contract is paused + let paused: bool = env.storage() + .instance() + .get(&UpgradeKey::Paused) + .unwrap(); + + if !paused { + panic!("Contract must be paused before upgrade"); + } + + let scheduled: u64 = env.storage() + .instance() + .get(&UpgradeKey::UpgradeTimestamp) + .unwrap(); + + let now = env.ledger().timestamp(); + + if now < scheduled { + panic!("Upgrade timelock not expired"); + } + + let wasm_hash: BytesN<32> = env.storage() + .instance() + .get(&UpgradeKey::PendingWasmHash) + .unwrap(); + + env.deployer().update_current_contract_wasm(wasm_hash); + + // Increment version + let version: u32 = env.storage() + .instance() + .get(&UpgradeKey::Version) + .unwrap(); + + env.storage() + .instance() + .set(&UpgradeKey::Version, &(version + 1)); + + // Clear proposal + env.storage() + .instance() + .remove(&UpgradeKey::PendingWasmHash); + + env.storage() + .instance() + .remove(&UpgradeKey::UpgradeTimestamp); +}