-
Notifications
You must be signed in to change notification settings - Fork 33
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
base: main
Are you sure you want to change the base?
Feat staker v2 no fp and subpointers #67
Conversation
…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 staked seconds calculation * added seconds per total staked test and added logic for calculation of that value when nothing is staked
…ilerplate. Implemented custom packing and custom data SubPointers.
…ize. Added convienience method for u64 / u128 case.
finished subpointers for staking_log. Subpointer always read whole felt252 from memory and converts it. So I had to change approach for packing.
inline everything remove storage read optimisations
src/staker.cairo
Outdated
@@ -78,6 +86,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 HALF: u128 = 0x80000000000000000000000000000000_u128; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
TWO_POW_127
is more descriptive
src/staker.cairo
Outdated
@@ -253,6 +264,12 @@ pub mod Staker { | |||
self | |||
.amount_delegated | |||
.write(delegate, self.insert_snapshot(delegate, get_block_timestamp()) + amount); | |||
|
|||
let total_staked = self.total_staked.read(); | |||
assert(total_staked + amount >= total_staked, 'BAD AMOUNT'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this supposed to be an overflow check? this cannot happen ever because addition is checked by default
src/staker_test.cairo
Outdated
assert_eq!(value.high, integer); | ||
assert_eq!(value.low, fractional); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
think it would be more readable if you just wrote assert_eq!(value, u256 {high: integer, low:fractional})
and also inlined that into the below test
src/staker_log.cairo
Outdated
pub(crate) timestamp: u64, | ||
|
||
// Only 128+32=160 bits are used | ||
// TODO: add validation checks |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wdym?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh. I've already did that. Forgot to remove. Thank you for catching.
src/staker_log.cairo
Outdated
const TWO_POW_32: u64 = 0x100000000_u64; | ||
const MASK_32_BITS: u128 = 0x100000000_u128 - 1; | ||
const TWO_POW_160: u256 = 0x10000000000000000000000000000000000000000; | ||
pub const MAX_FP: u128 = 0x8000000000000110000000000000000_u128; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what is this value?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maximum value that can be store in felt252 is MAX_V = 0x800000000000011000000000000000000000000000000000000000000000000.
MAX_FP = MAX_V >> 128 (maximum value of highest 123 bit of felt252).
src/staker.cairo
Outdated
} | ||
}; | ||
|
||
let seconds_diff = (timestamp - log_record.timestamp) / 1000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
block timestamps are in seconds arent they? why are we dividing by 1k
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is absolutely right. I can't believe tests did not catch it. Aaaa found it. Turned out I saved ms in record. Thank you for catching this. Need to add think on how to test storage.
* uniform get_block_timestamp usage * store log_record timestamp in seconds * inline test assert for fp value * rename HALF to TWO_POW_127
src/staker.cairo
Outdated
fn get_cumulative_seconds_per_total_staked_at( | ||
self: @ContractState, timestamp: u64, | ||
) -> u256 { | ||
let timestamp_seconds = timestamp / 1000; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
timestamp should not be expressed in milliseconds when everything onchain is in seconds
because of this division, there is no difference between e.g. 4000
and 4999
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh my! I though on-chain everything is in ms.
src/staker.cairo
Outdated
// substract fixed point values | ||
let divisor = next_log_record.cumulative_seconds_per_total_staked | ||
- log_record.cumulative_seconds_per_total_staked; | ||
assert(divisor.high < MAX_FP, 'FP_OVERFLOW'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
can this actually happen?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, in my practice things tend to happen. Assertions are also a good way of documenting code. But I see your point. Will remove.
src/staker.cairo
Outdated
self.total_staked.write(total_staked + amount); | ||
self.staking_log.log_change(amount, total_staked); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is it required to write total staked before calling log_change? if so, it's not locally obvious from the function names and arguments
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it is not. I'll think on how to rename those.
src/staker.cairo
Outdated
) -> u256 { | ||
if let Option::Some((log_record, idx)) = self | ||
.staking_log | ||
.find_in_change_log(timestamp) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's not finding that specific timestamp, maybe rename to find_previous.. or something
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good point.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Changed to find_change_log_on_or_before_timestamp
.
src/staker.cairo
Outdated
- log_record.cumulative_seconds_per_total_staked; | ||
|
||
if divisor.is_zero() { | ||
return 0_u64.into(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
in hindsight, we should actually treat total staked of 0
as if it's 1
with regards to this accumulator
that allows us to still compute the harmonic mean for periods where total staked is zero (it will not be perfectly accurate, but ok for it to be off by one)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it also means you don't have to worry about this value being 0 between two indices
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I refactored that bit. I calculate total_staked using total_staked * seconds passed. It's much simpler.
src/staker.cairo
Outdated
|
||
let seconds_diff = timestamp - log_record.timestamp; | ||
|
||
let staked_seconds: u256 = if total_staked == 0 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
variable name is confusing
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
changed to seconds_per_total_staked
src/staker.cairo
Outdated
let seconds_diff = timestamp - log_record.timestamp; | ||
|
||
let staked_seconds: u256 = if total_staked == 0 { | ||
0_u256 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
here you can just use the value 1 for total_staked
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@moodysalem
Do I understand you correctly, that this is a technic to cap the infinity? meaning that we probably wont really calculate stake shares over periods like that and those will be negated when snapshots are subtracted?
src/staker.cairo
Outdated
|
||
// Sum fixed posits | ||
let result = log_record.cumulative_seconds_per_total_staked + staked_seconds; | ||
assert(result.high < MAX_FP, 'FP_OVERFLOW'); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why are you doing this assertion? if it's because you're storing a u256 as a felt252 in the store packing, then it should be asserted where you're actually doing the storage
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had same though, but decided to fail as early as possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like in storage packing it'll fail anyway during conversion. Removing.
src/staker.cairo
Outdated
@@ -52,22 +52,28 @@ pub trait IStaker<TContractState> { | |||
fn get_average_delegated_over_last( | |||
self: @TContractState, delegate: ContractAddress, period: u64, | |||
) -> u128; | |||
|
|||
// Gets the cumulative staked amount * per second staked for the given timestamp and account. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
is this comment accurate? there were 2 diff quantities we wanted to be able to compute:
average total staked for a period of time
- requires storing
seconds passed * total_staked
, a 160+ bit wide value - can be computed by taking
(snapshotB-snapshotA) / periodLength
- can be used to calculate the total voting weight at the time a proposal's voting starts, so functionality could be added for proposal to be immediately passed if it reaches quorum with majority of total staked in favor
user's share of total stake over a period
- requires storing
seconds passed / total staked
, a 160+ bit wide value - computed via
stakedAmount * snapshotStakeEnd - snapshotStakeStart
- this also lets you compute the harmonic mean of total staked over a period, which could be useful for dynamically adjusting quorum
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
No it's not. It's outdated. I've updated it. This method calculates shapshots for "user's share of total stake over a period"
Fixed. That one. Will add other snapshots soon. Then add methods for 2 diff quantities.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
computed via stakedAmount * snapshotStakeEnd - snapshotStakeStart
Formula for user's share
seems incorrect:
- the dimension of the result is
seconds
. which raises suspicion. - brackets are missing.
I think that correct one is: stakedAmount * (snapshotStakeEnd - snapshotStakeStart) / (snapshotTimestampEnd-snapshotTimestampStart)
Can you please confirm that this is correct?
* find_in_change_log -> find_change_log_on_or_before_timestamp * log_change argument rename
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
@moodysalem I decided not to wait your replies and implemented. There are tests missing for |
@moodysalem |
Same as in staker v2 but way less code