Skip to main content

Staking Precompile

note

This page is intended for developers looking to build smart contracts or interfaces that interact with the staking system.

The entrypoint to the staking system is the stateful staking precompile.

This precompile allows delegators and validators to take actions that affect the composition of the validator set.

  • join the validator set (addValidator)
  • delegate their stake to a validator (delegate)
  • undelegate their stake from a validator (a multi-step process: undelegate, wait the required number of epochs, then withdraw)
  • compound the rewards they earned as a delegator (i.e. delegate the rewards) (compound)
  • claim the rewards they earned as a delegator (claimRewards)

Although users may delegate or undelegate at any time, stake weight changes only take effect at epoch boundaries, and stake weight changes made too close to the epoch boundary get queued until the next epoch boundary. This is to allow for the separation of consensus and execution in Monad, as described in Consensus and Execution.

note

For security, only standard CALLs are allowed to the staking precompile. In particular, STATICCALL and DELEGATECALL are not allowed.

Precompile Specification

Users take staking-related actions by interacting with a precompile.

First, let's survey the state stored in the precompile. Functions are surveyed in the following section.

Constants

// Minimum stake required from validator's own account
// to be eligible to join the valset, in Monad wei
uint256 MIN_VALIDATE_STAKE;
// Min stake required (including delegation) for validator
// to be eligible to join the valset, in Monad wei.
// note that ACTIVE_VALIDATOR_STAKE > MIN_VALIDATE_STAKE
uint256 ACTIVE_VALIDATOR_STAKE;
// Block Reward
uint256 REWARD;
// Accumulator unit multiplier. Chosen to preserve accuracy
uint256 UNIT_BIAS = 1e36;
// Staking precompile address
Address STAKING_CONTRACT_ADDRESS = 0x0000000000000000000000000000000000001000;
// Withdrawal delay, needed to facilitate slashing
uint8 WITHDRAWAL_DELAY = 1;
// Controls the maximum number of results returned by individual
// calls to valset-getters, get_delegators, and get_delegations
uint64 PAGINATED_RESULTS_SIZE = 100;

Validator structs

struct KeysPacked // A validator's consensus keys
{
bytes33 secp_pubkey;
bytes48 bls_pubkey;
};
struct ValExecution // Realtime execution state for one validator
{
uint256 stake; // Upcoming stake pool balance
uint256 acc; // Current accumulator value for validator
uint256 commission; // Proportion of block reward charged as commission, times 1e18; 10% = 1e17
KeysPacked keys; // Consensus keys
uint256 address_flags; // Flags to represent validators' current state
uint256 unclaimed_rewards; // Unclaimed rewards
address auth_address; // Delegator address with authority over validator stake
}
struct ValConsensus // State tracked by the consensus system
{
uint256 stake; // Current active stake
KeysPacked keys; // Consensus keys
uint256 commission; // Commission rate for current epoch
}

Delegator structs

struct DelInfo
{
uint256 stake; // Current active stake
uint256 acc; // Last checked accumulator
uint256 rewards; // Last checked rewards
uint256 delta_stake; // Stake to be activated next epoch
uint256 next_delta_stake; // Stake to be activated in 2 epochs
uint64 delta_epoch; // Epoch when delta_stake becomes active
uint64 next_delta_epoch; // Epoch when next_delta_stake becomes active
}
struct WithdrawalRequest
{
uint256 amount; // Amount to undelegate from validator
uint256 acc; // Validator accumulator when undelegate was called
uint64 epoch; // Epoch when undelegate stake deactivates
};
struct Accumulator
{
uint256 val; // Current accumulator value
uint256 refcount; // Reference count for this accumulator value
};

State variables

// Current consensus epoch
uint64 epoch;
// Flag indicating if currently in epoch delay period
bool in_boundary;
// Counter for validator ids
uint64 last_val_id;
// Current execution view of validator set
StorageArray<uint64> execution_valset;
// Previous consensus view of validator set
StorageArray<uint64> snapshot_valset;
// Current consensus view of validator set
StorageArray<uint64> consensus_valset;

Mappings

