Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add macro for code metadata #914

Merged
merged 18 commits into from
Apr 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 10 additions & 10 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,17 @@ soroban-ledger-snapshot = { version = "0.7.0", path = "soroban-ledger-snapshot"
[workspace.dependencies.soroban-env-common]
version = "0.0.15"
git = "https://github.com/stellar/rs-soroban-env"
rev = "5ef87058f07ad67eeba4c350a6b6af82bd17f916"
rev = "83ab1b0e2ce7e08d33d686ed4b4ea0564b454160"

[workspace.dependencies.soroban-env-guest]
version = "0.0.15"
git = "https://github.com/stellar/rs-soroban-env"
rev = "5ef87058f07ad67eeba4c350a6b6af82bd17f916"
rev = "83ab1b0e2ce7e08d33d686ed4b4ea0564b454160"

[workspace.dependencies.soroban-env-host]
version = "0.0.15"
git = "https://github.com/stellar/rs-soroban-env"
rev = "5ef87058f07ad67eeba4c350a6b6af82bd17f916"
rev = "83ab1b0e2ce7e08d33d686ed4b4ea0564b454160"

[workspace.dependencies.stellar-strkey]
version = "0.0.7"
Expand All @@ -54,15 +54,15 @@ git = "https://github.com/stellar/rs-stellar-strkey"
[workspace.dependencies.stellar-xdr]
version = "0.0.15"
git = "https://github.com/stellar/rs-stellar-xdr"
rev = "bcf6f4c3a9dd32822a20af89880650a421d10e7f"
rev = "5e7cd5792894a3f067067a868f30a62697c1dd7b"
default-features = false

# [patch."https://github.com/stellar/rs-soroban-env"]
# soroban-env-common = { path = "../rs-soroban-env/soroban-env-common" }
# soroban-env-guest = { path = "../rs-soroban-env/soroban-env-guest" }
# soroban-env-host = { path = "../rs-soroban-env/soroban-env-host/" }
# [patch."https://github.com/stellar/rs-stellar-xdr"]
# stellar-xdr = { path = "../rs-stellar-xdr/" }
#[patch."https://github.com/stellar/rs-soroban-env"]
#soroban-env-common = { path = "../rs-soroban-env/soroban-env-common" }
#soroban-env-guest = { path = "../rs-soroban-env/soroban-env-guest" }
#soroban-env-host = { path = "../rs-soroban-env/soroban-env-host/" }
#[patch."https://github.com/stellar/rs-stellar-xdr"]
#stellar-xdr = { path = "../rs-stellar-xdr/" }

[profile.dev]
overflow-checks = true
Expand Down
70 changes: 70 additions & 0 deletions soroban-sdk-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ use syn_ext::HasFnsItem;

use soroban_spec::gen::rust::{generate_from_wasm, GenerateFromFileError};

use stellar_xdr::{ScMetaEntry, ScMetaV0, StringM, WriteXdr};

fn default_crate_path() -> Path {
parse_str("soroban_sdk").unwrap()
}
Expand Down Expand Up @@ -134,6 +136,74 @@ pub fn contractimpl(_metadata: TokenStream, input: TokenStream) -> TokenStream {
}
}

#[derive(Debug, FromMeta)]
struct MetadataArgs {
key: String,
val: String,
}

#[proc_macro]
pub fn contractmeta(metadata: TokenStream) -> TokenStream {
let args = parse_macro_input!(metadata as AttributeArgs);
let args = match MetadataArgs::from_list(&args) {
Ok(v) => v,
Err(e) => return e.write_errors().into(),
};

let gen = {
let key: StringM = match args.key.clone().try_into() {
Ok(k) => k,
Err(e) => {
return Error::new(Span::call_site(), e.to_string())
.into_compile_error()
.into()
}
};

let val: StringM = match args.val.try_into() {
Ok(k) => k,
Err(e) => {
return Error::new(Span::call_site(), e.to_string())
.into_compile_error()
.into()
}
};

let meta_v0 = ScMetaV0 { key, val };
let meta_entry = ScMetaEntry::ScMetaV0(meta_v0);
let metadata_xdr: Vec<u8> = match meta_entry.to_xdr() {
Ok(v) => v,
Err(e) => {
return Error::new(Span::call_site(), e.to_string())
.into_compile_error()
.into()
}
};

let metadata_xdr_lit = proc_macro2::Literal::byte_string(metadata_xdr.as_slice());
let metadata_xdr_len = metadata_xdr.len();

let ident = format_ident!(
"__CONTRACT_KEY_{}",
args.key
.as_bytes()
.iter()
.map(|b| format!("{b:02x}"))
.collect::<String>()
);
quote! {
#[doc(hidden)]
#[cfg_attr(target_family = "wasm", link_section = "contractmetav0")]
static #ident: [u8; #metadata_xdr_len] = *#metadata_xdr_lit;
}
};

quote! {
#gen
}
.into()
}

