فهرست منبع

Explicit CLI commands

Armani Ferrante 4 سال پیش
والد
کامیت
9825710c8f
8فایلهای تغییر یافته به همراه435 افزوده شده و 285 حذف شده
  1. 1 0
      Cargo.lock
  2. 1 0
      cli/Cargo.toml
  3. 18 18
      cli/src/config.rs
  4. 412 264
      cli/src/main.rs
  5. 0 1
      client/example/src/main.rs
  6. 1 1
      lang/attribute/state/src/lib.rs
  7. 1 0
      lang/src/ctor.rs
  8. 1 1
      ts/src/coder.ts

+ 1 - 0
Cargo.lock

@@ -108,6 +108,7 @@ dependencies = [
  "dirs",
  "flate2",
  "heck",
+ "rand",
  "serde",
  "serde_json",
  "serde_yaml",

+ 1 - 0
cli/Cargo.toml

@@ -26,3 +26,4 @@ serum-common = { git = "https://github.com/project-serum/serum-dex", features =
 dirs = "3.0"
 heck = "0.3.1"
 flate2 = "1.0.19"
+rand = "0.7.3"

+ 18 - 18
cli/src/config.rs

@@ -92,24 +92,7 @@ impl FromStr for Config {
     }
 }
 
-pub fn find_cargo_toml() -> Result<Option<PathBuf>> {
-    let _cwd = std::env::current_dir()?;
-    let mut cwd_opt = Some(_cwd.as_path());
-    while let Some(cwd) = cwd_opt {
-        let files = fs::read_dir(cwd)?;
-        for f in files {
-            let p = f?.path();
-            if let Some(filename) = p.file_name() {
-                if filename.to_str() == Some("Cargo.toml") {
-                    return Ok(Some(PathBuf::from(p)));
-                }
-            }
-        }
-        cwd_opt = cwd.parent();
-    }
-    Ok(None)
-}
-
+// TODO: this should read idl dir instead of parsing source.
 pub fn read_all_programs() -> Result<Vec<Program>> {
     let files = fs::read_dir("programs")?;
     let mut r = vec![];
@@ -155,4 +138,21 @@ pub struct Program {
     pub idl: Idl,
 }
 
+impl Program {
+    pub fn anchor_keypair_path(&self) -> PathBuf {
+        std::env::current_dir()
+            .expect("Must have current dir")
+            .join(format!(
+                "target/deploy/anchor-{}-keypair.json",
+                self.lib_name
+            ))
+    }
+
+    pub fn binary_path(&self) -> PathBuf {
+        std::env::current_dir()
+            .expect("Must have current dir")
+            .join(format!("target/deploy/{}.so", self.lib_name))
+    }
+}
+
 serum_common::home_path!(WalletPath, ".config/solana/id.json");

+ 412 - 264
cli/src/main.rs

@@ -1,4 +1,4 @@
-use crate::config::{find_cargo_toml, read_all_programs, Config, Program};
+use crate::config::{read_all_programs, Config, Program};
 use anchor_lang::idl::IdlAccount;
 use anchor_lang::{AnchorDeserialize, AnchorSerialize};
 use anchor_syn::idl::Idl;
@@ -7,12 +7,14 @@ use clap::Clap;
 use flate2::read::ZlibDecoder;
 use flate2::write::ZlibEncoder;
 use flate2::Compression;
+use rand::rngs::OsRng;
 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::Keypair;
 use solana_sdk::signature::Signer;
 use solana_sdk::transaction::Transaction;
 use std::fs::{self, File};
@@ -34,23 +36,27 @@ pub struct Opts {
 pub enum Command {
     /// Initializes a workspace.
     Init { name: String },
-    /// Builds a Solana program.
+    /// Builds the workspace.
     Build {
         /// Output directory for the IDL.
         #[clap(short, long)]
         idl: Option<String>,
     },
     /// Runs integration tests against a localnetwork.
-    Test,
+    Test {
+        /// Use this flag if you want to run tests against previously deployed
+        /// programs.
+        #[clap(short, long)]
+        skip_deploy: bool,
+    },
     /// Creates a new program.
     New { name: String },
-    /// Commands for interact with interface definitions.
+    /// Commands for interacting with interface definitions.
     Idl {
         #[clap(subcommand)]
         subcmd: IdlCommand,
     },
-    /// Deploys the workspace, creates IDL accounts, and runs the migration
-    /// script.
+    /// Deploys each program in the workspace.
     Deploy {
         #[clap(short, long)]
         url: Option<String>,
@@ -60,11 +66,29 @@ pub enum Command {
     /// Runs the deploy migration script.
     Migrate {
         #[clap(short, long)]
-        url: String,
+        url: Option<String>,
+    },
+    /// Deploys, initializes an IDL, and migrates all in one command.
+    Launch {
+        #[clap(short, long)]
+        url: Option<String>,
+        #[clap(short, long)]
+        keypair: Option<String>,
+    },
+    /// Upgrades a single program. The configured wallet must be the upgrade
+    /// authority.
+    Upgrade {
+        /// The program to upgrade.
+        #[clap(short, long)]
+        program_id: Pubkey,
+        /// Filepath to the new program binary.
+        program_filepath: String,
+    },
+    /// Runs an airdrop loop, continuously funding the configured wallet.
+    Airdrop {
+        #[clap(short, long)]
+        url: Option<String>,
     },
-    /// Not yet implemented. Please use `solana program deploy` command to
-    /// upgrade your program.
-    Upgrade {},
 }
 
 #[derive(Debug, Clap)]
@@ -75,6 +99,12 @@ pub enum IdlCommand {
         #[clap(short, long)]
         filepath: String,
     },
+    /// Updates the IDL to the new file.
+    Update {
+        program_id: Pubkey,
+        #[clap(short, long)]
+        filepath: String,
+    },
     /// Parses an IDL from source.
     Parse {
         /// Path to the program's interface definition.
@@ -95,19 +125,20 @@ pub enum IdlCommand {
 
 fn main() -> Result<()> {
     let opts = Opts::parse();
-
     match opts.command {
         Command::Init { name } => init(name),
-        Command::Build { idl } => build(idl),
-        Command::Test => test(),
         Command::New { name } => new(name),
-        Command::Idl { subcmd } => idl(subcmd),
+        Command::Build { idl } => build(idl),
         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),
+        Command::Upgrade {
+            program_id,
+            program_filepath,
+        } => upgrade(program_id, program_filepath),
+        Command::Idl { subcmd } => idl(subcmd),
+        Command::Migrate { url } => migrate(url),
+        Command::Launch { url, keypair } => launch(url, keypair),
+        Command::Test { skip_deploy } => test(skip_deploy),
+        Command::Airdrop { url } => airdrop(url),
     }
 }
 
@@ -153,25 +184,19 @@ fn init(name: String) -> Result<()> {
 
 // Creates a new program crate in the `programs/<name>` directory.
 fn new(name: String) -> Result<()> {
-    match Config::discover()? {
-        None => {
-            println!("Not in anchor workspace.");
-            std::process::exit(1);
-        }
-        Some((_cfg, cfg_path, _inside_cargo)) => {
-            match cfg_path.parent() {
-                None => {
-                    println!("Unable to make new program");
-                }
-                Some(parent) => {
-                    std::env::set_current_dir(&parent)?;
-                    new_program(&name)?;
-                    println!("Created new program.");
-                }
-            };
-        }
-    }
-    Ok(())
+    with_workspace(|_cfg, path, _cargo| {
+        match path.parent() {
+            None => {
+                println!("Unable to make new program");
+            }
+            Some(parent) => {
+                std::env::set_current_dir(&parent)?;
+                new_program(&name)?;
+                println!("Created new program.");
+            }
+        };
+        Ok(())
+    })
 }
 
 // Creates a new program crate in the current directory with `name`.
@@ -188,28 +213,11 @@ fn new_program(name: &str) -> Result<()> {
 }
 
 fn build(idl: Option<String>) -> Result<()> {
-    match Config::discover()? {
-        None => build_cwd(idl),
-        Some((cfg, cfg_path, inside_cargo)) => build_ws(cfg, cfg_path, inside_cargo, idl),
-    }
-}
-
-// Runs the build inside a workspace.
-//
-// * Builds a single program if the current dir is within a Cargo subdirectory,
-//   e.g., `programs/my-program/src`.
-// * Builds *all* programs if thje current dir is anywhere else in the workspace.
-//
-fn build_ws(
-    cfg: Config,
-    cfg_path: PathBuf,
-    cargo_toml: Option<PathBuf>,
-    idl: Option<String>,
-) -> Result<()> {
+    let (cfg, path, cargo) = Config::discover()?.expect("Not in workspace.");
     let idl_out = match idl {
         Some(idl) => Some(PathBuf::from(idl)),
         None => {
-            let cfg_parent = match cfg_path.parent() {
+            let cfg_parent = match path.parent() {
                 None => return Err(anyhow!("Invalid Anchor.toml")),
                 Some(parent) => parent,
             };
@@ -217,38 +225,32 @@ fn build_ws(
             Some(cfg_parent.join("target/idl"))
         }
     };
-    match cargo_toml {
-        None => build_all(cfg, cfg_path, idl_out),
-        Some(ct) => _build_cwd(ct, idl_out),
-    }
+    match cargo {
+        None => build_all(&cfg, path, idl_out)?,
+        Some(ct) => build_cwd(ct, idl_out)?,
+    };
+
+    set_workspace_dir_or_exit();
+
+    Ok(())
 }
 
-fn build_all(_cfg: Config, cfg_path: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
+fn build_all(_cfg: &Config, cfg_path: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
     match cfg_path.parent() {
         None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
         Some(parent) => {
             let files = fs::read_dir(parent.join("programs"))?;
             for f in files {
                 let p = f?.path();
-                _build_cwd(p.join("Cargo.toml"), idl_out.clone())?;
+                build_cwd(p.join("Cargo.toml"), idl_out.clone())?;
             }
             Ok(())
         }
     }
 }
 
-fn build_cwd(idl_out: Option<String>) -> Result<()> {
-    match find_cargo_toml()? {
-        None => {
-            println!("Cargo.toml not found");
-            std::process::exit(1);
-        }
-        Some(cargo_toml) => _build_cwd(cargo_toml, idl_out.map(PathBuf::from)),
-    }
-}
-
 // Runs the build command outside of a workspace.
-fn _build_cwd(cargo_toml: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
+fn build_cwd(cargo_toml: PathBuf, idl_out: Option<PathBuf>) -> Result<()> {
     match cargo_toml.parent() {
         None => return Err(anyhow!("Unable to find parent")),
         Some(p) => std::env::set_current_dir(&p)?,
@@ -308,20 +310,136 @@ fn idl(subcmd: IdlCommand) -> Result<()> {
             program_id,
             filepath,
         } => idl_init(program_id, filepath),
+        IdlCommand::Update {
+            program_id,
+            filepath,
+        } => idl_update(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();
+    with_workspace(|cfg, _path, _cargo| {
+        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_update(program_id: Pubkey, idl_filepath: String) -> Result<()> {
+    with_workspace(|cfg, _path, _cargo| {
+        let bytes = std::fs::read(idl_filepath)?;
+        let idl: Idl = serde_json::from_reader(&*bytes)?;
+
+        idl_clear(cfg, &program_id)?;
+        idl_write(cfg, &program_id, &idl)?;
+
+        Ok(())
+    })
+}
+
+// Clears out *all* IDL data. The authority for the IDL must be the configured
+// wallet.
+fn idl_clear(cfg: &Config, program_id: &Pubkey) -> Result<()> {
+    let idl_address = IdlAccount::address(program_id);
+    let keypair = solana_sdk::signature::read_keypair_file(&cfg.wallet.to_string())
+        .map_err(|_| anyhow!("Unable to read keypair file"))?;
+    let client = RpcClient::new(cfg.cluster.url().to_string());
+
+    let data = serialize_idl_ix(anchor_lang::idl::IdlInstruction::Clear)?;
+    let accounts = vec![
+        AccountMeta::new(idl_address, false),
+        AccountMeta::new_readonly(keypair.pubkey(), true),
+    ];
+    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()
+        },
+    )?;
 
-    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)?;
+    Ok(())
+}
+
+// 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.
+fn idl_write(cfg: &Config, program_id: &Pubkey, idl: &Idl) -> Result<()> {
+    // Misc.
+    let idl_address = IdlAccount::address(program_id);
+    let keypair = solana_sdk::signature::read_keypair_file(&cfg.wallet.to_string())
+        .map_err(|_| anyhow!("Unable to read keypair file"))?;
+    let client = RpcClient::new(cfg.cluster.url().to_string());
 
-    println!("Idl account created: {:?}", idl_address);
+    // 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()?
+    };
+
+    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(())
 }
 
@@ -358,62 +476,40 @@ enum OutFile {
 }
 
 // Builds, deploys, and tests all workspace programs in a single command.
-fn test() -> Result<()> {
-    // Switch directories to top level workspace.
-    set_workspace_dir_or_exit();
-
-    // Build everything.
-    build(None)?;
-
-    // Switch again (todo: restore cwd in `build` command).
-    set_workspace_dir_or_exit();
-
-    // Deploy all programs.
-    let cfg = Config::discover()?.expect("Inside a workspace").0;
-
-    // Bootup validator.
-    let validator_handle = match cfg.cluster.url() {
-        "http://127.0.0.1:8899" => Some(start_test_validator()?),
-        _ => None,
-    };
+fn test(skip_deploy: bool) -> Result<()> {
+    with_workspace(|cfg, _path, _cargo| {
+        // Bootup validator, if needed.
+        let validator_handle = match cfg.cluster.url() {
+            "http://127.0.0.1:8899" => Some(start_test_validator()?),
+            _ => None,
+        };
 
-    let programs = deploy_ws(cfg.cluster.url(), &cfg.wallet.to_string())?;
-
-    // Store deployed program addresses in IDL metadata (for consumption by
-    // client + tests).
-    for (program, address) in programs {
-        // Add metadata to the IDL.
-        let mut idl = program.idl;
-        idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
-            address: address.to_string(),
-        })?);
-        // Persist it.
-        let idl_out = PathBuf::from("target/idl")
-            .join(&idl.name)
-            .with_extension("json");
-        write_idl(&idl, OutFile::File(idl_out))?;
-    }
+        // Deploy programs.
+        if !skip_deploy {
+            deploy(None, None)?;
+        }
 
-    // Run the tests.
-    if let Err(e) = std::process::Command::new("mocha")
-        .arg("-t")
-        .arg("1000000")
-        .arg("tests/")
-        .env("ANCHOR_PROVIDER_URL", cfg.cluster.url())
-        .stdout(Stdio::inherit())
-        .stderr(Stdio::inherit())
-        .output()
-    {
+        // Run the tests.
+        if let Err(e) = std::process::Command::new("mocha")
+            .arg("-t")
+            .arg("1000000")
+            .arg("tests/")
+            .env("ANCHOR_PROVIDER_URL", cfg.cluster.url())
+            .stdout(Stdio::inherit())
+            .stderr(Stdio::inherit())
+            .output()
+        {
+            if let Some(mut validator_handle) = validator_handle {
+                validator_handle.kill()?;
+            }
+            return Err(anyhow::format_err!("{}", e.to_string()));
+        }
         if let Some(mut validator_handle) = validator_handle {
             validator_handle.kill()?;
         }
-        return Err(anyhow::format_err!("{}", e.to_string()));
-    }
-    if let Some(mut validator_handle) = validator_handle {
-        validator_handle.kill()?;
-    }
 
-    Ok(())
+        Ok(())
+    })
 }
 
 #[derive(Debug, Serialize, Deserialize)]
@@ -467,43 +563,156 @@ fn start_test_validator() -> Result<Child> {
 // TODO: Testing and deploys should use separate sections of metadata.
 //       Similarly, each network should have separate metadata.
 fn deploy(url: Option<String>, keypair: Option<String>) -> Result<()> {
-    // Build all programs.
+    _deploy(url, keypair).map(|_| ())
+}
+
+fn _deploy(url: Option<String>, keypair: Option<String>) -> Result<Vec<(Pubkey, Program)>> {
+    with_workspace(|cfg, _path, _cargo| {
+        build(None)?;
+
+        // Fallback to config vars if not provided via CLI.
+        let url = url.unwrap_or(cfg.cluster.url().to_string());
+        let keypair = keypair.unwrap_or(cfg.wallet.to_string());
+
+        // Deploy the programs.
+        println!("Deploying workspace: {}", url);
+        println!("Upgrade authority: {}", keypair);
+
+        let mut programs = Vec::new();
+
+        for mut program in read_all_programs()? {
+            let binary_path = program.binary_path().display().to_string();
+
+            println!("Deploying {}...", binary_path);
+
+            // Write the program's keypair filepath. This forces a new deploy
+            // address.
+            let program_kp = Keypair::generate(&mut OsRng);
+            let mut file = File::create(program.anchor_keypair_path())?;
+            file.write_all(format!("{:?}", &program_kp.to_bytes()).as_bytes())?;
+
+            // Send deploy transactions.
+            let exit = std::process::Command::new("solana")
+                .arg("program")
+                .arg("deploy")
+                .arg("--url")
+                .arg(&url)
+                .arg("--keypair")
+                .arg(&keypair)
+                .arg("--program-id")
+                .arg(program.anchor_keypair_path().display().to_string())
+                .arg(&binary_path)
+                .stdout(Stdio::inherit())
+                .stderr(Stdio::inherit())
+                .output()
+                .expect("Must deploy");
+            if !exit.status.success() {
+                println!("There was a problem deploying: {:?}.", exit);
+                std::process::exit(exit.status.code().unwrap_or(1));
+            }
+
+            // Add program address to the IDL.
+            program.idl.metadata = Some(serde_json::to_value(IdlTestMetadata {
+                address: program_kp.pubkey().to_string(),
+            })?);
+
+            // Persist it.
+            let idl_out = PathBuf::from("target/idl")
+                .join(&program.idl.name)
+                .with_extension("json");
+            write_idl(&program.idl, OutFile::File(idl_out))?;
+
+            programs.push((program_kp.pubkey(), program))
+        }
+
+        println!("Deploy success");
+
+        Ok(programs)
+    })
+}
+
+fn upgrade(program_id: Pubkey, program_filepath: String) -> Result<()> {
+    let path: PathBuf = program_filepath.parse().unwrap();
+    let program_filepath = path.canonicalize()?.display().to_string();
+
+    with_workspace(|cfg, _path, _cargo| {
+        let exit = std::process::Command::new("solana")
+            .arg("program")
+            .arg("deploy")
+            .arg("--url")
+            .arg(cfg.cluster.url())
+            .arg("--keypair")
+            .arg(&cfg.wallet.to_string())
+            .arg("--program-id")
+            .arg(program_id.to_string())
+            .arg(&program_filepath)
+            .stdout(Stdio::inherit())
+            .stderr(Stdio::inherit())
+            .output()
+            .expect("Must deploy");
+        if !exit.status.success() {
+            println!("There was a problem deploying: {:?}.", exit);
+            std::process::exit(exit.status.code().unwrap_or(1));
+        }
+        Ok(())
+    })
+}
+
+fn launch(url: Option<String>, keypair: Option<String>) -> Result<()> {
+    // Build and deploy.
+    let programs = _deploy(url.clone(), keypair.clone())?;
+
+    with_workspace(|cfg, _path, _cargo| {
+        let url = url.unwrap_or(cfg.cluster.url().to_string());
+        let keypair = keypair.unwrap_or(cfg.wallet.to_string());
+
+        // Add metadata to all IDLs.
+        for (address, program) in programs {
+            // Store the IDL on chain.
+            let idl_address = create_idl_account(&cfg, &keypair, &address, &program.idl)?;
+            println!("IDL account created: {}", idl_address.to_string());
+        }
+
+        // Run migration script.
+        if Path::new("migrations/deploy.js").exists() {
+            migrate(Some(url))?;
+        }
+
+        Ok(())
+    })
+}
+
+// with_workspace ensures the current working directory is always the top level
+// workspace directory, i.e., where the `Anchor.toml` file is located, before
+// and after the closure invocation.
+//
+// The closure passed into this function must never change the working directory
+// to be outside the workspace. Doing so will have undefined behavior.
+fn with_workspace<R>(f: impl FnOnce(&Config, PathBuf, Option<PathBuf>) -> R) -> R {
     set_workspace_dir_or_exit();
-    build(None)?;
+
+    clear_program_keys().unwrap();
+
+    let (cfg, cfg_path, cargo_toml) = Config::discover()
+        .expect("Previously set the workspace dir")
+        .expect("Anchor.toml must always exist");
+    let r = f(&cfg, cfg_path, cargo_toml);
+
     set_workspace_dir_or_exit();
+    clear_program_keys().unwrap();
 
-    // Deploy all programs.
-    let cfg = Config::discover()?.expect("Inside a workspace").0;
-    let url = url.unwrap_or(cfg.cluster.url().to_string());
-    let keypair = keypair.unwrap_or(cfg.wallet.to_string());
-    let deployment = deploy_ws(&url, &keypair)?;
-
-    // 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 {
-            address: address.to_string(),
-        })?);
-
-        // Persist it.
-        let idl_out = PathBuf::from("target/idl")
-            .join(&idl.name)
-            .with_extension("json");
-        write_idl(&idl, OutFile::File(idl_out))?;
-
-        println!("Deployed {} at {}", idl.name, address.to_string());
-    }
+    r
+}
 
-    // Run migration script.
-    if Path::new("migrations/deploy.js").exists() {
-        migrate(&url)?;
+// The Solana CLI doesn't redeploy a program if this file exists.
+// So remove it to make all commands explicit.
+fn clear_program_keys() -> Result<()> {
+    for program in read_all_programs().expect("Programs dir doesn't exist") {
+        let anchor_keypair_path = program.anchor_keypair_path();
+        if Path::exists(&anchor_keypair_path) {
+            std::fs::remove_file(anchor_keypair_path).expect("Always remove");
+        }
     }
-
     Ok(())
 }
 
@@ -563,51 +772,7 @@ fn create_idl_account(
         )?;
     }
 
-    // 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;
-        }
-    }
+    idl_write(cfg, program_id, idl)?;
 
     Ok(idl_address)
 }
@@ -618,66 +783,29 @@ fn serialize_idl_ix(ix_inner: anchor_lang::idl::IdlInstruction) -> Result<Vec<u8
     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());
-    let deploy_script_host_str = template::deploy_script_host(url, &module_path);
-    std::env::set_current_dir(".anchor")?;
-
-    std::fs::write("deploy.js", deploy_script_host_str)?;
-    if let Err(_e) = std::process::Command::new("node")
-        .arg("deploy.js")
-        .stdout(Stdio::inherit())
-        .stderr(Stdio::inherit())
-        .output()
-    {
-        std::process::exit(1);
-    }
-
-    println!("Deploy complete.");
-    Ok(())
-}
-
-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("--url")
-            .arg(url)
-            .arg("--keypair")
-            .arg(keypair)
-            .arg(&binary_path)
+fn migrate(url: Option<String>) -> Result<()> {
+    with_workspace(|cfg, _path, _cargo| {
+        println!("Running migration deploy script");
+
+        let url = url.unwrap_or(cfg.cluster.url().to_string());
+        let cur_dir = std::env::current_dir()?;
+        let module_path = format!("{}/migrations/deploy.js", cur_dir.display());
+        let deploy_script_host_str = template::deploy_script_host(&url, &module_path);
+        std::env::set_current_dir(".anchor")?;
+
+        std::fs::write("deploy.js", deploy_script_host_str)?;
+        if let Err(_e) = std::process::Command::new("node")
+            .arg("deploy.js")
+            .stdout(Stdio::inherit())
+            .stderr(Stdio::inherit())
             .output()
-            .expect("Must deploy");
-        if !exit.status.success() {
-            println!("There was a problem deploying: {:?}.", exit);
-            std::process::exit(exit.status.code().unwrap_or(1));
+        {
+            std::process::exit(1);
         }
-        let stdout: DeployStdout = serde_json::from_str(std::str::from_utf8(&exit.stdout)?)?;
-        programs.push((program, stdout.program_id.parse()?));
-    }
-    println!("Deploy success!");
-    Ok(programs)
-}
 
-#[derive(Debug, Deserialize)]
-#[serde(rename_all = "PascalCase")]
-pub struct DeployStdout {
-    program_id: String,
+        println!("Deploy complete.");
+        Ok(())
+    })
 }
 
 fn set_workspace_dir_or_exit() {
@@ -709,3 +837,23 @@ fn set_workspace_dir_or_exit() {
         }
     }
 }
+
+fn airdrop(url: Option<String>) -> Result<()> {
+    let url = url.unwrap_or("https://devnet.solana.com".to_string());
+    loop {
+        let exit = std::process::Command::new("solana")
+            .arg("airdrop")
+            .arg("10")
+            .arg("--url")
+            .arg(&url)
+            .stdout(Stdio::inherit())
+            .stderr(Stdio::inherit())
+            .output()
+            .expect("Must airdrop");
+        if !exit.status.success() {
+            println!("There was a problem airdropping: {:?}.", exit);
+            std::process::exit(exit.status.code().unwrap_or(1));
+        }
+        std::thread::sleep(std::time::Duration::from_millis(10000));
+    }
+}

+ 0 - 1
client/example/src/main.rs

@@ -13,7 +13,6 @@ use basic_2::Author;
 use composite::accounts::{Bar, CompositeUpdate, Foo, Initialize};
 use composite::instruction::CompositeInstruction;
 use composite::{DummyA, DummyB};
-
 use rand::rngs::OsRng;
 
 fn main() -> Result<()> {

+ 1 - 1
lang/attribute/state/src/lib.rs

@@ -11,7 +11,7 @@ pub fn state(
     let item_struct = parse_macro_input!(input as syn::ItemStruct);
 
     proc_macro::TokenStream::from(quote! {
-        #[account("state")]
+        #[account]
         #item_struct
     })
 }

+ 1 - 0
lang/src/ctor.rs

@@ -11,6 +11,7 @@ use solana_program::sysvar::rent::Rent;
 #[derive(Accounts)]
 pub struct Ctor<'info> {
     // Payer of the transaction.
+    #[account(signer)]
     pub from: AccountInfo<'info>,
     // The deterministically defined "state" account being created via
     // `create_account_with_seed`.

+ 1 - 1
ts/src/coder.ts

@@ -333,7 +333,7 @@ export async function accountDiscriminator(name: string): Promise<Buffer> {
 export async function stateDiscriminator(name: string): Promise<Buffer> {
   return Buffer.from(
     (
-      await sha256(`state:${name}`, {
+      await sha256(`account:${name}`, {
         outputFormat: "buffer",
       })
     ).slice(0, 8)