axelar-cgp-sui

Module 0x0::governance

Governance module handles the governance of the Pool that it’s attached to. Users with non zero stake can create proposals and vote on them. Winning proposals are used to set the trade parameters for the next epoch.

use 0x0::constants;
use 0x0::math;
use 0x0::trade_params;
use 0x1::option;
use 0x1::u64;
use 0x2::event;
use 0x2::object;
use 0x2::tx_context;
use 0x2::vec_map;

Struct Proposal

Proposal struct that holds the parameters of a proposal and its current total votes.

struct Proposal has copy, drop, store
Fields
taker_fee: u64
maker_fee: u64
stake_required: u64
votes: u64

Struct Governance

Details of a pool. This is refreshed every epoch by the first State action against this pool.

struct Governance has store
Fields
epoch: u64
Tracks refreshes.
whitelisted: bool
If Pool is whitelisted.
stable: bool
If Pool is stable or volatile.
proposals: vec_map::VecMap<object::ID, governance::Proposal>
List of proposals for the current epoch.
trade_params: trade_params::TradeParams
Trade parameters for the current epoch.
next_trade_params: trade_params::TradeParams
Trade parameters for the next epoch.
voting_power: u64
All voting power from the current stakes.
quorum: u64
Quorum for the current epoch.

Struct TradeParamsUpdateEvent

Event emitted when trade parameters are updated.

struct TradeParamsUpdateEvent has copy, drop
Fields
taker_fee: u64
maker_fee: u64
stake_required: u64

Constants

const EInvalidMakerFee: u64 = 1;

const EInvalidTakerFee: u64 = 2;

const EMaxProposalsReachedNotEnoughVotes: u64 = 4;

const EProposalDoesNotExist: u64 = 3;

const EWhitelistedPoolCannotChange: u64 = 5;

const FEE_MULTIPLE: u64 = 1000;

const MAX_MAKER_STABLE: u64 = 50000;

const MAX_MAKER_VOLATILE: u64 = 500000;

const MAX_PROPOSALS: u64 = 100;

const MAX_TAKER_STABLE: u64 = 100000;

const MAX_TAKER_VOLATILE: u64 = 1000000;

const MIN_MAKER_STABLE: u64 = 20000;

const MIN_MAKER_VOLATILE: u64 = 200000;

const MIN_TAKER_STABLE: u64 = 50000;

const MIN_TAKER_VOLATILE: u64 = 500000;

const VOTING_POWER_THRESHOLD: u64 = 100000000000;

Function empty

public(friend) fun empty(stable_pool: bool, ctx: &tx_context::TxContext): governance::Governance
Implementation
public(package) fun empty(stable_pool: bool, ctx: &TxContext): Governance {
    let default_taker = if (stable_pool) {
        MAX_TAKER_STABLE
    } else {
        MAX_TAKER_VOLATILE
    };
    let default_maker = if (stable_pool) {
        MAX_MAKER_STABLE
    } else {
        MAX_MAKER_VOLATILE
    };
    Governance {
        epoch: ctx.epoch(),
        whitelisted: false,
        stable: stable_pool,
        proposals: vec_map::empty(),
        trade_params: trade_params::new(
            default_taker,
            default_maker,
            constants::default_stake_required(),
        ),
        next_trade_params: trade_params::new(
            default_taker,
            default_maker,
            constants::default_stake_required(),
        ),
        voting_power: 0,
        quorum: 0,
    }
}

Function set_whitelist

Whitelist a pool. This pool can be used as a DEEP reference price for other pools. This pool will have zero fees.

public(friend) fun set_whitelist(self: &mut governance::Governance, whitelisted: bool)
Implementation
public(package) fun set_whitelist(self: &mut Governance, whitelisted: bool) {
    self.whitelisted = whitelisted;
    self.reset_trade_params();
}

Function whitelisted

