Merge pull request #48 from project-serum/armani/deploy
This commit is contained in:
commit
6648f185ec
|
@ -101,10 +101,12 @@ dependencies = [
|
|||
name = "anchor-cli"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anchor-lang",
|
||||
"anchor-syn",
|
||||
"anyhow",
|
||||
"clap 3.0.0-beta.2",
|
||||
"dirs",
|
||||
"flate2",
|
||||
"heck",
|
||||
"serde",
|
||||
"serde_json",
|
||||
|
@ -112,6 +114,7 @@ dependencies = [
|
|||
"serum-common",
|
||||
"shellexpand",
|
||||
"solana-client",
|
||||
"solana-program",
|
||||
"solana-sdk",
|
||||
"syn 1.0.57",
|
||||
"toml",
|
||||
|
|
|
@ -8,12 +8,11 @@ edition = "2018"
|
|||
name = "anchor"
|
||||
path = "src/main.rs"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
clap = "3.0.0-beta.1"
|
||||
anyhow = "1.0.32"
|
||||
syn = { version = "1.0.54", features = ["full", "extra-traits"] }
|
||||
anchor-lang = { path = "../" }
|
||||
anchor-syn = { path = "../syn", features = ["idl"] }
|
||||
serde_json = "1.0"
|
||||
shellexpand = "2.1.0"
|
||||
|
@ -21,7 +20,9 @@ serde_yaml = "0.8"
|
|||
toml = "0.5.8"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
solana-sdk = "1.5.0"
|
||||
solana-program = "1.5.0"
|
||||
solana-client = "1.4.4"
|
||||
serum-common = { git = "https://github.com/project-serum/serum-dex", features = ["client"] }
|
||||
dirs = "3.0"
|
||||
heck = "0.3.1"
|
||||
heck = "0.3.1"
|
||||
flate2 = "1.0.19"
|
||||
|
|
|
@ -148,7 +148,7 @@ pub fn extract_lib_name(path: impl AsRef<Path>) -> Result<String> {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Program {
|
||||
pub lib_name: String,
|
||||
pub path: PathBuf,
|
||||
|
|
295
cli/src/main.rs
295
cli/src/main.rs
|
@ -1,10 +1,20 @@
|
|||
use crate::config::{find_cargo_toml, read_all_programs, Config, Program};
|
||||
use anchor_lang::idl::IdlAccount;
|
||||
use anchor_lang::{AnchorDeserialize, AnchorSerialize};
|
||||
use anchor_syn::idl::Idl;
|
||||
use anyhow::Result;
|
||||
use anyhow::{anyhow, Result};
|
||||
use clap::Clap;
|
||||
use flate2::read::ZlibDecoder;
|
||||
use flate2::write::ZlibEncoder;
|
||||
use flate2::Compression;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use solana_client::rpc_client::RpcClient;
|
||||
use solana_client::rpc_config::RpcSendTransactionConfig;
|
||||
use solana_program::instruction::{AccountMeta, Instruction};
|
||||
use solana_sdk::commitment_config::CommitmentConfig;
|
||||
use solana_sdk::pubkey::Pubkey;
|
||||
use solana_sdk::signature::Signer;
|
||||
use solana_sdk::transaction::Transaction;
|
||||
use std::fs::{self, File};
|
||||
use std::io::prelude::*;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
@ -34,8 +44,39 @@ pub enum Command {
|
|||
Test,
|
||||
/// Creates a new program.
|
||||
New { name: String },
|
||||
/// Outputs an interface definition file.
|
||||
/// Commands for interact with interface definitions.
|
||||
Idl {
|
||||
#[clap(subcommand)]
|
||||
subcmd: IdlCommand,
|
||||
},
|
||||
/// Deploys the workspace, creates IDL accounts, and runs the migration
|
||||
/// script.
|
||||
Deploy {
|
||||
#[clap(short, long)]
|
||||
url: Option<String>,
|
||||
#[clap(short, long)]
|
||||
keypair: Option<String>,
|
||||
},
|
||||
/// Runs the deploy migration script.
|
||||
Migrate {
|
||||
#[clap(short, long)]
|
||||
url: String,
|
||||
},
|
||||
/// Not yet implemented. Please use `solana program deploy` command to
|
||||
/// upgrade your program.
|
||||
Upgrade {},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clap)]
|
||||
pub enum IdlCommand {
|
||||
/// Initializes a program's IDL account. Can only be run once.
|
||||
Init {
|
||||
program_id: Pubkey,
|
||||
#[clap(short, long)]
|
||||
filepath: String,
|
||||
},
|
||||
/// Parses an IDL from source.
|
||||
Parse {
|
||||
/// Path to the program's interface definition.
|
||||
#[clap(short, long)]
|
||||
file: String,
|
||||
|
@ -43,12 +84,12 @@ pub enum Command {
|
|||
#[clap(short, long)]
|
||||
out: Option<String>,
|
||||
},
|
||||
/// Deploys the workspace to the configured cluster.
|
||||
Deploy {
|
||||
/// Fetches an IDL for the given program from a cluster.
|
||||
Fetch {
|
||||
program_id: Pubkey,
|
||||
/// Output file for the idl (stdout if not specified).
|
||||
#[clap(short, long)]
|
||||
url: Option<String>,
|
||||
#[clap(short, long)]
|
||||
keypair: Option<String>,
|
||||
out: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
|
@ -60,13 +101,13 @@ fn main() -> Result<()> {
|
|||
Command::Build { idl } => build(idl),
|
||||
Command::Test => test(),
|
||||
Command::New { name } => new(name),
|
||||
Command::Idl { file, out } => {
|
||||
if out.is_none() {
|
||||
return idl(file, None);
|
||||
}
|
||||
idl(file, Some(&PathBuf::from(out.unwrap())))
|
||||
}
|
||||
Command::Idl { subcmd } => idl(subcmd),
|
||||
Command::Deploy { url, keypair } => deploy(url, keypair),
|
||||
Command::Upgrade {} => {
|
||||
println!("This command is not yet implemented. Please use `solana program deploy`.");
|
||||
Ok(())
|
||||
}
|
||||
Command::Migrate { url } => migrate(&url),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -169,7 +210,7 @@ fn build_ws(
|
|||
Some(idl) => Some(PathBuf::from(idl)),
|
||||
None => {
|
||||
let cfg_parent = match cfg_path.parent() {
|
||||
None => return Err(anyhow::anyhow!("Invalid Anchor.toml")),
|
||||
None => return Err(anyhow!("Invalid Anchor.toml")),
|
||||
Some(parent) => parent,
|
||||
};
|
||||
fs::create_dir_all(cfg_parent.join("target/idl"))?;
|
||||
|
@ -184,10 +225,7 @@ fn build_ws(
|
|||
|
||||
fn build_all(_cfg: Config, cfg_path: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
|
||||
match cfg_path.parent() {
|
||||
None => Err(anyhow::anyhow!(
|
||||
"Invalid Anchor.toml at {}",
|
||||
cfg_path.display()
|
||||
)),
|
||||
None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
|
||||
Some(parent) => {
|
||||
let files = fs::read_dir(parent.join("programs"))?;
|
||||
for f in files {
|
||||
|
@ -212,7 +250,7 @@ fn build_cwd(idl_out: Option<String>) -> Result<()> {
|
|||
// Runs the build command outside of a workspace.
|
||||
fn _build_cwd(cargo_toml: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
|
||||
match cargo_toml.parent() {
|
||||
None => return Err(anyhow::anyhow!("Unable to find parent")),
|
||||
None => return Err(anyhow!("Unable to find parent")),
|
||||
Some(p) => std::env::set_current_dir(&p)?,
|
||||
};
|
||||
|
||||
|
@ -234,12 +272,29 @@ fn _build_cwd(cargo_toml: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
|
|||
Some(o) => PathBuf::from(&o.join(&idl.name).with_extension("json")),
|
||||
};
|
||||
|
||||
write_idl(&idl, Some(&out))
|
||||
write_idl(&idl, OutFile::File(out))
|
||||
}
|
||||
|
||||
fn idl(file: String, out: Option<&Path>) -> Result<()> {
|
||||
let idl = extract_idl(&file)?;
|
||||
write_idl(&idl, out)
|
||||
// Fetches an IDL for the given program_id.
|
||||
fn fetch_idl(program_id: Pubkey) -> Result<Idl> {
|
||||
let cfg = Config::discover()?.expect("Inside a workspace").0;
|
||||
let client = RpcClient::new(cfg.cluster.url().to_string());
|
||||
|
||||
let idl_addr = IdlAccount::address(&program_id);
|
||||
|
||||
let account = client
|
||||
.get_account_with_commitment(&idl_addr, CommitmentConfig::recent())?
|
||||
.value
|
||||
.map_or(Err(anyhow!("Account not found")), Ok)?;
|
||||
|
||||
// Cut off account discriminator.
|
||||
let mut d: &[u8] = &account.data[8..];
|
||||
let idl_account: IdlAccount = AnchorDeserialize::deserialize(&mut d)?;
|
||||
|
||||
let mut z = ZlibDecoder::new(&idl_account.data[..]);
|
||||
let mut s = Vec::new();
|
||||
z.read_to_end(&mut s)?;
|
||||
serde_json::from_slice(&s[..]).map_err(Into::into)
|
||||
}
|
||||
|
||||
fn extract_idl(file: &str) -> Result<Idl> {
|
||||
|
@ -247,15 +302,61 @@ fn extract_idl(file: &str) -> Result<Idl> {
|
|||
anchor_syn::parser::file::parse(&*file)
|
||||
}
|
||||
|
||||
fn write_idl(idl: &Idl, out: Option<&Path>) -> Result<()> {
|
||||
fn idl(subcmd: IdlCommand) -> Result<()> {
|
||||
match subcmd {
|
||||
IdlCommand::Init {
|
||||
program_id,
|
||||
filepath,
|
||||
} => idl_init(program_id, filepath),
|
||||
IdlCommand::Parse { file, out } => idl_parse(file, out),
|
||||
IdlCommand::Fetch { program_id, out } => idl_fetch(program_id, out),
|
||||
}
|
||||
}
|
||||
|
||||
fn idl_init(program_id: Pubkey, idl_filepath: String) -> Result<()> {
|
||||
let cfg = Config::discover()?.expect("Inside a workspace").0;
|
||||
let keypair = cfg.wallet.to_string();
|
||||
|
||||
let bytes = std::fs::read(idl_filepath)?;
|
||||
let idl: Idl = serde_json::from_reader(&*bytes)?;
|
||||
let idl_address = create_idl_account(&cfg, &keypair, &program_id, &idl)?;
|
||||
|
||||
println!("Idl account created: {:?}", idl_address);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn idl_parse(file: String, out: Option<String>) -> Result<()> {
|
||||
let idl = extract_idl(&file)?;
|
||||
let out = match out {
|
||||
None => OutFile::Stdout,
|
||||
Some(out) => OutFile::File(PathBuf::from(out)),
|
||||
};
|
||||
write_idl(&idl, out)
|
||||
}
|
||||
|
||||
fn idl_fetch(program_id: Pubkey, out: Option<String>) -> Result<()> {
|
||||
let idl = fetch_idl(program_id)?;
|
||||
let out = match out {
|
||||
None => OutFile::Stdout,
|
||||
Some(out) => OutFile::File(PathBuf::from(out)),
|
||||
};
|
||||
write_idl(&idl, out)
|
||||
}
|
||||
|
||||
fn write_idl(idl: &Idl, out: OutFile) -> Result<()> {
|
||||
let idl_json = serde_json::to_string_pretty(idl)?;
|
||||
match out.as_ref() {
|
||||
None => println!("{}", idl_json),
|
||||
Some(out) => std::fs::write(out, idl_json)?,
|
||||
match out {
|
||||
OutFile::Stdout => println!("{}", idl_json),
|
||||
OutFile::File(out) => std::fs::write(out, idl_json)?,
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
enum OutFile {
|
||||
Stdout,
|
||||
File(PathBuf),
|
||||
}
|
||||
|
||||
// Builds, deploys, and tests all workspace programs in a single command.
|
||||
fn test() -> Result<()> {
|
||||
// Switch directories to top level workspace.
|
||||
|
@ -290,13 +391,13 @@ fn test() -> Result<()> {
|
|||
let idl_out = PathBuf::from("target/idl")
|
||||
.join(&idl.name)
|
||||
.with_extension("json");
|
||||
write_idl(&idl, Some(&idl_out))?;
|
||||
write_idl(&idl, OutFile::File(idl_out))?;
|
||||
}
|
||||
|
||||
// Run the tests.
|
||||
if let Err(e) = std::process::Command::new("mocha")
|
||||
.arg("-t")
|
||||
.arg("10000")
|
||||
.arg("1000000")
|
||||
.arg("tests/")
|
||||
.env("ANCHOR_PROVIDER_URL", cfg.cluster.url())
|
||||
.stdout(Stdio::inherit())
|
||||
|
@ -379,6 +480,10 @@ fn deploy(url: Option<String>, keypair: Option<String>) -> Result<()> {
|
|||
|
||||
// Add metadata to all IDLs.
|
||||
for (program, address) in deployment {
|
||||
// Store the IDL on chain.
|
||||
let idl_address = create_idl_account(&cfg, &keypair, &address, &program.idl)?;
|
||||
println!("IDL account created: {}", idl_address.to_string());
|
||||
|
||||
// Add metadata to the IDL.
|
||||
let mut idl = program.idl;
|
||||
idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
|
||||
|
@ -389,18 +494,130 @@ fn deploy(url: Option<String>, keypair: Option<String>) -> Result<()> {
|
|||
let idl_out = PathBuf::from("target/idl")
|
||||
.join(&idl.name)
|
||||
.with_extension("json");
|
||||
write_idl(&idl, Some(&idl_out))?;
|
||||
write_idl(&idl, OutFile::File(idl_out))?;
|
||||
|
||||
println!("Deployed {} at {}", idl.name, address.to_string());
|
||||
}
|
||||
|
||||
run_hosted_deploy(&url)?;
|
||||
// Run migration script.
|
||||
migrate(&url)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn run_hosted_deploy(url: &str) -> Result<()> {
|
||||
println!("Running deploy script");
|
||||
fn create_idl_account(
|
||||
cfg: &Config,
|
||||
keypair_path: &str,
|
||||
program_id: &Pubkey,
|
||||
idl: &Idl,
|
||||
) -> Result<Pubkey> {
|
||||
// Misc.
|
||||
let idl_address = IdlAccount::address(program_id);
|
||||
let keypair = solana_sdk::signature::read_keypair_file(keypair_path)
|
||||
.map_err(|_| anyhow!("Unable to read keypair file"))?;
|
||||
let client = RpcClient::new(cfg.cluster.url().to_string());
|
||||
|
||||
// Serialize and compress the idl.
|
||||
let idl_data = {
|
||||
let json_bytes = serde_json::to_vec(idl)?;
|
||||
let mut e = ZlibEncoder::new(Vec::new(), Compression::default());
|
||||
e.write_all(&json_bytes)?;
|
||||
e.finish()?
|
||||
};
|
||||
|
||||
// Run `Create instruction.
|
||||
{
|
||||
let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Create {
|
||||
data_len: (idl_data.len() as u64) * 2, // Double for future growth.
|
||||
})?;
|
||||
let program_signer = Pubkey::find_program_address(&[], program_id).0;
|
||||
let accounts = vec![
|
||||
AccountMeta::new_readonly(keypair.pubkey(), true),
|
||||
AccountMeta::new(idl_address, false),
|
||||
AccountMeta::new_readonly(program_signer, false),
|
||||
AccountMeta::new_readonly(solana_program::system_program::ID, false),
|
||||
AccountMeta::new_readonly(*program_id, false),
|
||||
AccountMeta::new_readonly(solana_program::sysvar::rent::ID, false),
|
||||
];
|
||||
let ix = Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data,
|
||||
};
|
||||
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
&[ix],
|
||||
Some(&keypair.pubkey()),
|
||||
&[&keypair],
|
||||
recent_hash,
|
||||
);
|
||||
client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
CommitmentConfig::single(),
|
||||
RpcSendTransactionConfig {
|
||||
skip_preflight: true,
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
||||
// Write the idl to the account buffer, chopping up the IDL into pieces
|
||||
// and sending multiple transactions in the event the IDL doesn't fit into
|
||||
// a single transaction.
|
||||
{
|
||||
const MAX_WRITE_SIZE: usize = 1000;
|
||||
let mut offset = 0;
|
||||
while offset < idl_data.len() {
|
||||
// Instruction data.
|
||||
let data = {
|
||||
let start = offset;
|
||||
let end = std::cmp::min(offset + MAX_WRITE_SIZE, idl_data.len());
|
||||
serialize_idl_ix(anchor_lang::idl::IdlInstruction::Write {
|
||||
data: idl_data[start..end].to_vec(),
|
||||
})?
|
||||
};
|
||||
// Instruction accounts.
|
||||
let accounts = vec![
|
||||
AccountMeta::new(idl_address, false),
|
||||
AccountMeta::new_readonly(keypair.pubkey(), true),
|
||||
];
|
||||
// Instruction.
|
||||
let ix = Instruction {
|
||||
program_id: *program_id,
|
||||
accounts,
|
||||
data,
|
||||
};
|
||||
// Send transaction.
|
||||
let (recent_hash, _fee_calc) = client.get_recent_blockhash()?;
|
||||
let tx = Transaction::new_signed_with_payer(
|
||||
&[ix],
|
||||
Some(&keypair.pubkey()),
|
||||
&[&keypair],
|
||||
recent_hash,
|
||||
);
|
||||
client.send_and_confirm_transaction_with_spinner_and_config(
|
||||
&tx,
|
||||
CommitmentConfig::single(),
|
||||
RpcSendTransactionConfig {
|
||||
skip_preflight: true,
|
||||
..RpcSendTransactionConfig::default()
|
||||
},
|
||||
)?;
|
||||
offset += MAX_WRITE_SIZE;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(idl_address)
|
||||
}
|
||||
|
||||
fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8>> {
|
||||
let mut data = anchor_lang::idl::IDL_IX_TAG.to_le_bytes().to_vec();
|
||||
data.append(&mut ix_inner.try_to_vec()?);
|
||||
Ok(data)
|
||||
}
|
||||
|
||||
fn migrate(url: &str) -> Result<()> {
|
||||
println!("Running migration deploy script");
|
||||
|
||||
let cur_dir = std::env::current_dir()?;
|
||||
let module_path = format!("{}/migrations/deploy.js", cur_dir.display());
|
||||
|
@ -424,16 +641,24 @@ fn run_hosted_deploy(url: &str) -> Result<()> {
|
|||
fn deploy_ws(url: &str, keypair: &str) -> Result<Vec<(Program, Pubkey)>> {
|
||||
let mut programs = vec![];
|
||||
println!("Deploying workspace to {}...", url);
|
||||
println!("Upgrade authority: {}", keypair);
|
||||
for program in read_all_programs()? {
|
||||
let binary_path = format!("target/deploy/{}.so", program.lib_name);
|
||||
|
||||
// The Solana CLI doesn't redeploy a program if this file exists.
|
||||
// So remove it to make deploys explicit.
|
||||
let keypair_path = format!("target/deploy/{}-keypair.json", program.lib_name);
|
||||
std::fs::remove_file(keypair_path)?;
|
||||
|
||||
println!("Deploying {}...", binary_path);
|
||||
let exit = std::process::Command::new("solana")
|
||||
.arg("program")
|
||||
.arg("deploy")
|
||||
.arg(&binary_path)
|
||||
.arg("--url")
|
||||
.arg(url)
|
||||
.arg("--keypair")
|
||||
.arg(keypair)
|
||||
.arg(&binary_path)
|
||||
.output()
|
||||
.expect("Must deploy");
|
||||
if !exit.status.success() {
|
||||
|
@ -448,7 +673,7 @@ fn deploy_ws(url: &str, keypair: &str) -> Result<Vec<(Program, Pubkey)>> {
|
|||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[serde(rename_all = "PascalCase")]
|
||||
pub struct DeployStdout {
|
||||
program_id: String,
|
||||
}
|
||||
|
|
|
@ -24,7 +24,9 @@ name = "{1}"
|
|||
|
||||
[features]
|
||||
no-entrypoint = []
|
||||
no-idl = []
|
||||
cpi = ["no-entrypoint"]
|
||||
default = []
|
||||
|
||||
[dependencies]
|
||||
anchor-lang = {{ git = "https://github.com/project-serum/anchor", features = ["derive"] }}
|
||||
|
|
|
@ -90,15 +90,15 @@ async function genesis(provider) {
|
|||
) {
|
||||
return {
|
||||
srm: {
|
||||
withdrawalTimelock: 60,
|
||||
stakeRate: 1000 * 10 ** 6,
|
||||
rewardQLen: 100,
|
||||
withdrawalTimelock: 60 * 60 * 24 * 7, // 1 week.
|
||||
stakeRate: 500 * 10 ** 6, // 500 SRM.
|
||||
rewardQLen: 150,
|
||||
mint: "SRMuApVNdxXokk5GT7XD5cUUgXMBCoAz2LHeuAoKWRt",
|
||||
},
|
||||
msrm: {
|
||||
withdrawalTimelock: 45,
|
||||
withdrawalTimelock: 60 * 60 * 24 * 7, // 1 week.
|
||||
stakeRate: 1,
|
||||
rewardQLen: 100,
|
||||
rewardQLen: 150,
|
||||
mint: "MSRMcoVyrFxnSgo5uXwone5SKcGhT1KEJMFEkMEWf9L",
|
||||
},
|
||||
};
|
||||
|
@ -117,15 +117,15 @@ async function genesis(provider) {
|
|||
);
|
||||
return {
|
||||
token1: {
|
||||
withdrawalTimelock: 60,
|
||||
stakeRate: 2 * 10 ** 6,
|
||||
rewardQLen: 100,
|
||||
withdrawalTimelock: 60 * 60 * 24 * 7,
|
||||
stakeRate: 1000 * 10 ** 6,
|
||||
rewardQLen: 150,
|
||||
mint: token1Mint.toString(),
|
||||
},
|
||||
token2: {
|
||||
withdrawalTimelock: 45,
|
||||
withdrawalTimelock: 60 * 60 * 24 * 7,
|
||||
stakeRate: 1,
|
||||
rewardQLen: 100,
|
||||
rewardQLen: 150,
|
||||
mint: token2Mint.toString(),
|
||||
},
|
||||
};
|
||||
|
|
|
@ -23,7 +23,7 @@ pub mod lockup {
|
|||
}
|
||||
|
||||
impl Lockup {
|
||||
pub const WHITELIST_SIZE: usize = 5;
|
||||
pub const WHITELIST_SIZE: usize = 10;
|
||||
|
||||
pub fn new(ctx: Context<Auth>) -> Result<Self> {
|
||||
let mut whitelist = vec![];
|
||||
|
@ -111,7 +111,7 @@ pub mod lockup {
|
|||
ctx.accounts.clock.unix_timestamp,
|
||||
)
|
||||
{
|
||||
return Err(ErrorCode::InsufficienWithdrawalBalance.into());
|
||||
return Err(ErrorCode::InsufficientWithdrawalBalance.into());
|
||||
}
|
||||
|
||||
// Transfer funds out.
|
||||
|
@ -348,7 +348,7 @@ pub enum ErrorCode {
|
|||
#[msg("Vault amount must be zero.")]
|
||||
InvalidVaultAmount,
|
||||
#[msg("Insufficient withdrawal balance.")]
|
||||
InsufficienWithdrawalBalance,
|
||||
InsufficientWithdrawalBalance,
|
||||
#[msg("Whitelist is full")]
|
||||
WhitelistFull,
|
||||
#[msg("Whitelist entry already exists")]
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
const assert = require("assert");
|
||||
const anchor = require('@project-serum/anchor');
|
||||
const anchor = require("@project-serum/anchor");
|
||||
const serumCmn = require("@project-serum/common");
|
||||
const TokenInstructions = require("@project-serum/serum").TokenInstructions;
|
||||
const utils = require("./utils");
|
||||
|
@ -15,6 +15,7 @@ describe("Lockup and Registry", () => {
|
|||
const registry = anchor.workspace.Registry;
|
||||
|
||||
let lockupAddress = null;
|
||||
const WHITELIST_SIZE = 10;
|
||||
|
||||
let mint = null;
|
||||
let god = null;
|
||||
|
@ -39,7 +40,7 @@ describe("Lockup and Registry", () => {
|
|||
const lockupAccount = await lockup.state();
|
||||
|
||||
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
|
||||
assert.ok(lockupAccount.whitelist.length === 5);
|
||||
assert.ok(lockupAccount.whitelist.length === WHITELIST_SIZE);
|
||||
lockupAccount.whitelist.forEach((e) => {
|
||||
assert.ok(e.programId.equals(new anchor.web3.PublicKey()));
|
||||
});
|
||||
|
@ -76,11 +77,7 @@ describe("Lockup and Registry", () => {
|
|||
assert.ok(lockupAccount.authority.equals(provider.wallet.publicKey));
|
||||
});
|
||||
|
||||
let e0 = null;
|
||||
let e1 = null;
|
||||
let e2 = null;
|
||||
let e3 = null;
|
||||
let e4 = null;
|
||||
const entries = [];
|
||||
|
||||
it("Adds to the whitelist", async () => {
|
||||
const generateEntry = async () => {
|
||||
|
@ -89,36 +86,34 @@ describe("Lockup and Registry", () => {
|
|||
programId,
|
||||
};
|
||||
};
|
||||
e0 = await generateEntry();
|
||||
e1 = await generateEntry();
|
||||
e2 = await generateEntry();
|
||||
e3 = await generateEntry();
|
||||
e4 = await generateEntry();
|
||||
const e5 = await generateEntry();
|
||||
|
||||
for (let k = 0; k < WHITELIST_SIZE; k += 1) {
|
||||
entries.push(await generateEntry());
|
||||
}
|
||||
|
||||
const accounts = {
|
||||
authority: provider.wallet.publicKey,
|
||||
};
|
||||
|
||||
await lockup.state.rpc.whitelistAdd(e0, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(entries[0], { accounts });
|
||||
|
||||
let lockupAccount = await lockup.state();
|
||||
|
||||
assert.ok(lockupAccount.whitelist.length === 1);
|
||||
assert.deepEqual(lockupAccount.whitelist, [e0]);
|
||||
assert.deepEqual(lockupAccount.whitelist, [entries[0]]);
|
||||
|
||||
await lockup.state.rpc.whitelistAdd(e1, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e2, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e3, { accounts });
|
||||
await lockup.state.rpc.whitelistAdd(e4, { accounts });
|
||||
for (let k = 1; k < WHITELIST_SIZE; k += 1) {
|
||||
await lockup.state.rpc.whitelistAdd(entries[k], { accounts });
|
||||
}
|
||||
|
||||
lockupAccount = await lockup.state();
|
||||
|
||||
assert.deepEqual(lockupAccount.whitelist, [e0, e1, e2, e3, e4]);
|
||||
assert.deepEqual(lockupAccount.whitelist, entries);
|
||||
|
||||
await assert.rejects(
|
||||
async () => {
|
||||
await lockup.state.rpc.whitelistAdd(e5, { accounts });
|
||||
const e = await generateEntry();
|
||||
await lockup.state.rpc.whitelistAdd(e, { accounts });
|
||||
},
|
||||
(err) => {
|
||||
assert.equal(err.code, 108);
|
||||
|
@ -129,13 +124,13 @@ describe("Lockup and Registry", () => {
|
|||
});
|
||||
|
||||
it("Removes from the whitelist", async () => {
|
||||
await lockup.state.rpc.whitelistDelete(e0, {
|
||||
await lockup.state.rpc.whitelistDelete(entries[0], {
|
||||
accounts: {
|
||||
authority: provider.wallet.publicKey,
|
||||
},
|
||||
});
|
||||
let lockupAccount = await lockup.state();
|
||||
assert.deepEqual(lockupAccount.whitelist, [e1, e2, e3, e4]);
|
||||
assert.deepEqual(lockupAccount.whitelist, entries.slice(1));
|
||||
});
|
||||
|
||||
const vesting = new anchor.web3.Account();
|
||||
|
@ -264,7 +259,7 @@ describe("Lockup and Registry", () => {
|
|||
const rewardQ = new anchor.web3.Account();
|
||||
const withdrawalTimelock = new anchor.BN(4);
|
||||
const stakeRate = new anchor.BN(2);
|
||||
const rewardQLen = 100;
|
||||
const rewardQLen = 170;
|
||||
let registrarAccount = null;
|
||||
let registrarSigner = null;
|
||||
let nonce = null;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
use crate::{Accounts, AccountsExit, ToAccountInfo, ToAccountInfos, ToAccountMetas};
|
||||
use crate::{Accounts, AccountsExit, AccountsInit, ToAccountInfo, ToAccountInfos, ToAccountMetas};
|
||||
use solana_program::account_info::AccountInfo;
|
||||
use solana_program::entrypoint::ProgramResult;
|
||||
use solana_program::instruction::AccountMeta;
|
||||
|
@ -19,6 +19,31 @@ impl<'info> Accounts<'info> for AccountInfo<'info> {
|
|||
}
|
||||
}
|
||||
|
||||
impl<'info> AccountsInit<'info> for AccountInfo<'info> {
|
||||
fn try_accounts_init(
|
||||
_program_id: &Pubkey,
|
||||
accounts: &mut &[AccountInfo<'info>],
|
||||
) -> Result<Self, ProgramError> {
|
||||
if accounts.len() == 0 {
|
||||
return Err(ProgramError::NotEnoughAccountKeys);
|
||||
}
|
||||
|
||||
let account = &accounts[0];
|
||||
*accounts = &accounts[1..];
|
||||
|
||||
// The discriminator should be zero, since we're initializing.
|
||||
let data: &[u8] = &account.try_borrow_data()?;
|
||||
let mut disc_bytes = [0u8; 8];
|
||||
disc_bytes.copy_from_slice(&data[..8]);
|
||||
let discriminator = u64::from_le_bytes(disc_bytes);
|
||||
if discriminator != 0 {
|
||||
return Err(ProgramError::InvalidAccountData);
|
||||
}
|
||||
|
||||
Ok(account.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'info> ToAccountMetas for AccountInfo<'info> {
|
||||
fn to_account_metas(&self, is_signer: Option<bool>) -> Vec<AccountMeta> {
|
||||
let is_signer = is_signer.unwrap_or(self.is_signer);
|
||||
|
|
14
src/ctor.rs
14
src/ctor.rs
|
@ -5,13 +5,27 @@ use solana_program::sysvar::rent::Rent;
|
|||
// Needed for the `Accounts` macro.
|
||||
use crate as anchor_lang;
|
||||
|
||||
// The Ctor accounts that can be used to create any account within the program
|
||||
// itself (instead of creating the account on the client).
|
||||
//
|
||||
// This is used to create accounts at deterministic addresses, as a function of
|
||||
// nothing but a program ID--for example, to create state global program
|
||||
// structs and program IDL accounts.
|
||||
#[derive(Accounts)]
|
||||
pub struct Ctor<'info> {
|
||||
// Payer of the transaction.
|
||||
pub from: AccountInfo<'info>,
|
||||
// The deterministically defined "state" account being created via
|
||||
// `create_account_with_seed`.
|
||||
#[account(mut)]
|
||||
pub to: AccountInfo<'info>,
|
||||
// The program-derived-address signing off on the account creation.
|
||||
// Seeds = &[] + bump seed.
|
||||
pub base: AccountInfo<'info>,
|
||||
// The system program.
|
||||
pub system_program: AccountInfo<'info>,
|
||||
// The program whose state is being constructed.
|
||||
pub program: AccountInfo<'info>,
|
||||
// Rent sysvar.
|
||||
pub rent: Sysvar<'info, Rent>,
|
||||
}
|
||||
|
|
|
@ -0,0 +1,73 @@
|
|||
//! idl.rs defines the instructions and account state used to store a
|
||||
//! program's IDL.
|
||||
//!
|
||||
//! Note that the transaction to store the IDL can be larger than the max
|
||||
//! transaction size. As a reuslt, the transaction must be broken up into
|
||||
//! several pieces and stored into the IDL account with multiple transactions
|
||||
//! via the `Write` instruction to continuously append to the account's IDL data
|
||||
//! buffer.
|
||||
//!
|
||||
//! To upgrade the IDL, first invoke the `Clear` instruction to reset the data.
|
||||
//! And invoke `Write` once more. To eliminate the ability to change the IDL,
|
||||
//! set the authority to a key for which you can't sign, e.g., the zero address
|
||||
//! or the system program ID, or compile the program with the "no-idl" feature
|
||||
//! and upgrade the program with the upgradeable BPF loader.
|
||||
|
||||
use crate::prelude::*;
|
||||
use solana_program::pubkey::Pubkey;
|
||||
|
||||
// Needed for the `Accounts` macro.
|
||||
use crate as anchor_lang;
|
||||
|
||||
// The first 8 bytes of an instruction to create or modify the IDL account. This
|
||||
// instruction is defined outside the main program's instruction enum, so that
|
||||
// the enum variant tags can align with function source order.
|
||||
//
|
||||
// Sha256(anchor:idl)[..8];
|
||||
pub const IDL_IX_TAG: u64 = 0x0a69e9a778bcf440;
|
||||
|
||||
#[derive(AnchorSerialize, AnchorDeserialize)]
|
||||
pub enum IdlInstruction {
|
||||
// One time initializer for creating the program's idl account.
|
||||
Create { data_len: u64 },
|
||||
// Appends to the end of the idl account data.
|
||||
Write { data: Vec<u8> },
|
||||
// Clear's the IdlInstruction data. Used to update the IDL.
|
||||
Clear,
|
||||
// Sets a new authority on the IdlAccount.
|
||||
SetAuthority { new_authority: Pubkey },
|
||||
}
|
||||
|
||||
// Accounts for the Create instuction.
|
||||
pub type IdlCreateAccounts<'info> = crate::ctor::Ctor<'info>;
|
||||
|
||||
// Accounts for Idl instructions.
|
||||
#[derive(Accounts)]
|
||||
pub struct IdlAccounts<'info> {
|
||||
#[account(mut, has_one = authority)]
|
||||
pub idl: ProgramAccount<'info, IdlAccount>,
|
||||
#[account(signer)]
|
||||
pub authority: AccountInfo<'info>,
|
||||
}
|
||||
|
||||
// The account holding a program's IDL. This is stored on chain so that clients
|
||||
// can fetch it and generate a client with nothing but a program's ID.
|
||||
#[account]
|
||||
#[derive(Debug)]
|
||||
pub struct IdlAccount {
|
||||
// Address that can modify the IDL.
|
||||
pub authority: Pubkey,
|
||||
// Compressed idl bytes.
|
||||
pub data: Vec<u8>,
|
||||
}
|
||||
|
||||
impl IdlAccount {
|
||||
pub fn address(program_id: &Pubkey) -> Pubkey {
|
||||
let program_signer = Pubkey::find_program_address(&[], program_id).0;
|
||||
Pubkey::create_with_seed(&program_signer, IdlAccount::seed(), program_id)
|
||||
.expect("Seed is always valid")
|
||||
}
|
||||
pub fn seed() -> &'static str {
|
||||
"anchor:idl"
|
||||
}
|
||||
}
|
|
@ -33,6 +33,7 @@ mod context;
|
|||
mod cpi_account;
|
||||
mod ctor;
|
||||
mod error;
|
||||
pub mod idl;
|
||||
mod program_account;
|
||||
mod state;
|
||||
mod sysvar;
|
||||
|
|
|
@ -15,17 +15,17 @@ pub fn generate(accs: AccountsStruct) -> proc_macro2::TokenStream {
|
|||
let name = &s.ident;
|
||||
let ty = &s.raw_field.ty;
|
||||
quote! {
|
||||
let #name: #ty = Accounts::try_accounts(program_id, accounts)?;
|
||||
let #name: #ty = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
|
||||
}
|
||||
}
|
||||
AccountField::Field(f) => {
|
||||
let name = f.typed_ident();
|
||||
match f.is_init {
|
||||
false => quote! {
|
||||
let #name = Accounts::try_accounts(program_id, accounts)?;
|
||||
let #name = anchor_lang::Accounts::try_accounts(program_id, accounts)?;
|
||||
},
|
||||
true => quote! {
|
||||
let #name = AccountsInit::try_accounts_init(program_id, accounts)?;
|
||||
let #name = anchor_lang::AccountsInit::try_accounts_init(program_id, accounts)?;
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -217,7 +217,7 @@ pub fn generate_constraint_belongs_to(
|
|||
// todo: would be nice if target could be an account info object.
|
||||
quote! {
|
||||
if &#ident.#target != #target.to_account_info().key {
|
||||
return Err(ProgramError::Custom(1)); // todo: error codes
|
||||
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -237,7 +237,7 @@ pub fn generate_constraint_signer(f: &Field, _c: &ConstraintSigner) -> proc_macr
|
|||
// This check will be performed on the other end of the invocation.
|
||||
if cfg!(not(feature = "cpi")) {
|
||||
if !#info.is_signer {
|
||||
return Err(ProgramError::MissingRequiredSignature);
|
||||
return Err(anchor_lang::solana_program::program_error::ProgramError::MissingRequiredSignature);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -247,7 +247,7 @@ pub fn generate_constraint_literal(c: &ConstraintLiteral) -> proc_macro2::TokenS
|
|||
let tokens = &c.tokens;
|
||||
quote! {
|
||||
if !(#tokens) {
|
||||
return Err(ProgramError::Custom(1)); // todo: error codes
|
||||
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -263,7 +263,7 @@ pub fn generate_constraint_owner(f: &Field, c: &ConstraintOwner) -> proc_macro2:
|
|||
ConstraintOwner::Skip => quote! {},
|
||||
ConstraintOwner::Program => quote! {
|
||||
if #info.owner != program_id {
|
||||
return Err(ProgramError::Custom(1)); // todo: error codes
|
||||
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo: error codes
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -283,7 +283,7 @@ pub fn generate_constraint_rent_exempt(
|
|||
ConstraintRentExempt::Skip => quote! {},
|
||||
ConstraintRentExempt::Enforce => quote! {
|
||||
if !rent.is_exempt(#info.lamports(), #info.try_data_len()?) {
|
||||
return Err(ProgramError::Custom(2)); // todo: error codes
|
||||
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(2)); // todo: error codes
|
||||
}
|
||||
},
|
||||
}
|
||||
|
@ -296,9 +296,9 @@ pub fn generate_constraint_seeds(f: &Field, c: &ConstraintSeeds) -> proc_macro2:
|
|||
let program_signer = Pubkey::create_program_address(
|
||||
&#seeds,
|
||||
program_id,
|
||||
).map_err(|_| ProgramError::Custom(1))?; // todo
|
||||
).map_err(|_| anchor_lang::solana_program::program_error::ProgramError::Custom(1))?; // todo
|
||||
if #name.to_account_info().key != &program_signer {
|
||||
return Err(ProgramError::Custom(1)); // todo
|
||||
return Err(anchor_lang::solana_program::program_error::ProgramError::Custom(1)); // todo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,13 @@ pub fn generate(program: Program) -> proc_macro2::TokenStream {
|
|||
anchor_lang::solana_program::entrypoint!(entry);
|
||||
#[cfg(not(feature = "no-entrypoint"))]
|
||||
fn entry(program_id: &Pubkey, accounts: &[AccountInfo], instruction_data: &[u8]) -> ProgramResult {
|
||||
if cfg!(not(feature = "no-idl")) {
|
||||
if instruction_data.len() >= 8 {
|
||||
if anchor_lang::idl::IDL_IX_TAG.to_le_bytes() == instruction_data[..8] {
|
||||
return __private::__idl(program_id, accounts, &instruction_data[8..]);
|
||||
}
|
||||
}
|
||||
}
|
||||
let mut data: &[u8] = instruction_data;
|
||||
let ix = __private::instruction::#instruction_name::deserialize(&mut data)
|
||||
.map_err(|_| ProgramError::Custom(1))?; // todo: error code
|
||||
|
@ -114,6 +121,136 @@ pub fn generate_dispatch(program: &Program) -> proc_macro2::TokenStream {
|
|||
// so.
|
||||
pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStream {
|
||||
let program_name = &program.name;
|
||||
let non_inlined_idl: proc_macro2::TokenStream = {
|
||||
quote! {
|
||||
// Entry for all IDL related instructions. Use the "no-idl" feature
|
||||
// to eliminate this code, for example, if one wants to make the
|
||||
// IDL no longer mutable or if one doesn't want to store the IDL
|
||||
// on chain.
|
||||
#[inline(never)]
|
||||
#[cfg(not(feature = "no-idl"))]
|
||||
pub fn __idl(program_id: &Pubkey, accounts: &[AccountInfo], idl_ix_data: &[u8]) -> ProgramResult {
|
||||
let mut accounts = accounts;
|
||||
let mut data: &[u8] = idl_ix_data;
|
||||
|
||||
let ix = anchor_lang::idl::IdlInstruction::deserialize(&mut data)
|
||||
.map_err(|_| ProgramError::Custom(1))?; // todo
|
||||
|
||||
match ix {
|
||||
anchor_lang::idl::IdlInstruction::Create { data_len } => {
|
||||
let mut accounts = anchor_lang::idl::IdlCreateAccounts::try_accounts(program_id, &mut accounts)?;
|
||||
__idl_create_account(program_id, &mut accounts, data_len)?;
|
||||
accounts.exit(program_id)?;
|
||||
},
|
||||
anchor_lang::idl::IdlInstruction::Write { data } => {
|
||||
let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts)?;
|
||||
__idl_write(program_id, &mut accounts, data)?;
|
||||
accounts.exit(program_id)?;
|
||||
},
|
||||
anchor_lang::idl::IdlInstruction::Clear => {
|
||||
let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts)?;
|
||||
__idl_clear(program_id, &mut accounts)?;
|
||||
accounts.exit(program_id)?;
|
||||
},
|
||||
anchor_lang::idl::IdlInstruction::SetAuthority { new_authority } => {
|
||||
let mut accounts = anchor_lang::idl::IdlAccounts::try_accounts(program_id, &mut accounts)?;
|
||||
__idl_set_authority(program_id, &mut accounts, new_authority)?;
|
||||
accounts.exit(program_id)?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// One time IDL account initializer. Will faill on subsequent
|
||||
// invocations.
|
||||
#[inline(never)]
|
||||
pub fn __idl_create_account(
|
||||
program_id: &Pubkey,
|
||||
accounts: &mut anchor_lang::idl::IdlCreateAccounts,
|
||||
data_len: u64,
|
||||
) -> ProgramResult {
|
||||
// Create the IDL's account.
|
||||
let from = accounts.from.key;
|
||||
let (base, nonce) = Pubkey::find_program_address(&[], accounts.program.key);
|
||||
let seed = anchor_lang::idl::IdlAccount::seed();
|
||||
let owner = accounts.program.key;
|
||||
let to = Pubkey::create_with_seed(&base, seed, owner).unwrap();
|
||||
// Space: account discriminator || authority pubkey || vec len || vec data
|
||||
let space = 8 + 32 + 4 + data_len as usize;
|
||||
let lamports = accounts.rent.minimum_balance(space);
|
||||
let seeds = &[&[nonce][..]];
|
||||
let ix = anchor_lang::solana_program::system_instruction::create_account_with_seed(
|
||||
from,
|
||||
&to,
|
||||
&base,
|
||||
seed,
|
||||
lamports,
|
||||
space as u64,
|
||||
owner,
|
||||
);
|
||||
anchor_lang::solana_program::program::invoke_signed(
|
||||
&ix,
|
||||
&[
|
||||
accounts.from.clone(),
|
||||
accounts.to.clone(),
|
||||
accounts.base.clone(),
|
||||
accounts.system_program.clone(),
|
||||
],
|
||||
&[seeds],
|
||||
)?;
|
||||
|
||||
// Deserialize the newly created account.
|
||||
let mut idl_account = {
|
||||
let mut account_data = accounts.to.try_borrow_data()?;
|
||||
let mut account_data_slice: &[u8] = &account_data;
|
||||
anchor_lang::idl::IdlAccount::try_deserialize_unchecked(
|
||||
&mut account_data_slice,
|
||||
)?
|
||||
};
|
||||
|
||||
// Set the authority.
|
||||
idl_account.authority = *accounts.from.key;
|
||||
|
||||
// Store the new account data.
|
||||
let mut data = accounts.to.try_borrow_mut_data()?;
|
||||
let dst: &mut [u8] = &mut data;
|
||||
let mut cursor = std::io::Cursor::new(dst);
|
||||
idl_account.try_serialize(&mut cursor)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn __idl_write(
|
||||
program_id: &Pubkey,
|
||||
accounts: &mut anchor_lang::idl::IdlAccounts,
|
||||
idl_data: Vec<u8>,
|
||||
) -> ProgramResult {
|
||||
let mut idl = &mut accounts.idl;
|
||||
idl.data.extend(idl_data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn __idl_clear(
|
||||
program_id: &Pubkey,
|
||||
accounts: &mut anchor_lang::idl::IdlAccounts,
|
||||
) -> ProgramResult {
|
||||
accounts.idl.data = vec![];
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[inline(never)]
|
||||
pub fn __idl_set_authority(
|
||||
program_id: &Pubkey,
|
||||
accounts: &mut anchor_lang::idl::IdlAccounts,
|
||||
new_authority: Pubkey,
|
||||
) -> ProgramResult {
|
||||
accounts.idl.authority = new_authority;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
};
|
||||
let non_inlined_ctor: proc_macro2::TokenStream = match &program.state {
|
||||
None => quote! {},
|
||||
Some(state) => {
|
||||
|
@ -123,6 +260,8 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
|||
let mod_name = &program.name;
|
||||
let anchor_ident = &state.ctor_anchor;
|
||||
quote! {
|
||||
// One time state account initializer. Will faill on subsequent
|
||||
// invocations.
|
||||
#[inline(never)]
|
||||
pub fn __ctor(program_id: &Pubkey, accounts: &[AccountInfo], #(#ctor_typed_args),*) -> ProgramResult {
|
||||
let mut remaining_accounts: &[AccountInfo] = accounts;
|
||||
|
@ -277,6 +416,7 @@ pub fn generate_non_inlined_handlers(program: &Program) -> proc_macro2::TokenStr
|
|||
.collect();
|
||||
|
||||
quote! {
|
||||
#non_inlined_idl
|
||||
#non_inlined_ctor
|
||||
#(#non_inlined_state_handlers)*
|
||||
#(#non_inlined_handlers)*
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Idl {
|
||||
pub version: String,
|
||||
pub name: String,
|
||||
|
@ -17,7 +17,7 @@ pub struct Idl {
|
|||
pub metadata: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IdlState {
|
||||
#[serde(rename = "struct")]
|
||||
pub strct: IdlTypeDef,
|
||||
|
@ -26,7 +26,7 @@ pub struct IdlState {
|
|||
|
||||
pub type IdlStateMethod = IdlInstruction;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IdlInstruction {
|
||||
pub name: String,
|
||||
pub accounts: Vec<IdlAccountItem>,
|
||||
|
@ -34,14 +34,14 @@ pub struct IdlInstruction {
|
|||
}
|
||||
|
||||
// A single struct deriving `Accounts`.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IdlAccounts {
|
||||
pub name: String,
|
||||
pub accounts: Vec<IdlAccountItem>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum IdlAccountItem {
|
||||
IdlAccount(IdlAccount),
|
||||
|
@ -49,7 +49,7 @@ pub enum IdlAccountItem {
|
|||
}
|
||||
|
||||
// A single field in the accounts struct.
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct IdlAccount {
|
||||
pub name: String,
|
||||
|
@ -57,42 +57,42 @@ pub struct IdlAccount {
|
|||
pub is_signer: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IdlField {
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub ty: IdlType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IdlTypeDef {
|
||||
pub name: String,
|
||||
#[serde(rename = "type")]
|
||||
pub ty: IdlTypeDefTy,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "lowercase", tag = "kind")]
|
||||
pub enum IdlTypeDefTy {
|
||||
Struct { fields: Vec<IdlField> },
|
||||
Enum { variants: Vec<EnumVariant> },
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct EnumVariant {
|
||||
pub name: String,
|
||||
#[serde(skip_serializing_if = "Option::is_none", default)]
|
||||
pub fields: Option<EnumFields>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(untagged)]
|
||||
pub enum EnumFields {
|
||||
Named(Vec<IdlField>),
|
||||
Tuple(Vec<IdlType>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum IdlType {
|
||||
Bool,
|
||||
|
@ -112,7 +112,7 @@ pub enum IdlType {
|
|||
Vec(Box<IdlType>),
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IdlTypePublicKey;
|
||||
|
||||
impl std::str::FromStr for IdlType {
|
||||
|
@ -162,7 +162,7 @@ impl std::str::FromStr for IdlType {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct IdlErrorCode {
|
||||
pub code: u32,
|
||||
pub name: String,
|
||||
|
|
|
@ -27,13 +27,15 @@
|
|||
"@solana/web3.js": "^0.90.4",
|
||||
"@types/bn.js": "^4.11.6",
|
||||
"@types/bs58": "^4.0.1",
|
||||
"@types/pako": "^1.0.1",
|
||||
"bn.js": "^5.1.2",
|
||||
"bs58": "^4.0.1",
|
||||
"buffer-layout": "^1.2.0",
|
||||
"camelcase": "^5.3.1",
|
||||
"crypto-hash": "^1.3.0",
|
||||
"eventemitter3": "^4.0.7",
|
||||
"find": "^0.3.0"
|
||||
"find": "^0.3.0",
|
||||
"pako": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^8.2.0",
|
||||
|
|
|
@ -1,3 +1,6 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import * as borsh from "@project-serum/borsh";
|
||||
|
||||
export type Idl = {
|
||||
version: string;
|
||||
name: string;
|
||||
|
@ -101,3 +104,35 @@ type IdlErrorCode = {
|
|||
name: string;
|
||||
msg?: string;
|
||||
};
|
||||
|
||||
// Deterministic IDL address as a function of the program id.
|
||||
export async function idlAddress(programId: PublicKey): Promise<PublicKey> {
|
||||
const base = (await PublicKey.findProgramAddress([], programId))[0];
|
||||
return await PublicKey.createWithSeed(base, seed(), programId);
|
||||
}
|
||||
|
||||
// Seed for generating the idlAddress.
|
||||
export function seed(): string {
|
||||
return "anchor:idl";
|
||||
}
|
||||
|
||||
// The on-chain account of the IDL.
|
||||
export interface IdlProgramAccount {
|
||||
authority: PublicKey;
|
||||
data: Buffer;
|
||||
}
|
||||
|
||||
const IDL_ACCOUNT_LAYOUT: borsh.Layout<IdlProgramAccount> = borsh.struct([
|
||||
borsh.publicKey("authority"),
|
||||
borsh.vecU8("data"),
|
||||
]);
|
||||
|
||||
export function decodeIdlAccount(data: Buffer): IdlProgramAccount {
|
||||
return IDL_ACCOUNT_LAYOUT.decode(data);
|
||||
}
|
||||
|
||||
export function encodeIdlAccount(acc: IdlProgramAccount): Buffer {
|
||||
const buffer = Buffer.alloc(1000); // TODO: use a tighter buffer.
|
||||
const len = IDL_ACCOUNT_LAYOUT.encode(acc, buffer);
|
||||
return buffer.slice(0, len);
|
||||
}
|
||||
|
|
|
@ -1,7 +1,8 @@
|
|||
import { PublicKey } from "@solana/web3.js";
|
||||
import { inflate } from "pako";
|
||||
import Provider from "./provider";
|
||||
import { RpcFactory } from "./rpc";
|
||||
import { Idl } from "./idl";
|
||||
import { Idl, idlAddress, decodeIdlAccount } from "./idl";
|
||||
import Coder from "./coder";
|
||||
import { Rpcs, Ixs, Txs, Accounts, State } from "./rpc";
|
||||
import { getProvider } from "./";
|
||||
|
@ -78,4 +79,33 @@ export class Program {
|
|||
this.coder = coder;
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates a Program client by fetching the IDL from chain.
|
||||
*/
|
||||
public static async at(programId: PublicKey, provider?: Provider) {
|
||||
const idl = await Program.fetchIdl(programId, provider);
|
||||
return new Program(idl, programId, provider);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetches an idl from the blockchain.
|
||||
*/
|
||||
public static async fetchIdl(programId: PublicKey, provider?: Provider) {
|
||||
provider = provider ?? getProvider();
|
||||
const address = await idlAddress(programId);
|
||||
const accountInfo = await provider.connection.getAccountInfo(address);
|
||||
// Chop off account discriminator.
|
||||
let idlAccount = decodeIdlAccount(accountInfo.data.slice(8));
|
||||
const inflatedIdl = inflate(idlAccount.data);
|
||||
return JSON.parse(decodeUtf8(inflatedIdl));
|
||||
}
|
||||
}
|
||||
|
||||
function decodeUtf8(array: Uint8Array): string {
|
||||
const decoder =
|
||||
typeof TextDecoder === "undefined"
|
||||
? new (require("util").TextDecoder)("utf-8") // Node.
|
||||
: new TextDecoder("utf-8"); // Browser.
|
||||
return decoder.decode(array);
|
||||
}
|
||||
|
|
10
ts/yarn.lock
10
ts/yarn.lock
|
@ -829,6 +829,11 @@
|
|||
resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.0.tgz#e486d0d97396d79beedd0a6e33f4534ff6b4973e"
|
||||
integrity sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==
|
||||
|
||||
"@types/pako@^1.0.1":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@types/pako/-/pako-1.0.1.tgz#33b237f3c9aff44d0f82fe63acffa4a365ef4a61"
|
||||
integrity sha512-GdZbRSJ3Cv5fiwT6I0SQ3ckeN2PWNqxd26W9Z2fCK1tGrrasGy4puvNFtnddqH9UJFMQYXxEuuB7B8UK+LLwSg==
|
||||
|
||||
"@types/parse-json@^4.0.0":
|
||||
version "4.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@types/parse-json/-/parse-json-4.0.0.tgz#2f8bb441434d163b35fb8ffdccd7138927ffb8c0"
|
||||
|
@ -4510,6 +4515,11 @@ p-try@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
|
||||
integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
|
||||
|
||||
pako@^2.0.3:
|
||||
version "2.0.3"
|
||||
resolved "https://registry.yarnpkg.com/pako/-/pako-2.0.3.tgz#cdf475e31b678565251406de9e759196a0ea7a43"
|
||||
integrity sha512-WjR1hOeg+kki3ZIOjaf4b5WVcay1jaliKSYiEaB1XzwhMQZJxRdQRv0V31EKBYlxb4T7SK3hjfc/jxyU64BoSw==
|
||||
|
||||
parent-module@^1.0.0:
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
|
||||
|
|
Loading…
Reference in New Issue