//These mappings only exist to ensure the SECP/BLS Keys are unique
mapping (secp_eth_address => uint64) secp_to_val_id;
mapping (bls_eth_address => uint64) bls_to_val_id;
// Keys(val_id, epoch) => Value(acc)
// making note of the validator accumulator at start of epoch.
mapping(uint64 => mapping(uint64 => Accumulator)) epoch_acc;
// Key(val_id)
mapping(uint64 => ValExecution) val_execution;
// Key(val_id)
mapping(uint64 => ValConsensus) _val_consensus;
// Key(val_id)
mapping(uint64 => ValConsensus) _val_snapshot;
// Keys(val_id,msg.sender) => DelInfo
mapping(uint64 => mapping(address => DelInfo)) delegator;
// Keys(val_id,msg.sender,withdrawal_id) => WithdrawalRequest
mapping(uint64 => mapping(address => mapping (uint8 => WithdrawalRequest))) withdrawal;

Precompile Address and Selectors

The contract address is 0x0000000000000000000000000000000000001000.

The external functions are identified by the following 4-byte selectors.

External state-modifying methods:

  1. addValidator(bytes,bytes,bytes) - 0xf145204c
  2. delegate(uint64) - 0x84994fec
  3. undelegate(uint64,uint256,uint8) - 0x5cf41514
  4. compound(uint64) - 0xb34fea67
  5. withdraw(uint64,uint8) - 0xaed2ee73
  6. claimRewards(uint64) - 0xa76e2ca5
  7. changeCommission(uint64,uint256) - 0x9bdcc3c8
  8. externalReward(uint64) - 0xe4b3303b

External view methods:

  1. getValidator(uint64) - 0x2b6d639a
  2. getDelegator(uint64,address) - 0x573c1ce0
  3. getWithdrawalRequest(uint64,address,uint8) - 0x56fa2045
  4. getConsensusValidatorSet(uint32) - 0xfb29b729
  5. getSnapshotValidatorSet(uint32) - 0xde66a368
  6. getExecutionValidatorSet(uint32) - 0x7cb074df
  7. getDelegations(address,uint64) - 0x4fd66050
  8. getDelegators(uint64,address) - 0xa0843a26
  9. getEpoch() - 0x757991a8

Syscalls:

External State-Modifying Methods

addValidator

Function selector

addValidator(bytes,bytes,bytes) : 0xf145204c

Function signature

function addValidator(
bytes payload
bytes signed_secp_message,
bytes signed_bls_message
) external returns(uint64) payable;

