Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat staker v2 no fp and subpointers #67

Open
wants to merge 40 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
cb3e4dd
Initial implementation of staked seconds calculation
baitcode Dec 10, 2024
01b3f73
a bit of naming
baitcode Dec 10, 2024
924aef3
added upgrade method. NOTE: constructor changed. Staker now requires …
baitcode Dec 10, 2024
88f8db8
fix import
baitcode Dec 10, 2024
9553035
TODO: removal
baitcode Dec 10, 2024
aedb403
remove todo
baitcode Dec 10, 2024
9ee9324
test
baitcode Dec 11, 2024
c3c0244
local settings
baitcode Dec 11, 2024
692c36a
revert upgrade code for now
baitcode Dec 11, 2024
349919e
removed upgrade due to cyclic dependency with governor
baitcode Dec 11, 2024
d6504e9
finalise
baitcode Dec 11, 2024
5f32326
typo
baitcode Dec 11, 2024
b5ac50b
Added UFixedPoint type for unsigned FixedPoint operations. Currently …
baitcode Dec 13, 2024
2f9516a
removed old tests
baitcode Dec 13, 2024
bb4f99a
* debugged fixed point math. added tests
baitcode Dec 14, 2024
1a2d6fc
some comment fixes
baitcode Jan 3, 2025
b18d702
Extracted staker storage into separate file due to large amount of bo…
baitcode Jan 6, 2025
7fdfb19
rename staker_storage -> staker_log. moved all relevant logic there
baitcode Jan 6, 2025
b82953f
Simplified Fixed Point operations library. Reduced internal storage s…
baitcode Jan 6, 2025
80cf041
simplified more. better naming
baitcode Jan 6, 2025
f507631
clean up bitshifts from tests
baitcode Jan 6, 2025
164f64f
overflow checks
baitcode Jan 6, 2025
6e931c6
old tests did not respect overflow. had to create new ones.
baitcode Jan 7, 2025
fafba15
Debugging, cleaning up, fixing missed issues
baitcode Jan 8, 2025
b55b320
final fix
baitcode Jan 9, 2025
a28971c
final debugging
baitcode Jan 9, 2025
aa07801
cleanup
baitcode Jan 9, 2025
61f085c
cleanup
baitcode Jan 9, 2025
f52fff1
removing UFixedPoint124x128 struct
baitcode Jan 10, 2025
4cc807e
BugFix for a case when fetl252 is overflown
baitcode Jan 10, 2025
7e35137
remove fixed point lib
baitcode Jan 10, 2025
815d539
small fix
baitcode Jan 10, 2025
284ae39
lost deletions
baitcode Jan 10, 2025
c6102a1
Review fixes
baitcode Jan 13, 2025
e3067ab
scarb fmt
baitcode Jan 13, 2025
f622f34
Review fixes
baitcode Jan 14, 2025
56ec424
scarb format
baitcode Jan 14, 2025
974c68d
* remove MAX_FP related code
baitcode Jan 23, 2025
955ef24
simplified and renamed get_seconds_per_total_staked_sum_at
baitcode Jan 31, 2025
83eb22d
uncomment tests + scarb format
baitcode Jan 31, 2025
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
.env
.vscode
.starkli
.DS_Store
#######
Scarb
#######
Expand Down
4 changes: 4 additions & 0 deletions src/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ pub mod governor;
mod governor_test;

pub mod staker;

pub mod staker_log;
#[cfg(test)]
pub mod staker_log_test;
#[cfg(test)]
mod staker_test;

