-
Notifications
You must be signed in to change notification settings - Fork 33
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Feat staker v2 no fp and subpointers (#67)
* Initial implementation of staked seconds calculation * a bit of naming * added upgrade method. NOTE: constructor changed. Staker now requires governor. * fix import * TODO: removal * remove todo * test * local settings * revert upgrade code for now * removed upgrade due to cyclic dependency with governor * finalise * typo * Added UFixedPoint type for unsigned FixedPoint operations. Currently support Q128.128 FP. Added cumulative seconds per total staked history value, and getter for that value Added method that allows to calculate how much staked seconds would staked token amount generate in between 2 timestamps: `calculate_staked_seconds_for_amount_between` Added tiny amount of tests * removed old tests * * debugged fixed point math. added tests * removed staked seconds calculation * added seconds per total staked test and added logic for calculation of that value when nothing is staked * some comment fixes * Extracted staker storage into separate file due to large amount of boilerplate. Implemented custom packing and custom data SubPointers. * rename staker_storage -> staker_log. moved all relevant logic there * Simplified Fixed Point operations library. Reduced internal storage size. Added convienience method for u64 / u128 case. * simplified more. better naming * clean up bitshifts from tests * overflow checks * old tests did not respect overflow. had to create new ones. finished subpointers for staking_log. Subpointer always read whole felt252 from memory and converts it. So I had to change approach for packing. * Debugging, cleaning up, fixing missed issues * final fix * final debugging * cleanup * cleanup * removing UFixedPoint124x128 struct * BugFix for a case when fetl252 is overflown * remove fixed point lib inline everything remove storage read optimisations * small fix * lost deletions * Review fixes * uniform get_block_timestamp usage * store log_record timestamp in seconds * inline test assert for fp value * rename HALF to TWO_POW_127 * scarb fmt * Review fixes * scarb format * * remove MAX_FP related code * find_in_change_log -> find_change_log_on_or_before_timestamp * log_change argument rename * simplified and renamed get_seconds_per_total_staked_sum_at added get_time_weighted_total_staked_sum_at added get_total_staked_at to staker added get_user_share_of_total_staked_over_period to staker added get_average_total_staked_over_period to staker * uncomment tests + scarb format * Review fix: - reverted changes to staker - moved changes to staker_v2 - changed delegated_cumulative_snapshot to Map<ContractAddress, Vec<DelegatedSnapshot>> * fixup * fixup * remove Timestamped * remove TODO * * rewrite while loop as recursion * make search private --------- Co-authored-by: Moody Salem <moody.salem@gmail.com>
- Loading branch information
1 parent
475f33f
commit eac52cc
Showing
6 changed files
with
1,305 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
.env | ||
.vscode | ||
.starkli | ||
.DS_Store | ||
####### | ||
Scarb | ||
####### | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,178 @@ | ||
use starknet::storage::{ | ||
Mutable, StorageAsPath, StorageBase, StoragePointerReadAccess, StoragePointerWriteAccess, | ||
Vec, VecTrait, MutableVecTrait | ||
}; | ||
|
||
use starknet::storage_access::{StorePacking}; | ||
use starknet::{get_block_timestamp}; | ||
|
||
|
||
pub type StakingLog = Vec<StakingLogRecord>; | ||
|
||
const TWO_POW_32: u64 = 0x100000000_u64; | ||
const MASK_32_BITS: u128 = 0x100000000_u128 - 1; | ||
const TWO_POW_160: u256 = 0x10000000000000000000000000000000000000000; | ||
|
||
#[derive(Drop, Serde, Copy)] | ||
pub(crate) struct StakingLogRecord { | ||
pub(crate) timestamp: u64, | ||
// Only 128+32=160 bits are used | ||
pub(crate) time_weighted_total_staked_sum: u256, | ||
pub(crate) seconds_per_total_staked_sum: u256, | ||
} | ||
|
||
#[generate_trait] | ||
impl InternalStakingLogOperations of InternalLogOperations { | ||
fn search_recursive( | ||
self: @StorageBase<StakingLog>, timestamp: u64, left: u64, right: u64, | ||
) -> Option<(StakingLogRecord, u64)> { | ||
let log = self.as_path(); | ||
|
||
if left > right { | ||
return Option::None; | ||
} | ||
|
||
let center = (right + left) / 2; | ||
let record = log.at(center).read(); | ||
|
||
if record.timestamp <= timestamp { | ||
let res = self | ||
.search_recursive(timestamp, center + 1, right) | ||
.unwrap_or((record, center)); | ||
|
||
Option::Some(res) | ||
} else { | ||
self.search_recursive(timestamp, left, center - 1) | ||
} | ||
} | ||
} | ||
|
||
#[generate_trait] | ||
pub impl StakingLogOperations of LogOperations { | ||
|
||
fn find_record_on_or_before_timestamp( | ||
self: @StorageBase<StakingLog>, timestamp: u64, | ||
) -> Option<(StakingLogRecord, u64)> { | ||
let log = self.as_path(); | ||
if log.len() == 0 { | ||
return Option::None; | ||
} | ||
|
||
if log.at(0).read().timestamp > timestamp { | ||
return Option::None; | ||
} | ||
|
||
return self.search_recursive(timestamp, 0, log.len() - 1); | ||
} | ||
|
||
fn log_change( | ||
self: StorageBase<Mutable<StakingLog>>, amount: u128, total_staked_before_change: u128, | ||
) { | ||
let log = self.as_path(); | ||
|
||
let block_timestamp = get_block_timestamp(); | ||
|
||
if log.len() == 0 { | ||
log | ||
.append() | ||
.write( | ||
StakingLogRecord { | ||
timestamp: block_timestamp, | ||
time_weighted_total_staked_sum: 0_u256, | ||
seconds_per_total_staked_sum: 0_u64.into(), | ||
}, | ||
); | ||
|
||
return; | ||
} | ||
|
||
let last_record_ptr = log.at(log.len() - 1); | ||
|
||
let mut last_record = last_record_ptr.read(); | ||
|
||
let mut record = if last_record.timestamp == block_timestamp { | ||
// update record | ||
last_record_ptr | ||
} else { | ||
// create new record | ||
log.append() | ||
}; | ||
|
||
// Might be zero | ||
let seconds_diff = block_timestamp - last_record.timestamp; | ||
|
||
let time_weighted_total_staked = total_staked_before_change.into() * seconds_diff.into(); | ||
|
||
let staked_seconds_per_total_staked: u256 = if total_staked_before_change == 0 { | ||
0_u64.into() | ||
} else { | ||
let res = u256 { low: 0, high: seconds_diff.into() } | ||
/ total_staked_before_change.into(); | ||
res | ||
}; | ||
|
||
// Add a new record. | ||
record | ||
.write( | ||
StakingLogRecord { | ||
timestamp: block_timestamp, | ||
time_weighted_total_staked_sum: last_record.time_weighted_total_staked_sum | ||
+ time_weighted_total_staked, | ||
seconds_per_total_staked_sum: last_record.seconds_per_total_staked_sum | ||
+ staked_seconds_per_total_staked, | ||
}, | ||
); | ||
} | ||
} | ||
|
||
// | ||
// Storage layout for StakingLogRecord | ||
// | ||
|
||
pub(crate) impl StakingLogRecordStorePacking of StorePacking<StakingLogRecord, (felt252, felt252)> { | ||
fn pack(value: StakingLogRecord) -> (felt252, felt252) { | ||
let val1: felt252 = pack_u64_u256_tuple( | ||
value.timestamp, value.time_weighted_total_staked_sum, | ||
); | ||
|
||
let val2: felt252 = value.seconds_per_total_staked_sum.try_into().unwrap(); | ||
|
||
(val1, val2) | ||
} | ||
|
||
fn unpack(value: (felt252, felt252)) -> StakingLogRecord { | ||
let (packed_ts_time_weighted_total_staked, seconds_per_total_staked_sum) = value; | ||
let (timestamp, time_weighted_total_staked_sum) = unpack_u64_u256_tuple( | ||
packed_ts_time_weighted_total_staked, | ||
); | ||
|
||
StakingLogRecord { | ||
timestamp: timestamp, | ||
time_weighted_total_staked_sum: time_weighted_total_staked_sum, | ||
seconds_per_total_staked_sum: seconds_per_total_staked_sum.try_into().unwrap(), | ||
} | ||
} | ||
} | ||
|
||
pub(crate) fn pack_u64_u256_tuple(val1: u64, val2: u256) -> felt252 { | ||
let cumulative_total_staked_high_32_bits: u128 = val2.high & MASK_32_BITS; | ||
u256 { | ||
high: val1.into() * TWO_POW_32.into() + cumulative_total_staked_high_32_bits.into(), | ||
low: val2.low, | ||
} | ||
.try_into() | ||
.unwrap() | ||
} | ||
|
||
pub(crate) fn unpack_u64_u256_tuple(value: felt252) -> (u64, u256) { | ||
let packed_ts_total_staked_u256: u256 = value.into(); | ||
|
||
let cumulative_total_staked = u256 { | ||
high: packed_ts_total_staked_u256.high & MASK_32_BITS, low: packed_ts_total_staked_u256.low, | ||
}; | ||
|
||
return ( | ||
(packed_ts_total_staked_u256.high / TWO_POW_32.into()).try_into().unwrap(), | ||
cumulative_total_staked, | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
use crate::staker_log::{pack_u64_u256_tuple, unpack_u64_u256_tuple}; | ||
|
||
const MASK_32_BITS: u128 = 0x100000000_u128 - 1; | ||
const MASK_64_BITS: u128 = 0x10000000000000000_u128 - 1; | ||
const MASK_160_BITS: u256 = 0x10000000000000000000000000000000000000000 - 1; | ||
|
||
|
||
fn assert_packs_and_unpacks(timestamp: u64, total_staked: u256) { | ||
let packed: u256 = pack_u64_u256_tuple(timestamp, total_staked).into(); | ||
|
||
let first_160_bits: u256 = packed & MASK_160_BITS; | ||
|
||
let shifted_160_bits_right: u128 = packed.high / (MASK_32_BITS + 1); | ||
|
||
let last_64_bits: u64 = (shifted_160_bits_right & MASK_64_BITS).try_into().unwrap(); | ||
assert_eq!(first_160_bits, total_staked); | ||
assert_eq!(last_64_bits, timestamp); | ||
|
||
let (unpacked_timestamp, unpacked_cumulative_total_staked) = unpack_u64_u256_tuple( | ||
packed.try_into().unwrap(), | ||
); | ||
assert_eq!(unpacked_timestamp, timestamp); | ||
assert_eq!(unpacked_cumulative_total_staked, total_staked); | ||
} | ||
|
||
#[test] | ||
fn test_staking_log_packing() { | ||
assert_packs_and_unpacks(0_u64, 0_u256); | ||
assert_packs_and_unpacks(10_u64, 50_u256); | ||
assert_packs_and_unpacks( | ||
0xffffffffffffffff_u64, 0xffffffffffffffffffffffffffffffffffffffff_u256, | ||
) | ||
} |
Oops, something went wrong.