Add support for Solidity using the Solang compiler (#2421)

anchor init and anchor new have a new option `-s` (or `--solidity`) which
creates an example using Solidity in the soldity directory of the
anchor workspace.

anchor deploy/build/test work accordingly.

solang is required to be in the path, this can be downloaded from the
release at https://github.com/hyperledger/solang

anchor build/deploy finds all the solidity contracts by using the
solidity parser to find all contract declarations.
This commit is contained in:
Sean Young 2023-04-04 16:58:03 +01:00 committed by GitHub
parent fbd0688903
commit 9e7c33eec1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 1060 additions and 68 deletions

View File

@ -13,6 +13,7 @@ The minor version will be incremented upon a breaking change and the patch versi
### Features
- spl: Add metadata wrappers `approve_collection_authority`, `bubblegum_set_collection_size`, `burn_edition_nft`, `burn_nft`, `revoke_collection_authority`, `set_token_standard`, `utilize`, `unverify_sized_collection_item`, `unverify_collection` ([#2430](https://github.com/coral-xyz/anchor/pull/2430))
- cli: Add support for Solidity programs. `anchor init` and `anchor new` take an option `--solidity` which creates solidity code rather than rust. `anchor build` and `anchor test` work accordingly ([#2421](https://github.com/coral-xyz/anchor/pull/2421))
### Fixes
- ts: Narrowed `AccountClient` type to it's appropriate account type ([#2440](https://github.com/coral-xyz/anchor/pull/2440))

285
Cargo.lock generated
View File

@ -187,6 +187,7 @@ dependencies = [
"solana-faucet",
"solana-program",
"solana-sdk",
"solang-parser",
"syn 1.0.109",
"tar",
"tokio",
@ -413,6 +414,15 @@ version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eab1c04a571841102f5345a8fc0f6bb3d31c315dec879b5c6e42e40ce7ffa34e"
[[package]]
name = "ascii-canvas"
version = "3.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8824ecca2e851cec16968d54a01dd372ef8f95b244fb84b84e70128be347c3c6"
dependencies = [
"term",
]
[[package]]
name = "asn1-rs"
version = "0.5.1"
@ -498,7 +508,7 @@ version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi",
"hermit-abi 0.1.19",
"libc",
"winapi",
]
@ -536,6 +546,21 @@ dependencies = [
"serde",
]
[[package]]
name = "bit-set"
version = "0.5.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1"
dependencies = [
"bit-vec",
]
[[package]]
name = "bit-vec"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb"
[[package]]
name = "bitflags"
version = "1.3.2"
@ -1194,6 +1219,12 @@ dependencies = [
"zeroize",
]
[[package]]
name = "diff"
version = "0.1.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "56254986775e3233ffa9c4d7d3faaf6d36a2c09d30b20687e9f88bc8bafc16c8"
[[package]]
name = "digest"
version = "0.9.0"
@ -1336,6 +1367,15 @@ version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90e5c1c8368803113bf0c9584fc495a58b86dc8a29edbf8fe877d21d9507e797"
[[package]]
name = "ena"
version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c533630cf40e9caa44bd91aadc88a75d75a4c3a12b4cfde353cbed41daa1e1f1"
dependencies = [
"log",
]
[[package]]
name = "encode_unicode"
version = "0.3.6"
@ -1416,6 +1456,27 @@ dependencies = [
"termcolor",
]
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "event-listener"
version = "2.5.3"
@ -1459,6 +1520,12 @@ dependencies = [
"windows-sys 0.42.0",
]
[[package]]
name = "fixedbitset"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0ce7134b9999ecaf8bcd65542e436736ef32ddca1b3e06094cb6ec5755203b80"
[[package]]
name = "flate2"
version = "1.0.24"
@ -1701,6 +1768,15 @@ dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "histogram"
version = "0.6.9"
@ -1910,12 +1986,34 @@ dependencies = [
"cfg-if",
]
[[package]]
name = "io-lifetimes"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys 0.42.0",
]
[[package]]
name = "ipnet"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879d54834c8c76457ef4293a689b2a8c59b076067ad77b15efafbb05f92a592b"
[[package]]
name = "is-terminal"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
dependencies = [
"hermit-abi 0.2.6",
"io-lifetimes",
"rustix",
"windows-sys 0.42.0",
]
[[package]]
name = "itertools"
version = "0.9.0"
@ -1979,6 +2077,38 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f9b7d56ba4a8344d6be9729995e6b06f928af29998cdf79fe390cbf6b1fee838"
[[package]]
name = "lalrpop"
version = "0.19.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f34313ec00c2eb5c3c87ca6732ea02dcf3af99c3ff7a8fb622ffb99c9d860a87"
dependencies = [
"ascii-canvas",
"bit-set",
"diff",
"ena",
"is-terminal",
"itertools 0.10.5",
"lalrpop-util",
"petgraph",
"pico-args",
"regex",
"regex-syntax",
"string_cache",
"term",
"tiny-keccak",
"unicode-xid 0.2.4",
]
[[package]]
name = "lalrpop-util"
version = "0.19.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e5c1f7869c94d214466c5fd432dfed12c379fd87786768d36455892d46b18edd"
dependencies = [
"regex",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
@ -2058,6 +2188,12 @@ dependencies = [
"cc",
]
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "lock_api"
version = "0.4.9"
@ -2225,6 +2361,12 @@ dependencies = [
"spl-token",
]
[[package]]
name = "new_debug_unreachable"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e4a24736216ec316047a1fc4252e27dabb04218aa4a3f37c6e7ddbf1f9782b54"
[[package]]
name = "nix"
version = "0.25.1"
@ -2354,7 +2496,7 @@ version = "1.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "19e64526ebdee182341572e50e9ad03965aa510cd94427a4549448f285e957a1"
dependencies = [
"hermit-abi",
"hermit-abi 0.1.19",
"libc",
]
@ -2514,6 +2656,73 @@ dependencies = [
"ucd-trie",
]
[[package]]
name = "petgraph"
version = "0.6.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4dd7d28ee937e54fe3080c91faa1c3a46c06de6252988a7f4592ba2310ef22a4"
dependencies = [
"fixedbitset",
"indexmap",
]
[[package]]
name = "phf"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "928c6535de93548188ef63bb7c4036bd415cd8f36ad25af44b9789b2ee72a48c"
dependencies = [
"phf_macros",
"phf_shared 0.11.1",
]
[[package]]
name = "phf_generator"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1181c94580fa345f50f19d738aaa39c0ed30a600d95cb2d3e23f94266f14fbf"
dependencies = [
"phf_shared 0.11.1",
"rand 0.8.5",
]
[[package]]
name = "phf_macros"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "92aacdc5f16768709a569e913f7451034034178b05bdc8acda226659a3dccc66"
dependencies = [
"phf_generator",
"phf_shared 0.11.1",
"proc-macro2 1.0.47",
"quote 1.0.21",
"syn 1.0.109",
]
[[package]]
name = "phf_shared"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6796ad771acdc0123d2a88dc428b5e38ef24456743ddb1744ed628f9815c096"
dependencies = [
"siphasher",
]
[[package]]
name = "phf_shared"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e1fb5f6f826b772a8d4c0394209441e7d37cbbb967ae9c7e0e8134365c9ee676"
dependencies = [
"siphasher",
]
[[package]]
name = "pico-args"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "db8bcd96cb740d03149cbad5518db9fd87126a10ab519c011893b1754134c468"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@ -2576,6 +2785,12 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
[[package]]
name = "precomputed-hash"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c"
[[package]]
name = "proc-macro-crate"
version = "0.1.5"
@ -3016,6 +3231,20 @@ dependencies = [
"nom",
]
[[package]]
name = "rustix"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys 0.42.0",
]
[[package]]
name = "rustls"
version = "0.20.7"
@ -3400,6 +3629,12 @@ version = "1.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "74233d3b3b2f6d4b006dc19dee745e73e2a6bfb6f93607cd3b02bd5b00797d7c"
[[package]]
name = "siphasher"
version = "0.3.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bd3e3206899af3f8b12af284fafc038cc1dc2b41d1b89dd17297221c5d225de"
[[package]]
name = "sized-chunks"
version = "0.6.5"
@ -4232,6 +4467,19 @@ dependencies = [
"winapi",
]
[[package]]
name = "solang-parser"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ff87dae6cdccacdbf3b19e99b271083556e808de0f59c74a01482f64fdbc61fc"
dependencies = [
"itertools 0.10.5",
"lalrpop",
"lalrpop-util",
"phf",
"unicode-xid 0.2.4",
]
[[package]]
name = "spin"
version = "0.5.2"
@ -4312,6 +4560,19 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "string_cache"
version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f91138e76242f575eb1d3b38b4f1362f10d3a43f47d182a5b359af488a02293b"
dependencies = [
"new_debug_unreachable",
"once_cell",
"parking_lot",
"phf_shared 0.10.0",
"precomputed-hash",
]
[[package]]
name = "strsim"
version = "0.8.0"
@ -4389,6 +4650,17 @@ dependencies = [
"winapi",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.1.3"
@ -4502,6 +4774,15 @@ dependencies = [
"zeroize",
]
[[package]]
name = "tiny-keccak"
version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2c9d3793400a45f954c52e73d068316d76b6f4e36977e3fcebb13a2721e80237"
dependencies = [
"crunchy",
]
[[package]]
name = "tinyvec"
version = "1.6.0"

View File

@ -26,6 +26,7 @@ anchor-syn = { path = "../lang/syn", features = ["idl", "init-if-needed"], versi
serde_json = "1.0"
shellexpand = "2.1.0"
toml = "0.5.8"
solang-parser = "=0.2.3"
semver = "1.0.4"
serde = { version = "1.0.122", features = ["derive"] }
solana-sdk = "1.14.16"

View File

@ -1,7 +1,7 @@
use crate::is_hidden;
use anchor_client::Cluster;
use anchor_syn::idl::Idl;
use anyhow::{anyhow, Context, Error, Result};
use anyhow::{anyhow, bail, Context, Error, Result};
use clap::{Parser, ValueEnum};
use heck::ToSnakeCase;
use reqwest::Url;
@ -10,8 +10,10 @@ use serde::{Deserialize, Deserializer, Serialize};
use solana_cli_config::{Config as SolanaConfig, CONFIG_FILE};
use solana_sdk::pubkey::Pubkey;
use solana_sdk::signature::{Keypair, Signer};
use solang_parser::pt::{ContractTy, SourceUnitPart};
use std::collections::{BTreeMap, HashMap};
use std::convert::TryFrom;
use std::ffi::OsStr;
use std::fs::{self, File};
use std::io::prelude::*;
use std::marker::PhantomData;
@ -145,7 +147,7 @@ impl Deref for Manifest {
}
impl WithPath<Config> {
pub fn get_program_list(&self) -> Result<Vec<PathBuf>> {
pub fn get_rust_program_list(&self) -> Result<Vec<PathBuf>> {
// Canonicalize the workspace filepaths to compare with relative paths.
let (members, exclude) = self.canonicalize_workspace()?;
@ -156,12 +158,16 @@ impl WithPath<Config> {
let program_paths: Vec<PathBuf> = {
if members.is_empty() {
let path = self.path().parent().unwrap().join("programs");
fs::read_dir(path)?
.filter(|entry| entry.as_ref().map(|e| e.path().is_dir()).unwrap_or(false))
.map(|dir| dir.map(|d| d.path().canonicalize().unwrap()))
.collect::<Vec<Result<PathBuf, std::io::Error>>>()
.into_iter()
.collect::<Result<Vec<PathBuf>, std::io::Error>>()?
if let Ok(entries) = fs::read_dir(path) {
entries
.filter(|entry| entry.as_ref().map(|e| e.path().is_dir()).unwrap_or(false))
.map(|dir| dir.map(|d| d.path().canonicalize().unwrap()))
.collect::<Vec<Result<PathBuf, std::io::Error>>>()
.into_iter()
.collect::<Result<Vec<PathBuf>, std::io::Error>>()?
} else {
Vec::new()
}
} else {
members
}
@ -174,9 +180,56 @@ impl WithPath<Config> {
.collect())
}
/// Parse all the files with the .sol extension, and get a list of the all
/// contracts defined in them along with their path. One Solidity file may
/// define multiple contracts.
pub fn get_solidity_program_list(&self) -> Result<Vec<(String, PathBuf)>> {
let path = self.path().parent().unwrap().join("solidity");
let mut res = Vec::new();
if let Ok(entries) = fs::read_dir(path) {
for entry in entries {
let path = entry?.path();
if !path.is_file() || path.extension() != Some(OsStr::new("sol")) {
continue;
}
let source = fs::read_to_string(&path)?;
let tree = match solang_parser::parse(&source, 0) {
Ok((tree, _)) => tree,
Err(diag) => {
// The parser can return multiple errors, however this is exceedingly rare.
// Just use the first one, else the formatting will be a mess.
bail!(
"{}: {}: {}",
path.display(),
diag[0].level.to_string(),
diag[0].message
);
}
};
tree.0.iter().for_each(|part| {
if let SourceUnitPart::ContractDefinition(contract) = part {
// Must be a contract, not library/interface/abstract contract
if matches!(&contract.ty, ContractTy::Contract(..)) {
if let Some(name) = &contract.name {
res.push((name.name.clone(), path.clone()));
}
}
}
});
}
}
Ok(res)
}
pub fn read_all_programs(&self) -> Result<Vec<Program>> {
let mut r = vec![];
for path in self.get_program_list()? {
for path in self.get_rust_program_list()? {
let cargo = Manifest::from_path(path.join("Cargo.toml"))?;
let lib_name = cargo.lib_name()?;
@ -188,6 +241,21 @@ impl WithPath<Config> {
r.push(Program {
lib_name,
solidity: false,
path,
idl,
});
}
for (lib_name, path) in self.get_solidity_program_list()? {
let idl_filepath = format!("target/idl/{lib_name}.json");
let idl = fs::read(idl_filepath)
.ok()
.map(|bytes| serde_json::from_reader(&*bytes))
.transpose()?;
r.push(Program {
lib_name,
solidity: true,
path,
idl,
});
@ -1126,7 +1194,8 @@ impl Merge for _Validator {
#[derive(Debug, Clone)]
pub struct Program {
pub lib_name: String,
// Canonicalized path to the program directory.
pub solidity: bool,
// Canonicalized path to the program directory or Solidity source file
pub path: PathBuf,
pub idl: Option<Idl>,
}

View File

@ -46,7 +46,8 @@ use tar::Archive;
pub mod config;
mod path;
pub mod template;
pub mod rust_template;
pub mod solidity_template;
// Version of the docker image.
pub const VERSION: &str = env!("CARGO_PKG_VERSION");
@ -68,6 +69,8 @@ pub enum Command {
name: String,
#[clap(short, long)]
javascript: bool,
#[clap(short, long)]
solidity: bool,
#[clap(long)]
no_git: bool,
#[clap(long)]
@ -200,7 +203,11 @@ pub enum Command {
cargo_args: Vec<String>,
},
/// Creates a new program.
New { name: String },
New {
#[clap(short, long)]
solidity: bool,
name: String,
},
/// Commands for interacting with interface definitions.
Idl {
#[clap(subcommand)]
@ -214,7 +221,7 @@ pub enum Command {
#[clap(short, long)]
program_name: Option<String>,
/// Keypair of the program (filepath) (requires program-name)
#[clap(long, requires = "program-name")]
#[clap(long, requires = "program_name")]
program_keypair: Option<String>,
},
/// Runs the deploy migration script.
@ -412,10 +419,11 @@ pub fn entry(opts: Opts) -> Result<()> {
Command::Init {
name,
javascript,
solidity,
no_git,
jest,
} => init(&opts.cfg_override, name, javascript, no_git, jest),
Command::New { name } => new(&opts.cfg_override, name),
} => init(&opts.cfg_override, name, javascript, solidity, no_git, jest),
Command::New { solidity, name } => new(&opts.cfg_override, solidity, name),
Command::Build {
idl,
idl_ts,
@ -559,6 +567,7 @@ fn init(
cfg_override: &ConfigOverride,
name: String,
javascript: bool,
solidity: bool,
no_git: bool,
jest: bool,
) -> Result<()> {
@ -617,7 +626,11 @@ fn init(
localnet.insert(
rust_name,
ProgramDeployment {
address: template::default_program_id(),
address: if solidity {
solidity_template::default_program_id()
} else {
rust_template::default_program_id()
},
path: None,
idl: None,
},
@ -626,20 +639,25 @@ fn init(
let toml = cfg.to_string();
fs::write("Anchor.toml", toml)?;
// Build virtual manifest.
fs::write("Cargo.toml", template::virtual_manifest())?;
// Initialize .gitignore file
fs::write(".gitignore", template::git_ignore())?;
fs::write(".gitignore", rust_template::git_ignore())?;
// Initialize .prettierignore file
fs::write(".prettierignore", template::prettier_ignore())?;
fs::write(".prettierignore", rust_template::prettier_ignore())?;
// Build the program.
fs::create_dir("programs")?;
if solidity {
fs::create_dir("solidity")?;
new_program(&project_name)?;
new_solidity_program(&project_name)?;
} else {
// Build virtual manifest for rust programs
fs::write("Cargo.toml", rust_template::virtual_manifest())?;
fs::create_dir("programs")?;
new_rust_program(&project_name)?;
}
// Build the test suite.
fs::create_dir("tests")?;
// Build the migrations directory.
@ -648,31 +666,44 @@ fn init(
if javascript {
// Build javascript config
let mut package_json = File::create("package.json")?;
package_json.write_all(template::package_json(jest).as_bytes())?;
package_json.write_all(rust_template::package_json(jest).as_bytes())?;
if jest {
let mut test = File::create(format!("tests/{}.test.js", &project_name))?;
test.write_all(template::jest(&project_name).as_bytes())?;
if solidity {
test.write_all(solidity_template::jest(&project_name).as_bytes())?;
} else {
test.write_all(rust_template::jest(&project_name).as_bytes())?;
}
} else {
let mut test = File::create(format!("tests/{}.js", &project_name))?;
test.write_all(template::mocha(&project_name).as_bytes())?;
if solidity {
test.write_all(solidity_template::mocha(&project_name).as_bytes())?;
} else {
test.write_all(rust_template::mocha(&project_name).as_bytes())?;
}
}
let mut deploy = File::create("migrations/deploy.js")?;
deploy.write_all(template::deploy_script().as_bytes())?;
deploy.write_all(rust_template::deploy_script().as_bytes())?;
} else {
// Build typescript config
let mut ts_config = File::create("tsconfig.json")?;
ts_config.write_all(template::ts_config(jest).as_bytes())?;
ts_config.write_all(rust_template::ts_config(jest).as_bytes())?;
let mut ts_package_json = File::create("package.json")?;
ts_package_json.write_all(template::ts_package_json(jest).as_bytes())?;
ts_package_json.write_all(rust_template::ts_package_json(jest).as_bytes())?;
let mut deploy = File::create("migrations/deploy.ts")?;
deploy.write_all(template::ts_deploy_script().as_bytes())?;
deploy.write_all(rust_template::ts_deploy_script().as_bytes())?;
let mut mocha = File::create(format!("tests/{}.ts", &project_name))?;
mocha.write_all(template::ts_mocha(&project_name).as_bytes())?;
if solidity {
mocha.write_all(solidity_template::ts_mocha(&project_name).as_bytes())?;
} else {
mocha.write_all(rust_template::ts_mocha(&project_name).as_bytes())?;
}
}
let yarn_result = install_node_modules("yarn")?;
@ -717,7 +748,7 @@ fn install_node_modules(cmd: &str) -> Result<std::process::Output> {
}
// Creates a new program crate in the `programs/<name>` directory.
fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
fn new(cfg_override: &ConfigOverride, solidity: bool, name: String) -> Result<()> {
with_workspace(cfg_override, |cfg| {
match cfg.path().parent() {
None => {
@ -725,7 +756,11 @@ fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
}
Some(parent) => {
std::env::set_current_dir(parent)?;
new_program(&name)?;
if solidity {
new_solidity_program(&name)?;
} else {
new_rust_program(&name)?;
}
println!("Created new program.");
}
};
@ -733,16 +768,26 @@ fn new(cfg_override: &ConfigOverride, name: String) -> Result<()> {
})
}
// 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/{name}/src/"))?;
// Creates a new rust program crate in the current directory with `name`.
fn new_rust_program(name: &str) -> Result<()> {
if !PathBuf::from("Cargo.toml").exists() {
fs::write("Cargo.toml", rust_template::virtual_manifest())?;
}
fs::create_dir_all(format!("programs/{name}/src/"))?;
let mut cargo_toml = File::create(format!("programs/{name}/Cargo.toml"))?;
cargo_toml.write_all(template::cargo_toml(name).as_bytes())?;
cargo_toml.write_all(rust_template::cargo_toml(name).as_bytes())?;
let mut xargo_toml = File::create(format!("programs/{name}/Xargo.toml"))?;
xargo_toml.write_all(template::xargo_toml().as_bytes())?;
xargo_toml.write_all(rust_template::xargo_toml().as_bytes())?;
let mut lib_rs = File::create(format!("programs/{name}/src/lib.rs"))?;
lib_rs.write_all(template::lib_rs(name).as_bytes())?;
lib_rs.write_all(rust_template::lib_rs(name).as_bytes())?;
Ok(())
}
// Creates a new solidity program in the current directory with `name`.
fn new_solidity_program(name: &str) -> Result<()> {
fs::create_dir_all("solidity")?;
let mut lib_rs = File::create(format!("solidity/{name}.sol"))?;
lib_rs.write_all(solidity_template::solidity(name).as_bytes())?;
Ok(())
}
@ -786,7 +831,7 @@ fn expand_all(
cargo_args: &[String],
) -> Result<()> {
let cur_dir = std::env::current_dir()?;
for p in workspace_cfg.get_program_list()? {
for p in workspace_cfg.get_rust_program_list()? {
expand_program(p, expansions_path.clone(), cargo_args)?;
}
std::env::set_current_dir(cur_dir)?;
@ -923,7 +968,7 @@ pub fn build(
arch,
)?,
// Cargo.toml represents a single package. Build it.
Some(cargo) => build_cwd(
Some(cargo) => build_rust_cwd(
&cfg,
cargo.path().to_path_buf(),
idl_out,
@ -963,8 +1008,8 @@ fn build_all(
let r = match cfg_path.parent() {
None => Err(anyhow!("Invalid Anchor.toml at {}", cfg_path.display())),
Some(_parent) => {
for p in cfg.get_program_list()? {
build_cwd(
for p in cfg.get_rust_program_list()? {
build_rust_cwd(
cfg,
p.join("Cargo.toml"),
idl_out.clone(),
@ -979,6 +1024,19 @@ fn build_all(
&arch,
)?;
}
for (name, path) in cfg.get_solidity_program_list()? {
build_solidity_cwd(
cfg,
name,
path,
idl_out.clone(),
idl_ts_out.clone(),
build_config,
stdout.as_ref().map(|f| f.try_clone()).transpose()?,
stderr.as_ref().map(|f| f.try_clone()).transpose()?,
cargo_args.clone(),
)?;
}
Ok(())
}
};
@ -988,7 +1046,7 @@ fn build_all(
// Runs the build command outside of a workspace.
#[allow(clippy::too_many_arguments)]
fn build_cwd(
fn build_rust_cwd(
cfg: &WithPath<Config>,
cargo_toml: PathBuf,
idl_out: Option<PathBuf>,
@ -1007,7 +1065,7 @@ fn build_cwd(
Some(p) => std::env::set_current_dir(p)?,
};
match build_config.verifiable {
false => _build_cwd(cfg, idl_out, idl_ts_out, skip_lint, arch, cargo_args),
false => _build_rust_cwd(cfg, idl_out, idl_ts_out, skip_lint, arch, cargo_args),
true => build_cwd_verifiable(
cfg,
cargo_toml,
@ -1023,6 +1081,31 @@ fn build_cwd(
}
}
// Runs the build command outside of a workspace.
#[allow(clippy::too_many_arguments)]
fn build_solidity_cwd(
cfg: &WithPath<Config>,
name: String,
path: PathBuf,
idl_out: Option<PathBuf>,
idl_ts_out: Option<PathBuf>,
build_config: &BuildConfig,
stdout: Option<File>,
stderr: Option<File>,
cargo_args: Vec<String>,
) -> Result<()> {
match path.parent() {
None => return Err(anyhow!("Unable to find parent")),
Some(p) => std::env::set_current_dir(p)?,
};
match build_config.verifiable {
false => _build_solidity_cwd(
cfg, &name, &path, idl_out, idl_ts_out, stdout, stderr, cargo_args,
),
true => panic!("verifiable solidity not supported"),
}
}
// Builds an anchor program in a docker image and copies the build artifacts
// into the `target/` directory.
#[allow(clippy::too_many_arguments)]
@ -1078,7 +1161,7 @@ fn build_cwd_verifiable(
// 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)?)?;
fs::write(&ts_file, rust_template::idl_ts(&idl)?)?;
// Copy out the TypeScript type.
if !&cfg.workspace.types.is_empty() {
@ -1341,7 +1424,7 @@ fn docker_exec(container_name: &str, args: &[&str]) -> Result<()> {
}
}
fn _build_cwd(
fn _build_rust_cwd(
cfg: &WithPath<Config>,
idl_out: Option<PathBuf>,
idl_ts_out: Option<PathBuf>,
@ -1377,7 +1460,7 @@ fn _build_cwd(
// Write out the JSON file.
write_idl(&idl, OutFile::File(out))?;
// Write out the TypeScript type.
fs::write(&ts_out, template::idl_ts(&idl)?)?;
fs::write(&ts_out, rust_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() {
@ -1394,6 +1477,80 @@ fn _build_cwd(
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn _build_solidity_cwd(
cfg: &WithPath<Config>,
name: &str,
path: &Path,
idl_out: Option<PathBuf>,
idl_ts_out: Option<PathBuf>,
stdout: Option<File>,
stderr: Option<File>,
solang_args: Vec<String>,
) -> Result<()> {
let mut cmd = std::process::Command::new("solang");
let cmd = cmd.args(["compile", "--target", "solana", "--contract", name]);
if let Some(idl_out) = &idl_out {
cmd.arg("--output-meta");
cmd.arg(idl_out);
}
let target_bin = cfg.path().parent().unwrap().join("target").join("deploy");
cmd.arg("--output");
cmd.arg(target_bin);
cmd.arg("--verbose");
cmd.arg(path);
let exit = cmd
.args(solang_args)
.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!("{}", e.to_string()))?;
if !exit.status.success() {
std::process::exit(exit.status.code().unwrap_or(1));
}
// idl is written to idl_out or .
let idl_path = idl_out
.unwrap_or(PathBuf::from("."))
.join(format!("{}.json", name));
let idl = fs::read_to_string(idl_path)?;
let idl: Idl = serde_json::from_str(&idl)?;
// 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")),
};
// Write out the TypeScript type.
fs::write(&ts_out, rust_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"),
)?;
}
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn verify(
cfg_override: &ConfigOverride,
@ -1476,17 +1633,24 @@ fn cd_member(cfg_override: &ConfigOverride, program_name: &str) -> Result<()> {
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(());
if program.solidity {
if let Some(path) = program.path.parent() {
std::env::set_current_dir(path)?;
return Ok(());
}
} else {
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(());
}
}
}
Err(anyhow!("{} is not part of the workspace", program_name,))
@ -2007,7 +2171,7 @@ fn idl_parse(
// Write out the TypeScript IDL.
if let Some(out) = out_ts {
fs::write(out, template::idl_ts(&idl)?)?;
fs::write(out, rust_template::idl_ts(&idl)?)?;
}
Ok(())
@ -3116,7 +3280,7 @@ fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
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());
rust_template::deploy_ts_script_host(&url, &module_path.display().to_string());
fs::write("deploy.ts", deploy_script_host_str)?;
std::process::Command::new("ts-node")
.arg("deploy.ts")
@ -3127,7 +3291,7 @@ fn migrate(cfg_override: &ConfigOverride) -> Result<()> {
} 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());
rust_template::deploy_js_script_host(&url, &module_path.display().to_string());
fs::write("deploy.js", deploy_script_host_str)?;
std::process::Command::new("node")
.arg("deploy.js")
@ -3259,7 +3423,7 @@ fn shell(cfg_override: &ConfigOverride) -> Result<()> {
}
};
let url = cluster_url(cfg, &cfg.test_validator);
let js_code = template::node_shell(&url, &cfg.provider.wallet.to_string(), programs)?;
let js_code = rust_template::node_shell(&url, &cfg.provider.wallet.to_string(), programs)?;
let mut child = std::process::Command::new("node")
.args(["-e", &js_code, "-i", "--experimental-repl-await"])
.stdout(Stdio::inherit())
@ -3309,7 +3473,7 @@ fn login(_cfg_override: &ConfigOverride, token: String) -> Result<()> {
// 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())?;
file.write_all(rust_template::credentials(&token).as_bytes())?;
Ok(())
}
@ -3388,7 +3552,7 @@ fn publish(
}
// All workspace programs.
for path in cfg.get_program_list()? {
for path in cfg.get_rust_program_list()? {
let mut dirs = walkdir::WalkDir::new(path)
.into_iter()
.filter_entry(|e| !is_hidden(e));
@ -3674,6 +3838,7 @@ mod tests {
true,
false,
false,
false,
)
.unwrap();
}
@ -3690,6 +3855,7 @@ mod tests {
true,
false,
false,
false,
)
.unwrap();
}
@ -3706,6 +3872,7 @@ mod tests {
true,
false,
false,
false,
)
.unwrap();
}

View File

@ -0,0 +1,473 @@
use crate::config::ProgramWorkspace;
use crate::VERSION;
use anchor_syn::idl::Idl;
use anyhow::Result;
use heck::{ToLowerCamelCase, ToSnakeCase, ToUpperCamelCase};
use solana_sdk::pubkey::Pubkey;
use std::fmt::Write;
pub fn default_program_id() -> Pubkey {
"F1ipperKF9EfD821ZbbYjS319LXYiBmjhzkkf5a26rC"
.parse()
.unwrap()
}
pub fn idl_ts(idl: &Idl) -> Result<String> {
let mut idl = idl.clone();
for acc in idl.accounts.iter_mut() {
acc.name = acc.name.to_lower_camel_case();
}
let idl_json = serde_json::to_string_pretty(&idl)?;
Ok(format!(
r#"export type {} = {};
export const IDL: {} = {};
"#,
idl.name.to_upper_camel_case(),
idl_json,
idl.name.to_upper_camel_case(),
idl_json
))
}
pub fn deploy_js_script_host(cluster_url: &str, script_path: &str) -> String {
format!(
r#"
const anchor = require('@coral-xyz/anchor');
// Deploy script defined by the user.
const userScript = require("{script_path}");
async function main() {{
const url = "{cluster_url}";
const preflightCommitment = 'recent';
const connection = new anchor.web3.Connection(url, preflightCommitment);
const wallet = anchor.Wallet.local();
const provider = new anchor.AnchorProvider(connection, wallet, {{
preflightCommitment,
commitment: 'recent',
}});
// Run the user's deploy script.
userScript(provider);
}}
main();
"#
)
}
pub fn deploy_ts_script_host(cluster_url: &str, script_path: &str) -> String {
format!(
r#"import * as anchor from '@coral-xyz/anchor';
// Deploy script defined by the user.
const userScript = require("{script_path}");
async function main() {{
const url = "{cluster_url}";
const preflightCommitment = 'recent';
const connection = new anchor.web3.Connection(url, preflightCommitment);
const wallet = anchor.Wallet.local();
const provider = new anchor.AnchorProvider(connection, wallet, {{
preflightCommitment,
commitment: 'recent',
}});
// Run the user's deploy script.
userScript(provider);
}}
main();
"#
)
}
pub fn deploy_script() -> &'static str {
r#"// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.
const anchor = require("@coral-xyz/anchor");
module.exports = async function (provider) {
// Configure client to use the provider.
anchor.setProvider(provider);
// Add your deploy script here.
};
"#
}
pub fn ts_deploy_script() -> &'static str {
r#"// Migrations are an early feature. Currently, they're nothing more than this
// single deploy script that's invoked from the CLI, injecting a provider
// configured from the workspace's Anchor.toml.
const anchor = require("@coral-xyz/anchor");
module.exports = async function (provider) {
// Configure client to use the provider.
anchor.setProvider(provider);
// Add your deploy script here.
};
"#
}
pub fn solidity(name: &str) -> String {
format!(
r#"
@program_id("{}")
contract {} {{
bool private value = true;
@payer(payer)
constructor(address payer) {{
print("Hello, World!");
}}
/// A message that can be called on instantiated contracts.
/// This one flips the value of the stored `bool` from `true`
/// to `false` and vice versa.
function flip() public {{
value = !value;
}}
/// Simply returns the current value of our `bool`.
function get() public view returns (bool) {{
return value;
}}
}}
"#,
default_program_id(),
name.to_snake_case(),
)
}
pub fn mocha(name: &str) -> String {
format!(
r#"const anchor = require("@coral-xyz/anchor");
describe("{}", () => {{
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
it("Is initialized!", async () => {{
// Add your test here.
const program = anchor.workspace.{};
const tx = await program.methods.initialize().rpc();
console.log("Your transaction signature", tx);
const val1 = await program.methods.get()
.accounts({{ dataAccount: dataAccount.publicKey }})
.view();
console.log("state", val1);
await program.methods.flip()
.accounts({{ dataAccount: dataAccount.publicKey }})
.rpc();
const val2 = await program.methods.get()
.accounts({{ dataAccount: dataAccount.publicKey }})
.view();
console.log("state", val2);
}});
}});
"#,
name,
name.to_upper_camel_case(),
)
}
pub fn jest(name: &str) -> String {
format!(
r#"const anchor = require("@coral-xyz/anchor");
describe("{}", () => {{
// Configure the client to use the local cluster.
anchor.setProvider(anchor.AnchorProvider.env());
it("Is initialized!", async () => {{
// Add your test here.
const program = anchor.workspace.{};
const tx = await program.methods.initialize().rpc();
console.log("Your transaction signature", tx);
}});
}});
"#,
name,
name.to_upper_camel_case(),
)
}
pub fn package_json(jest: bool) -> String {
if jest {
format!(
r#"{{
"scripts": {{
"lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
"lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
}},
"dependencies": {{
"@coral-xyz/anchor": "^{VERSION}"
}},
"devDependencies": {{
"jest": "^29.0.3",
"prettier": "^2.6.2"
}}
}}
"#
)
} else {
format!(
r#"{{
"scripts": {{
"lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
"lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
}},
"dependencies": {{
"@coral-xyz/anchor": "^{VERSION}"
}},
"devDependencies": {{
"chai": "^4.3.4",
"mocha": "^9.0.3",
"prettier": "^2.6.2"
}}
}}
"#
)
}
}
pub fn ts_package_json(jest: bool) -> String {
if jest {
format!(
r#"{{
"scripts": {{
"lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
"lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
}},
"dependencies": {{
"@coral-xyz/anchor": "^{VERSION}"
}},
"devDependencies": {{
"@types/bn.js": "^5.1.0",
"@types/jest": "^29.0.3",
"jest": "^29.0.3",
"prettier": "^2.6.2",
"ts-jest": "^29.0.2",
"typescript": "^4.3.5"
}}
}}
"#
)
} else {
format!(
r#"{{
"scripts": {{
"lint:fix": "prettier */*.js \"*/**/*{{.js,.ts}}\" -w",
"lint": "prettier */*.js \"*/**/*{{.js,.ts}}\" --check"
}},
"dependencies": {{
"@coral-xyz/anchor": "^{VERSION}"
}},
"devDependencies": {{
"chai": "^4.3.4",
"mocha": "^9.0.3",
"ts-mocha": "^10.0.0",
"@types/bn.js": "^5.1.0",
"@types/chai": "^4.3.0",
"@types/mocha": "^9.0.0",
"typescript": "^4.3.5",
"prettier": "^2.6.2"
}}
}}
"#
)
}
}
pub fn ts_mocha(name: &str) -> String {
format!(
r#"import * as anchor from "@coral-xyz/anchor";
import {{ Program }} from "@coral-xyz/anchor";
import {{ {} }} from "../target/types/{}";
describe("{}", () => {{
// Configure the client to use the local cluster.
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const dataAccount = anchor.web3.Keypair.generate();
const wallet = provider.wallet;
const program = anchor.workspace.{} as Program<{}>;
it("Is initialized!", async () => {{
// Add your test here.
const tx = await program.methods.new(wallet.publicKey)
.accounts({{ dataAccount: dataAccount.publicKey }})
.signers([dataAccount]).rpc();
console.log("Your transaction signature", tx);
const val1 = await program.methods.get()
.accounts({{ dataAccount: dataAccount.publicKey }})
.view();
console.log("state", val1);
await program.methods.flip()
.accounts({{ dataAccount: dataAccount.publicKey }})
.rpc();
const val2 = await program.methods.get()
.accounts({{ dataAccount: dataAccount.publicKey }})
.view();
console.log("state", val2); }});
}});
"#,
name.to_upper_camel_case(),
name.to_snake_case(),
name,
name.to_upper_camel_case(),
name.to_upper_camel_case(),
)
}
pub fn ts_jest(name: &str) -> String {
format!(
r#"import * as anchor from "@coral-xyz/anchor";
import {{ Program }} from "@coral-xyz/anchor";
import {{ {} }} from "../target/types/{}";
describe("{}", () => {{
// Configure the client to use the local cluster.
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const dataAccount = anchor.web3.Keypair.generate();
const wallet = provider.wallet;
const program = anchor.workspace.{} as Program<{}>;
it("Is initialized!", async () => {{
// Add your test here.
const tx = await program.methods.new(wallet.publicKey)
.accounts({{ dataAccount: dataAccount.publicKey }})
.signers([dataAccount]).rpc();
console.log("Your transaction signature", tx);
}});
}});
"#,
name.to_upper_camel_case(),
name.to_snake_case(),
name,
name.to_upper_camel_case(),
name.to_upper_camel_case(),
)
}
pub fn ts_config(jest: bool) -> &'static str {
if jest {
r#"{
"compilerOptions": {
"types": ["jest"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}
"#
} else {
r#"{
"compilerOptions": {
"types": ["mocha", "chai"],
"typeRoots": ["./node_modules/@types"],
"lib": ["es2015"],
"module": "commonjs",
"target": "es6",
"esModuleInterop": true
}
}
"#
}
}
pub fn git_ignore() -> &'static str {
r#"
.anchor
.DS_Store
target
**/*.rs.bk
node_modules
test-ledger
"#
}
pub fn prettier_ignore() -> &'static str {
r#"
.anchor
.DS_Store
target
node_modules
dist
build
test-ledger
"#
}
pub fn node_shell(
cluster_url: &str,
wallet_path: &str,
programs: Vec<ProgramWorkspace>,
) -> Result<String> {
let mut eval_string = format!(
r#"
const anchor = require('@coral-xyz/anchor');
const web3 = anchor.web3;
const PublicKey = anchor.web3.PublicKey;
const Keypair = anchor.web3.Keypair;
const __wallet = new anchor.Wallet(
Keypair.fromSecretKey(
Buffer.from(
JSON.parse(
require('fs').readFileSync(
"{wallet_path}",
{{
encoding: "utf-8",
}},
),
),
),
),
);
const __connection = new web3.Connection("{cluster_url}", "processed");
const provider = new anchor.AnchorProvider(__connection, __wallet, {{
commitment: "processed",
preflightcommitment: "processed",
}});
anchor.setProvider(provider);
"#
);
for program in programs {
write!(
&mut eval_string,
r#"
anchor.workspace.{} = new anchor.Program({}, new PublicKey("{}"), provider);
"#,
program.name.to_upper_camel_case(),
serde_json::to_string(&program.idl)?,
program.program_id
)?;
}
Ok(eval_string)
}