Parameters

  1. payload - consists of the following fields, packed together in big endian:
    • bytes secp_pubkey (unique SECP public key used for consensus)
    • bytes bls_pubkey (unique BLS public key used for consensus)
    • address auth_address (address used for the validator’s delegator account. This address has withdrawal authority for the validator's staked amount)
    • uint256 amount (amount the validator is self-staking. Must equal msg.value)
    • uint256 commission (commission charged to delegators multiplied by 1e18, e.g. 10% = 1e17)
  2. signed_secp_message - SECP signature over payload
  3. signed_bls_message - BLS signature over payload

Gas cost

505125

Behavior

This creates a validator with an associated delegator account and returns the resultant val_id. The method starts by unpacking the payload to retrieve the secp_pubkey, bls_pubkey, auth_address, amount, and commission, then verifying that the signed_secp_message and signed_bls_message correspond to the payload signed by the corresponding SECP and BLS private keys.

  • The validator must provide both a unique BLS key and a unique SECP key.
  • The msg.value must meet one of the following conditions:
    • If msg.value >= ACTIVE_VALIDATOR_STAKE, the validator is added to the validator set but not immediately active.
    • If MIN_VALIDATE_STAKE <= msg.value < ACTIVE_VALIDATOR_STAKE, the validator is registered, but not added to the validator set.
  • Both signatures (signed_secp_message and signed_bls_message) must be valid and must sign over the payload.
  • Multiple validators may share the same auth_address.
  • Submissions with any repeated public keys will revert.
  • If the validator meets the ACTIVE_VALIDATOR_STAKE threshold, then
    • If request was before the boundary block, the validator stake and associated delegator stake become active in epoch n+1;
    • Else, the validator stake and associated delegator stake become active in epoch n+2.
Pseudocode
secp_pubkey, bls_pubkey, auth_address, amount, commission = payload
assert amount == msg.value
// increment validator id
last_val_id = last_val_id + 1;
// set uniqueness of keys
secp_to_val_id[secp_eth_address] = last_val_id;
bls_to_val_id[bls_eth_address] = last_val_id;
// set validator info
val_execution[last_val_id] = ValExecution{
uint256 stake = msg.value;
uint256 commission = commission;
KeysPacked keys = KeysPacked{secp_pubkey, bls_pubkey}
uint256 address_flags = set_flags();
}
// set authority delegator info
delegator[last_val_id][input.auth_address] = DelInfo{
uint256 delta_stake = set_stake()[0];
uint256 next_delta_stake = set_stake()[1];
uint64 delta_epoch = set_stake()[2];
uint64 next_delta_epoch = set_stake()[3];
}
// set delegator accumulator
epoch_acc[last_val_id][getEpoch()] = Accumulator{
uint256 ref_count += 1;
}
// set flags
set_flags();
// push validator id
if (val_execution[last_val_id].stake() >= ACTIVE_VALIDATOR_STAKE
and last_val_id not in execution_valset):
execution_valset.push(last_val_id);
return last_val_id;
def set_flags():
if msg.value + val_execution[last_val_id].stake() >= ACTIVE_VALIDATOR_STAKE:
return ValidatorFlagsOk;
if msg.value + val_execution[last_val_id].stake() >= MIN_VALIDATE_STAKE
return ValidatorFlagsStakeTooLow;
def set_stake():
if in_boundary:
delta_stake = 0;
next_delta_stake = msg.value;
delta_epoch = 0;
next_delta_epoch = current_epoch + 2;
else:
delta_stake = msg.value;
next_delta_stake = 0;
delta_epoch = current_epoch + 1;
next_delta_epoch = 0;
return [delta_stake, next_delta_stake, delta_epoch, next_delta_epoch];

Usage

Here is an example of assembling the payload and signing:

def generate_add_validator_call_data_and_sign(
secp_pubkey: bytes,
bls_pubkey: bytes,
auth_address: bytes,
amount: int,
commission: int
secp_privkey: bytes
bls_privkey: bytes
) -> bytes:
# 1) Encode
payload_parts = [
secp_pubkey,
bls_pubkey,
auth_address,
toBigEndian32(amount),
toBigEndian32(commission),
]
payload = b"".join(payload_parts)
# 2) Sign with both keys
secp_sig = SECP256K1_SIGN(blake3(payload), secp_privkey)
bls_sig = BLS_SIGN(hash_to_curve(payload), bls_privkey)
# 3) Solidity encode the payload and two signatures
return eth_abi.encode(['bytes', 'bytes', 'bytes'], [payload, secp_sig, bls_sig])

delegate

Function selector

delegate(uint64) : 0x84994fec

Function signature

function delegate(
uint64 val_id
) external returns(bool) payable;

Parameters

  1. val_id - id of the validator that delegator would like to delegate to
  2. msg.value - the amount to delegate

Gas cost

260850

Behavior

This creates a delegator account if it does not exist and increments the delegator's balance.

  • The delegator account is determined by msg.sender.
  • val_id must correspond to a valid validator.
  • msg.value must be > 0.
  • If this delegation causes the validator's total stake to exceed ACTIVE_VALIDATOR_STAKE, then the validator will be added to execution_valset if not already present.
  • The delegator stake becomes active in the valset
    • in epoch n+1 if the request is before the boundary block
    • in epoch n+2 otherwise
Pseudocode
validator_id = msg.input.val_id;
// set validator information
val_execution[validator_id] = ValExecution{
uint256 stake += msg.value();
}
// set delegator information
DelInfo current_delegator = delegator[validator_id][msg.sender];
// apply get_current_stake() first. This updates the delegator stake
// to be inline with the current stake activated in consensus.
get_current_stake();
// apply add_stake() second.
uint256[4] add_stake_info = add_stake(msg.value());
current_delegator = DelInfo{
uint256 delta_stake = add_stake_info[0];
uint256 next_delta_stake = add_stake_info[1];
uint64 delta_epoch = add_stake_info[2];
uint64 next_delta_epoch = add_stake_info[3];
}
// set epoch accumulator
epoch_acc[validator_id][getEpoch()].ref_count += 1;
// set flags
set_flags();
// push validator id
if val_execution[validator_id].stake() >= ACTIVE_VALIDATOR_STAKE
and validator_id not in execution_valset:
execution_valset.push(validator_id);
def add_stake(uint256 amount):
uint256 _delta_stake;
uint256 _next_delta_stake;
uint64 _delta_epoch;
uint64 _next_delta_epoch;
if not in_boundary:
_delta_stake = current_delegator.delta_stake() + amount;
_next_delta_stake = 0;
_delta_epoch = current_epoch + 1;
_next_delta_epoch = 0;
else:
_delta_stake = 0;
_next_delta_stake = current_delegator.next_delta_stake() + amount;
_delta_epoch = 0;
_next_delta_epoch = current_epoch + 2;
return [_delta_stake, _next_delta_stake, _delta_epoch, _next_delta_epoch];
def maybe_process_next_epoch_state():
"""
Helper function to process and update rewards
based on the current epoch state.
"""
if (
epoch_acc[validator_id][current_delegator.delta_epoch()] != 0
and current_epoch > current_delegator.delta_epoch()
and current_delegator.delta_epoch() > 0
):
// Compute rewards from the last checked epoch.
_rewards += current_delegator.stake() * (
epoch_acc[validator_id][current_delegator.delta_epoch()].val()
- current_delegator.acc()
)
// Promote stake to active in delegator view.
current_delegator.stake() += current_delegator.delta_stake()
current_delegator.acc() = (
epoch_acc[validator_id][current_delegator.delta_epoch()].val()
)
current_delegator.delta_epoch() = current_delegator.next_delta_epoch()
current_delegator.delta_stake() = current_delegator.next_delta_stake()
current_delegator.next_delta_epoch() = 0
current_delegator.next_delta_stake() = 0
epoch_acc[validator_id][current_delegator.delta_epoch].ref_count -= 1
def get_current_stake():
uint256 _rewards = 0;
// Process next epoch rewards and increment stake
maybe_process_next_epoch_state()
// Perform again to capture max two additional epochs
maybe_process_next_epoch_state()
current_delegator.rewards() += _rewards;
return _rewards;

undelegate

Function selector

undelegate(uint64,uint256,uint8) : 0x5cf41514

Function signature

function undelegate(
uint64 val_id,
uint256 amount,
uint8 withdraw_id
) external returns(bool);

Parameters

  1. val_id - id of the validator to which sender previously delegated, from which we are removing delegation
  2. amount - amount to unstake, in Monad wei
  3. withdraw_id - identifier for a delegator's withdrawal. For each (validator, delegator) tuple, there can be a maximum of 256 in-flight withdrawal requests
  4. msg.value - should be 0; the transaction will revert otherwise

Gas cost

147750

Behavior

This deducts amount from the delegator account and moves it to a withdrawal request object, where it remains in a pending state for a predefined number of epochs before the funds are claimable.

  • The delegator account is determined by msg.sender.
  • val_id must correspond to a valid validator to which the sender previously delegated
  • The delegator must have stake >= amount.
  • If the withdrawal causes Val(val_id).stake() to drop below ACTIVE_VALIDATOR_STAKE, then the validator is scheduled to be removed from valset.
  • If a delegator causes del(auth_address).stake() to drop below MIN_VALIDATE_STAKE, then the validator is scheduled to be removed from valset.
  • Delegator can only remove stake after it has activated. This is the stake field in the delegator struct.
  • The delegator stake becomes inactive in the valset
    • in epoch n+1 if the request is before the boundary block
    • in epoch n+2 otherwise
  • Let k represent the withdrawal_delay, then the delegator stake becomes withdrawable and thus no longer subject to slashing
    • in epoch n+1+k if the request is before the boundary block
    • in epoch n+2+k otherwise
  • The transaction will revert if msg.value is nonzero
timeline of undelegation and withdrawal
Timeline of withdrawability of stake relative to undelegate command
Pseudocode
uint64 validator_id = msg.input.val_id;
uint256 amount = msg.input.amount;
uint8 withdraw_id = msg.input.withdraw_id;
ValExecution current_validator = val_execution[validator_id];
// set validator information
current_validator = ValExecution{
uint256 stake -= amount;
}
// apply get_current_stake() first.
get_current_stake();
DelInfo current_delegator = delegator[validator_id][msg.sender];
// set delegator information
current_delegator = DelInfo{
uint256 stake -= amount;
}
// set withdraw request
withdrawal[validator_id][msg.sender][withdraw_id] = WithdrawalRequest{
uint256 amount = amount;
uint256 acc = current_validator.acc();
uint64 epoch = getEpoch();
});
// set epoch accumulator
epoch_acc[validator_id][getEpoch()].ref_count += 1;
// schedule validator to leave set
if current_validator.stake
< ACTIVE_VALIDATOR_STAKE and validator_id in execution_valset:
current_validator.set_flag(INSUFFICIENT_STAKE);
if (current_delegator.stake <= MIN_VALIDATE_STAKE and validator_id in execution_valset) and msg.sender == current_validator.auth_address:
current_validator.set_flag(INSUFFICIENT_VALIDATOR_STAKE);

