From 4f2e0628deac36079de759c2b187334934d1865e Mon Sep 17 00:00:00 2001 From: marcus Date: Wed, 23 Apr 2025 20:46:52 -0400 Subject: [PATCH 1/4] gblend init foundry template --- .gitignore | 3 +- src/commands/legacy_init.rs | 139 +++++++++++++++++- src/commands/rust/init.rs | 4 +- templates/blendedAppFoundry/.gitmodules | 3 + templates/blendedAppFoundry/README.md | 101 +++++++++++++ .../blendedAppFoundry/javascript/.gitignore | 2 + .../blendedAppFoundry/javascript/package.json | 5 + .../blendedAppFoundry/javascript/rust.js | 49 ++++++ .../blendedAppFoundry/javascript/solidity.js | 52 +++++++ templates/blendedAppFoundry/rust/.gitignore | 3 + templates/blendedAppFoundry/rust/Cargo.toml | 21 +++ templates/blendedAppFoundry/rust/Makefile | 7 + templates/blendedAppFoundry/rust/lib.rs | 92 ++++++++++++ .../blendedAppFoundry/rust/rust-toolchain | 1 + .../solidity/.github/workflows/test.yml | 43 ++++++ .../blendedAppFoundry/solidity/.gitignore | 14 ++ .../blendedAppFoundry/solidity/README.md | 66 +++++++++ .../blendedAppFoundry/solidity/foundry.toml | 6 + .../solidity/src/FluentSdkRustTypesTest.sol | 58 ++++++++ .../FluentSdkRustTypesTest.txt | 1 + .../test/FluentSdkRustTypesTest.t.sol | 60 ++++++++ 21 files changed, 720 insertions(+), 10 deletions(-) create mode 100644 templates/blendedAppFoundry/.gitmodules create mode 100644 templates/blendedAppFoundry/README.md create mode 100644 templates/blendedAppFoundry/javascript/.gitignore create mode 100644 templates/blendedAppFoundry/javascript/package.json create mode 100644 templates/blendedAppFoundry/javascript/rust.js create mode 100644 templates/blendedAppFoundry/javascript/solidity.js create mode 100644 templates/blendedAppFoundry/rust/.gitignore create mode 100644 templates/blendedAppFoundry/rust/Cargo.toml create mode 100644 templates/blendedAppFoundry/rust/Makefile create mode 100644 templates/blendedAppFoundry/rust/lib.rs create mode 100644 templates/blendedAppFoundry/rust/rust-toolchain create mode 100644 templates/blendedAppFoundry/solidity/.github/workflows/test.yml create mode 100644 templates/blendedAppFoundry/solidity/.gitignore create mode 100644 templates/blendedAppFoundry/solidity/README.md create mode 100644 templates/blendedAppFoundry/solidity/foundry.toml create mode 100644 templates/blendedAppFoundry/solidity/src/FluentSdkRustTypesTest.sol create mode 100644 templates/blendedAppFoundry/solidity/src/deployConstructor/FluentSdkRustTypesTest.txt create mode 100644 templates/blendedAppFoundry/solidity/test/FluentSdkRustTypesTest.t.sol diff --git a/.gitignore b/.gitignore index c215885..76b41c9 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ /target /testing /tmp -/.idea \ No newline at end of file +/.idea +.DS_Store \ No newline at end of file diff --git a/src/commands/legacy_init.rs b/src/commands/legacy_init.rs index 5832031..02385f0 100644 --- a/src/commands/legacy_init.rs +++ b/src/commands/legacy_init.rs @@ -27,7 +27,8 @@ pub async fn legacy_init() -> Result<()> { "Hardhat JavaScript (Solidity & Vyper)", "Hardhat TypeScript (Solidity & Vyper)", "Rust", - "Blendedapp 🔄", + "Blendedapp Hardhat 🔄", + "Blendedapp Foundry 🔄", "Exit", ]; let selection = Select::new() @@ -41,8 +42,9 @@ pub async fn legacy_init() -> Result<()> { 0 => spin_js(use_erc20)?, 1 => spin_ts(use_erc20)?, 2 => spin_rust()?, - 3 => spin_blended_app()?, - 4 => { + 3 => spin_blended_app_hardhat()?, + 4 => spin_blended_app_foundry()?, + 5 => { println!("Exiting program."); return Ok(()); // Exit the program gracefully } @@ -243,8 +245,8 @@ fn spin_ts(use_erc20: bool) -> Result<()> { Ok(()) } -fn spin_blended_app() -> Result<()> { - println!("Creating blended app ..."); +fn spin_blended_app_hardhat() -> Result<()> { + println!("Creating blended app with Hardhat ..."); // Embed the files in the binary using `include_str!` const HARDHAT_CONFIG: &str = include_str!(concat!( @@ -328,7 +330,132 @@ fn spin_blended_app() -> Result<()> { create_file_with_content(".env", ENV)?; create_file_with_content(".gitignore", GIT_IGNORE)?; - println!("Blended app created successfully!"); + println!("Blended app with Hardhat created successfully!"); + + Ok(()) +} + +fn spin_blended_app_foundry() -> Result<()> { + println!("Creating blended app with Foundry..."); + + // Embed the files in the binary using `include_str!` + const JAVASCRIPT_GIT_IGNORE: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/javascript/.gitignore" + )); + const JAVASCRIPT_PACKAGE_JSON: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/javascript/package.json" + )); + const JAVASCRIPT_SOLIDITY: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/javascript/solidity.js" + )); + const JAVASCRIPT_RUST: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/javascript/rust.js" + )); + + const RUST_GIT_IGNORE: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/rust/.gitignore" + )); + const RUST_CARGO_TOML: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/rust/Cargo.toml" + )); + const RUST_MAKEFILE: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/rust/Makefile" + )); + const RUST_LIB_RS: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/rust/lib.rs" + )); + const RUST_TOOLCHAIN: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/rust/rust-toolchain" + )); + + const SOLIDITY_GIT_IGNORE: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/solidity/.gitignore" + )); + const SOLIDITY_README: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/solidity/README.md" + )); + const SOLIDITY_FOUNDRY_TOML: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/solidity/foundry.toml" + )); + + const SOLIDITY_GIT_WORKFLOW_TEST: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/solidity/.github/workflows/test.yml" + )); + const SOLIDITY_SOURCE_CONTRACT: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/solidity/src/FluentSdkRustTypesTest.sol" + )); + const SOLIDITY_SOURCE_CONSTRUCTOR: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/solidity/src/deployConstructor/FluentSdkRustTypesTest.txt" + )); + const SOLIDITY_TEST_CONTRACT: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/solidity/test/FluentSdkRustTypesTest.t.sol" + )); + + const README: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/README.md" + )); + const GIT_MODULES: &str = include_str!(concat!( + env!("CARGO_MANIFEST_DIR"), + "/templates/blendedAppFoundry/.gitmodules" + )); + + // Create necessary directories and write files + create_directories("javascript")?; + create_directories("rust")?; + create_directories("solidity")?; + + create_file_with_content("javascript/.gitignore", JAVASCRIPT_GIT_IGNORE)?; + create_file_with_content("javascript/package.json", JAVASCRIPT_PACKAGE_JSON)?; + create_file_with_content("javascript/solidity.js", JAVASCRIPT_SOLIDITY)?; + create_file_with_content("javascript/rust.js", JAVASCRIPT_RUST)?; + + create_file_with_content("rust/.gitignore", RUST_GIT_IGNORE)?; + create_file_with_content("rust/Cargo.toml", RUST_CARGO_TOML)?; + create_file_with_content("rust/Makefile", RUST_MAKEFILE)?; + create_file_with_content("rust/lib.rs", RUST_LIB_RS)?; + create_file_with_content("rust/rust-toolchain", RUST_TOOLCHAIN)?; + + create_file_with_content("solidity/.gitignore", SOLIDITY_GIT_IGNORE)?; + create_file_with_content("solidity/README.md", SOLIDITY_README)?; + create_file_with_content("solidity/foundry.toml", SOLIDITY_FOUNDRY_TOML)?; + create_file_with_content( + "solidity/.github/workflows/test.yml", + SOLIDITY_GIT_WORKFLOW_TEST, + )?; + create_file_with_content( + "solidity/src/FluentSdkRustTypesTest.sol", + SOLIDITY_SOURCE_CONTRACT, + )?; + create_file_with_content( + "solidity/src/deployConstructor/FluentSdkRustTypesTest.txt", + SOLIDITY_SOURCE_CONSTRUCTOR, + )?; + create_file_with_content( + "solidity/test/FluentSdkRustTypesTest.t.sol", + SOLIDITY_TEST_CONTRACT, + )?; + + create_file_with_content("README.md", README)?; + create_file_with_content(".gitmodules", GIT_MODULES)?; + + println!("Blended app with Foundry created successfully!"); Ok(()) } diff --git a/src/commands/rust/init.rs b/src/commands/rust/init.rs index 9191836..f839c87 100644 --- a/src/commands/rust/init.rs +++ b/src/commands/rust/init.rs @@ -1,8 +1,6 @@ use super::{ constants::{ - BASIC_TEMPLATE_CARGO_TOML, - BASIC_TEMPLATE_LIB_RS, - BASIC_TEMPLATE_MAKEFILE, + BASIC_TEMPLATE_CARGO_TOML, BASIC_TEMPLATE_LIB_RS, BASIC_TEMPLATE_MAKEFILE, BASIC_TEMPLATE_RUST_TOOLCHAIN, }, template_manager::TemplateManager, diff --git a/templates/blendedAppFoundry/.gitmodules b/templates/blendedAppFoundry/.gitmodules new file mode 100644 index 0000000..e96f58e --- /dev/null +++ b/templates/blendedAppFoundry/.gitmodules @@ -0,0 +1,3 @@ +[submodule "solidity/lib/forge-std"] + path = solidity/lib/forge-std + url = https://github.com/foundry-rs/forge-std \ No newline at end of file diff --git a/templates/blendedAppFoundry/README.md b/templates/blendedAppFoundry/README.md new file mode 100644 index 0000000..2c2a7a8 --- /dev/null +++ b/templates/blendedAppFoundry/README.md @@ -0,0 +1,101 @@ +# blended-sdk + +Blended App template SDK for Solidity and Rust WASM contracts with Javascript ethers.js test scripts. + +## Documentation + +https://docs.fluent.xyz/developer-guides/building-a-blended-app/ + +## Deploy Blended Contracts + +### Step 1 - Deploy Rust Contract + +Change directory to `rust` contract folder + +```shell +cd rust +``` + +Compile Rust contract for WASM binary + +```shell +gblend build rust -r +``` + +Deploy Rust contract with WASM binary + +```shell +gblend deploy \ +--private-key $devTestnetPrivateKey \ +--dev lib.wasm \ +--gas-limit 3000000 +``` + +Copy this deployed Rust contract address, +since this will be used for the Solidity contract communication. + +⚠️ Note: to update Rust crate `fluentbase-sdk` if there are issues: ⚠️ + +```shell +cargo clean +cargo update -p fluentbase-sdk +``` + +### Step 2 - Deploy Solidity Contract + +Switch back to the root of this repo, then switch to the `solidity` contract folder + +```shell +cd ../ +cd solidity +``` + +Deploy the Solidity contract with the Rust contract address +with the Forge flag which defines path to constructor input text file `--constructor-args-path` + +```shell +forge create src/FluentSdkRustTypesTest.sol:FluentSdkRustTypesTest \ +--constructor-args-path src/deployConstructor/FluentSdkRustTypesTest.txt \ +--private-key $devTestnetPrivateKey \ +--rpc-url https://rpc.dev.gblend.xyz/ \ +--broadcast \ +--verify \ +--verifier blockscout \ +--verifier-url https://blockscout.dev.gblend.xyz/api/ +``` + +Foundry test fork deployed contracts on Fluent testnet + +```shell +forge coverage --fork-url https://rpc.dev.gblend.xyz/ +``` + +### Step 3 - Test Javascript ethers.js Interaction + +Switch back to the root of this repo, then switch to the `javascript` folder + +```shell +cd ../ +cd javascript +``` + +Install packages such as ethers.js with `package.json` with + +```shell +npm i +``` + +Run the ethers.js test script to have the Solidity contract call the Rust contract + +```shell +node solidity.js +``` + +Run the ethers.js test script to call the Rust contract directly (using Solidity interface ABI) + +```shell +node rust.js +``` + +This ethers.js Javascript example can be +modified from node.js to a frontend application for users to interact with. diff --git a/templates/blendedAppFoundry/javascript/.gitignore b/templates/blendedAppFoundry/javascript/.gitignore new file mode 100644 index 0000000..25c8fdb --- /dev/null +++ b/templates/blendedAppFoundry/javascript/.gitignore @@ -0,0 +1,2 @@ +node_modules +package-lock.json \ No newline at end of file diff --git a/templates/blendedAppFoundry/javascript/package.json b/templates/blendedAppFoundry/javascript/package.json new file mode 100644 index 0000000..0629c60 --- /dev/null +++ b/templates/blendedAppFoundry/javascript/package.json @@ -0,0 +1,5 @@ +{ + "dependencies": { + "ethers": "^5.7.2" + } +} diff --git a/templates/blendedAppFoundry/javascript/rust.js b/templates/blendedAppFoundry/javascript/rust.js new file mode 100644 index 0000000..5a40815 --- /dev/null +++ b/templates/blendedAppFoundry/javascript/rust.js @@ -0,0 +1,49 @@ +const ethers = require("ethers") // npm i ethers@5.7.2 https://github.com/smartcontractkit/full-blockchain-solidity-course-js/discussions/5139#discussioncomment-5444517 + +const rpcURL = "https://rpc.dev.gblend.xyz/" // Your RPC URL goes here + +const provider = new ethers.providers.JsonRpcProvider(rpcURL) + +const contractAddress = '0x04160C19738bB6429c0554fBdC11A96079D7297D' +const contractABI = [{"inputs":[],"name":"rustAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rustBool","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rustBytes","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rustBytes32","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rustInt256","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rustString","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"rustUint256","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] + +const contractDeployed = new ethers.Contract(contractAddress, contractABI, provider); + +let fluent_sepolia_chain_id = 20993; + +testRustContractRead() + +async function testRustContractRead() { + + const connectedNetworkObject = await provider.getNetwork(); + const chainIdConnected = connectedNetworkObject.chainId; + console.log("chainIdConnected: " + chainIdConnected) + + if(chainIdConnected != fluent_sepolia_chain_id){ + console.log("RPC endpoint not connected to Fluent Sepolia (chainId: " + fluent_sepolia_chain_id + ")."); + console.log("Switch to Fluent Sepolia then try again."); + return; + } + + const rustString = await contractDeployed.rustString() + console.log("rustString: " + rustString) + + const rustUint256 = await contractDeployed.rustUint256() + console.log("rustUint256: " + rustUint256) + + const rustInt256 = await contractDeployed.rustInt256() + console.log("rustInt256: " + rustInt256) + + const rustAddress = await contractDeployed.rustAddress() + console.log("rustAddress: " + rustAddress) + + const rustBytes = await contractDeployed.rustBytes() + console.log("rustBytes: " + rustBytes) + + const rustBytes32 = await contractDeployed.rustBytes32() + console.log("rustBytes32: " + rustBytes32) + + const rustBool = await contractDeployed.rustBool() + console.log("rustBool: " + rustBool) + +} diff --git a/templates/blendedAppFoundry/javascript/solidity.js b/templates/blendedAppFoundry/javascript/solidity.js new file mode 100644 index 0000000..3851889 --- /dev/null +++ b/templates/blendedAppFoundry/javascript/solidity.js @@ -0,0 +1,52 @@ +const ethers = require("ethers") // npm i ethers@5.7.2 https://github.com/smartcontractkit/full-blockchain-solidity-course-js/discussions/5139#discussioncomment-5444517 + +const rpcURL = "https://rpc.dev.gblend.xyz/" // Your RPC URL goes here + +const provider = new ethers.providers.JsonRpcProvider(rpcURL) + +const contractAddress = '0xD96a275ca2e9Ef5B10bF9fDb106718b670Afc8B2' +const contractABI = [{"inputs":[{"internalType":"address","name":"FluentRustAddress","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"fluentRust","outputs":[{"internalType":"contractIFluentRust","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRustAddress","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRustBool","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRustBytes","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRustBytes32","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRustInt256","outputs":[{"internalType":"int256","name":"","type":"int256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRustString","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getRustUint256","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"}] + +const contractDeployed = new ethers.Contract(contractAddress, contractABI, provider); + +let fluent_sepolia_chain_id = 20993; + +testSolidityContractRead() + +async function testSolidityContractRead() { + + const connectedNetworkObject = await provider.getNetwork(); + const chainIdConnected = connectedNetworkObject.chainId; + console.log("chainIdConnected: " + chainIdConnected) + + if(chainIdConnected != fluent_sepolia_chain_id){ + console.log("RPC endpoint not connected to Fluent Sepolia (chainId: " + fluent_sepolia_chain_id + ")."); + console.log("Switch to Fluent Sepolia then try again."); + return; + } + + const fluentRustContractAddress = await contractDeployed.fluentRust() + console.log("fluentRustContractAddress: " + fluentRustContractAddress) + + const rustString = await contractDeployed.getRustString() + console.log("rustString: " + rustString) + + const rustUint256 = await contractDeployed.getRustUint256() + console.log("rustUint256: " + rustUint256) + + const rustInt256 = await contractDeployed.getRustInt256() + console.log("rustInt256: " + rustInt256) + + const rustAddress = await contractDeployed.getRustAddress() + console.log("rustAddress: " + rustAddress) + + const rustBytes = await contractDeployed.getRustBytes() + console.log("rustBytes: " + rustBytes) + + const rustBytes32 = await contractDeployed.getRustBytes32() + console.log("rustBytes32: " + rustBytes32) + + const rustBool = await contractDeployed.getRustBool() + console.log("rustBool: " + rustBool) + +} diff --git a/templates/blendedAppFoundry/rust/.gitignore b/templates/blendedAppFoundry/rust/.gitignore new file mode 100644 index 0000000..498fe95 --- /dev/null +++ b/templates/blendedAppFoundry/rust/.gitignore @@ -0,0 +1,3 @@ +/target +/bin +Cargo.lock \ No newline at end of file diff --git a/templates/blendedAppFoundry/rust/Cargo.toml b/templates/blendedAppFoundry/rust/Cargo.toml new file mode 100644 index 0000000..243e1ee --- /dev/null +++ b/templates/blendedAppFoundry/rust/Cargo.toml @@ -0,0 +1,21 @@ +[package] +edition = "2021" +name = "rustsimplesc" +version = "0.1.0" + +[dependencies] +fluentbase-sdk = { git = "https://github.com/fluentlabs-xyz/fluentbase", branch = "devel", default-features = false } + +[dev-dependencies] +hex-literal = "0.4.1" +hex = "0.4.3" + +[lib] +crate-type = ["cdylib", "staticlib"] +path = "lib.rs" + +[features] +default = ["std"] +std = [ +"fluentbase-sdk/std" +] diff --git a/templates/blendedAppFoundry/rust/Makefile b/templates/blendedAppFoundry/rust/Makefile new file mode 100644 index 0000000..e4b0918 --- /dev/null +++ b/templates/blendedAppFoundry/rust/Makefile @@ -0,0 +1,7 @@ +RUSTFLAGS='-C link-arg=-zstack-size=262144 -C target-feature=+bulk-memory' + +.PHONY: lib.wasm +lib.wasm: lib.rs Cargo.toml + RUSTFLAGS=$(RUSTFLAGS) cargo build --release --target=wasm32-unknown-unknown --no-default-features --features="" + cp ./target/wasm32-unknown-unknown/release/*.wasm ./lib.wasm + wasm2wat ./lib.wasm > ./lib.wat || true \ No newline at end of file diff --git a/templates/blendedAppFoundry/rust/lib.rs b/templates/blendedAppFoundry/rust/lib.rs new file mode 100644 index 0000000..4bdc578 --- /dev/null +++ b/templates/blendedAppFoundry/rust/lib.rs @@ -0,0 +1,92 @@ +#![cfg_attr(target_arch = "wasm32", no_std)] +extern crate alloc; + +use alloc::string::{String, ToString}; +use fluentbase_sdk::{ + basic_entrypoint, + derive::{function_id, router, Contract}, + SharedAPI, + U256, // alloy Solidity type for uint256 + I256, // alloy Solidity type for int256 + Address, // alloy Solidity type for address + address, // alloy Solidity marco to define values for type Address + Bytes, // alloy Solidity type for bytes + B256, // alloy Solidity type for bytes32 + b256 // alloy Solidity marco to define values for type B256 +}; + +#[derive(Contract)] +struct ROUTER { + sdk: SDK, +} + +pub trait RouterAPI { + // Make sure type interfaces are defined here or else there will be a compiler error. + fn rust_string(&self) -> String; + fn rust_uint256(&self) -> U256; + fn rust_int256(&self) -> I256; + fn rust_address(&self) -> Address; + fn rust_bytes(&self) -> Bytes; + fn rust_bytes32(&self) -> B256; + fn rust_bool(&self) -> bool; +} + +#[router(mode = "solidity")] +impl RouterAPI for ROUTER { + + // ERC-20 with Fluent SDK example: + // https://github.com/fluentlabs-xyz/fluentbase/blob/devel/examples/erc20/lib.rs + + #[function_id("rustString()")] + fn rust_string(&self) -> String { + let string_test = "Hello".to_string(); + return string_test; + } + + #[function_id("rustUint256()")] + fn rust_uint256(&self) -> U256 { + let uint256_test = U256::from(10); + return uint256_test; + } + + #[function_id("rustInt256()")] + fn rust_int256(&self) -> I256 { + // Declare Signed variables in alloy.rs: + // https://docs.rs/alloy-primitives/latest/alloy_primitives/struct.Signed.html#method.from_dec_str + let int256_test = I256::unchecked_from(-10); + return int256_test; + } + + #[function_id("rustAddress()")] + fn rust_address(&self) -> Address { + let address_test: Address = address!("d8da6bf26964af9d7eed9e03e53415d37aa96045"); // vitalik.eth 0xd8da6bf26964af9d7eed9e03e53415d37aa96045 + return address_test; + } + + #[function_id("rustBytes()")] + fn rust_bytes(&self) -> Bytes { + let bytes_test = Bytes::from("FLUENT"); + return bytes_test; + } + + #[function_id("rustBytes32()")] + fn rust_bytes32(&self) -> B256 { + let bytes256_test = b256!("0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"); + return bytes256_test; + } + + #[function_id("rustBool()")] + fn rust_bool(&self) -> bool { + let bool_test = true; + return bool_test; + } + +} + +impl ROUTER { + fn deploy(&self) { + // any custom deployment logic here + } +} + +basic_entrypoint!(ROUTER); \ No newline at end of file diff --git a/templates/blendedAppFoundry/rust/rust-toolchain b/templates/blendedAppFoundry/rust/rust-toolchain new file mode 100644 index 0000000..bf867e0 --- /dev/null +++ b/templates/blendedAppFoundry/rust/rust-toolchain @@ -0,0 +1 @@ +nightly diff --git a/templates/blendedAppFoundry/solidity/.github/workflows/test.yml b/templates/blendedAppFoundry/solidity/.github/workflows/test.yml new file mode 100644 index 0000000..34a4a52 --- /dev/null +++ b/templates/blendedAppFoundry/solidity/.github/workflows/test.yml @@ -0,0 +1,43 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + strategy: + fail-fast: true + + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Run Forge fmt + run: | + forge fmt --check + id: fmt + + - name: Run Forge build + run: | + forge build --sizes + id: build + + - name: Run Forge tests + run: | + forge test -vvv + id: test diff --git a/templates/blendedAppFoundry/solidity/.gitignore b/templates/blendedAppFoundry/solidity/.gitignore new file mode 100644 index 0000000..85198aa --- /dev/null +++ b/templates/blendedAppFoundry/solidity/.gitignore @@ -0,0 +1,14 @@ +# Compiler files +cache/ +out/ + +# Ignores development broadcast logs +!/broadcast +/broadcast/*/31337/ +/broadcast/**/dry-run/ + +# Docs +docs/ + +# Dotenv file +.env diff --git a/templates/blendedAppFoundry/solidity/README.md b/templates/blendedAppFoundry/solidity/README.md new file mode 100644 index 0000000..9265b45 --- /dev/null +++ b/templates/blendedAppFoundry/solidity/README.md @@ -0,0 +1,66 @@ +## Foundry + +**Foundry is a blazing fast, portable and modular toolkit for Ethereum application development written in Rust.** + +Foundry consists of: + +- **Forge**: Ethereum testing framework (like Truffle, Hardhat and DappTools). +- **Cast**: Swiss army knife for interacting with EVM smart contracts, sending transactions and getting chain data. +- **Anvil**: Local Ethereum node, akin to Ganache, Hardhat Network. +- **Chisel**: Fast, utilitarian, and verbose solidity REPL. + +## Documentation + +https://book.getfoundry.sh/ + +## Usage + +### Build + +```shell +$ forge build +``` + +### Test + +```shell +$ forge test +``` + +### Format + +```shell +$ forge fmt +``` + +### Gas Snapshots + +```shell +$ forge snapshot +``` + +### Anvil + +```shell +$ anvil +``` + +### Deploy + +```shell +$ forge script script/Counter.s.sol:CounterScript --rpc-url --private-key +``` + +### Cast + +```shell +$ cast +``` + +### Help + +```shell +$ forge --help +$ anvil --help +$ cast --help +``` diff --git a/templates/blendedAppFoundry/solidity/foundry.toml b/templates/blendedAppFoundry/solidity/foundry.toml new file mode 100644 index 0000000..25b918f --- /dev/null +++ b/templates/blendedAppFoundry/solidity/foundry.toml @@ -0,0 +1,6 @@ +[profile.default] +src = "src" +out = "out" +libs = ["lib"] + +# See more config options https://github.com/foundry-rs/foundry/blob/master/crates/config/README.md#all-options diff --git a/templates/blendedAppFoundry/solidity/src/FluentSdkRustTypesTest.sol b/templates/blendedAppFoundry/solidity/src/FluentSdkRustTypesTest.sol new file mode 100644 index 0000000..a9c6858 --- /dev/null +++ b/templates/blendedAppFoundry/solidity/src/FluentSdkRustTypesTest.sol @@ -0,0 +1,58 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.28; + +interface IFluentRust { + // Make sure type interfaces are defined here or else there will be a compiler error. + function rustString() external view returns (string memory); + function rustUint256() external view returns (uint256); + function rustInt256() external view returns (int256); + function rustAddress() external view returns (address); + function rustBytes() external view returns (bytes memory); + function rustBytes32() external view returns (bytes32); + function rustBool() external view returns (bool); +} + +contract FluentSdkRustTypesTest { + + IFluentRust public fluentRust; + + constructor(address FluentRustAddress) { + fluentRust = IFluentRust(FluentRustAddress); + } + + function getRustString() external view returns (string memory) { + string memory rustString = fluentRust.rustString(); + return string(abi.encodePacked(rustString, " World")); + } + + function getRustUint256() external view returns (uint256) { + uint256 rustUint256 = fluentRust.rustUint256(); + return rustUint256; + } + + function getRustInt256() external view returns (int256) { + int256 rustInt256 = fluentRust.rustInt256(); + return rustInt256; + } + + function getRustAddress() external view returns (address) { + address rustAddress = fluentRust.rustAddress(); + return rustAddress; + } + + function getRustBytes() external view returns (bytes memory) { + bytes memory rustBytes = fluentRust.rustBytes(); + return rustBytes; + } + + function getRustBytes32() external view returns (bytes32) { + bytes32 rustBytes32 = fluentRust.rustBytes32(); + return rustBytes32; + } + + function getRustBool() external view returns (bool) { + bool rustBool = fluentRust.rustBool(); + return rustBool; + } + +} \ No newline at end of file diff --git a/templates/blendedAppFoundry/solidity/src/deployConstructor/FluentSdkRustTypesTest.txt b/templates/blendedAppFoundry/solidity/src/deployConstructor/FluentSdkRustTypesTest.txt new file mode 100644 index 0000000..f8485ea --- /dev/null +++ b/templates/blendedAppFoundry/solidity/src/deployConstructor/FluentSdkRustTypesTest.txt @@ -0,0 +1 @@ +0x04160C19738bB6429c0554fBdC11A96079D7297D \ No newline at end of file diff --git a/templates/blendedAppFoundry/solidity/test/FluentSdkRustTypesTest.t.sol b/templates/blendedAppFoundry/solidity/test/FluentSdkRustTypesTest.t.sol new file mode 100644 index 0000000..4aa4855 --- /dev/null +++ b/templates/blendedAppFoundry/solidity/test/FluentSdkRustTypesTest.t.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.28; + +import {Test, console} from "forge-std/Test.sol"; +import {FluentSdkRustTypesTest, IFluentRust} from "../src/FluentSdkRustTypesTest.sol"; + +contract FluentSdkRustTypesTestTest is Test { + + FluentSdkRustTypesTest public fluentSdkRustTypesTest; + + address constant rustContractFluentTestnet = 0x04160C19738bB6429c0554fBdC11A96079D7297D; + + function setUp() public { + fluentSdkRustTypesTest = new FluentSdkRustTypesTest(rustContractFluentTestnet); + } + + function test_get_fluentRust() view public { + IFluentRust test_fluentRust = fluentSdkRustTypesTest.fluentRust(); + address rustContractAddress = address(test_fluentRust); + assertEq(rustContractAddress, rustContractFluentTestnet); + } + + function test_get_string() view public { + string memory test_string = fluentSdkRustTypesTest.getRustString(); + assertEq(test_string, "Hello World"); + } + + function test_get_uint256() view public { + uint256 test_uint256 = fluentSdkRustTypesTest.getRustUint256(); + assertEq(test_uint256, 10); + } + + function test_get_int256() view public { + int256 test_int256 = fluentSdkRustTypesTest.getRustInt256(); + assertEq(test_int256, -10); + } + + function test_get_address() view public { + address test_address = fluentSdkRustTypesTest.getRustAddress(); + assertEq(test_address, 0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045); + } + + function test_get_bytes32() view public { + bytes32 test_bytes32_contract = fluentSdkRustTypesTest.getRustBytes32(); + bytes32 test_bytes32_value = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + assertEq(test_bytes32_contract, test_bytes32_value); + } + + function test_get_bytes() view public { + bytes memory test_bytes_contract = fluentSdkRustTypesTest.getRustBytes(); + bytes memory test_bytes_value = hex"464c55454e54"; + assertEq(test_bytes_contract, test_bytes_value); + } + + function test_get_bool() view public { + bool test_bool = fluentSdkRustTypesTest.getRustBool(); + assertEq(test_bool, true); + } + +} From ca8dfec673d3a5147e32e60c907cb1a011cae1f4 Mon Sep 17 00:00:00 2001 From: d1r1 Date: Thu, 24 Apr 2025 15:49:40 +0400 Subject: [PATCH 2/4] chore: up rustup toolchain and fix fmt warnings --- .github/workflows/ci.yml | 22 ++++------------------ rust-toolchain | 2 +- src/commands/rust/init.rs | 4 +++- 3 files changed, 8 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 633c52d..d4a7858 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -13,18 +13,12 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - - uses: Swatinem/rust-cache@v2 - with: - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - uses: dtolnay/rust-toolchain@master with: - toolchain: nightly-2024-10-01 + toolchain: nightly-2025-01-27 components: rustfmt, clippy - + - name: Lint run: | cargo fmt -- --check @@ -34,19 +28,11 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - - uses: Swatinem/rust-cache@v2 - with: - key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }} - restore-keys: | - ${{ runner.os }}-cargo- - + - uses: dtolnay/rust-toolchain@stable - name: Install wasm32 target run: rustup target add wasm32-unknown-unknown - + - name: Test run: cargo test --all-features --locked - - diff --git a/rust-toolchain b/rust-toolchain index f31e089..840adca 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2024-10-01 +nightly-2025-01-27 diff --git a/src/commands/rust/init.rs b/src/commands/rust/init.rs index f839c87..9191836 100644 --- a/src/commands/rust/init.rs +++ b/src/commands/rust/init.rs @@ -1,6 +1,8 @@ use super::{ constants::{ - BASIC_TEMPLATE_CARGO_TOML, BASIC_TEMPLATE_LIB_RS, BASIC_TEMPLATE_MAKEFILE, + BASIC_TEMPLATE_CARGO_TOML, + BASIC_TEMPLATE_LIB_RS, + BASIC_TEMPLATE_MAKEFILE, BASIC_TEMPLATE_RUST_TOOLCHAIN, }, template_manager::TemplateManager, From c7bc46cec07d8f7f6bbd4e84c0be9a29dcfbbd2e Mon Sep 17 00:00:00 2001 From: d1r1 Date: Thu, 24 Apr 2025 15:52:04 +0400 Subject: [PATCH 3/4] chore: fix clippy warnings --- src/commands/rust/build.rs | 54 ++++++++++++++++++-------------------- src/commands/rust/utils.rs | 3 +-- 2 files changed, 27 insertions(+), 30 deletions(-) diff --git a/src/commands/rust/build.rs b/src/commands/rust/build.rs index c16ff7a..c0471e0 100644 --- a/src/commands/rust/build.rs +++ b/src/commands/rust/build.rs @@ -117,39 +117,37 @@ fn build_project( if verbose { println!(" ~ detected project name: {}", project_name) } - - let (target_dir, library_name) = if target_dir.is_none() { - let result = Command::new("cargo") - .arg("metadata") - .arg("--no-deps") - .output() - .map_err(|e| { + let (target_dir, library_name) = match target_dir { + None => { + let result = Command::new("cargo") + .arg("metadata") + .arg("--no-deps") + .output() + .map_err(|e| { + if verbose { + println!("Failed to get target directory: {:?}", e); + } + Error::Build("Failed to get target directory".to_string()) + })?; + let utf8_string = from_utf8(&result.stdout).map_err(|e| { if verbose { - println!("Failed to get target directory: {:?}", e); + println!("Failed to decode UTF-8 output: {:?}", e); } Error::Build("Failed to get target directory".to_string()) })?; - let utf8_string = from_utf8(&result.stdout).map_err(|e| { - if verbose { - println!("Failed to decode UTF-8 output: {:?}", e); - } - Error::Build("Failed to get target directory".to_string()) - })?; - let json_value = json::parse(utf8_string).expect("can't parse json with manifest"); - let package = json_value["packages"] - .members() - .find(|package| package["name"].to_string() == project_name) - .expect("can't find package in the manifest"); - let library_name = package["targets"].members().find_map(|target| { - target["kind"] + let json_value = json::parse(utf8_string).expect("can't parse json with manifest"); + let package = json_value["packages"] .members() - .find(|lib| lib.to_string() == "cdylib")?; - Some(format!("{}.wasm", target["name"].to_string())) - }); - let target_directory = json_value["target_directory"].to_string(); - (target_directory, library_name) - } else { - (target_dir.unwrap(), None) + .find(|package| package["name"] == project_name) + .expect("can't find package in the manifest"); + let library_name = package["targets"].members().find_map(|target| { + target["kind"].members().find(|lib| *lib == "cdylib")?; + Some(format!("{}.wasm", target["name"])) + }); + let target_directory = json_value["target_directory"].to_string(); + (target_directory, library_name) + } + Some(dir) => (dir, None), }; let library_name = diff --git a/src/commands/rust/utils.rs b/src/commands/rust/utils.rs index 138c549..b09fd0d 100644 --- a/src/commands/rust/utils.rs +++ b/src/commands/rust/utils.rs @@ -36,8 +36,7 @@ impl Tool { .is_ok(), Self::WasmTarget => Command::new("rustup") .args(["target", "list", "--installed"]) - .output() - .map_or(false, |output| { + .output().is_ok_and(|output| { String::from_utf8_lossy(&output.stdout).contains("wasm32-unknown-unknown") }), Self::Wasm2Wat => Command::new(self.command()) From ae654fb055379447133eee2c5479f8e968aa7d8e Mon Sep 17 00:00:00 2001 From: d1r1 Date: Thu, 24 Apr 2025 15:53:21 +0400 Subject: [PATCH 4/4] chore: fix one more fmt error --- src/commands/rust/utils.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/commands/rust/utils.rs b/src/commands/rust/utils.rs index b09fd0d..c09460d 100644 --- a/src/commands/rust/utils.rs +++ b/src/commands/rust/utils.rs @@ -36,7 +36,8 @@ impl Tool { .is_ok(), Self::WasmTarget => Command::new("rustup") .args(["target", "list", "--installed"]) - .output().is_ok_and(|output| { + .output() + .is_ok_and(|output| { String::from_utf8_lossy(&output.stdout).contains("wasm32-unknown-unknown") }), Self::Wasm2Wat => Command::new(self.command())