Skip to content

Commit 2fd78f8

Browse files
committed
gblend init foundry template
1 parent 87fb581 commit 2fd78f8

20 files changed

+707
-7
lines changed

.gitignore

+2-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@
22
/target
33
/testing
44
/tmp
5-
/.idea
5+
/.idea
6+
.DS_Store

src/commands/legacy_init.rs

+121-6
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,8 @@ pub async fn legacy_init() -> Result<()> {
2727
"Hardhat JavaScript (Solidity & Vyper)",
2828
"Hardhat TypeScript (Solidity & Vyper)",
2929
"Rust",
30-
"Blendedapp 🔄",
30+
"Blendedapp Hardhat 🔄",
31+
"Blendedapp Foundry 🔄",
3132
"Exit",
3233
];
3334
let selection = Select::new()
@@ -41,8 +42,9 @@ pub async fn legacy_init() -> Result<()> {
4142
0 => spin_js(use_erc20)?,
4243
1 => spin_ts(use_erc20)?,
4344
2 => spin_rust()?,
44-
3 => spin_blended_app()?,
45-
4 => {
45+
3 => spin_blended_app_hardhat()?,
46+
4 => spin_blended_app_foundry()?,
47+
5 => {
4648
println!("Exiting program.");
4749
return Ok(()); // Exit the program gracefully
4850
}
@@ -243,8 +245,8 @@ fn spin_ts(use_erc20: bool) -> Result<()> {
243245
Ok(())
244246
}
245247

246-
fn spin_blended_app() -> Result<()> {
247-
println!("Creating blended app ...");
248+
fn spin_blended_app_hardhat() -> Result<()> {
249+
println!("Creating blended app with Hardhat ...");
248250

249251
// Embed the files in the binary using `include_str!`
250252
const HARDHAT_CONFIG: &str = include_str!(concat!(
@@ -328,7 +330,120 @@ fn spin_blended_app() -> Result<()> {
328330
create_file_with_content(".env", ENV)?;
329331
create_file_with_content(".gitignore", GIT_IGNORE)?;
330332

331-
println!("Blended app created successfully!");
333+
println!("Blended app with Hardhat created successfully!");
332334

333335
Ok(())
334336
}
337+
338+
fn spin_blended_app_foundry() -> Result<()> {
339+
println!("Creating blended app with Foundry...");
340+
341+
// Embed the files in the binary using `include_str!`
342+
const JAVASCRIPT_GIT_IGNORE: &str = include_str!(concat!(
343+
env!("CARGO_MANIFEST_DIR"),
344+
"/templates/blendedAppFoundry/javascript/.gitignore"
345+
));
346+
const JAVASCRIPT_PACKAGE_JSON: &str = include_str!(concat!(
347+
env!("CARGO_MANIFEST_DIR"),
348+
"/templates/blendedAppFoundry/javascript/package.json"
349+
));
350+
const JAVASCRIPT_SOLIDITY: &str = include_str!(concat!(
351+
env!("CARGO_MANIFEST_DIR"),
352+
"/templates/blendedAppFoundry/javascript/solidity.js"
353+
));
354+
const JAVASCRIPT_RUST: &str = include_str!(concat!(
355+
env!("CARGO_MANIFEST_DIR"),
356+
"/templates/blendedAppFoundry/javascript/rust.js"
357+
));
358+
359+
const RUST_GIT_IGNORE: &str = include_str!(concat!(
360+
env!("CARGO_MANIFEST_DIR"),
361+
"/templates/blendedAppFoundry/rust/.gitignore"
362+
));
363+
const RUST_CARGO_TOML: &str = include_str!(concat!(
364+
env!("CARGO_MANIFEST_DIR"),
365+
"/templates/blendedAppFoundry/rust/Cargo.toml"
366+
));
367+
const RUST_MAKEFILE: &str = include_str!(concat!(
368+
env!("CARGO_MANIFEST_DIR"),
369+
"/templates/blendedAppFoundry/rust/Makefile"
370+
));
371+
const RUST_LIB_RS: &str = include_str!(concat!(
372+
env!("CARGO_MANIFEST_DIR"),
373+
"/templates/blendedAppFoundry/rust/lib.rs"
374+
));
375+
const RUST_TOOLCHAIN: &str = include_str!(concat!(
376+
env!("CARGO_MANIFEST_DIR"),
377+
"/templates/blendedAppFoundry/rust/rust-toolchain"
378+
));
379+
380+
const SOLIDITY_GIT_IGNORE: &str = include_str!(concat!(
381+
env!("CARGO_MANIFEST_DIR"),
382+
"/templates/blendedAppFoundry/solidity/.gitignore"
383+
));
384+
const SOLIDITY_README: &str = include_str!(concat!(
385+
env!("CARGO_MANIFEST_DIR"),
386+
"/templates/blendedAppFoundry/solidity/README.md"
387+
));
388+
const SOLIDITY_FOUNDRY_TOML: &str = include_str!(concat!(
389+
env!("CARGO_MANIFEST_DIR"),
390+
"/templates/blendedAppFoundry/solidity/foundry.toml"
391+
));
392+
393+
const SOLIDITY_GIT_WORKFLOW_TEST: &str = include_str!(concat!(
394+
env!("CARGO_MANIFEST_DIR"),
395+
"/templates/blendedAppFoundry/solidity/.github/workflows/test.yml"
396+
));
397+
const SOLIDITY_SOURCE_CONTRACT: &str = include_str!(concat!(
398+
env!("CARGO_MANIFEST_DIR"),
399+
"/templates/blendedAppFoundry/solidity/src/FluentSdkRustTypesTest.sol"
400+
));
401+
const SOLIDITY_SOURCE_CONSTRUCTOR: &str = include_str!(concat!(
402+
env!("CARGO_MANIFEST_DIR"),
403+
"/templates/blendedAppFoundry/solidity/src/deployConstructor/FluentSdkRustTypesTest.txt"
404+
));
405+
const SOLIDITY_TEST_CONTRACT: &str = include_str!(concat!(
406+
env!("CARGO_MANIFEST_DIR"),
407+
"/templates/blendedAppFoundry/solidity/test/FluentSdkRustTypesTest.t.sol"
408+
));
409+
410+
const README: &str = include_str!(concat!(
411+
env!("CARGO_MANIFEST_DIR"),
412+
"/templates/blendedAppFoundry/README.md"
413+
));
414+
const GIT_MODULES: &str = include_str!(concat!(
415+
env!("CARGO_MANIFEST_DIR"),
416+
"/templates/blendedAppFoundry/.gitmodules"
417+
));
418+
419+
// Create necessary directories and write files
420+
create_directories("javascript")?;
421+
create_directories("rust")?;
422+
create_directories("solidity")?;
423+
424+
create_file_with_content("javascript/.gitignore", JAVASCRIPT_GIT_IGNORE)?;
425+
create_file_with_content("javascript/package.json", JAVASCRIPT_PACKAGE_JSON)?;
426+
create_file_with_content("javascript/solidity.js", JAVASCRIPT_SOLIDITY)?;
427+
create_file_with_content("javascript/rust.js", JAVASCRIPT_RUST)?;
428+
429+
create_file_with_content("rust/.gitignore", RUST_GIT_IGNORE)?;
430+
create_file_with_content("rust/Cargo.toml", RUST_CARGO_TOML)?;
431+
create_file_with_content("rust/Makefile", RUST_MAKEFILE)?;
432+
create_file_with_content("rust/lib.rs", RUST_LIB_RS)?;
433+
create_file_with_content("rust/rust-toolchain", RUST_TOOLCHAIN)?;
434+
435+
create_file_with_content("solidity/.gitignore", SOLIDITY_GIT_IGNORE)?;
436+
create_file_with_content("solidity/README.md", SOLIDITY_README)?;
437+
create_file_with_content("solidity/foundry.toml", SOLIDITY_FOUNDRY_TOML)?;
438+
create_file_with_content("solidity/.github/workflows/test.yml", SOLIDITY_GIT_WORKFLOW_TEST)?;
439+
create_file_with_content("solidity/src/FluentSdkRustTypesTest.sol", SOLIDITY_SOURCE_CONTRACT)?;
440+
create_file_with_content("solidity/src/deployConstructor/FluentSdkRustTypesTest.txt", SOLIDITY_SOURCE_CONSTRUCTOR)?;
441+
create_file_with_content("solidity/test/FluentSdkRustTypesTest.t.sol", SOLIDITY_TEST_CONTRACT)?;
442+
443+
create_file_with_content("README.md", README)?;
444+
create_file_with_content(".gitmodules", GIT_MODULES)?;
445+
446+
println!("Blended app with Foundry created successfully!");
447+
448+
Ok(())
449+
}
+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "solidity/lib/forge-std"]
2+
path = solidity/lib/forge-std
3+
url = https://github.com/foundry-rs/forge-std

templates/blendedAppFoundry/README.md

+101
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# blended-sdk
2+
3+
Blended App template SDK for Solidity and Rust WASM contracts with Javascript ethers.js test scripts.
4+
5+
## Documentation
6+
7+
https://docs.fluent.xyz/developer-guides/building-a-blended-app/
8+
9+
## Deploy Blended Contracts
10+
11+
### Step 1 - Deploy Rust Contract
12+
13+
Change directory to `rust` contract folder
14+
15+
```shell
16+
cd rust
17+
```
18+
19+
Compile Rust contract for WASM binary
20+
21+
```shell
22+
gblend build rust -r
23+
```
24+
25+
Deploy Rust contract with WASM binary
26+
27+
```shell
28+
gblend deploy \
29+
--private-key $devTestnetPrivateKey \
30+
--dev lib.wasm \
31+
--gas-limit 3000000
32+
```
33+
34+
Copy this deployed Rust contract address,
35+
since this will be used for the Solidity contract communication.
36+
37+
⚠️ Note: to update Rust crate `fluentbase-sdk` if there are issues: ⚠️
38+
39+
```shell
40+
cargo clean
41+
cargo update -p fluentbase-sdk
42+
```
43+
44+
### Step 2 - Deploy Solidity Contract
45+
46+
Switch back to the root of this repo, then switch to the `solidity` contract folder
47+
48+
```shell
49+
cd ../
50+
cd solidity
51+
```
52+
53+
Deploy the Solidity contract with the Rust contract address
54+
with the Forge flag which defines path to constructor input text file `--constructor-args-path`
55+
56+
```shell
57+
forge create src/FluentSdkRustTypesTest.sol:FluentSdkRustTypesTest \
58+
--constructor-args-path src/deployConstructor/FluentSdkRustTypesTest.txt \
59+
--private-key $devTestnetPrivateKey \
60+
--rpc-url https://rpc.dev.gblend.xyz/ \
61+
--broadcast \
62+
--verify \
63+
--verifier blockscout \
64+
--verifier-url https://blockscout.dev.gblend.xyz/api/
65+
```
66+
67+
Foundry test fork deployed contracts on Fluent testnet
68+
69+
```shell
70+
forge coverage --fork-url https://rpc.dev.gblend.xyz/
71+
```
72+
73+
### Step 3 - Test Javascript ethers.js Interaction
74+
75+
Switch back to the root of this repo, then switch to the `javascript` folder
76+
77+
```shell
78+
cd ../
79+
cd javascript
80+
```
81+
82+
Install packages such as ethers.js with `package.json` with
83+
84+
```shell
85+
npm i
86+
```
87+
88+
Run the ethers.js test script to have the Solidity contract call the Rust contract
89+
90+
```shell
91+
node solidity.js
92+
```
93+
94+
Run the ethers.js test script to call the Rust contract directly (using Solidity interface ABI)
95+
96+
```shell
97+
node rust.js
98+
```
99+
100+
This ethers.js Javascript example can be
101+
modified from node.js to a frontend application for users to interact with.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
node_modules
2+
package-lock.json
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"dependencies": {
3+
"ethers": "^5.7.2"
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
const ethers = require("ethers") // npm i [email protected] https://github.com/smartcontractkit/full-blockchain-solidity-course-js/discussions/5139#discussioncomment-5444517
2+
3+
const rpcURL = "https://rpc.dev.gblend.xyz/" // Your RPC URL goes here
4+
5+
const provider = new ethers.providers.JsonRpcProvider(rpcURL)
6+
7+
const contractAddress = '0x04160C19738bB6429c0554fBdC11A96079D7297D'
8+
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"}]
9+
10+
const contractDeployed = new ethers.Contract(contractAddress, contractABI, provider);
11+
12+
let fluent_sepolia_chain_id = 20993;
13+
14+
testRustContractRead()
15+
16+
async function testRustContractRead() {
17+
18+
const connectedNetworkObject = await provider.getNetwork();
19+
const chainIdConnected = connectedNetworkObject.chainId;
20+
console.log("chainIdConnected: " + chainIdConnected)
21+
22+
if(chainIdConnected != fluent_sepolia_chain_id){
23+
console.log("RPC endpoint not connected to Fluent Sepolia (chainId: " + fluent_sepolia_chain_id + ").");
24+
console.log("Switch to Fluent Sepolia then try again.");
25+
return;
26+
}
27+
28+
const rustString = await contractDeployed.rustString()
29+
console.log("rustString: " + rustString)
30+
31+
const rustUint256 = await contractDeployed.rustUint256()
32+
console.log("rustUint256: " + rustUint256)
33+
34+
const rustInt256 = await contractDeployed.rustInt256()
35+
console.log("rustInt256: " + rustInt256)
36+
37+
const rustAddress = await contractDeployed.rustAddress()
38+
console.log("rustAddress: " + rustAddress)
39+
40+
const rustBytes = await contractDeployed.rustBytes()
41+
console.log("rustBytes: " + rustBytes)
42+
43+
const rustBytes32 = await contractDeployed.rustBytes32()
44+
console.log("rustBytes32: " + rustBytes32)
45+
46+
const rustBool = await contractDeployed.rustBool()
47+
console.log("rustBool: " + rustBool)
48+
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
const ethers = require("ethers") // npm i [email protected] https://github.com/smartcontractkit/full-blockchain-solidity-course-js/discussions/5139#discussioncomment-5444517
2+
3+
const rpcURL = "https://rpc.dev.gblend.xyz/" // Your RPC URL goes here
4+
5+
const provider = new ethers.providers.JsonRpcProvider(rpcURL)
6+
7+
const contractAddress = '0xD96a275ca2e9Ef5B10bF9fDb106718b670Afc8B2'
8+
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"}]
9+
10+
const contractDeployed = new ethers.Contract(contractAddress, contractABI, provider);
11+
12+
let fluent_sepolia_chain_id = 20993;
13+
14+
testSolidityContractRead()
15+
16+
async function testSolidityContractRead() {
17+
18+
const connectedNetworkObject = await provider.getNetwork();
19+
const chainIdConnected = connectedNetworkObject.chainId;
20+
console.log("chainIdConnected: " + chainIdConnected)
21+
22+
if(chainIdConnected != fluent_sepolia_chain_id){
23+
console.log("RPC endpoint not connected to Fluent Sepolia (chainId: " + fluent_sepolia_chain_id + ").");
24+
console.log("Switch to Fluent Sepolia then try again.");
25+
return;
26+
}
27+
28+
const fluentRustContractAddress = await contractDeployed.fluentRust()
29+
console.log("fluentRustContractAddress: " + fluentRustContractAddress)
30+
31+
const rustString = await contractDeployed.getRustString()
32+
console.log("rustString: " + rustString)
33+
34+
const rustUint256 = await contractDeployed.getRustUint256()
35+
console.log("rustUint256: " + rustUint256)
36+
37+
const rustInt256 = await contractDeployed.getRustInt256()
38+
console.log("rustInt256: " + rustInt256)
39+
40+
const rustAddress = await contractDeployed.getRustAddress()
41+
console.log("rustAddress: " + rustAddress)
42+
43+
const rustBytes = await contractDeployed.getRustBytes()
44+
console.log("rustBytes: " + rustBytes)
45+
46+
const rustBytes32 = await contractDeployed.getRustBytes32()
47+
console.log("rustBytes32: " + rustBytes32)
48+
49+
const rustBool = await contractDeployed.getRustBool()
50+
console.log("rustBool: " + rustBool)
51+
52+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/target
2+
/bin
3+
Cargo.lock

0 commit comments

Comments
 (0)