Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
148 changes: 142 additions & 6 deletions contracts/vesting_contracts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub struct Vault {
pub keeper_fee: i128, // Fee paid to anyone who triggers auto_claim
pub is_initialized: bool, // Lazy initialization flag
pub is_irrevocable: bool, // Security flag to prevent admin withdrawal
pub creation_time: u64, // Timestamp of creation for clawback grace period
pub is_transferable: bool, // Can the beneficiary transfer this vault?
}

#[contracttype]
Expand Down Expand Up @@ -195,6 +197,8 @@ impl VestingContract {
.instance()
.set(&DataKey::AdminBalance, &admin_balance);

let now = env.ledger().timestamp();

// Create vault with full initialization
let vault = Vault {
owner: owner.clone(),
Expand All @@ -203,7 +207,11 @@ impl VestingContract {
released_amount: 0,
start_time,
end_time,
<
keeper_fee,
is_initialized: true,
is_irrevocable: !is_revocable,
creation_time: now,
is_transferable,
};

// Store vault data immediately (expensive gas usage)
Expand All @@ -228,7 +236,6 @@ impl VestingContract {
.set(&DataKey::VaultCount, &vault_count);

// Emit VaultCreated event with strictly typed fields
let now = env.ledger().timestamp();
let cliff_duration = start_time.saturating_sub(now);
let vault_created = VaultCreated {
vault_id: vault_count,
Expand Down Expand Up @@ -269,6 +276,8 @@ impl VestingContract {
.instance()
.set(&DataKey::AdminBalance, &admin_balance);

let now = env.ledger().timestamp();

// Create vault with lazy initialization (minimal storage)
let vault = Vault {
owner: owner.clone(),
Expand All @@ -280,6 +289,8 @@ impl VestingContract {
keeper_fee,
is_initialized: false, // Mark as lazy initialized
is_irrevocable: !is_revocable, // Convert from is_revocable parameter
creation_time: now,
is_transferable,
};

// Store only essential data initially (cheaper gas)
Expand All @@ -295,7 +306,6 @@ impl VestingContract {
// Don't update user vaults list yet (lazy)

// Emit VaultCreated event with strictly typed fields
let now = env.ledger().timestamp();
let cliff_duration = start_time.saturating_sub(now);
let vault_created = VaultCreated {
vault_id: vault_count,
Expand Down Expand Up @@ -626,6 +636,7 @@ impl VestingContract {
.instance()
.set(&DataKey::AdminBalance, &admin_balance);

let now = env.ledger().timestamp();
for i in 0..batch_data.recipients.len() {
let vault_id = initial_count + i as u64 + 1;

Expand All @@ -640,6 +651,8 @@ impl VestingContract {
keeper_fee: batch_data.keeper_fees.get(i).unwrap(),
is_initialized: false, // Lazy initialization
is_irrevocable: false, // Default to revocable for batch operations
creation_time: now,
is_transferable: false, // Default to non-transferable for batch
};

// Store vault data (minimal writes)
Expand All @@ -648,7 +661,6 @@ impl VestingContract {
.set(&DataKey::VaultData(vault_id), &vault);
vault_ids.push_back(vault_id);
// Emit VaultCreated event for each created vault
let now = env.ledger().timestamp();
let start_time = batch_data.start_times.get(i).unwrap();
let cliff_duration = start_time.saturating_sub(now);
let vault_created = VaultCreated {
Expand Down Expand Up @@ -697,6 +709,7 @@ impl VestingContract {
.instance()
.set(&DataKey::AdminBalance, &admin_balance);

let now = env.ledger().timestamp();
for i in 0..batch_data.recipients.len() {
let vault_id = initial_count + i as u64 + 1;

Expand All @@ -708,7 +721,12 @@ impl VestingContract {
released_amount: 0,
start_time: batch_data.start_times.get(i).unwrap(),
end_time: batch_data.end_times.get(i).unwrap(),
};
keeper_fee: batch_data.keeper_fees.get(i).unwrap(),
is_initialized: true,
is_irrevocable: false, // Default to revocable for batch operations
creation_time: now,
is_transferable: false, // Default to non-transferable for batch
};

// Store vault data (expensive writes)
env.storage()
Expand All @@ -728,7 +746,6 @@ impl VestingContract {

vault_ids.push_back(vault_id);
// Emit VaultCreated event for each created vault
let now = env.ledger().timestamp();
let start_time = batch_data.start_times.get(i).unwrap();
let cliff_duration = start_time.saturating_sub(now);
let vault_created = VaultCreated {
Expand Down Expand Up @@ -895,6 +912,125 @@ impl VestingContract {
amount
}

// Clawback a vault within the grace period (1 hour)
pub fn clawback_vault(env: Env, vault_id: u64) -> i128 {
Self::require_admin(&env);

let mut vault: Vault = env
.storage()
.instance()
.get(&DataKey::VaultData(vault_id))
.unwrap_or_else(|| {
panic!("Vault not found");
});

let now = env.ledger().timestamp();
let grace_period = 3600; // 1 hour in seconds

if now > vault.creation_time + grace_period {
panic!("Grace period expired");
}

if vault.released_amount > 0 {
panic!("Tokens already claimed");
}

// Refund admin
let mut admin_balance: i128 = env
.storage()
.instance()
.get(&DataKey::AdminBalance)
.unwrap_or(0);
admin_balance += vault.total_amount;
env.storage()
.instance()
.set(&DataKey::AdminBalance, &admin_balance);

// Mark as released/revoked so it can't be claimed
vault.released_amount = vault.total_amount;
env.storage()
.instance()
.set(&DataKey::VaultData(vault_id), &vault);

// Emit event
env.events().publish(
(Symbol::new(&env, "VaultClawedBack"), vault_id),
vault.total_amount,
);

vault.total_amount
}

// Transfer vault ownership to another beneficiary (if transferable)
pub fn transfer_vault(env: Env, vault_id: u64, new_beneficiary: Address) {
let mut vault: Vault = env
.storage()
.instance()
.get(&DataKey::VaultData(vault_id))
.unwrap_or_else(|| {
panic!("Vault not found");
});

if !vault.is_initialized {
panic!("Vault not initialized");
}

if !vault.is_transferable {
panic!("Vault is non-transferable");
}

// Check if caller is the vault owner
let caller = env.current_contract_address();
if caller != vault.owner {
panic!("Only vault owner can transfer");
}

let old_owner = vault.owner.clone();

// Update UserVaults
// Remove from old owner
let mut old_user_vaults: Vec<u64> = env
.storage()
.instance()
.get(&DataKey::UserVaults(old_owner.clone()))
.unwrap_or(Vec::new(&env));

let mut new_old_user_vaults = Vec::new(&env);
for id in old_user_vaults.iter() {
if id != vault_id {
new_old_user_vaults.push_back(id);
}
}
env.storage()
.instance()
.set(&DataKey::UserVaults(old_owner.clone()), &new_old_user_vaults);

// Add to new owner
let mut new_user_vaults: Vec<u64> = env
.storage()
.instance()
.get(&DataKey::UserVaults(new_beneficiary.clone()))
.unwrap_or(Vec::new(&env));
new_user_vaults.push_back(vault_id);
env.storage()
.instance()
.set(&DataKey::UserVaults(new_beneficiary.clone()), &new_user_vaults);

// Update vault
vault.owner = new_beneficiary.clone();
vault.delegate = None; // Reset delegate on transfer

env.storage()
.instance()
.set(&DataKey::VaultData(vault_id), &vault);

// Emit event
env.events().publish(
(Symbol::new(&env, "VaultTransferred"), vault_id),
(old_owner, new_beneficiary),
);
}

// Mark a vault as irrevocable to prevent admin withdrawal
pub fn mark_irrevocable(env: Env, vault_id: u64) {
Self::require_admin(&env);
Expand Down