-
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
timelock optimization #24
Merged
Merged
Changes from 9 commits
Commits
Show all changes
11 commits
Select commit
Hold shift + click to select a range
46a3273
timelock optimization
Flydexo f71e04a
storepacking trait
Flydexo 9d0212d
execution_window
Flydexo 485949c
64 bits timestamps
Flydexo a0f4542
optimized & fixed tests
Flydexo 75c69b6
tuple store & factory
Flydexo b22e081
resolves comments
Flydexo 13c9cab
separated file, removed CANCELED, inline
Flydexo 694ea04
rm canceled checl
Flydexo 1d3b6fe
Merge branch 'timestamps'
Flydexo c570a51
fix conflicts
Flydexo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
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 |
---|---|---|
|
@@ -295,7 +295,7 @@ mod Governor { | |
let data = call.execute(); | ||
|
||
self.emit(Executed { id, }); | ||
|
||
data | ||
} | ||
|
||
|
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 |
---|---|---|
@@ -1,6 +1,47 @@ | ||
use core::option::OptionTrait; | ||
use core::traits::TryInto; | ||
use core::result::ResultTrait; | ||
use starknet::{ContractAddress}; | ||
use starknet::account::{Call}; | ||
use starknet::storage_access::{StorePacking}; | ||
use governance::utils::timestamps::{ThreeU64TupleStorePacking, TwoU64TupleStorePacking}; | ||
|
||
#[derive(Copy, Drop, Serde)] | ||
struct ExecutionState { | ||
started: u64, | ||
executed: u64, | ||
canceled: u64 | ||
} | ||
|
||
impl ExecutionStateStorePacking of StorePacking<ExecutionState, felt252> { | ||
#[inline(always)] | ||
fn pack(value: ExecutionState) -> felt252 { | ||
ThreeU64TupleStorePacking::pack((value.started, value.executed, value.canceled)) | ||
} | ||
#[inline(always)] | ||
fn unpack(value: felt252) -> ExecutionState { | ||
let (started, executed, canceled) = ThreeU64TupleStorePacking::unpack(value); | ||
ExecutionState { started, executed, canceled } | ||
} | ||
} | ||
|
||
#[derive(Copy, Drop, Serde)] | ||
struct TimelockConfig { | ||
delay: u64, | ||
window: u64, | ||
} | ||
|
||
impl TimelockConfigStorePacking of StorePacking<TimelockConfig, u128> { | ||
#[inline(always)] | ||
fn pack(value: TimelockConfig) -> u128 { | ||
TwoU64TupleStorePacking::pack((value.delay, value.window)) | ||
} | ||
#[inline(always)] | ||
fn unpack(value: u128) -> TimelockConfig { | ||
let (delay, window) = TwoU64TupleStorePacking::unpack(value); | ||
TimelockConfig { delay, window } | ||
} | ||
} | ||
|
||
#[starknet::interface] | ||
trait ITimelock<TStorage> { | ||
|
@@ -14,23 +55,32 @@ trait ITimelock<TStorage> { | |
fn execute(ref self: TStorage, calls: Span<Call>) -> Array<Span<felt252>>; | ||
|
||
// Return the execution window, i.e. the start and end timestamp in which the call can be executed | ||
fn get_execution_window(self: @TStorage, id: felt252) -> (u64, u64); | ||
fn get_execution_window(self: @TStorage, id: felt252) -> ExecutionWindow; | ||
|
||
// Get the current owner | ||
fn get_owner(self: @TStorage) -> ContractAddress; | ||
|
||
// Returns the delay and the window for call execution | ||
fn get_configuration(self: @TStorage) -> (u64, u64); | ||
fn get_configuration(self: @TStorage) -> TimelockConfig; | ||
|
||
// Transfer ownership, i.e. the address that can queue and cancel calls. This must be self-called via #queue. | ||
fn transfer(ref self: TStorage, to: ContractAddress); | ||
// Configure the delay and the window for call execution. This must be self-called via #queue. | ||
fn configure(ref self: TStorage, delay: u64, window: u64); | ||
fn configure(ref self: TStorage, config: TimelockConfig); | ||
} | ||
|
||
#[derive(Copy, Drop, Serde)] | ||
struct ExecutionWindow { | ||
earliest: u64, | ||
latest: u64 | ||
} | ||
|
||
#[starknet::contract] | ||
mod Timelock { | ||
use super::{ITimelock, ContractAddress, Call}; | ||
use super::{ | ||
ITimelock, ContractAddress, Call, TimelockConfig, ExecutionState, | ||
TimelockConfigStorePacking, ExecutionStateStorePacking, ExecutionWindow | ||
}; | ||
use governance::call_trait::{CallTrait, HashCall}; | ||
use hash::{LegacyHash}; | ||
use array::{ArrayTrait, SpanTrait}; | ||
|
@@ -69,18 +119,15 @@ mod Timelock { | |
#[storage] | ||
struct Storage { | ||
owner: ContractAddress, | ||
delay: u64, | ||
window: u64, | ||
execution_started: LegacyMap<felt252, u64>, | ||
executed: LegacyMap<felt252, u64>, | ||
canceled: LegacyMap<felt252, u64>, | ||
config: TimelockConfig, | ||
// started_executed_canceled | ||
execution_state: LegacyMap<felt252, ExecutionState>, | ||
} | ||
|
||
#[constructor] | ||
fn constructor(ref self: ContractState, owner: ContractAddress, delay: u64, window: u64) { | ||
fn constructor(ref self: ContractState, owner: ContractAddress, config: TimelockConfig) { | ||
self.owner.write(owner); | ||
self.delay.write(delay); | ||
self.window.write(window); | ||
self.config.write(config); | ||
} | ||
|
||
// Take a list of calls and convert it to a unique identifier for the execution | ||
|
@@ -113,10 +160,15 @@ mod Timelock { | |
self.check_owner(); | ||
|
||
let id = to_id(calls); | ||
let execution_state = self.execution_state.read(id); | ||
|
||
assert(self.execution_started.read(id).is_zero(), 'ALREADY_QUEUED'); | ||
assert(execution_state.started.is_zero(), 'ALREADY_QUEUED'); | ||
|
||
self.execution_started.write(id, get_block_timestamp()); | ||
self | ||
.execution_state | ||
.write( | ||
id, ExecutionState { started: get_block_timestamp(), executed: 0, canceled: 0 } | ||
); | ||
|
||
self.emit(Queued { id, calls, }); | ||
|
||
|
@@ -125,26 +177,47 @@ mod Timelock { | |
|
||
fn cancel(ref self: ContractState, id: felt252) { | ||
self.check_owner(); | ||
assert(self.execution_started.read(id).is_non_zero(), 'DOES_NOT_EXIST'); | ||
assert(self.executed.read(id).is_zero(), 'ALREADY_EXECUTED'); | ||
|
||
self.execution_started.write(id, 0); | ||
let execution_state = self.execution_state.read(id); | ||
assert(execution_state.started.is_non_zero(), 'DOES_NOT_EXIST'); | ||
assert(execution_state.executed.is_zero(), 'ALREADY_EXECUTED'); | ||
|
||
self | ||
.execution_state | ||
.write( | ||
id, | ||
ExecutionState { | ||
started: 0, | ||
executed: execution_state.executed, | ||
canceled: execution_state.canceled | ||
} | ||
); | ||
|
||
self.emit(Canceled { id, }); | ||
} | ||
|
||
fn execute(ref self: ContractState, mut calls: Span<Call>) -> Array<Span<felt252>> { | ||
let id = to_id(calls); | ||
|
||
assert(self.executed.read(id).is_zero(), 'ALREADY_EXECUTED'); | ||
let execution_state = self.execution_state.read(id); | ||
|
||
let (earliest, latest) = self.get_execution_window(id); | ||
assert(execution_state.executed.is_zero(), 'ALREADY_EXECUTED'); | ||
|
||
let execution_window = self.get_execution_window(id); | ||
let time_current = get_block_timestamp(); | ||
|
||
assert(time_current >= earliest, 'TOO_EARLY'); | ||
assert(time_current < latest, 'TOO_LATE'); | ||
assert(time_current >= execution_window.earliest, 'TOO_EARLY'); | ||
assert(time_current < execution_window.latest, 'TOO_LATE'); | ||
|
||
self.executed.write(id, time_current); | ||
self | ||
.execution_state | ||
.write( | ||
id, | ||
ExecutionState { | ||
started: execution_state.started, | ||
executed: time_current, | ||
canceled: execution_state.canceled | ||
} | ||
); | ||
|
||
let mut results: Array<Span<felt252>> = ArrayTrait::new(); | ||
|
||
|
@@ -160,26 +233,27 @@ mod Timelock { | |
results | ||
} | ||
|
||
fn get_execution_window(self: @ContractState, id: felt252) -> (u64, u64) { | ||
let start_time = self.execution_started.read(id); | ||
fn get_execution_window(self: @ContractState, id: felt252) -> ExecutionWindow { | ||
let start_time = self.execution_state.read(id).started; | ||
|
||
// this is how we prevent the 0 timestamp from being considered valid | ||
assert(start_time != 0, 'DOES_NOT_EXIST'); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hmmm maybe we could just check that canceled is nonzero as well and remove the other logic changes. sorry, i realize this is out of scope for the initial pr. |
||
|
||
let (delay, window) = (self.get_configuration()); | ||
let configuration = (self.get_configuration()); | ||
|
||
let earliest = start_time + configuration.delay; | ||
|
||
let earliest = start_time + delay; | ||
let latest = earliest + window; | ||
let latest = earliest + configuration.window; | ||
|
||
(earliest, latest) | ||
ExecutionWindow { earliest, latest } | ||
} | ||
|
||
fn get_owner(self: @ContractState) -> ContractAddress { | ||
self.owner.read() | ||
} | ||
|
||
fn get_configuration(self: @ContractState) -> (u64, u64) { | ||
(self.delay.read(), self.window.read()) | ||
fn get_configuration(self: @ContractState) -> TimelockConfig { | ||
self.config.read() | ||
} | ||
|
||
fn transfer(ref self: ContractState, to: ContractAddress) { | ||
|
@@ -188,11 +262,10 @@ mod Timelock { | |
self.owner.write(to); | ||
} | ||
|
||
fn configure(ref self: ContractState, delay: u64, window: u64) { | ||
fn configure(ref self: ContractState, config: TimelockConfig) { | ||
self.check_self_call(); | ||
|
||
self.delay.write(delay); | ||
self.window.write(window); | ||
self.config.write(config); | ||
} | ||
} | ||
} |
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 @@ | ||
mod timestamps; |
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,31 @@ | ||
use core::integer::{u128_to_felt252, u64_try_from_felt252, u128_safe_divmod}; | ||
use starknet::storage_access::{StorePacking}; | ||
|
||
const TWO_POW_64: u128 = 0x10000000000000000; | ||
|
||
impl ThreeU64TupleStorePacking of StorePacking<(u64, u64, u64), felt252> { | ||
#[inline(always)] | ||
fn pack(value: (u64, u64, u64)) -> felt252 { | ||
let (a, b, c) = value; | ||
u256 { low: TwoU64TupleStorePacking::pack((a, b)), high: c.into() }.try_into().unwrap() | ||
} | ||
#[inline(always)] | ||
fn unpack(value: felt252) -> (u64, u64, u64) { | ||
let u256_value: u256 = value.into(); | ||
let (a, b) = TwoU64TupleStorePacking::unpack(u256_value.low); | ||
(a, b, (u256_value.high).try_into().unwrap()) | ||
} | ||
} | ||
|
||
impl TwoU64TupleStorePacking of StorePacking<(u64, u64), u128> { | ||
#[inline(always)] | ||
fn pack(value: (u64, u64)) -> u128 { | ||
let (a, b) = value; | ||
a.into() + b.into() * TWO_POW_64 | ||
} | ||
#[inline(always)] | ||
fn unpack(value: u128) -> (u64, u64) { | ||
let (q, r) = u128_safe_divmod(value, TWO_POW_64.try_into().unwrap()); | ||
(r.try_into().unwrap(), q.try_into().unwrap()) | ||
} | ||
} |
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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 causes a second read of the same slot
not super costly, but wasteful
better to revert if already executed or canceled from this function, since there is no execution window