From c47affe595b7c24de76752163e0c092b0df7c182 Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Tue, 17 Dec 2024 21:23:25 -0500 Subject: [PATCH 01/14] Don't go into main loop if it is last period for vesting and unlock --- .../sources/pbo_delegation_pool.move | 16 ++++++-- .../sources/vesting_without_staking.move | 37 +++++++++++++------ 2 files changed, 38 insertions(+), 15 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move index 0a94b098908d1..68cab4bc556fe 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -133,8 +133,6 @@ module supra_framework::pbo_delegation_pool { use supra_framework::staking_config; use supra_framework::timestamp; use supra_framework::multisig_account; - #[test_only] - use aptos_std::debug; const MODULE_SALT: vector = b"supra_framework::pbo_delegation_pool"; @@ -1782,9 +1780,19 @@ module supra_framework::pbo_delegation_pool { } else { *vector::borrow(&unlock_schedule.schedule, last_unlocked_period) }; - cfraction = fixed_point64::add(cfraction, next_fraction); + let next_fraction_unchanged = next_fraction == *vector::borrow(&unlock_schedule.schedule, schedule_length - 1); + // If next fraction is same as last unlocked period, then no need to fetch from schedule + if (next_fraction_unchanged) { + while (last_unlocked_period < unlock_periods_passed + && fixed_point64::less(cfraction, one)) { + cfraction = fixed_point64::add(cfraction, next_fraction); + last_unlocked_period = last_unlocked_period + 1; + }; + } else { + cfraction = fixed_point64::add(cfraction, next_fraction); - last_unlocked_period = last_unlocked_period + 1; + last_unlocked_period = last_unlocked_period + 1; + }; }; unlock_schedule.cumulative_unlocked_fraction = cfraction; diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index 2937c904e0a29..20aa10d312454 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -503,17 +503,32 @@ module supra_framework::vesting_without_staking { // Last vesting schedule fraction will repeat until the grant runs out. *vector::borrow(schedule, vector::length(schedule) - 1) }; - vest_transfer(vesting_record, signer_cap, beneficiary, vesting_fraction); - - emit_event(&mut vesting_contract.vest_events, - VestEvent { - admin: vesting_contract.admin, - shareholder_address: shareholder_address, - vesting_contract_address: contract_address, - period_vested: next_period_to_vest, - }, - ); - next_period_to_vest = next_period_to_vest + 1; + let vesting_fraction_unchange = vesting_fraction == *vector::borrow(schedule, vector::length(schedule) - 1); + if (vesting_fraction_unchange) { + while (last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0) { + vest_transfer(vesting_record, signer_cap, beneficiary, vesting_fraction); + emit_event(&mut vesting_contract.vest_events, + VestEvent { + admin: vesting_contract.admin, + shareholder_address: shareholder_address, + vesting_contract_address: contract_address, + period_vested: next_period_to_vest, + }, + ); + next_period_to_vest = next_period_to_vest + 1; + } + } else { + vest_transfer(vesting_record, signer_cap, beneficiary, vesting_fraction); + emit_event(&mut vesting_contract.vest_events, + VestEvent { + admin: vesting_contract.admin, + shareholder_address: shareholder_address, + vesting_contract_address: contract_address, + period_vested: next_period_to_vest, + }, + ); + next_period_to_vest = next_period_to_vest + 1; + }; }; //update last_vested_period for the shareholder From 786b6dd5e86ea153031647bd97f4cfa90288ce26 Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Tue, 17 Dec 2024 21:32:00 -0500 Subject: [PATCH 02/14] Add new function that multiply u128 and return fixpoint64 --- .../framework/aptos-stdlib/sources/fixed_point64.move | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move b/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move index ac864c821495b..48c7ae67b65ee 100644 --- a/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move +++ b/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move @@ -85,6 +85,16 @@ module aptos_std::fixed_point64 { (val * multiplier.value) >> 64 } + public fun multiply_u128_return_fixpoint64(val: u128, multiplier: FixedPoint64): FixedPoint64 { + // The product of two 128 bit values has 256 bits, so perform the + // multiplication with u256 types and keep the full 256 bit product + // to avoid losing accuracy. + let unscaled_product = (val as u256) * (multiplier.value as u256); + // Check whether the value is too large. + assert!(unscaled_product <= MAX_U128, EMULTIPLICATION); + create_from_raw_value((unscaled_product as u128)) + } + /// Divide a u128 integer by a fixed-point number, truncating any /// fractional part of the quotient. This will abort if the divisor /// is zero or if the quotient overflows. From de0b413af0ea3e7dd6ccd762a1a0cb41d8a25021 Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Wed, 18 Dec 2024 22:00:47 -0500 Subject: [PATCH 03/14] Apply correct optimization and add documentation --- .../move-stdlib/sources/fixed_point32.move | 10 +++ .../sources/pbo_delegation_pool.move | 32 +++------- .../sources/vesting_without_staking.move | 61 ++++++++----------- 3 files changed, 47 insertions(+), 56 deletions(-) diff --git a/aptos-move/framework/move-stdlib/sources/fixed_point32.move b/aptos-move/framework/move-stdlib/sources/fixed_point32.move index 96409a9ac4dfd..9eb68e92c57e6 100644 --- a/aptos-move/framework/move-stdlib/sources/fixed_point32.move +++ b/aptos-move/framework/move-stdlib/sources/fixed_point32.move @@ -56,6 +56,16 @@ module std::fixed_point32 { (val * multiplier.value) >> 32 } + public fun multiply_u64_return_fixpoint32(val: u64, multiplier: FixedPoint32): FixedPoint32 { + // The product of two 64 bit values has 128 bits, so perform the + // multiplication with u128 types and keep the full 128 bit product + // to avoid losing accuracy. + let unscaled_product = (val as u128) * (multiplier.value as u128); + // Check whether the value is too large. + assert!(unscaled_product <= MAX_U64, EMULTIPLICATION); + create_from_raw_value((product as u64)); + } + /// Divide a u64 integer by a fixed-point number, truncating any /// fractional part of the quotient. This will abort if the divisor /// is zero or if the quotient overflows. diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move index 68cab4bc556fe..af5009f4c445c 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -1772,34 +1772,22 @@ module supra_framework::pbo_delegation_pool { let last_unlocked_period = unlock_schedule.last_unlock_period; let schedule_length = vector::length(&unlock_schedule.schedule); let cfraction = unlock_schedule.cumulative_unlocked_fraction; - while (last_unlocked_period < unlock_periods_passed - && fixed_point64::less(cfraction, one)) { - let next_fraction = - if (schedule_length <= last_unlocked_period) { - *vector::borrow(&unlock_schedule.schedule, schedule_length - 1) - } else { - *vector::borrow(&unlock_schedule.schedule, last_unlocked_period) - }; - let next_fraction_unchanged = next_fraction == *vector::borrow(&unlock_schedule.schedule, schedule_length - 1); - // If next fraction is same as last unlocked period, then no need to fetch from schedule - if (next_fraction_unchanged) { - while (last_unlocked_period < unlock_periods_passed - && fixed_point64::less(cfraction, one)) { - cfraction = fixed_point64::add(cfraction, next_fraction); - last_unlocked_period = last_unlocked_period + 1; - }; - } else { - cfraction = fixed_point64::add(cfraction, next_fraction); - - last_unlocked_period = last_unlocked_period + 1; - }; + while (last_unlocked_period < unlock_periods_passed && fixed_point64::less(cfraction, one) + && last_unlocked_period < schedule_length) { + let next_fraction = *vector::borrow(&unlock_schedule.schedule, last_unlocked_period); + cfraction = fixed_point64::add(cfraction, next_fraction); + last_unlocked_period = last_unlocked_period + 1; }; + let final_fraction= *vector::borrow(&unlock_schedule.schedule, schedule_length - 1); + // Fast foward calculation to current period and don't update last_unlocked_period since it is not used anymore + cfraction = fixed_point64::add(cfraction, fixed_point64::multiply_u128_return_fixpoint64((unlock_periods_passed - last_unlocked_period as u128), final_fraction)); + cfraction = fixed_point64::min(cfraction, one); + unlock_schedule.cumulative_unlocked_fraction = cfraction; unlock_schedule.last_unlock_period = unlock_periods_passed; let unlockable_amount = cached_unlockable_balance(delegator_addr, pool_address); amount <= unlockable_amount - } /// Unlock `amount` from the active + pending_active stake of `delegator` or diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index 20aa10d312454..2be38d29d95e6 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -495,42 +495,35 @@ module supra_framework::vesting_without_staking { / vesting_schedule.period_duration; // Index is 0-based while period is 1-based so we need to subtract 1. - while (last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0) { - let schedule_index = next_period_to_vest - 1; - let vesting_fraction = if (schedule_index < vector::length(schedule)) { - *vector::borrow(schedule, schedule_index) - } else { - // Last vesting schedule fraction will repeat until the grant runs out. - *vector::borrow(schedule, vector::length(schedule) - 1) - }; - let vesting_fraction_unchange = vesting_fraction == *vector::borrow(schedule, vector::length(schedule) - 1); - if (vesting_fraction_unchange) { - while (last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0) { - vest_transfer(vesting_record, signer_cap, beneficiary, vesting_fraction); - emit_event(&mut vesting_contract.vest_events, - VestEvent { - admin: vesting_contract.admin, - shareholder_address: shareholder_address, - vesting_contract_address: contract_address, - period_vested: next_period_to_vest, - }, - ); - next_period_to_vest = next_period_to_vest + 1; - } - } else { - vest_transfer(vesting_record, signer_cap, beneficiary, vesting_fraction); - emit_event(&mut vesting_contract.vest_events, - VestEvent { - admin: vesting_contract.admin, - shareholder_address: shareholder_address, - vesting_contract_address: contract_address, - period_vested: next_period_to_vest, - }, - ); - next_period_to_vest = next_period_to_vest + 1; - }; + while (last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0 && next_period_to_vest <= vector::length(schedule)) { + // let schedule_index = next_period_to_vest - 1; + let vesting_fraction = *vector::borrow(schedule, vector::length(schedule) - 1); + vest_transfer(vesting_record, signer_cap, beneficiary, vesting_fraction); + emit_event(&mut vesting_contract.vest_events, + VestEvent { + admin: vesting_contract.admin, + shareholder_address: shareholder_address, + vesting_contract_address: contract_address, + period_vested: next_period_to_vest, + }, + ); + next_period_to_vest = next_period_to_vest + 1; }; + let final_fraction = *vector::borrow(schedule, vector::length(schedule) - 1); + let total_fraction = fixed_point32::multiply_u64_return_fixpoint32((last_completed_period - next_period_to_vest ), final_fraction); + // We don't need to check vesting_record.left_amount > 0 because vest_transfer will handle that. + vest_transfer(vesting_record, signer_cap, beneficiary, total_fraction); + next_period_to_vest = last_completed_period + 1; + emit_event(&mut vesting_contract.vest_events, + VestEvent { + admin: vesting_contract.admin, + shareholder_address: shareholder_address, + vesting_contract_address: contract_address, + period_vested: next_period_to_vest, + }, + ); + //update last_vested_period for the shareholder vesting_record.last_vested_period = next_period_to_vest - 1; } From f0b760558dda5517993e331eae480a48d414b98a Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Wed, 18 Dec 2024 23:56:11 -0500 Subject: [PATCH 04/14] Fix new function --- aptos-move/framework/move-stdlib/sources/fixed_point32.move | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aptos-move/framework/move-stdlib/sources/fixed_point32.move b/aptos-move/framework/move-stdlib/sources/fixed_point32.move index 9eb68e92c57e6..fbdf0f569e8cf 100644 --- a/aptos-move/framework/move-stdlib/sources/fixed_point32.move +++ b/aptos-move/framework/move-stdlib/sources/fixed_point32.move @@ -63,7 +63,7 @@ module std::fixed_point32 { let unscaled_product = (val as u128) * (multiplier.value as u128); // Check whether the value is too large. assert!(unscaled_product <= MAX_U64, EMULTIPLICATION); - create_from_raw_value((product as u64)); + create_from_raw_value((unscaled_product as u64)) } /// Divide a u64 integer by a fixed-point number, truncating any From 88bb9811bf5b1cb48bc71ae380d767bc2530fb7b Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Thu, 19 Dec 2024 01:39:51 -0500 Subject: [PATCH 05/14] Correct implementation --- .../aptos-stdlib/sources/fixed_point64.move | 14 +++++++ .../sources/pbo_delegation_pool.move | 16 +++++--- .../sources/vesting_without_staking.move | 40 ++++++++++++------- 3 files changed, 49 insertions(+), 21 deletions(-) diff --git a/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move b/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move index 48c7ae67b65ee..71ceaa9b8307a 100644 --- a/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move +++ b/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move @@ -56,6 +56,19 @@ module aptos_std::fixed_point64 { ensures result.value == x.value + y.value; } + /// Returns x / y. The result cannot be greater than MAX_U128. + public fun divide(x: FixedPoint64, y: FixedPoint64): FixedPoint64 { + let x_raw = get_raw_value(x); + let y_raw = get_raw_value(y); + // If it is divisable, return the result. If not, return the result + 1. + let result = (x_raw as u256) / (y_raw as u256); + if ((x_raw as u256) % (y_raw as u256) != 0) { + result = result + 1; + }; + assert!(result <= MAX_U128, ERATIO_OUT_OF_RANGE); + create_from_raw_value((result as u128)) + } + /// Multiply a u128 integer by a fixed-point number, truncating any /// fractional part of the product. This will abort if the product /// overflows. @@ -95,6 +108,7 @@ module aptos_std::fixed_point64 { create_from_raw_value((unscaled_product as u128)) } + /// Divide a u128 integer by a fixed-point number, truncating any /// fractional part of the quotient. This will abort if the divisor /// is zero or if the quotient overflows. diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move index af5009f4c445c..c6ecefcbebbcb 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -122,6 +122,7 @@ module supra_framework::pbo_delegation_pool { use aptos_std::table::{Self, Table}; use aptos_std::smart_table::{Self, SmartTable}; use aptos_std::fixed_point64::{Self, FixedPoint64}; + use aptos_std::math64::min; use supra_framework::coin::Coin; use supra_framework::account; @@ -1778,12 +1779,15 @@ module supra_framework::pbo_delegation_pool { cfraction = fixed_point64::add(cfraction, next_fraction); last_unlocked_period = last_unlocked_period + 1; }; - - let final_fraction= *vector::borrow(&unlock_schedule.schedule, schedule_length - 1); - // Fast foward calculation to current period and don't update last_unlocked_period since it is not used anymore - cfraction = fixed_point64::add(cfraction, fixed_point64::multiply_u128_return_fixpoint64((unlock_periods_passed - last_unlocked_period as u128), final_fraction)); - cfraction = fixed_point64::min(cfraction, one); - + if (last_unlocked_period < unlock_periods_passed && fixed_point64::less(cfraction, one)) { + let final_fraction= *vector::borrow(&unlock_schedule.schedule, schedule_length - 1); + // Determine how many periods is needed + let periods_needed = min(unlock_periods_passed - last_unlocked_period, + ((fixed_point64::get_raw_value(fixed_point64::divide(fixed_point64::sub(one, cfraction), final_fraction)) as u64) ) + ); + // Acclerate calculation to current period and don't update last_unlocked_period since it is not used anymore + cfraction = fixed_point64::add(cfraction, fixed_point64::multiply_u128_return_fixpoint64((periods_needed as u128), final_fraction)); + }; unlock_schedule.cumulative_unlocked_fraction = cfraction; unlock_schedule.last_unlock_period = unlock_periods_passed; let unlockable_amount = cached_unlockable_balance(delegator_addr, pool_address); diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index 2be38d29d95e6..dd21e4e3235b9 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -496,8 +496,8 @@ module supra_framework::vesting_without_staking { // Index is 0-based while period is 1-based so we need to subtract 1. while (last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0 && next_period_to_vest <= vector::length(schedule)) { - // let schedule_index = next_period_to_vest - 1; - let vesting_fraction = *vector::borrow(schedule, vector::length(schedule) - 1); + let schedule_index = next_period_to_vest - 1; + let vesting_fraction = *vector::borrow(schedule, schedule_index); vest_transfer(vesting_record, signer_cap, beneficiary, vesting_fraction); emit_event(&mut vesting_contract.vest_events, VestEvent { @@ -510,19 +510,29 @@ module supra_framework::vesting_without_staking { next_period_to_vest = next_period_to_vest + 1; }; - let final_fraction = *vector::borrow(schedule, vector::length(schedule) - 1); - let total_fraction = fixed_point32::multiply_u64_return_fixpoint32((last_completed_period - next_period_to_vest ), final_fraction); - // We don't need to check vesting_record.left_amount > 0 because vest_transfer will handle that. - vest_transfer(vesting_record, signer_cap, beneficiary, total_fraction); - next_period_to_vest = last_completed_period + 1; - emit_event(&mut vesting_contract.vest_events, - VestEvent { - admin: vesting_contract.admin, - shareholder_address: shareholder_address, - vesting_contract_address: contract_address, - period_vested: next_period_to_vest, - }, - ); + if(last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0) { + let final_fraction = *vector::borrow(schedule, vector::length(schedule) - 1); + // Determine how many periods is needed based on the left_amount + let periods_need_used_up_amount = if (vesting_record.left_amount % fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) == 0) { + vesting_record.left_amount / fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + } else { + vesting_record.left_amount / fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + 1 + }; + let periods_needed = + min(periods_need_used_up_amount, last_completed_period - next_period_to_vest + 1); + let total_fraction = fixed_point32::multiply_u64_return_fixpoint32(periods_needed, final_fraction); + // We don't need to check vesting_record.left_amount > 0 because vest_transfer will handle that. + vest_transfer(vesting_record, signer_cap, beneficiary, total_fraction); + next_period_to_vest = next_period_to_vest + periods_needed; + emit_event(&mut vesting_contract.vest_events, + VestEvent { + admin: vesting_contract.admin, + shareholder_address: shareholder_address, + vesting_contract_address: contract_address, + period_vested: next_period_to_vest, + }, + ); + }; //update last_vested_period for the shareholder vesting_record.last_vested_period = next_period_to_vest - 1; From 9759972c3f96ca54bb8fc2179cc325768912a524 Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Thu, 19 Dec 2024 22:17:36 -0500 Subject: [PATCH 06/14] Add test cases --- .../sources/pbo_delegation_pool.move | 158 ++++++++++++++++++ .../sources/vesting_without_staking.move | 140 +++++++++++++++- 2 files changed, 296 insertions(+), 2 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move index c6ecefcbebbcb..e99f11e31a120 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -9313,4 +9313,162 @@ module supra_framework::pbo_delegation_pool { ); assert!(unlock_coin, 20); } + + #[test(supra_framework = @supra_framework, validator = @0x123, delegator = @0x010)] + // Testing whether fast forward is working as expected + public entry fun test_unlocking_principle_stake_success_can_fastforward( + supra_framework: &signer, validator: &signer, delegator: &signer + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + initialize_for_test(supra_framework); + account::create_account_for_test(signer::address_of(validator)); + let delegator_address = signer::address_of(delegator); + let delegator_address_vec = vector[delegator_address]; + let principle_stake = vector[100 * ONE_SUPRA]; + let coin = stake::mint_coins(100 * ONE_SUPRA); + let principle_lockup_time = 7776000; // 3 month cliff + let multisig = generate_multisig_account(validator, vector[@0x12134], 2); + + initialize_test_validator( + validator, + 0, + true, + true, + 0, + delegator_address_vec, + principle_stake, + coin, + option::some(multisig), + vector[2, 3, 1], + 10, + principle_lockup_time, + LOCKUP_CYCLE_SECONDS // monthly unlocking + ); + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + + // after 2 month unlock reward + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + + // 3 month + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + + // after 4 months + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + + // It's acceptable to round off 9 because this coin will remain locked and won't be transferred anywhere. + let unlock_coin = + can_principle_unlock( + delegator_address, + pool_address, + (20 * ONE_SUPRA) - 9 + ); + assert!(unlock_coin, 11); + + // after 5 months + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + let unlock_coin = + can_principle_unlock( + delegator_address, + pool_address, + (50 * ONE_SUPRA) - 9 + ); + assert!(unlock_coin, 12); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + + // after 11 months + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + let unlock_coin = + can_principle_unlock(delegator_address, pool_address, 100 * ONE_SUPRA); + assert!(unlock_coin, 18); + } + + #[test(supra_framework = @supra_framework, validator = @0x123, delegator = @0x010)] + // Testing whether fast forward is working as expected + public entry fun test_unlocking_principle_stake_success_can_fastforward_nondivisable( + supra_framework: &signer, validator: &signer, delegator: &signer + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + initialize_for_test(supra_framework); + account::create_account_for_test(signer::address_of(validator)); + let delegator_address = signer::address_of(delegator); + let delegator_address_vec = vector[delegator_address]; + let principle_stake = vector[113 * ONE_SUPRA]; + let coin = stake::mint_coins(113 * ONE_SUPRA); + let principle_lockup_time = 7776000; // 3 month cliff + let multisig = generate_multisig_account(validator, vector[@0x12134], 2); + + initialize_test_validator( + validator, + 0, + true, + true, + 0, + delegator_address_vec, + principle_stake, + coin, + option::some(multisig), + vector[2, 3, 1], + 10, + principle_lockup_time, + LOCKUP_CYCLE_SECONDS // monthly unlocking + ); + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + + // after 2 month unlock reward + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + + // 3 month + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + + // after 4 months + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + + // It's acceptable to round off 9 because this coin will remain locked and won't be transferred anywhere. + let unlock_coin = + can_principle_unlock( + delegator_address, + pool_address, + (22 * ONE_SUPRA) - 9 + ); + assert!(unlock_coin, 11); + + // after 5 months + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + let unlock_coin = + can_principle_unlock( + delegator_address, + pool_address, + (55 * ONE_SUPRA) - 9 + ); + assert!(unlock_coin, 12); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + + // after 11 months + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + end_aptos_epoch(); + let unlock_coin = + can_principle_unlock(delegator_address, pool_address, 113 * ONE_SUPRA); + assert!(unlock_coin, 18); + } } diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index dd21e4e3235b9..752cfb8397631 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -9,6 +9,7 @@ module supra_framework::vesting_without_staking { use std::signer; use std::string::{utf8, String}; use std::vector; + use aptos_std::debug; use aptos_std::simple_map::{Self, SimpleMap}; use aptos_std::math64::min; @@ -502,7 +503,7 @@ module supra_framework::vesting_without_staking { emit_event(&mut vesting_contract.vest_events, VestEvent { admin: vesting_contract.admin, - shareholder_address: shareholder_address, + shareholder_address, vesting_contract_address: contract_address, period_vested: next_period_to_vest, }, @@ -527,7 +528,7 @@ module supra_framework::vesting_without_staking { emit_event(&mut vesting_contract.vest_events, VestEvent { admin: vesting_contract.admin, - shareholder_address: shareholder_address, + shareholder_address, vesting_contract_address: contract_address, period_vested: next_period_to_vest, }, @@ -1622,4 +1623,139 @@ module supra_framework::vesting_without_staking { fixed_point32::multiply_u64(total, fixed_point32::create_from_rational(numerator, denominator)) } + + #[test(supra_framework = @0x1, admin = @0x123, shareholder = @0x234, withdrawal = @111)] + public entry fun test_end_to_end_can_fast_forward_divisable( + supra_framework: &signer, + admin: &signer, + shareholder: &signer, + withdrawal: &signer, + ) acquires AdminStore, VestingContract { + let admin_address = signer::address_of(admin); + let withdrawal_address = signer::address_of(withdrawal); + let shareholder_address = signer::address_of(shareholder); + let shareholders = &vector[shareholder_address]; + let shareholder_share = GRANT_AMOUNT; + let shares = &vector[shareholder_share]; + // Create the vesting contract. + setup(supra_framework, + vector[ + admin_address, + withdrawal_address, + shareholder_address]); + let contract_address = setup_vesting_contract(admin, shareholders, shares, + withdrawal_address); + assert!(vector::length(&borrow_global(admin_address).vesting_contracts) == + 1, 0); + let vested_amount = 0; + // Because the time is behind the start time, vest will do nothing. + vest(contract_address); + assert!(coin::balance(contract_address) == GRANT_AMOUNT, 0); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + + // Time is now at the start time, vest will unlock the first period, which is 2/10. + timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( + contract_address)); + vest(contract_address); + vested_amount = vested_amount + fraction(shareholder_share, 2, 10); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + + timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( + contract_address) * 9); + vest(contract_address); + vested_amount = shareholder_share; + assert!(coin::balance(shareholder_address) == vested_amount, 0); + } + + #[test(supra_framework = @0x1, admin = @0x123, shareholder = @0x234, withdrawal = @111)] + public entry fun test_end_to_end_can_fast_forward_nondivisable( + supra_framework: &signer, + admin: &signer, + shareholder: &signer, + withdrawal: &signer, + ) acquires AdminStore, VestingContract { + let admin_address = signer::address_of(admin); + let withdrawal_address = signer::address_of(withdrawal); + let shareholder_address = signer::address_of(shareholder); + let shareholders = &vector[shareholder_address]; + let shareholder_share = 3334; + let shares = &vector[shareholder_share]; + // Create the vesting contract. + setup(supra_framework, + vector[ + admin_address, + withdrawal_address, + shareholder_address]); + let contract_address = setup_vesting_contract(admin, shareholders, shares, + withdrawal_address); + assert!(vector::length(&borrow_global(admin_address).vesting_contracts) == + 1, 0); + let vested_amount = 0; + // Because the time is behind the start time, vest will do nothing. + vest(contract_address); + assert!(coin::balance(contract_address) == 3334, 0); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + + // Time is now at the start time, vest will unlock the first period, which is 2/10. + timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( + contract_address)); + vest(contract_address); + vested_amount = vested_amount + fraction(shareholder_share, 2, 10); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + + timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( + contract_address) * 9); + vest(contract_address); + vested_amount = shareholder_share; + assert!(coin::balance(shareholder_address) == vested_amount, 0); + } + + #[test(supra_framework = @0x1, admin = @0x123, shareholder = @0x234, withdrawal = @111)] + public entry fun test_end_to_end_can_fast_forward_time_unchanged( + supra_framework: &signer, + admin: &signer, + shareholder: &signer, + withdrawal: &signer, + ) acquires AdminStore, VestingContract { + let admin_address = signer::address_of(admin); + let withdrawal_address = signer::address_of(withdrawal); + let shareholder_address = signer::address_of(shareholder); + let shareholders = &vector[shareholder_address]; + let shareholder_share = 1000; + let shares = &vector[shareholder_share]; + // Create the vesting contract. + setup(supra_framework, + vector[ + admin_address, + withdrawal_address, + shareholder_address]); + let contract_address = setup_vesting_contract(admin, shareholders, shares, + withdrawal_address); + assert!(vector::length(&borrow_global(admin_address).vesting_contracts) == + 1, 0); + let vested_amount = 0; + // Because the time is behind the start time, vest will do nothing. + vest(contract_address); + assert!(coin::balance(contract_address) == 1000, 0); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + // Time is now at the start time, vest will unlock the first period, which is 2/10. + timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( + contract_address)); + vest(contract_address); + vested_amount = vested_amount + fraction(shareholder_share, 2, 10); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + // The time is unchanged, vest will do nothing. + vest(contract_address); + vested_amount = vested_amount + fraction(shareholder_share, 2, 10); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + vest(contract_address); + vested_amount = vested_amount + fraction(shareholder_share, 2, 10); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + + timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( + contract_address) * 9); + vest(contract_address); + vested_amount = shareholder_share; + assert!(coin::balance(shareholder_address) == vested_amount, 0); + } } From f0b799d37da9a2f2b1a4714f54bc957d2de8256a Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Thu, 19 Dec 2024 22:20:16 -0500 Subject: [PATCH 07/14] Add comment --- .../supra-framework/sources/pbo_delegation_pool.move | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move index e99f11e31a120..0279b562588e0 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -9439,7 +9439,7 @@ module supra_framework::pbo_delegation_pool { timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); end_aptos_epoch(); - // It's acceptable to round off 9 because this coin will remain locked and won't be transferred anywhere. + // After three mounth cliff and one extra mouth, 2/10 of the principle stake (113) = 22.6 can be unlocked. minus 9 for rounding off. let unlock_coin = can_principle_unlock( delegator_address, @@ -9448,7 +9448,7 @@ module supra_framework::pbo_delegation_pool { ); assert!(unlock_coin, 11); - // after 5 months + // after 5 months, 5/10 of the principle stake (113) = 56.5 can be unlocked. minus 9 for rounding off. timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); end_aptos_epoch(); let unlock_coin = From 602da5fc12d41d57be648ed985dc9b3e11aaffde Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Thu, 19 Dec 2024 22:29:07 -0500 Subject: [PATCH 08/14] Remove incorrect addition --- .../supra-framework/sources/vesting_without_staking.move | 2 -- 1 file changed, 2 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index 752cfb8397631..5625e368cee37 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -1746,10 +1746,8 @@ module supra_framework::vesting_without_staking { assert!(coin::balance(shareholder_address) == vested_amount, 0); // The time is unchanged, vest will do nothing. vest(contract_address); - vested_amount = vested_amount + fraction(shareholder_share, 2, 10); assert!(coin::balance(shareholder_address) == vested_amount, 0); vest(contract_address); - vested_amount = vested_amount + fraction(shareholder_share, 2, 10); assert!(coin::balance(shareholder_address) == vested_amount, 0); timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( From b25141ac60a61680d2b14aac9c9e4cfe80ba5d8d Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Fri, 20 Dec 2024 00:03:46 -0500 Subject: [PATCH 09/14] Update logic --- .../sources/pbo_delegation_pool.move | 7 ++----- .../sources/vesting_without_staking.move | 20 ++++++++++++------- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move index 0279b562588e0..abae0a1ab5ab0 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -1781,12 +1781,9 @@ module supra_framework::pbo_delegation_pool { }; if (last_unlocked_period < unlock_periods_passed && fixed_point64::less(cfraction, one)) { let final_fraction= *vector::borrow(&unlock_schedule.schedule, schedule_length - 1); - // Determine how many periods is needed - let periods_needed = min(unlock_periods_passed - last_unlocked_period, - ((fixed_point64::get_raw_value(fixed_point64::divide(fixed_point64::sub(one, cfraction), final_fraction)) as u64) ) - ); // Acclerate calculation to current period and don't update last_unlocked_period since it is not used anymore - cfraction = fixed_point64::add(cfraction, fixed_point64::multiply_u128_return_fixpoint64((periods_needed as u128), final_fraction)); + cfraction = fixed_point64::add(cfraction, fixed_point64::multiply_u128_return_fixpoint64((unlock_periods_passed - last_unlocked_period as u128), final_fraction)); + cfraction = fixed_point64::min(cfraction, one); }; unlock_schedule.cumulative_unlocked_fraction = cfraction; unlock_schedule.last_unlock_period = unlock_periods_passed; diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index 5625e368cee37..50fe6f1ca5dfd 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -514,17 +514,23 @@ module supra_framework::vesting_without_staking { if(last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0) { let final_fraction = *vector::borrow(schedule, vector::length(schedule) - 1); // Determine how many periods is needed based on the left_amount - let periods_need_used_up_amount = if (vesting_record.left_amount % fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) == 0) { - vesting_record.left_amount / fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + let added_fraction = fixed_point32::multiply_u64_return_fixpoint32(last_completed_period - next_period_to_vest + 1, final_fraction); + // If the added_fraction is greater than or equal to the left_amount, then we can vest all the left_amount + let periods_need = + if (fixed_point32::multiply_u64(vesting_record.init_amount, added_fraction) >= vesting_record.left_amount){ + if (vesting_record.left_amount % fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) == 0) { + vesting_record.left_amount / fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + } else { + vesting_record.left_amount / fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + 1 + } } else { - vesting_record.left_amount / fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + 1 + last_completed_period - next_period_to_vest + 1 }; - let periods_needed = - min(periods_need_used_up_amount, last_completed_period - next_period_to_vest + 1); - let total_fraction = fixed_point32::multiply_u64_return_fixpoint32(periods_needed, final_fraction); + + let total_fraction = fixed_point32::multiply_u64_return_fixpoint32(periods_need, final_fraction); // We don't need to check vesting_record.left_amount > 0 because vest_transfer will handle that. vest_transfer(vesting_record, signer_cap, beneficiary, total_fraction); - next_period_to_vest = next_period_to_vest + periods_needed; + next_period_to_vest = next_period_to_vest + periods_need; emit_event(&mut vesting_contract.vest_events, VestEvent { admin: vesting_contract.admin, From 2f628d059773a5c7195833892db08da02b18a356 Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Fri, 20 Dec 2024 00:04:11 -0500 Subject: [PATCH 10/14] Remove unused import --- .../framework/supra-framework/sources/pbo_delegation_pool.move | 1 - .../supra-framework/sources/vesting_without_staking.move | 1 - 2 files changed, 2 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move index abae0a1ab5ab0..ee62674c0c15f 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -122,7 +122,6 @@ module supra_framework::pbo_delegation_pool { use aptos_std::table::{Self, Table}; use aptos_std::smart_table::{Self, SmartTable}; use aptos_std::fixed_point64::{Self, FixedPoint64}; - use aptos_std::math64::min; use supra_framework::coin::Coin; use supra_framework::account; diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index 50fe6f1ca5dfd..ab7f639069dae 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -9,7 +9,6 @@ module supra_framework::vesting_without_staking { use std::signer; use std::string::{utf8, String}; use std::vector; - use aptos_std::debug; use aptos_std::simple_map::{Self, SimpleMap}; use aptos_std::math64::min; From 366ad87f37e5a409496bfd48b81dd481b142933b Mon Sep 17 00:00:00 2001 From: Aric <158555905+axiongsupra@users.noreply.github.com> Date: Mon, 23 Dec 2024 21:07:55 -0500 Subject: [PATCH 11/14] Update aptos-move/framework/supra-framework/sources/vesting_without_staking.move Co-authored-by: Saurabh Joshi <106232965+sjoshisupra@users.noreply.github.com> --- .../supra-framework/sources/vesting_without_staking.move | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index ab7f639069dae..dbd7767e0e699 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -512,15 +512,18 @@ module supra_framework::vesting_without_staking { if(last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0) { let final_fraction = *vector::borrow(schedule, vector::length(schedule) - 1); + let final_fraction_amount = fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) // Determine how many periods is needed based on the left_amount let added_fraction = fixed_point32::multiply_u64_return_fixpoint32(last_completed_period - next_period_to_vest + 1, final_fraction); // If the added_fraction is greater than or equal to the left_amount, then we can vest all the left_amount let periods_need = if (fixed_point32::multiply_u64(vesting_record.init_amount, added_fraction) >= vesting_record.left_amount){ - if (vesting_record.left_amount % fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) == 0) { - vesting_record.left_amount / fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + let result = vesting_record.left_amount / final_fraction_amount; + // check if `left_amount` is perfectly divisible by `final_fraction_amount` + if (vesting_record.left_amount == final_fraction_amount*result) { + result } else { - vesting_record.left_amount / fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + 1 + result + 1 } } else { last_completed_period - next_period_to_vest + 1 From 61a5ad06de19ebce3bbb71f68a7f2171caa0a814 Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Mon, 23 Dec 2024 21:43:51 -0500 Subject: [PATCH 12/14] Remove unused and add comma --- .../aptos-stdlib/sources/fixed_point64.move | 13 ------------- .../sources/vesting_without_staking.move | 2 +- 2 files changed, 1 insertion(+), 14 deletions(-) diff --git a/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move b/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move index 71ceaa9b8307a..b24ec894e6c3f 100644 --- a/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move +++ b/aptos-move/framework/aptos-stdlib/sources/fixed_point64.move @@ -56,19 +56,6 @@ module aptos_std::fixed_point64 { ensures result.value == x.value + y.value; } - /// Returns x / y. The result cannot be greater than MAX_U128. - public fun divide(x: FixedPoint64, y: FixedPoint64): FixedPoint64 { - let x_raw = get_raw_value(x); - let y_raw = get_raw_value(y); - // If it is divisable, return the result. If not, return the result + 1. - let result = (x_raw as u256) / (y_raw as u256); - if ((x_raw as u256) % (y_raw as u256) != 0) { - result = result + 1; - }; - assert!(result <= MAX_U128, ERATIO_OUT_OF_RANGE); - create_from_raw_value((result as u128)) - } - /// Multiply a u128 integer by a fixed-point number, truncating any /// fractional part of the product. This will abort if the product /// overflows. diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index dbd7767e0e699..4eae3a7596aae 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -512,7 +512,7 @@ module supra_framework::vesting_without_staking { if(last_completed_period >= next_period_to_vest && vesting_record.left_amount > 0) { let final_fraction = *vector::borrow(schedule, vector::length(schedule) - 1); - let final_fraction_amount = fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction) + let final_fraction_amount = fixed_point32::multiply_u64(vesting_record.init_amount, final_fraction); // Determine how many periods is needed based on the left_amount let added_fraction = fixed_point32::multiply_u64_return_fixpoint32(last_completed_period - next_period_to_vest + 1, final_fraction); // If the added_fraction is greater than or equal to the left_amount, then we can vest all the left_amount From eabd31b5a8d1a86992a41e85d4c18670e50dd615 Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Mon, 23 Dec 2024 21:45:24 -0500 Subject: [PATCH 13/14] Add two required test cases --- .../sources/vesting_without_staking.move | 72 +++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move index 4eae3a7596aae..3d8d4199460dc 100644 --- a/aptos-move/framework/supra-framework/sources/vesting_without_staking.move +++ b/aptos-move/framework/supra-framework/sources/vesting_without_staking.move @@ -1764,4 +1764,76 @@ module supra_framework::vesting_without_staking { vested_amount = shareholder_share; assert!(coin::balance(shareholder_address) == vested_amount, 0); } + + #[test(supra_framework = @0x1, admin = @0x123, shareholder = @0x234, withdrawal = @111)] + public entry fun test_end_to_end_can_fast_forward_time_5_out_of_10( + supra_framework: &signer, + admin: &signer, + shareholder: &signer, + withdrawal: &signer, + ) acquires AdminStore, VestingContract { + let admin_address = signer::address_of(admin); + let withdrawal_address = signer::address_of(withdrawal); + let shareholder_address = signer::address_of(shareholder); + let shareholders = &vector[shareholder_address]; + let shareholder_share = 1000; + let shares = &vector[shareholder_share]; + // Create the vesting contract. + setup(supra_framework, + vector[ + admin_address, + withdrawal_address, + shareholder_address]); + let contract_address = setup_vesting_contract_with_schedule(admin, shareholders, shares, + withdrawal_address, &vector[2, 3, 1], 10); + assert!(vector::length(&borrow_global(admin_address).vesting_contracts) == + 1, 0); + let vested_amount = 0; + // Because the time is behind the start time, vest will do nothing. + vest(contract_address); + assert!(coin::balance(contract_address) == 1000, 0); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + // Time is now at the start time, vest will unlock the first period, which is 2/10. + timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( + contract_address) * 2); + vest(contract_address); + vested_amount = vested_amount + fraction(shareholder_share, 5, 10); + assert!(coin::balance(shareholder_address) + 2 == vested_amount, 0); + } + + #[test(supra_framework = @0x1, admin = @0x123, shareholder = @0x234, withdrawal = @111)] + public entry fun test_end_to_end_can_fast_forward_time_7_out_of_10( + supra_framework: &signer, + admin: &signer, + shareholder: &signer, + withdrawal: &signer, + ) acquires AdminStore, VestingContract { + let admin_address = signer::address_of(admin); + let withdrawal_address = signer::address_of(withdrawal); + let shareholder_address = signer::address_of(shareholder); + let shareholders = &vector[shareholder_address]; + let shareholder_share = 1000; + let shares = &vector[shareholder_share]; + // Create the vesting contract. + setup(supra_framework, + vector[ + admin_address, + withdrawal_address, + shareholder_address]); + let contract_address = setup_vesting_contract_with_schedule(admin, shareholders, shares, + withdrawal_address, &vector[2, 3, 1], 10); + assert!(vector::length(&borrow_global(admin_address).vesting_contracts) == + 1, 0); + let vested_amount = 0; + // Because the time is behind the start time, vest will do nothing. + vest(contract_address); + assert!(coin::balance(contract_address) == 1000, 0); + assert!(coin::balance(shareholder_address) == vested_amount, 0); + // Time is now at the start time, vest will unlock the first period, which is 2/10. + timestamp::update_global_time_for_test_secs(vesting_start_secs(contract_address) + period_duration_secs( + contract_address) * 4); + vest(contract_address); + vested_amount = vested_amount + fraction(shareholder_share, 7, 10); + assert!(coin::balance(shareholder_address) + 3 == vested_amount, 0); + } } From 483248b4fb9eb7da69f4c5767f6ebb1c8399e371 Mon Sep 17 00:00:00 2001 From: axiongsupra Date: Mon, 30 Dec 2024 23:06:45 -0500 Subject: [PATCH 14/14] Add test case in PBO --- .../sources/pbo_delegation_pool.move | 144 ++++++++++++++++++ 1 file changed, 144 insertions(+) diff --git a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move index e5c8bac746f87..efe43a60339ae 100644 --- a/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move +++ b/aptos-move/framework/supra-framework/sources/pbo_delegation_pool.move @@ -9514,4 +9514,148 @@ module supra_framework::pbo_delegation_pool { can_principle_unlock(delegator_address, pool_address, 113 * ONE_SUPRA); assert!(unlock_coin, 18); } + + #[test(supra_framework = @supra_framework, validator = @0x123, delegator = @0x010)] + // Testing whether fast forward is working as expected + public entry fun test_unlocking_principle_stake_success_can_fastforward_5_out_of_10( + supra_framework: &signer, validator: &signer, delegator: &signer + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + initialize_for_test(supra_framework); + account::create_account_for_test(signer::address_of(validator)); + let delegator_address = signer::address_of(delegator); + let delegator_address_vec = vector[delegator_address]; + let principle_stake = vector[1000 * ONE_SUPRA]; + let coin = stake::mint_coins(1000 * ONE_SUPRA); + let principle_lockup_time = 7776000; // 3 month cliff + let multisig = generate_multisig_account(validator, vector[@0x12134], 2); + + initialize_test_validator( + validator, + 0, + true, + true, + 0, + delegator_address_vec, + principle_stake, + coin, + option::some(multisig), + vector[2, 3, 1], + 10, + principle_lockup_time, + LOCKUP_CYCLE_SECONDS // monthly unlocking + ); + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + + // 3 month + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + // After cliff, 5/10 of the principle stake (1000) = 500 can be unlocked. + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS * 2); + + let unlock_coin = + can_principle_unlock( + delegator_address, + pool_address, + (500 * ONE_SUPRA)-1 + ); + assert!(unlock_coin, 11); + } + + #[test(supra_framework = @supra_framework, validator = @0x123, delegator = @0x010)] + // Testing whether fast forward is working as expected + public entry fun test_unlocking_principle_stake_success_can_fastforward_7_out_of_10( + supra_framework: &signer, validator: &signer, delegator: &signer + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + initialize_for_test(supra_framework); + account::create_account_for_test(signer::address_of(validator)); + let delegator_address = signer::address_of(delegator); + let delegator_address_vec = vector[delegator_address]; + let principle_stake = vector[1000 * ONE_SUPRA]; + let coin = stake::mint_coins(1000 * ONE_SUPRA); + let principle_lockup_time = 7776000; // 3 month cliff + let multisig = generate_multisig_account(validator, vector[@0x12134], 2); + + initialize_test_validator( + validator, + 0, + true, + true, + 0, + delegator_address_vec, + principle_stake, + coin, + option::some(multisig), + vector[2, 3, 1], + 10, + principle_lockup_time, + LOCKUP_CYCLE_SECONDS // monthly unlocking + ); + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + + // 3 month + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + // After cliff, 7/10 of the principle stake (1000) = 700 can be unlocked. + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS * 4); + + let unlock_coin = + can_principle_unlock( + delegator_address, + pool_address, + (700 * ONE_SUPRA) - 1 + ); + assert!(unlock_coin, 11); + } + + #[test(supra_framework = @supra_framework, validator = @0x123, delegator = @0x010)] + // Testing whether fast forward is working as expected + public entry fun test_unlocking_principle_stake_success_can_fastforward_10_out_of_10( + supra_framework: &signer, validator: &signer, delegator: &signer + ) acquires DelegationPoolOwnership, DelegationPool, GovernanceRecords, BeneficiaryForOperator, NextCommissionPercentage { + initialize_for_test(supra_framework); + account::create_account_for_test(signer::address_of(validator)); + let delegator_address = signer::address_of(delegator); + let delegator_address_vec = vector[delegator_address]; + let principle_stake = vector[1000 * ONE_SUPRA]; + let coin = stake::mint_coins(1000 * ONE_SUPRA); + let principle_lockup_time = 7776000; // 3 month cliff + let multisig = generate_multisig_account(validator, vector[@0x12134], 2); + + initialize_test_validator( + validator, + 0, + true, + true, + 0, + delegator_address_vec, + principle_stake, + coin, + option::some(multisig), + vector[2, 3, 1], + 10, + principle_lockup_time, + LOCKUP_CYCLE_SECONDS // monthly unlocking + ); + let validator_address = signer::address_of(validator); + let pool_address = get_owned_pool_address(validator_address); + + // 3 month + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS); + // After cliff, all of the principle stake (1000) can be unlocked. + timestamp::fast_forward_seconds(LOCKUP_CYCLE_SECONDS * 8); + + let unlock_coin = + can_principle_unlock( + delegator_address, + pool_address, + (1000 * ONE_SUPRA) + ); + assert!(unlock_coin, 11); + } }