compound

Function selector

compound(uint64) : 0xb34fea67

Function signature

function compound(
uint64 val_id
) external returns(bool);

Parameters

  1. val_id - id of the validator to which sender previously delegated, for which we are compounding rewards
  2. msg.value - should be 0; the transaction will revert otherwise

Gas cost

285050

Behavior

This precompile converts the delegator's accumulated rewards into additional stake.

  • The account compounded is determined by msg.sender. If a delegator account does not exist, then revert
  • val_id must correspond to a valid validator to which the sender previously delegated
  • The delegator rewards become active in the valset
    • in epoch n+1 if the request is before the boundary block
    • in epoch n+2 otherwise.
  • The transaction will revert if msg.value is nonzero
Pseudocode
validator_id = msg.input.val_id;
// set delegator information
DelInfo current_delegator = delegator[validator_id][msg.sender];
// apply get_current_stake() first. This updates the delegator stake
// to be inline with the current stake activated in consensus.
rewards_compounded = get_current_stake();
// apply add_stake() second.
uint256[4] add_stake_info = add_stake(rewards_compounded);
// set delegator information
current_delegator = DelInfo{
uint256 delta_stake = add_stake_info[0];
uint256 next_delta_stake = add_stake_info[1];
uint64 delta_epoch = add_stake_info[2];
uint64 next_delta_epoch = add_stake_info[3];
uint256 rewards = 0;
}
// set validator information
val_execution[validator_id] = ValExecution{
uint256 stake += rewards_compounded;
}
// set accumulator
epoch_acc[validator_id][getEpoch()] = Accumulator{
uint256 ref_count += 1;
}
// set flags
set_flags();
// push validator id
if val_execution[validator_id].stake() >= ACTIVE_VALIDATOR_STAKE and validator_id not in execution_valset:
execution_valset.push(validator_id);