public(friend) fun whitelisted(self: &governance::Governance): bool
Implementation
public(package) fun whitelisted(self: &Governance): bool {
    self.whitelisted
}

Function update

Update the governance state. This is called at the start of every epoch.

public(friend) fun update(self: &mut governance::Governance, ctx: &tx_context::TxContext)
Implementation
public(package) fun update(self: &mut Governance, ctx: &TxContext) {
    let epoch = ctx.epoch();
    if (self.epoch == epoch) return;

    self.epoch = epoch;
    self.quorum = math::mul(self.voting_power, constants::half());
    self.proposals = vec_map::empty();
    self.trade_params = self.next_trade_params;

    event::emit(TradeParamsUpdateEvent {
        taker_fee: self.trade_params.taker_fee(),
        maker_fee: self.trade_params.maker_fee(),
        stake_required: self.trade_params.stake_required(),
    });
}

Function add_proposal

Add a new proposal to governance. Check if proposer already voted, if so will give error. If proposer has not voted, and there are already MAX_PROPOSALS proposals, remove the proposal with the lowest votes if it has less votes than the voting power. Validation of the account adding is done in State.

public(friend) fun add_proposal(self: &mut governance::Governance, taker_fee: u64, maker_fee: u64, stake_required: u64, stake_amount: u64, balance_manager_id: object::ID)
Implementation
public(package) fun add_proposal(
    self: &mut Governance,
    taker_fee: u64,
    maker_fee: u64,
    stake_required: u64,
    stake_amount: u64,
    balance_manager_id: ID,
) {
    assert!(!self.whitelisted, EWhitelistedPoolCannotChange);
    assert!(taker_fee % FEE_MULTIPLE == 0, EInvalidTakerFee);
    assert!(maker_fee % FEE_MULTIPLE == 0, EInvalidMakerFee);

    if (self.stable) {
        assert!(
            taker_fee >= MIN_TAKER_STABLE && taker_fee <= MAX_TAKER_STABLE,
            EInvalidTakerFee,
        );
        assert!(
            maker_fee >= MIN_MAKER_STABLE && maker_fee <= MAX_MAKER_STABLE,
            EInvalidMakerFee,
        );
    } else {
        assert!(
            taker_fee >= MIN_TAKER_VOLATILE && taker_fee <= MAX_TAKER_VOLATILE,
            EInvalidTakerFee,
        );
        assert!(
            maker_fee >= MIN_MAKER_VOLATILE && maker_fee <= MAX_MAKER_VOLATILE,
            EInvalidMakerFee,
        );
    };

    let voting_power = stake_to_voting_power(stake_amount);
    if (self.proposals.size() == MAX_PROPOSALS) {
        self.remove_lowest_proposal(voting_power);
    };

    let new_proposal = new_proposal(taker_fee, maker_fee, stake_required);
    self.proposals.insert(balance_manager_id, new_proposal);
}

Function adjust_vote

Vote on a proposal. Validation of the account and stake is done in State. If from_proposal_id is some, the account is removing their vote from that proposal. If to_proposal_id is some, the account is voting for that proposal.

public(friend) fun adjust_vote(self: &mut governance::Governance, from_proposal_id: option::Option<object::ID>, to_proposal_id: option::Option<object::ID>, stake_amount: u64)
Implementation
public(package) fun adjust_vote(
    self: &mut Governance,
    from_proposal_id: Option<ID>,
    to_proposal_id: Option<ID>,
    stake_amount: u64,
) {
    let votes = stake_to_voting_power(stake_amount);

    if (
        from_proposal_id.is_some() && self
            .proposals
            .contains(from_proposal_id.borrow())
    ) {
        let proposal = &mut self.proposals[from_proposal_id.borrow()];
        proposal.votes = proposal.votes - votes;
        if (
            proposal.votes + votes > self.quorum && proposal.votes < self.quorum
        ) {
            self.next_trade_params = self.trade_params;
        };
    };

    to_proposal_id.do_ref!(|proposal_id| {
        assert!(self.proposals.contains(proposal_id), EProposalDoesNotExist);

        let proposal = &mut self.proposals[proposal_id];
        proposal.votes = proposal.votes + votes;
        if (proposal.votes > self.quorum) {
            self.next_trade_params = proposal.to_trade_params();
        };
    });
}

