-
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
fix a couple nits with the interfaces and code #37
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -43,10 +43,15 @@ pub trait IGovernor<TContractState> { | |
// Vote on the given proposal. | ||
fn vote(ref self: TContractState, id: felt252, yea: bool); | ||
|
||
// Cancel the given proposal. The proposer may cancel the proposal at any time during before or during the voting period. | ||
// Cancellation can happen by any address if the average voting weight is below the proposal_creation_threshold. | ||
// Cancel the proposal with the given ID. Same as #cancel_at_timestamp, but uses the current timestamp for computing the voting weight. | ||
fn cancel(ref self: TContractState, id: felt252); | ||
|
||
// Cancel the proposal with the given ID. The proposal may be canceled at any time before it is executed. | ||
// There are two ways the proposal cancellation can be authorized: | ||
// - The proposer can cancel the proposal | ||
// - Anyone can cancel if the average voting weight of the proposer was below the proposal_creation_threshold during the voting period (at the given breach_timestamp) | ||
fn cancel_at_timestamp(ref self: TContractState, id: felt252, breach_timestamp: u64); | ||
|
||
// Execute the given proposal. | ||
fn execute(ref self: TContractState, call: Call) -> Span<felt252>; | ||
|
||
|
@@ -100,6 +105,7 @@ pub mod Governor { | |
#[derive(starknet::Event, Drop)] | ||
pub struct Canceled { | ||
pub id: felt252, | ||
pub breach_timestamp: u64, | ||
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. previously we knew it was always the block in which the event was emitted since it's no longer true, we need to emit it from the event to know |
||
} | ||
|
||
#[derive(starknet::Event, Drop)] | ||
|
@@ -150,7 +156,8 @@ pub mod Governor { | |
if latest_proposal_id.is_non_zero() { | ||
let latest_proposal_state = self.get_proposal(latest_proposal_id).execution_state; | ||
|
||
if (latest_proposal_state.canceled.is_zero()) { | ||
// if the proposal is not canceled, check that the voting for that proposal has ended | ||
if latest_proposal_state.canceled.is_zero() { | ||
assert( | ||
latest_proposal_state.created | ||
+ config.voting_start_delay | ||
|
@@ -236,54 +243,60 @@ pub mod Governor { | |
self.proposals.write(id, proposal); | ||
self.has_voted.write((voter, id), true); | ||
|
||
self.emit(Voted { id, voter, weight, yea, }); | ||
self.emit(Voted { id, voter, weight, yea }); | ||
} | ||
|
||
|
||
fn cancel(ref self: ContractState, id: felt252) { | ||
self.cancel_at_timestamp(id, get_block_timestamp()) | ||
} | ||
|
||
fn cancel_at_timestamp(ref self: ContractState, id: felt252, breach_timestamp: u64) { | ||
let config = self.config.read(); | ||
let voting_token = self.staker.read(); | ||
let staker = self.staker.read(); | ||
let mut proposal = self.proposals.read(id); | ||
|
||
assert(proposal.proposer.is_non_zero(), 'DOES_NOT_EXIST'); | ||
|
||
if (proposal.proposer != get_caller_address()) { | ||
// if at any point the average voting weight is below the proposal_creation_threshold for the proposer, it can be canceled | ||
assert(proposal.execution_state.canceled.is_zero(), 'ALREADY_CANCELED'); | ||
assert(proposal.execution_state.executed.is_zero(), 'ALREADY_EXECUTED'); | ||
assert(breach_timestamp >= proposal.execution_state.created, 'PROPOSAL_NOT_CREATED'); | ||
|
||
assert( | ||
breach_timestamp < (proposal.execution_state.created | ||
+ config.voting_start_delay | ||
+ config.voting_period), | ||
'VOTING_ENDED' | ||
); | ||
Comment on lines
+265
to
+270
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. note that the proposal can still be canceled after voting ends, but before it is executed |
||
|
||
// iff the proposer is not calling this we need to check the voting weight | ||
if proposal.proposer != get_caller_address() { | ||
// if at the given timestamp (during the voting period), | ||
// the average voting weight is below the proposal_creation_threshold for the proposer, it can be canceled | ||
assert( | ||
voting_token | ||
.get_average_delegated_over_last( | ||
staker | ||
.get_average_delegated( | ||
delegate: proposal.proposer, | ||
period: config.voting_weight_smoothing_duration | ||
start: breach_timestamp - config.voting_weight_smoothing_duration, | ||
end: breach_timestamp | ||
) < config | ||
.proposal_creation_threshold, | ||
'THRESHOLD_NOT_BREACHED' | ||
); | ||
} | ||
|
||
let timestamp_current = get_block_timestamp(); | ||
|
||
assert( | ||
timestamp_current < (proposal.execution_state.created | ||
+ config.voting_start_delay | ||
+ config.voting_period), | ||
'VOTING_ENDED' | ||
); | ||
|
||
// we know it's not executed since we check voting has not ended | ||
proposal | ||
.execution_state = | ||
ExecutionState { | ||
created: proposal.execution_state.created, | ||
// we asserted that it is not already executed | ||
executed: 0, | ||
canceled: timestamp_current | ||
canceled: get_block_timestamp() | ||
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. still, we use the current timestamp for the time at which it was canceled |
||
}; | ||
|
||
self.proposals.write(id, proposal); | ||
|
||
// allows the proposer to create a new proposal | ||
self.latest_proposal_by_proposer.write(proposal.proposer, Zero::zero()); | ||
|
||
self.emit(Canceled { id }); | ||
self.emit(Canceled { id, breach_timestamp }); | ||
} | ||
|
||
fn execute(ref self: ContractState, call: Call) -> Span<felt252> { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -13,8 +13,14 @@ pub trait IStaker<TContractState> { | |
// Transfer the approved amount of token from the caller into this contract and delegates it to the given address | ||
fn stake(ref self: TContractState, delegate: ContractAddress); | ||
|
||
// Withdraws the delegated tokens from the contract to the given recipient address | ||
fn withdraw( | ||
// Transfer the specified amount of token from the caller into this contract and delegates the voting weight to the specified delegate | ||
fn stake_amount(ref self: TContractState, delegate: ContractAddress, amount: u128); | ||
|
||
// Unstakes and withdraws all of the tokens delegated by the sender to the delegate from the contract to the given recipient address | ||
fn withdraw(ref self: TContractState, delegate: ContractAddress, recipient: ContractAddress); | ||
|
||
// Unstakes and withdraws the specified amount of tokens delegated by the sender to the delegate from the contract to the given recipient address | ||
fn withdraw_amount( | ||
Comment on lines
+16
to
+23
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. these improvements to the interface allow you to approve once and then stake for multiple delegates, or move a partial delegation |
||
ref self: TContractState, | ||
delegate: ContractAddress, | ||
recipient: ContractAddress, | ||
|
@@ -214,27 +220,45 @@ pub mod Staker { | |
} | ||
|
||
fn stake(ref self: ContractState, delegate: ContractAddress) { | ||
self | ||
.stake_amount( | ||
delegate, | ||
self | ||
.token | ||
.read() | ||
.allowance(get_caller_address(), get_contract_address()) | ||
.try_into() | ||
.expect('ALLOWANCE_OVERFLOW') | ||
Comment on lines
+226
to
+231
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. if called without an amount argument, stakes the entire approval |
||
); | ||
} | ||
|
||
fn stake_amount(ref self: ContractState, delegate: ContractAddress, amount: u128) { | ||
let from = get_caller_address(); | ||
let this_address = get_contract_address(); | ||
let token = self.token.read(); | ||
let amount = token.allowance(from, this_address); | ||
|
||
let amount_small: u128 = amount.try_into().expect('ALLOWANCE_OVERFLOW'); | ||
assert( | ||
token.transferFrom(from, get_contract_address(), amount), 'TRANSFER_FROM_FAILED' | ||
token.transferFrom(from, get_contract_address(), amount.into()), | ||
'TRANSFER_FROM_FAILED' | ||
); | ||
|
||
let key = (from, delegate); | ||
self.staked.write((from, delegate), amount_small + self.staked.read(key)); | ||
self.staked.write((from, delegate), amount + self.staked.read(key)); | ||
self | ||
.amount_delegated | ||
.write( | ||
delegate, self.insert_snapshot(delegate, get_block_timestamp()) + amount_small | ||
); | ||
self.emit(Staked { from, delegate, amount: amount_small }); | ||
.write(delegate, self.insert_snapshot(delegate, get_block_timestamp()) + amount); | ||
self.emit(Staked { from, delegate, amount }); | ||
} | ||
|
||
fn withdraw( | ||
ref self: ContractState, delegate: ContractAddress, recipient: ContractAddress | ||
) { | ||
self | ||
.withdraw_amount( | ||
delegate, recipient, self.staked.read((get_caller_address(), delegate)) | ||
) | ||
} | ||
|
||
fn withdraw_amount( | ||
ref self: ContractState, | ||
delegate: ContractAddress, | ||
recipient: ContractAddress, | ||
|
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.
added this method so that a proposal can be canceled by anyone if the proposer's voting weight dips below the threshold at any time
t
between when the proposal is created and when the proposal ends