withdraw

Function selector

withdraw(uint64,uint8) : 0xaed2ee73

Function signature

function withdraw(
uint64 val_id,
uint8 withdraw_id
) external returns (bool);

Parameters

  1. val_id - id of the validator to which sender previously delegated, from which we previously issued an undelegate command
  2. withdraw_id - identifier for a delegator's withdrawal. For each (validator, delegator) tuple, there can be a maximum of 256 in-flight withdrawal requests
  3. msg.value - should be 0; the transaction will revert otherwise

Gas cost

68675

Behavior

This completes an undelegation action (which started with a call to the undelegate function), sending the amount to msg.sender, provided that sufficient epochs have passed.

  • The delegator is msg.sender. The withdrawal is identified by msg.sender, val_id, and withdraw_id
  • Let k represent the withdrawal_delay. Then the withdrawal request becomes withdrawable and thus unslashable:
    • in epoch n+1+k if request is not in the epoch delay period since the undelegate call.
    • in epoch n+2+k if request is in the epoch delay period since the undelegate call.
  • The transaction will revert if msg.value is nonzero.
Pseudocode
uint64 validator_id = msg.input.val_id;
uint8 withdraw_id = msg.input.withdraw_id;
WithdrawalRequest current_withdraw = withdrawal[validator_id][msg.sender][withdraw_id];
// Compute any additional rewards and transfer funds to delegator
transfer(msg.sender, current_withdraw.amount + get_withdraw_rewards());
// unset withdraw request
withdrawal[validator_id][msg.sender][withdraw_id] = WithdrawalRequest{
uint256 amount = 0,
uint256 acc = 0,
uint64 epoch = 0
};
def get_withdraw_rewards():
epoch_acc[validator_id][current_withdraw.epoch].ref_count -= 1;
return current_withdraw.amount() * (epoch_acc[validator_id][current_withdraw.epoch()].val() - current_withdraw.acc());

claimRewards

Function selector

claimRewards(uint64) : 0xa76e2ca5

Function signature

function claimRewards(
uint64 val_id
) external returns (bool);

Parameters

  1. val_id - id of the validator to which sender previously delegated, for which we are claiming rewards
  2. msg.value - should be 0; the transaction will revert otherwise

Gas cost

155375

Behavior

This precompile allows a delegator to claim any rewards instead of redelegating them.

  • val_id must correspond to a valid validator to which the sender previously delegated
  • If delegator account does not exist for this (val_id, msg.sender) tuple, then the call reverts
  • The transaction will revert if msg.value is nonzero
  • The delegator's accumulated rewards are transferred to their address and reset to zero
