Skip to content

Commit

Permalink
feat: add stellar_asset! macro. Resolves a token client at compile (#…
Browse files Browse the repository at this point in the history
…133)

Currently you need to hard code the contract ids of existing SAC contracts like "native". This allows this to be resolved at compile time. It does currently require that STELLAR_NETWORK be `local`, `testnet`, `futurenet` or `mainnet` and defaults to `local`.
  • Loading branch information
willemneal authored Sep 24, 2024
1 parent a73bc23 commit db2414f
Show file tree
Hide file tree
Showing 13 changed files with 299 additions and 43 deletions.
31 changes: 26 additions & 5 deletions Cargo.lock

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

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,12 @@ loam-soroban-sdk = { path = "./crates/loam-soroban-sdk" }
loam-sdk-macro = { path = "./crates/loam-sdk-macro" }
loam-subcontract-ft = { path = "./crates/loam-subcontract-ft" }

soroban-sdk = { version = "21.2.0" }
stellar-strkey = "0.0.8"

cargo_metadata = "0.18.1"
thiserror = "1.0.38"
sha2 = "0.10.7"

[profile.contracts]
inherits = "release"
Expand Down
4 changes: 3 additions & 1 deletion crates/loam-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,9 @@ clap = { version = "4.1.8", features = [
] }

cargo_metadata = { workspace = true }
sha2 = { workspace = true }


clap-cargo-extra = "0.3.0"

thiserror = "1.0.31"
Expand All @@ -52,7 +55,6 @@ ignore = "0.4"
strsim = "0.11.1"
heck = "0.5.0"
pathdiff = "0.2.1"
sha2 = "0.10.7"
hex = "0.4.3"
shlex = "1.1.0"
symlink = "0.1.0"
Expand Down
19 changes: 19 additions & 0 deletions crates/loam-cli/examples/soroban/wallet/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[package]
name = "example-wallet"
version = "0.0.0"
authors = ["Stellar Development Foundation <[email protected]>"]
license = "Apache-2.0"
edition = "2021"
publish = false

[lib]
crate-type = ["cdylib"]
doctest = false

[dependencies]
loam-sdk = { workspace = true, features = ["loam-soroban-sdk"] }
loam-subcontract-core = { workspace = true }


[dev_dependencies]
loam-sdk = { workspace = true, features = ["soroban-sdk-testutils"] }
9 changes: 9 additions & 0 deletions crates/loam-cli/examples/soroban/wallet/src/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
use loam_sdk::soroban_sdk::{self, contracterror};

#[contracterror]
#[derive(Copy, Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
#[repr(u32)]
pub enum Error {
/// The operation results in an integer overflow
Overflow = 1,
}
11 changes: 11 additions & 0 deletions crates/loam-cli/examples/soroban/wallet/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#![no_std]
use loam_subcontract_core::{admin::Admin, Core};

pub mod error;
pub mod subcontract;

pub use error::Error;
use subcontract::{Calculator, Token};

#[loam_sdk::derive_contract(Core(Admin), Token(Calculator))]
pub struct Contract;
36 changes: 36 additions & 0 deletions crates/loam-cli/examples/soroban/wallet/src/subcontract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
use loam_sdk::{
soroban_sdk::{env, token, Address, Lazy},
stellar_asset, subcontract,
};

#[derive(Lazy, Default)]
pub struct Calculator;

#[subcontract]
pub trait IsToken {
/// Get contract's xlm balance
#[allow(clippy::missing_errors_doc)]
fn balance(&self) -> i128;

/// Puts two into into a vector
fn transfer(&self, to: loam_sdk::soroban_sdk::Address, amount: i128);

fn addr(&self) -> loam_sdk::soroban_sdk::Address;
}

fn native() -> token::Client<'static> {
stellar_asset!("native")
}

impl IsToken for Calculator {
fn balance(&self) -> i128 {
native().balance(&env().current_contract_address())
}

fn transfer(&self, to: Address, amount: i128) {
native().transfer(&env().current_contract_address(), &to, &amount);
}
fn addr(&self) -> loam_sdk::soroban_sdk::Address {
native().address
}
}
2 changes: 1 addition & 1 deletion crates/loam-cli/tests/it/build_clients/init_script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ STELLAR_ACCOUNT=bob mint --amount 2000000 --to bob
assert!(String::from_utf8_lossy(&output.stderr).contains(
"✅ Initialization script for \"soroban_token_contract\" completed successfully"
));
})
});
}

#[test]
Expand Down
8 changes: 7 additions & 1 deletion crates/loam-sdk-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ proc-macro = true

[dependencies]
loam-build = { path = "../loam-build", version = "0.7.3" }

soroban-sdk = { workspace = true, features = ["testutils"] }
stellar-strkey = { workspace = true }

sha2 = { workspace = true }
proc-macro2 = "1.0"
syn = { version = "2", features = ["full", "fold", "extra-traits", "visit"] }
quote = "1.0"
Expand All @@ -20,7 +25,8 @@ thiserror = { workspace = true }
syn-file-expand = "0.3.0"
cargo_metadata = { workspace = true }
darling = "0.20.8"
itertools = "0.12.1"
itertools = "0.13.0"
regex = "1.10.5"

[dev-dependencies]
assert_fs = "1.0.13"
Expand Down
17 changes: 17 additions & 0 deletions crates/loam-sdk-macro/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,20 @@ pub fn derive_contract(args: TokenStream, item: TokenStream) -> TokenStream {
let parsed: Item = syn::parse(item.clone()).expect("failed to parse Item");
derive_contract_impl(proc_macro2::TokenStream::from(args), parsed).into()
}

/// Generates a contract Client for a given asset.
/// It is expected that the name of an asset, e.g. "native" or "USDC:G1...."
///
/// # Panics
///
#[proc_macro]
pub fn stellar_asset(input: TokenStream) -> TokenStream {
// Parse the input as a string literal
let input_str = syn::parse_macro_input!(input as syn::LitStr);
let network = std::env::var("STELLAR_NETWORK").unwrap_or_else(|_| "local".to_owned());
let asset = util::parse_asset_literal(&input_str, &network);

// Return the generated code as a TokenStream
asset.into()
}
35 changes: 1 addition & 34 deletions crates/loam-sdk-macro/src/subcontract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -250,37 +250,8 @@ fn group_to_ident(g: &Group) -> Ident {
#[cfg(test)]
mod tests {

use std::io::{Read, Write};

/// Format the given snippet. The snippet is expected to be *complete* code.
/// When we cannot parse the given snippet, this function returns `None`.
fn format_snippet(snippet: &str) -> String {
let mut child = std::process::Command::new("rustfmt")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.spawn()
.unwrap();
child
.stdin
.as_mut()
.unwrap()
.write_all(snippet.as_bytes())
.map_err(p_e)
.unwrap();
child.wait().unwrap();
let mut buf = String::new();
child.stdout.unwrap().read_to_string(&mut buf).unwrap();
println!("\n\n\n{buf}\n\n\n");
buf
}
use super::*;

fn equal_tokens(expected: &TokenStream, actual: &TokenStream) {
assert_eq!(
format_snippet(&expected.to_string()),
format_snippet(&actual.to_string())
);
}
use crate::util::*;

#[test]
fn first() {
Expand Down Expand Up @@ -375,8 +346,4 @@ mod tests {
// let impl_ = syn::parse_str::<ItemImpl>(result.as_str()).unwrap();
// println!("{impl_:#?}");
}
fn p_e(e: std::io::Error) -> std::io::Error {
eprintln!("{e:#?}");
e
}
}
Loading

0 comments on commit db2414f

Please sign in to comment.