ARC-38: Create Delegated Staking Standard Program with Commission #66
Replies: 9 comments 11 replies
-
What a clever approach! |
Beta Was this translation helpful? Give feedback.
-
There's typo of |
Beta Was this translation helpful? Give feedback.
-
In case it's missed in the discussion text, here is the link to full aleo/leo code for this delegated staking program: https://github.com/AleoHQ/ARCs/pull/68/files. Something to note -- Aleo instructions supports reading from external mappings, but Leo code does not. You'll see deliberately incorrect mappings in the Leo program in order to get the code to compile to mostly correct Aleo instructions, and then we swap out the incorrect mapping for the correct mapping by hand. Example of a placeholder mapping in Leo -> Example of changing the placeholder in the aleo instructions |
Beta Was this translation helpful? Give feedback.
-
I have a suggestion for this: because the commission is the most difficult factor for depositing, withdrawing and recalculating shares, It may be much easier if we separate the commission aside on each reward procedure |
Beta Was this translation helpful? Give feedback.
-
Are we going to remove the native staking functions in credits.aleo in favor of the new program-based staking methods? |
Beta Was this translation helpful? Give feedback.
-
Doesn't create_withdraw_claim need to have a check on unbonding mapping as well? Because if the protocol is fully unbonded but hasn't been claimed then the call to the claim_withdraw_public might fail if the protocol doesn't have enough balance. |
Beta Was this translation helpful? Give feedback.
-
In withdraw_public, if I set withdrawal_shares to the shares I possess but set total_withdrawal a lot less than what my shares could have given me. Does this mean I will lose all the shares and that excess amount will be distributed among other shareholders? |
Beta Was this translation helpful? Give feedback.
-
@fulltimemike I suppose that we still need to consider the issue of fraudulent deployment of arc_0038.aleo to block any scam possibility. |
Beta Was this translation helpful? Give feedback.
-
This is amazing 👏 |
Beta Was this translation helpful? Give feedback.
-
arc: 0038
title: Create Delegated Staking Standard Program with Commission
authors: chris@demoxlabs.xyz mike@demoxlabs.xyz evan@demoxlabs.xyz, OzielLa
discussion: #66
topic: Application
status: Draft
created: 3/19/2024
Abstract
Validators play an important role in running the network. Validators receive rewards for taking on this responsibility, and this reward is split among the delegators bonded to them. To account for the cost associated with running a validator node, validators should be allowed to take a commission on the rewards earned from bonding to them. This proposal would also allow smaller delegators to participate in staking, as the minimum stake may be prohibitive for smaller investors.
Specification
Required Constants:
Validators will need to set an admin address from which they will be able to control certain aspects of the program, such as the commission rate and address of the validator node
This is the precompiled address of the program itself. This is used for reading the mapping values for the program in
credits.aleo
(account, bonded, etc).Shares in the protocol are equivalent to nanocredits (at time of initial deposit) in order to more precisely calculate the commission due to the validator. This constant helps us move between shares and microcredits for the initial deposit.
A constant used for added precision when performing integer calculations
This is the maximum allowed commission rate for the program. It is relative to the
PRECISION_UNSIGNED
above. e.g. 100u128 = 10%The unbonding period in blocks, as defined in
credits.aleo
Used for batching withdrawal requestsThis minimum stake as defined in
credits.aleo
. Used to ensure an attempt to unbond the full balance will successfully remove the value in thebonded
mapping.Required Mappings:
Key
0u8
stores a boolean showing whether the program has been initialized by the admin.Key
0u8
stores the percentage of rewards taken as commission. Relative toPRECISION_UNSIGNED
e.g. 100u128 = 10%Key
0u8
stores the current address used for bonding to the validator.Key
1u8
stores the next address to use for bonding, in the case the validator address needs to be updated. Automatically reset when the pool funds are bonded.Key
0u8
stores the total balance of microcredits that have been deposited to the program, excluding commissions.Key
0u8
stores the total number of shares owned by delegators. Shares represent the portion of thetotal_balance
that a delegator owns. For example, upon withdrawal, we calculate the amount of microcredits to disburse to the delegator based on the portion of the shares pool they own. (delegator_shares / total_shares
)Maps from a delegator to the number of shares they own.
Key
0u8
stores the total amount of microcredits that delegators are waiting to withdraw.Key
0u8
stores the height at which the current batch of withdrawals will be available for claim. Used to prevent indefinite unbonding due to withdrawals. If the value is not present or equal to0u32
, there is no batch currently unbonding, and a new batch may be started.Maps from a delegator to their
withdrawal_state
, which contains the amount of microcredits they have pending withdrawal and the height at which they will be available to claim.Required Structs:
In normal operation of the protocol, most if not all of the credits owned by the protocol will be bonded to a validator, which means withdrawals must unbond, claim, and then transfer credits to the withdrawer.
To keep track of the amount of microcredits a withdrawer can claim, as well as to keep track of when the withdrawer can claim, this struct holds both properties. For use of this struct, see the
withdrawals
mapping,create_withdraw_claim
finalize block, and thewithdraw_public
finalize block.Required Records:
None
Required Functions:
Initialize
The
initialize
function takes two arguments:commission_rate
, the initial commission rate as au128
andvalidator_address
, theaddress
of the validator the program will bond to.The transition is straightforward - we assert that it is the admin calling this function and that the commission rate is within bounds.
The finalize confirms that the program has not already been initialized and then sets
is_initialized
to true and sets the initial values for each of the program’s mappings.Initial Deposit
initial_deposit
takes three arguments:input_record
(credits.aleo/credits record) andmicrocredits
(u64) which are used to transfer credits into the program, andvalidator_address
(address) used to callbond_public
with the credits transferred in. Note: oncetransfer_public_signer
is added tocredits.aleo
, we won’t need to accept private records and can instead only take microcredits as the singular argument for this function. Currently,transfer_public
uses the caller and not the signer to transfer credits, which means the protocol address would be transferring credits from and to itself in this function.The transition simply asserts that the admin is calling this function and handles the calls to
credits.aleo
for transferring and bonding.The finalize block first confirms that the program has been initialized, and there are no funds present in the program. It then initializes the balance of microcredits and shares (in nanocredits) and assigns the new shares to the admin in
delegator_shares
.Get Commission
get_commission
is an inline function (i.e. a helper function that, when compiled to aleo instructions, is inserted directly everywhere it is called) that takes two arguments:rewards
the total amount of rewards earned from bonding in microcredits, andcommission_rate
the current commission rate of the program both asu128
sget_commission
is used to calculate the portion of rewards that is owed to the validator as commission. We useu128
s for safety against overflow when multiplying and normalize back tou64
by dividing byPRECISION_UNSIGNED
.Calculate New Shares
calculate_new_shares
is an inline function that takes three arguments:balance
the total balance of microcredits in the program (deposits + rewards),deposit
the amount of microcredits being deposited, andshares
the total amount of shares outstanding.calculate_new_shares
is used to determine the amount of shares to mint for the depositor. This is determined by first calculating the ratio of the current amount of shares and the current balance in microcredits. The goal is to keep this ratio constant, so we determine the number of shares to mint based on the relative change in microcredits.This code represents the following formula:
new_shares = ( total_shares / total_balance) * (total_balance + deposit) - total_shares
Set Commission Percent
set_commission_percent
takes one argument:new_commission_rate
as au128
which will be set as the new value forcommission_percent[0u8]
The transition simply confirms that the program admin is calling this function and that the new commission rate is within bounds.
The concerns of the finalize block are to:
Set Next Validator
set_next_validator
takes one argument:validator_address
, the newaddress
that the program will bond to after any currently bonded funds are unbonded.The transition simply confirms that only the admin may call this function, and the finalize block handles setting the value into
validator[1u8]
Unbond All
unbond_all
takes one argument:pool_balance
which is the total amount of microcredits to unbond, as au64
The transition simply calls
unbond_public
with the supplied value, and is permissionless.The finalize block handles the following:
unbond_all
should only occur as part of a validator address changepool_balance
and the actual amount bonded is less than the minimum stake amount)Claim Unbond
claim_unbond
takes no arguments. The transition simply callsclaim_unbond_public
, to claim any unbonded credits - whether from a withdrawal or as a result ofunbond_all
.The finalize block removes the value of
current_batch_height
to allow a new withdrawal batch to begin.Bond All
bond_all
takes two arguments:validator_address
as an address, andamount
as a u64.The transition part is straightforward – the credits.aleo program is called to bond credits held by the protocol to the validator, either the next validator if one is set or to the current validator.
In a nutshell, the concerns of the finalize portion of bond_all are to:
Claim Commission
claim_commission
takes no arguments.claim_commission
is intended for the admin of the protocol to harvest rewards from staking at any point.In a nutshell, the concerns of the finalize portion of
claim_commission
are to:Deposit Public
deposit_public
takes two arguments:input_record
as acredits.aleo/credits record
, andmicrocredits
as a u64. Note: oncetransfer_public_signer
is added tocredits.aleo
, we won’t need to accept private records and can instead only take microcredits as the singular argument for this function. Currently,transfer_public
uses the caller and not the signer to transfer credits, which means the protocol address would be transferring credits from and to itself in this function.The transition part is straightforward – the
credits.aleo
program is called to transfer credits from the depositor to the protocol address.In a nutshell, the concerns of the finalize portion of
deposit_public
are to:Deposit public does not automatically bond the credits. This is for several reasons. By not directly bonding credits, we do not enforce a minimum deposit. We also save the depositor on fees, since the constraints of the bond call are not a part of the overall transition.
bond_all
must be called in order to bond the microcredits held by the protocol to the validator.Withdraw Public
withdraw_public
takes two arguments:withdrawal_shares
andtotal_withdrawal
, both as u64s. Withdrawal shares are the amount of shares to burn in exchange fortotal_withdrawal
microcredits.withdraw_public
is meant to be used in the normal operation of the protocol – most credits (excepting deposits and pending withdrawals) should be bonded to the validator.The transition part is straightforward – the
credits.aleo
program is called to unbond thetotal_withdrawal
microcredits from the protocol address.In a nutshell, the concerns of the finalize portion of
withdraw_public
are to:total_withdrawal
microcredits are less than or equal to the proportion of microcredits held by the withdrawal_sharesclaim_height
Get New Batch Height
get_new_batch_height
is an inline function (i.e. a helper function that, when compiled to aleo instructions, is inserted directly everywhere it is called) takes one argument:height
as a u32, representing the current block height.get_new_batch_height
rounds up the currentblock.height
to the nearest 1000th block height. Given an input of 0, we expect an output of 1000. Given input of 999, we expect an output of 1000.Create Withdraw Claim
create_withdraw_claim
takes one argument:withdrawal_shares
, as a u64. Withdrawal shares are the amount of shares to burn in exchange for their proportional amount of the protocol’s microcredits.create_withdraw_claim
is intended to be used in special circumstances for the protocol. The credits of the protocol should all be unbonded, which means that credits are not earning rewards, and withdrawers do not need to callunbond_public
from thecredits.aleo
program.In a nutshell, the concerns of the finalize portion of
create_withdraw_claim
are to:withdrawal_state
so that the withdrawer may claim their creditsClaim Withdrawal Public
claim_withdrawal_public
takes two arguments:recipient
as an address, andamount
as a u64. Given that a withdrawer has a withdrawal claim, they can pass in arecipient
to receiveamount
. Note, to keep the protocol simple, theamount
must be the full amount of their withdrawal claim.claim_withdrawal_public
is intended to be used at any point that the withdrawer has a withdraw claim with aclaim_height
that is greater than or equal to the current block height.In a nutshell, the concerns of the finalize portion of
claim_withdrawal_public
are to:withdrawal_state
so that the withdrawer may claim more credits in a separate withdrawal processTest Cases
We are implementing test cases to ensure this protocol works as expected, and always allows for depositors to recollect their funds. The major test cases we want to include in our suite are:
-- In normal operation
-- When everything has unbonded through the protocol
-- When a validator forcibly unbonds the protocol’s stake
Dependencies
As this is an application ARC, there are no dependencies other than what is currently available in Aleo, except for transfer_public_signer, which is necessary to remove private state from the program.
Backwards Compatibility
Not necessary.
Security & Compliance
This is an application ARC standard, so this only affects the security of managing assets on-chain. We will have this code audited by an external firm. For compliance purposes, this program will operate publicly, without records.
References
Beta Was this translation helpful? Give feedback.
All reactions