#[derive(Debug, FromMeta)]
struct ContractTypeArgs {
#[darling(default = "default_crate_path")]
Expand Down
4 changes: 2 additions & 2 deletions soroban-sdk/src/env.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,8 +131,8 @@ use crate::{
storage::Storage, Address, BytesN, Vec,
};
use internal::{
AddressObject, Bool, BytesObject, I128Object, I64Object, Object, StringObject, Symbol,
SymbolObject, U128Object, U32Val, U64Object, U64Val, Void,
AddressObject, Bool, BytesObject, I128Object, I256Object, I64Object, Object, StringObject,
Symbol, SymbolObject, U128Object, U256Object, U32Val, U64Object, U64Val, Void,
};

#[doc(hidden)]
Expand Down
39 changes: 39 additions & 0 deletions soroban-sdk/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,45 @@ pub use soroban_sdk_macros::contractimport;
/// ```
pub use soroban_sdk_macros::contractimpl;

/// Adds a serialized SCMetaEntry::SCMetaV0 to the WASM contracts custom section
/// under the section name 'contractmetav0'. Contract developers can use this to
/// append metadata to their contract.
///
/// ### Examples
///
/// ```
/// use soroban_sdk::{contractimpl, contractmeta, vec, BytesN, Env, Symbol, Vec};
///
/// contractmeta!(key="desc", val="hello world contract");
///
/// pub struct HelloContract;
///
/// #[contractimpl]
/// impl HelloContract {
/// pub fn hello(env: Env, to: Symbol) -> Vec<Symbol> {
/// vec![&env, Symbol::short("Hello"), to]
/// }
/// }
///
///
/// #[test]
/// fn test() {
/// # }
/// # #[cfg(feature = "testutils")]
/// # fn main() {
/// let env = Env::default();
/// let contract_id = env.register_contract(None, HelloContract);
/// let client = HelloContractClient::new(&env, &contract_id);
///
/// let words = client.hello(&Symbol::short("Dev"));
///
/// assert_eq!(words, vec![&env, Symbol::short("Hello"), Symbol::short("Dev"),]);
/// }
/// # #[cfg(not(feature = "testutils"))]
/// # fn main() { }
/// ```
pub use soroban_sdk_macros::contractmeta;

/// Generates conversions from the struct/enum from/into a `RawVal`.
///
/// There are some constraints on the types that are supported:
Expand Down
3 changes: 1 addition & 2 deletions soroban-sdk/src/tests/budget.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,10 @@ fn test_budget() {
let b = client.add();
e.budget().print();

assert_eq!(e.budget().input(CostType::MapNew), 1);
// Here the cost of 5 for `MapEntry` is broken down into
// 2 - charge for adding the two elements
// 1 - charge for binary search of map with len == 0
// 2 - charge for binary search of map with len == 1
assert_eq!(e.budget().input(CostType::MapEntry), 5);
assert_eq!(e.budget().tracker(CostType::MapEntry), (5, None));
assert_eq!(b, map![&e, (1, 10), (2, 20)]);
}
2 changes: 1 addition & 1 deletion soroban-sdk/src/tests/contractfile_with_sha256.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use crate as soroban_sdk;
pub const WASM: &[u8] = soroban_sdk::contractfile!(
file = "../target/wasm32-unknown-unknown/release/test_add_u64.wasm",
sha256 = "18788979ec03430c36f36986267f4535c7293258211fa25bc49b48c2a95f7fd8",
sha256 = "2d04e781c8d1f00f257a03d143ac91187f72630c1a67b12c07d8d789e91ee258",
);

#[test]
Expand Down
2 changes: 1 addition & 1 deletion soroban-sdk/src/tests/contractimport_with_sha256.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ mod addcontract {
use crate as soroban_sdk;
soroban_sdk::contractimport!(
file = "../target/wasm32-unknown-unknown/release/test_add_u64.wasm",
sha256 = "18788979ec03430c36f36986267f4535c7293258211fa25bc49b48c2a95f7fd8",
sha256 = "2d04e781c8d1f00f257a03d143ac91187f72630c1a67b12c07d8d789e91ee258",
);
}

Expand Down
29 changes: 15 additions & 14 deletions soroban-sdk/src/testutils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ pub trait Ledger {
}

pub mod budget {
use core::fmt::Display;
use core::fmt::{Debug, Display};

#[doc(inline)]
pub use crate::env::internal::budget::CostType;
Expand Down Expand Up @@ -68,14 +68,13 @@ pub mod budget {

impl Display for Budget {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(f, "Costs:")?;
writeln!(f, "- CPU Instructions: {}", self.cpu_instruction_cost())?;
writeln!(f, "- Memory Bytes: {}", self.memory_bytes_cost())?;
writeln!(f, "Inputs:")?;
for cost_type in CostType::variants() {
writeln!(f, "- {cost_type:?}: {}", self.input(*cost_type))?;
}
Ok(())
writeln!(f, "{}", self.0)
}
}

impl Debug for Budget {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
writeln!(f, "{:?}", self.0)
}
}

Expand All @@ -97,8 +96,8 @@ pub mod budget {
self.0.reset_limits(cpu, mem);
}

pub fn reset_inputs(&mut self) {
self.0.reset_inputs();
pub fn reset_tracker(&mut self) {
self.0.reset_tracker();
}

/// Returns the CPU instruction cost.
Expand All @@ -117,12 +116,14 @@ pub mod budget {
self.0.get_mem_bytes_count()
}

/// Get inputs that have led to the cost.
/// Get the input tracker associated with the cost. The tracker tracks
/// the cumulative (iterations, inputs). If the underlying model is a
/// constant model, then inputs is `None` and only iterations matter.
///
/// Note that VM cost types are likely to be underestimated when running
/// Rust code compared to running the WASM equivalent.
pub fn input(&self, cost_type: CostType) -> u64 {
self.0.get_input(cost_type)
pub fn tracker(&self, cost_type: CostType) -> (u64, Option<u64>) {
self.0.get_tracker(cost_type)
}

/// Print the budget costs and inputs to stdout.
Expand Down