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