Pseudocode
// set delegator information
DelInfo current_delegator = delegator[validator_id][msg.sender];
// apply get_current_stake() first.
uint256 current_rewards = get_current_stake();
// set delegator information
current_delegator = DelInfo{
uint256 rewards = 0;
)
// send rewards to delegator
transfer(msg.sender, current_rewards);

changeCommission

Function selector

changeCommission(uint64,uint256) : 0x9bdcc3c8

Function signature

function changeCommission(
uint64 val_id,
uint256 commission
) external returns (bool);

Parameters

  1. val_id - id of the validator, who would like to change their commission rate
  2. commission - commission rate taken from block rewards, expressed in 1e18 units (e.g., 10% = 1e17)
  3. msg.value - should be 0; the transaction will revert otherwise

Gas cost

39475

Behavior

This allows (only) the auth_account to modify the commission of the specified val_id after the validator has been registered via addValidator.

The commission cannot be set larger than MAX_COMMISSION (currently 100%).

  • The msg.sender must be the auth_address for the respective validator Id.
  • The commission cannot be set larger than MAX_COMMISSION (currently 100%).
  • The change in commission occurs in the following epochs:
    • in epoch n+1 if request is not in the epoch delay period.
    • in epoch n+2 if request is in the epoch delay period.
  • The transaction will revert if msg.value is nonzero
Pseudocode
validator_id = msg.input.val_id;
val_execution[validator_id] = ValExecution{
uint256 commission = msg.input.commission;
}

externalReward

Function selector

externalReward(uint64) : 0xe4b3303b

Function signature

function externalReward(
uint64 val_id
) external returns (bool);

Parameters

  1. val_id - id of the validator
  2. msg.value - the value to add to unclaimed rewards

Gas cost

62300

Behavior

This function allows anyone to send extra MON to the stakers of a particular validator.

This typically will be called by the validator themselves to share extra tips to their delegators.

  • This can only be called for a validator currently in the consensus validator set otherwise the transaction reverts.
  • The msg.value is between the following values otherwise the transaction reverts: minimum reward is 1 mon and the maximum reward is 1000000 mon.
Pseudocode
validator_id = msg.input.val_id;
require(msg.value >= 1e18 && msg.value <= 1e24, "Reward out of bounds");
require(val_consensus[validator_id] > 0 , "Validator not active");
val_execution[validator_id].unclaimed_reward += msg.value;
val_execution[val_id].acc += msg.value / val_consensus[val_id].stake();

External View Methods

getValidator

Function selector

getValidator(uint64) : 0x2b6d639a

Function signature

function getValidator(
uint64 val_id
) external view returns (
address auth_address,
uint256 flags,
uint256 stake,
uint256 acc_reward_per_token,
uint256 commission,
uint256 unclaimed_reward,
uint256 consensus_stake,
uint256 consensus_commission,
uint256 snapshot_stake,
uint256 snapshot_commission,
bytes memory secp_pubkey,
bytes memory bls_pubkey
);

Parameters

  1. val_id - id of the validator

Gas cost

97200

Behavior

This is the primary method to obtain information about a validator state. It provides a complete view of the validator’s state across execution, consensus, and snapshot contexts.

It returns:

  • ValExecution (execution view)
  • Stake and commission (consensus view)
  • Stake and commission (snapshot view)

getDelegator

Function selector

getDelegator(uint64,address) : 0x573c1ce0

Function signature

function getDelegator(
uint64 val_id,
address delegator
) external returns (
uint256 stake,
uint256 acc_reward_per_token,
uint256 rewards,
uint256 delta_stake,
uint256 next_delta_stake,
uint256 delta_epoch,
uint256 next_delta_epoch
)

Parameters

  1. val_id - id of the validator
  2. delegator - address of the delegator about whose stake we are inquiring

Gas cost

184900

Behavior

This returns the delegator’s DelInfo for the specified validator, providing a view of the delegator’s stake, accumulated rewards and pending changes in stake.

Gas cost is 184900.

getWithdrawalRequest

Function selector

getWithdrawalRequest(uint64,address,uint8) : 0x56fa2045

Function signature

function getWithdrawalRequest(
uint64 val_id,
address delegator,
uint8 withdraw_id
) external returns (
uint256 withdrawal_amount,
uint256 acc_reward_per_token,
uint256 withdrawal_epoch
)

Gas cost

24300

Behavior

This returns the pending WithdrawalRequest for the (val_id, delegator, withdraw_id) tuple.

get*ValidatorSet

Function selectors

