Add account creation to node setup tutorial (#78)

This commit is contained in:
Armani Ferrante 2020-12-27 12:29:03 -08:00 committed by GitHub
parent b202b8f8ee
commit a51d6e8d65
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 202 additions and 95 deletions

1
Cargo.lock generated
View File

@ -3123,6 +3123,7 @@ version = "0.1.0"
dependencies = [
"anyhow",
"clap 3.0.0-beta.2",
"serde_json",
"serum-context",
"serum-registry-rewards-client",
"solana-client-gen",

View File

@ -27,6 +27,9 @@ pub struct Context {
pub lockup_pid: Pubkey,
pub dex_pid: Pubkey,
pub faucet_pid: Option<Pubkey>,
pub registrar: Pubkey,
pub safe: Pubkey,
pub rewards_instance: Pubkey,
}
impl Context {
@ -66,6 +69,7 @@ struct Config {
pub network: Network,
pub mints: Mints,
pub programs: Programs,
pub accounts: Option<Accounts>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
@ -89,6 +93,13 @@ struct Programs {
pub faucet_pid: Option<String>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct Accounts {
pub registrar: String,
pub safe: String,
pub rewards_instance: String,
}
impl Config {
fn from(path: &str) -> Result<Self> {
let rdr = fs::File::open(path)?;
@ -126,6 +137,18 @@ impl TryFrom<Config> for Context {
meta_entity_pid: cfg.programs.meta_entity_pid.parse()?,
dex_pid: cfg.programs.dex_pid.parse()?,
faucet_pid,
registrar: cfg
.accounts
.as_ref()
.map_or(Ok(Default::default()), |a| a.registrar.parse::<Pubkey>())?,
safe: cfg
.accounts
.as_ref()
.map_or(Ok(Default::default()), |a| a.safe.parse())?,
rewards_instance: cfg
.accounts
.as_ref()
.map_or(Ok(Default::default()), |a| a.rewards_instance.parse())?,
})
}
}

View File

@ -210,9 +210,6 @@ pub enum Command {
#[derive(Debug, Clap, Clone)]
pub struct RewardsContext {
/// Account controlling the rewards vault for crankers.
#[clap(long = "rewards.instance")]
pub instance: Pubkey,
/// Token account rewards are sent to when cranking.
#[clap(long = "rewards.receiver")]
pub receiver: Pubkey,
@ -220,6 +217,8 @@ pub struct RewardsContext {
#[clap(long = "rewards.registry-entity")]
pub registry_entity: Pubkey,
#[clap(skip)]
pub instance: Pubkey,
#[clap(skip)]
pub url: String,
#[clap(skip)]
pub program_id: Pubkey,
@ -337,6 +336,7 @@ pub fn start(ctx: Option<Context>, opts: Opts) -> Result<()> {
let ctx = ctx.expect("context must exist when consuming rewards");
r_ctx.url = opts.cluster.url().to_string();
r_ctx.program_id = ctx.rewards_pid;
r_ctx.instance = ctx.rewards_instance;
init_logger(log_directory);
// Unused by the DEX. Nevertheless required to be passed in.

View File

@ -9,17 +9,13 @@ 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).
[A technical introduction to the Serum DEX](https://docs.google.com/document/d/1isGJES4jzQutI0GtQGuqtrBUqeHxl_xJNXdtOv4SdII/edit). For an introduction to staking, see [Serum Staking](./staking.md).
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.)
of events processed and the reward vendor's fee rate. This proxy program can be found [here](../registry/rewards/program).
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
@ -39,7 +35,6 @@ 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
@ -50,30 +45,7 @@ To verify the installation worked, run `serum -h`.
## Setup your CLI Config
Add your YAML config for Mainnet Beta at `~/.config/serum/cli/config.yaml`.
For Mainnet Beta
```yaml
---
network:
cluster: mainnet
mints:
srm: SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt
msrm: MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L
programs:
rewards_pid: 8xYo1X6uw7SBngXgPzib8jghWb8BhiiVxv5yV799Tw3G
registry_pid: Gw1XNGbSnx7PJcHTTuxxhWfkjjPmq29Qkv1hWbVFnrDp
meta_entity_pid: 9etE5ZjHZTrZ2wQfyfTSp5WBxjpvaakNJa5fSVToZn17
lockup_pid: 6GSn1woRF541HaiEWqNofYn8quzJuRBPi1nwoho8zNnh
dex_pid: EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o
```
When operating over multiple networks, you can specify your config file with the
`serum --config <path>` option. For example, one might want to test
against Devnet with the following config
Add your YAML config for Devnet at `~/.config/serum/cli/config.yaml`.
```yaml
---
@ -81,35 +53,83 @@ network:
cluster: devnet
mints:
srm: 5ya5rnzm5MkvCXhLDCJqAUzT16A37ks2DekkfoNnwNMn
msrm: 3fHm9sEBS3CukUX366mwzn3YwEc5zWNzR4JcGK6EVQad
srm: Bap9SwT53SjGPeKq4LAC6i86fCzEzUGGHsYfRiCFSGyF
msrm: CmtL8e86367ZLiAuJELx4WqmDz7dRnD1oyaiq4TQDdEU
programs:
rewards_pid: EXzpf5GBfUQkwLeLEJXLmVKxGpxyMQWxpudYxogW4ad8
registry_pid: 3ofaHrxu7RdqH8m1wXfVrsTqgwctmx2NsHPv6m7oh1dy
meta_entity_pid: 8v8hwdeyBhmV4y235F9XQ7g5Vz2EYvJTkGqTfrh3Hz5f
lockup_pid: Az4dD6YeA4akzz4Qx3RuQqaCtLEaDiBT8u7mDL24sbAu
dex_pid: DiDDva9iDSXTtJ4CeWXbKdDvQ3M6g5G87PZGUGvxi3eV
rewards_pid: 7sXyzeu6GJqkXZz8VhjdsXvDg1xR1PEkXbbDaxMc186C
registry_pid: CKKz2WYvneiLb2mzouWc4iPpKisuXs5XKYn7ZUrRjkeK
meta_entity_pid: BsVgsh8mqi3qn8eKRiye1a4eF8Qwqot8p2n3ZMNdL2UY
lockup_pid: 8wreDpv5nuY1gee1X4wkqtRkzoGypVYzWBrMmzipAJKN
dex_pid: 9MVDeYQnJmN2Dt7H44Z8cob4bET2ysdNu2uFJcatDJno
accounts:
registrar: 7Tzf4D4BU1tzwitXbHeUf7bwMNSSVQXfzPsgnbM5RY7d
safe: CxiGCt8kVm5BuzmWycJdZwXsYt52iLB8Huty5r1xRvRZ
rewards_instance: 32qFc9QWX4wBU6EFZ9FzyxGthSHNwCUKaN7APn3GAH2X
```
## Cranking a market
If not specified, the `wallet` key will be searched for in the standard location:
`~/.config/solana/id.json` and used as the **payer** for all transactions initiated
by the CLI.
## Create an Entity
An **Entity** is the on-chain Solana account representing a node and
it's collective **Member** accounts.
To create an **Entity** with the **Registrar**, run
```bash
serum registry create-entity --name <string> --about <string>
```
Entering your node's `name` and `about` info, which can be displayed in UIs. Note that, by default,
the wallet creating the entity will be tagged as the node leader, which is the address eligible for
earning crank rewards.
## Create a Member
After creating a node entity, use your new **Entity** address to create a member account, which will
allow you to stake.
```bash
serum registry create-member --entity <address>
```
## Activate your Node
Once created, one must "activate" a node by staking MSRM before being able to earn rewards. Any **Member**
associated with the **Entity** can stake this MSRM.
For now, it's recommended to do this through the UI at https://project-serum.github.io/serum-ts/stake-ui,
where you can
* Select a network.
* Connect your wallet.
* Deposit 1 MSRM into your **Member** account via the **Deposit** button. If you're on Devnet,
[airdrop](https://www.spl-token-ui.com/#/token-faucets) yourself tokens. The faucet address can be found in the **Environment** tab of the UI.
* Stake the newly deposited 1 MSRM via the **Stake** tab into the **Mega Stake Pool**.
You should see that your Entity is now in the `Active` state making it eligible for rewards.
## 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>
serum crank consume-event-rewards \
--market <address> \
--log-directory <path> \
--rewards.receiver <address> \
--rewards.registry-entity <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
If the given `--rewards.registry-entity` is properly staked, and if the configured
rewards `instance` is funded, then you should see your SPL token account
`--rewards.receiver` start to receive rewards with each event consumed.
## Finding a market to crank
## Finding a Market to Crank
You can crank any market of your choosing. To find all market addresses one can use the `getProgramAccounts`
API exposed by the Solana JSON RPC. In python,
@ -133,3 +153,31 @@ def find_market_addresses(program_id: str):
}).json()
return [info['pubkey'] for info in resp['result']]
```
## Switching to Mainnet Beta
When operating over multiple networks, you can specify your config file with the
`serum --config <path>` option. For example, when switching to Mainnet Beta,
one can use the following config.
```yaml
---
network:
cluster: mainnet
mints:
srm: SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt
msrm: MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L
programs:
rewards_pid: 4bcHoAgLP9NBje1oVo9WKRDYkvSxcqtJeTSXMRFX5AdZ
registry_pid: 6J7ZoSxtKJUjVLpGRcBrEtvE2T3YVf9mfKUaicndzpCc
meta_entity_pid: 68gpi9be8NNVTDViQxSYtbM1788uebczX2Vz7obSnQRz
lockup_pid: 4nvqpaMz7H12VgHSABjEDFmH62MoWP3BxfMG3BAFQiBo
dex_pid: EUqojwWA2rd19FZrzeBncJsm38Jm1hEhE3zsmX3bRc2o
accounts:
registrar: 8ZS85GGfa92JH6vrmpy8fgQQEBLWwYUwWmpBhcA94fDH
safe: Dp5zzdTLnYNq9E6H81YQu1ucNLK3FzH3Ah3KedydiNgE
rewards_instance: BX1aNRFES78bz9G8GJWmrYSkRVWcV5wdnvSWfXJhTEkE
```

View File

@ -30,9 +30,6 @@ pub enum Command {
},
/// Creates a node entity, setting the active wallet as leader.
CreateEntity {
/// Registrar account address.
#[clap(short, long)]
registrar: Pubkey,
#[clap(short, long)]
name: String,
#[clap(short, long)]
@ -40,6 +37,11 @@ pub enum Command {
#[clap(short, long)]
image_url: Option<String>,
},
/// Creates a member account with the given entity.
CreateMember {
#[clap(short, long)]
entity: Pubkey,
},
/// Updates an entity. Active wallet must be the node leader.
UpdateEntity {
#[clap(short, long)]
@ -59,8 +61,6 @@ pub enum Command {
token: Pubkey,
#[clap(long)]
vendor: Pubkey,
#[clap(short, long)]
registrar: Pubkey,
},
/// Sends all unclaimed funds from an expired locked reward vendor to a given
/// account.
@ -70,8 +70,6 @@ pub enum Command {
token: Pubkey,
#[clap(long)]
vendor: Pubkey,
#[clap(short, long)]
registrar: Pubkey,
},
}
@ -126,41 +124,33 @@ pub fn run(ctx: Context, cmd: Command) -> Result<()> {
stake_rate_mega,
),
Command::CreateEntity {
registrar,
name,
about,
image_url,
} => create_entity_cmd(&ctx, registry_pid, registrar, name, about, image_url),
} => create_entity_cmd(&ctx, registry_pid, ctx.registrar, name, about, image_url),
Command::CreateMember { entity } => create_member_cmd(&ctx, entity),
Command::UpdateEntity {
name,
about,
image_url,
entity,
} => update_entity_cmd(&ctx, name, about, image_url, entity),
Command::ExpireUnlockedReward {
token,
vendor,
registrar,
} => {
Command::ExpireUnlockedReward { token, vendor } => {
let client = ctx.connect::<Client>(registry_pid)?;
let resp = client.expire_unlocked_reward(ExpireUnlockedRewardRequest {
token,
vendor,
registrar,
registrar: ctx.registrar,
})?;
println!("Transaction executed: {:?}", resp.tx);
Ok(())
}
Command::ExpireLockedReward {
token,
vendor,
registrar,
} => {
Command::ExpireLockedReward { token, vendor } => {
let client = ctx.connect::<Client>(registry_pid)?;
let resp = client.expire_locked_reward(ExpireLockedRewardRequest {
token,
vendor,
registrar,
registrar: ctx.registrar,
})?;
println!("Transaction executed: {:?}", resp.tx);
Ok(())
@ -195,6 +185,26 @@ fn create_entity_cmd(
Ok(())
}
fn create_member_cmd(ctx: &Context, entity: Pubkey) -> Result<()> {
let beneficiary = ctx.wallet()?;
let client = ctx.connect::<Client>(ctx.registry_pid)?;
// Vesting vault authority.
let (delegate, _nonce) = Pubkey::find_program_address(
&[ctx.safe.as_ref(), beneficiary.pubkey().as_ref()],
&ctx.lockup_pid,
);
let CreateMemberResponse { member, .. } = client.create_member(CreateMemberRequest {
entity,
beneficiary: &beneficiary,
delegate,
registrar: ctx.registrar,
})?;
println!("Member created: {}", member.to_string());
Ok(())
}
fn update_entity_cmd(
ctx: &Context,
name: Option<String>,

View File

@ -10,4 +10,5 @@ clap = "3.0.0-beta.1"
serum-context = { path = "../../../context" }
solana-client-gen = { path = "../../../solana-client-gen" }
anyhow = "1.0.32"
serum-registry-rewards-client = { path = "../client" }
serum-registry-rewards-client = { path = "../client" }
serde_json = "1.0.59"

View File

@ -106,7 +106,10 @@ fn gov_cmd(ctx: &Context, cmd: GovCommand) -> Result<()> {
fee_rate,
authority: wallet.pubkey(),
})?;
println!("Rewards instance created: {:?}", resp.instance);
println!(
"{}",
serde_json::json!({"instance": resp.instance.to_string()})
);
}
GovCommand::SetAuthority {
new_authority,

View File

@ -9,7 +9,6 @@ lazy_static::lazy_static! {
.expect("Registrar has a fixed size");
}
/// Registry defines the account representing an instance of the program.
#[derive(Clone, Debug, Default, PartialEq, BorshSerialize, BorshDeserialize, BorshSchema)]
pub struct Registrar {
/// Set by the program on initialization.

View File

@ -61,6 +61,10 @@ STAKE_RATE=10000000
#
STAKE_RATE_MEGA=1
#
# Unit of srm to pay crankers.
#
CRANK_FEE_RATE=1
#
# Must be built with the `dev` feature on.
#
serum=$(pwd)/target/debug/serum
@ -158,7 +162,7 @@ EOM
# Now intialize all the accounts.
#
echo "Initializing registrar..."
local rInit=$($serum --config $CONFIG_FILE \
local r_init=$($serum --config $CONFIG_FILE \
registry init \
--deactivation-timelock $DEACTIVATION_TIMELOCK \
--withdrawal-timelock $WITHDRAWAL_TIMELOCK \
@ -166,29 +170,15 @@ EOM
--stake-rate $STAKE_RATE \
--stake-rate-mega $STAKE_RATE_MEGA)
local registrar=$(echo $rInit | jq .registrar -r)
local registrar_nonce=$(echo $rInit | jq .nonce -r)
local reward_q=$(echo $rInit | jq .rewardEventQueue -r)
local registrar=$(echo $r_init | jq .registrar -r)
local registrar_nonce=$(echo $r_init | jq .nonce -r)
local reward_q=$(echo $r_init | jq .rewardEventQueue -r)
echo "Initializing lockup..."
local lInit=$($serum --config $CONFIG_FILE \
local l_init=$($serum --config $CONFIG_FILE \
lockup initialize)
local safe=$(echo $lInit | jq .safe -r)
#
# Initialize a node entity. Hack until we separate joining entities
# from creating member accounts.
#
echo "Creating the default node entity..."
local createEntity=$($serum --config $CONFIG_FILE \
registry create-entity \
--registrar $registrar \
--about "This the default entity all new members join." \
--image-url " " \
--name "Default" )
local entity=$(echo $createEntity | jq .entity -r)
local safe=$(echo $l_init | jq .safe -r)
#
# Add the registry to the lockup program whitelist.
@ -202,6 +192,38 @@ EOM
--nonce $registrar_nonce \
--program-id $registry_pid
#
# Create a crank rewards instance.
#
local crank_rewards=$($serum --config $CONFIG_FILE rewards gov init \
--registrar $registrar \
--reward-mint $srm_mint \
--fee-rate $CRANK_FEE_RATE)
local rewards_instance=$(echo $crank_rewards | jq .instance -r)
#
# Append to the previously generated config file.
#
cat << EOM >> $CONFIG_FILE
accounts:
registrar: $registrar
safe: $safe
rewards_instance: $rewards_instance
EOM
#
# Initialize the default node entity.
#
echo "Creating the default node entity..."
local create_entity=$($serum --config $CONFIG_FILE \
registry create-entity \
--about "This the default entity all new members join." \
--image-url " " \
--name "Default" )
local entity=$(echo $create_entity | jq .entity -r)
#
# Log the generated TypeScript.
#