docs: Staking, lockup, and cranking rewards (#66)
This commit is contained in:
parent
e7bcee8ecf
commit
689e382cd3
|
@ -0,0 +1,99 @@
|
||||||
|
# Serum Lockups
|
||||||
|
|
||||||
|
WARNING: All code related to Serum Lockups is unaudited. Use at your own risk.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The **Lockup** program provides a simple mechanism on Solana to lockup tokens
|
||||||
|
of any mint, and release those funds over time as defined by a vesting schedule.
|
||||||
|
Although these lockups track a target **beneficiary**, who will eventually receive the
|
||||||
|
funds upon vesting, a proper deployment of the program will ensure this **beneficiary**
|
||||||
|
can never actually retrieve tokens before vesting. Funds are *never* in an SPL
|
||||||
|
token wallet owned by a user, and are completely program controlled.
|
||||||
|
|
||||||
|
## Creating a Vesting Account
|
||||||
|
|
||||||
|
Lockup occurs when tokens are transferred into the program creating a **Vesting**
|
||||||
|
account on behalf of a **beneficiary** via the `CreateVesting` instruction.
|
||||||
|
There are three parameters to specify:
|
||||||
|
|
||||||
|
* End timestamp - unix timestamp (in seconds) of the time when all tokens will unlock.
|
||||||
|
* Period count - the amount of times vesting should occur.
|
||||||
|
* Deposit amount - the total amount to vest.
|
||||||
|
|
||||||
|
Together these parameters form a linearly unlocked vesting schedule. For example,
|
||||||
|
if one wanted to lock 100 SRM that unlocked twice, 50 SRM each time, over the next year, we'd
|
||||||
|
use the following parameters (in JavaScript).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const endTimestamp = Date.now()/1000 + 60*60*24*365;
|
||||||
|
const periodCount = 2;
|
||||||
|
const depositAmount = 100 * 10**6; // 6 decimal places.
|
||||||
|
```
|
||||||
|
|
||||||
|
From these three parameters, one can deduce the total amount vested at any given time.
|
||||||
|
See how this is done via the `total_vested` function [here](https://github.com/project-serum/serum-dex/blob/master/lockup/src/accounts/vesting.rs#L72).
|
||||||
|
|
||||||
|
Once created, a **Vesting** account's schedule cannot be mutated.
|
||||||
|
|
||||||
|
## Withdrawing from a Vesting Account
|
||||||
|
|
||||||
|
Withdrawing is straightforward. Simply invoke the `Withdraw` instruction, specifying an
|
||||||
|
amount to withdraw from a **Vesting** account. Of course, the **beneficiary** of the
|
||||||
|
**Vesting** account must sign the transaction, but if enough time has passed for an
|
||||||
|
amount to be vested, and, if the funds are indeed held in the lockup program's vault
|
||||||
|
(a point we will get to next) then the program will release the funds.
|
||||||
|
|
||||||
|
## Whitelisted Programs
|
||||||
|
|
||||||
|
Although funds cannot be freely withdrawn prior to vesting, they can be sent to/from
|
||||||
|
other programs that are part of a **Whitelist**. These programs are completely trusted.
|
||||||
|
Any bug or flaw in the design of a whitelisted program can lead to locked tokens being released
|
||||||
|
ahead of schedule, so it's important to take great care when whitelisting any program.
|
||||||
|
|
||||||
|
This of course begs the question, who approves the whitelist? The **Lockup** program doesn't
|
||||||
|
care. There simply exists an **authority** key that can, for example, be a democratic multisig,
|
||||||
|
a single admin, or the program itself (in which case the authority ceases to exist). Whoever controls
|
||||||
|
that key controls the whitelist. So when using the **Lockup** program, one should always be
|
||||||
|
cognizant of it's whitelist governance, which ultimately anchors one's trust in the program,
|
||||||
|
if any at all.
|
||||||
|
|
||||||
|
## Creating a Whitelisted Program
|
||||||
|
|
||||||
|
To create a whitelisted program that receives withdrawals/deposits from/to the Lockup program,
|
||||||
|
one needs to implement the whitelist transfer interface, which assumes nothing about the
|
||||||
|
`instruction_data` but requires accounts to be provided in a specific [order](https://github.com/project-serum/serum-dex/blob/master/registry/program/src/deposit.rs#L18).
|
||||||
|
|
||||||
|
We'll use staking locked SRM as a working example.
|
||||||
|
|
||||||
|
### Staking Locked SRM
|
||||||
|
|
||||||
|
Suppose you have a vesting account with some funds you want to stake.
|
||||||
|
|
||||||
|
First, one must add the staking **Registry** as a whitelisted program, so that the Lockup program
|
||||||
|
allows the movement of funds. This is done by the `WhitelistAdd` instruction.
|
||||||
|
|
||||||
|
Once whitelisted, **Vesting** accounts can transfer funds out of the **Lockup** program and
|
||||||
|
into the **Registry** program by invoking the **Lockup** program's `WhitelistWithdraw`
|
||||||
|
instruction, which, other than access control, simply relays the instruction from the
|
||||||
|
**Lockup** program to the **Registry** program along with accounts, signing the
|
||||||
|
Cross-Program-Invocation (CPI) with the **Lockup**'s program-derived-address to allow
|
||||||
|
the transfer of funds, which ultimately is done by the **Registry**. *It is the Registry's responsibility
|
||||||
|
to track where these funds came from, keep them locked, and eventually send them back.*
|
||||||
|
|
||||||
|
When creating this [instruction](https://github.com/project-serum/serum-dex/blob/master/lockup/src/lib.rs#L79)
|
||||||
|
on the client, there are two parameters to provide:
|
||||||
|
the maximum `amount` available for transfer and the opaque CPI `instruction_data`.
|
||||||
|
In the example, here, it would be the Borsh serialized instruction data for the
|
||||||
|
**Registry**'s `Deposit` instruction.
|
||||||
|
|
||||||
|
The other direction follows, similarly. One invokes the `WhitelistDeposit` instruction
|
||||||
|
on the **LockupProgram**, relaying the transaction to the **Registry**, which ultimately
|
||||||
|
transfer funds back into the lockup program on behalf of the **Vesting** account.
|
||||||
|
|
||||||
|
## Major version upgrades.
|
||||||
|
|
||||||
|
Assuming the `authority` account is set on the **Lockup** program, we can use this Whitelist
|
||||||
|
mechanism to do major version upgrades of the lockup program. One can whitelist the
|
||||||
|
new **Lockup** program, and then all **Vesting** accounts would invidiually perform the migration
|
||||||
|
by transferring their funds to the new proigram via the `WhitelistWithdraw` instruction.
|
|
@ -0,0 +1,115 @@
|
||||||
|
# Serum Node Setup
|
||||||
|
|
||||||
|
WARNING: All code related to Serum Nodes is unaudited. Use at your own risk.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
Serum nodes are run by staked node leaders, who become eligible for cranking
|
||||||
|
when their node has at least 1 MSRM staked. These "cranking rewards"
|
||||||
|
are effectively transaction fees earned for operating the DEX.
|
||||||
|
|
||||||
|
For an introduction to the DEX and the idea of cranking, see
|
||||||
|
[A technical introduction to the Serum DEX](https://docs.google.com/document/d/1isGJES4jzQutI0GtQGuqtrBUqeHxl_xJNXdtOv4SdII/edit).
|
||||||
|
|
||||||
|
The way cranking rewards work is simple, instead of sending transactions directly to the DEX,
|
||||||
|
a cranker sends transactions to a cranking rewards vendor, which is an on-chain
|
||||||
|
Solana program that proxies all requests to the DEX, recording the amount of events
|
||||||
|
cranked, and then sends a reward to the cranker's wallet as a function of the number
|
||||||
|
of events processed and the reward vendor's fee rate.
|
||||||
|
|
||||||
|
(Note that, although similar in spirit, the cranking rewards vendor is an entirely different
|
||||||
|
program and account from the **Registry**'s reward vendors. Only node leaders are eligible
|
||||||
|
to crank.)
|
||||||
|
|
||||||
|
If the rewards vendor's vault becomes empty or if the node leader's Entity stake
|
||||||
|
balance ever transitions to **inactive**, then the vendor will refuse to pay
|
||||||
|
rewards to the node leader until the vault is funded and/or the node becomes **active** again.
|
||||||
|
|
||||||
|
## Install Rust
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
|
||||||
|
```
|
||||||
|
|
||||||
|
On Linux systems you may need to install additional dependencies. On Ubuntu,
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo apt-get install -y pkg-config build-essential python3-pip jq
|
||||||
|
```
|
||||||
|
|
||||||
|
## Install the CLI
|
||||||
|
|
||||||
|
The CLI is a work in progress, so there's not yet a proper installer.
|
||||||
|
For now, we can use Cargo.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cargo install --git https://github.com/project-serum/serum-dex serum-cli
|
||||||
|
```
|
||||||
|
|
||||||
|
To verify the installation worked, run `serum -h`.
|
||||||
|
|
||||||
|
## Setup your CLI Config
|
||||||
|
|
||||||
|
Add your YAML config for Devnet at `~/.config/serum/cli/config.yaml`.
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
network:
|
||||||
|
cluster: devnet
|
||||||
|
|
||||||
|
mints:
|
||||||
|
srm: 4Ghge2MMPmWXeD2FR541akGhjjgUi7RUtk7DBP5bTwGB
|
||||||
|
msrm: 5PsAVQLCrgtKqZpLdg7HsTXHMcvVCQ1c4bFHHej8Axxn
|
||||||
|
|
||||||
|
programs:
|
||||||
|
rewards_pid: nwEt8jsBDCjV5vNg9c5YN9ktyak314DCwVTTuA3Swd9
|
||||||
|
registry_pid: FigXetJcXogqm94qfmyKWy6U5KJAwtxSgJMjUHercVQp
|
||||||
|
meta_entity_pid: 8wfM5sd5Yivn4WWkcSp4pNua7ytDvjeyLVLaU3QWiLAT
|
||||||
|
lockup_pid: CiNaYvdnQ42BNdbKvvAapHxiP18pvc3Vk5WuZ59ia64x
|
||||||
|
dex_pid: F9b23Ph1JdBev2fULXTZLzaxVh2nYVdMVq9CTEaEZrid
|
||||||
|
```
|
||||||
|
|
||||||
|
When operating over multiple networks, you can specify your config file with the
|
||||||
|
`serum --config <path>` option.
|
||||||
|
|
||||||
|
## Cranking a market
|
||||||
|
|
||||||
|
Finally you can run your crank. Pick a market and run
|
||||||
|
|
||||||
|
```bash
|
||||||
|
serum crank consume-event-rewards \
|
||||||
|
--market <address> \
|
||||||
|
--log-directory <path> \
|
||||||
|
--rewards.receiver <address> \
|
||||||
|
--rewards.registry-entity <address> \
|
||||||
|
--rewards.instance <address>
|
||||||
|
```
|
||||||
|
|
||||||
|
If the given `--rewards.registry-entity` is properly staked, and if the given
|
||||||
|
`--rewards.instance` is funded, then you should see your token account
|
||||||
|
`--rewards.receiver` start to receive rewards with each event consumed.
|
||||||
|
|
||||||
|
## Finding a market to crank
|
||||||
|
|
||||||
|
You can crank any market of your choosing. To find all markets one can use the `getProgramAccounts`
|
||||||
|
API exposed by the Solana JSON RPC. In python,
|
||||||
|
|
||||||
|
```python
|
||||||
|
def find_market_addresses(program_id: str):
|
||||||
|
resp = requests.post('https://devnet.solana.com', json={
|
||||||
|
'jsonrpc': '2.0',
|
||||||
|
'method': 'getProgramAccounts',
|
||||||
|
'id': 1,
|
||||||
|
'params': [
|
||||||
|
program_id,
|
||||||
|
{
|
||||||
|
'encoding': 'base64',
|
||||||
|
'filters': [
|
||||||
|
# Base58 encoding of 0x0300000000000000
|
||||||
|
{'memcmp': {'offset': 5, 'bytes': 'W723RTUpoZ'}},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).json()
|
||||||
|
return [info['pubkey'] for info in resp['result']]
|
||||||
|
```
|
|
@ -0,0 +1,204 @@
|
||||||
|
# Serum Staking
|
||||||
|
|
||||||
|
WARNING: All code related to Serum Staking is unaudited. Use at your own risk.
|
||||||
|
|
||||||
|
## Introduction
|
||||||
|
|
||||||
|
The **Registry** program provides the central point of on-chain coordination for stakers,
|
||||||
|
providing two features: a gateway to the staking pool and a repository for node state.
|
||||||
|
|
||||||
|
As a gateway to the staking pool, the **Registry** controls who and when stakers can enter
|
||||||
|
and exit the pool. This enables controls like a mandatory 1 MSRM node deposit before entering
|
||||||
|
the pool, the staking of *locked* SRM, and a 1 week timelock for withdrawals. As a repository for
|
||||||
|
node state, the **Registry** allows other programs to
|
||||||
|
perform access control on staked accounts. This is useful for programs building on-top of the
|
||||||
|
registry. For example, a **crank-rewards** program could use Registry accounts to determine if
|
||||||
|
a node-leader is eligble for payment. A **governance** program could tally votes from
|
||||||
|
node member accounts based on their stake weight.
|
||||||
|
|
||||||
|
In short, the **Registry** allows node entities to be created, members to stake, and rewards to
|
||||||
|
be generated, while providing a foundation for other programs to build on.
|
||||||
|
|
||||||
|
Here, we'll discuss it's role in facilitating staking.
|
||||||
|
|
||||||
|
## Creating a member account.
|
||||||
|
|
||||||
|
Before being able to enter the stake pool, one must create a [Member](https://github.com/project-serum/serum-dex/blob/master/registry/src/accounts/member.rs) account with the
|
||||||
|
[Registrar](https://github.com/project-serum/serum-dex/blob/master/registry/src/accounts/registrar.rs), providing identity to the **Registry** program. By default, each member has four types of token vaults making up a set
|
||||||
|
of balances owned by the program on behalf of a **Member**:
|
||||||
|
|
||||||
|
* Free-balances
|
||||||
|
* Pending
|
||||||
|
* Stake
|
||||||
|
* Stake pool token
|
||||||
|
|
||||||
|
Each of these vaults provide a unit of balance isolation unique to a **Member**.
|
||||||
|
That is, although we provide a pooling mechanism, funds between **Member** accounts do not
|
||||||
|
share SPL token accounts. The only way for funds to move is for a **Member** to authorize
|
||||||
|
instructions that either exit the system or move funds between a **Member**'s own vaults.
|
||||||
|
|
||||||
|
## Depositing and Withdrawing.
|
||||||
|
|
||||||
|
Funds enter and exit the **Registry** through the `Deposit` and `Withdraw` instructions,
|
||||||
|
which transfer funds into and out of the **free-balances** vault.
|
||||||
|
As the name suggests, all funds in this vault are freely available, unrestricted, and
|
||||||
|
earn zero interest. The vault is purely a gateway for funds to enter the system. One
|
||||||
|
could even perform token transfer directly into the vault, but it's recommended to use the
|
||||||
|
instruction API, which provide additional safety checks to help ensure the funds are moved
|
||||||
|
as intended.
|
||||||
|
|
||||||
|
## Staking.
|
||||||
|
|
||||||
|
Once deposited, **Members** invoke the `Stake` instruction to transfer funds from
|
||||||
|
their **free-balances-vault** to their **stake-vault**, creating newly minted
|
||||||
|
**stake-pool-tokens** as proof of the stake deposit. These new tokens represent
|
||||||
|
one's proportional right to all rewards distributed to the staking pool and are offered
|
||||||
|
by the **Registry** program at a fixed price of 1000 SRM--to start and subject to change
|
||||||
|
in future versions. This creates some restrictions on the underlying stake.
|
||||||
|
|
||||||
|
## Unstaking
|
||||||
|
|
||||||
|
Once staked, funds cannot be immediately withdrawn. Rather, the **Registry** will enforce
|
||||||
|
a one week timelock before funds are released. Upon executing the `StartStakeWithdrawal`
|
||||||
|
instruction, three operations execute. 1) The given amount of stake pool tokens will be burned.
|
||||||
|
2) Staked funds proportional to the stake pool tokens burned will be transferred from the
|
||||||
|
**Member**'s **stake-vault** to the **Member**'s **pending-vault**. 3) A `PendingWithdrawal`
|
||||||
|
account will be created as proof of the stake withdrawal, stamping the current block's
|
||||||
|
`unix_timestamp` onto the account. When the timelock period ends, a **Member** can invoke the
|
||||||
|
`EndStakeWithdrawal` instruction to complete the transfer out of the `pending-vault` and
|
||||||
|
into the `free-balances`, providing the previously printed `PendingWithdrawal`
|
||||||
|
receipt to the program as proof that the timelock has passed. At this point, the exit
|
||||||
|
from the stake pool is complete, and the funds are ready to be used again.
|
||||||
|
|
||||||
|
## Reward Design Motivation
|
||||||
|
|
||||||
|
Feel free to skip this section and jump to the **Reward Vendors** section if you want to
|
||||||
|
just see how rewards work.
|
||||||
|
|
||||||
|
One could imagine several ways to drop rewards onto a staking pool, each with their own downsides.
|
||||||
|
Of course what you want is, for a given reward amount, to atomically snapshot the state
|
||||||
|
of the staking pool and to distribute it proportionally to all stake holders. Effectively,
|
||||||
|
an on chain program such as
|
||||||
|
|
||||||
|
```python
|
||||||
|
for account in stake_pool:
|
||||||
|
account.token_amount += total_reward * (account.stake_pool_token.amount / stake_pool_token.supply)
|
||||||
|
```
|
||||||
|
|
||||||
|
Surprisingly, such a mechanism is not immediately obvious.
|
||||||
|
|
||||||
|
First, the above program is a non starter. Not only does the SPL token
|
||||||
|
program not have the ability to iterate through all accounts for a given mint within a program,
|
||||||
|
but, since Solana transactions require the specification of all accounts being accessed
|
||||||
|
in a transaction (this is how it achieves parallelism), such a transaction's size would be
|
||||||
|
well over the limit. So modifying global state atomically in a single transaction is out of the
|
||||||
|
question.
|
||||||
|
|
||||||
|
So if you can't do this on chain, one can try doing it off chain. One could write an program to
|
||||||
|
snapshot the pool state, and just airdrop tokens onto the pool. This works, but
|
||||||
|
adds an additional layer of trust. Who snapshots the pool state? At what time?
|
||||||
|
How do you know they calculated the rewards correctly? What happens if my reward was not given?
|
||||||
|
This is not auditable or verifiable. And if you want to answer these questions, requires
|
||||||
|
complex off-chain protocols that require either fancy cryptography or effectively
|
||||||
|
recreating a BFT system off chain.
|
||||||
|
|
||||||
|
Another solution considerered was to use a uniswap-style AMM pool (without the swapping).
|
||||||
|
This has a lot of advantages. First it's easy to reason about and implement in a single transaction.
|
||||||
|
To drop rewards gloablly onto the pool, one can deposit funds directly into the pool, in which case
|
||||||
|
the reward is automatically received by owners of the staking pool token upon redemption, a process
|
||||||
|
known as "gulping"--since dropping rewards increases the total value of the pool
|
||||||
|
while their proportion of the pool remained constant.
|
||||||
|
|
||||||
|
However, there are enough downsides with using an AMM style pool to offset the convience.
|
||||||
|
Unfortunately, we lose the nice balance isolation property **Member** accounts have, because
|
||||||
|
tokens have to be pooled into the same vault, which is an additional security concern that could
|
||||||
|
easily lead to loss of funds, e.g., if there's a bug in the redemption calculation. Moreover, dropping
|
||||||
|
arbitrary tokens onto the pool is a challenge. Not only do you have to create new pool vaults for
|
||||||
|
every new token you drop onto the pool, but you also need to have stakers purchase those tokens to enter
|
||||||
|
the pool. So not only are we staking SRM, but we're also staking other tokens. An additional oddity is that
|
||||||
|
as rewards are dropped onto the pool, the price to enter the pool monotonically increases. Remember, entering this
|
||||||
|
type of pool requires "creating" pool tokens, i.e., depositing enough tokens so that you don't dilute
|
||||||
|
any other member. So if a single pool token represents one SRM. And if a single SRM is dropped onto every
|
||||||
|
member of the pool, all the existing member's shares are now worth two SRM. So to enter the pool without
|
||||||
|
dilution, one would have to "create" at a price of 2 SRM per share. This means that rewarding
|
||||||
|
stakers becomes more expensive over time. One could of course solve this problem by implementing
|
||||||
|
arbitrary `n:m` pool token splits, which leads us right back to the problem of mutating global account
|
||||||
|
state for an SPL token. Furthermore, we haven't even touched upon dropping locked token rewards,
|
||||||
|
which of course can't be dropped directly onto a pool, since they are controlled by an additional
|
||||||
|
program controlling it's own set of accounts. So, if we did go with an AMM style pool, we'd need a separate
|
||||||
|
mechanism for handling locked token rewards. Ideally, we'd have a single mechanism for both.
|
||||||
|
|
||||||
|
## Reward Vendors
|
||||||
|
|
||||||
|
Instead of trying to *push* rewards to users via a direct transfer or airdrop, we can use a *polling* model
|
||||||
|
where users effectively event source a log on demand.
|
||||||
|
|
||||||
|
When a reward is created, we do two things:
|
||||||
|
|
||||||
|
1) Create a **Reward Vendor** account with an associated token vault holding the reward.
|
||||||
|
2) Assign the **Reward Vendor** the next available position in a **Reward Event Queue**. Then, to retrieve
|
||||||
|
a reward, a staker invokes the `ClaimReward` command, providing a proof that the funds were
|
||||||
|
staked at the time of the reward being dropped, and in response, the program transfers or,
|
||||||
|
some might say, *vends* the proportion of the dropped reward to the polling **Member**. The
|
||||||
|
operation completes by incrementing the **Member**'s queue cursor, ensuring that a given
|
||||||
|
reward can only be processed once.
|
||||||
|
|
||||||
|
This allows us to provide a way of dropping rewards to the stake pool in a way that is
|
||||||
|
on chain and verifiable. Of course, it requires an external trigger, some account willing to
|
||||||
|
transfer funds to a new **RewardVendor**, but that is outside of the scope of the **Registry**
|
||||||
|
program. The reward dropper can be an off chain BFT committee, or it can be an on-chain multisig. It can be a charitable individual,
|
||||||
|
or funds can flow directly from the DEX, which itself creates a Reward Vendor from fees collected.
|
||||||
|
It doesn't matter to the **Registry** program.
|
||||||
|
|
||||||
|
Note that this solution also allows for rewards to be denominated in any token, not just SRM.
|
||||||
|
Since rewards are paid out by the vendor immediately and to a token account of the **Member**'s
|
||||||
|
choosing, it *just works*. Even more, this extends to arbitrary program accounts, particularly
|
||||||
|
**Locked SRM**. A **Reward Vendor** needs to additionally know the accounts and instruction data
|
||||||
|
to relay to the program, but otherwise, the mechanism is the same. The details of **Locked SRM** will
|
||||||
|
be explained in an additional document.
|
||||||
|
|
||||||
|
|
||||||
|
## Reward Eligibility
|
||||||
|
|
||||||
|
To be eligible for reward, a node **Entity** must have 1 MSRM staked to it, marking
|
||||||
|
it "active". If the MSRM stake balance ever drops below 1, the node will be marked as pending
|
||||||
|
deactivation, starting a week long countdown ending with the **Entity** transitioning into the
|
||||||
|
inactive state, at which point rewards cease to be distributed. As soon one enters the pending
|
||||||
|
deactivation state, 1 MSRM needs to either be restaked by an associated **Member**
|
||||||
|
or all **Members** should move to a new **Entity**. Note that transitioning to an inactive state
|
||||||
|
does not affect one's **Member** vaults. It only affects one's ability to retrieve rewards from
|
||||||
|
a vendor.
|
||||||
|
|
||||||
|
## Misc
|
||||||
|
|
||||||
|
### Entity
|
||||||
|
|
||||||
|
An **Entity** is an additional **Registry** owned account representing a collection of **Member**
|
||||||
|
accounts with an associated "node leader", who is eligible for additional rewards via "node duties".
|
||||||
|
These "duties" amount to earning what are, effectively, transaction fees. An additional document will describe nodes
|
||||||
|
and their setup, but for the purposes of staking, all a **Member** needs to know is that it
|
||||||
|
belongs to an **Entity** account, and the stake associated with that **Entity** determines
|
||||||
|
its state.
|
||||||
|
|
||||||
|
### Member Accounts
|
||||||
|
|
||||||
|
This document describes 4 vault types belonging to **Member** accounts, making up a single set of balances. However,
|
||||||
|
there are two stake pools: one for SRM holders and one for MSRM holders, so really there are 8 vault types.
|
||||||
|
Additionally there are two types of balance groups: locked and unlocked.
|
||||||
|
As a result, there are really 16 vaults for each **Member**, 8 types of vaults in 2 separate sets,
|
||||||
|
each isolated from the other, so that locked tokens don't get intermingled with unlocked tokens.
|
||||||
|
|
||||||
|
But if we're staking locked tokens, we need to ensure we don't accidently unlock tokens.
|
||||||
|
To maintain the **Lockup** program's invariant, we need a mechanism for safely entering and exiting
|
||||||
|
the system; that is, locked tokens should only be sent back to the lockup program.
|
||||||
|
|
||||||
|
As a result, we assign each set of balances, locked and unlocked, it's own unique identifier.
|
||||||
|
For the unlocked set of accounts the identifier is the **Member** account's beneficiary
|
||||||
|
(i.e. the authority of the entire account), and for the locked set of accounts it's the vesting account's program
|
||||||
|
derived address, controlled by the lockup program. Upon depositing or withdrawing from the **Registry**,
|
||||||
|
the program ensures that tokens coming into the system are from vaults owned by the correct balance
|
||||||
|
identifier. Similarly, tokens going out of the system can only go to vaults owned by the correct balance
|
||||||
|
identifier.
|
||||||
|
|
||||||
|
In future work, this setup will allow us to extend the staking program to stake arbitrary assets owned by
|
||||||
|
arbitrary programs on behalf of an account owner.
|
Loading…
Reference in New Issue