Expand Down
152 changes: 149 additions & 3 deletions src/staker.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,48 @@ pub trait IStaker<TContractState> {
fn get_average_delegated_over_last(
self: @TContractState, delegate: ContractAddress, period: u64,
) -> u128;

// Calculates snapshot for seconds_per_total_staked_sum (val) at given timestamp (ts).
// If timestamp if before first record, returns 0.
// If timestamp is between records, calculates Δt = (ts - record.ts) where record is
// first record in log before timestamp, then calculates total amount using the
// weighted_total_staked diff diveded by time diff.
// If timestamp is after last record, calculates Δt = (ts - last_record.ts) and
// takes total_staked from storage and adds Δt / total_staked to accumulator.
// In case total_staked is 0 this method turns is to 1 to simplify calculations
// TODO: this should be a part of StakingLog
fn get_seconds_per_total_staked_sum_at(self: @TContractState, timestamp: u64) -> u256;

// Calculates snapshot for time_weighted_total_staked (val) at given timestamp (ts).
// Does pretty much the same as `get_seconds_per_total_staked_sum_at` but simpler due to
// absence of FP division.
fn get_time_weighted_total_staked_sum_at(self: @TContractState, timestamp: u64) -> u256;

fn get_total_staked_at(self: @TContractState, timestamp: u64) -> u128;

fn get_average_total_staked_over_period(self: @TContractState, start: u64, end: u64) -> u128;

fn get_user_share_of_total_staked_over_period(
self: @TContractState, staked: u128, start: u64, end: u64,
) -> u128;
}