Function adjust_voting_power

Adjust the total voting power by adding and removing stake. For example, if an account’s stake goes from 2000 to 3000, then stake_before is 2000 and stake_after is 3000. Validation of inputs done in State.

public(friend) fun adjust_voting_power(self: &mut governance::Governance, stake_before: u64, stake_after: u64)
Implementation
public(package) fun adjust_voting_power(
    self: &mut Governance,
    stake_before: u64,
    stake_after: u64,
) {
    self.voting_power =
        self.voting_power +
        stake_to_voting_power(stake_after) -
        stake_to_voting_power(stake_before);
}

Function trade_params

public(friend) fun trade_params(self: &governance::Governance): trade_params::TradeParams
Implementation
public(package) fun trade_params(self: &Governance): TradeParams {
    self.trade_params
}

Function stake_to_voting_power

Convert stake to voting power.

fun stake_to_voting_power(stake: u64): u64
Implementation
fun stake_to_voting_power(stake: u64): u64 {
    let mut voting_power = stake.min(VOTING_POWER_THRESHOLD);
    if (stake > VOTING_POWER_THRESHOLD) {
        voting_power =
            voting_power + math::sqrt(stake, constants::deep_unit()) -
            math::sqrt(VOTING_POWER_THRESHOLD, constants::deep_unit());
    };

    voting_power
}

Function new_proposal

fun new_proposal(taker_fee: u64, maker_fee: u64, stake_required: u64): governance::Proposal
Implementation
fun new_proposal(
    taker_fee: u64,
    maker_fee: u64,
    stake_required: u64,
): Proposal {
    Proposal { taker_fee, maker_fee, stake_required, votes: 0 }
}

Function remove_lowest_proposal

Remove the proposal with the lowest votes if it has less votes than the voting power. If there are multiple proposals with the same lowest votes, the latest one is removed.

fun remove_lowest_proposal(self: &mut governance::Governance, voting_power: u64)
Implementation
fun remove_lowest_proposal(self: &mut Governance, voting_power: u64) {
    let mut removal_id = option::none();
    let mut cur_lowest_votes = constants::max_u64();
    let (keys, values) = self.proposals.into_keys_values();

    self.proposals.size().do!(|i| {
        let proposal_votes = values[i].votes;
        if (
            proposal_votes < voting_power && proposal_votes <= cur_lowest_votes
        ) {
            removal_id = option::some(keys[i]);
            cur_lowest_votes = proposal_votes;
        };
    });

    assert!(removal_id.is_some(), EMaxProposalsReachedNotEnoughVotes);
    self.proposals.remove(removal_id.borrow());
}

Function reset_trade_params

fun reset_trade_params(self: &mut governance::Governance)
Implementation
fun reset_trade_params(self: &mut Governance) {
    self.proposals = vec_map::empty();
    let stake = self.trade_params.stake_required();
    if (self.whitelisted) {
        self.trade_params = trade_params::new(0, 0, 0);
    } else if (self.stable) {
        self.trade_params =
            trade_params::new(MAX_TAKER_STABLE, MAX_MAKER_STABLE, stake);
    } else {
        self.trade_params =
            trade_params::new(MAX_TAKER_VOLATILE, MAX_MAKER_VOLATILE, stake);
    };
    self.next_trade_params = self.trade_params;
}

Function to_trade_params

fun to_trade_params(proposal: &governance::Proposal): trade_params::TradeParams
Implementation
fun to_trade_params(proposal: &Proposal): TradeParams {
    trade_params::new(
        proposal.taker_fee,
        proposal.maker_fee,
        proposal.stake_required,
    )
}