diff --git a/Cargo.lock b/Cargo.lock index 09ae57e..00419ad 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3085,7 +3085,9 @@ dependencies = [ "serum-registry-cli", "serum-registry-rewards-cli", "serum_dex", + "solana-client", "solana-sdk", + "spl-token 2.0.6", ] [[package]] diff --git a/Makefile b/Makefile index 212c03c..929fc46 100644 --- a/Makefile +++ b/Makefile @@ -12,13 +12,15 @@ TEST_PAYER_FILEPATH="$(HOME)/.config/solana/id.json" # # The solana cluster to test against. Defaults to local. # +ifndef TEST_CLUSTER TEST_CLUSTER=l -#TEST_CLUSTER=devnet +endif # # The url of TEST_CLUSTER. # +ifndef TEST_CLUSTER_URL TEST_CLUSTER_URL="http://localhost:8899" -#TEST_CLUSTER_URL="https://devnet.solana.com" +endif # # One can optionally set this along with the test-program command # to avoid redeploying everytime tests are run. diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 9781979..fdaafba 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -9,7 +9,7 @@ name = "serum" path = "src/main.rs" [features] -dev = ["serum-common", "serum-common-tests", "serum_dex", "solana-sdk", "serde_json", "serum-registry"] +dev = ["serum-common", "serum-common-tests", "serum_dex", "solana-sdk", "serde_json", "serum-registry", "spl-token", "solana-client"] default = [] @@ -28,4 +28,6 @@ serum-common-tests = { path = "../common/tests", optional = true } serum_dex = { path = "../dex", default-features = false, features = ["client"], optional = true } solana-sdk = { version = "1.3.14", optional = true } serde_json = { version = "1.0.59", optional = true } -serum-registry = { path = "../registry", optional = true } \ No newline at end of file +serum-registry = { path = "../registry", optional = true } +spl-token = { version = "2.0.6", optional = true } +solana-client = { version = "1.4.4", optional = true } \ No newline at end of file diff --git a/cli/src/dev/faucet.rs b/cli/src/dev/faucet.rs new file mode 100644 index 0000000..cfe5842 --- /dev/null +++ b/cli/src/dev/faucet.rs @@ -0,0 +1,81 @@ +use anyhow::{anyhow, Result}; +use serum_common::client::rpc; +use serum_context::Context; +use solana_client::rpc_config::RpcSendTransactionConfig; +use solana_sdk::commitment_config::CommitmentConfig; +use solana_sdk::instruction::{AccountMeta, Instruction}; +use solana_sdk::pubkey::Pubkey; +use solana_sdk::signature::Signer; +use solana_sdk::sysvar; +use solana_sdk::transaction::Transaction; + +const FAUCET_SIZE: usize = 77; + +// `admin` must be the current mint authority. +// +// Faucet program here: +// +// https://github.com/paul-schaaf/spl-token-faucet/blob/main/src/program/src/instruction.rs. +pub fn create(ctx: &Context, mint: Pubkey, amount: u64, admin: Pubkey) -> Result { + let faucet_pid = ctx.faucet_pid.ok_or(anyhow!("faucet not provided"))?; + let faucet = rpc::create_account_rent_exempt( + &ctx.rpc_client(), + &ctx.wallet()?, + FAUCET_SIZE, + &faucet_pid, + )? + .pubkey(); + + let ixs = { + let (faucet_pda, _nonce) = + Pubkey::find_program_address(&["faucet".to_string().as_bytes()], &faucet_pid); + + let set_auth_ix = spl_token::instruction::set_authority( + &spl_token::ID, + &mint, + Some(&faucet_pda), + spl_token::instruction::AuthorityType::MintTokens, + &admin, + &[], + )?; + + let create_faucet_ix = { + let accounts = vec![ + AccountMeta::new_readonly(mint, false), + AccountMeta::new(faucet, false), + AccountMeta::new(sysvar::rent::ID, false), + AccountMeta::new_readonly(admin, false), + ]; + + let mut data = vec![0]; + data.extend_from_slice(&amount.to_le_bytes()); + + Instruction { + program_id: faucet_pid, + data, + accounts, + } + }; + + [set_auth_ix, create_faucet_ix] + }; + + let _tx = { + let client = ctx.rpc_client(); + let payer = ctx.wallet()?; + let (recent_hash, _fee_calc) = client.get_recent_blockhash()?; + let tx = + Transaction::new_signed_with_payer(&ixs, Some(&payer.pubkey()), &[&payer], recent_hash); + let sig = client.send_and_confirm_transaction_with_spinner_and_config( + &tx, + CommitmentConfig::single(), + RpcSendTransactionConfig { + skip_preflight: true, + ..RpcSendTransactionConfig::default() + }, + )?; + sig + }; + + Ok(faucet) +} diff --git a/cli/src/dev.rs b/cli/src/dev/mod.rs similarity index 80% rename from cli/src/dev.rs rename to cli/src/dev/mod.rs index d520254..b6d128e 100644 --- a/cli/src/dev.rs +++ b/cli/src/dev/mod.rs @@ -13,11 +13,16 @@ use solana_sdk::pubkey::Pubkey; use solana_sdk::signature::Signer; use std::num::NonZeroU64; +mod faucet; + #[derive(Debug, Clap)] pub enum Command { /// Creates 1) SRM mint, 2) MSRM mint 3) SRM funded token account, and /// 4) MSRM funded token account, all owned by the configured wallet. - InitMint, + InitMint { + #[clap(short, long)] + faucet: bool, + }, AllocateAccount { #[clap(short, long)] program_id: Pubkey, @@ -34,7 +39,7 @@ pub enum Command { pub fn run(ctx: Context, cmd: Command) -> Result<()> { match cmd { - Command::InitMint => init_mint(&ctx), + Command::InitMint { faucet } => init_mint(&ctx, faucet), Command::AllocateAccount { program_id, size } => allocate_account(&ctx, program_id, size), Command::GenerateOrders { coin_wallet, @@ -43,7 +48,7 @@ pub fn run(ctx: Context, cmd: Command) -> Result<()> { } } -fn init_mint(ctx: &Context) -> Result<()> { +fn init_mint(ctx: &Context, faucet: bool) -> Result<()> { // Doesn't matter. let program_id = Pubkey::new_from_array([0; 32]).to_string(); let payer_filepath = &ctx.wallet_path.to_string(); @@ -51,13 +56,22 @@ fn init_mint(ctx: &Context) -> Result<()> { std::env::set_var(serum_common_tests::TEST_PROGRAM_ID, program_id); std::env::set_var(serum_common_tests::TEST_PAYER_FILEPATH, payer_filepath); std::env::set_var(serum_common_tests::TEST_CLUSTER, cluster); - let (_client, g) = serum_common_tests::genesis::(); + let (client, g) = serum_common_tests::genesis::(); + let (srm_faucet, msrm_faucet) = match faucet { + false => (None, None), + true => { + let srm_faucet = + faucet::create(ctx, g.srm_mint, 1_000_000_000_000, client.payer().pubkey())?; + let msrm_faucet = + faucet::create(ctx, g.msrm_mint, 1_000_000_000_000, client.payer().pubkey())?; + (Some(srm_faucet), Some(msrm_faucet)) + } + }; println!( "{}", serde_json::json!({ "wallet": g.wallet.to_string(), - "mintAuthority": g.mint_authority.to_string(), "srmMint": g.srm_mint.to_string(), "msrmMint": g.msrm_mint.to_string(), "god": g.god.to_string(), @@ -65,6 +79,14 @@ fn init_mint(ctx: &Context) -> Result<()> { "godBalanceBefore": g.god_balance_before, "godMsrmBalanceBefore": g.god_msrm_balance_before, "godOwner": g.god_owner.to_string(), + "srmFaucet": match srm_faucet { + None => "null".to_string(), + Some(f) => f.to_string(), + }, + "msrmFaucet": match msrm_faucet { + None => "null".to_string(), + Some(f) => f.to_string(), + } }) ); diff --git a/common/src/client/mod.rs b/common/src/client/mod.rs index 76131e3..13cbda8 100644 --- a/common/src/client/mod.rs +++ b/common/src/client/mod.rs @@ -32,7 +32,7 @@ impl FromStr for Cluster { "l" | "localnet" => Ok(Cluster::Localnet), "g" | "debug" => Ok(Cluster::Debug), _ => Err(anyhow::Error::msg( - "Cluster must be one of [testnet, mainnet, devnet] or be an http or https url\n", + "Cluster must be one of [localnet, testnet, mainnet, devnet] or be an http or https url\n", )), } } diff --git a/context/src/lib.rs b/context/src/lib.rs index 0a1789f..61989dd 100644 --- a/context/src/lib.rs +++ b/context/src/lib.rs @@ -26,6 +26,7 @@ pub struct Context { pub meta_entity_pid: Pubkey, pub lockup_pid: Pubkey, pub dex_pid: Pubkey, + pub faucet_pid: Option, } impl Context { @@ -85,6 +86,7 @@ struct Programs { pub meta_entity_pid: String, pub lockup_pid: String, pub dex_pid: String, + pub faucet_pid: Option, } impl Config { @@ -98,11 +100,20 @@ impl Config { impl TryFrom for Context { type Error = anyhow::Error; fn try_from(cfg: Config) -> std::result::Result { + let cluster = cfg + .network + .cluster + .map_or(Ok(Default::default()), |c| c.parse())?; + let faucet_pid = cfg + .programs + .faucet_pid + .or_else(|| match &cluster { + Cluster::Devnet => Some("4bXpkKSV8swHSnwqtzuboGPaPDeEgAn4Vt8GfarV5rZt".to_string()), + _ => None, + }) + .map(|f| f.parse().unwrap()); Ok(Self { - cluster: cfg - .network - .cluster - .map_or(Ok(Default::default()), |c| c.parse())?, + cluster, wallet_path: cfg .wallet_path .map_or(Default::default(), |p| WalletPath(p)), @@ -114,6 +125,7 @@ impl TryFrom for Context { lockup_pid: cfg.programs.lockup_pid.parse()?, meta_entity_pid: cfg.programs.meta_entity_pid.parse()?, dex_pid: cfg.programs.dex_pid.parse()?, + faucet_pid, }) } } diff --git a/scripts/deploy-staking.sh b/scripts/deploy-staking.sh index 181b8f6..a3347a1 100755 --- a/scripts/deploy-staking.sh +++ b/scripts/deploy-staking.sh @@ -6,10 +6,39 @@ # Does deployment + initialization of all programs and accounts needed to run # the staking + lockup application. # +# Usage: +# +# ./scripts/deploy-staking.sh +# ################################################################################ -CLUSTER=l -#CLUSTER=devnet +set -euox pipefail + +CLUSTER=$1 + +if [ "$CLUSTER" = "devnet" ]; then + echo "Deploying to Devnet..." + FAUCET_FLAG="--faucet" + CONFIG_FILE=~/.config/serum/cli/devnet.yaml + CLUSTER_URL="https://devnet.solana.com" +elif [ "$CLUSTER" = "mainnet" ]; then + echo "Deploying to Mainnet..." + FAUCET_FLAG="" + CONFIG_FILE=~/.config/serum/cli/mainnet.yaml + CLUSTER_URL="https://api.mainnet-beta.solana.com" +elif [ "$CLUSTER" = "localnet" ]; then + echo "Deploying to Localnet..." + FAUCET_FLAG="" + CONFIG_FILE=~/.config/serum/cli/localnet.yaml + CLUSTER_URL="http://localhost:8899" +else + echo "Invalid cluster" + exit 1 +fi + +# +# Seconds. +# DEACTIVATION_TIMELOCK=60 WITHDRAWAL_TIMELOCK=60 # @@ -24,8 +53,9 @@ STAKE_RATE=1000000 # 1 MSRM (0 decimals) to stake. # STAKE_RATE_MEGA=1 - -CONFIG_FILE=~/.config/serum/cli/dev.yaml +# +# Must be built with the `dev` feature on. +# serum=$(pwd)/target/debug/serum main() { @@ -40,6 +70,7 @@ main() { # # Build all programs. # + echo "Building all programs..." make -s -C lockup build make -s -C registry build make -s -C registry/meta-entity build @@ -49,34 +80,51 @@ main() { # # Deploy all the programs. # - local pids=$(make -s -C registry deploy-all) - local rewards_pids=$(make -s -C registry/rewards deploy-all) + echo "Deploying all programs..." + local pids=$(TEST_CLUSTER="$CLUSTER" TEST_CLUSTER_URL="$CLUSTER_URL" make -s -C registry deploy-all) + local rewards_pids=$(TEST_CLUSTER="$CLUSTER" TEST_CLUSTER_URL="$CLUSTER_URL" make -s -C registry/rewards deploy-all) local registry_pid=$(echo $pids | jq .registryProgramId -r) local lockup_pid=$(echo $pids | jq .lockupProgramId -r) local meta_entity_pid=$(echo $pids | jq .metaEntityProgramId -r) - local dex_pid=$(echo $rewards_pids | jq .dexProgramId -r) local rewards_pid=$(echo $rewards_pids | jq .rewardsProgramId -r) + local dex_pid=$(echo $rewards_pids | jq .dexProgramId -r) # - # Generate genesis state. + # Generate genesis state. Use dummy accounts, if needed. # - local genesis=$($serum dev init-mint) + local srm_mint="SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt" + local msrm_mint="MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L" + local god="FhmUh2PEpTzUwBWPt4qgDBeqfmb2ES3T64CkT1ZiktSS" # Dummy. + local god_msrm="FhmUh2PEpTzUwBWPt4qgDBeqfmb2ES3T64CkT1ZiktSS" # Dummy. + local srm_faucet="None" + local msrm_faucet="None" + if [ "$CLUSTER" != "mainnet" ]; then + echo "Genesis initialization..." + genesis=$($serum --config $CONFIG_FILE dev init-mint $FAUCET_FLAG) - local srm_mint=$(echo $genesis | jq .srmMint -r) - local msrm_mint=$(echo $genesis | jq .msrmMint -r) - local god=$(echo $genesis | jq .god -r) - local god_msrm=$(echo $genesis | jq .godMsrm -r) + srm_mint=$(echo $genesis | jq .srmMint -r) + msrm_mint=$(echo $genesis | jq .msrmMint -r) + god=$(echo $genesis | jq .god -r) + god_msrm=$(echo $genesis | jq .godMsrm -r) + srm_faucet=$(echo $genesis | jq .srmFaucet -r) + msrm_faucet=$(echo $genesis | jq .msrmFaucet -r) + fi # # Write out the CLI configuration file. # + echo "Writing config $CONFIG_FILE..." mkdir -p $(dirname $CONFIG_FILE) cat << EOM > $CONFIG_FILE --- network: cluster: $CLUSTER +# +# SRM Faucet: $srm_faucet +# MSRM Faucet: $msrm_faucet +# mints: srm: $srm_mint msrm: $msrm_mint @@ -87,11 +135,13 @@ programs: meta_entity_pid: $meta_entity_pid lockup_pid: $lockup_pid dex_pid: $dex_pid + EOM # # Now intialize all the accounts. # + echo "Initializing registrar..." local rInit=$($serum --config $CONFIG_FILE \ registry init \ --deactivation-timelock $DEACTIVATION_TIMELOCK \ @@ -104,6 +154,7 @@ EOM local registrar_nonce=$(echo $rInit | jq .nonce -r) local reward_q=$(echo $rInit | jq .rewardEventQueue -r) + echo "Initializing lockup..." local lInit=$($serum --config $CONFIG_FILE \ lockup initialize) @@ -113,6 +164,7 @@ EOM # 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 \ @@ -125,6 +177,7 @@ EOM # # Add the registry to the lockup program whitelist. # + echo "Adding registry to the lockup whitelist..." $serum --config $CONFIG_FILE \ lockup gov \ --safe $safe \ @@ -136,6 +189,7 @@ EOM # # Log the generated TypeScript. # + set +e read -r -d '' VAR << EOM { srm: new PublicKey('${srm_mint}'),