#[starknet::contract]
pub mod Staker {
use core::num::traits::zero::{Zero};
use crate::staker_log::{LogOperations, StakingLog};
use governance::interfaces::erc20::{IERC20Dispatcher, IERC20DispatcherTrait};
use starknet::storage::VecTrait;
use starknet::storage::{
Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePathEntry,
StoragePointerReadAccess, StoragePointerWriteAccess,
};

use starknet::{
get_block_timestamp, get_caller_address, get_contract_address,
ContractAddress, get_block_timestamp, get_caller_address, get_contract_address,
storage_access::{StorePacking},
};
use super::{ContractAddress, IStaker};

use super::{IStaker};

#[derive(Copy, Drop, PartialEq, Debug)]
pub struct DelegatedSnapshot {
Expand All @@ -78,6 +104,7 @@ pub mod Staker {
const TWO_POW_64: u128 = 0x10000000000000000;
const TWO_POW_192: u256 = 0x1000000000000000000000000000000000000000000000000;
const TWO_POW_192_DIVISOR: NonZero<u256> = 0x1000000000000000000000000000000000000000000000000;
const TWO_POW_127: u128 = 0x80000000000000000000000000000000_u128;

pub(crate) impl DelegatedSnapshotStorePacking of StorePacking<DelegatedSnapshot, felt252> {
fn pack(value: DelegatedSnapshot) -> felt252 {
Expand All @@ -104,6 +131,8 @@ pub mod Staker {
amount_delegated: Map<ContractAddress, u128>,
delegated_cumulative_num_snapshots: Map<ContractAddress, u64>,
delegated_cumulative_snapshot: Map<ContractAddress, Map<u64, DelegatedSnapshot>>,
total_staked: u128,
staking_log: StakingLog,
}

#[constructor]
Expand Down Expand Up @@ -253,6 +282,12 @@ pub mod Staker {
self
.amount_delegated
.write(delegate, self.insert_snapshot(delegate, get_block_timestamp()) + amount);

let current_total_staked = self.total_staked.read();

self.total_staked.write(current_total_staked + amount);
self.staking_log.log_change(amount, current_total_staked);

self.emit(Staked { from, delegate, amount });
}

Expand Down Expand Up @@ -280,6 +315,11 @@ pub mod Staker {
.amount_delegated
.write(delegate, self.insert_snapshot(delegate, get_block_timestamp()) - amount);
assert(self.token.read().transfer(recipient, amount.into()), 'TRANSFER_FAILED');

let total_staked = self.total_staked.read();
self.total_staked.write(total_staked - amount);
self.staking_log.log_change(amount, total_staked);

self.emit(Withdrawn { from, delegate, to: recipient, amount });
}

Expand Down Expand Up @@ -333,5 +373,111 @@ pub mod Staker {
let now = get_block_timestamp();
self.get_average_delegated(delegate, now - period, now)
}

// Check interface for detailed description.
fn get_seconds_per_total_staked_sum_at(self: @ContractState, timestamp: u64) -> u256 {
let record = self.staking_log.find_record_on_or_before_timestamp(timestamp);

if let Option::Some((record, idx)) = record {
let total_staked = if (idx == self.staking_log.len() - 1) {
// if last record found
self.total_staked.read()
} else {
// This helps to avoid couple of FP divisions.
let next_record = self.staking_log.at(idx + 1).read();
let time_weighted_total_staked_sum_diff = next_record
.time_weighted_total_staked_sum
- record.time_weighted_total_staked_sum;
let timestamp_diff = next_record.timestamp - record.timestamp;
(time_weighted_total_staked_sum_diff / timestamp_diff.into())
.try_into()
.unwrap()
};

let seconds_diff = timestamp - record.timestamp;

let seconds_per_total_staked: u256 = if total_staked == 0 {
seconds_diff.into() // as if total_staked is 1
} else {
// Divide u64 by u128
u256 { low: 0, high: seconds_diff.into() } / total_staked.into()
};

// Sum fixed posits
return record.seconds_per_total_staked_sum + seconds_per_total_staked;
}

return 0_u256;
}

fn get_time_weighted_total_staked_sum_at(self: @ContractState, timestamp: u64) -> u256 {
let record = self.staking_log.find_record_on_or_before_timestamp(timestamp);

if let Option::Some((record, idx)) = record {
let total_staked = if (idx == self.staking_log.len() - 1) {
// if last rescord found
self.total_staked.read()
} else {
let next_record = self.staking_log.at(idx + 1).read();
let time_weighted_total_staked_sum_diff = next_record
.time_weighted_total_staked_sum
- record.time_weighted_total_staked_sum;
let timestamp_diff = next_record.timestamp - record.timestamp;
(time_weighted_total_staked_sum_diff / timestamp_diff.into())
.try_into()
.unwrap()
};

let seconds_diff = timestamp - record.timestamp;
let time_weighted_total_staked: u256 = total_staked.into() * seconds_diff.into();

return record.time_weighted_total_staked_sum + time_weighted_total_staked;
}

return 0_u256;
}

fn get_total_staked_at(self: @ContractState, timestamp: u64) -> u128 {
let record = self.staking_log.find_record_on_or_before_timestamp(timestamp);
if let Option::Some((record, idx)) = record {
if (idx == self.staking_log.len() - 1) {
self.total_staked.read()
} else {
let next_record = self.staking_log.at(idx + 1).read();
let time_weighted_total_staked_sum_diff = next_record
.time_weighted_total_staked_sum
- record.time_weighted_total_staked_sum;
let timestamp_diff = next_record.timestamp - record.timestamp;
(time_weighted_total_staked_sum_diff / timestamp_diff.into())
.try_into()
.unwrap()
}
} else {
0_u128
}
}

fn get_average_total_staked_over_period(
self: @ContractState, start: u64, end: u64,
) -> u128 {
assert(end > start, 'ORDER');

let start_snapshot = self.get_time_weighted_total_staked_sum_at(start);
let end_snapshot = self.get_time_weighted_total_staked_sum_at(end);
let period_length = end - start;

((end_snapshot - start_snapshot) / period_length.into()).try_into().unwrap()
}

fn get_user_share_of_total_staked_over_period(
self: @ContractState, staked: u128, start: u64, end: u64,
) -> u128 {
assert(end > start, 'ORDER');

let start_snapshot = self.get_seconds_per_total_staked_sum_at(start);
let end_snapshot = self.get_seconds_per_total_staked_sum_at(end);

staked * ((end_snapshot - start_snapshot) * 100).high / (end - start).into()
}
}
}
Loading