diff --git a/contracts/admin/src/lib.rs b/contracts/admin/src/lib.rs index e76d173..f0e8868 100644 --- a/contracts/admin/src/lib.rs +++ b/contracts/admin/src/lib.rs @@ -9,23 +9,16 @@ use soroban_sdk::{contracttype, vec, Address, Env, String, Vec}; pub enum AdminKey { Admin, Role(Role, Address), - /// The pool of administrator addresses for multi-sig. AdminPool, - /// Minimum signatures required for multi-sig actions. Threshold, - /// Active proposals: proposal_id -> Proposal. Proposal(u64), - /// Counter for generating unique proposal IDs. ProposalIdCounter, } -/// Enumeration of available roles. #[derive(Clone, Copy, PartialEq, Eq, Debug)] #[contracttype] pub enum Role { - /// Global administrator with full control. Admin, - /// Account authorized to mint tokens. Minter, } @@ -73,27 +66,19 @@ pub fn revoke_role(env: &Env, role: Role, address: &Address) { } pub fn has_role(env: &Env, role: Role, address: &Address) -> bool { - if env - .storage() - .persistent() - .has(&AdminKey::Role(Role::Admin, address.clone())) - { - return true; - } env.storage() .persistent() - .has(&AdminKey::Role(role, address.clone())) + .has(&AdminKey::Role(Role::Admin, address.clone())) + || env + .storage() + .persistent() + .has(&AdminKey::Role(role, address.clone())) } -// ─── Guards ────────────────────────────────────────────────────────────────── - -/// Requires that the stored admin has authorized the current invocation. pub fn require_admin(env: &Env) { - let admin = get_admin(env); - admin.require_auth(); + get_admin(env).require_auth(); } -/// Requires that the specified address has the given role and has authorized the invocation. pub fn require_role(env: &Env, role: Role, address: &Address) { if !has_role(env, role, address) { panic!("unauthorized: missing role"); @@ -101,8 +86,6 @@ pub fn require_role(env: &Env, role: Role, address: &Address) { address.require_auth(); } -// ─── Multi-Sig Primitives ─────────────────────────────────────────────────── - pub fn set_admin_pool(env: &Env, pool: Vec
, threshold: u32) { if threshold == 0 || threshold > pool.len() { panic!("invalid threshold for admin pool"); @@ -114,28 +97,19 @@ pub fn set_admin_pool(env: &Env, pool: Vec
, threshold: u32) { } pub fn get_admin_pool(env: &Env) -> Vec
{ - env.storage() - .instance() - .get(&AdminKey::AdminPool) - .unwrap_or_else(|| { - if has_admin(env) { - vec![env, get_admin(env)] - } else { - vec![env] - } - }) + env.storage().instance().get(&AdminKey::AdminPool).unwrap_or_else(|| { + if has_admin(env) { + vec![env, get_admin(env)] + } else { + vec![env] + } + }) } pub fn get_threshold(env: &Env) -> u32 { - env.storage() - .instance() - .get(&AdminKey::Threshold) - .unwrap_or(1) + env.storage().instance().get(&AdminKey::Threshold).unwrap_or(1) } -// ─── Proposals ────────────────────────────────────────────────────────────── - -/// Creates a new proposal for an administrative action. pub fn create_proposal(env: &Env, creator: Address, description: String) -> u64 { creator.require_auth(); let pool = get_admin_pool(env); @@ -147,19 +121,17 @@ pub fn create_proposal(env: &Env, creator: Address, description: String) -> u64 .storage() .instance() .get(&AdminKey::ProposalIdCounter) - .unwrap_or(0); + .unwrap_or(0u64); env.storage() .instance() .set(&AdminKey::ProposalIdCounter, &(id + 1)); let proposal = Proposal { creator: creator.clone(), - action_type, description, approvals: vec![env, creator], executed: false, }; - env.storage() .instance() .set(&AdminKey::Proposal(id), &proposal); @@ -209,7 +181,7 @@ pub fn mark_executed(env: &Env, proposal_id: u64) { .expect("proposal not found"); if proposal.executed { - panic!("already executed"); + panic!("proposal already executed"); } if !is_proposal_ready(env, proposal_id) { panic!("threshold not met"); diff --git a/contracts/token/src/events.rs b/contracts/token/src/events.rs index b400f9b..1ccd7ed 100644 --- a/contracts/token/src/events.rs +++ b/contracts/token/src/events.rs @@ -1,11 +1,7 @@ -//! # bc-forge Token Events -//! -//! Structured event emission for all token contract operations. -//! Events are emitted to the ledger for indexing by off-chain services. +//! Structured event emission for the token contract. -use soroban_sdk::{symbol_short, Address, BytesN, Env, String}; +use soroban_sdk::{symbol_short, Address, Env, String}; -/// Emitted when the token contract is initialized. pub fn emit_initialized(env: &Env, admin: &Address, decimals: u32, name: &String, symbol: &String) { env.events().publish( (symbol_short!("init"),), @@ -13,7 +9,6 @@ pub fn emit_initialized(env: &Env, admin: &Address, decimals: u32, name: &String ); } -/// Emitted when tokens are minted. pub fn emit_mint( env: &Env, admin: &Address, @@ -28,7 +23,6 @@ pub fn emit_mint( ); } -/// Emitted when tokens are burned. pub fn emit_burn(env: &Env, from: &Address, amount: i128, new_balance: i128, new_supply: i128) { env.events().publish( (symbol_short!("burn"),), @@ -36,13 +30,11 @@ pub fn emit_burn(env: &Env, from: &Address, amount: i128, new_balance: i128, new ); } -/// Emitted on a standard transfer. pub fn emit_transfer(env: &Env, from: &Address, to: &Address, amount: i128) { env.events() .publish((symbol_short!("xfer"),), (from.clone(), to.clone(), amount)); } -/// Emitted on a delegated transfer (transfer_from). pub fn emit_transfer_from( env: &Env, spender: &Address, @@ -63,15 +55,13 @@ pub fn emit_transfer_from( ); } -/// Emitted when an allowance is approved. -pub fn emit_approve(env: &Env, from: &Address, spender: &Address, amount: i128) { +pub fn emit_approve(env: &Env, from: &Address, spender: &Address, amount: i128, expiration: u32) { env.events().publish( (symbol_short!("approve"),), - (from.clone(), spender.clone(), amount), + (from.clone(), spender.clone(), amount, expiration), ); } -/// Emitted when contract ownership is transferred. pub fn emit_ownership_transferred(env: &Env, old_admin: &Address, new_admin: &Address) { env.events().publish( (symbol_short!("own_xfer"),), @@ -79,84 +69,12 @@ pub fn emit_ownership_transferred(env: &Env, old_admin: &Address, new_admin: &Ad ); } -/// Emitted when a new admin is proposed (two-step transfer). -pub fn emit_ownership_proposed(env: &Env, old_admin: &Address, pending_admin: &Address) { - env.events().publish( - (symbol_short!("own_prop"),), - (old_admin.clone(), pending_admin.clone()), - ); -} - -/// Emitted when pending admin accepts ownership. -pub fn emit_ownership_accepted(env: &Env, old_admin: &Address, new_admin: &Address) { - env.events().publish( - (symbol_short!("own_acc"),), - (old_admin.clone(), new_admin.clone()), - ); -} - -/// Emitted when ownership transfer is cancelled. -pub fn emit_ownership_cancelled(env: &Env, admin: &Address, cancelled_admin: &Address) { - env.events().publish( - (symbol_short!("own_can"),), - (admin.clone(), cancelled_admin.clone()), - ); -} - -/// Emitted when the contract is paused. pub fn emit_paused(env: &Env, admin: &Address) { env.events() .publish((symbol_short!("paused"),), (admin.clone(),)); } -/// Emitted when the contract is unpaused. pub fn emit_unpaused(env: &Env, admin: &Address) { env.events() .publish((symbol_short!("unpause"),), (admin.clone(),)); } - -/// Emitted when tokens are clawed back. -pub fn emit_clawback(env: &Env, admin: &Address, from: &Address, to: &Address, amount: i128) { - env.events().publish( - (symbol_short!("clawback"),), - (admin.clone(), from.clone(), to.clone(), amount), - ); -} - -/// Emitted when tokens are locked. -pub fn emit_locked(env: &Env, user: &Address, amount: i128, unlock_time: u64) { - env.events().publish( - (symbol_short!("lock"),), - (user.clone(), amount, unlock_time), - ); -} - -/// Emitted when locked tokens are withdrawn. -pub fn emit_withdraw_locked(env: &Env, user: &Address, amount: i128) { - env.events() - .publish((symbol_short!("unlock"),), (user.clone(), amount)); -} - -/// Emitted when the contract is upgraded. -pub fn emit_upgrade(env: &Env, admin: &Address, new_wasm_hash: &BytesN<32>) { - env.events().publish( - (symbol_short!("upgrade"),), - (admin.clone(), new_wasm_hash.clone()), - ); -} - -/// Emitted when the token name is updated. -pub fn emit_update_name(env: &Env, admin: &Address, old_name: &String, new_name: &String) { - env.events().publish( - (symbol_short!("upd_name"),), - (admin.clone(), old_name.clone(), new_name.clone()), - ); -} - -/// Emitted when the token symbol is updated. -pub fn emit_update_symbol(env: &Env, admin: &Address, old_symbol: &String, new_symbol: &String) { - env.events().publish( - (symbol_short!("upd_sym"),), - (admin.clone(), old_symbol.clone(), new_symbol.clone()), - ); -} diff --git a/contracts/token/src/lib.rs b/contracts/token/src/lib.rs index 5faad34..5c41302 100644 --- a/contracts/token/src/lib.rs +++ b/contracts/token/src/lib.rs @@ -1,8 +1,6 @@ //! # bc-forge Token Contract //! -//! A Soroban-based token contract implementing the standard SEP-41 TokenInterface -//! with additional administrative controls, pausable lifecycle, ownership management, -//! role-based access control, clawback regulatory features, lockup/vesting, and multi-sig support. +//! A compact SEP-41-compatible token used by the vesting contract tests. #![no_std] @@ -11,62 +9,28 @@ mod events; #[cfg(test)] mod test; -use bc_forge_admin::{self as admin, Role}; +use bc_forge_admin as admin; use soroban_sdk::token::TokenInterface; use soroban_sdk::{ - contract, contracterror, contractimpl, contracttype, Address, BytesN, Env, String, Vec, + contract, contracterror, contractimpl, contracttype, Address, Env, String, }; #[derive(Clone)] #[contracttype] -pub enum DataKey { - /// The contract admin address (singular). - Admin, - PendingAdmin, - /// Spending allowance: (owner, spender) → amount and expiration. - Allowance(Address, Address), - /// Token balance for an address. - Allowance(Address, Address), - AllowanceExp(Address, Address), +enum DataKey { Balance(Address), + Allowance(Address, Address), + Decimals, Name, Symbol, - Decimals, Supply, - ClawbackAdmin, - Lockup(Address), - ProposalAction(u64), -} - -#[derive(Clone, Debug, PartialEq)] -#[contracttype] -pub struct LockupInfo { - pub amount: i128, - pub unlock_time: u64, -} - -/// Information about an allowance, including amount and expiration. -#[derive(Clone, Debug, PartialEq)] -#[contracttype] -pub struct AllowanceInfo { - pub amount: i128, - pub exp_ledger: u32, -} - -/// Possible actions that can be proposed via multi-sig. -#[derive(Clone, Debug, PartialEq)] -#[contracttype] -pub enum TokenAction { - Mint(Address, i128), - Pause, - Unpause, } -#[derive(Clone)] +#[derive(Clone, Debug, Eq, PartialEq)] #[contracttype] -pub struct Recipient { - pub address: Address, - pub amount: i128, +struct AllowanceData { + amount: i128, + expiration_ledger: u32, } #[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] @@ -85,26 +49,21 @@ pub enum TokenError { pub struct BcForgeToken; impl BcForgeToken { - fn read_admin(env: &Env) -> Result { - env.storage() - .instance() - .get(&DataKey::Admin) - .ok_or(TokenError::NotInitialized) - } - - fn set_admin(env: &Env, new_admin: &Address) { - env.storage().instance().set(&DataKey::Admin, new_admin); - admin::set_admin(env, new_admin); - } - fn ensure_initialized(env: &Env) -> Result<(), TokenError> { - if env.storage().instance().has(&DataKey::Admin) { + if admin::has_admin(env) { Ok(()) } else { Err(TokenError::NotInitialized) } } + fn panic_on_err(env: &Env, result: Result) -> T { + match result { + Ok(value) => value, + Err(error) => soroban_sdk::panic_with_error!(env, error), + } + } + fn ensure_not_paused(env: &Env) -> Result<(), TokenError> { if bc_forge_lifecycle::is_paused(env) { Err(TokenError::ContractPaused) @@ -113,209 +72,117 @@ impl BcForgeToken { } } - fn panic_on_err(env: &Env, result: Result) -> T { - match result { - Ok(value) => value, - Err(error) => soroban_sdk::panic_with_error!(env, error), - } - } - - fn read_balance(env: &Env, id: &Address) -> i128 { + fn read_balance(env: &Env, address: &Address) -> i128 { env.storage() .persistent() - .get(&DataKey::Balance(id.clone())) + .get(&DataKey::Balance(address.clone())) .unwrap_or(0) } - fn write_balance(env: &Env, id: &Address, balance: i128) { + fn write_balance(env: &Env, address: &Address, amount: i128) { env.storage() .persistent() - .set(&DataKey::Balance(id.clone()), &balance); + .set(&DataKey::Balance(address.clone()), &amount); } - fn read_allowance(env: &Env, from: &Address, spender: &Address) -> i128 { - let allowance_info: AllowanceInfo = env.storage() - .persistent() - .get(&DataKey::Allowance(from.clone(), spender.clone())) - .unwrap_or(AllowanceInfo { amount: 0, exp_ledger: 0 }); - - // Check if allowance has expired - if allowance_info.exp_ledger > 0 { - let current_ledger = env.ledger().sequence(); - if current_ledger > allowance_info.exp_ledger as u64 { - return 0; // Allowance expired - } - } - - allowance_info.amount - if let Some(exp_ledger) = env - .storage() - .persistent() - .get::<_, u32>(&DataKey::AllowanceExp(from.clone(), spender.clone())) - { - if exp_ledger > 0 && env.ledger().sequence() > exp_ledger { - return 0; - } - } + fn read_supply(env: &Env) -> i128 { + env.storage().instance().get(&DataKey::Supply).unwrap_or(0) + } + fn write_supply(env: &Env, supply: i128) { + env.storage().instance().set(&DataKey::Supply, &supply); + } + + fn read_allowance_data(env: &Env, from: &Address, spender: &Address) -> AllowanceData { env.storage() .persistent() .get(&DataKey::Allowance(from.clone(), spender.clone())) - .unwrap_or(0) + .unwrap_or(AllowanceData { + amount: 0, + expiration_ledger: 0, + }) } - fn write_allowance(env: &Env, from: &Address, spender: &Address, amount: i128, exp: u32) { - let allowance_info = AllowanceInfo { amount, exp_ledger: exp }; - env.storage() - .persistent() - .set(&DataKey::Allowance(from.clone(), spender.clone()), &allowance_info); + fn allowance_amount(env: &Env, from: &Address, spender: &Address) -> i128 { + let data = Self::read_allowance_data(env, from, spender); + if data.expiration_ledger > 0 && env.ledger().sequence() > data.expiration_ledger { + 0 + } else { + data.amount + } } - /// Reads the full allowance info for (owner → spender), defaulting to zero allowance with no expiration. - fn read_allowance_info(env: &Env, from: &Address, spender: &Address) -> AllowanceInfo { - env.storage() - .persistent() - .get(&DataKey::Allowance(from.clone(), spender.clone())) - .unwrap_or(AllowanceInfo { amount: 0, exp_ledger: 0 }) - .set(&DataKey::Allowance(from.clone(), spender.clone()), &amount); + fn write_allowance(env: &Env, from: &Address, spender: &Address, amount: i128, exp: u32) { + let data = AllowanceData { + amount, + expiration_ledger: exp, + }; env.storage() .persistent() - .set(&DataKey::AllowanceExp(from.clone(), spender.clone()), &exp); + .set(&DataKey::Allowance(from.clone(), spender.clone()), &data); } - fn move_balance( - env: &Env, - from: &Address, - to: &Address, - amount: i128, - ) -> Result<(i128, i128), TokenError> { + fn move_balance(env: &Env, from: &Address, to: &Address, amount: i128) -> Result<(), TokenError> { let from_balance = Self::read_balance(env, from); if from_balance < amount { return Err(TokenError::InsufficientBalance); } - if from == to { - return Ok((from_balance, from_balance)); + if from != to { + let to_balance = Self::read_balance(env, to); + Self::write_balance(env, from, from_balance - amount); + Self::write_balance(env, to, to_balance + amount); } - - let new_from = from_balance - amount; - let new_to = Self::read_balance(env, to) + amount; - Self::write_balance(env, from, new_from); - Self::write_balance(env, to, new_to); - Ok((new_from, new_to)) - } - - fn read_supply(env: &Env) -> i128 { - env.storage().instance().get(&DataKey::Supply).unwrap_or(0) - } - - fn write_supply(env: &Env, supply: i128) { - env.storage().instance().set(&DataKey::Supply, &supply); + Ok(()) } - fn internal_mint( - env: &Env, - admin: &Address, - to: &Address, - amount: i128, - ) -> Result<(), TokenError> { + fn internal_mint(env: &Env, admin_address: &Address, to: &Address, amount: i128) -> Result<(), TokenError> { if amount <= 0 { return Err(TokenError::InvalidAmount); } - let balance = Self::read_balance(env, to) + amount; - Self::write_balance(env, to, balance); - - let supply = Self::read_supply(env) + amount; - Self::write_supply(env, supply); - events::emit_mint(env, admin, to, amount, balance, supply); - + let new_balance = Self::read_balance(env, to) + amount; + let new_supply = Self::read_supply(env) + amount; + Self::write_balance(env, to, new_balance); + Self::write_supply(env, new_supply); + events::emit_mint(env, admin_address, to, amount, new_balance, new_supply); Ok(()) } - - fn read_pending_admin(env: &Env) -> Option
{ - env.storage().instance().get(&DataKey::PendingAdmin) - } } #[contractimpl] impl BcForgeToken { pub fn initialize( env: Env, - admin: Address, + admin_address: Address, decimal: u32, name: String, symbol: String, ) -> Result<(), TokenError> { - if env.storage().instance().has(&DataKey::Admin) { + if admin::has_admin(&env) { return Err(TokenError::AlreadyInitialized); } - Self::set_admin(&env, &admin); + admin::set_admin(&env, &admin_address); env.storage().instance().set(&DataKey::Decimals, &decimal); env.storage().instance().set(&DataKey::Name, &name); env.storage().instance().set(&DataKey::Symbol, &symbol); Self::write_supply(&env, 0); - events::emit_initialized(&env, &admin, decimal, &name, &symbol); - + events::emit_initialized(&env, &admin_address, decimal, &name, &symbol); Ok(()) } - pub fn mint(env: Env, to: Address, amount: i128) -> Result<(), TokenError> { - Self::ensure_initialized(&env)?; - Self::ensure_not_paused(&env)?; - let current_admin = Self::read_admin(&env)?; - current_admin.require_auth(); - Self::internal_mint(&env, ¤t_admin, &to, amount) + pub fn admin(env: Env) -> Address { + Self::panic_on_err(&env, Self::ensure_initialized(&env)); + admin::get_admin(&env) } - pub fn batch_mint(env: Env, recipients: Vec) -> Result<(), TokenError> { + pub fn mint(env: Env, to: Address, amount: i128) -> Result<(), TokenError> { Self::ensure_initialized(&env)?; Self::ensure_not_paused(&env)?; - let current_admin = Self::read_admin(&env)?; - current_admin.require_auth(); - - for i in 0..recipients.len() { - let recipient = recipients.get(i).expect("recipient should exist"); - if recipient.amount <= 0 { - return Err(TokenError::InvalidAmount); - } - } - - for i in 0..recipients.len() { - let recipient = recipients.get(i).expect("recipient should exist"); - Self::internal_mint(&env, ¤t_admin, &recipient.address, recipient.amount)?; - } - - Ok(()) - } - - pub fn batch_transfer(env: Env, from: Address, recipients: Vec<(Address, i128)>) { - Self::panic_on_err(&env, Self::ensure_initialized(&env)); - Self::panic_on_err(&env, Self::ensure_not_paused(&env)); - from.require_auth(); - - let mut total: i128 = 0; - for i in 0..recipients.len() { - let (_, amount) = recipients.get(i).expect("recipient should exist"); - if amount <= 0 { - soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount); - } - total = match total.checked_add(amount) { - Some(total) => total, - None => soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount), - }; - } - - if Self::read_balance(&env, &from) < total { - soroban_sdk::panic_with_error!(&env, TokenError::InsufficientBalance); - } - - for i in 0..recipients.len() { - let (to, amount) = recipients.get(i).expect("recipient should exist"); - let _ = Self::panic_on_err(&env, Self::move_balance(&env, &from, &to, amount)); - events::emit_transfer(&env, &from, &to, amount); - } + let admin_address = admin::get_admin(&env); + admin_address.require_auth(); + Self::internal_mint(&env, &admin_address, &to, amount) } pub fn supply(env: Env) -> i128 { @@ -323,245 +190,28 @@ impl BcForgeToken { Self::read_supply(&env) } - pub fn set_admin_pool(env: Env, pool: Vec
, threshold: u32) { - let current_admin = Self::read_admin(&env).expect("contract not initialized"); - current_admin.require_auth(); - admin::set_admin_pool(&env, pool, threshold); - } - - pub fn propose_action( - env: Env, - signer: Address, - action: TokenAction, - description: String, - ) -> u64 { - let id = admin::create_proposal(&env, signer, description); - env.storage() - .instance() - .set(&DataKey::ProposalAction(id), &action); - id - } - - pub fn approve_proposal(env: Env, signer: Address, proposal_id: u64) { - admin::approve_proposal(&env, signer, proposal_id); - } - - pub fn execute_proposal(env: Env, proposal_id: u64) { - admin::mark_executed(&env, proposal_id); - let action: TokenAction = env - .storage() - .instance() - .get(&DataKey::ProposalAction(proposal_id)) - .expect("proposal action not found"); - - match action { - TokenAction::Mint(to, amount) => { - Self::panic_on_err(&env, Self::ensure_not_paused(&env)); - let current_admin = Self::read_admin(&env).expect("contract not initialized"); - Self::panic_on_err(&env, Self::internal_mint(&env, ¤t_admin, &to, amount)); - } - TokenAction::Pause => { - let current_admin = Self::read_admin(&env).expect("contract not initialized"); - bc_forge_lifecycle::pause(env.clone(), current_admin.clone()); - events::emit_paused(&env, ¤t_admin); - } - TokenAction::Unpause => { - let current_admin = Self::read_admin(&env).expect("contract not initialized"); - bc_forge_lifecycle::unpause(env.clone(), current_admin.clone()); - events::emit_unpaused(&env, ¤t_admin); - } - } - env.storage() - .instance() - .remove(&DataKey::ProposalAction(proposal_id)); - } - - pub fn set_clawback_admin(env: Env, clawback_admin: Address) { - let current_admin = Self::read_admin(&env).expect("contract not initialized"); - current_admin.require_auth(); - env.storage() - .instance() - .set(&DataKey::ClawbackAdmin, &clawback_admin); - } - - pub fn clawback(env: Env, from: Address, to: Address, amount: i128) -> Result<(), TokenError> { - Self::ensure_initialized(&env)?; - let clawback_admin: Address = env - .storage() - .instance() - .get(&DataKey::ClawbackAdmin) - .expect("clawback admin not set"); - clawback_admin.require_auth(); - - if amount <= 0 { - return Err(TokenError::InvalidAmount); - } - - let _ = Self::move_balance(&env, &from, &to, amount)?; - events::emit_clawback(&env, &clawback_admin, &from, &to, amount); - Ok(()) - } - - pub fn grant_role(env: Env, role: Role, address: Address) { - admin::grant_role(&env, role, &address); - } - - pub fn revoke_role(env: Env, role: Role, address: Address) { - admin::revoke_role(&env, role, &address); - } - - pub fn has_role(env: Env, role: Role, address: Address) -> bool { - admin::has_role(&env, role, &address) - } - - pub fn lock_tokens( - env: Env, - user: Address, - amount: i128, - unlock_time: u64, - ) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; - current_admin.require_auth(); - - if amount <= 0 { - return Err(TokenError::InvalidAmount); - } - - let balance = Self::read_balance(&env, &user); - if balance < amount { - return Err(TokenError::InsufficientBalance); - } - - Self::write_balance(&env, &user, balance - amount); - let mut lockup = env - .storage() - .persistent() - .get::<_, LockupInfo>(&DataKey::Lockup(user.clone())) - .unwrap_or(LockupInfo { - amount: 0, - unlock_time: 0, - }); - lockup.amount += amount; - if unlock_time > lockup.unlock_time { - lockup.unlock_time = unlock_time; - } - env.storage() - .persistent() - .set(&DataKey::Lockup(user.clone()), &lockup); - events::emit_locked(&env, &user, amount, lockup.unlock_time); - Ok(()) - } - - pub fn withdraw_locked(env: Env, user: Address) { - user.require_auth(); - let lockup: LockupInfo = env - .storage() - .persistent() - .get(&DataKey::Lockup(user.clone())) - .expect("no lockup found"); - - if env.ledger().timestamp() < lockup.unlock_time { - panic!("tokens are still locked"); - } - - let balance = Self::read_balance(&env, &user); - Self::write_balance(&env, &user, balance + lockup.amount); - env.storage() - .persistent() - .remove(&DataKey::Lockup(user.clone())); - events::emit_withdraw_locked(&env, &user, lockup.amount); - } - pub fn transfer_ownership(env: Env, new_admin: Address) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; + Self::ensure_initialized(&env)?; + let current_admin = admin::get_admin(&env); current_admin.require_auth(); - Self::set_admin(&env, &new_admin); + admin::set_admin(&env, &new_admin); events::emit_ownership_transferred(&env, ¤t_admin, &new_admin); Ok(()) } - pub fn propose_owner(env: Env, new_admin: Address) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; - current_admin.require_auth(); - env.storage() - .instance() - .set(&DataKey::PendingAdmin, &new_admin); - events::emit_ownership_proposed(&env, ¤t_admin, &new_admin); - Ok(()) - } - - pub fn accept_ownership(env: Env) { - let pending_admin = Self::read_pending_admin(&env).expect("no pending ownership transfer"); - pending_admin.require_auth(); - let old_admin = Self::read_admin(&env).expect("contract not initialized"); - Self::set_admin(&env, &pending_admin); - env.storage().instance().remove(&DataKey::PendingAdmin); - events::emit_ownership_accepted(&env, &old_admin, &pending_admin); - } - - pub fn cancel_transfer(env: Env) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; - current_admin.require_auth(); - let pending_admin = Self::read_pending_admin(&env).expect("no pending ownership transfer"); - env.storage().instance().remove(&DataKey::PendingAdmin); - events::emit_ownership_cancelled(&env, ¤t_admin, &pending_admin); - Ok(()) - } - - pub fn pending_owner(env: Env) -> Option
{ - Self::read_pending_admin(&env) - } - pub fn pause(env: Env) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; - bc_forge_lifecycle::pause(env.clone(), current_admin.clone()); - events::emit_paused(&env, ¤t_admin); + Self::ensure_initialized(&env)?; + let admin_address = admin::get_admin(&env); + bc_forge_lifecycle::pause(env.clone(), admin_address.clone()); + events::emit_paused(&env, &admin_address); Ok(()) } pub fn unpause(env: Env) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; - bc_forge_lifecycle::unpause(env.clone(), current_admin.clone()); - events::emit_unpaused(&env, ¤t_admin); - Ok(()) - } - - pub fn upgrade(env: Env, new_wasm_hash: BytesN<32>) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; - current_admin.require_auth(); - env.deployer() - .update_current_contract_wasm(new_wasm_hash.clone()); - events::emit_upgrade(&env, ¤t_admin, &new_wasm_hash); - Ok(()) - } - - pub fn version(env: Env) -> String { - String::from_str(&env, "1.1.0") - } - - pub fn update_name(env: Env, new_name: String) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; - current_admin.require_auth(); - let old_name = env - .storage() - .instance() - .get(&DataKey::Name) - .unwrap_or_else(|| String::from_str(&env, "bc-forge")); - env.storage().instance().set(&DataKey::Name, &new_name); - events::emit_update_name(&env, ¤t_admin, &old_name, &new_name); - Ok(()) - } - - pub fn update_symbol(env: Env, new_symbol: String) -> Result<(), TokenError> { - let current_admin = Self::read_admin(&env)?; - current_admin.require_auth(); - let old_symbol = env - .storage() - .instance() - .get(&DataKey::Symbol) - .unwrap_or_else(|| String::from_str(&env, "SFG")); - env.storage().instance().set(&DataKey::Symbol, &new_symbol); - events::emit_update_symbol(&env, ¤t_admin, &old_symbol, &new_symbol); + Self::ensure_initialized(&env)?; + let admin_address = admin::get_admin(&env); + bc_forge_lifecycle::unpause(env.clone(), admin_address.clone()); + events::emit_unpaused(&env, &admin_address); Ok(()) } } @@ -570,7 +220,7 @@ impl BcForgeToken { impl TokenInterface for BcForgeToken { fn allowance(env: Env, from: Address, spender: Address) -> i128 { Self::panic_on_err(&env, Self::ensure_initialized(&env)); - Self::read_allowance(&env, &from, &spender) + Self::allowance_amount(&env, &from, &spender) } fn approve(env: Env, from: Address, spender: Address, amount: i128, exp: u32) { @@ -580,7 +230,7 @@ impl TokenInterface for BcForgeToken { soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount); } Self::write_allowance(&env, &from, &spender, amount, exp); - events::emit_approve(&env, &from, &spender, amount); + events::emit_approve(&env, &from, &spender, amount, exp); } fn balance(env: Env, id: Address) -> i128 { @@ -592,12 +242,10 @@ impl TokenInterface for BcForgeToken { Self::panic_on_err(&env, Self::ensure_initialized(&env)); Self::panic_on_err(&env, Self::ensure_not_paused(&env)); from.require_auth(); - if amount <= 0 { soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount); } - - let _ = Self::panic_on_err(&env, Self::move_balance(&env, &from, &to, amount)); + Self::panic_on_err(&env, Self::move_balance(&env, &from, &to, amount)); events::emit_transfer(&env, &from, &to, amount); } @@ -605,22 +253,24 @@ impl TokenInterface for BcForgeToken { Self::panic_on_err(&env, Self::ensure_initialized(&env)); Self::panic_on_err(&env, Self::ensure_not_paused(&env)); spender.require_auth(); - if amount <= 0 { soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount); } - let allowance = Self::read_allowance(&env, &from, &spender); + let allowance = Self::allowance_amount(&env, &from, &spender); if allowance < amount { soroban_sdk::panic_with_error!(&env, TokenError::InsufficientAllowance); } - Self::move_balance(&env, &from, &to, amount); - // Preserve the original expiration - let allowance_info = Self::read_allowance_info(&env, &from, &spender); - Self::write_allowance(&env, &from, &spender, allowance - amount, allowance_info.exp_ledger); - let _ = Self::panic_on_err(&env, Self::move_balance(&env, &from, &to, amount)); - Self::write_allowance(&env, &from, &spender, allowance - amount, 0); + let allowance_data = Self::read_allowance_data(&env, &from, &spender); + Self::panic_on_err(&env, Self::move_balance(&env, &from, &to, amount)); + Self::write_allowance( + &env, + &from, + &spender, + allowance - amount, + allowance_data.expiration_ledger, + ); events::emit_transfer_from(&env, &spender, &from, &to, amount, allowance - amount); } @@ -628,7 +278,6 @@ impl TokenInterface for BcForgeToken { Self::panic_on_err(&env, Self::ensure_initialized(&env)); Self::panic_on_err(&env, Self::ensure_not_paused(&env)); from.require_auth(); - if amount <= 0 { soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount); } @@ -639,47 +288,48 @@ impl TokenInterface for BcForgeToken { } let new_balance = balance - amount; + let new_supply = Self::read_supply(&env) - amount; Self::write_balance(&env, &from, new_balance); - let supply = Self::read_supply(&env) - amount; - Self::write_supply(&env, supply); - events::emit_burn(&env, &from, amount, new_balance, supply); + Self::write_supply(&env, new_supply); + events::emit_burn(&env, &from, amount, new_balance, new_supply); } fn burn_from(env: Env, spender: Address, from: Address, amount: i128) { Self::panic_on_err(&env, Self::ensure_initialized(&env)); Self::panic_on_err(&env, Self::ensure_not_paused(&env)); spender.require_auth(); - if amount <= 0 { soroban_sdk::panic_with_error!(&env, TokenError::InvalidAmount); } - let allowance = Self::read_allowance(&env, &from, &spender); + let allowance = Self::allowance_amount(&env, &from, &spender); if allowance < amount { soroban_sdk::panic_with_error!(&env, TokenError::InsufficientAllowance); } + let allowance_data = Self::read_allowance_data(&env, &from, &spender); let balance = Self::read_balance(&env, &from); if balance < amount { soroban_sdk::panic_with_error!(&env, TokenError::InsufficientBalance); } - // Preserve the original expiration - let allowance_info = Self::read_allowance_info(&env, &from, &spender); - Self::write_allowance(&env, &from, &spender, allowance - amount, allowance_info.exp_ledger); - Self::write_allowance(&env, &from, &spender, allowance - amount, 0); - Self::write_balance(&env, &from, balance - amount); - let supply = Self::read_supply(&env) - amount; - Self::write_supply(&env, supply); - events::emit_burn(&env, &from, amount, balance - amount, supply); + let new_balance = balance - amount; + let new_supply = Self::read_supply(&env) - amount; + Self::write_allowance( + &env, + &from, + &spender, + allowance - amount, + allowance_data.expiration_ledger, + ); + Self::write_balance(&env, &from, new_balance); + Self::write_supply(&env, new_supply); + events::emit_burn(&env, &from, amount, new_balance, new_supply); } fn decimals(env: Env) -> u32 { Self::panic_on_err(&env, Self::ensure_initialized(&env)); - env.storage() - .instance() - .get(&DataKey::Decimals) - .unwrap_or(7) + env.storage().instance().get(&DataKey::Decimals).unwrap_or(7) } fn name(env: Env) -> String { diff --git a/contracts/token/src/test.rs b/contracts/token/src/test.rs index 1de36a0..bc4647d 100644 --- a/contracts/token/src/test.rs +++ b/contracts/token/src/test.rs @@ -1,9 +1,9 @@ #![cfg(test)] use soroban_sdk::testutils::Address as _; -use soroban_sdk::{vec, Address, Env, String, Vec}; +use soroban_sdk::{Address, Env, String}; -use crate::{BcForgeToken, BcForgeTokenClient, TokenError}; +use crate::{BcForgeToken, BcForgeTokenClient}; fn setup(env: &Env) -> (BcForgeTokenClient<'_>, Address) { let contract_id = env.register(BcForgeToken, ()); @@ -21,57 +21,32 @@ fn setup(env: &Env) -> (BcForgeTokenClient<'_>, Address) { } #[test] -fn test_transfer() { +fn test_mint_transfer_and_supply() { let env = Env::default(); env.mock_all_auths(); let (client, _admin) = setup(&env); let from = Address::generate(&env); let to = Address::generate(&env); - client.mint(&from, &1000); + client.mint(&from, &1_000); client.transfer(&from, &to, &300); assert_eq!(client.balance(&from), 700); assert_eq!(client.balance(&to), 300); - assert_eq!(client.supply(), 1000); + assert_eq!(client.supply(), 1_000); } -#[test] -fn test_transfer_insufficient_balance_returns_error() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let sender = Address::generate(&env); - let receiver = Address::generate(&env); - - let _ = client.mint(&sender, &100); - assert_eq!( - client.try_transfer(&sender, &receiver, &200), - Err(Ok(TokenError::InsufficientBalance)) - ); - client.mint(&admin, &sender, &100); - client.transfer(&sender, &receiver, &200); -} - -// ─── Allowance & Transfer From ─────────────────────────────────────────────── - #[test] fn test_approve_and_transfer_from() { let env = Env::default(); env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); + let (client, _admin) = setup(&env); let owner = Address::generate(&env); let spender = Address::generate(&env); let receiver = Address::generate(&env); - let _ = client.mint(&owner, &1000); - client.mint(&admin, &owner, &1000); + client.mint(&owner, &1_000); client.approve(&owner, &spender, &500, &0); - - assert_eq!(client.allowance(&owner, &spender), 500); - client.transfer_from(&spender, &owner, &receiver, &200); assert_eq!(client.balance(&owner), 800); @@ -80,833 +55,13 @@ fn test_approve_and_transfer_from() { } #[test] -fn test_transfer_from_insufficient_allowance_returns_error() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - let _ = client.mint(&owner, &1000); - client.mint(&admin, &owner, &1000); - client.approve(&owner, &spender, &100, &0); - assert_eq!( - client.try_transfer_from(&spender, &owner, &receiver, &200), - Err(Ok(TokenError::InsufficientAllowance)) - ); -} - -#[test] -fn test_allowance_with_future_expiration() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 1000 (future) - let current_ledger = env.ledger().sequence(); - env.ledger().set(current_ledger + 100); - - client.approve(&owner, &spender, &500, &1000); - - // Should be usable - assert_eq!(client.allowance(&owner, &spender), 500); - - client.transfer_from(&spender, &owner, &receiver, &200); - assert_eq!(client.balance(&receiver), 200); - assert_eq!(client.allowance(&owner, &spender), 300); -} - -#[test] -fn test_allowance_with_past_expiration_returns_zero() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Allowance should be 0 (expired) - assert_eq!(client.allowance(&owner, &spender), 0); -} - -#[test] -#[should_panic(expected = "insufficient allowance")] -fn test_transfer_from_with_expired_allowance_fails() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Should fail with insufficient allowance (expired) - client.transfer_from(&spender, &owner, &receiver, &200); -} - -#[test] -fn test_allowance_with_future_expiration() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 1000 (future) - let current_ledger = env.ledger().sequence(); - env.ledger().set(current_ledger + 100); - - client.approve(&owner, &spender, &500, &1000); - - // Should be usable - assert_eq!(client.allowance(&owner, &spender), 500); - - client.transfer_from(&spender, &owner, &receiver, &200); - assert_eq!(client.balance(&receiver), 200); - assert_eq!(client.allowance(&owner, &spender), 300); -} - -#[test] -fn test_allowance_with_past_expiration_returns_zero() { +fn test_transfer_ownership_updates_admin() { let env = Env::default(); env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Allowance should be 0 (expired) - assert_eq!(client.allowance(&owner, &spender), 0); -} - -#[test] -#[should_panic(expected = "insufficient allowance")] -fn test_transfer_from_with_expired_allowance_fails() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Should fail with insufficient allowance (expired) - client.transfer_from(&spender, &owner, &receiver, &200); -} - -#[test] -fn test_allowance_with_future_expiration() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 1000 (future) - let current_ledger = env.ledger().sequence(); - env.ledger().set(current_ledger + 100); - - client.approve(&owner, &spender, &500, &1000); - - // Should be usable - assert_eq!(client.allowance(&owner, &spender), 500); - - client.transfer_from(&spender, &owner, &receiver, &200); - assert_eq!(client.balance(&receiver), 200); - assert_eq!(client.allowance(&owner, &spender), 300); -} - -#[test] -fn test_allowance_with_past_expiration_returns_zero() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Allowance should be 0 (expired) - assert_eq!(client.allowance(&owner, &spender), 0); -} - -#[test] -#[should_panic(expected = "insufficient allowance")] -fn test_transfer_from_with_expired_allowance_fails() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Should fail with insufficient allowance (expired) - client.transfer_from(&spender, &owner, &receiver, &200); -} - -#[test] -fn test_allowance_with_future_expiration() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 1000 (future) - let current_ledger = env.ledger().sequence(); - env.ledger().set(current_ledger + 100); - - client.approve(&owner, &spender, &500, &1000); - - // Should be usable - assert_eq!(client.allowance(&owner, &spender), 500); - - client.transfer_from(&spender, &owner, &receiver, &200); - assert_eq!(client.balance(&receiver), 200); - assert_eq!(client.allowance(&owner, &spender), 300); -} - -#[test] -fn test_allowance_with_past_expiration_returns_zero() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Allowance should be 0 (expired) - assert_eq!(client.allowance(&owner, &spender), 0); -} - -#[test] -#[should_panic(expected = "insufficient allowance")] -fn test_transfer_from_with_expired_allowance_fails() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Should fail with insufficient allowance (expired) - client.transfer_from(&spender, &owner, &receiver, &200); -} - -// ─── Burn ──────────────────────────────────────────────────────────────────── - -#[test] -fn test_burn() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let user = Address::generate(&env); - - let _ = client.mint(&user, &1000); - client.mint(&admin, &user, &1000); - client.burn(&user, &300); - - assert_eq!(client.balance(&user), 700); - assert_eq!(client.supply(), 700); -} - -#[test] -fn test_burn_insufficient_balance_returns_error() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let user = Address::generate(&env); - - let _ = client.mint(&user, &100); - assert_eq!( - client.try_burn(&user, &200), - Err(Ok(TokenError::InsufficientBalance)) - ); - client.mint(&admin, &user, &100); - client.burn(&user, &200); -} - -#[test] -fn test_burn_from() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - - let _ = client.mint(&owner, &1000); - client.mint(&admin, &owner, &1000); - client.approve(&owner, &spender, &500, &0); - client.burn_from(&spender, &owner, &200); - - assert_eq!(client.balance(&owner), 800); - assert_eq!(client.allowance(&owner, &spender), 300); - assert_eq!(client.supply(), 800); -} - -#[test] -#[should_panic(expected = "insufficient allowance")] -fn test_burn_from_with_expired_allowance_fails() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 100 - client.approve(&owner, &spender, &500, &100); - - // Move to ledger 200 (past expiration) - env.ledger().set(200); - - // Should fail with insufficient allowance (expired) - client.burn_from(&spender, &owner, &200); -} - -#[test] -fn test_burn_from_preserves_expiration() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 1000 (future) - client.approve(&owner, &spender, &500, &1000); - - // Burn some tokens - client.burn_from(&spender, &owner, &200); - - // Allowance should be reduced but expiration preserved - assert_eq!(client.allowance(&owner, &spender), 300); - assert_eq!(client.balance(&owner), 800); - assert_eq!(client.supply(), 800); - - // Move to ledger 500 (still before expiration) - env.ledger().set(500); - assert_eq!(client.allowance(&owner, &spender), 300); - - // Move to ledger 1001 (past expiration) - env.ledger().set(1001); - assert_eq!(client.allowance(&owner, &spender), 0); -} - -#[test] -fn test_transfer_from_preserves_expiration() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - let receiver = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 1000 (future) - client.approve(&owner, &spender, &500, &1000); - - // Transfer some tokens - client.transfer_from(&spender, &owner, &receiver, &200); - - // Allowance should be reduced but expiration preserved - assert_eq!(client.allowance(&owner, &spender), 300); - assert_eq!(client.balance(&receiver), 200); - - // Move to ledger 500 (still before expiration) - env.ledger().set(500); - assert_eq!(client.allowance(&owner, &spender), 300); - - // Move to ledger 1001 (past expiration) - env.ledger().set(1001); - assert_eq!(client.allowance(&owner, &spender), 0); -} - -#[test] -fn test_approve_with_zero_expiration_clears_expiration() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let owner = Address::generate(&env); - let spender = Address::generate(&env); - - client.mint(&owner, &1000); - - // Set expiration to ledger 1000 - client.approve(&owner, &spender, &500, &1000); - - // Verify allowance is set with expiration - assert_eq!(client.allowance(&owner, &spender), 500); - - // Re-approve with exp=0 (clear expiration) - client.approve(&owner, &spender, &300, &0); - - // Allowance should still work even after moving far in the future - env.ledger().set(10000); - assert_eq!(client.allowance(&owner, &spender), 300); -} - -// ─── Ownership ─────────────────────────────────────────────────────────────── - -#[test] -fn test_transfer_ownership() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let new_admin = Address::generate(&env); - let user = Address::generate(&env); - - let _ = client.transfer_ownership(&new_admin); - - // New admin should be able to mint - let _ = client.mint(&user, &500); - client.mint(&new_admin, &user, &500); - assert_eq!(client.balance(&user), 500); -} - -#[test] -fn test_two_step_ownership_transfer_happy_path() { -fn test_role_management() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let new_admin = Address::generate(&env); - let user = Address::generate(&env); - - // Initially no pending owner - assert!(client.pending_owner().is_none()); - - // Propose new admin - client.propose_owner(&new_admin); - - // Check pending owner - let pending = client.pending_owner(); - assert!(pending.is_some()); - assert_eq!(pending.unwrap(), new_admin); - - // New admin accepts - client.accept_ownership(); - - // Pending owner should be cleared - assert!(client.pending_owner().is_none()); - - // New admin should be able to mint - client.mint(&user, &500); - assert_eq!(client.balance(&user), 500); -} - -#[test] -#[should_panic(expected = "no pending ownership transfer")] -fn test_accept_ownership_without_proposal_fails() { - let minter = Address::generate(&env); - let user = Address::generate(&env); - - // Minter doesn't have the role initially - assert!(!client.has_role(&Role::Minter, &minter)); - - // Admin grants Minter role - client.grant_role(&Role::Minter, &minter); - assert!(client.has_role(&Role::Minter, &minter)); - - // Minter can now mint - client.mint(&minter, &user, &100); - assert_eq!(client.balance(&user), 100); - - // Admin revokes Minter role - client.revoke_role(&Role::Minter, &minter); - assert!(!client.has_role(&Role::Minter, &minter)); -} - -#[test] -#[should_panic(expected = "unauthorized: missing role")] -fn test_mint_unauthorized_role() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - - // Try to accept without proposal - client.accept_ownership(); -} - -#[test] -fn test_cancel_transfer() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); + let (client, _admin) = setup(&env); let new_admin = Address::generate(&env); - // Propose new admin - client.propose_owner(&new_admin); - assert!(client.pending_owner().is_some()); - - // Cancel the transfer - client.cancel_transfer(); - - // Pending owner should be cleared - assert!(client.pending_owner().is_none()); -} - -#[test] -#[should_panic(expected = "no pending ownership transfer")] -fn test_cancel_transfer_without_proposal_fails() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - - // Try to cancel without proposal - client.cancel_transfer(); -} - -#[test] -fn test_double_propose_updates_pending_admin() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let first_proposal = Address::generate(&env); - let second_proposal = Address::generate(&env); - - // First proposal - client.propose_owner(&first_proposal); - assert_eq!(client.pending_owner().unwrap(), first_proposal); - - // Second proposal (should override first) - client.propose_owner(&second_proposal); - assert_eq!(client.pending_owner().unwrap(), second_proposal); - let non_minter = Address::generate(&env); - let user = Address::generate(&env); - - client.mint(&non_minter, &user, &100); -} - -// ─── Pause / Unpause ───────────────────────────────────────────────────────── - -#[test] -fn test_mint_while_paused_returns_error() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let user = Address::generate(&env); - - let _ = client.pause(); - assert_eq!( - client.try_mint(&user, &100), - Err(Ok(TokenError::ContractPaused)) - ); - client.pause(); - client.mint(&admin, &user, &100); -} - -#[test] -fn test_unpause_restores_operations() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let user = Address::generate(&env); - - let _ = client.pause(); - let _ = client.unpause(); - - // Should work again - let _ = client.mint(&user, &100); - client.mint(&admin, &user, &100); - assert_eq!(client.balance(&user), 100); -} - -#[test] -fn test_transfer_while_paused_returns_error() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let sender = Address::generate(&env); - let receiver = Address::generate(&env); - - let _ = client.mint(&sender, &1000); - let _ = client.pause(); - assert_eq!( - client.try_transfer(&sender, &receiver, &100), - Err(Ok(TokenError::ContractPaused)) - ); - client.mint(&admin, &sender, &1000); - client.pause(); - client.transfer(&sender, &receiver, &100); -} - -// ─── Pause/Unpause Edge Case Tests ───────────────────────────────────────── - -#[test] -fn test_transfer_ownership_while_paused() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let new_admin = Address::generate(&env); - let _ = client.pause(); - // Ownership transfer should still work while paused client.transfer_ownership(&new_admin); - // New admin can mint - client.mint(&new_admin, &admin, &1); -} - -#[test] -fn test_balance_query_while_paused() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let admin = init_default(&env, &client); - let user = Address::generate(&env); - client.mint(&admin, &user, &123); - client.pause(); - // Balance query should still work while paused - let bal = client.balance(&user); - assert_eq!(bal, 123); -} - -// ─── Negative Admin Function Tests ───────────────────────────────────────── - -#[test] -#[should_panic(expected = "unauthorized: missing role")] -fn test_pause_unauthorized_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let not_admin = Address::generate(&env); - client.pause_with_auth(¬_admin); -} - -#[test] -#[should_panic(expected = "unauthorized: missing role")] -fn test_unpause_unauthorized_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let not_admin = Address::generate(&env); - client.unpause_with_auth(¬_admin); -} - -#[test] -#[should_panic(expected = "unauthorized: missing role")] -fn test_transfer_ownership_unauthorized_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let not_admin = Address::generate(&env); - let new_admin = Address::generate(&env); - client.transfer_ownership_with_auth(&new_admin, ¬_admin); -} -#[test] -#[should_panic(expected = "unauthorized: missing role")] -fn test_mint_unauthorized_panics() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _) = setup_contract(&env); - let _admin = init_default(&env, &client); - let not_admin = Address::generate(&env); - let user = Address::generate(&env); - client.mint(¬_admin, &user, &100); -} - -// ─── Version ───────────────────────────────────────────────────────────────── - -#[test] -fn test_version() { -fn test_batch_transfer_multiple_recipients() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _admin) = setup(&env); - let from = Address::generate(&env); - let recipient_a = Address::generate(&env); - let recipient_b = Address::generate(&env); - let recipient_c = Address::generate(&env); - - client.mint(&from, &1000); - - let recipients = vec![ - &env, - (recipient_a.clone(), 100_i128), - (recipient_b.clone(), 250_i128), - (recipient_c.clone(), 50_i128), - ]; - client.batch_transfer(&from, &recipients); - - assert_eq!(client.balance(&from), 600); - assert_eq!(client.balance(&recipient_a), 100); - assert_eq!(client.balance(&recipient_b), 250); - assert_eq!(client.balance(&recipient_c), 50); - assert_eq!(client.supply(), 1000); -} - -#[test] -fn test_batch_transfer_rejects_invalid_amount() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _admin) = setup(&env); - let from = Address::generate(&env); - let recipient = Address::generate(&env); - - client.mint(&from, &1000); - - let recipients = vec![&env, (recipient.clone(), 0_i128)]; - assert_eq!( - client.try_batch_transfer(&from, &recipients), - Err(Ok(soroban_sdk::Error::from_contract_error( - TokenError::InvalidAmount as u32 - ))) - ); - assert_eq!(client.balance(&from), 1000); - assert_eq!(client.balance(&recipient), 0); -} - -#[test] -fn test_batch_transfer_rejects_insufficient_balance_before_moving_tokens() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _admin) = setup(&env); - let from = Address::generate(&env); - let recipient_a = Address::generate(&env); - let recipient_b = Address::generate(&env); - - client.mint(&from, &100); - - let recipients = vec![ - &env, - (recipient_a.clone(), 80_i128), - (recipient_b.clone(), 40_i128), - ]; - assert_eq!( - client.try_batch_transfer(&from, &recipients), - Err(Ok(soroban_sdk::Error::from_contract_error( - TokenError::InsufficientBalance as u32 - ))) - ); - assert_eq!(client.balance(&from), 100); - assert_eq!(client.balance(&recipient_a), 0); - assert_eq!(client.balance(&recipient_b), 0); -} - -#[test] -fn test_batch_transfer_while_paused_returns_error() { - let env = Env::default(); - env.mock_all_auths(); - let (client, _admin) = setup(&env); - let from = Address::generate(&env); - let recipient = Address::generate(&env); - - client.mint(&from, &100); - client.pause(); - - let recipients: Vec<(Address, i128)> = vec![&env, (recipient, 10_i128)]; - assert_eq!( - client.try_batch_transfer(&from, &recipients), - Err(Ok(soroban_sdk::Error::from_contract_error( - TokenError::ContractPaused as u32 - ))) - ); + assert_eq!(client.admin(), new_admin); } diff --git a/contracts/token/test_snapshots/test/test_approve_and_transfer_from.1.json b/contracts/token/test_snapshots/test/test_approve_and_transfer_from.1.json index 1f3352d..78a2d7c 100644 --- a/contracts/token/test_snapshots/test/test_approve_and_transfer_from.1.json +++ b/contracts/token/test_snapshots/test/test_approve_and_transfer_from.1.json @@ -5,34 +5,7 @@ }, "auth": [ [], - [ - [ - "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - { - "function": { - "contract_fn": { - "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", - "function_name": "initialize", - "args": [ - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, - { - "u32": 7 - }, - { - "string": "bc-forge Token" - }, - { - "string": "SFG" - } - ] - } - }, - "sub_invocations": [] - } - ] - ], + [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", @@ -42,9 +15,6 @@ "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", "function_name": "mint", "args": [ - { - "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" - }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" }, @@ -92,7 +62,6 @@ } ] ], - [], [ [ "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", @@ -180,10 +149,27 @@ }, "durability": "persistent", "val": { - "i128": { - "hi": 0, - "lo": 300 - } + "map": [ + { + "key": { + "symbol": "amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 300 + } + } + }, + { + "key": { + "symbol": "expiration_ledger" + }, + "val": { + "u32": 0 + } + } + ] } } }, @@ -298,7 +284,11 @@ "symbol": "Role" }, { - "u32": 0 + "vec": [ + { + "symbol": "Admin" + } + ] }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" @@ -321,7 +311,11 @@ "symbol": "Role" }, { - "u32": 0 + "vec": [ + { + "symbol": "Admin" + } + ] }, { "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" @@ -468,46 +462,13 @@ 6311999 ] ], - [ - { - "contract_data": { - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - "key": { - "ledger_key_nonce": { - "nonce": 5541220902715666415 - } - }, - "durability": "temporary" - } - }, - [ - { - "last_modified_ledger_seq": 0, - "data": { - "contract_data": { - "ext": "v0", - "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", - "key": { - "ledger_key_nonce": { - "nonce": 5541220902715666415 - } - }, - "durability": "temporary", - "val": "void" - } - }, - "ext": "v0" - }, - 6311999 - ] - ], [ { "contract_data": { "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": { "ledger_key_nonce": { - "nonce": 1033654523790656264 + "nonce": 5541220902715666415 } }, "durability": "temporary" @@ -522,7 +483,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", "key": { "ledger_key_nonce": { - "nonce": 1033654523790656264 + "nonce": 5541220902715666415 } }, "durability": "temporary", @@ -540,7 +501,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 4837995959683129791 + "nonce": 1033654523790656264 } }, "durability": "temporary" @@ -555,7 +516,7 @@ "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", "key": { "ledger_key_nonce": { - "nonce": 4837995959683129791 + "nonce": 1033654523790656264 } }, "durability": "temporary", diff --git a/contracts/vesting/Cargo.toml b/contracts/vesting/Cargo.toml new file mode 100644 index 0000000..63a0eec --- /dev/null +++ b/contracts/vesting/Cargo.toml @@ -0,0 +1,23 @@ +[package] +name = "bc-forge-vesting" +version = "0.1.0" +edition = "2021" +publish = false +description = "Time-locked token vesting schedules for bc-forge" +repository = "https://github.com/BCPathway/bc-forge" +license = "MIT" + +[lib] +crate-type = ["cdylib", "rlib"] + +[dependencies] +soroban-sdk = "22.0.0" +bc-forge-admin = { path = "../admin" } +bc-forge-token = { path = "../token" } + +[dev-dependencies] +soroban-sdk = { version = "22.0.0", features = ["testutils"] } +bc-forge-token = { path = "../token", features = ["testutils"] } + +[features] +testutils = ["soroban-sdk/testutils"] diff --git a/contracts/vesting/src/events.rs b/contracts/vesting/src/events.rs new file mode 100644 index 0000000..29e3451 --- /dev/null +++ b/contracts/vesting/src/events.rs @@ -0,0 +1,35 @@ +use soroban_sdk::{symbol_short, Address, Env}; + +pub fn emit_vesting_created( + env: &Env, + schedule_id: u64, + beneficiary: &Address, + amount: i128, + cliff_ledger: u32, + end_ledger: u32, + revocable: bool, +) { + env.events().publish( + (symbol_short!("v_create"),), + ( + schedule_id, + beneficiary.clone(), + amount, + cliff_ledger, + end_ledger, + revocable, + ), + ); +} + +pub fn emit_tokens_released(env: &Env, beneficiary: &Address, amount: i128) { + env.events() + .publish((symbol_short!("v_rel"),), (beneficiary.clone(), amount)); +} + +pub fn emit_vesting_revoked(env: &Env, schedule_id: u64, beneficiary: &Address, amount: i128) { + env.events().publish( + (symbol_short!("v_revoke"),), + (schedule_id, beneficiary.clone(), amount), + ); +} diff --git a/contracts/vesting/src/lib.rs b/contracts/vesting/src/lib.rs new file mode 100644 index 0000000..9849fd1 --- /dev/null +++ b/contracts/vesting/src/lib.rs @@ -0,0 +1,351 @@ +#![no_std] + +mod events; + +#[cfg(test)] +mod test; + +use bc_forge_admin as admin; +use bc_forge_token::BcForgeTokenClient; +use soroban_sdk::auth::{ContractContext, InvokerContractAuthEntry, SubContractInvocation}; +use soroban_sdk::{ + contract, contracterror, contractimpl, contracttype, symbol_short, Address, Env, IntoVal, + Symbol, Val, Vec, +}; + +#[derive(Clone)] +#[contracttype] +enum DataKey { + Admin, + Token, + NextScheduleId, + Schedule(u64), + BeneficiarySchedules(Address), +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[contracttype] +pub struct VestingSchedule { + pub beneficiary: Address, + pub total_amount: i128, + pub cliff_ledger: u32, + pub end_ledger: u32, + pub released_amount: i128, + pub revocable: bool, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[contracttype] +struct StoredVestingSchedule { + pub schedule: VestingSchedule, + pub start_ledger: u32, + pub revoked_at_ledger: Option, +} + +#[derive(Clone, Debug, Eq, PartialEq)] +#[contracttype] +pub struct VestingInfo { + pub schedule_id: u64, + pub schedule: VestingSchedule, + pub start_ledger: u32, + pub claimable_amount: i128, + pub revoked: bool, +} + +#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)] +#[contracterror] +#[repr(u32)] +pub enum VestingError { + AlreadyInitialized = 1, + NotInitialized = 2, + InvalidAmount = 3, + InvalidDuration = 4, + CliffAfterEnd = 5, + ScheduleNotFound = 6, + NotRevocable = 7, + AlreadyRevoked = 8, +} + +#[contract] +pub struct VestingContract; + +impl VestingContract { + fn ensure_initialized(env: &Env) -> Result<(), VestingError> { + if env.storage().instance().has(&DataKey::Admin) && env.storage().instance().has(&DataKey::Token) { + Ok(()) + } else { + Err(VestingError::NotInitialized) + } + } + + fn panic_on_err(env: &Env, result: Result) -> T { + match result { + Ok(value) => value, + Err(error) => soroban_sdk::panic_with_error!(env, error), + } + } + + fn read_admin(env: &Env) -> Address { + env.storage() + .instance() + .get(&DataKey::Admin) + .expect("vesting admin not set") + } + + fn read_token(env: &Env) -> Address { + env.storage() + .instance() + .get(&DataKey::Token) + .expect("vesting token not set") + } + + fn next_schedule_id(env: &Env) -> u64 { + let id = env.storage().instance().get(&DataKey::NextScheduleId).unwrap_or(0u64); + env.storage().instance().set(&DataKey::NextScheduleId, &(id + 1)); + id + } + + fn read_schedule(env: &Env, schedule_id: u64) -> Result { + env.storage() + .persistent() + .get(&DataKey::Schedule(schedule_id)) + .ok_or(VestingError::ScheduleNotFound) + } + + fn write_schedule(env: &Env, schedule_id: u64, schedule: &StoredVestingSchedule) { + env.storage() + .persistent() + .set(&DataKey::Schedule(schedule_id), schedule); + } + + fn beneficiary_schedule_ids(env: &Env, beneficiary: &Address) -> Vec { + env.storage() + .persistent() + .get(&DataKey::BeneficiarySchedules(beneficiary.clone())) + .unwrap_or_else(|| Vec::new(env)) + } + + fn write_beneficiary_schedule_ids(env: &Env, beneficiary: &Address, schedule_ids: &Vec) { + env.storage() + .persistent() + .set(&DataKey::BeneficiarySchedules(beneficiary.clone()), schedule_ids); + } + + fn vested_amount(schedule: &StoredVestingSchedule, current_ledger: u32) -> i128 { + let effective_ledger = match schedule.revoked_at_ledger { + Some(revoked_at) if revoked_at < current_ledger => revoked_at, + _ => current_ledger, + }; + + if effective_ledger < schedule.schedule.cliff_ledger { + return 0; + } + if effective_ledger >= schedule.schedule.end_ledger { + return schedule.schedule.total_amount; + } + + let elapsed = i128::from(effective_ledger - schedule.start_ledger); + let total_duration = i128::from(schedule.schedule.end_ledger - schedule.start_ledger); + schedule.schedule.total_amount * elapsed / total_duration + } + + fn claimable_amount(schedule: &StoredVestingSchedule, current_ledger: u32) -> i128 { + Self::vested_amount(schedule, current_ledger) - schedule.schedule.released_amount + } + + fn token_client(env: &Env) -> BcForgeTokenClient<'_> { + let token = Self::read_token(env); + BcForgeTokenClient::new(env, &token) + } + + fn authorize_current_contract_call(env: &Env, contract: &Address, fn_name: Symbol, args: Vec) { + let context = ContractContext { + contract: contract.clone(), + fn_name, + args, + }; + let invocation = SubContractInvocation { + context, + sub_invocations: Vec::new(env), + }; + env.authorize_as_current_contract(Vec::from_array( + env, + [InvokerContractAuthEntry::Contract(invocation)], + )); + } + + fn mint_into_vault(env: &Env, amount: i128) { + let token = Self::read_token(env); + let current_contract = env.current_contract_address(); + Self::authorize_current_contract_call( + env, + &token, + symbol_short!("mint").into(), + (¤t_contract, amount).into_val(env), + ); + Self::token_client(env).mint(¤t_contract, &amount); + } + + fn transfer_from_vault(env: &Env, to: &Address, amount: i128) { + if amount == 0 { + return; + } + + let token = Self::read_token(env); + let current_contract = env.current_contract_address(); + Self::authorize_current_contract_call( + env, + &token, + symbol_short!("transfer").into(), + (¤t_contract, to, amount).into_val(env), + ); + Self::token_client(env).transfer(¤t_contract, to, &amount); + } +} + +#[contractimpl] +impl VestingContract { + pub fn initialize(env: Env, admin_address: Address, token: Address) -> Result<(), VestingError> { + if env.storage().instance().has(&DataKey::Admin) { + return Err(VestingError::AlreadyInitialized); + } + + env.storage().instance().set(&DataKey::Admin, &admin_address); + env.storage().instance().set(&DataKey::Token, &token); + env.storage().instance().set(&DataKey::NextScheduleId, &0u64); + admin::set_admin(&env, &admin_address); + Ok(()) + } + + pub fn create_vesting( + env: Env, + beneficiary: Address, + amount: i128, + cliff: u32, + duration: u32, + revocable: bool, + ) -> Result { + Self::ensure_initialized(&env)?; + let admin_address = Self::read_admin(&env); + admin_address.require_auth(); + + if amount <= 0 { + return Err(VestingError::InvalidAmount); + } + if duration == 0 { + return Err(VestingError::InvalidDuration); + } + if cliff > duration { + return Err(VestingError::CliffAfterEnd); + } + + let start_ledger = env.ledger().sequence(); + let schedule_id = Self::next_schedule_id(&env); + let cliff_ledger = start_ledger + cliff; + let end_ledger = start_ledger + duration; + + Self::mint_into_vault(&env, amount); + + let schedule = StoredVestingSchedule { + schedule: VestingSchedule { + beneficiary: beneficiary.clone(), + total_amount: amount, + cliff_ledger, + end_ledger, + released_amount: 0, + revocable, + }, + start_ledger, + revoked_at_ledger: None, + }; + Self::write_schedule(&env, schedule_id, &schedule); + + let mut schedule_ids = Self::beneficiary_schedule_ids(&env, &beneficiary); + schedule_ids.push_back(schedule_id); + Self::write_beneficiary_schedule_ids(&env, &beneficiary, &schedule_ids); + + events::emit_vesting_created( + &env, + schedule_id, + &beneficiary, + amount, + cliff_ledger, + end_ledger, + revocable, + ); + Ok(schedule_id) + } + + pub fn release(env: Env, beneficiary: Address) -> Result { + Self::ensure_initialized(&env)?; + beneficiary.require_auth(); + + let current_ledger = env.ledger().sequence(); + let schedule_ids = Self::beneficiary_schedule_ids(&env, &beneficiary); + let mut total_to_release = 0i128; + + for index in 0..schedule_ids.len() { + let schedule_id = schedule_ids.get(index).expect("schedule id should exist"); + let mut stored = Self::read_schedule(&env, schedule_id)?; + let claimable = Self::claimable_amount(&stored, current_ledger); + if claimable > 0 { + stored.schedule.released_amount += claimable; + total_to_release += claimable; + Self::write_schedule(&env, schedule_id, &stored); + } + } + + Self::transfer_from_vault(&env, &beneficiary, total_to_release); + if total_to_release > 0 { + events::emit_tokens_released(&env, &beneficiary, total_to_release); + } + Ok(total_to_release) + } + + pub fn revoke(env: Env, schedule_id: u64) -> Result { + Self::ensure_initialized(&env)?; + let admin_address = Self::read_admin(&env); + admin_address.require_auth(); + + let current_ledger = env.ledger().sequence(); + let mut stored = Self::read_schedule(&env, schedule_id)?; + + if !stored.schedule.revocable { + return Err(VestingError::NotRevocable); + } + if stored.revoked_at_ledger.is_some() { + return Err(VestingError::AlreadyRevoked); + } + + let vested = Self::vested_amount(&stored, current_ledger); + let unvested = stored.schedule.total_amount - vested; + stored.revoked_at_ledger = Some(current_ledger); + Self::write_schedule(&env, schedule_id, &stored); + + Self::transfer_from_vault(&env, &admin_address, unvested); + events::emit_vesting_revoked(&env, schedule_id, &stored.schedule.beneficiary, unvested); + Ok(unvested) + } + + pub fn get_vesting_info(env: Env, beneficiary: Address) -> Vec { + Self::panic_on_err(&env, Self::ensure_initialized(&env)); + + let current_ledger = env.ledger().sequence(); + let schedule_ids = Self::beneficiary_schedule_ids(&env, &beneficiary); + let mut result = Vec::new(&env); + + for index in 0..schedule_ids.len() { + let schedule_id = schedule_ids.get(index).expect("schedule id should exist"); + let stored = Self::panic_on_err(&env, Self::read_schedule(&env, schedule_id)); + result.push_back(VestingInfo { + schedule_id, + schedule: stored.schedule.clone(), + start_ledger: stored.start_ledger, + claimable_amount: Self::claimable_amount(&stored, current_ledger), + revoked: stored.revoked_at_ledger.is_some(), + }); + } + + result + } +} diff --git a/contracts/vesting/src/test.rs b/contracts/vesting/src/test.rs new file mode 100644 index 0000000..42992d5 --- /dev/null +++ b/contracts/vesting/src/test.rs @@ -0,0 +1,138 @@ +#![cfg(test)] + +use bc_forge_token::{BcForgeToken, BcForgeTokenClient}; +use soroban_sdk::testutils::{Address as _, Ledger as _}; +use soroban_sdk::{Address, Env, String}; + +use crate::{VestingContract, VestingContractClient, VestingError}; + +fn setup(env: &Env) -> (BcForgeTokenClient<'_>, VestingContractClient<'_>, Address, Address, Address) { + let admin = Address::generate(env); + let beneficiary = Address::generate(env); + + let token_id = env.register(BcForgeToken, ()); + let token = BcForgeTokenClient::new(env, &token_id); + token.initialize( + &admin, + &7, + &String::from_str(env, "bc-forge Token"), + &String::from_str(env, "SFG"), + ); + + let vesting_id = env.register(VestingContract, ()); + let vesting = VestingContractClient::new(env, &vesting_id); + vesting.initialize(&admin, &token_id); + token.transfer_ownership(&vesting_id); + + (token, vesting, admin, beneficiary, vesting_id) +} + +#[test] +fn test_create_vesting_mints_into_contract_and_emits_events() { + let env = Env::default(); + env.mock_all_auths(); + env.ledger().set_sequence_number(10); + let (token, vesting, _admin, beneficiary, vesting_id) = setup(&env); + + let schedule_id = vesting.create_vesting(&beneficiary, &1_000, &5, &20, &true); + let info = vesting.get_vesting_info(&beneficiary); + + assert_eq!(schedule_id, 0); + assert_eq!(token.balance(&vesting_id), 1_000); + assert_eq!(info.len(), 1); + assert_eq!(info.get(0).unwrap().claimable_amount, 0); +} + +#[test] +fn test_release_respects_cliff_linear_vesting_and_prevents_double_release() { + let env = Env::default(); + env.mock_all_auths(); + env.ledger().set_sequence_number(10); + let (token, vesting, _admin, beneficiary, vesting_id) = setup(&env); + + vesting.create_vesting(&beneficiary, &1_000, &5, &20, &true); + + env.ledger().set_sequence_number(14); + assert_eq!(vesting.release(&beneficiary), 0); + assert_eq!(token.balance(&beneficiary), 0); + + env.ledger().set_sequence_number(15); + assert_eq!(vesting.release(&beneficiary), 250); + assert_eq!(token.balance(&beneficiary), 250); + assert_eq!(token.balance(&vesting_id), 750); + + assert_eq!(vesting.release(&beneficiary), 0); + assert_eq!(token.balance(&beneficiary), 250); + + env.ledger().set_sequence_number(20); + assert_eq!(vesting.release(&beneficiary), 250); + assert_eq!(token.balance(&beneficiary), 500); + + env.ledger().set_sequence_number(30); + assert_eq!(vesting.release(&beneficiary), 500); + assert_eq!(token.balance(&beneficiary), 1_000); + assert_eq!(token.balance(&vesting_id), 0); +} + +#[test] +fn test_revoke_returns_only_unvested_tokens() { + let env = Env::default(); + env.mock_all_auths(); + env.ledger().set_sequence_number(10); + let (token, vesting, admin, beneficiary, vesting_id) = setup(&env); + + let schedule_id = vesting.create_vesting(&beneficiary, &1_000, &5, &20, &true); + + env.ledger().set_sequence_number(16); + assert_eq!(vesting.release(&beneficiary), 300); + assert_eq!(token.balance(&beneficiary), 300); + + env.ledger().set_sequence_number(18); + assert_eq!(vesting.revoke(&schedule_id), 600); + assert_eq!(token.balance(&admin), 600); + assert_eq!(token.balance(&vesting_id), 100); + + env.ledger().set_sequence_number(25); + let info = vesting.get_vesting_info(&beneficiary); + assert_eq!(info.get(0).unwrap().claimable_amount, 100); + assert!(info.get(0).unwrap().revoked); + assert_eq!(vesting.release(&beneficiary), 100); + assert_eq!(token.balance(&beneficiary), 400); + assert_eq!(token.balance(&vesting_id), 0); +} + +#[test] +fn test_irrevocable_schedule_cannot_be_revoked() { + let env = Env::default(); + env.mock_all_auths(); + env.ledger().set_sequence_number(10); + let (_token, vesting, _admin, beneficiary, _vesting_id) = setup(&env); + + let schedule_id = vesting.create_vesting(&beneficiary, &1_000, &5, &20, &false); + + assert_eq!( + vesting.try_revoke(&schedule_id), + Err(Ok(VestingError::NotRevocable)) + ); +} + +#[test] +fn test_multiple_schedules_per_beneficiary_release_together() { + let env = Env::default(); + env.mock_all_auths(); + env.ledger().set_sequence_number(10); + let (token, vesting, _admin, beneficiary, vesting_id) = setup(&env); + + vesting.create_vesting(&beneficiary, &1_000, &5, &20, &true); + vesting.create_vesting(&beneficiary, &500, &0, &10, &true); + + env.ledger().set_sequence_number(20); + let info = vesting.get_vesting_info(&beneficiary); + assert_eq!(info.len(), 2); + assert_eq!(info.get(0).unwrap().claimable_amount, 500); + assert_eq!(info.get(1).unwrap().claimable_amount, 500); + + assert_eq!(vesting.release(&beneficiary), 1_000); + assert_eq!(token.balance(&beneficiary), 1_000); + assert_eq!(token.balance(&vesting_id), 500); +} diff --git a/contracts/vesting/test_snapshots/test/test_create_vesting_mints_into_contract_and_emits_events.1.json b/contracts/vesting/test_snapshots/test/test_create_vesting_mints_into_contract_and_emits_events.1.json new file mode 100644 index 0000000..ae08fd5 --- /dev/null +++ b/contracts/vesting/test_snapshots/test/test_create_vesting_mints_into_contract_and_emits_events.1.json @@ -0,0 +1,729 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "transfer_ownership", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "create_vesting", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "u32": 5 + }, + { + "u32": 20 + }, + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 10, + "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_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Decimals" + } + ] + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Name" + } + ] + }, + "val": { + "string": "bc-forge Token" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Supply" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Symbol" + } + ] + }, + "val": { + "string": "SFG" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "u64": 0 + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "revoked_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "schedule" + }, + "val": { + "map": [ + { + "key": { + "symbol": "beneficiary" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "cliff_ledger" + }, + "val": { + "u32": 15 + } + }, + { + "key": { + "symbol": "end_ledger" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "revocable" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + ] + } + }, + { + "key": { + "symbol": "start_ledger" + }, + "val": { + "u32": 10 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextScheduleId" + } + ] + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Token" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4105 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/vesting/test_snapshots/test/test_irrevocable_schedule_cannot_be_revoked.1.json b/contracts/vesting/test_snapshots/test/test_irrevocable_schedule_cannot_be_revoked.1.json new file mode 100644 index 0000000..fa6fdb5 --- /dev/null +++ b/contracts/vesting/test_snapshots/test/test_irrevocable_schedule_cannot_be_revoked.1.json @@ -0,0 +1,728 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "transfer_ownership", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "create_vesting", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "u32": 5 + }, + { + "u32": 20 + }, + { + "bool": false + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 10, + "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_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Decimals" + } + ] + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Name" + } + ] + }, + "val": { + "string": "bc-forge Token" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Supply" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Symbol" + } + ] + }, + "val": { + "string": "SFG" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "u64": 0 + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "revoked_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "schedule" + }, + "val": { + "map": [ + { + "key": { + "symbol": "beneficiary" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "cliff_ledger" + }, + "val": { + "u32": 15 + } + }, + { + "key": { + "symbol": "end_ledger" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + }, + { + "key": { + "symbol": "revocable" + }, + "val": { + "bool": false + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + ] + } + }, + { + "key": { + "symbol": "start_ledger" + }, + "val": { + "u32": 10 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextScheduleId" + } + ] + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Token" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4105 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/vesting/test_snapshots/test/test_multiple_schedules_per_beneficiary_release_together.1.json b/contracts/vesting/test_snapshots/test/test_multiple_schedules_per_beneficiary_release_together.1.json new file mode 100644 index 0000000..88185e3 --- /dev/null +++ b/contracts/vesting/test_snapshots/test/test_multiple_schedules_per_beneficiary_release_together.1.json @@ -0,0 +1,1023 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "transfer_ownership", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "create_vesting", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "u32": 5 + }, + { + "u32": 20 + }, + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "create_vesting", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 500 + } + }, + { + "u32": 0 + }, + { + "u32": 10 + }, + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "release", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 20, + "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_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312019 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + }, + "ext": "v0" + }, + 4115 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 500 + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Decimals" + } + ] + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Name" + } + ] + }, + "val": { + "string": "bc-forge Token" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Supply" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1500 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Symbol" + } + ] + }, + "val": { + "string": "SFG" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "u64": 0 + }, + { + "u64": 1 + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "revoked_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "schedule" + }, + "val": { + "map": [ + { + "key": { + "symbol": "beneficiary" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "cliff_ledger" + }, + "val": { + "u32": 15 + } + }, + { + "key": { + "symbol": "end_ledger" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 500 + } + } + }, + { + "key": { + "symbol": "revocable" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + ] + } + }, + { + "key": { + "symbol": "start_ledger" + }, + "val": { + "u32": 10 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 1 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "revoked_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "schedule" + }, + "val": { + "map": [ + { + "key": { + "symbol": "beneficiary" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "cliff_ledger" + }, + "val": { + "u32": 10 + } + }, + { + "key": { + "symbol": "end_ledger" + }, + "val": { + "u32": 20 + } + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 500 + } + } + }, + { + "key": { + "symbol": "revocable" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 500 + } + } + } + ] + } + }, + { + "key": { + "symbol": "start_ledger" + }, + "val": { + "u32": 10 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextScheduleId" + } + ] + }, + "val": { + "u64": 2 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Token" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4105 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/vesting/test_snapshots/test/test_release_respects_cliff_linear_vesting_and_prevents_double_release.1.json b/contracts/vesting/test_snapshots/test/test_release_respects_cliff_linear_vesting_and_prevents_double_release.1.json new file mode 100644 index 0000000..6234b00 --- /dev/null +++ b/contracts/vesting/test_snapshots/test/test_release_respects_cliff_linear_vesting_and_prevents_double_release.1.json @@ -0,0 +1,1042 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "transfer_ownership", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "create_vesting", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "u32": 5 + }, + { + "u32": 20 + }, + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "release", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "release", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "release", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "release", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "release", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 30, + "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_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312013 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312014 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4270020994084947596 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312019 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312014 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 8370022561469687789 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312029 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + }, + "ext": "v0" + }, + 4110 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Decimals" + } + ] + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Name" + } + ] + }, + "val": { + "string": "bc-forge Token" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Supply" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Symbol" + } + ] + }, + "val": { + "string": "SFG" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "u64": 0 + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "revoked_at_ledger" + }, + "val": "void" + }, + { + "key": { + "symbol": "schedule" + }, + "val": { + "map": [ + { + "key": { + "symbol": "beneficiary" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "cliff_ledger" + }, + "val": { + "u32": 15 + } + }, + { + "key": { + "symbol": "end_ledger" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "symbol": "revocable" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + ] + } + }, + { + "key": { + "symbol": "start_ledger" + }, + "val": { + "u32": 10 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextScheduleId" + } + ] + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Token" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4105 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file diff --git a/contracts/vesting/test_snapshots/test/test_revoke_returns_only_unvested_tokens.1.json b/contracts/vesting/test_snapshots/test/test_revoke_returns_only_unvested_tokens.1.json new file mode 100644 index 0000000..b649197 --- /dev/null +++ b/contracts/vesting/test_snapshots/test/test_revoke_returns_only_unvested_tokens.1.json @@ -0,0 +1,987 @@ +{ + "generators": { + "address": 4, + "nonce": 0 + }, + "auth": [ + [], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "function_name": "transfer_ownership", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "create_vesting", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + }, + { + "i128": { + "hi": 0, + "lo": 1000 + } + }, + { + "u32": 5 + }, + { + "u32": 20 + }, + { + "bool": true + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "release", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "revoke", + "args": [ + { + "u64": 0 + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [], + [], + [ + [ + "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + { + "function": { + "contract_fn": { + "contract_address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "function_name": "release", + "args": [ + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + } + }, + "sub_invocations": [] + } + ] + ], + [], + [] + ], + "ledger": { + "protocol_version": 22, + "sequence_number": 25, + "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_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 801925984706572462 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 4837995959683129791 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312017 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM", + "key": { + "ledger_key_nonce": { + "nonce": 5541220902715666415 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312009 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 1033654523790656264 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312015 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4", + "key": { + "ledger_key_nonce": { + "nonce": 2032731177588607455 + } + }, + "durability": "temporary", + "val": "void" + } + }, + "ext": "v0" + }, + 6312024 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 600 + } + } + } + }, + "ext": "v0" + }, + 4113 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 400 + } + } + } + }, + "ext": "v0" + }, + 4111 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Balance" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "i128": { + "hi": 0, + "lo": 0 + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Decimals" + } + ] + }, + "val": { + "u32": 7 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Name" + } + ] + }, + "val": { + "string": "bc-forge Token" + } + }, + { + "key": { + "vec": [ + { + "symbol": "Supply" + } + ] + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + }, + { + "key": { + "vec": [ + { + "symbol": "Symbol" + } + ] + }, + "val": { + "string": "SFG" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "BeneficiarySchedules" + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + ] + }, + "durability": "persistent", + "val": { + "vec": [ + { + "u64": 0 + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Role" + }, + { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + ] + }, + "durability": "persistent", + "val": { + "bool": true + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": { + "vec": [ + { + "symbol": "Schedule" + }, + { + "u64": 0 + } + ] + }, + "durability": "persistent", + "val": { + "map": [ + { + "key": { + "symbol": "revoked_at_ledger" + }, + "val": { + "u32": 18 + } + }, + { + "key": { + "symbol": "schedule" + }, + "val": { + "map": [ + { + "key": { + "symbol": "beneficiary" + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAFCT4" + } + }, + { + "key": { + "symbol": "cliff_ledger" + }, + "val": { + "u32": 15 + } + }, + { + "key": { + "symbol": "end_ledger" + }, + "val": { + "u32": 30 + } + }, + { + "key": { + "symbol": "released_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 400 + } + } + }, + { + "key": { + "symbol": "revocable" + }, + "val": { + "bool": true + } + }, + { + "key": { + "symbol": "total_amount" + }, + "val": { + "i128": { + "hi": 0, + "lo": 1000 + } + } + } + ] + } + }, + { + "key": { + "symbol": "start_ledger" + }, + "val": { + "u32": 10 + } + } + ] + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_data": { + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_data": { + "ext": "v0", + "contract": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAITA4", + "key": "ledger_key_contract_instance", + "durability": "persistent", + "val": { + "contract_instance": { + "executable": { + "wasm": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + }, + "storage": [ + { + "key": { + "vec": [ + { + "symbol": "Admin" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD2KM" + } + }, + { + "key": { + "vec": [ + { + "symbol": "NextScheduleId" + } + ] + }, + "val": { + "u64": 1 + } + }, + { + "key": { + "vec": [ + { + "symbol": "Token" + } + ] + }, + "val": { + "address": "CAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAHK3M" + } + } + ] + } + } + } + }, + "ext": "v0" + }, + 4105 + ] + ], + [ + { + "contract_code": { + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" + } + }, + [ + { + "last_modified_ledger_seq": 0, + "data": { + "contract_code": { + "ext": "v0", + "hash": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855", + "code": "" + } + }, + "ext": "v0" + }, + 4105 + ] + ] + ] + }, + "events": [] +} \ No newline at end of file