forked from MystenLabs/sui
-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[MoveLib] Initial checkin of the governance contract (MystenLabs#1436)
* [Move] Add Barebone governance contract * Add treasury and initial coins * Address feedback * Add tests * Address feedback * Change quorum threshold pct to stake threshold
- Loading branch information
Showing
7 changed files
with
828 additions
and
11 deletions.
There are no files selected for viewing
55 changes: 55 additions & 0 deletions
55
sui_programmability/framework/sources/Governance/Genesis.move
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,55 @@ | ||
// Copyright (c) 2022, Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
module Sui::Genesis { | ||
use Std::Vector; | ||
|
||
use Sui::Coin; | ||
use Sui::SUI; | ||
use Sui::SuiSystem; | ||
use Sui::TxContext::TxContext; | ||
use Sui::Validator; | ||
|
||
/// The initial amount of SUI locked in the storage fund. | ||
/// 10^14, an arbitrary number. | ||
const INIT_STORAGE_FUND: u64 = 100000000000000; | ||
|
||
/// Initial value of the lower-bound on the amount of stake required to become a validator. | ||
const INIT_MIN_VALIDATOR_STAKE: u64 = 100000000000000; | ||
|
||
/// Initial value of the upper-bound on the amount of stake allowed to become a validator. | ||
const INIT_MAX_VALIDATOR_STAKE: u64 = 100000000000000000; | ||
|
||
/// Initial value of the upper-bound on the number of validators. | ||
const INIT_MAX_VALIDATOR_COUNT: u64 = 100; | ||
|
||
/// Basic information of Validator1, as an example, all dummy values. | ||
const VALIDATOR1_SUI_ADDRESS: address = @0x1234; | ||
const VALIDATOR1_NAME: vector<u8> = b"Validator1"; | ||
const VALIDATOR1_IP_ADDRESS: vector<u8> = x"00FF00FF"; | ||
const VALIDATOR1_STAKE: u64 = 100000000000000; | ||
|
||
/// This is a module initializer that runs during module publishing. | ||
/// It will create a singleton SuiSystemState object, which contains | ||
/// all the information we need in the system. | ||
fun init(ctx: &mut TxContext) { | ||
let treasury_cap = SUI::new(ctx); | ||
let storage_fund = Coin::mint(INIT_STORAGE_FUND, &mut treasury_cap, ctx); | ||
let validators = Vector::empty(); | ||
Vector::push_back(&mut validators, Validator::new( | ||
VALIDATOR1_SUI_ADDRESS, | ||
VALIDATOR1_NAME, | ||
VALIDATOR1_IP_ADDRESS, | ||
Coin::mint(VALIDATOR1_STAKE, &mut treasury_cap, ctx), | ||
)); | ||
SuiSystem::create( | ||
validators, | ||
treasury_cap, | ||
storage_fund, | ||
INIT_MAX_VALIDATOR_COUNT, | ||
INIT_MIN_VALIDATOR_STAKE, | ||
INIT_MAX_VALIDATOR_STAKE, | ||
ctx, | ||
); | ||
} | ||
} |
165 changes: 165 additions & 0 deletions
165
sui_programmability/framework/sources/Governance/SuiSystem.move
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,165 @@ | ||
// Copyright (c) 2022, Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
module Sui::SuiSystem { | ||
use Sui::Coin::{Self, Coin, TreasuryCap}; | ||
use Sui::ID::VersionedID; | ||
use Sui::SUI::SUI; | ||
use Sui::Transfer; | ||
use Sui::TxContext::{Self, TxContext}; | ||
use Sui::Validator::{Self, Validator}; | ||
use Sui::ValidatorSet::{Self, ValidatorSet}; | ||
|
||
friend Sui::Genesis; | ||
|
||
/// A list of system config parameters. | ||
// TDOO: We will likely add more, a few potential ones: | ||
// - the change in stake across epochs can be at most +/- x% | ||
// - the change in the validator set across epochs can be at most x validators | ||
struct SystemParameters has store { | ||
/// Lower-bound on the amount of stake required to become a validator. | ||
min_validator_stake: u64, | ||
/// Upper-bound on the amount of stake allowed to become a validator. | ||
max_validator_stake: u64, | ||
/// Maximum number of validator candidates at any moment. | ||
/// We do not allow the number of validators in any epoch to go above this. | ||
max_validator_candidate_count: u64, | ||
} | ||
|
||
/// The top-level object containing all information of the Sui system. | ||
struct SuiSystemState has key { | ||
id: VersionedID, | ||
/// The current epoch ID, starting from 0. | ||
epoch: u64, | ||
/// Contains all information about the validators. | ||
validators: ValidatorSet, | ||
/// The SUI treasury capability needed to mint SUI. | ||
treasury_cap: TreasuryCap<SUI>, | ||
/// The storage fund. | ||
storage_fund: Coin<SUI>, | ||
/// A list of system config parameters. | ||
parameters: SystemParameters, | ||
} | ||
|
||
// ==== functions that can only be called by Genesis ==== | ||
|
||
/// Create a new SuiSystemState object and make it shared. | ||
/// This function will be called only once in Genesis. | ||
public(friend) fun create( | ||
validators: vector<Validator>, | ||
treasury_cap: TreasuryCap<SUI>, | ||
storage_fund: Coin<SUI>, | ||
max_validator_candidate_count: u64, | ||
min_validator_stake: u64, | ||
max_validator_stake: u64, | ||
ctx: &mut TxContext, | ||
) { | ||
assert!(min_validator_stake < max_validator_stake, 0); | ||
let state = SuiSystemState { | ||
id: TxContext::new_id(ctx), | ||
epoch: 0, | ||
validators: ValidatorSet::new(validators), | ||
treasury_cap, | ||
storage_fund, | ||
parameters: SystemParameters { | ||
min_validator_stake, | ||
max_validator_stake, | ||
max_validator_candidate_count, | ||
}, | ||
}; | ||
Transfer::share_object(state); | ||
} | ||
|
||
// ==== entry functions ==== | ||
|
||
/// Can be called by anyone who wishes to become a validator in the next epoch. | ||
/// The `validator` object needs to be created before calling this. | ||
/// The amount of stake in the `validator` object must meet the requirements. | ||
// TODO: Does this need to go through a voting process? Any other criteria for | ||
// someone to become a validator? | ||
public(script) fun request_add_validator( | ||
self: &mut SuiSystemState, | ||
name: vector<u8>, | ||
net_address: vector<u8>, | ||
stake: Coin<SUI>, | ||
ctx: &mut TxContext, | ||
) { | ||
assert!( | ||
ValidatorSet::get_total_validator_candidate_count(&self.validators) < self.parameters.max_validator_candidate_count, | ||
0 | ||
); | ||
let stake_amount = Coin::value(&stake); | ||
assert!( | ||
stake_amount >= self.parameters.min_validator_stake | ||
&& stake_amount <= self.parameters.max_validator_stake, | ||
0 | ||
); | ||
let validator = Validator::new(TxContext::sender(ctx), name, net_address, stake); | ||
ValidatorSet::request_add_validator(&mut self.validators, validator); | ||
} | ||
|
||
/// A validator can call this function to request a removal in the next epoch. | ||
/// We use the sender of `ctx` to look up the validator | ||
/// (i.e. sender must match the sui_address in the validator). | ||
/// At the end of the epoch, the `validator` object will be returned to the sui_address | ||
/// of the validator. | ||
public(script) fun request_remove_validator( | ||
self: &mut SuiSystemState, | ||
ctx: &mut TxContext, | ||
) { | ||
ValidatorSet::request_remove_validator( | ||
&mut self.validators, | ||
ctx, | ||
) | ||
} | ||
|
||
/// A validator can request adding more stake. This will be processed at the end of epoch. | ||
public(script) fun request_add_stake( | ||
self: &mut SuiSystemState, | ||
new_stake: Coin<SUI>, | ||
ctx: &mut TxContext, | ||
) { | ||
ValidatorSet::request_add_stake( | ||
&mut self.validators, | ||
new_stake, | ||
self.parameters.max_validator_stake, | ||
ctx, | ||
) | ||
} | ||
|
||
/// A validator can request to withdraw stake. | ||
/// If the sender represents a pending validator (i.e. has just requested to become a validator | ||
/// in the current epoch and hence is not active yet), the stake will be withdrawn immediately | ||
/// and a coin with the withdraw amount will be sent to the validator's address. | ||
/// If the sender represents an active validator, the request will be processed at the end of epoch. | ||
public(script) fun request_withdraw_stake( | ||
self: &mut SuiSystemState, | ||
withdraw_amount: u64, | ||
ctx: &mut TxContext, | ||
) { | ||
ValidatorSet::request_withdraw_stake( | ||
&mut self.validators, | ||
withdraw_amount, | ||
self.parameters.min_validator_stake, | ||
ctx, | ||
) | ||
} | ||
|
||
/// This function should be called at the end of an epoch, and advances the system to the next epoch. | ||
public(script) fun advance_epoch( | ||
self: &mut SuiSystemState, | ||
new_epoch: u64, | ||
ctx: &mut TxContext, | ||
) { | ||
// Only an active validator can make a call to this function. | ||
assert!(ValidatorSet::is_active_validator(&self.validators, TxContext::sender(ctx)), 0); | ||
|
||
self.epoch = self.epoch + 1; | ||
// Sanity check to make sure we are advancing to the right epoch. | ||
assert!(new_epoch == self.epoch, 0); | ||
ValidatorSet::advance_epoch( | ||
&mut self.validators, | ||
ctx, | ||
) | ||
} | ||
} |
157 changes: 157 additions & 0 deletions
157
sui_programmability/framework/sources/Governance/Validator.move
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,157 @@ | ||
// Copyright (c) 2022, Mysten Labs, Inc. | ||
// SPDX-License-Identifier: Apache-2.0 | ||
|
||
module Sui::Validator { | ||
use Std::ASCII::{Self, String}; | ||
use Std::Option::{Self, Option}; | ||
use Std::Vector; | ||
|
||
use Sui::Coin::{Self, Coin}; | ||
use Sui::SUI::SUI; | ||
use Sui::Transfer; | ||
use Sui::TxContext::TxContext; | ||
|
||
friend Sui::Genesis; | ||
friend Sui::SuiSystem; | ||
friend Sui::ValidatorSet; | ||
|
||
#[test_only] | ||
friend Sui::ValidatorTests; | ||
#[test_only] | ||
friend Sui::ValidatorSetTests; | ||
|
||
struct Validator has store { | ||
/// The Sui Address of the validator. This is the sender that created the Validator object, | ||
/// and also the address to send validator/coins to during withdraws. | ||
sui_address: address, | ||
/// A unique human-readable name of this validator. | ||
name: String, | ||
/// The network address of the validator (could also contain extra info such as port, DNS and etc.). | ||
net_address: vector<u8>, | ||
/// The current active stake. This will not change during an epoch. It can only | ||
/// be updated at the end of epoch. | ||
stake: Coin<SUI>, | ||
/// Pending stake deposits. It will be put into `stake` at the end of epoch. | ||
pending_stake: Option<Coin<SUI>>, | ||
/// Pending withdraw amount, processed at end of epoch. | ||
pending_withdraw: u64, | ||
} | ||
|
||
public(friend) fun new( | ||
sui_address: address, | ||
name: vector<u8>, | ||
net_address: vector<u8>, | ||
stake: Coin<SUI>, | ||
): Validator { | ||
assert!( | ||
Vector::length(&net_address) <= 100 || Vector::length(&name) <= 50, | ||
0 | ||
); | ||
Validator { | ||
sui_address, | ||
name: ASCII::string(name), | ||
net_address, | ||
stake, | ||
pending_stake: Option::none(), | ||
pending_withdraw: 0, | ||
} | ||
} | ||
|
||
public(friend) fun destroy(self: Validator) { | ||
let Validator { | ||
sui_address, | ||
name: _, | ||
net_address: _, | ||
stake, | ||
pending_stake, | ||
pending_withdraw, | ||
} = self; | ||
Transfer::transfer(stake, sui_address); | ||
assert!(pending_withdraw == 0 && Option::is_none(&pending_stake), 0); | ||
Option::destroy_none(pending_stake); | ||
} | ||
|
||
/// Add stake to an active validator. The new stake is added to the pending_stake field, | ||
/// which will be processed at the end of epoch. | ||
public(friend) fun request_add_stake( | ||
self: &mut Validator, | ||
new_stake: Coin<SUI>, | ||
max_validator_stake: u64, | ||
) { | ||
let cur_stake = Coin::value(&self.stake); | ||
if (Option::is_none(&self.pending_stake)) { | ||
assert!( | ||
cur_stake + Coin::value(&new_stake) <= max_validator_stake, | ||
0 | ||
); | ||
Option::fill(&mut self.pending_stake, new_stake) | ||
} else { | ||
let pending_stake = Option::extract(&mut self.pending_stake); | ||
Coin::join(&mut pending_stake, new_stake); | ||
assert!( | ||
cur_stake + Coin::value(&pending_stake) <= max_validator_stake, | ||
0 | ||
); | ||
Option::fill(&mut self.pending_stake, pending_stake); | ||
} | ||
} | ||
|
||
/// Withdraw stake from an active validator. Since it's active, we need | ||
/// to add it to the pending withdraw amount and process it at the end | ||
/// of epoch. We also need to make sure there is sufficient amount to withdraw. | ||
public(friend) fun request_withdraw_stake( | ||
self: &mut Validator, | ||
withdraw_amount: u64, | ||
min_validator_stake: u64, | ||
) { | ||
self.pending_withdraw = self.pending_withdraw + withdraw_amount; | ||
|
||
let pending_stake_amount = if (Option::is_none(&self.pending_stake)) { | ||
0 | ||
} else { | ||
Coin::value(Option::borrow(&self.pending_stake)) | ||
}; | ||
let total_stake = Coin::value(&self.stake) + pending_stake_amount; | ||
assert!(total_stake >= self.pending_withdraw + min_validator_stake, 0); | ||
} | ||
|
||
/// Process pending stake and pending withdraws. | ||
public(friend) fun adjust_stake(self: &mut Validator, ctx: &mut TxContext) { | ||
if (Option::is_some(&self.pending_stake)) { | ||
let pending_stake = Option::extract(&mut self.pending_stake); | ||
Coin::join(&mut self.stake, pending_stake); | ||
}; | ||
if (self.pending_withdraw > 0) { | ||
let coin = Coin::withdraw(&mut self.stake, self.pending_withdraw, ctx); | ||
Coin::transfer(coin, self.sui_address); | ||
self.pending_withdraw = 0; | ||
} | ||
} | ||
|
||
|
||
public fun sui_address(self: &Validator): address { | ||
self.sui_address | ||
} | ||
|
||
public fun stake_amount(self: &Validator): u64 { | ||
Coin::value(&self.stake) | ||
} | ||
|
||
public fun pending_stake_amount(self: &Validator): u64 { | ||
if (Option::is_some(&self.pending_stake)) { | ||
Coin::value(Option::borrow(&self.pending_stake)) | ||
} else { | ||
0 | ||
} | ||
} | ||
|
||
public fun pending_withdraw(self: &Validator): u64 { | ||
self.pending_withdraw | ||
} | ||
|
||
public fun is_duplicate(self: &Validator, other: &Validator): bool { | ||
self.sui_address == other.sui_address | ||
|| self.name == other.name | ||
|| self.net_address == other.net_address | ||
} | ||
} |
Oops, something went wrong.