diff --git a/Cargo.lock b/Cargo.lock
index e3916c5f9d..ac115d89a7 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -727,6 +727,12 @@ version = "3.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
+[[package]]
+name = "byteorder"
+version = "0.5.3"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "0fc10e8cc6b2580fda3f36eb6dc5316657f812a3df879a44a66fc9f0fdbc4855"
+
[[package]]
name = "byteorder"
version = "1.5.0"
@@ -1084,7 +1090,7 @@ version = "3.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b9fdf9972b2bd6af2d913799d9ebc165ea4d2e65878e329d9c6b372c4491b61"
dependencies = [
- "byteorder",
+ "byteorder 1.5.0",
"digest 0.9.0",
"rand_core 0.5.1",
"subtle",
@@ -2692,6 +2698,12 @@ version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe"
+[[package]]
+name = "leb128"
+version = "0.2.5"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "884e2677b40cc8c339eaefcb701c32ef1fd2493d71118dc0ca4b6a736c93bd67"
+
[[package]]
name = "ledger-apdu"
version = "0.10.0"
@@ -2719,7 +2731,7 @@ version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "45ba81a1f5f24396b37211478aff7fbcd605dd4544df8dbed07b9da3c2057aee"
dependencies = [
- "byteorder",
+ "byteorder 1.5.0",
"cfg-if",
"hex",
"hidapi",
@@ -4507,6 +4519,7 @@ dependencies = [
"ulid",
"url",
"walkdir",
+ "wasm-gen",
"wasm-opt",
"wasmparser 0.90.0",
"which",
@@ -4674,9 +4687,9 @@ version = "21.5.0"
[[package]]
name = "soroban-ledger-snapshot"
-version = "21.7.3"
+version = "21.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "84589856911dfd6731695c9b51c858aed6d4540118c0a1e5c4c858ea13bc744c"
+checksum = "9cf596b2083946a95914a55d7d29cee6a8095b515fd06211851f45bf6af5a496"
dependencies = [
"serde",
"serde_json",
@@ -4715,8 +4728,8 @@ dependencies = [
"serde_json",
"soroban-env-guest 21.2.1",
"soroban-env-host 21.2.1",
- "soroban-ledger-snapshot 21.7.3",
- "soroban-sdk-macros 21.7.3",
+ "soroban-ledger-snapshot 21.7.2",
+ "soroban-sdk-macros 21.7.2",
"stellar-strkey 0.0.8",
]
@@ -4739,9 +4752,9 @@ dependencies = [
[[package]]
name = "soroban-sdk-macros"
-version = "21.7.3"
+version = "21.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "63c2173f1aacd56b4405eed71cb2a9694dff99d51ba72d4f0cbc5e4961fdabf4"
+checksum = "da03fa00b8ca6e392f013359c06d790d2d379f9c8d6f8a6dfe563ec64311e5d3"
dependencies = [
"crate-git-revision 0.0.6",
"darling",
@@ -4751,8 +4764,8 @@ dependencies = [
"rustc_version",
"sha2 0.10.8",
"soroban-env-common 21.2.1",
- "soroban-spec 21.7.3",
- "soroban-spec-rust 21.7.3",
+ "soroban-spec 21.7.2",
+ "soroban-spec-rust 21.7.2",
"stellar-xdr 21.2.0",
"syn 2.0.77",
]
@@ -4779,9 +4792,9 @@ dependencies = [
[[package]]
name = "soroban-spec"
-version = "21.7.3"
+version = "21.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7705bffbcc747c08e81698b87b4a787f8b268c25d88f777160091dc1ee8121cb"
+checksum = "64c723195463d8742bcb481520bd8b8325da66c39ea236ad46261e6af992e8a8"
dependencies = [
"base64 0.13.1",
"stellar-xdr 21.2.0",
@@ -4817,15 +4830,15 @@ dependencies = [
[[package]]
name = "soroban-spec-rust"
-version = "21.7.3"
+version = "21.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "48207ebc8616c2804a17203d1d86c53c3d3c804b682cbab011a135893db1cf78"
+checksum = "c0f1b0ec2af54e38f138910f09e101b100130efe625f69ece51c76dd4f06f8b2"
dependencies = [
"prettyplease",
"proc-macro2",
"quote",
"sha2 0.10.8",
- "soroban-spec 21.7.3",
+ "soroban-spec 21.7.2",
"stellar-xdr 21.2.0",
"syn 2.0.77",
"thiserror",
@@ -4948,7 +4961,7 @@ dependencies = [
"smallvec",
"spin",
"wasmi_collections",
- "wasmi_core 0.36.3",
+ "wasmi_core 0.36.5",
"wasmparser-nostd",
]
@@ -4991,7 +5004,7 @@ version = "21.5.0"
dependencies = [
"async-trait",
"bollard",
- "byteorder",
+ "byteorder 1.5.0",
"ed25519-dalek 2.1.1",
"env_logger",
"futures",
@@ -6014,6 +6027,16 @@ version = "0.2.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
+[[package]]
+name = "wasm-gen"
+version = "0.1.4"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "b854b1461005a7b3365742310f7faa3cac3add809d66928c64a40c7e9e842ebb"
+dependencies = [
+ "byteorder 0.5.3",
+ "leb128",
+]
+
[[package]]
name = "wasm-opt"
version = "0.114.2"
@@ -6056,9 +6079,9 @@ dependencies = [
[[package]]
name = "wasm-streams"
-version = "0.4.1"
+version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e072d4e72f700fb3443d8fe94a39315df013eef1104903cdb0a2abd322bbecd"
+checksum = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129"
dependencies = [
"futures-util",
"js-sys",
@@ -6075,9 +6098,9 @@ checksum = "104a7f73be44570cac297b3035d76b169d6599637631cf37a1703326a0727073"
[[package]]
name = "wasmi_collections"
-version = "0.36.3"
+version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a1ee1cf2328e7fbb8654fda8449395c64c36ef5a30511e79fae0265a96e1a446"
+checksum = "8eddc10bfb0069e913399ebd66c5a72c7d9aceabddcaa0296f062a55ab61d404"
dependencies = [
"ahash",
"hashbrown 0.14.5",
@@ -6098,9 +6121,9 @@ dependencies = [
[[package]]
name = "wasmi_core"
-version = "0.36.3"
+version = "0.36.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4e77c1e701d21edfd263e5c6c940861202c6b840c715040cfdca6211bf8857aa"
+checksum = "f08b12621457c17cfd5349cce25029eeac3769b63b1b02bd850d595a00f375ff"
dependencies = [
"downcast-rs",
"libm",
@@ -6442,7 +6465,7 @@ version = "0.7.35"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0"
dependencies = [
- "byteorder",
+ "byteorder 1.5.0",
"zerocopy-derive",
]
diff --git a/FULL_HELP_DOCS.md b/FULL_HELP_DOCS.md
index b3724b98b2..8c151cb849 100644
--- a/FULL_HELP_DOCS.md
+++ b/FULL_HELP_DOCS.md
@@ -334,6 +334,7 @@ To view the commands that will be executed, without executing them, use the --pr
If ommitted, wasm files are written only to the cargo target directory.
* `--print-commands-only` — Print commands to build without executing them
+* `--meta ` — Add key-value to contract meta (adds the meta to the `contractmetav0` custom section)
diff --git a/cmd/crates/soroban-spec-tools/src/contract.rs b/cmd/crates/soroban-spec-tools/src/contract.rs
index 9984a19d1f..501609111a 100644
--- a/cmd/crates/soroban-spec-tools/src/contract.rs
+++ b/cmd/crates/soroban-spec-tools/src/contract.rs
@@ -40,9 +40,9 @@ pub enum Error {
impl Spec {
pub fn new(bytes: &[u8]) -> Result {
- let mut env_meta: Option<&[u8]> = None;
- let mut meta: Option<&[u8]> = None;
- let mut spec: Option<&[u8]> = None;
+ let mut env_meta: Option> = None;
+ let mut meta: Option> = None;
+ let mut spec: Option> = None;
for payload in wasmparser::Parser::new(0).parse_all(bytes) {
let payload = payload?;
if let wasmparser::Payload::CustomSection(section) = payload {
@@ -52,13 +52,19 @@ impl Spec {
"contractspecv0" => &mut spec,
_ => continue,
};
- *out = Some(section.data());
+
+ if let Some(existing_data) = out {
+ let combined_data = [existing_data, section.data()].concat();
+ *out = Some(combined_data);
+ } else {
+ *out = Some(section.data().to_vec());
+ }
};
}
let mut env_meta_base64 = None;
let env_meta = if let Some(env_meta) = env_meta {
- env_meta_base64 = Some(base64.encode(env_meta));
+ env_meta_base64 = Some(base64.encode(&env_meta));
let cursor = Cursor::new(env_meta);
let mut read = Limited::new(cursor, Limits::none());
ScEnvMetaEntry::read_xdr_iter(&mut read).collect::, xdr::Error>>()?
@@ -68,7 +74,7 @@ impl Spec {
let mut meta_base64 = None;
let meta = if let Some(meta) = meta {
- meta_base64 = Some(base64.encode(meta));
+ meta_base64 = Some(base64.encode(&meta));
let cursor = Cursor::new(meta);
let mut depth_limit_read = Limited::new(cursor, Limits::none());
ScMetaEntry::read_xdr_iter(&mut depth_limit_read)
@@ -78,7 +84,7 @@ impl Spec {
};
let (spec_base64, spec) = if let Some(spec) = spec {
- let (spec_base64, spec) = Spec::spec_to_base64(spec)?;
+ let (spec_base64, spec) = Spec::spec_to_base64(&spec)?;
(Some(spec_base64), spec)
} else {
(None, vec![])
diff --git a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/src/lib.rs b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/src/lib.rs
index 1552f58554..d04162b5af 100644
--- a/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/src/lib.rs
+++ b/cmd/crates/soroban-test/tests/fixtures/workspace/contracts/add/src/lib.rs
@@ -1,9 +1,11 @@
#![no_std]
-use soroban_sdk::{contract, contractimpl};
+use soroban_sdk::{contract, contractimpl, contractmeta};
#[contract]
pub struct Contract;
+contractmeta!(key = "Description", val = "A test add contract");
+
#[contractimpl]
impl Contract {
pub fn add(x: u64, y: u64) -> u128 {
diff --git a/cmd/crates/soroban-test/tests/it/build.rs b/cmd/crates/soroban-test/tests/it/build.rs
index aba6aadf57..925d88df1c 100644
--- a/cmd/crates/soroban-test/tests/it/build.rs
+++ b/cmd/crates/soroban-test/tests/it/build.rs
@@ -116,3 +116,37 @@ cargo rustc --manifest-path=contracts/add/Cargo.toml --crate-type=cdylib --targe
",
));
}
+
+#[test]
+fn build_with_metadata() {
+ let sandbox = TestEnv::default();
+ let cargo_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
+ let fixture_path = cargo_dir.join("tests/fixtures/workspace/contracts/add");
+ let outdir = sandbox.dir().join("out");
+
+ sandbox
+ .new_assert_cmd("contract")
+ .current_dir(&fixture_path)
+ .arg("build")
+ .arg("--meta")
+ .arg("contract meta=added on build")
+ .arg("--out-dir")
+ .arg(&outdir)
+ .assert()
+ .success();
+
+ // verify that the metadata added in the contract code via contractmetadata! macro is present
+ // as well as the meta that is included on build
+ let wasm_path = fixture_path.join(&outdir).join("add.wasm");
+ sandbox
+ .new_assert_cmd("contract")
+ .current_dir(&fixture_path)
+ .arg("info")
+ .arg("meta")
+ .arg("--wasm")
+ .arg(wasm_path)
+ .assert()
+ .success()
+ .stdout(predicate::str::contains("Description: A test add contract"))
+ .stdout(predicate::str::contains("contract meta: added on build"));
+}
diff --git a/cmd/soroban-cli/Cargo.toml b/cmd/soroban-cli/Cargo.toml
index 0cc12e74f9..78c3974f3b 100644
--- a/cmd/soroban-cli/Cargo.toml
+++ b/cmd/soroban-cli/Cargo.toml
@@ -124,6 +124,7 @@ glob = "0.3.1"
fqdn = "0.3.12"
open = "5.3.0"
url = "2.5.2"
+wasm-gen = "0.1.4"
[build-dependencies]
crate-git-revision = "0.0.4"
diff --git a/cmd/soroban-cli/src/commands/contract/build.rs b/cmd/soroban-cli/src/commands/contract/build.rs
index c71327a386..01ccce3115 100644
--- a/cmd/soroban-cli/src/commands/contract/build.rs
+++ b/cmd/soroban-cli/src/commands/contract/build.rs
@@ -1,3 +1,4 @@
+use cargo_metadata::{Metadata, MetadataCommand, Package};
use clap::Parser;
use itertools::Itertools;
use std::{
@@ -9,8 +10,7 @@ use std::{
path::{self, Path, PathBuf},
process::{Command, ExitStatus, Stdio},
};
-
-use cargo_metadata::{Metadata, MetadataCommand, Package};
+use stellar_xdr::curr::{Limits, ScMetaEntry, ScMetaV0, StringM, WriteXdr};
/// Build a contract from source
///
@@ -62,6 +62,20 @@ pub struct Cmd {
/// Print commands to build without executing them
#[arg(long, conflicts_with = "out_dir", help_heading = "Other")]
pub print_commands_only: bool,
+ /// Add key-value to contract meta (adds the meta to the `contractmetav0` custom section)
+ #[arg(long, num_args=1, value_parser=parse_meta_arg, action=clap::ArgAction::Append, help_heading = "Metadata")]
+ pub meta: Vec<(String, String)>,
+}
+
+fn parse_meta_arg(s: &str) -> Result<(String, String), Error> {
+ let parts = s.splitn(2, '=');
+
+ let (key, value) = parts
+ .map(str::trim)
+ .next_tuple()
+ .ok_or_else(|| Error::MetaArg("must be in the form 'key=value'".to_string()))?;
+
+ Ok((key.to_string(), value.to_string()))
}
#[derive(thiserror::Error, Debug)]
@@ -82,8 +96,17 @@ pub enum Error {
CopyingWasmFile(io::Error),
#[error("getting the current directory: {0}")]
GettingCurrentDir(io::Error),
+ #[error("reading wasm file: {0}")]
+ ReadingWasmFile(io::Error),
+ #[error("writing wasm file: {0}")]
+ WritingWasmFile(io::Error),
+ #[error("invalid meta entry: {0}")]
+ MetaArg(String),
}
+const WASM_TARGET: &str = "wasm32-unknown-unknown";
+const META_CUSTOM_SECTION_NAME: &str = "contractmetav0";
+
impl Cmd {
pub fn run(&self) -> Result<(), Error> {
let working_dir = env::current_dir().map_err(Error::GettingCurrentDir)?;
@@ -111,7 +134,7 @@ impl Cmd {
manifest_path.to_string_lossy()
));
cmd.arg("--crate-type=cdylib");
- cmd.arg("--target=wasm32-unknown-unknown");
+ cmd.arg(format!("--target={WASM_TARGET}"));
if self.profile == "release" {
cmd.arg("--release");
} else {
@@ -145,14 +168,16 @@ impl Cmd {
return Err(Error::Exit(status));
}
+ let file = format!("{}.wasm", p.name.replace('-', "_"));
+ let target_file_path = Path::new(target_dir)
+ .join(WASM_TARGET)
+ .join(&self.profile)
+ .join(&file);
+
+ self.handle_contract_metadata_args(&target_file_path)?;
+
if let Some(out_dir) = &self.out_dir {
fs::create_dir_all(out_dir).map_err(Error::CreatingOutDir)?;
-
- let file = format!("{}.wasm", p.name.replace('-', "_"));
- let target_file_path = Path::new(target_dir)
- .join("wasm32-unknown-unknown")
- .join(&self.profile)
- .join(&file);
let out_file_path = Path::new(out_dir).join(&file);
fs::copy(target_file_path, out_file_path).map_err(Error::CopyingWasmFile)?;
}
@@ -228,4 +253,32 @@ impl Cmd {
// the output.
cmd.exec()
}
+
+ fn handle_contract_metadata_args(&self, target_file_path: &PathBuf) -> Result<(), Error> {
+ if self.meta.is_empty() {
+ return Ok(());
+ }
+
+ let mut wasm_bytes = fs::read(target_file_path).map_err(Error::ReadingWasmFile)?;
+
+ for (k, v) in self.meta.clone() {
+ let key: StringM = k
+ .clone()
+ .try_into()
+ .map_err(|e| Error::MetaArg(format!("{k} is an invalid metadata key: {e}")))?;
+
+ let val: StringM = v
+ .clone()
+ .try_into()
+ .map_err(|e| Error::MetaArg(format!("{v} is an invalid metadata value: {e}")))?;
+ let meta_entry = ScMetaEntry::ScMetaV0(ScMetaV0 { key, val });
+ let xdr: Vec = meta_entry
+ .to_xdr(Limits::none())
+ .map_err(|e| Error::MetaArg(format!("failed to encode metadata entry: {e}")))?;
+
+ wasm_gen::write_custom_section(&mut wasm_bytes, META_CUSTOM_SECTION_NAME, &xdr);
+ }
+
+ fs::write(target_file_path, wasm_bytes).map_err(Error::WritingWasmFile)
+ }
}