getConsensusValidatorSet(uint32) : 0xfb29b729
getSnapshotValidatorSet(uint32) : 0xde66a368
getExecutionValidatorSet(uint32) : 0x7cb074df

Function signatures

getConsensusValidatorSet(
uint32 start_index
) returns (bool done, uint32 next_index, uint256[] valids)
getSnapshotValidatorSet(
uint32 start_index
) returns (bool done, uint32 next_index, uint256[] valids)
getExecutionValidatorSet(
uint32 start_index
) returns (bool done, uint32 next_index, uint256[] valids)

Parameters

  1. start_index - since the list being looked up is potentially very long, each of these functions is paginated, returning a fixed-length subset of the desired list. Pass start_index to indicate where in the list to start.

Gas cost

Each function costs 814000 gas (assuming PAGINATED_RESULTS_SIZE = 100).

Behavior

These functions return the consensus, snapshot, and execution validator ids, respectively.

Each call retrieves up to PAGINATED_RESULTS_SIZE validator ids starting from start_index and returns a tuple (bool done, uint32 next_index, uint256[] valids).

The bool done indicates whether the end of the list was reached. The uint32 next_index is the last slot in the array.

getDelegations

Function selector

getDelegations(address,uint64) : 0x4fd66050

Function signature

function getDelegations(
address delegator,
uint64 start_val_id
) returns (bool done, uint64 next_val_id, uint64[] vals_page)

Parameters

  1. delegator - the address whose delegations we want to look up
  2. start_val_id

Gas cost

814000

Behavior

Each call retrieves up to PAGINATED_RESULTS_SIZE validator ids starting from start_val_id and returns a tuple (bool done, uint64 next_val_id, uint64[] vals_page) with delegation from the input delegator address.

The bool done indicates whether the end of the list was reached. The uint64 next_val_id is the id after the last element in vals_page. Use it as the start_val_id for the next call.

If delegator has delegated to over PAGINATED_RESULTS_SIZE validator ids, multiple calls are required (while done is false).

To capture the full set, the function should be called with start_val_id = 0.

getDelegators

Function selector

getDelegators(uint64,address) : 0xa0843a26

Function signature

function getDelegators(
uint64 val_id,
address start_delegator
) returns (bool done, address next_del_addr, address[] dels_page)

Parameters

  1. val_id - the id of the validator for which we want to know the delegators
  2. start_delegator

Gas cost

814000

Behavior

Each call retrieves up to PAGINATED_RESULTS_SIZE delegator addresses starting from start_delegator and returns a tuple (bool done, address next_del_addr, address[] dels_page) with delegation to the input val_id.

The bool done indicates the end of the list was reached. The next_del_addr is the address immediately after the last element in dels_page. Use it as start_delegator for the next call.

To capture the full set, the function should be called with start_delegator = 0.

note

The number of delegators to a given validator can be very large, so it is recommended to maintain an updated list via the events framework, rather than periodically calling this expensive lookup.

getEpoch

Function selector

getEpoch() : 0x757991a8

Function signature

function getEpoch() external returns(uint64, bool)

Gas cost

16200

Behavior

This function is a handy utility to determine the current epoch and timing within the epoch (before or after the boundary block).

Concretely, the call returns a tuple (uint64 epoch, bool in_epoch_delay_period). If in_epoch_delay_period is false, the boundary block has not been reached yet and write operations at that time should be effective for epoch + 1. If in_epoch_delay_period is true, the network is past the boundary block and and write operations at that time should be effective for epoch + 2

Syscalls

There are currently three syscalls. Users cannot invoke these directly. They are only triggered through special system transactions.

syscallOnEpochChange

Function selector

syscallOnEpochChange(uint64) : 0x1d4e9f02

Function signature

function syscallOnEpochChange(uint64 epoch);

Parameters

  1. epoch - the new consensus epoch being entered

Behavior

This syscall is triggered at the end of the epoch delay period. It performs the following actions:

  1. If the validator received a request to change stake in the previous epoch and participated in the previous epoch’s consensus validator set then it saves the corresponding accumulator value
  2. If any validator was active in the previous epoch but becomes inactive in the current epoch, it also saves their current accumulator value
  3. Sets the current epoch in state
Pseudocode
uint64 current_epoch = msg.input.epoch;
for i in snapshot_valset:
if epoch_acc[i][current_epoch] is not empty:
epoch_acc[i][current_epoch].val() = execution_valset[i].acc()
if epoch_acc[i][current_epoch + 1] is not empty:
epoch_acc[i][current_epoch].val() = execution_valset[i].acc()
in_boundary = false;
epoch = current_epoch;

