2021-09-07 17:57:01 -07:00
use crate ::config ::{
2021-11-24 12:09:48 -08:00
AnchorPackage , BootstrapMode , BuildConfig , Config , ConfigOverride , Manifest , ProgramDeployment ,
2022-03-30 14:17:54 -07:00
ProgramWorkspace , ScriptsConfig , TestValidator , WithPath , SHUTDOWN_WAIT , STARTUP_WAIT ,
2021-09-07 17:57:01 -07:00
} ;
2021-05-22 16:06:08 -07:00
use anchor_client ::Cluster ;
2022-04-04 15:37:24 -07:00
use anchor_lang ::idl ::{ IdlAccount , IdlInstruction , ERASED_AUTHORITY } ;
2021-01-31 03:07:52 -08:00
use anchor_lang ::{ AccountDeserialize , AnchorDeserialize , AnchorSerialize } ;
2021-01-02 22:40:17 -08:00
use anchor_syn ::idl ::Idl ;
2021-03-27 10:09:03 -07:00
use anyhow ::{ anyhow , Context , Result } ;
2022-01-27 16:32:58 -08:00
use clap ::Parser ;
2021-09-25 09:51:56 -07:00
use flate2 ::read ::GzDecoder ;
2021-01-29 06:19:00 -08:00
use flate2 ::read ::ZlibDecoder ;
2021-08-08 02:11:48 -07:00
use flate2 ::write ::{ GzEncoder , ZlibEncoder } ;
2021-01-29 06:19:00 -08:00
use flate2 ::Compression ;
2021-09-11 12:59:53 -07:00
use heck ::SnakeCase ;
2021-01-31 00:59:01 -08:00
use rand ::rngs ::OsRng ;
2021-08-08 02:11:48 -07:00
use reqwest ::blocking ::multipart ::{ Form , Part } ;
use reqwest ::blocking ::Client ;
2021-10-25 09:47:59 -07:00
use semver ::{ Version , VersionReq } ;
2021-01-02 22:40:17 -08:00
use serde ::{ Deserialize , Serialize } ;
2021-01-04 18:29:16 -08:00
use solana_client ::rpc_client ::RpcClient ;
2021-01-29 06:19:00 -08:00
use solana_client ::rpc_config ::RpcSendTransactionConfig ;
use solana_program ::instruction ::{ AccountMeta , Instruction } ;
2021-03-09 05:40:09 -08:00
use solana_sdk ::account_utils ::StateMut ;
2021-11-29 18:04:09 -08:00
use solana_sdk ::bpf_loader ;
use solana_sdk ::bpf_loader_deprecated ;
use solana_sdk ::bpf_loader_upgradeable ::{ self , UpgradeableLoaderState } ;
2021-01-29 06:19:00 -08:00
use solana_sdk ::commitment_config ::CommitmentConfig ;
2021-01-02 22:40:17 -08:00
use solana_sdk ::pubkey ::Pubkey ;
2021-01-31 00:59:01 -08:00
use solana_sdk ::signature ::Keypair ;
2021-01-29 06:19:00 -08:00
use solana_sdk ::signature ::Signer ;
2021-03-12 12:43:26 -08:00
use solana_sdk ::sysvar ;
2021-01-29 06:19:00 -08:00
use solana_sdk ::transaction ::Transaction ;
2021-09-07 17:57:01 -07:00
use std ::collections ::BTreeMap ;
2021-05-22 16:06:08 -07:00
use std ::collections ::HashMap ;
2022-03-12 14:36:35 -08:00
use std ::collections ::HashSet ;
2021-12-27 10:34:45 -08:00
use std ::ffi ::OsString ;
2021-03-27 10:09:03 -07:00
use std ::fs ::{ self , File } ;
2021-01-02 22:40:17 -08:00
use std ::io ::prelude ::* ;
use std ::path ::{ Path , PathBuf } ;
use std ::process ::{ Child , Stdio } ;
2022-03-12 14:36:35 -08:00
use std ::str ::FromStr ;
2021-01-02 22:40:17 -08:00
use std ::string ::ToString ;
2021-09-25 09:51:56 -07:00
use tar ::Archive ;
2021-01-02 22:40:17 -08:00
2021-08-08 02:11:48 -07:00
pub mod config ;
2022-03-21 16:48:01 -07:00
mod path ;
2021-08-08 02:11:48 -07:00
pub mod template ;
2020-12-31 15:48:06 -08:00
2021-04-06 15:15:18 -07:00
// Version of the docker image.
2021-08-08 02:11:48 -07:00
pub const VERSION : & str = env! ( " CARGO_PKG_VERSION " ) ;
pub const DOCKER_BUILDER_VERSION : & str = VERSION ;
2021-04-06 15:15:18 -07:00
2022-01-27 16:32:58 -08:00
#[ derive(Debug, Parser) ]
2021-04-06 17:55:01 -07:00
#[ clap(version = VERSION) ]
2020-12-31 15:48:06 -08:00
pub struct Opts {
2021-05-24 16:04:17 -07:00
#[ clap(flatten) ]
pub cfg_override : ConfigOverride ,
2020-12-31 15:48:06 -08:00
#[ clap(subcommand) ]
pub command : Command ,
}
2022-01-27 16:32:58 -08:00
#[ derive(Debug, Parser) ]
2020-12-31 15:48:06 -08:00
pub enum Command {
2021-01-02 22:40:17 -08:00
/// Initializes a workspace.
2021-03-01 01:25:59 -08:00
Init {
name : String ,
#[ clap(short, long) ]
2021-10-17 17:53:32 -07:00
javascript : bool ,
2022-03-14 08:43:05 -07:00
#[ clap(long) ]
no_git : bool ,
2021-03-01 01:25:59 -08:00
} ,
2021-01-31 00:59:01 -08:00
/// Builds the workspace.
2022-04-22 14:04:05 -07:00
#[ clap(name = " build " , alias = " b " ) ]
2021-01-02 22:40:17 -08:00
Build {
/// Output directory for the IDL.
#[ clap(short, long) ]
idl : Option < String > ,
2022-02-20 16:43:19 -08:00
/// True if the build should not fail even if there are
/// no "CHECK" comments where normally required
#[ clap(long) ]
skip_lint : bool ,
2021-11-09 22:40:28 -08:00
/// Output directory for the TypeScript IDL.
#[ clap(short = 't', long) ]
idl_ts : Option < String > ,
2021-04-29 10:24:43 -07:00
/// True if the build artifact needs to be deterministic and verifiable.
2021-03-09 05:40:09 -08:00
#[ clap(short, long) ]
verifiable : bool ,
2021-06-07 18:13:53 -07:00
#[ clap(short, long) ]
program_name : Option < String > ,
2021-08-08 02:11:48 -07:00
/// Version of the Solana toolchain to use. For --verifiable builds
/// only.
#[ clap(short, long) ]
solana_version : Option < String > ,
2021-11-24 12:09:48 -08:00
/// Docker image to use. For --verifiable builds only.
#[ clap(short, long) ]
docker_image : Option < String > ,
/// Bootstrap docker image from scratch, installing all requirements for
/// verifiable builds. Only works for debian-based images.
#[ clap(arg_enum, short, long, default_value = " none " ) ]
bootstrap : BootstrapMode ,
2021-09-27 08:45:28 -07:00
/// Arguments to pass to the underlying `cargo build-bpf` command
2021-09-26 17:15:31 -07:00
#[ clap(
required = false ,
takes_value = true ,
multiple_values = true ,
last = true
) ]
2021-09-27 08:45:28 -07:00
cargo_args : Vec < String > ,
2022-04-21 13:37:41 -07:00
/// Suppress doc strings in IDL output
#[ clap(long) ]
no_docs : bool ,
2021-03-09 05:40:09 -08:00
} ,
2021-12-27 10:34:45 -08:00
/// Expands macros (wrapper around cargo expand)
///
/// Use it in a program folder to expand program
///
/// Use it in a workspace but outside a program
/// folder to expand the entire workspace
Expand {
/// Expand only this program
#[ clap(short, long) ]
program_name : Option < String > ,
/// Arguments to pass to the underlying `cargo expand` command
#[ clap(
required = false ,
takes_value = true ,
multiple_values = true ,
last = true
) ]
cargo_args : Vec < String > ,
} ,
2021-03-09 05:40:09 -08:00
/// Verifies the on-chain bytecode matches the locally compiled artifact.
/// Run this command inside a program subdirectory, i.e., in the dir
/// containing the program's Cargo.toml.
Verify {
/// The deployed program to compare against.
program_id : Pubkey ,
2021-08-08 02:11:48 -07:00
#[ clap(short, long) ]
program_name : Option < String > ,
/// Version of the Solana toolchain to use. For --verifiable builds
/// only.
#[ clap(short, long) ]
solana_version : Option < String > ,
2021-11-24 12:09:48 -08:00
/// Docker image to use. For --verifiable builds only.
#[ clap(short, long) ]
docker_image : Option < String > ,
/// Bootstrap docker image from scratch, installing all requirements for
/// verifiable builds. Only works for debian-based images.
#[ clap(arg_enum, short, long, default_value = " none " ) ]
bootstrap : BootstrapMode ,
2021-09-27 08:45:28 -07:00
/// Arguments to pass to the underlying `cargo build-bpf` command.
#[ clap(
required = false ,
takes_value = true ,
multiple_values = true ,
last = true
) ]
cargo_args : Vec < String > ,
2021-01-02 22:40:17 -08:00
} ,
2022-04-22 14:04:05 -07:00
#[ clap(name = " test " , alias = " t " ) ]
2021-01-02 22:40:17 -08:00
/// Runs integration tests against a localnetwork.
2021-01-31 00:59:01 -08:00
Test {
/// Use this flag if you want to run tests against previously deployed
/// programs.
2021-02-26 06:16:19 -08:00
#[ clap(long) ]
2021-01-31 00:59:01 -08:00
skip_deploy : bool ,
2022-02-20 16:43:19 -08:00
/// True if the build should not fail even if there are
/// no "CHECK" comments where normally required
#[ clap(long) ]
skip_lint : bool ,
2021-02-26 06:16:19 -08:00
/// Flag to skip starting a local validator, if the configured cluster
/// url is a localnet.
#[ clap(long) ]
skip_local_validator : bool ,
2021-05-21 22:20:59 -07:00
/// Flag to skip building the program in the workspace,
/// use this to save time when running test and the program code is not altered.
#[ clap(long) ]
skip_build : bool ,
2021-09-22 12:24:27 -07:00
/// Flag to keep the local validator running after tests
/// to be able to check the transactions.
#[ clap(long) ]
detach : bool ,
2021-08-31 14:52:41 -07:00
#[ clap(multiple_values = true) ]
2021-07-29 14:38:24 -07:00
args : Vec < String > ,
2021-09-27 08:45:28 -07:00
/// Arguments to pass to the underlying `cargo build-bpf` command.
#[ clap(
required = false ,
takes_value = true ,
multiple_values = true ,
last = true
) ]
cargo_args : Vec < String > ,
2021-01-31 00:59:01 -08:00
} ,
2021-01-02 22:40:17 -08:00
/// Creates a new program.
New { name : String } ,
2021-01-31 00:59:01 -08:00
/// Commands for interacting with interface definitions.
2020-12-31 15:48:06 -08:00
Idl {
2021-01-29 06:19:00 -08:00
#[ clap(subcommand) ]
subcmd : IdlCommand ,
2020-12-31 15:48:06 -08:00
} ,
2022-03-11 07:46:52 -08:00
/// Remove all artifacts from the target directory except program keypairs.
Clean ,
2021-01-31 00:59:01 -08:00
/// Deploys each program in the workspace.
2021-01-04 12:27:35 -08:00
Deploy {
2022-04-20 13:37:14 -07:00
/// Only deploy this program
2021-05-17 10:50:36 -07:00
#[ clap(short, long) ]
program_name : Option < String > ,
2022-04-20 13:37:14 -07:00
/// Keypair of the program (filepath) (requires program-name)
#[ clap(long, requires = " program-name " ) ]
program_keypair : Option < String > ,
2021-01-04 12:27:35 -08:00
} ,
2021-01-29 06:19:00 -08:00
/// Runs the deploy migration script.
2021-05-24 16:04:17 -07:00
Migrate ,
2021-01-31 00:59:01 -08:00
/// Deploys, initializes an IDL, and migrates all in one command.
/// 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 ,
} ,
2021-03-12 12:43:26 -08:00
#[ cfg(feature = " dev " ) ]
2021-01-31 00:59:01 -08:00
/// Runs an airdrop loop, continuously funding the configured wallet.
Airdrop {
#[ clap(short, long) ]
url : Option < String > ,
2021-01-29 06:19:00 -08:00
} ,
2021-04-14 21:03:08 -07:00
/// Cluster commands.
Cluster {
#[ clap(subcommand) ]
subcmd : ClusterCommand ,
} ,
2021-05-22 16:06:08 -07:00
/// Starts a node shell with an Anchor client setup according to the local
/// config.
2021-05-24 16:04:17 -07:00
Shell ,
2021-06-27 13:17:05 -07:00
/// Runs the script defined by the current workspace's Anchor.toml.
Run {
/// The name of the script to run.
script : String ,
2022-05-29 14:24:45 -07:00
/// Argument to pass to the underlying script.
#[ clap(
required = false ,
takes_value = true ,
multiple_values = true ,
last = true
) ]
script_args : Vec < String > ,
2021-06-27 13:17:05 -07:00
} ,
2021-08-08 02:11:48 -07:00
/// Saves an api token from the registry locally.
Login {
/// API access token.
token : String ,
} ,
/// Publishes a verified build to the Anchor registry.
Publish {
/// The name of the program to publish.
program : String ,
2021-09-27 08:45:28 -07:00
/// Arguments to pass to the underlying `cargo build-bpf` command.
#[ clap(
required = false ,
takes_value = true ,
multiple_values = true ,
last = true
) ]
cargo_args : Vec < String > ,
2022-05-03 06:48:51 -07:00
/// Flag to skip building the program in the workspace,
/// use this to save time when publishing the program
#[ clap(long) ]
skip_build : bool ,
2021-08-08 02:11:48 -07:00
} ,
2021-09-07 13:06:15 -07:00
/// Keypair commands.
Keys {
#[ clap(subcommand) ]
subcmd : KeysCommand ,
} ,
2021-10-02 14:46:57 -07:00
/// Localnet commands.
Localnet {
/// Flag to skip building the program in the workspace,
/// use this to save time when running test and the program code is not altered.
#[ clap(long) ]
skip_build : bool ,
/// Use this flag if you want to run tests against previously deployed
/// programs.
#[ clap(long) ]
skip_deploy : bool ,
2022-02-20 16:43:19 -08:00
/// True if the build should not fail even if there are
/// no "CHECK" comments where normally required
#[ clap(long) ]
skip_lint : bool ,
2021-10-02 14:46:57 -07:00
/// Arguments to pass to the underlying `cargo build-bpf` command.
#[ clap(
required = false ,
takes_value = true ,
multiple_values = true ,
last = true
) ]
cargo_args : Vec < String > ,
} ,
2021-09-07 13:06:15 -07:00
}
2022-01-27 16:32:58 -08:00
#[ derive(Debug, Parser) ]
2021-09-07 13:06:15 -07:00
pub enum KeysCommand {
List ,
2020-12-31 15:48:06 -08:00
}
2022-01-27 16:32:58 -08:00
#[ derive(Debug, Parser) ]
2021-01-29 06:19:00 -08:00
pub enum IdlCommand {
/// Initializes a program's IDL account. Can only be run once.
Init {
program_id : Pubkey ,
#[ clap(short, long) ]
filepath : String ,
} ,
2021-03-12 12:43:26 -08:00
/// Writes an IDL into a buffer account. This can be used with SetBuffer
/// to perform an upgrade.
WriteBuffer {
program_id : Pubkey ,
#[ clap(short, long) ]
filepath : String ,
} ,
/// Sets a new IDL buffer for the program.
SetBuffer {
program_id : Pubkey ,
/// Address of the buffer account to set as the idl on the program.
#[ clap(short, long) ]
buffer : Pubkey ,
} ,
/// Upgrades the IDL to the new file. An alias for first writing and then
/// then setting the idl buffer account.
2021-01-31 03:07:52 -08:00
Upgrade {
2021-01-31 00:59:01 -08:00
program_id : Pubkey ,
#[ clap(short, long) ]
filepath : String ,
} ,
2021-01-31 03:07:52 -08:00
/// Sets a new authority on the IDL account.
SetAuthority {
2021-03-12 12:43:26 -08:00
/// The IDL account buffer to set the authority of. If none is given,
/// then the canonical IDL account is used.
address : Option < Pubkey > ,
2021-01-31 03:07:52 -08:00
/// Program to change the IDL authority.
#[ clap(short, long) ]
program_id : Pubkey ,
/// New authority of the IDL account.
#[ clap(short, long) ]
new_authority : Pubkey ,
} ,
/// Command to remove the ability to modify the IDL account. This should
/// likely be used in conjection with eliminating an "upgrade authority" on
/// the program.
EraseAuthority {
#[ clap(short, long) ]
program_id : Pubkey ,
} ,
/// Outputs the authority for the IDL account.
Authority {
/// The program to view.
program_id : Pubkey ,
} ,
2021-01-29 06:19:00 -08:00
/// Parses an IDL from source.
Parse {
/// Path to the program's interface definition.
#[ clap(short, long) ]
file : String ,
2021-11-02 17:00:13 -07:00
/// Output file for the IDL (stdout if not specified).
2021-01-29 06:19:00 -08:00
#[ clap(short, long) ]
out : Option < String > ,
2021-11-02 17:00:13 -07:00
/// Output file for the TypeScript IDL.
#[ clap(short = 't', long) ]
out_ts : Option < String > ,
2022-04-21 13:37:41 -07:00
/// Suppress doc strings in output
#[ clap(long) ]
no_docs : bool ,
2021-01-29 06:19:00 -08:00
} ,
2021-03-12 12:43:26 -08:00
/// Fetches an IDL for the given address from a cluster.
/// The address can be a program, IDL account, or IDL buffer.
2021-01-29 06:19:00 -08:00
Fetch {
2021-03-12 12:43:26 -08:00
address : Pubkey ,
2021-01-29 06:19:00 -08:00
/// Output file for the idl (stdout if not specified).
#[ clap(short, long) ]
out : Option < String > ,
} ,
}
2022-01-27 16:32:58 -08:00
#[ derive(Debug, Parser) ]
2021-04-14 21:03:08 -07:00
pub enum ClusterCommand {
/// Prints common cluster urls.
List ,
}
2021-08-08 02:11:48 -07:00
pub fn entry ( opts : Opts ) -> Result < ( ) > {
2020-12-31 15:48:06 -08:00
match opts . command {
2022-03-14 08:43:05 -07:00
Command ::Init {
name ,
javascript ,
no_git ,
} = > init ( & opts . cfg_override , name , javascript , no_git ) ,
2021-05-24 16:04:17 -07:00
Command ::New { name } = > new ( & opts . cfg_override , name ) ,
2021-06-07 18:13:53 -07:00
Command ::Build {
idl ,
2021-11-09 22:40:28 -08:00
idl_ts ,
2021-06-07 18:13:53 -07:00
verifiable ,
program_name ,
2021-08-08 02:11:48 -07:00
solana_version ,
2021-11-24 12:09:48 -08:00
docker_image ,
bootstrap ,
2021-09-27 08:45:28 -07:00
cargo_args ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2022-04-21 13:37:41 -07:00
no_docs ,
2021-08-08 02:11:48 -07:00
} = > build (
& opts . cfg_override ,
idl ,
2021-11-09 22:40:28 -08:00
idl_ts ,
2021-08-08 02:11:48 -07:00
verifiable ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2021-08-08 02:11:48 -07:00
program_name ,
solana_version ,
2021-11-24 12:09:48 -08:00
docker_image ,
bootstrap ,
2021-08-08 02:11:48 -07:00
None ,
None ,
2021-09-27 08:45:28 -07:00
cargo_args ,
2022-04-21 13:37:41 -07:00
no_docs ,
2021-08-08 02:11:48 -07:00
) ,
Command ::Verify {
program_id ,
program_name ,
solana_version ,
2021-11-24 12:09:48 -08:00
docker_image ,
bootstrap ,
2021-09-27 08:45:28 -07:00
cargo_args ,
} = > verify (
& opts . cfg_override ,
program_id ,
program_name ,
solana_version ,
2021-11-24 12:09:48 -08:00
docker_image ,
bootstrap ,
2021-09-27 08:45:28 -07:00
cargo_args ,
) ,
2022-03-11 07:46:52 -08:00
Command ::Clean = > clean ( & opts . cfg_override ) ,
2022-04-20 13:37:14 -07:00
Command ::Deploy {
program_name ,
program_keypair ,
} = > deploy ( & opts . cfg_override , program_name , program_keypair ) ,
2021-12-27 10:34:45 -08:00
Command ::Expand {
program_name ,
cargo_args ,
} = > expand ( & opts . cfg_override , program_name , & cargo_args ) ,
2021-01-31 00:59:01 -08:00
Command ::Upgrade {
program_id ,
program_filepath ,
2021-05-24 16:04:17 -07:00
} = > upgrade ( & opts . cfg_override , program_id , program_filepath ) ,
Command ::Idl { subcmd } = > idl ( & opts . cfg_override , subcmd ) ,
Command ::Migrate = > migrate ( & opts . cfg_override ) ,
2021-02-26 06:16:19 -08:00
Command ::Test {
skip_deploy ,
skip_local_validator ,
2021-05-21 22:20:59 -07:00
skip_build ,
2021-09-22 12:24:27 -07:00
detach ,
2021-07-29 14:38:24 -07:00
args ,
2021-09-27 08:45:28 -07:00
cargo_args ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2021-05-24 16:04:17 -07:00
} = > test (
& opts . cfg_override ,
skip_deploy ,
skip_local_validator ,
skip_build ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2021-09-22 12:24:27 -07:00
detach ,
2021-07-29 14:38:24 -07:00
args ,
2021-09-27 08:45:28 -07:00
cargo_args ,
2021-05-24 16:04:17 -07:00
) ,
2021-03-12 12:43:26 -08:00
#[ cfg(feature = " dev " ) ]
2022-02-03 16:15:23 -08:00
Command ::Airdrop { .. } = > airdrop ( & opts . cfg_override ) ,
2021-04-14 21:03:08 -07:00
Command ::Cluster { subcmd } = > cluster ( subcmd ) ,
2021-05-24 16:04:17 -07:00
Command ::Shell = > shell ( & opts . cfg_override ) ,
2022-05-29 14:24:45 -07:00
Command ::Run {
script ,
script_args ,
} = > run ( & opts . cfg_override , script , script_args ) ,
2021-08-08 02:11:48 -07:00
Command ::Login { token } = > login ( & opts . cfg_override , token ) ,
2021-09-27 08:45:28 -07:00
Command ::Publish {
program ,
cargo_args ,
2022-05-03 06:48:51 -07:00
skip_build ,
} = > publish ( & opts . cfg_override , program , cargo_args , skip_build ) ,
2021-09-07 13:06:15 -07:00
Command ::Keys { subcmd } = > keys ( & opts . cfg_override , subcmd ) ,
2021-10-02 14:46:57 -07:00
Command ::Localnet {
skip_build ,
skip_deploy ,
2022-02-20 16:43:19 -08:00
skip_lint ,
cargo_args ,
} = > localnet (
& opts . cfg_override ,
skip_build ,
skip_deploy ,
skip_lint ,
2021-10-02 14:46:57 -07:00
cargo_args ,
2022-02-20 16:43:19 -08:00
) ,
2020-12-31 15:48:06 -08:00
}
}
2022-03-14 08:43:05 -07:00
fn init ( cfg_override : & ConfigOverride , name : String , javascript : bool , no_git : bool ) -> Result < ( ) > {
2021-08-11 00:40:51 -07:00
if Config ::discover ( cfg_override ) ? . is_some ( ) {
return Err ( anyhow! ( " Workspace already initialized " ) ) ;
}
2021-01-02 22:40:17 -08:00
2022-06-06 17:21:10 -07:00
// Additional keywords that have not been added to the `syn` crate as reserved words
// https://github.com/dtolnay/syn/pull/1098
let extra_keywords = [ " async " , " await " , " try " ] ;
// Anchor converts to snake case before writing the program name
if syn ::parse_str ::< syn ::Ident > ( & name . to_snake_case ( ) ) . is_err ( )
| | extra_keywords . contains ( & name . to_snake_case ( ) . as_str ( ) )
{
2021-11-15 16:54:51 -08:00
return Err ( anyhow! (
2022-06-06 17:21:10 -07:00
" Anchor workspace name must be a valid Rust identifier. It may not be a Rust reserved word, start with a digit, or include certain disallowed characters. See https://doc.rust-lang.org/reference/identifiers.html for more detail. " ,
2021-11-15 16:54:51 -08:00
) ) ;
}
2021-01-02 22:40:17 -08:00
fs ::create_dir ( name . clone ( ) ) ? ;
std ::env ::set_current_dir ( & name ) ? ;
fs ::create_dir ( " app " ) ? ;
2021-07-29 14:38:24 -07:00
let mut cfg = Config ::default ( ) ;
cfg . scripts . insert (
" test " . to_owned ( ) ,
2021-10-17 17:53:32 -07:00
if javascript {
2021-11-11 14:38:35 -08:00
" yarn run mocha -t 1000000 tests/ "
2021-10-17 17:53:32 -07:00
} else {
2021-11-11 14:38:35 -08:00
" yarn run ts-mocha -p ./tsconfig.json -t 1000000 tests/**/*.ts "
2021-07-29 14:38:24 -07:00
}
. to_owned ( ) ,
) ;
2021-09-07 17:57:01 -07:00
let mut localnet = BTreeMap ::new ( ) ;
localnet . insert (
2021-09-11 15:42:50 -07:00
name . to_snake_case ( ) ,
2021-09-07 17:57:01 -07:00
ProgramDeployment {
address : template ::default_program_id ( ) ,
path : None ,
idl : None ,
} ,
) ;
cfg . programs . insert ( Cluster ::Localnet , localnet ) ;
2021-01-02 22:40:17 -08:00
let toml = cfg . to_string ( ) ;
2022-04-07 06:14:38 -07:00
fs ::write ( " Anchor.toml " , toml ) ? ;
2021-01-02 22:40:17 -08:00
// Build virtual manifest.
2022-04-07 06:14:38 -07:00
fs ::write ( " Cargo.toml " , template ::virtual_manifest ( ) ) ? ;
2021-01-02 22:40:17 -08:00
2021-03-08 18:49:11 -08:00
// Initialize .gitignore file
2022-04-07 06:14:38 -07:00
fs ::write ( " .gitignore " , template ::git_ignore ( ) ) ? ;
// Initialize .prettierignore file
fs ::write ( " .prettierignore " , template ::prettier_ignore ( ) ) ? ;
2021-03-08 18:49:11 -08:00
2021-01-02 22:40:17 -08:00
// Build the program.
fs ::create_dir ( " programs " ) ? ;
new_program ( & name ) ? ;
// Build the test suite.
fs ::create_dir ( " tests " ) ? ;
2021-03-30 23:14:15 -07:00
// Build the migrations directory.
fs ::create_dir ( " migrations " ) ? ;
2021-10-17 17:53:32 -07:00
if javascript {
// Build javascript config
let mut package_json = File ::create ( " package.json " ) ? ;
package_json . write_all ( template ::package_json ( ) . as_bytes ( ) ) ? ;
2021-10-17 20:06:48 -07:00
2021-10-17 17:53:32 -07:00
let mut mocha = File ::create ( & format! ( " tests/ {} .js " , name ) ) ? ;
mocha . write_all ( template ::mocha ( & name ) . as_bytes ( ) ) ? ;
2021-10-17 20:06:48 -07:00
2021-10-17 17:53:32 -07:00
let mut deploy = File ::create ( " migrations/deploy.js " ) ? ;
deploy . write_all ( template ::deploy_script ( ) . as_bytes ( ) ) ? ;
} else {
2021-03-01 01:25:59 -08:00
// Build typescript config
let mut ts_config = File ::create ( " tsconfig.json " ) ? ;
ts_config . write_all ( template ::ts_config ( ) . as_bytes ( ) ) ? ;
2021-08-18 23:50:00 -07:00
let mut ts_package_json = File ::create ( " package.json " ) ? ;
ts_package_json . write_all ( template ::ts_package_json ( ) . as_bytes ( ) ) ? ;
2021-03-30 23:14:15 -07:00
let mut deploy = File ::create ( " migrations/deploy.ts " ) ? ;
2021-06-27 17:09:46 -07:00
deploy . write_all ( template ::ts_deploy_script ( ) . as_bytes ( ) ) ? ;
2021-03-30 23:14:15 -07:00
2021-06-30 11:00:37 -07:00
let mut mocha = File ::create ( & format! ( " tests/ {} .ts " , name ) ) ? ;
2021-03-01 01:25:59 -08:00
mocha . write_all ( template ::ts_mocha ( & name ) . as_bytes ( ) ) ? ;
2021-03-30 23:14:15 -07:00
}
2021-01-27 19:58:26 -08:00
2021-08-18 23:50:00 -07:00
// Install node modules.
let yarn_result = std ::process ::Command ::new ( " yarn " )
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " yarn install failed: {} " , e . to_string ( ) ) ) ? ;
if ! yarn_result . status . success ( ) {
println! ( " Failed yarn install will attempt to npm install " ) ;
std ::process ::Command ::new ( " npm " )
2022-03-18 10:19:01 -07:00
. arg ( " install " )
2021-08-18 23:50:00 -07:00
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " npm install failed: {} " , e . to_string ( ) ) ) ? ;
println! ( " Failed to install node dependencies " )
}
2022-03-14 08:43:05 -07:00
if ! no_git {
let git_result = std ::process ::Command ::new ( " git " )
. arg ( " init " )
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " git init failed: {} " , e . to_string ( ) ) ) ? ;
if ! git_result . status . success ( ) {
eprintln! ( " Failed to automatically initialize a new git repository " ) ;
}
}
2021-01-02 22:40:17 -08:00
println! ( " {} initialized " , name ) ;
2020-12-31 15:48:06 -08:00
Ok ( ( ) )
}
2021-01-02 22:40:17 -08:00
// Creates a new program crate in the `programs/<name>` directory.
2021-05-24 16:04:17 -07:00
fn new ( cfg_override : & ConfigOverride , name : String ) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2021-08-08 02:11:48 -07:00
match cfg . path ( ) . parent ( ) {
2021-01-31 00:59:01 -08:00
None = > {
println! ( " Unable to make new program " ) ;
}
Some ( parent ) = > {
std ::env ::set_current_dir ( & parent ) ? ;
new_program ( & name ) ? ;
println! ( " Created new program. " ) ;
}
} ;
Ok ( ( ) )
} )
2020-12-31 15:48:06 -08:00
}
2021-01-02 22:40:17 -08:00
// Creates a new program crate in the current directory with `name`.
fn new_program ( name : & str ) -> Result < ( ) > {
fs ::create_dir ( & format! ( " programs/ {} " , name ) ) ? ;
fs ::create_dir ( & format! ( " programs/ {} /src/ " , name ) ) ? ;
let mut cargo_toml = File ::create ( & format! ( " programs/ {} /Cargo.toml " , name ) ) ? ;
2021-06-27 17:09:46 -07:00
cargo_toml . write_all ( template ::cargo_toml ( name ) . as_bytes ( ) ) ? ;
2021-01-02 22:40:17 -08:00
let mut xargo_toml = File ::create ( & format! ( " programs/ {} /Xargo.toml " , name ) ) ? ;
2021-01-04 12:27:35 -08:00
xargo_toml . write_all ( template ::xargo_toml ( ) . as_bytes ( ) ) ? ;
2021-01-02 22:40:17 -08:00
let mut lib_rs = File ::create ( & format! ( " programs/ {} /src/lib.rs " , name ) ) ? ;
2021-06-27 17:09:46 -07:00
lib_rs . write_all ( template ::lib_rs ( name ) . as_bytes ( ) ) ? ;
2021-01-02 22:40:17 -08:00
Ok ( ( ) )
}
2021-12-27 10:34:45 -08:00
pub fn expand (
cfg_override : & ConfigOverride ,
program_name : Option < String > ,
cargo_args : & [ String ] ,
) -> Result < ( ) > {
// Change to the workspace member directory, if needed.
if let Some ( program_name ) = program_name . as_ref ( ) {
cd_member ( cfg_override , program_name ) ? ;
}
let workspace_cfg = Config ::discover ( cfg_override ) ? . expect ( " Not in workspace. " ) ;
let cfg_parent = workspace_cfg . path ( ) . parent ( ) . expect ( " Invalid Anchor.toml " ) ;
let cargo = Manifest ::discover ( ) ? ;
let expansions_path = cfg_parent . join ( " .anchor/expanded-macros " ) ;
fs ::create_dir_all ( & expansions_path ) ? ;
match cargo {
// No Cargo.toml found, expand entire workspace
None = > expand_all ( & workspace_cfg , expansions_path , cargo_args ) ,
// Cargo.toml is at root of workspace, expand entire workspace
Some ( cargo ) if cargo . path ( ) . parent ( ) = = workspace_cfg . path ( ) . parent ( ) = > {
expand_all ( & workspace_cfg , expansions_path , cargo_args )
}
// Reaching this arm means Cargo.toml belongs to a single package. Expand it.
Some ( cargo ) = > expand_program (
// If we found Cargo.toml, it must be in a directory so unwrap is safe
cargo . path ( ) . parent ( ) . unwrap ( ) . to_path_buf ( ) ,
expansions_path ,
cargo_args ,
) ,
}
}
fn expand_all (
workspace_cfg : & WithPath < Config > ,
expansions_path : PathBuf ,
cargo_args : & [ String ] ,
) -> Result < ( ) > {
let cur_dir = std ::env ::current_dir ( ) ? ;
for p in workspace_cfg . get_program_list ( ) ? {
expand_program ( p , expansions_path . clone ( ) , cargo_args ) ? ;
}
std ::env ::set_current_dir ( cur_dir ) ? ;
Ok ( ( ) )
}
fn expand_program (
program_path : PathBuf ,
expansions_path : PathBuf ,
cargo_args : & [ String ] ,
) -> Result < ( ) > {
let cargo = Manifest ::from_path ( program_path . join ( " Cargo.toml " ) )
. map_err ( | _ | anyhow! ( " Could not find Cargo.toml for program " ) ) ? ;
let target_dir_arg = {
let mut target_dir_arg = OsString ::from ( " --target-dir= " ) ;
target_dir_arg . push ( expansions_path . join ( " expand-target " ) ) ;
target_dir_arg
} ;
let package_name = & cargo
. package
. as_ref ( )
. ok_or_else ( | | anyhow! ( " Cargo config is missing a package " ) ) ?
. name ;
let program_expansions_path = expansions_path . join ( package_name ) ;
fs ::create_dir_all ( & program_expansions_path ) ? ;
let exit = std ::process ::Command ::new ( " cargo " )
. arg ( " expand " )
. arg ( target_dir_arg )
. arg ( & format! ( " --package= {} " , package_name ) )
. args ( cargo_args )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " {} " , e . to_string ( ) ) ) ? ;
if ! exit . status . success ( ) {
eprintln! ( " 'anchor expand' failed. Perhaps you have not installed 'cargo-expand'? https://github.com/dtolnay/cargo-expand#installation " ) ;
std ::process ::exit ( exit . status . code ( ) . unwrap_or ( 1 ) ) ;
}
let version = cargo . version ( ) ;
let time = chrono ::Utc ::now ( ) . to_string ( ) . replace ( ' ' , " _ " ) ;
let file_path =
program_expansions_path . join ( format! ( " {} - {} - {} .rs " , package_name , version , time ) ) ;
fs ::write ( & file_path , & exit . stdout ) . map_err ( | e | anyhow ::format_err! ( " {} " , e . to_string ( ) ) ) ? ;
println! (
" Expanded {} into file {} \n " ,
package_name ,
file_path . to_string_lossy ( )
) ;
Ok ( ( ) )
}
2021-09-26 18:37:40 -07:00
#[ allow(clippy::too_many_arguments) ]
2021-08-08 02:11:48 -07:00
pub fn build (
2021-06-07 18:13:53 -07:00
cfg_override : & ConfigOverride ,
idl : Option < String > ,
2021-11-09 22:40:28 -08:00
idl_ts : Option < String > ,
2021-06-07 18:13:53 -07:00
verifiable : bool ,
2022-02-20 16:43:19 -08:00
skip_lint : bool ,
2021-06-07 18:13:53 -07:00
program_name : Option < String > ,
2021-08-08 02:11:48 -07:00
solana_version : Option < String > ,
2021-11-24 12:09:48 -08:00
docker_image : Option < String > ,
bootstrap : BootstrapMode ,
2021-08-08 02:11:48 -07:00
stdout : Option < File > , // Used for the package registry server.
stderr : Option < File > , // Used for the package registry server.
2021-09-27 08:45:28 -07:00
cargo_args : Vec < String > ,
2022-04-21 13:37:41 -07:00
no_docs : bool ,
2021-06-07 18:13:53 -07:00
) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
// Change to the workspace member directory, if needed.
2021-08-08 02:11:48 -07:00
if let Some ( program_name ) = program_name . as_ref ( ) {
2021-08-10 21:36:56 -07:00
cd_member ( cfg_override , program_name ) ? ;
2021-08-08 02:11:48 -07:00
}
2021-08-10 21:36:56 -07:00
let cfg = Config ::discover ( cfg_override ) ? . expect ( " Not in workspace. " ) ;
2021-11-24 12:09:48 -08:00
let build_config = BuildConfig {
verifiable ,
solana_version : solana_version . or_else ( | | cfg . solana_version . clone ( ) ) ,
docker_image : docker_image . unwrap_or_else ( | | cfg . docker ( ) ) ,
bootstrap ,
} ;
2021-10-04 23:16:13 -07:00
let cfg_parent = cfg . path ( ) . parent ( ) . expect ( " Invalid Anchor.toml " ) ;
2021-08-10 21:36:56 -07:00
let cargo = Manifest ::discover ( ) ? ;
2021-01-02 22:40:17 -08:00
let idl_out = match idl {
Some ( idl ) = > Some ( PathBuf ::from ( idl ) ) ,
2021-10-04 23:16:13 -07:00
None = > Some ( cfg_parent . join ( " target/idl " ) ) ,
2021-01-02 22:40:17 -08:00
} ;
2021-11-09 22:40:28 -08:00
fs ::create_dir_all ( idl_out . as_ref ( ) . unwrap ( ) ) ? ;
let idl_ts_out = match idl_ts {
Some ( idl_ts ) = > Some ( PathBuf ::from ( idl_ts ) ) ,
None = > Some ( cfg_parent . join ( " target/types " ) ) ,
} ;
fs ::create_dir_all ( idl_ts_out . as_ref ( ) . unwrap ( ) ) ? ;
2021-08-08 02:11:48 -07:00
2021-11-17 15:04:14 -08:00
if ! & cfg . workspace . types . is_empty ( ) {
fs ::create_dir_all ( cfg_parent . join ( & cfg . workspace . types ) ) ? ;
} ;
2021-01-31 00:59:01 -08:00
match cargo {
2021-08-10 21:36:56 -07:00
// No Cargo.toml so build the entire workspace.
None = > build_all (
2021-08-08 02:11:48 -07:00
& cfg ,
2021-08-10 21:36:56 -07:00
cfg . path ( ) ,
2021-08-08 02:11:48 -07:00
idl_out ,
2021-10-04 23:16:13 -07:00
idl_ts_out ,
2021-11-24 12:09:48 -08:00
& build_config ,
2021-08-08 02:11:48 -07:00
stdout ,
stderr ,
2021-09-27 08:45:28 -07:00
cargo_args ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2022-04-21 13:37:41 -07:00
no_docs ,
2021-08-08 02:11:48 -07:00
) ? ,
2021-08-10 21:36:56 -07:00
// If the Cargo.toml is at the root, build the entire workspace.
Some ( cargo ) if cargo . path ( ) . parent ( ) = = cfg . path ( ) . parent ( ) = > build_all (
& cfg ,
cfg . path ( ) ,
idl_out ,
2021-10-04 23:16:13 -07:00
idl_ts_out ,
2021-11-24 12:09:48 -08:00
& build_config ,
2021-08-10 21:36:56 -07:00
stdout ,
stderr ,
2021-09-27 08:45:28 -07:00
cargo_args ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2022-04-21 13:37:41 -07:00
no_docs ,
2021-08-10 21:36:56 -07:00
) ? ,
// Cargo.toml represents a single package. Build it.
Some ( cargo ) = > build_cwd (
& cfg ,
cargo . path ( ) . to_path_buf ( ) ,
idl_out ,
2021-10-04 23:16:13 -07:00
idl_ts_out ,
2021-11-24 12:09:48 -08:00
& build_config ,
2021-08-10 21:36:56 -07:00
stdout ,
stderr ,
2021-09-27 08:45:28 -07:00
cargo_args ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2022-04-21 13:37:41 -07:00
no_docs ,
2021-08-10 21:36:56 -07:00
) ? ,
}
2021-01-31 00:59:01 -08:00
set_workspace_dir_or_exit ( ) ;
Ok ( ( ) )
2021-01-02 22:40:17 -08:00
}
2021-09-26 18:37:40 -07:00
#[ allow(clippy::too_many_arguments) ]
2021-03-09 05:40:09 -08:00
fn build_all (
2021-08-08 02:11:48 -07:00
cfg : & WithPath < Config > ,
cfg_path : & Path ,
2021-03-09 05:40:09 -08:00
idl_out : Option < PathBuf > ,
2021-10-04 23:16:13 -07:00
idl_ts_out : Option < PathBuf > ,
2021-11-24 12:09:48 -08:00
build_config : & BuildConfig ,
2021-08-10 21:36:56 -07:00
stdout : Option < File > , // Used for the package registry server.
stderr : Option < File > , // Used for the package registry server.
2021-09-27 08:45:28 -07:00
cargo_args : Vec < String > ,
2022-02-20 16:43:19 -08:00
skip_lint : bool ,
2022-04-21 13:37:41 -07:00
no_docs : bool ,
2021-03-09 05:40:09 -08:00
) -> Result < ( ) > {
let cur_dir = std ::env ::current_dir ( ) ? ;
let r = match cfg_path . parent ( ) {
2021-01-29 06:19:00 -08:00
None = > Err ( anyhow! ( " Invalid Anchor.toml at {} " , cfg_path . display ( ) ) ) ,
2021-08-08 02:11:48 -07:00
Some ( _parent ) = > {
for p in cfg . get_program_list ( ) ? {
2021-03-09 05:40:09 -08:00
build_cwd (
2021-08-08 02:11:48 -07:00
cfg ,
2021-03-09 05:40:09 -08:00
p . join ( " Cargo.toml " ) ,
idl_out . clone ( ) ,
2021-10-04 23:16:13 -07:00
idl_ts_out . clone ( ) ,
2021-11-24 12:09:48 -08:00
build_config ,
2021-08-10 21:36:56 -07:00
stdout . as_ref ( ) . map ( | f | f . try_clone ( ) ) . transpose ( ) ? ,
stderr . as_ref ( ) . map ( | f | f . try_clone ( ) ) . transpose ( ) ? ,
2021-09-27 08:45:28 -07:00
cargo_args . clone ( ) ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2022-04-21 13:37:41 -07:00
no_docs ,
2021-03-09 05:40:09 -08:00
) ? ;
2021-01-02 22:40:17 -08:00
}
Ok ( ( ) )
}
2021-03-09 05:40:09 -08:00
} ;
std ::env ::set_current_dir ( cur_dir ) ? ;
r
2021-01-02 22:40:17 -08:00
}
// Runs the build command outside of a workspace.
2021-09-26 18:37:40 -07:00
#[ allow(clippy::too_many_arguments) ]
2021-03-09 05:40:09 -08:00
fn build_cwd (
2021-08-08 02:11:48 -07:00
cfg : & WithPath < Config > ,
2021-03-09 05:40:09 -08:00
cargo_toml : PathBuf ,
idl_out : Option < PathBuf > ,
2021-10-04 23:16:13 -07:00
idl_ts_out : Option < PathBuf > ,
2021-11-24 12:09:48 -08:00
build_config : & BuildConfig ,
2021-08-08 02:11:48 -07:00
stdout : Option < File > ,
stderr : Option < File > ,
2021-09-27 08:45:28 -07:00
cargo_args : Vec < String > ,
2022-02-20 16:43:19 -08:00
skip_lint : bool ,
2022-04-21 13:37:41 -07:00
no_docs : bool ,
2021-03-09 05:40:09 -08:00
) -> Result < ( ) > {
2021-01-02 22:40:17 -08:00
match cargo_toml . parent ( ) {
2021-01-29 06:19:00 -08:00
None = > return Err ( anyhow! ( " Unable to find parent " ) ) ,
2021-01-02 22:40:17 -08:00
Some ( p ) = > std ::env ::set_current_dir ( & p ) ? ,
} ;
2021-11-24 12:09:48 -08:00
match build_config . verifiable {
2022-02-20 16:43:19 -08:00
false = > _build_cwd ( cfg , idl_out , idl_ts_out , skip_lint , cargo_args ) ,
true = > build_cwd_verifiable (
cfg ,
cargo_toml ,
build_config ,
stdout ,
stderr ,
skip_lint ,
cargo_args ,
2022-04-21 13:37:41 -07:00
no_docs ,
2022-02-20 16:43:19 -08:00
) ,
2021-03-09 05:40:09 -08:00
}
}
// Builds an anchor program in a docker image and copies the build artifacts
// into the `target/` directory.
2022-04-21 13:37:41 -07:00
#[ allow(clippy::too_many_arguments) ]
2021-08-08 02:11:48 -07:00
fn build_cwd_verifiable (
cfg : & WithPath < Config > ,
cargo_toml : PathBuf ,
2021-11-24 12:09:48 -08:00
build_config : & BuildConfig ,
2021-08-08 02:11:48 -07:00
stdout : Option < File > ,
stderr : Option < File > ,
2022-02-20 16:43:19 -08:00
skip_lint : bool ,
2021-11-17 18:38:26 -08:00
cargo_args : Vec < String > ,
2022-04-21 13:37:41 -07:00
no_docs : bool ,
2021-08-08 02:11:48 -07:00
) -> Result < ( ) > {
2021-03-09 05:40:09 -08:00
// Create output dirs.
2021-08-08 02:11:48 -07:00
let workspace_dir = cfg . path ( ) . parent ( ) . unwrap ( ) . canonicalize ( ) ? ;
2021-08-10 21:36:56 -07:00
fs ::create_dir_all ( workspace_dir . join ( " target/verifiable " ) ) ? ;
2021-03-09 05:40:09 -08:00
fs ::create_dir_all ( workspace_dir . join ( " target/idl " ) ) ? ;
2021-10-05 10:30:38 -07:00
fs ::create_dir_all ( workspace_dir . join ( " target/types " ) ) ? ;
2021-11-17 15:04:14 -08:00
if ! & cfg . workspace . types . is_empty ( ) {
fs ::create_dir_all ( workspace_dir . join ( & cfg . workspace . types ) ) ? ;
}
2021-03-09 05:40:09 -08:00
2021-08-08 02:11:48 -07:00
let container_name = " anchor-program " ;
// Build the binary in docker.
let result = docker_build (
cfg ,
container_name ,
cargo_toml ,
2021-11-24 12:09:48 -08:00
build_config ,
2021-08-08 02:11:48 -07:00
stdout ,
stderr ,
2021-11-17 18:38:26 -08:00
cargo_args ,
2021-08-08 02:11:48 -07:00
) ;
2021-11-24 12:09:48 -08:00
match & result {
Err ( e ) = > {
eprintln! ( " Error during Docker build: {:?} " , e ) ;
}
Ok ( _ ) = > {
// Build the idl.
println! ( " Extracting the IDL " ) ;
2022-04-21 13:37:41 -07:00
if let Ok ( Some ( idl ) ) = extract_idl ( cfg , " src/lib.rs " , skip_lint , no_docs ) {
2021-11-24 12:09:48 -08:00
// Write out the JSON file.
println! ( " Writing the IDL file " ) ;
let out_file = workspace_dir . join ( format! ( " target/idl/ {} .json " , idl . name ) ) ;
write_idl ( & idl , OutFile ::File ( out_file ) ) ? ;
// Write out the TypeScript type.
println! ( " Writing the .ts file " ) ;
let ts_file = workspace_dir . join ( format! ( " target/types/ {} .ts " , idl . name ) ) ;
fs ::write ( & ts_file , template ::idl_ts ( & idl ) ? ) ? ;
// Copy out the TypeScript type.
if ! & cfg . workspace . types . is_empty ( ) {
fs ::copy (
ts_file ,
workspace_dir
. join ( & cfg . workspace . types )
. join ( idl . name )
. with_extension ( " ts " ) ,
) ? ;
}
}
println! ( " Build success " ) ;
2021-11-17 15:04:14 -08:00
}
2021-03-09 05:40:09 -08:00
}
2021-08-08 02:11:48 -07:00
result
}
fn docker_build (
cfg : & WithPath < Config > ,
container_name : & str ,
cargo_toml : PathBuf ,
2021-11-24 12:09:48 -08:00
build_config : & BuildConfig ,
2021-08-08 02:11:48 -07:00
stdout : Option < File > ,
stderr : Option < File > ,
2021-11-17 18:38:26 -08:00
cargo_args : Vec < String > ,
2021-08-08 02:11:48 -07:00
) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
let binary_name = Manifest ::from_path ( & cargo_toml ) ? . lib_name ( ) ? ;
2021-08-08 02:11:48 -07:00
// Docker vars.
2021-11-24 12:09:48 -08:00
let workdir = Path ::new ( " /workdir " ) ;
2021-08-08 02:11:48 -07:00
let volume_mount = format! (
2021-11-24 12:09:48 -08:00
" {}:{} " ,
cfg . path ( ) . parent ( ) . unwrap ( ) . canonicalize ( ) ? . display ( ) ,
workdir . to_str ( ) . unwrap ( ) ,
2021-08-08 02:11:48 -07:00
) ;
2021-11-24 12:09:48 -08:00
println! ( " Using image {:?} " , build_config . docker_image ) ;
2021-08-08 02:11:48 -07:00
// Start the docker image running detached in the background.
2021-11-24 12:09:48 -08:00
let target_dir = workdir . join ( " docker-target " ) ;
2021-08-08 02:11:48 -07:00
println! ( " Run docker image " ) ;
2021-03-09 05:40:09 -08:00
let exit = std ::process ::Command ::new ( " docker " )
2021-08-08 02:11:48 -07:00
. args ( & [
" run " ,
" -it " ,
" -d " ,
" --name " ,
container_name ,
" --env " ,
2021-11-24 12:09:48 -08:00
& format! (
" CARGO_TARGET_DIR={} " ,
target_dir . as_path ( ) . to_str ( ) . unwrap ( )
) ,
2021-08-08 02:11:48 -07:00
" -v " ,
& volume_mount ,
2021-11-24 12:09:48 -08:00
" -w " ,
workdir . to_str ( ) . unwrap ( ) ,
& build_config . docker_image ,
2021-08-08 02:11:48 -07:00
" bash " ,
] )
2021-03-09 05:40:09 -08:00
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
2021-08-08 02:11:48 -07:00
. map_err ( | e | anyhow ::format_err! ( " Docker build failed: {} " , e . to_string ( ) ) ) ? ;
2021-03-09 05:40:09 -08:00
if ! exit . status . success ( ) {
2021-08-08 02:11:48 -07:00
return Err ( anyhow! ( " Failed to build program " ) ) ;
2021-03-09 05:40:09 -08:00
}
2021-01-02 22:40:17 -08:00
2021-11-24 12:09:48 -08:00
let result = docker_prep ( container_name , build_config ) . and_then ( | _ | {
let cfg_parent = cfg . path ( ) . parent ( ) . unwrap ( ) ;
docker_build_bpf (
container_name ,
cargo_toml . as_path ( ) ,
cfg_parent ,
target_dir . as_path ( ) ,
binary_name ,
stdout ,
stderr ,
cargo_args ,
)
} ) ;
// Cleanup regardless of errors
docker_cleanup ( container_name , target_dir . as_path ( ) ) ? ;
// Done.
result
}
fn docker_prep ( container_name : & str , build_config : & BuildConfig ) -> Result < ( ) > {
2021-08-08 02:11:48 -07:00
// Set the solana version in the container, if given. Otherwise use the
// default.
2021-11-24 12:09:48 -08:00
match build_config . bootstrap {
BootstrapMode ::Debian = > {
// Install build requirements
docker_exec ( container_name , & [ " apt " , " update " ] ) ? ;
docker_exec (
container_name ,
& [ " apt " , " install " , " -y " , " curl " , " build-essential " ] ,
) ? ;
2021-08-08 02:11:48 -07:00
2021-11-24 12:09:48 -08:00
// Install Rust
docker_exec (
2021-08-08 02:11:48 -07:00
container_name ,
2021-11-24 12:09:48 -08:00
& [ " curl " , " https://sh.rustup.rs " , " -sfo " , " rustup.sh " ] ,
) ? ;
docker_exec ( container_name , & [ " sh " , " rustup.sh " , " -y " ] ) ? ;
docker_exec ( container_name , & [ " rm " , " -f " , " rustup.sh " ] ) ? ;
}
BootstrapMode ::None = > { }
}
if let Some ( solana_version ) = & build_config . solana_version {
println! ( " Using solana version: {} " , solana_version ) ;
// Install Solana CLI
docker_exec (
container_name ,
& [
2021-08-08 02:11:48 -07:00
" curl " ,
" -sSfL " ,
& format! ( " https://release.solana.com/v {0} /install " , solana_version , ) ,
" -o " ,
" solana_installer.sh " ,
2021-11-24 12:09:48 -08:00
] ,
) ? ;
docker_exec ( container_name , & [ " sh " , " solana_installer.sh " ] ) ? ;
docker_exec ( container_name , & [ " rm " , " -f " , " solana_installer.sh " ] ) ? ;
2021-08-08 02:11:48 -07:00
}
2021-11-24 12:09:48 -08:00
Ok ( ( ) )
}
2021-08-08 02:11:48 -07:00
2021-11-24 12:09:48 -08:00
#[ allow(clippy::too_many_arguments) ]
fn docker_build_bpf (
container_name : & str ,
cargo_toml : & Path ,
cfg_parent : & Path ,
target_dir : & Path ,
binary_name : String ,
stdout : Option < File > ,
stderr : Option < File > ,
cargo_args : Vec < String > ,
) -> Result < ( ) > {
let manifest_path =
pathdiff ::diff_paths ( cargo_toml . canonicalize ( ) ? , cfg_parent . canonicalize ( ) ? )
. ok_or_else ( | | anyhow! ( " Unable to diff paths " ) ) ? ;
2021-08-08 02:11:48 -07:00
println! (
" Building {} manifest: {:?} " ,
binary_name ,
2022-01-15 14:09:53 -08:00
manifest_path . display ( )
2021-08-08 02:11:48 -07:00
) ;
// Execute the build.
let exit = std ::process ::Command ::new ( " docker " )
. args ( & [
" exec " ,
2021-11-24 12:09:48 -08:00
" --env " ,
" PATH=/root/.local/share/solana/install/active_release/bin:/root/.cargo/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin " ,
2021-08-08 02:11:48 -07:00
container_name ,
" cargo " ,
" build-bpf " ,
" --manifest-path " ,
& manifest_path . display ( ) . to_string ( ) ,
] )
2021-11-17 18:38:26 -08:00
. args ( cargo_args )
2021-08-08 02:11:48 -07:00
. stdout ( match stdout {
None = > Stdio ::inherit ( ) ,
Some ( f ) = > f . into ( ) ,
} )
. stderr ( match stderr {
None = > Stdio ::inherit ( ) ,
Some ( f ) = > f . into ( ) ,
} )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " Docker build failed: {} " , e . to_string ( ) ) ) ? ;
if ! exit . status . success ( ) {
return Err ( anyhow! ( " Failed to build program " ) ) ;
}
// Copy the binary out of the docker image.
println! ( " Copying out the build artifacts " ) ;
2021-11-24 12:09:48 -08:00
let out_file = cfg_parent
2021-08-08 02:11:48 -07:00
. canonicalize ( ) ?
2021-08-10 21:36:56 -07:00
. join ( format! ( " target/verifiable/ {} .so " , binary_name ) )
2021-08-08 02:11:48 -07:00
. display ( )
. to_string ( ) ;
// This requires the target directory of any built program to be located at
// the root of the workspace.
2021-11-24 12:09:48 -08:00
let mut bin_path = target_dir . join ( " deploy " ) ;
bin_path . push ( format! ( " {} .so " , binary_name ) ) ;
2021-08-08 02:11:48 -07:00
let bin_artifact = format! (
2021-11-24 12:09:48 -08:00
" {}:{} " ,
container_name ,
bin_path . as_path ( ) . to_str ( ) . unwrap ( )
2021-08-08 02:11:48 -07:00
) ;
2021-03-09 05:40:09 -08:00
let exit = std ::process ::Command ::new ( " docker " )
2021-08-08 02:11:48 -07:00
. args ( & [ " cp " , & bin_artifact , & out_file ] )
2021-03-09 05:40:09 -08:00
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " {} " , e . to_string ( ) ) ) ? ;
if ! exit . status . success ( ) {
2021-11-24 12:09:48 -08:00
Err ( anyhow! (
2021-08-08 02:11:48 -07:00
" Failed to copy binary out of docker. Is the target directory set correctly? "
2021-11-24 12:09:48 -08:00
) )
} else {
Ok ( ( ) )
2021-03-09 05:40:09 -08:00
}
2021-11-24 12:09:48 -08:00
}
2021-03-09 05:40:09 -08:00
2021-11-24 12:09:48 -08:00
fn docker_cleanup ( container_name : & str , target_dir : & Path ) -> Result < ( ) > {
// Wipe the generated docker-target dir.
println! ( " Cleaning up the docker target directory " ) ;
docker_exec ( container_name , & [ " rm " , " -rf " , target_dir . to_str ( ) . unwrap ( ) ] ) ? ;
// Remove the docker image.
2022-01-07 08:00:57 -08:00
println! ( " Removing the docker container " ) ;
2021-11-24 12:09:48 -08:00
let exit = std ::process ::Command ::new ( " docker " )
. args ( & [ " rm " , " -f " , container_name ] )
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " {} " , e . to_string ( ) ) ) ? ;
if ! exit . status . success ( ) {
2022-01-07 08:00:57 -08:00
println! ( " Unable to remove the docker container " ) ;
2021-11-24 12:09:48 -08:00
std ::process ::exit ( exit . status . code ( ) . unwrap_or ( 1 ) ) ;
}
2021-03-09 05:40:09 -08:00
Ok ( ( ) )
}
2021-11-24 12:09:48 -08:00
fn docker_exec ( container_name : & str , args : & [ & str ] ) -> Result < ( ) > {
let exit = std ::process ::Command ::new ( " docker " )
. args ( [ & [ " exec " , container_name ] , args ] . concat ( ) )
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow! ( " Failed to run command \" {:?} \" : {:?} " , args , e ) ) ? ;
if ! exit . status . success ( ) {
Err ( anyhow! ( " Failed to run command: {:?} " , args ) )
} else {
Ok ( ( ) )
}
}
2021-10-04 23:16:13 -07:00
fn _build_cwd (
2021-11-17 15:04:14 -08:00
cfg : & WithPath < Config > ,
2021-10-04 23:16:13 -07:00
idl_out : Option < PathBuf > ,
idl_ts_out : Option < PathBuf > ,
2022-02-20 16:43:19 -08:00
skip_lint : bool ,
2021-10-04 23:16:13 -07:00
cargo_args : Vec < String > ,
) -> Result < ( ) > {
2021-01-02 22:40:17 -08:00
let exit = std ::process ::Command ::new ( " cargo " )
. arg ( " build-bpf " )
2021-09-27 08:45:28 -07:00
. args ( cargo_args )
2021-01-02 22:40:17 -08:00
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " {} " , e . to_string ( ) ) ) ? ;
if ! exit . status . success ( ) {
std ::process ::exit ( exit . status . code ( ) . unwrap_or ( 1 ) ) ;
}
2021-10-25 16:28:04 -07:00
// Always assume idl is located at src/lib.rs.
2022-04-21 13:37:41 -07:00
if let Some ( idl ) = extract_idl ( cfg , " src/lib.rs " , skip_lint , false ) ? {
2021-10-04 23:16:13 -07:00
// JSON out path.
2021-08-08 02:11:48 -07:00
let out = match idl_out {
None = > PathBuf ::from ( " . " ) . join ( & idl . name ) . with_extension ( " json " ) ,
Some ( o ) = > PathBuf ::from ( & o . join ( & idl . name ) . with_extension ( " json " ) ) ,
} ;
2021-10-04 23:16:13 -07:00
// TS out path.
let ts_out = match idl_ts_out {
None = > PathBuf ::from ( " . " ) . join ( & idl . name ) . with_extension ( " ts " ) ,
Some ( o ) = > PathBuf ::from ( & o . join ( & idl . name ) . with_extension ( " ts " ) ) ,
} ;
2021-01-02 22:40:17 -08:00
2021-10-04 23:16:13 -07:00
// Write out the JSON file.
2021-08-08 02:11:48 -07:00
write_idl ( & idl , OutFile ::File ( out ) ) ? ;
2021-10-04 23:16:13 -07:00
// Write out the TypeScript type.
2021-11-17 15:04:14 -08:00
fs ::write ( & ts_out , template ::idl_ts ( & idl ) ? ) ? ;
// Copy out the TypeScript type.
let cfg_parent = cfg . path ( ) . parent ( ) . expect ( " Invalid Anchor.toml " ) ;
if ! & cfg . workspace . types . is_empty ( ) {
fs ::copy (
& ts_out ,
cfg_parent
. join ( & cfg . workspace . types )
. join ( & idl . name )
. with_extension ( " ts " ) ,
) ? ;
}
2021-08-08 02:11:48 -07:00
}
2021-01-02 22:40:17 -08:00
2021-08-08 02:11:48 -07:00
Ok ( ( ) )
2021-01-02 22:40:17 -08:00
}
2021-08-08 02:11:48 -07:00
fn verify (
cfg_override : & ConfigOverride ,
program_id : Pubkey ,
program_name : Option < String > ,
solana_version : Option < String > ,
2021-11-24 12:09:48 -08:00
docker_image : Option < String > ,
bootstrap : BootstrapMode ,
2021-09-27 08:45:28 -07:00
cargo_args : Vec < String > ,
2021-08-08 02:11:48 -07:00
) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
// Change to the workspace member directory, if needed.
2021-08-08 02:11:48 -07:00
if let Some ( program_name ) = program_name . as_ref ( ) {
2021-08-10 21:36:56 -07:00
cd_member ( cfg_override , program_name ) ? ;
2021-08-08 02:11:48 -07:00
}
// Proceed with the command.
2021-08-10 21:36:56 -07:00
let cfg = Config ::discover ( cfg_override ) ? . expect ( " Not in workspace. " ) ;
let cargo = Manifest ::discover ( ) ? . ok_or_else ( | | anyhow! ( " Cargo.toml not found " ) ) ? ;
2021-03-09 05:40:09 -08:00
// Build the program we want to verify.
let cur_dir = std ::env ::current_dir ( ) ? ;
2021-08-08 02:11:48 -07:00
build (
cfg_override ,
2021-11-24 12:09:48 -08:00
None , // idl
None , // idl ts
true , // verifiable
2022-02-20 16:43:19 -08:00
true , // skip lint
2021-11-24 12:09:48 -08:00
None , // program name
solana_version . or_else ( | | cfg . solana_version . clone ( ) ) , // solana version
docker_image , // docker image
bootstrap , // bootstrap docker image
None , // stdout
None , // stderr
2021-09-27 08:45:28 -07:00
cargo_args ,
2022-04-21 13:37:41 -07:00
false ,
2021-08-08 02:11:48 -07:00
) ? ;
2021-03-09 05:40:09 -08:00
std ::env ::set_current_dir ( & cur_dir ) ? ;
// Verify binary.
2021-08-10 21:36:56 -07:00
let binary_name = cargo . lib_name ( ) ? ;
2021-08-08 02:11:48 -07:00
let bin_path = cfg
. path ( )
. parent ( )
. ok_or_else ( | | anyhow! ( " Unable to find workspace root " ) ) ?
2021-08-10 21:36:56 -07:00
. join ( " target/verifiable/ " )
2021-08-08 02:11:48 -07:00
. join ( format! ( " {} .so " , binary_name ) ) ;
2022-03-30 14:17:54 -07:00
let url = cluster_url ( & cfg , & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let bin_ver = verify_bin ( program_id , & bin_path , & url ) ? ;
2021-08-08 02:11:48 -07:00
if ! bin_ver . is_verified {
println! ( " Error: Binaries don't match " ) ;
std ::process ::exit ( 1 ) ;
}
2021-03-10 12:16:54 -08:00
// Verify IDL (only if it's not a buffer account).
2022-04-21 13:37:41 -07:00
if let Some ( local_idl ) = extract_idl ( & cfg , " src/lib.rs " , true , false ) ? {
2021-08-08 02:11:48 -07:00
if bin_ver . state ! = BinVerificationState ::Buffer {
let deployed_idl = fetch_idl ( cfg_override , program_id ) ? ;
if local_idl ! = deployed_idl {
println! ( " Error: IDLs don't match " ) ;
std ::process ::exit ( 1 ) ;
}
2021-03-10 12:16:54 -08:00
}
}
2021-03-09 05:40:09 -08:00
println! ( " {} is verified. " , program_id ) ;
Ok ( ( ) )
}
2021-08-10 21:36:56 -07:00
fn cd_member ( cfg_override : & ConfigOverride , program_name : & str ) -> Result < ( ) > {
// Change directories to the given `program_name`, if given.
let cfg = Config ::discover ( cfg_override ) ? . expect ( " Not in workspace. " ) ;
for program in cfg . read_all_programs ( ) ? {
let cargo_toml = program . path . join ( " Cargo.toml " ) ;
if ! cargo_toml . exists ( ) {
return Err ( anyhow! (
" Did not find Cargo.toml at the path: {} " ,
program . path . display ( )
) ) ;
}
let p_lib_name = Manifest ::from_path ( & cargo_toml ) ? . lib_name ( ) ? ;
if program_name = = p_lib_name {
std ::env ::set_current_dir ( & program . path ) ? ;
return Ok ( ( ) ) ;
}
}
return Err ( anyhow! ( " {} is not part of the workspace " , program_name , ) ) ;
}
2021-08-08 02:11:48 -07:00
pub fn verify_bin ( program_id : Pubkey , bin_path : & Path , cluster : & str ) -> Result < BinVerification > {
2021-03-09 05:40:09 -08:00
let client = RpcClient ::new ( cluster . to_string ( ) ) ;
// Get the deployed build artifacts.
2021-08-08 02:11:48 -07:00
let ( deployed_bin , state ) = {
2021-03-09 05:40:09 -08:00
let account = client
. get_account_with_commitment ( & program_id , CommitmentConfig ::default ( ) ) ?
. value
. map_or ( Err ( anyhow! ( " Account not found " ) ) , Ok ) ? ;
2021-11-29 18:04:09 -08:00
if account . owner = = bpf_loader ::id ( ) | | account . owner = = bpf_loader_deprecated ::id ( ) {
let bin = account . data . to_vec ( ) ;
let state = BinVerificationState ::ProgramData {
slot : 0 , // Need to look through the transaction history.
upgrade_authority_address : None ,
} ;
( bin , state )
} else if account . owner = = bpf_loader_upgradeable ::id ( ) {
match account . state ( ) ? {
UpgradeableLoaderState ::Program {
programdata_address ,
} = > {
let account = client
. get_account_with_commitment (
& programdata_address ,
CommitmentConfig ::default ( ) ,
) ?
. value
. map_or ( Err ( anyhow! ( " Account not found " ) ) , Ok ) ? ;
let bin = account . data
[ UpgradeableLoaderState ::programdata_data_offset ( ) . unwrap_or ( 0 ) .. ]
. to_vec ( ) ;
if let UpgradeableLoaderState ::ProgramData {
2021-08-08 02:11:48 -07:00
slot ,
upgrade_authority_address ,
2021-11-29 18:04:09 -08:00
} = account . state ( ) ?
{
let state = BinVerificationState ::ProgramData {
slot ,
upgrade_authority_address ,
} ;
( bin , state )
} else {
return Err ( anyhow! ( " Expected program data " ) ) ;
}
}
UpgradeableLoaderState ::Buffer { .. } = > {
let offset = UpgradeableLoaderState ::buffer_data_offset ( ) . unwrap_or ( 0 ) ;
(
account . data [ offset .. ] . to_vec ( ) ,
BinVerificationState ::Buffer ,
)
}
_ = > {
return Err ( anyhow! (
" Invalid program id, not a buffer or program account "
) )
2021-08-08 02:11:48 -07:00
}
}
2021-11-29 18:04:09 -08:00
} else {
return Err ( anyhow! (
" Invalid program id, not owned by any loader program "
) ) ;
2021-03-09 05:40:09 -08:00
}
} ;
let mut local_bin = {
let mut f = File ::open ( bin_path ) ? ;
let mut contents = vec! [ ] ;
f . read_to_end ( & mut contents ) ? ;
contents
} ;
// The deployed program probably has zero bytes appended. The default is
// 2x the binary size in case of an upgrade.
if local_bin . len ( ) < deployed_bin . len ( ) {
local_bin . append ( & mut vec! [ 0 ; deployed_bin . len ( ) - local_bin . len ( ) ] ) ;
}
// Finally, check the bytes.
2021-08-08 02:11:48 -07:00
let is_verified = local_bin = = deployed_bin ;
Ok ( BinVerification { state , is_verified } )
}
2021-03-09 05:40:09 -08:00
2021-08-08 02:11:48 -07:00
#[ derive(PartialEq) ]
pub struct BinVerification {
pub state : BinVerificationState ,
pub is_verified : bool ,
}
#[ derive(PartialEq) ]
pub enum BinVerificationState {
Buffer ,
ProgramData {
slot : u64 ,
upgrade_authority_address : Option < Pubkey > ,
} ,
2021-03-09 05:40:09 -08:00
}
2021-01-29 06:19:00 -08:00
// Fetches an IDL for the given program_id.
2021-05-24 16:04:17 -07:00
fn fetch_idl ( cfg_override : & ConfigOverride , idl_addr : Pubkey ) -> Result < Idl > {
2022-03-12 14:25:27 -08:00
let url = match Config ::discover ( cfg_override ) ? {
2022-03-30 14:17:54 -07:00
Some ( cfg ) = > cluster_url ( & cfg , & cfg . test_validator ) ,
2022-03-12 14:25:27 -08:00
None = > {
// If the command is not run inside a workspace,
// cluster_url will be used from default solana config
// provider.cluster option can be used to override this
if let Some ( cluster ) = cfg_override . cluster . clone ( ) {
cluster . url ( ) . to_string ( )
} else {
config ::get_solana_cfg_url ( ) ?
}
}
} ;
2021-10-05 13:12:55 -07:00
let client = RpcClient ::new ( url ) ;
2021-01-29 06:19:00 -08:00
2021-03-12 12:43:26 -08:00
let mut account = client
2021-02-26 01:31:48 -08:00
. get_account_with_commitment ( & idl_addr , CommitmentConfig ::processed ( ) ) ?
2021-01-29 06:19:00 -08:00
. value
. map_or ( Err ( anyhow! ( " Account not found " ) ) , Ok ) ? ;
2021-03-12 12:43:26 -08:00
if account . executable {
let idl_addr = IdlAccount ::address ( & idl_addr ) ;
account = client
. get_account_with_commitment ( & idl_addr , CommitmentConfig ::processed ( ) ) ?
. value
. map_or ( Err ( anyhow! ( " Account not found " ) ) , Ok ) ? ;
}
2021-01-29 06:19:00 -08:00
// 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 )
2021-01-02 22:40:17 -08:00
}
2022-04-21 13:37:41 -07:00
fn extract_idl (
cfg : & WithPath < Config > ,
file : & str ,
skip_lint : bool ,
no_docs : bool ,
) -> Result < Option < Idl > > {
2021-01-02 22:40:17 -08:00
let file = shellexpand ::tilde ( file ) ;
2021-12-18 15:25:04 -08:00
let manifest_from_path = std ::env ::current_dir ( ) ? . join ( PathBuf ::from ( & * file ) . parent ( ) . unwrap ( ) ) ;
2021-11-27 09:06:01 -08:00
let cargo = Manifest ::discover_from_path ( manifest_from_path ) ?
. ok_or_else ( | | anyhow! ( " Cargo.toml not found " ) ) ? ;
2022-04-21 13:37:41 -07:00
anchor_syn ::idl ::file ::parse (
& * file ,
cargo . version ( ) ,
cfg . features . seeds ,
no_docs ,
2022-07-04 09:12:50 -07:00
! ( cfg . features . skip_lint | | skip_lint ) ,
2022-04-21 13:37:41 -07:00
)
2021-01-02 22:40:17 -08:00
}
2021-05-24 16:04:17 -07:00
fn idl ( cfg_override : & ConfigOverride , subcmd : IdlCommand ) -> Result < ( ) > {
2021-01-29 06:19:00 -08:00
match subcmd {
IdlCommand ::Init {
program_id ,
filepath ,
2021-05-24 16:04:17 -07:00
} = > idl_init ( cfg_override , program_id , filepath ) ,
2021-03-12 12:43:26 -08:00
IdlCommand ::WriteBuffer {
program_id ,
filepath ,
2021-05-24 16:04:17 -07:00
} = > idl_write_buffer ( cfg_override , program_id , filepath ) . map ( | _ | ( ) ) ,
IdlCommand ::SetBuffer { program_id , buffer } = > {
idl_set_buffer ( cfg_override , program_id , buffer )
}
2021-01-31 03:07:52 -08:00
IdlCommand ::Upgrade {
2021-01-31 00:59:01 -08:00
program_id ,
filepath ,
2021-05-24 16:04:17 -07:00
} = > idl_upgrade ( cfg_override , program_id , filepath ) ,
2021-01-31 03:07:52 -08:00
IdlCommand ::SetAuthority {
program_id ,
2021-03-12 12:43:26 -08:00
address ,
2021-01-31 03:07:52 -08:00
new_authority ,
2021-05-24 16:04:17 -07:00
} = > idl_set_authority ( cfg_override , program_id , address , new_authority ) ,
IdlCommand ::EraseAuthority { program_id } = > idl_erase_authority ( cfg_override , program_id ) ,
IdlCommand ::Authority { program_id } = > idl_authority ( cfg_override , program_id ) ,
2022-04-21 13:37:41 -07:00
IdlCommand ::Parse {
file ,
out ,
out_ts ,
no_docs ,
} = > idl_parse ( cfg_override , file , out , out_ts , no_docs ) ,
2021-05-24 16:04:17 -07:00
IdlCommand ::Fetch { address , out } = > idl_fetch ( cfg_override , address , out ) ,
2021-01-29 06:19:00 -08:00
}
}
2021-05-24 16:04:17 -07:00
fn idl_init ( cfg_override : & ConfigOverride , program_id : Pubkey , idl_filepath : String ) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2021-05-23 10:00:24 -07:00
let keypair = cfg . provider . wallet . to_string ( ) ;
2021-01-31 00:59:01 -08:00
2021-10-04 23:16:13 -07:00
let bytes = fs ::read ( idl_filepath ) ? ;
2021-01-31 00:59:01 -08:00
let idl : Idl = serde_json ::from_reader ( & * bytes ) ? ;
2021-06-27 17:09:46 -07:00
let idl_address = create_idl_account ( cfg , & keypair , & program_id , & idl ) ? ;
2021-01-31 00:59:01 -08:00
println! ( " Idl account created: {:?} " , idl_address ) ;
Ok ( ( ) )
} )
}
2021-05-24 16:04:17 -07:00
fn idl_write_buffer (
cfg_override : & ConfigOverride ,
program_id : Pubkey ,
idl_filepath : String ,
) -> Result < Pubkey > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2021-05-23 10:00:24 -07:00
let keypair = cfg . provider . wallet . to_string ( ) ;
2021-03-12 12:43:26 -08:00
2021-10-04 23:16:13 -07:00
let bytes = fs ::read ( idl_filepath ) ? ;
2021-01-31 00:59:01 -08:00
let idl : Idl = serde_json ::from_reader ( & * bytes ) ? ;
2021-06-27 17:09:46 -07:00
let idl_buffer = create_idl_buffer ( cfg , & keypair , & program_id , & idl ) ? ;
idl_write ( cfg , & program_id , & idl , idl_buffer ) ? ;
2021-03-12 12:43:26 -08:00
println! ( " Idl buffer created: {:?} " , idl_buffer ) ;
Ok ( idl_buffer )
} )
}
2021-05-24 16:04:17 -07:00
fn idl_set_buffer ( cfg_override : & ConfigOverride , program_id : Pubkey , buffer : Pubkey ) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2021-05-23 10:00:24 -07:00
let keypair = solana_sdk ::signature ::read_keypair_file ( & cfg . provider . wallet . to_string ( ) )
2021-03-12 12:43:26 -08:00
. map_err ( | _ | anyhow! ( " Unable to read keypair file " ) ) ? ;
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let client = RpcClient ::new ( url ) ;
2021-03-12 12:43:26 -08:00
// Instruction to set the buffer onto the IdlAccount.
let set_buffer_ix = {
let accounts = vec! [
AccountMeta ::new ( buffer , false ) ,
AccountMeta ::new ( IdlAccount ::address ( & program_id ) , false ) ,
AccountMeta ::new ( keypair . pubkey ( ) , true ) ,
] ;
let mut data = anchor_lang ::idl ::IDL_IX_TAG . to_le_bytes ( ) . to_vec ( ) ;
data . append ( & mut IdlInstruction ::SetBuffer . try_to_vec ( ) ? ) ;
Instruction {
program_id ,
accounts ,
data ,
}
} ;
// Build the transaction.
2022-03-26 17:28:55 -07:00
let latest_hash = client . get_latest_blockhash ( ) ? ;
2021-03-12 12:43:26 -08:00
let tx = Transaction ::new_signed_with_payer (
& [ set_buffer_ix ] ,
Some ( & keypair . pubkey ( ) ) ,
& [ & keypair ] ,
2022-03-26 17:28:55 -07:00
latest_hash ,
2021-03-12 12:43:26 -08:00
) ;
// Send the transaction.
client . send_and_confirm_transaction_with_spinner_and_config (
& tx ,
CommitmentConfig ::confirmed ( ) ,
RpcSendTransactionConfig {
skip_preflight : true ,
.. RpcSendTransactionConfig ::default ( )
} ,
) ? ;
2021-01-31 00:59:01 -08:00
Ok ( ( ) )
} )
}
2021-05-24 16:04:17 -07:00
fn idl_upgrade (
cfg_override : & ConfigOverride ,
program_id : Pubkey ,
idl_filepath : String ,
) -> Result < ( ) > {
let buffer = idl_write_buffer ( cfg_override , program_id , idl_filepath ) ? ;
idl_set_buffer ( cfg_override , program_id , buffer )
2021-03-12 12:43:26 -08:00
}
2021-05-24 16:04:17 -07:00
fn idl_authority ( cfg_override : & ConfigOverride , program_id : Pubkey ) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let client = RpcClient ::new ( url ) ;
2021-03-12 12:43:26 -08:00
let idl_address = {
let account = client
. get_account_with_commitment ( & program_id , CommitmentConfig ::processed ( ) ) ?
. value
. map_or ( Err ( anyhow! ( " Account not found " ) ) , Ok ) ? ;
if account . executable {
IdlAccount ::address ( & program_id )
} else {
program_id
}
} ;
2021-01-31 03:07:52 -08:00
let account = client . get_account ( & idl_address ) ? ;
let mut data : & [ u8 ] = & account . data ;
let idl_account : IdlAccount = AccountDeserialize ::try_deserialize ( & mut data ) ? ;
println! ( " {:?} " , idl_account . authority ) ;
Ok ( ( ) )
} )
}
2021-03-12 12:43:26 -08:00
fn idl_set_authority (
2021-05-24 16:04:17 -07:00
cfg_override : & ConfigOverride ,
2021-03-12 12:43:26 -08:00
program_id : Pubkey ,
address : Option < Pubkey > ,
new_authority : Pubkey ,
) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2021-01-31 03:07:52 -08:00
// Misc.
2021-03-12 12:43:26 -08:00
let idl_address = match address {
None = > IdlAccount ::address ( & program_id ) ,
Some ( addr ) = > addr ,
} ;
2021-05-23 10:00:24 -07:00
let keypair = solana_sdk ::signature ::read_keypair_file ( & cfg . provider . wallet . to_string ( ) )
2021-01-31 03:07:52 -08:00
. map_err ( | _ | anyhow! ( " Unable to read keypair file " ) ) ? ;
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let client = RpcClient ::new ( url ) ;
2021-01-31 03:07:52 -08:00
// Instruction data.
let data =
serialize_idl_ix ( anchor_lang ::idl ::IdlInstruction ::SetAuthority { new_authority } ) ? ;
// Instruction accounts.
let accounts = vec! [
AccountMeta ::new ( idl_address , false ) ,
AccountMeta ::new_readonly ( keypair . pubkey ( ) , true ) ,
] ;
// Instruction.
let ix = Instruction {
program_id ,
accounts ,
data ,
} ;
// Send transaction.
2022-03-26 17:28:55 -07:00
let latest_hash = client . get_latest_blockhash ( ) ? ;
2021-01-31 03:07:52 -08:00
let tx = Transaction ::new_signed_with_payer (
& [ ix ] ,
Some ( & keypair . pubkey ( ) ) ,
& [ & keypair ] ,
2022-03-26 17:28:55 -07:00
latest_hash ,
2021-01-31 03:07:52 -08:00
) ;
client . send_and_confirm_transaction_with_spinner_and_config (
& tx ,
2021-02-26 01:31:48 -08:00
CommitmentConfig ::confirmed ( ) ,
2021-01-31 03:07:52 -08:00
RpcSendTransactionConfig {
skip_preflight : true ,
.. RpcSendTransactionConfig ::default ( )
} ,
) ? ;
println! ( " Authority update complete. " ) ;
Ok ( ( ) )
} )
}
2021-05-24 16:04:17 -07:00
fn idl_erase_authority ( cfg_override : & ConfigOverride , program_id : Pubkey ) -> Result < ( ) > {
2021-01-31 03:07:52 -08:00
println! ( " Are you sure you want to erase the IDL authority: [y/n] " ) ;
let stdin = std ::io ::stdin ( ) ;
let mut stdin_lines = stdin . lock ( ) . lines ( ) ;
let input = stdin_lines . next ( ) . unwrap ( ) . unwrap ( ) ;
if input ! = " y " {
println! ( " Not erasing. " ) ;
return Ok ( ( ) ) ;
}
2022-04-04 15:37:24 -07:00
idl_set_authority ( cfg_override , program_id , None , ERASED_AUTHORITY ) ? ;
2021-01-29 06:19:00 -08:00
2021-01-31 00:59:01 -08:00
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.
2021-03-12 12:43:26 -08:00
fn idl_write ( cfg : & Config , program_id : & Pubkey , idl : & Idl , idl_address : Pubkey ) -> Result < ( ) > {
2021-03-09 05:40:09 -08:00
// Remove the metadata before deploy.
let mut idl = idl . clone ( ) ;
idl . metadata = None ;
2021-01-31 00:59:01 -08:00
// Misc.
2021-05-23 10:00:24 -07:00
let keypair = solana_sdk ::signature ::read_keypair_file ( & cfg . provider . wallet . to_string ( ) )
2021-01-31 00:59:01 -08:00
. map_err ( | _ | anyhow! ( " Unable to read keypair file " ) ) ? ;
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let client = RpcClient ::new ( url ) ;
2021-01-29 06:19:00 -08:00
2021-01-31 00:59:01 -08:00
// Serialize and compress the idl.
let idl_data = {
2021-03-09 05:40:09 -08:00
let json_bytes = serde_json ::to_vec ( & idl ) ? ;
2021-01-31 00:59:01 -08:00
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.
2022-03-26 17:28:55 -07:00
let latest_hash = client . get_latest_blockhash ( ) ? ;
2021-01-31 00:59:01 -08:00
let tx = Transaction ::new_signed_with_payer (
& [ ix ] ,
Some ( & keypair . pubkey ( ) ) ,
& [ & keypair ] ,
2022-03-26 17:28:55 -07:00
latest_hash ,
2021-01-31 00:59:01 -08:00
) ;
client . send_and_confirm_transaction_with_spinner_and_config (
& tx ,
2021-02-26 01:31:48 -08:00
CommitmentConfig ::confirmed ( ) ,
2021-01-31 00:59:01 -08:00
RpcSendTransactionConfig {
skip_preflight : true ,
.. RpcSendTransactionConfig ::default ( )
} ,
) ? ;
offset + = MAX_WRITE_SIZE ;
}
2021-01-29 06:19:00 -08:00
Ok ( ( ) )
}
2022-01-24 11:44:24 -08:00
fn idl_parse (
cfg_override : & ConfigOverride ,
file : String ,
out : Option < String > ,
out_ts : Option < String > ,
2022-04-21 13:37:41 -07:00
no_docs : bool ,
2022-01-24 11:44:24 -08:00
) -> Result < ( ) > {
let cfg = Config ::discover ( cfg_override ) ? . expect ( " Not in workspace. " ) ;
2022-04-21 13:37:41 -07:00
let idl = extract_idl ( & cfg , & file , true , no_docs ) ? . ok_or_else ( | | anyhow! ( " IDL not parsed " ) ) ? ;
2021-01-29 06:19:00 -08:00
let out = match out {
None = > OutFile ::Stdout ,
Some ( out ) = > OutFile ::File ( PathBuf ::from ( out ) ) ,
} ;
2021-11-02 17:00:13 -07:00
write_idl ( & idl , out ) ? ;
// Write out the TypeScript IDL.
if let Some ( out ) = out_ts {
fs ::write ( out , template ::idl_ts ( & idl ) ? ) ? ;
}
Ok ( ( ) )
2021-01-29 06:19:00 -08:00
}
2021-05-24 16:04:17 -07:00
fn idl_fetch ( cfg_override : & ConfigOverride , address : Pubkey , out : Option < String > ) -> Result < ( ) > {
let idl = fetch_idl ( cfg_override , address ) ? ;
2021-01-29 06:19:00 -08:00
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 < ( ) > {
2021-01-02 22:40:17 -08:00
let idl_json = serde_json ::to_string_pretty ( idl ) ? ;
2021-01-29 06:19:00 -08:00
match out {
OutFile ::Stdout = > println! ( " {} " , idl_json ) ,
2021-10-04 23:16:13 -07:00
OutFile ::File ( out ) = > fs ::write ( out , idl_json ) ? ,
2021-01-02 22:40:17 -08:00
} ;
2021-10-04 23:16:13 -07:00
2021-01-02 22:40:17 -08:00
Ok ( ( ) )
}
2021-01-29 06:19:00 -08:00
enum OutFile {
Stdout ,
File ( PathBuf ) ,
}
2021-01-02 22:40:17 -08:00
// Builds, deploys, and tests all workspace programs in a single command.
2022-02-20 16:43:19 -08:00
#[ allow(clippy::too_many_arguments) ]
2021-05-08 17:00:35 -07:00
fn test (
2021-05-24 16:04:17 -07:00
cfg_override : & ConfigOverride ,
2021-05-08 17:00:35 -07:00
skip_deploy : bool ,
skip_local_validator : bool ,
2021-05-21 22:20:59 -07:00
skip_build : bool ,
2022-02-20 16:43:19 -08:00
skip_lint : bool ,
2021-09-22 12:24:27 -07:00
detach : bool ,
2021-07-29 14:38:24 -07:00
extra_args : Vec < String > ,
2021-09-27 08:45:28 -07:00
cargo_args : Vec < String > ,
2021-05-08 17:00:35 -07:00
) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2021-05-25 14:10:50 -07:00
// Build if needed.
if ! skip_build {
2021-09-27 08:45:28 -07:00
build (
cfg_override ,
None ,
2021-11-09 22:40:28 -08:00
None ,
2021-09-27 08:45:28 -07:00
false ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2021-09-27 08:45:28 -07:00
None ,
None ,
None ,
2021-11-24 12:09:48 -08:00
BootstrapMode ::None ,
None ,
2021-09-27 08:45:28 -07:00
None ,
cargo_args ,
2022-04-21 13:37:41 -07:00
false ,
2021-09-27 08:45:28 -07:00
) ? ;
2021-05-25 14:10:50 -07:00
}
2022-04-12 12:27:03 -07:00
let root = cfg . path ( ) . parent ( ) . unwrap ( ) . to_owned ( ) ;
cfg . add_test_config ( root ) ? ;
2021-05-25 14:10:50 -07:00
// Run the deploy against the cluster in two cases:
//
// 1. The cluster is not localnet.
// 2. The cluster is localnet, but we're not booting a local validator.
//
// In either case, skip the deploy if the user specifies.
let is_localnet = cfg . provider . cluster = = Cluster ::Localnet ;
2021-06-27 17:09:46 -07:00
if ( ! is_localnet | | skip_local_validator ) & & ! skip_deploy {
2022-04-20 13:37:14 -07:00
deploy ( cfg_override , None , None ) ? ;
2021-05-25 14:10:50 -07:00
}
2022-03-30 14:17:54 -07:00
let mut is_first_suite = true ;
if cfg . scripts . get ( " test " ) . is_some ( ) {
is_first_suite = false ;
println! ( " \n Found a 'test' script in the Anchor.toml. Running it as a test suite! " ) ;
run_test_suite (
cfg . path ( ) ,
cfg ,
is_localnet ,
skip_local_validator ,
skip_deploy ,
detach ,
& cfg . test_validator ,
& cfg . scripts ,
& extra_args ,
) ? ;
2021-05-25 14:10:50 -07:00
}
2022-03-30 14:17:54 -07:00
if let Some ( test_config ) = & cfg . test_config {
for test_suite in test_config . iter ( ) {
if ! is_first_suite {
std ::thread ::sleep ( std ::time ::Duration ::from_millis (
test_suite
. 1
. test
. as_ref ( )
. map ( | val | val . shutdown_wait )
. unwrap_or ( SHUTDOWN_WAIT ) as u64 ,
) ) ;
} else {
is_first_suite = false ;
}
2021-03-27 09:52:09 -07:00
2022-03-30 14:17:54 -07:00
run_test_suite (
test_suite . 0 ,
cfg ,
is_localnet ,
skip_local_validator ,
skip_deploy ,
detach ,
& test_suite . 1. test ,
& test_suite . 1. scripts ,
& extra_args ,
) ? ;
}
}
Ok ( ( ) )
} )
}
2021-10-05 13:12:55 -07:00
2022-03-30 14:17:54 -07:00
#[ allow(clippy::too_many_arguments) ]
fn run_test_suite (
test_suite_path : impl AsRef < Path > ,
cfg : & WithPath < Config > ,
is_localnet : bool ,
skip_local_validator : bool ,
skip_deploy : bool ,
detach : bool ,
test_validator : & Option < TestValidator > ,
scripts : & ScriptsConfig ,
extra_args : & [ String ] ,
) -> Result < ( ) > {
println! ( " \n Running test suite: {:#?} \n " , test_suite_path . as_ref ( ) ) ;
// Start local test validator, if needed.
let mut validator_handle = None ;
if is_localnet & & ( ! skip_local_validator ) {
let flags = match skip_deploy {
true = > None ,
false = > Some ( validator_flags ( cfg , test_validator ) ? ) ,
} ;
validator_handle = Some ( start_test_validator ( cfg , test_validator , flags , true ) ? ) ;
}
2021-10-23 13:50:47 -07:00
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , test_validator ) ;
2021-02-09 09:04:18 -08:00
2022-03-30 14:17:54 -07:00
let node_options = format! (
" {} {} " ,
match std ::env ::var_os ( " NODE_OPTIONS " ) {
Some ( value ) = > value
. into_string ( )
. map_err ( std ::env ::VarError ::NotUnicode ) ? ,
None = > " " . to_owned ( ) ,
} ,
get_node_dns_option ( ) ? ,
) ;
2021-07-29 14:38:24 -07:00
2022-03-30 14:17:54 -07:00
// Setup log reader.
let log_streams = stream_logs ( cfg , & url ) ;
// Run the tests.
let test_result : Result < _ > = {
let cmd = scripts
. get ( " test " )
. expect ( " Not able to find script for `test` " )
. clone ( ) ;
let mut args : Vec < & str > = cmd
. split ( ' ' )
. chain ( extra_args . iter ( ) . map ( | arg | arg . as_str ( ) ) )
. collect ( ) ;
let program = args . remove ( 0 ) ;
std ::process ::Command ::new ( program )
. args ( args )
. env ( " ANCHOR_PROVIDER_URL " , url )
. env ( " ANCHOR_WALLET " , cfg . provider . wallet . to_string ( ) )
. env ( " NODE_OPTIONS " , node_options )
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( anyhow ::Error ::from )
. context ( cmd )
} ;
2021-02-19 07:24:22 -08:00
2022-03-30 14:17:54 -07:00
// Keep validator running if needed.
if test_result . is_ok ( ) & & detach {
println! ( " Local validator still running. Press Ctrl + C quit. " ) ;
std ::io ::stdin ( ) . lock ( ) . lines ( ) . next ( ) . unwrap ( ) . unwrap ( ) ;
}
2021-09-22 12:24:27 -07:00
2022-03-30 14:17:54 -07:00
// Check all errors and shut down.
if let Some ( mut child ) = validator_handle {
if let Err ( err ) = child . kill ( ) {
println! ( " Failed to kill subprocess {} : {} " , child . id ( ) , err ) ;
2021-01-31 00:59:01 -08:00
}
2022-03-30 14:17:54 -07:00
}
for mut child in log_streams ? {
if let Err ( err ) = child . kill ( ) {
println! ( " Failed to kill subprocess {} : {} " , child . id ( ) , err ) ;
2021-01-27 19:31:15 -08:00
}
2022-03-30 14:17:54 -07:00
}
2021-02-09 09:04:18 -08:00
2022-03-30 14:17:54 -07:00
// Must exist *after* shutting down the validator and log streams.
match test_result {
Ok ( exit ) = > {
if ! exit . status . success ( ) {
std ::process ::exit ( exit . status . code ( ) . unwrap ( ) ) ;
2021-09-23 17:06:38 -07:00
}
}
2022-03-30 14:17:54 -07:00
Err ( err ) = > {
println! ( " Failed to run test: {:#} " , err )
}
}
2021-09-23 17:06:38 -07:00
2022-03-30 14:17:54 -07:00
Ok ( ( ) )
2021-01-02 22:40:17 -08:00
}
2021-10-05 13:12:55 -07:00
// Returns the solana-test-validator flags. This will embed the workspace
// programs in the genesis block so we don't have to deploy every time. It also
// allows control of other solana-test-validator features.
2022-03-30 14:17:54 -07:00
fn validator_flags (
cfg : & WithPath < Config > ,
test_validator : & Option < TestValidator > ,
) -> Result < Vec < String > > {
2021-08-08 02:11:48 -07:00
let programs = cfg . programs . get ( & Cluster ::Localnet ) ;
2021-07-28 14:04:14 -07:00
2021-02-09 07:31:52 -08:00
let mut flags = Vec ::new ( ) ;
2021-07-25 17:25:40 -07:00
for mut program in cfg . read_all_programs ( ) ? {
2021-02-09 07:31:52 -08:00
let binary_path = program . binary_path ( ) . display ( ) . to_string ( ) ;
2021-09-07 13:06:15 -07:00
// Use the [programs.cluster] override and fallback to the keypair
// files if no override is given.
2021-08-08 02:11:48 -07:00
let address = programs
. and_then ( | m | m . get ( & program . lib_name ) )
2021-09-07 13:06:15 -07:00
. map ( | deployment | Ok ( deployment . address . to_string ( ) ) )
. unwrap_or_else ( | | program . pubkey ( ) . map ( | p | p . to_string ( ) ) ) ? ;
2021-02-09 07:31:52 -08:00
flags . push ( " --bpf-program " . to_string ( ) ) ;
flags . push ( address . clone ( ) ) ;
flags . push ( binary_path ) ;
2021-08-08 02:11:48 -07:00
if let Some ( mut idl ) = program . idl . as_mut ( ) {
// Add program address to the IDL.
idl . metadata = Some ( serde_json ::to_value ( IdlTestMetadata { address } ) ? ) ;
2021-02-09 07:31:52 -08:00
2021-08-08 02:11:48 -07:00
// Persist it.
let idl_out = PathBuf ::from ( " target/idl " )
. join ( & idl . name )
. with_extension ( " json " ) ;
write_idl ( idl , OutFile ::File ( idl_out ) ) ? ;
}
2021-02-09 07:31:52 -08:00
}
2021-10-05 13:12:55 -07:00
2022-03-30 14:17:54 -07:00
if let Some ( test ) = test_validator . as_ref ( ) {
2021-10-05 13:12:55 -07:00
if let Some ( genesis ) = & test . genesis {
for entry in genesis {
2021-10-07 13:11:26 -07:00
let program_path = Path ::new ( & entry . program ) ;
if ! program_path . exists ( ) {
return Err ( anyhow! (
" Program in genesis configuration does not exist at path: {} " ,
program_path . display ( )
) ) ;
}
2021-10-05 13:12:55 -07:00
flags . push ( " --bpf-program " . to_string ( ) ) ;
flags . push ( entry . address . clone ( ) ) ;
flags . push ( entry . program . clone ( ) ) ;
}
}
if let Some ( validator ) = & test . validator {
2022-03-12 14:36:35 -08:00
let entries = serde_json ::to_value ( validator ) ? ;
for ( key , value ) in entries . as_object ( ) . unwrap ( ) {
2021-10-05 13:12:55 -07:00
if key = = " ledger " {
2022-01-31 16:52:08 -08:00
// Ledger flag is a special case as it is passed separately to the rest of
// these validator flags.
2021-10-05 13:12:55 -07:00
continue ;
} ;
2022-01-31 16:52:08 -08:00
if key = = " account " {
for entry in value . as_array ( ) . unwrap ( ) {
// Push the account flag for each array entry
flags . push ( " --account " . to_string ( ) ) ;
flags . push ( entry [ " address " ] . as_str ( ) . unwrap ( ) . to_string ( ) ) ;
flags . push ( entry [ " filename " ] . as_str ( ) . unwrap ( ) . to_string ( ) ) ;
}
} else if key = = " clone " {
2022-03-12 14:36:35 -08:00
// Client for fetching accounts data
let client = if let Some ( url ) = entries [ " url " ] . as_str ( ) {
RpcClient ::new ( url . to_string ( ) )
} else {
return Err ( anyhow! (
" Validator url for Solana's JSON RPC should be provided in order to clone accounts from it "
) ) ;
} ;
let mut pubkeys = value
. as_array ( )
. unwrap ( )
. iter ( )
. map ( | entry | {
let address = entry [ " address " ] . as_str ( ) . unwrap ( ) ;
Pubkey ::from_str ( address )
. map_err ( | _ | anyhow! ( " Invalid pubkey {} " , address ) )
} )
. collect ::< Result < HashSet < Pubkey > > > ( ) ? ;
let accounts_keys = pubkeys . iter ( ) . cloned ( ) . collect ::< Vec < _ > > ( ) ;
let accounts = client
. get_multiple_accounts_with_commitment (
& accounts_keys ,
CommitmentConfig ::default ( ) ,
) ?
. value ;
// Check if there are program accounts
for ( account , acc_key ) in accounts . iter ( ) . zip ( accounts_keys ) {
if let Some ( account ) = account {
if account . owner = = bpf_loader_upgradeable ::id ( ) {
let upgradable : UpgradeableLoaderState = account
. deserialize_data ( )
. map_err ( | _ | anyhow! ( " Invalid program account {} " , acc_key ) ) ? ;
if let UpgradeableLoaderState ::Program {
programdata_address ,
} = upgradable
{
pubkeys . insert ( programdata_address ) ;
}
}
} else {
return Err ( anyhow! ( " Account {} not found " , acc_key ) ) ;
}
}
for pubkey in & pubkeys {
2022-01-31 16:52:08 -08:00
// Push the clone flag for each array entry
flags . push ( " --clone " . to_string ( ) ) ;
2022-03-12 14:36:35 -08:00
flags . push ( pubkey . to_string ( ) ) ;
2022-01-31 16:52:08 -08:00
}
2021-10-05 13:12:55 -07:00
} else {
2022-01-31 16:52:08 -08:00
// Remaining validator flags are non-array types
flags . push ( format! ( " -- {} " , key . replace ( '_' , " - " ) ) ) ;
if let serde_json ::Value ::String ( v ) = value {
flags . push ( v . to_string ( ) ) ;
} else {
flags . push ( value . to_string ( ) ) ;
}
2021-10-05 13:12:55 -07:00
}
}
2021-02-22 09:41:40 -08:00
}
}
2021-10-05 13:12:55 -07:00
2021-02-09 07:31:52 -08:00
Ok ( flags )
}
2021-10-05 13:12:55 -07:00
fn stream_logs ( config : & WithPath < Config > , rpc_url : & str ) -> Result < Vec < std ::process ::Child > > {
2021-02-09 09:04:18 -08:00
let program_logs_dir = " .anchor/program-logs " ;
if Path ::new ( program_logs_dir ) . exists ( ) {
2021-10-04 23:16:13 -07:00
fs ::remove_dir_all ( program_logs_dir ) ? ;
2021-02-09 09:04:18 -08:00
}
fs ::create_dir_all ( program_logs_dir ) ? ;
let mut handles = vec! [ ] ;
2021-07-25 17:25:40 -07:00
for program in config . read_all_programs ( ) ? {
2021-02-09 09:04:18 -08:00
let mut file = File ::open ( & format! ( " target/idl/ {} .json " , program . lib_name ) ) ? ;
let mut contents = vec! [ ] ;
file . read_to_end ( & mut contents ) ? ;
let idl : Idl = serde_json ::from_slice ( & contents ) ? ;
2022-04-20 16:07:14 -07:00
let metadata = idl . metadata . ok_or_else ( | | {
anyhow! (
" Metadata property not found in IDL of program: {} " ,
program . lib_name
)
} ) ? ;
2021-02-09 09:04:18 -08:00
let metadata : IdlTestMetadata = serde_json ::from_value ( metadata ) ? ;
let log_file = File ::create ( format! (
" {}/{}.{}.log " ,
2021-08-08 02:11:48 -07:00
program_logs_dir , metadata . address , program . lib_name ,
2021-02-09 09:04:18 -08:00
) ) ? ;
let stdio = std ::process ::Stdio ::from ( log_file ) ;
let child = std ::process ::Command ::new ( " solana " )
. arg ( " logs " )
. arg ( metadata . address )
. arg ( " --url " )
2021-10-05 13:12:55 -07:00
. arg ( rpc_url )
2021-02-09 09:04:18 -08:00
. stdout ( stdio )
. spawn ( ) ? ;
handles . push ( child ) ;
}
2022-03-30 14:17:54 -07:00
if let Some ( test ) = config . test_validator . as_ref ( ) {
2021-10-05 13:12:55 -07:00
if let Some ( genesis ) = & test . genesis {
for entry in genesis {
let log_file = File ::create ( format! ( " {} / {} .log " , program_logs_dir , entry . address ) ) ? ;
let stdio = std ::process ::Stdio ::from ( log_file ) ;
let child = std ::process ::Command ::new ( " solana " )
. arg ( " logs " )
. arg ( entry . address . clone ( ) )
. arg ( " --url " )
. arg ( rpc_url )
. stdout ( stdio )
. spawn ( ) ? ;
handles . push ( child ) ;
}
2021-08-10 02:39:02 -07:00
}
}
2021-02-09 09:04:18 -08:00
Ok ( handles )
}
2021-01-02 22:40:17 -08:00
#[ derive(Debug, Serialize, Deserialize) ]
pub struct IdlTestMetadata {
address : String ,
}
2021-10-02 14:46:57 -07:00
fn start_test_validator (
cfg : & Config ,
2022-03-30 14:17:54 -07:00
test_validator : & Option < TestValidator > ,
2021-10-02 14:46:57 -07:00
flags : Option < Vec < String > > ,
test_log_stdout : bool ,
) -> Result < Child > {
2021-10-05 13:12:55 -07:00
//
2022-03-30 14:17:54 -07:00
let ( test_ledger_directory , test_ledger_log_filename ) =
test_validator_file_paths ( test_validator ) ;
2021-01-02 22:40:17 -08:00
// Start a validator for testing.
2021-10-02 14:46:57 -07:00
let ( test_validator_stdout , test_validator_stderr ) = match test_log_stdout {
true = > {
2021-10-05 13:12:55 -07:00
let test_validator_stdout_file = File ::create ( & test_ledger_log_filename ) ? ;
2021-10-02 14:46:57 -07:00
let test_validator_sterr_file = test_validator_stdout_file . try_clone ( ) ? ;
(
Stdio ::from ( test_validator_stdout_file ) ,
Stdio ::from ( test_validator_sterr_file ) ,
)
}
false = > ( Stdio ::inherit ( ) , Stdio ::inherit ( ) ) ,
} ;
2021-10-05 13:12:55 -07:00
2022-03-30 14:17:54 -07:00
let rpc_url = test_validator_rpc_url ( test_validator ) ;
2021-10-05 13:12:55 -07:00
2022-03-24 16:16:25 -07:00
let rpc_port = cfg
2022-03-30 14:17:54 -07:00
. test_validator
2022-03-24 16:16:25 -07:00
. as_ref ( )
. and_then ( | test | test . validator . as_ref ( ) . map ( | v | v . rpc_port ) )
2022-03-30 14:17:54 -07:00
. unwrap_or ( solana_sdk ::rpc_port ::DEFAULT_RPC_PORT ) ;
2022-03-24 16:16:25 -07:00
if ! portpicker ::is_free ( rpc_port ) {
return Err ( anyhow! (
" Your configured rpc port: {rpc_port} is already in use "
) ) ;
}
let faucet_port = cfg
2022-03-30 14:17:54 -07:00
. test_validator
2022-03-24 16:16:25 -07:00
. as_ref ( )
2022-03-30 14:17:54 -07:00
. and_then ( | test | test . validator . as_ref ( ) . and_then ( | v | v . faucet_port ) )
. unwrap_or ( solana_faucet ::faucet ::FAUCET_PORT ) ;
2022-03-24 16:16:25 -07:00
if ! portpicker ::is_free ( faucet_port ) {
return Err ( anyhow! (
" Your configured faucet port: {faucet_port} is already in use "
) ) ;
}
2021-09-28 10:30:19 -07:00
let mut validator_handle = std ::process ::Command ::new ( " solana-test-validator " )
2021-01-02 22:40:17 -08:00
. arg ( " --ledger " )
2021-10-05 13:12:55 -07:00
. arg ( test_ledger_directory )
2021-04-09 10:33:59 -07:00
. arg ( " --mint " )
. arg ( cfg . wallet_kp ( ) ? . pubkey ( ) . to_string ( ) )
2021-02-15 21:52:54 -08:00
. args ( flags . unwrap_or_default ( ) )
2021-10-02 14:46:57 -07:00
. stdout ( test_validator_stdout )
. stderr ( test_validator_stderr )
2021-01-02 22:40:17 -08:00
. spawn ( )
. map_err ( | e | anyhow ::format_err! ( " {} " , e . to_string ( ) ) ) ? ;
2021-01-04 18:29:16 -08:00
// Wait for the validator to be ready.
2021-10-05 13:12:55 -07:00
let client = RpcClient ::new ( rpc_url ) ;
2021-01-04 18:29:16 -08:00
let mut count = 0 ;
2022-03-30 14:17:54 -07:00
let ms_wait = test_validator
2021-10-06 09:14:19 -07:00
. as_ref ( )
2022-03-30 14:17:54 -07:00
. map ( | test | test . startup_wait )
. unwrap_or ( STARTUP_WAIT ) ;
2021-01-04 18:29:16 -08:00
while count < ms_wait {
2022-03-26 17:28:55 -07:00
let r = client . get_latest_blockhash ( ) ;
2021-01-04 18:29:16 -08:00
if r . is_ok ( ) {
break ;
}
std ::thread ::sleep ( std ::time ::Duration ::from_millis ( 1 ) ) ;
count + = 1 ;
}
2021-09-28 10:30:19 -07:00
if count = = ms_wait {
2021-10-02 13:27:08 -07:00
eprintln! (
2022-03-26 17:28:55 -07:00
" Unable to get latest blockhash. Test validator does not look started. Check {} for errors. Consider increasing [test.startup_wait] in Anchor.toml. " ,
2021-10-02 13:27:08 -07:00
test_ledger_log_filename
) ;
2021-10-01 08:47:57 -07:00
validator_handle . kill ( ) ? ;
2021-01-04 18:29:16 -08:00
std ::process ::exit ( 1 ) ;
}
2021-01-02 22:40:17 -08:00
Ok ( validator_handle )
}
2021-10-05 13:12:55 -07:00
// Return the URL that solana-test-validator should be running on given the
// configuration
2022-03-30 14:17:54 -07:00
fn test_validator_rpc_url ( test_validator : & Option < TestValidator > ) -> String {
match test_validator {
Some ( TestValidator {
2021-10-05 13:12:55 -07:00
validator : Some ( validator ) ,
..
} ) = > format! ( " http:// {} : {} " , validator . bind_address , validator . rpc_port ) ,
_ = > " http://localhost:8899 " . to_string ( ) ,
}
}
// Setup and return paths to the solana-test-validator ledger directory and log
// files given the configuration
2022-03-30 14:17:54 -07:00
fn test_validator_file_paths ( test_validator : & Option < TestValidator > ) -> ( String , String ) {
let ledger_directory = match test_validator {
Some ( TestValidator {
2021-10-05 13:12:55 -07:00
validator : Some ( validator ) ,
..
} ) = > & validator . ledger ,
_ = > " .anchor/test-ledger " ,
} ;
if ! Path ::new ( & ledger_directory ) . is_relative ( ) {
// Prevent absolute paths to avoid someone using / or similar, as the
// directory gets removed
eprintln! ( " Ledger directory {} must be relative " , ledger_directory ) ;
std ::process ::exit ( 1 ) ;
}
if Path ::new ( & ledger_directory ) . exists ( ) {
fs ::remove_dir_all ( & ledger_directory ) . unwrap ( ) ;
}
fs ::create_dir_all ( & ledger_directory ) . unwrap ( ) ;
(
ledger_directory . to_string ( ) ,
format! ( " {} /test-ledger-log.txt " , ledger_directory ) ,
)
}
2022-03-30 14:17:54 -07:00
fn cluster_url ( cfg : & Config , test_validator : & Option < TestValidator > ) -> String {
2021-10-05 13:12:55 -07:00
let is_localnet = cfg . provider . cluster = = Cluster ::Localnet ;
match is_localnet {
// Cluster is Localnet, assume the intent is to use the configuration
// for solana-test-validator
2022-03-30 14:17:54 -07:00
true = > test_validator_rpc_url ( test_validator ) ,
2021-10-05 13:12:55 -07:00
false = > cfg . provider . cluster . url ( ) . to_string ( ) ,
}
}
2022-03-11 07:46:52 -08:00
fn clean ( cfg_override : & ConfigOverride ) -> Result < ( ) > {
let cfg = Config ::discover ( cfg_override ) ? . expect ( " Not in workspace. " ) ;
let cfg_parent = cfg . path ( ) . parent ( ) . expect ( " Invalid Anchor.toml " ) ;
let target_dir = cfg_parent . join ( " target " ) ;
let deploy_dir = target_dir . join ( " deploy " ) ;
for entry in fs ::read_dir ( target_dir ) ? {
let path = entry ? . path ( ) ;
if path . is_dir ( ) & & path ! = deploy_dir {
fs ::remove_dir_all ( & path )
. map_err ( | e | anyhow! ( " Could not remove directory {}: {} " , path . display ( ) , e ) ) ? ;
} else if path . is_file ( ) {
fs ::remove_file ( & path )
. map_err ( | e | anyhow! ( " Could not remove file {}: {} " , path . display ( ) , e ) ) ? ;
}
}
for file in fs ::read_dir ( deploy_dir ) ? {
let path = file ? . path ( ) ;
if path . extension ( ) ! = Some ( & OsString ::from ( " json " ) ) {
fs ::remove_file ( & path )
. map_err ( | e | anyhow! ( " Could not remove file {}: {} " , path . display ( ) , e ) ) ? ;
}
}
Ok ( ( ) )
}
2022-04-20 13:37:14 -07:00
fn deploy (
cfg_override : & ConfigOverride ,
program_str : Option < String > ,
program_keypair : Option < String > ,
) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-05-24 16:04:17 -07:00
let keypair = cfg . provider . wallet . to_string ( ) ;
2021-01-31 00:59:01 -08:00
// Deploy the programs.
println! ( " Deploying workspace: {} " , url ) ;
println! ( " Upgrade authority: {} " , keypair ) ;
2021-07-25 17:25:40 -07:00
for mut program in cfg . read_all_programs ( ) ? {
2021-05-17 10:50:36 -07:00
if let Some ( single_prog_str ) = & program_str {
let program_name = program . path . file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( ) ;
if single_prog_str . as_str ( ) ! = program_name {
continue ;
}
}
2021-01-31 00:59:01 -08:00
let binary_path = program . binary_path ( ) . display ( ) . to_string ( ) ;
2021-05-17 10:50:36 -07:00
println! (
" Deploying program {:?}... " ,
program . path . file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( )
) ;
2022-07-08 06:21:56 -07:00
2021-05-17 10:50:36 -07:00
println! ( " Program path: {} ... " , binary_path ) ;
2021-01-31 00:59:01 -08:00
2022-04-20 13:37:14 -07:00
let program_keypair_filepath = match & program_keypair {
Some ( program_keypair ) = > program_keypair . clone ( ) ,
None = > program . keypair_file ( ) ? . path ( ) . display ( ) . to_string ( ) ,
} ;
2021-01-31 00:59:01 -08:00
// Send deploy transactions.
let exit = std ::process ::Command ::new ( " solana " )
. arg ( " program " )
. arg ( " deploy " )
. arg ( " --url " )
. arg ( & url )
. arg ( " --keypair " )
2021-11-23 18:12:28 -08:00
. arg ( & keypair )
2021-01-31 00:59:01 -08:00
. arg ( " --program-id " )
2022-07-08 06:21:56 -07:00
. arg ( strip_workspace_prefix ( program_keypair_filepath ) )
. arg ( strip_workspace_prefix ( binary_path ) )
2021-01-31 00:59:01 -08:00
. 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 ) ) ;
}
2021-09-07 13:06:15 -07:00
let program_pubkey = program . pubkey ( ) ? ;
2021-08-08 02:11:48 -07:00
if let Some ( mut idl ) = program . idl . as_mut ( ) {
// Add program address to the IDL.
idl . metadata = Some ( serde_json ::to_value ( IdlTestMetadata {
2021-09-07 13:06:15 -07:00
address : program_pubkey . to_string ( ) ,
2021-08-08 02:11:48 -07:00
} ) ? ) ;
// Persist it.
let idl_out = PathBuf ::from ( " target/idl " )
. join ( & idl . name )
. with_extension ( " json " ) ;
write_idl ( idl , OutFile ::File ( idl_out ) ) ? ;
}
2021-01-31 00:59:01 -08:00
}
println! ( " Deploy success " ) ;
2021-09-07 13:06:15 -07:00
Ok ( ( ) )
2021-01-31 00:59:01 -08:00
} )
}
2021-05-24 16:04:17 -07:00
fn upgrade (
cfg_override : & ConfigOverride ,
program_id : Pubkey ,
program_filepath : String ,
) -> Result < ( ) > {
2021-01-31 00:59:01 -08:00
let path : PathBuf = program_filepath . parse ( ) . unwrap ( ) ;
let program_filepath = path . canonicalize ( ) ? . display ( ) . to_string ( ) ;
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-01-31 00:59:01 -08:00
let exit = std ::process ::Command ::new ( " solana " )
. arg ( " program " )
. arg ( " deploy " )
. arg ( " --url " )
2021-10-05 13:12:55 -07:00
. arg ( url )
2021-01-31 00:59:01 -08:00
. arg ( " --keypair " )
2021-05-23 10:00:24 -07:00
. arg ( & cfg . provider . wallet . to_string ( ) )
2021-01-31 00:59:01 -08:00
. arg ( " --program-id " )
2022-07-08 06:21:56 -07:00
. arg ( strip_workspace_prefix ( program_id . to_string ( ) ) )
. arg ( strip_workspace_prefix ( program_filepath ) )
2021-01-31 00:59:01 -08:00
. 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 ( ( ) )
} )
}
2021-01-29 06:19:00 -08:00
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 " ) ) ? ;
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let client = RpcClient ::new ( url ) ;
2021-03-12 12:43:26 -08:00
let idl_data = serialize_idl ( idl ) ? ;
2021-01-29 06:19:00 -08:00
// 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 ,
} ;
2022-03-26 17:28:55 -07:00
let latest_hash = client . get_latest_blockhash ( ) ? ;
2021-01-29 06:19:00 -08:00
let tx = Transaction ::new_signed_with_payer (
& [ ix ] ,
Some ( & keypair . pubkey ( ) ) ,
& [ & keypair ] ,
2022-03-26 17:28:55 -07:00
latest_hash ,
2021-01-29 06:19:00 -08:00
) ;
client . send_and_confirm_transaction_with_spinner_and_config (
& tx ,
2022-04-20 14:38:45 -07:00
CommitmentConfig ::finalized ( ) ,
2021-01-29 06:19:00 -08:00
RpcSendTransactionConfig {
skip_preflight : true ,
.. RpcSendTransactionConfig ::default ( )
} ,
) ? ;
}
2021-03-12 12:43:26 -08:00
// Write directly to the IDL account buffer.
idl_write ( cfg , program_id , idl , IdlAccount ::address ( program_id ) ) ? ;
2021-01-29 06:19:00 -08:00
Ok ( idl_address )
}
2021-03-12 12:43:26 -08:00
fn create_idl_buffer (
cfg : & Config ,
keypair_path : & str ,
program_id : & Pubkey ,
idl : & Idl ,
) -> Result < Pubkey > {
let keypair = solana_sdk ::signature ::read_keypair_file ( keypair_path )
. map_err ( | _ | anyhow! ( " Unable to read keypair file " ) ) ? ;
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let client = RpcClient ::new ( url ) ;
2021-03-12 12:43:26 -08:00
let buffer = Keypair ::generate ( & mut OsRng ) ;
// Creates the new buffer account with the system program.
let create_account_ix = {
let space = 8 + 32 + 4 + serialize_idl ( idl ) ? . len ( ) as usize ;
let lamports = client . get_minimum_balance_for_rent_exemption ( space ) ? ;
solana_sdk ::system_instruction ::create_account (
& keypair . pubkey ( ) ,
& buffer . pubkey ( ) ,
lamports ,
space as u64 ,
program_id ,
)
} ;
// Program instruction to create the buffer.
let create_buffer_ix = {
let accounts = vec! [
AccountMeta ::new ( buffer . pubkey ( ) , false ) ,
AccountMeta ::new_readonly ( keypair . pubkey ( ) , true ) ,
AccountMeta ::new_readonly ( sysvar ::rent ::ID , false ) ,
] ;
let mut data = anchor_lang ::idl ::IDL_IX_TAG . to_le_bytes ( ) . to_vec ( ) ;
data . append ( & mut IdlInstruction ::CreateBuffer . try_to_vec ( ) ? ) ;
Instruction {
program_id : * program_id ,
accounts ,
data ,
}
} ;
// Build the transaction.
2022-03-26 17:28:55 -07:00
let latest_hash = client . get_latest_blockhash ( ) ? ;
2021-03-12 12:43:26 -08:00
let tx = Transaction ::new_signed_with_payer (
& [ create_account_ix , create_buffer_ix ] ,
Some ( & keypair . pubkey ( ) ) ,
& [ & keypair , & buffer ] ,
2022-03-26 17:28:55 -07:00
latest_hash ,
2021-03-12 12:43:26 -08:00
) ;
// Send the transaction.
client . send_and_confirm_transaction_with_spinner_and_config (
& tx ,
CommitmentConfig ::confirmed ( ) ,
RpcSendTransactionConfig {
skip_preflight : true ,
.. RpcSendTransactionConfig ::default ( )
} ,
) ? ;
Ok ( buffer . pubkey ( ) )
}
// Serialize and compress the idl.
fn serialize_idl ( idl : & Idl ) -> Result < Vec < u8 > > {
let json_bytes = serde_json ::to_vec ( idl ) ? ;
let mut e = ZlibEncoder ::new ( Vec ::new ( ) , Compression ::default ( ) ) ;
e . write_all ( & json_bytes ) ? ;
e . finish ( ) . map_err ( Into ::into )
}
2021-01-29 06:19:00 -08:00
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 )
}
2021-05-24 16:04:17 -07:00
fn migrate ( cfg_override : & ConfigOverride ) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2021-01-31 00:59:01 -08:00
println! ( " Running migration deploy script " ) ;
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-01-31 00:59:01 -08:00
let cur_dir = std ::env ::current_dir ( ) ? ;
2021-03-30 23:14:15 -07:00
2021-07-04 19:57:10 -07:00
let use_ts =
Path ::new ( " tsconfig.json " ) . exists ( ) & & Path ::new ( " migrations/deploy.ts " ) . exists ( ) ;
2021-03-09 05:40:09 -08:00
if ! Path ::new ( " .anchor " ) . exists ( ) {
fs ::create_dir ( " .anchor " ) ? ;
}
2021-01-31 00:59:01 -08:00
std ::env ::set_current_dir ( " .anchor " ) ? ;
2021-07-04 19:57:10 -07:00
let exit = if use_ts {
let module_path = cur_dir . join ( " migrations/deploy.ts " ) ;
let deploy_script_host_str =
template ::deploy_ts_script_host ( & url , & module_path . display ( ) . to_string ( ) ) ;
2021-10-04 23:16:13 -07:00
fs ::write ( " deploy.ts " , deploy_script_host_str ) ? ;
2021-07-04 19:57:10 -07:00
std ::process ::Command ::new ( " ts-node " )
. arg ( " deploy.ts " )
2021-09-26 11:26:56 -07:00
. env ( " ANCHOR_WALLET " , cfg . provider . wallet . to_string ( ) )
2021-07-04 19:57:10 -07:00
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( ) ?
} else {
let module_path = cur_dir . join ( " migrations/deploy.js " ) ;
let deploy_script_host_str =
template ::deploy_js_script_host ( & url , & module_path . display ( ) . to_string ( ) ) ;
2021-10-04 23:16:13 -07:00
fs ::write ( " deploy.js " , deploy_script_host_str ) ? ;
2021-07-04 19:57:10 -07:00
std ::process ::Command ::new ( " node " )
. arg ( " deploy.js " )
2021-09-22 11:52:53 -07:00
. env ( " ANCHOR_WALLET " , cfg . provider . wallet . to_string ( ) )
2021-07-04 19:57:10 -07:00
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( ) ?
} ;
2021-03-30 23:14:15 -07:00
if ! exit . status . success ( ) {
println! ( " Deploy failed. " ) ;
std ::process ::exit ( exit . status . code ( ) . unwrap ( ) ) ;
2021-01-02 22:40:17 -08:00
}
2021-01-31 00:59:01 -08:00
println! ( " Deploy complete. " ) ;
Ok ( ( ) )
} )
2021-01-02 22:40:17 -08:00
}
fn set_workspace_dir_or_exit ( ) {
2021-08-10 21:36:56 -07:00
let d = match Config ::discover ( & ConfigOverride ::default ( ) ) {
2021-10-05 13:12:55 -07:00
Err ( err ) = > {
println! ( " Workspace configuration error: {} " , err ) ;
2021-01-02 22:40:17 -08:00
std ::process ::exit ( 1 ) ;
}
Ok ( d ) = > d ,
} ;
match d {
None = > {
println! ( " Not in anchor workspace. " ) ;
std ::process ::exit ( 1 ) ;
}
2021-08-10 21:36:56 -07:00
Some ( cfg ) = > {
2021-08-08 02:11:48 -07:00
match cfg . path ( ) . parent ( ) {
2021-01-02 22:40:17 -08:00
None = > {
println! ( " Unable to make new program " ) ;
}
2021-02-15 21:52:54 -08:00
Some ( parent ) = > {
if std ::env ::set_current_dir ( & parent ) . is_err ( ) {
2021-01-02 22:40:17 -08:00
println! ( " Not in anchor workspace. " ) ;
std ::process ::exit ( 1 ) ;
}
2021-02-15 21:52:54 -08:00
}
2021-01-02 22:40:17 -08:00
} ;
}
}
}
2021-01-31 00:59:01 -08:00
2021-03-12 12:43:26 -08:00
#[ cfg(feature = " dev " ) ]
2021-05-24 16:04:17 -07:00
fn airdrop ( cfg_override : & ConfigOverride ) -> Result < ( ) > {
let url = cfg_override
. cluster
2022-02-03 16:15:23 -08:00
. as_ref ( )
. unwrap_or_else ( | | & Cluster ::Devnet )
. url ( ) ;
2021-01-31 00:59:01 -08:00
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 ) ) ;
}
}
2021-04-14 21:03:08 -07:00
fn cluster ( _cmd : ClusterCommand ) -> Result < ( ) > {
println! ( " Cluster Endpoints: \n " ) ;
println! ( " * Mainnet - https://solana-api.projectserum.com " ) ;
println! ( " * Mainnet - https://api.mainnet-beta.solana.com " ) ;
2021-06-07 19:48:52 -07:00
println! ( " * Devnet - https://api.devnet.solana.com " ) ;
println! ( " * Testnet - https://api.testnet.solana.com " ) ;
2021-04-14 21:03:08 -07:00
Ok ( ( ) )
}
2021-05-22 16:06:08 -07:00
2021-05-24 16:04:17 -07:00
fn shell ( cfg_override : & ConfigOverride ) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2021-05-22 16:06:08 -07:00
let programs = {
2021-08-08 02:11:48 -07:00
// Create idl map from all workspace programs.
2021-07-25 17:25:40 -07:00
let mut idls : HashMap < String , Idl > = cfg
. read_all_programs ( ) ?
2021-05-22 16:06:08 -07:00
. iter ( )
2021-08-08 02:11:48 -07:00
. filter ( | program | program . idl . is_some ( ) )
. map ( | program | {
(
program . idl . as_ref ( ) . unwrap ( ) . name . clone ( ) ,
program . idl . clone ( ) . unwrap ( ) ,
)
} )
2021-05-22 16:06:08 -07:00
. collect ( ) ;
2021-06-27 13:17:05 -07:00
// Insert all manually specified idls into the idl map.
2021-08-08 02:11:48 -07:00
if let Some ( programs ) = cfg . programs . get ( & cfg . provider . cluster ) {
2021-06-27 13:17:05 -07:00
let _ = programs
. iter ( )
. map ( | ( name , pd ) | {
if let Some ( idl_fp ) = & pd . idl {
let file_str =
2021-10-04 23:16:13 -07:00
fs ::read_to_string ( idl_fp ) . expect ( " Unable to read IDL file " ) ;
2021-06-27 13:17:05 -07:00
let idl = serde_json ::from_str ( & file_str ) . expect ( " Idl not readable " ) ;
idls . insert ( name . clone ( ) , idl ) ;
}
} )
. collect ::< Vec < _ > > ( ) ;
2021-06-27 17:09:46 -07:00
}
2021-09-07 13:06:15 -07:00
2021-08-08 02:11:48 -07:00
// Finalize program list with all programs with IDLs.
match cfg . programs . get ( & cfg . provider . cluster ) {
2021-05-22 16:06:08 -07:00
None = > Vec ::new ( ) ,
Some ( programs ) = > programs
. iter ( )
2021-08-08 02:11:48 -07:00
. filter_map ( | ( name , program_deployment ) | {
Some ( ProgramWorkspace {
name : name . to_string ( ) ,
program_id : program_deployment . address ,
idl : match idls . get ( name ) {
None = > return None ,
Some ( idl ) = > idl . clone ( ) ,
} ,
} )
2021-05-22 16:06:08 -07:00
} )
. collect ::< Vec < ProgramWorkspace > > ( ) ,
}
} ;
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let js_code = template ::node_shell ( & url , & cfg . provider . wallet . to_string ( ) , programs ) ? ;
2021-05-22 16:06:08 -07:00
let mut child = std ::process ::Command ::new ( " node " )
. args ( & [ " -e " , & js_code , " -i " , " --experimental-repl-await " ] )
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. spawn ( )
. map_err ( | e | anyhow ::format_err! ( " {} " , e . to_string ( ) ) ) ? ;
if ! child . wait ( ) ? . success ( ) {
println! ( " Error running node shell " ) ;
return Ok ( ( ) ) ;
}
Ok ( ( ) )
} )
}
2021-05-23 13:57:55 -07:00
2022-05-29 14:24:45 -07:00
fn run ( cfg_override : & ConfigOverride , script : String , script_args : Vec < String > ) -> Result < ( ) > {
2021-08-10 21:36:56 -07:00
with_workspace ( cfg_override , | cfg | {
2022-03-30 14:17:54 -07:00
let url = cluster_url ( cfg , & cfg . test_validator ) ;
2021-06-27 13:17:05 -07:00
let script = cfg
. scripts
. get ( & script )
2021-06-27 17:09:46 -07:00
. ok_or_else ( | | anyhow! ( " Unable to find script " ) ) ? ;
2022-05-29 14:24:45 -07:00
let script_with_args = format! ( " {script} {} " , script_args . join ( " " ) ) ;
2021-06-27 13:17:05 -07:00
let exit = std ::process ::Command ::new ( " bash " )
. arg ( " -c " )
2022-05-29 14:24:45 -07:00
. arg ( & script_with_args )
2021-10-05 13:12:55 -07:00
. env ( " ANCHOR_PROVIDER_URL " , url )
2021-09-18 08:27:01 -07:00
. env ( " ANCHOR_WALLET " , cfg . provider . wallet . to_string ( ) )
2021-06-27 13:17:05 -07:00
. stdout ( Stdio ::inherit ( ) )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. unwrap ( ) ;
if ! exit . status . success ( ) {
std ::process ::exit ( exit . status . code ( ) . unwrap_or ( 1 ) ) ;
}
Ok ( ( ) )
} )
}
2021-08-08 02:11:48 -07:00
fn login ( _cfg_override : & ConfigOverride , token : String ) -> Result < ( ) > {
let dir = shellexpand ::tilde ( " ~/.config/anchor " ) ;
if ! Path ::new ( & dir . to_string ( ) ) . exists ( ) {
fs ::create_dir ( dir . to_string ( ) ) ? ;
}
std ::env ::set_current_dir ( dir . to_string ( ) ) ? ;
// Freely overwrite the entire file since it's not used for anything else.
let mut file = File ::create ( " credentials " ) ? ;
file . write_all ( template ::credentials ( & token ) . as_bytes ( ) ) ? ;
Ok ( ( ) )
}
2021-09-27 08:45:28 -07:00
fn publish (
cfg_override : & ConfigOverride ,
program_name : String ,
cargo_args : Vec < String > ,
2022-05-03 06:48:51 -07:00
skip_build : bool ,
2021-09-27 08:45:28 -07:00
) -> Result < ( ) > {
2021-08-08 02:11:48 -07:00
// Discover the various workspace configs.
2021-08-10 21:36:56 -07:00
let cfg = Config ::discover ( cfg_override ) ? . expect ( " Not in workspace. " ) ;
let program = cfg
. get_program ( & program_name ) ?
. ok_or_else ( | | anyhow! ( " Workspace member not found " ) ) ? ;
2021-08-08 02:11:48 -07:00
2021-08-10 21:36:56 -07:00
let program_cargo_lock = pathdiff ::diff_paths (
program . path ( ) . join ( " Cargo.lock " ) ,
cfg . path ( ) . parent ( ) . unwrap ( ) ,
)
. ok_or_else ( | | anyhow! ( " Unable to diff Cargo.lock path " ) ) ? ;
let cargo_lock = Path ::new ( " Cargo.lock " ) ;
// There must be a Cargo.lock
if ! program_cargo_lock . exists ( ) & & ! cargo_lock . exists ( ) {
2021-08-08 02:11:48 -07:00
return Err ( anyhow! ( " Cargo.lock must exist for a verifiable build " ) ) ;
}
println! ( " Publishing will make your code public. Are you sure? Enter (yes)/no: " ) ;
let answer = std ::io ::stdin ( ) . lock ( ) . lines ( ) . next ( ) . unwrap ( ) . unwrap ( ) ;
if answer ! = " yes " {
println! ( " Aborting " ) ;
return Ok ( ( ) ) ;
}
let anchor_package = AnchorPackage ::from ( program_name . clone ( ) , & cfg ) ? ;
let anchor_package_bytes = serde_json ::to_vec ( & anchor_package ) ? ;
// Set directory to top of the workspace.
let workspace_dir = cfg . path ( ) . parent ( ) . unwrap ( ) ;
std ::env ::set_current_dir ( workspace_dir ) ? ;
// Create the workspace tarball.
let dot_anchor = workspace_dir . join ( " .anchor " ) ;
fs ::create_dir_all ( & dot_anchor ) ? ;
let tarball_filename = dot_anchor . join ( format! ( " {} .tar.gz " , program_name ) ) ;
let tar_gz = File ::create ( & tarball_filename ) ? ;
let enc = GzEncoder ::new ( tar_gz , Compression ::default ( ) ) ;
let mut tar = tar ::Builder ::new ( enc ) ;
// Files that will always be included if they exist.
2021-08-10 21:36:56 -07:00
println! ( " PACKING: Anchor.toml " ) ;
2021-08-08 02:11:48 -07:00
tar . append_path ( " Anchor.toml " ) ? ;
2021-08-10 21:36:56 -07:00
if cargo_lock . exists ( ) {
println! ( " PACKING: Cargo.lock " ) ;
tar . append_path ( cargo_lock ) ? ;
}
2021-08-08 02:11:48 -07:00
if Path ::new ( " Cargo.toml " ) . exists ( ) {
2021-08-10 21:36:56 -07:00
println! ( " PACKING: Cargo.toml " ) ;
2021-08-08 02:11:48 -07:00
tar . append_path ( " Cargo.toml " ) ? ;
}
if Path ::new ( " LICENSE " ) . exists ( ) {
2021-08-10 21:36:56 -07:00
println! ( " PACKING: LICENSE " ) ;
2021-08-08 02:11:48 -07:00
tar . append_path ( " LICENSE " ) ? ;
}
if Path ::new ( " README.md " ) . exists ( ) {
2021-08-10 21:36:56 -07:00
println! ( " PACKING: README.md " ) ;
2021-08-08 02:11:48 -07:00
tar . append_path ( " README.md " ) ? ;
}
// All workspace programs.
for path in cfg . get_program_list ( ) ? {
2021-08-10 21:36:56 -07:00
let mut dirs = walkdir ::WalkDir ::new ( & path )
. into_iter ( )
. filter_entry ( | e | ! is_hidden ( e ) ) ;
// Skip the parent dir.
let _ = dirs . next ( ) . unwrap ( ) ? ;
for entry in dirs {
let e = entry . map_err ( | e | anyhow! ( " {:?} " , e ) ) ? ;
let e = pathdiff ::diff_paths ( e . path ( ) , cfg . path ( ) . parent ( ) . unwrap ( ) )
. ok_or_else ( | | anyhow! ( " Unable to diff paths " ) ) ? ;
let path_str = e . display ( ) . to_string ( ) ;
// Skip target dir.
if ! path_str . contains ( " target/ " ) & & ! path_str . contains ( " /target " ) {
// Only add the file if it's not empty.
2021-10-04 23:16:13 -07:00
let metadata = fs ::File ::open ( & e ) ? . metadata ( ) ? ;
2021-08-10 21:36:56 -07:00
if metadata . len ( ) > 0 {
2021-11-14 06:46:18 -08:00
println! ( " PACKING: {} " , e . display ( ) ) ;
2021-08-10 21:36:56 -07:00
if e . is_dir ( ) {
tar . append_dir_all ( & e , & e ) ? ;
} else {
tar . append_path ( & e ) ? ;
}
}
}
2021-08-08 02:11:48 -07:00
}
}
2021-08-10 21:36:56 -07:00
// Tar pack complete.
2021-08-08 02:11:48 -07:00
tar . into_inner ( ) ? ;
2021-09-25 09:51:56 -07:00
// Create tmp directory for workspace.
let ws_dir = dot_anchor . join ( " workspace " ) ;
if Path ::exists ( & ws_dir ) {
fs ::remove_dir_all ( & ws_dir ) ? ;
}
fs ::create_dir_all ( & ws_dir ) ? ;
// Unpack the archive into the new workspace directory.
std ::env ::set_current_dir ( & ws_dir ) ? ;
unpack_archive ( & tarball_filename ) ? ;
// Build the program before sending it to the server.
2022-05-03 06:48:51 -07:00
if ! skip_build {
build (
cfg_override ,
None ,
None ,
true ,
false ,
Some ( program_name ) ,
None ,
None ,
BootstrapMode ::None ,
None ,
None ,
cargo_args ,
true ,
) ? ;
}
2021-09-25 09:51:56 -07:00
2021-08-08 02:11:48 -07:00
// Upload the tarball to the server.
let token = registry_api_token ( cfg_override ) ? ;
let form = Form ::new ( )
. part ( " manifest " , Part ::bytes ( anchor_package_bytes ) )
. part ( " workspace " , {
let file = File ::open ( & tarball_filename ) ? ;
Part ::reader ( file )
} ) ;
let client = Client ::new ( ) ;
let resp = client
. post ( & format! ( " {} /api/v0/build " , cfg . registry . url ) )
. bearer_auth ( token )
. multipart ( form )
. send ( ) ? ;
if resp . status ( ) = = 200 {
println! ( " Build triggered " ) ;
} else {
println! (
" {:?} " ,
resp . text ( ) . unwrap_or_else ( | _ | " Server error " . to_string ( ) )
) ;
}
Ok ( ( ) )
}
2021-09-25 09:51:56 -07:00
// Unpacks the tarball into the current directory.
fn unpack_archive ( tar_path : impl AsRef < Path > ) -> Result < ( ) > {
let tar = GzDecoder ::new ( std ::fs ::File ::open ( tar_path ) ? ) ;
let mut archive = Archive ::new ( tar ) ;
archive . unpack ( " . " ) ? ;
archive . into_inner ( ) ;
Ok ( ( ) )
}
2021-08-08 02:11:48 -07:00
fn registry_api_token ( _cfg_override : & ConfigOverride ) -> Result < String > {
#[ derive(Debug, Deserialize) ]
struct Registry {
token : String ,
}
#[ derive(Debug, Deserialize) ]
struct Credentials {
registry : Registry ,
}
let filename = shellexpand ::tilde ( " ~/.config/anchor/credentials " ) ;
let mut file = File ::open ( filename . to_string ( ) ) ? ;
let mut contents = String ::new ( ) ;
file . read_to_string ( & mut contents ) ? ;
let credentials_toml : Credentials = toml ::from_str ( & contents ) ? ;
Ok ( credentials_toml . registry . token )
}
2021-09-07 13:06:15 -07:00
fn keys ( cfg_override : & ConfigOverride , cmd : KeysCommand ) -> Result < ( ) > {
match cmd {
KeysCommand ::List = > keys_list ( cfg_override ) ,
}
}
fn keys_list ( cfg_override : & ConfigOverride ) -> Result < ( ) > {
let cfg = Config ::discover ( cfg_override ) ? . expect ( " Not in workspace. " ) ;
for program in cfg . read_all_programs ( ) ? {
let pubkey = program . pubkey ( ) ? ;
2021-11-14 06:46:18 -08:00
println! ( " {} : {} " , program . lib_name , pubkey ) ;
2021-09-07 13:06:15 -07:00
}
Ok ( ( ) )
}
2021-10-02 14:46:57 -07:00
fn localnet (
cfg_override : & ConfigOverride ,
skip_build : bool ,
skip_deploy : bool ,
2022-02-20 16:43:19 -08:00
skip_lint : bool ,
2021-10-02 14:46:57 -07:00
cargo_args : Vec < String > ,
) -> Result < ( ) > {
with_workspace ( cfg_override , | cfg | {
// Build if needed.
if ! skip_build {
build (
cfg_override ,
None ,
2021-11-09 22:40:28 -08:00
None ,
2021-10-02 14:46:57 -07:00
false ,
2022-02-20 16:43:19 -08:00
skip_lint ,
2021-10-02 14:46:57 -07:00
None ,
None ,
None ,
2021-11-24 12:09:48 -08:00
BootstrapMode ::None ,
None ,
2021-10-02 14:46:57 -07:00
None ,
cargo_args ,
2022-04-21 13:37:41 -07:00
false ,
2021-10-02 14:46:57 -07:00
) ? ;
}
let flags = match skip_deploy {
true = > None ,
2022-03-30 14:17:54 -07:00
false = > Some ( validator_flags ( cfg , & cfg . test_validator ) ? ) ,
2021-10-02 14:46:57 -07:00
} ;
2021-10-05 13:12:55 -07:00
2022-03-30 14:17:54 -07:00
let validator_handle = & mut start_test_validator ( cfg , & cfg . test_validator , flags , false ) ? ;
2021-10-02 14:46:57 -07:00
2021-10-05 13:12:55 -07:00
// Setup log reader.
2022-03-30 14:17:54 -07:00
let url = test_validator_rpc_url ( & cfg . test_validator ) ;
2021-10-05 13:12:55 -07:00
let log_streams = stream_logs ( cfg , & url ) ;
2021-10-02 14:46:57 -07:00
std ::io ::stdin ( ) . lock ( ) . lines ( ) . next ( ) . unwrap ( ) . unwrap ( ) ;
// Check all errors and shut down.
if let Err ( err ) = validator_handle . kill ( ) {
println! (
" Failed to kill subprocess {}: {} " ,
validator_handle . id ( ) ,
err
) ;
}
for mut child in log_streams ? {
if let Err ( err ) = child . kill ( ) {
println! ( " Failed to kill subprocess {} : {} " , child . id ( ) , err ) ;
}
}
Ok ( ( ) )
} )
}
2021-05-23 13:57:55 -07:00
// 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.
2022-04-12 12:27:03 -07:00
fn with_workspace < R > (
cfg_override : & ConfigOverride ,
f : impl FnOnce ( & mut WithPath < Config > ) -> R ,
) -> R {
2021-05-23 13:57:55 -07:00
set_workspace_dir_or_exit ( ) ;
2022-04-12 12:27:03 -07:00
let mut cfg = Config ::discover ( cfg_override )
2021-05-23 13:57:55 -07:00
. expect ( " Previously set the workspace dir " )
. expect ( " Anchor.toml must always exist " ) ;
2021-05-24 16:04:17 -07:00
2022-04-12 12:27:03 -07:00
let r = f ( & mut cfg ) ;
2021-05-23 13:57:55 -07:00
set_workspace_dir_or_exit ( ) ;
r
}
2021-08-10 21:36:56 -07:00
fn is_hidden ( entry : & walkdir ::DirEntry ) -> bool {
entry
. file_name ( )
. to_str ( )
. map ( | s | s = = " . " | | s . starts_with ( '.' ) | | s = = " target " )
. unwrap_or ( false )
}
2021-10-25 09:47:59 -07:00
fn get_node_version ( ) -> Result < Version > {
let node_version = std ::process ::Command ::new ( " node " )
. arg ( " --version " )
. stderr ( Stdio ::inherit ( ) )
. output ( )
. map_err ( | e | anyhow ::format_err! ( " node failed: {} " , e . to_string ( ) ) ) ? ;
let output = std ::str ::from_utf8 ( & node_version . stdout ) ?
. strip_prefix ( 'v' )
. unwrap ( )
. trim ( ) ;
Version ::parse ( output ) . map_err ( Into ::into )
}
fn get_node_dns_option ( ) -> Result < & 'static str > {
let version = get_node_version ( ) ? ;
let req = VersionReq ::parse ( " >=16.4.0 " ) . unwrap ( ) ;
let option = match req . matches ( & version ) {
true = > " --dns-result-order=ipv4first " ,
false = > " " ,
} ;
Ok ( option )
}
2022-06-06 17:21:10 -07:00
2022-07-08 06:21:56 -07:00
// Remove the current workspace directory if it prefixes a string.
// This is used as a workaround for the Solana CLI using the uriparse crate to
// parse args but not handling percent encoding/decoding when using the path as
// a local filesystem path. Removing the workspace prefix handles most/all cases
// of spaces in keypair/binary paths, but this should be fixed in the Solana CLI
// and removed here.
fn strip_workspace_prefix ( absolute_path : String ) -> String {
let workspace_prefix = std ::env ::current_dir ( ) . unwrap ( ) . display ( ) . to_string ( ) + " / " ;
absolute_path
. strip_prefix ( & workspace_prefix )
. unwrap_or ( & absolute_path )
. into ( )
}
2022-06-06 17:21:10 -07:00
#[ cfg(test) ]
mod tests {
use super ::* ;
#[ test ]
#[ should_panic(expected = " Anchor workspace name must be a valid Rust identifier. " ) ]
fn test_init_reserved_word ( ) {
init (
& ConfigOverride {
cluster : None ,
wallet : None ,
} ,
" await " . to_string ( ) ,
true ,
false ,
)
. unwrap ( ) ;
}
#[ test ]
#[ should_panic(expected = " Anchor workspace name must be a valid Rust identifier. " ) ]
fn test_init_reserved_word_from_syn ( ) {
init (
& ConfigOverride {
cluster : None ,
wallet : None ,
} ,
" fn " . to_string ( ) ,
true ,
false ,
)
. unwrap ( ) ;
}
#[ test ]
#[ should_panic(expected = " Anchor workspace name must be a valid Rust identifier. " ) ]
fn test_init_invalid_ident_chars ( ) {
init (
& ConfigOverride {
cluster : None ,
wallet : None ,
} ,
" project.name " . to_string ( ) ,
true ,
false ,
)
. unwrap ( ) ;
}
#[ test ]
#[ should_panic(expected = " Anchor workspace name must be a valid Rust identifier. " ) ]
fn test_init_starting_with_digit ( ) {
init (
& ConfigOverride {
cluster : None ,
wallet : None ,
} ,
" 1project " . to_string ( ) ,
true ,
false ,
)
. unwrap ( ) ;
}
}