syscallReward

Function selector

syscallReward(address) : 0x791bdcf3

Function signature

function syscallReward(address block_author);

Parameters

  1. block_author — the address of the validator that produced the block.

Behavior

This syscall is invoked for every block. It rewards the block-producing validator (and their delegators) with the configured block reward:

  1. If the validator has a nonzero commission, a portion of the reward is allocated to the validator’s auth_address.
  2. The remaining reward is claimable to the validator’s delegators.

Note that the commission is calculated as a percentage of the total block reward.

Example
  • Suppose that a validator's personal stake comprises 20% of the total delegation to their validator.
  • The commission is set at 10% of total rewards.

Then the validator receives 10% of the total block reward as their commission. The remaining 90% of the reward is distributed to the stake pool. Since the validator owns 20% of the pool, they also receive 20% of that remaining amount.

Pseudocode
uint64 val_id = secp_to_val_id[block_author];
DelInfo auth_del = delegator[val_id][val_execution[val_id].auth_address()];
uint256 _commission = REWARD * val_execution[val_id].commission / 1e18;
uint256 _unclaimed_rewards = REWARD - _commission;
// state update
auth_del.rewards() += _commission;
val_execution[val_id].unclaimed_rewards += _unclaimed_rewards;
val_execution[val_id].acc += _unclaimed_rewards / val_consensus[val_id].stake();
mint(STAKING_CONTRACT_ADDRESS, REWARD);

syscallSnapshot

Function selector

syscallSnapshot() : 0x157eeb21

Function signature

function syscallSnapshot();

Parameters

(none)

Behavior

This syscall sorts the current execution-layer validator set. It selects the top N staked validators as the upcoming consensus validator set. The updated set is stored in state. The previous consensus set is cleared.

Pseudocode
uint64[] filter_top_n_validators = sort(execution_valset);
for i in snapshot_valset:
_val_snapshot[i].stake = 0;
_val_snapshot[i].commission = 0;
snapshot_valset = consensus_valset;
consensus_valset = filter_top_n_validators;
for i in filter_top_n_validators:
_val_consensus[i].stake = val_execution[i].stake;
_val_consensus[i].commission = val_execution[i].commission;

Events

The staking precompiles emit standard events that appear in transaction receipts. These events provide indexed information about validator and delegator actions.

ValidatorCreated

event ValidatorCreated(
uint64 indexed valId,
address indexed auth_delegator
);

Emitted when a validator is added via addValidator.

ValidatorStatusChanged

event ValidatorStatusChanged(
uint64 indexed valId,
address indexed auth_delegator,
uint64 flags
);

Emitted during addValidator, delegate, undelegate, or compound. if the validator's flags change.

Delegate

event Delegate(
uint64 indexed valId,
address indexed delegator,
uint256 amount,
uint64 activationEpoch
);

Emitted when delegation amount is increased, i.e. during addValidator, delegate, or compound.

Undelegate

event Undelegate(
uint64 indexed valId,
address indexed delegator,
uint8 withdrawal_id,
uint256 amount,
uint64 activationEpoch
);

Emitted when a delegator calls undelegate.

Withdraw

event Withdraw(
uint64 indexed valId,
address indexed delegator,
uint8 withdrawal_id,
uint256 amount,
uint64 withdrawEpoch
);

Emitted when a delegator executes withdraw successfully.

ClaimRewards

event ClaimRewards(
uint256 indexed valId,
address indexed delegatorAddress
uint256 amount
);

Emitted when a delegator claims rewards via claimRewards.

CommissionChanged

event CommissionChanged(
uint256 indexed valId,
uint256 oldCommission
uint256 newCommission
);

Emitted when a validator changes commission via changeCommission.

FAQ

Is there a removeValidator function?

There is no direct removeValidator function. Instead, if a validator’s auth_account removes enough stake through undelegate, the validator is removed from the consensus set in a future epoch.

This occurs in either epoch n or epoch n+1, depending on whether the undelegate occurred within the epoch delay period.

Note that the validator’s information must be retained, since other delegators may still be delegating and need to reference that val_id to undelegate/withdraw.

How does a validator change their commission?

A validator can change their commission by calling changeCommission.

What methods give visibility into the list of validator ids?
What methods give visibility into a validator's state?
What methods give visibility into a delegator's delegation to one particular validator?

See getDelegator.

For pending withdrawals by that delegator from that validator, see getWithdrawalRequest.