From 95cd78ec7f1af1126ce743db72d79edd38e122bb Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 21 Aug 2025 12:53:44 +0200 Subject: [PATCH 1/6] add vyper template and test Co-authored-by: startup-dreamer --- .../{ => solidity}/CounterTemplate.s.sol | 0 .../assets/{ => solidity}/CounterTemplate.sol | 0 .../{ => solidity}/CounterTemplate.t.sol | 0 .../{ => solidity}/workflowTemplate.yml | 2 +- .../forge/assets/vyper/CounterTemplate.s.sol | 19 ++++++ .../forge/assets/vyper/CounterTemplate.t.sol | 24 +++++++ crates/forge/assets/vyper/CounterTemplate.vy | 11 ++++ .../forge/assets/vyper/ICounterTemplate.sol | 8 +++ .../forge/assets/vyper/workflowTemplate.yml | 52 ++++++++++++++++ crates/forge/src/cmd/init.rs | 62 +++++++++++++++---- crates/forge/tests/cli/cmd.rs | 25 ++++++++ 11 files changed, 189 insertions(+), 14 deletions(-) rename crates/forge/assets/{ => solidity}/CounterTemplate.s.sol (100%) rename crates/forge/assets/{ => solidity}/CounterTemplate.sol (100%) rename crates/forge/assets/{ => solidity}/CounterTemplate.t.sol (100%) rename crates/forge/assets/{ => solidity}/workflowTemplate.yml (94%) create mode 100644 crates/forge/assets/vyper/CounterTemplate.s.sol create mode 100644 crates/forge/assets/vyper/CounterTemplate.t.sol create mode 100644 crates/forge/assets/vyper/CounterTemplate.vy create mode 100644 crates/forge/assets/vyper/ICounterTemplate.sol create mode 100644 crates/forge/assets/vyper/workflowTemplate.yml diff --git a/crates/forge/assets/CounterTemplate.s.sol b/crates/forge/assets/solidity/CounterTemplate.s.sol similarity index 100% rename from crates/forge/assets/CounterTemplate.s.sol rename to crates/forge/assets/solidity/CounterTemplate.s.sol diff --git a/crates/forge/assets/CounterTemplate.sol b/crates/forge/assets/solidity/CounterTemplate.sol similarity index 100% rename from crates/forge/assets/CounterTemplate.sol rename to crates/forge/assets/solidity/CounterTemplate.sol diff --git a/crates/forge/assets/CounterTemplate.t.sol b/crates/forge/assets/solidity/CounterTemplate.t.sol similarity index 100% rename from crates/forge/assets/CounterTemplate.t.sol rename to crates/forge/assets/solidity/CounterTemplate.t.sol diff --git a/crates/forge/assets/workflowTemplate.yml b/crates/forge/assets/solidity/workflowTemplate.yml similarity index 94% rename from crates/forge/assets/workflowTemplate.yml rename to crates/forge/assets/solidity/workflowTemplate.yml index 4481ec6a8b2de..7e97c03a36931 100644 --- a/crates/forge/assets/workflowTemplate.yml +++ b/crates/forge/assets/solidity/workflowTemplate.yml @@ -13,7 +13,7 @@ jobs: name: Foundry project runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 with: submodules: recursive diff --git a/crates/forge/assets/vyper/CounterTemplate.s.sol b/crates/forge/assets/vyper/CounterTemplate.s.sol new file mode 100644 index 0000000000000..aab0faa29c80c --- /dev/null +++ b/crates/forge/assets/vyper/CounterTemplate.s.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Script} from "forge-std/Script.sol"; +import {ICounter} from "../src/ICounter.sol"; + +contract CounterScript is Script { + ICounter public counter; + + function setUp() public {} + + function run() public { + vm.startBroadcast(); + + counter = ICounter(deployCode("src/Counter.vy")); + + vm.stopBroadcast(); + } +} diff --git a/crates/forge/assets/vyper/CounterTemplate.t.sol b/crates/forge/assets/vyper/CounterTemplate.t.sol new file mode 100644 index 0000000000000..b492089b190ea --- /dev/null +++ b/crates/forge/assets/vyper/CounterTemplate.t.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; +import {ICounter} from "../src/ICounter.sol"; + +contract CounterTest is Test { + ICounter public counter; + + function setUp() public { + counter = ICounter(deployCode("src/Counter.vy")); + counter.setNumber(0); + } + + function test_Increment() public { + counter.increment(); + assertEq(counter.number(), 1); + } + + function testFuzz_SetNumber(uint256 x) public { + counter.setNumber(x); + assertEq(counter.number(), x); + } +} diff --git a/crates/forge/assets/vyper/CounterTemplate.vy b/crates/forge/assets/vyper/CounterTemplate.vy new file mode 100644 index 0000000000000..f0a00db3d6209 --- /dev/null +++ b/crates/forge/assets/vyper/CounterTemplate.vy @@ -0,0 +1,11 @@ +# pragma version ~=0.4.3 + +number: public(uint256) + +@external +def setNumber(newNumber: uint256): + self.number = newNumber + +@external +def increment(): + self.number += 1 \ No newline at end of file diff --git a/crates/forge/assets/vyper/ICounterTemplate.sol b/crates/forge/assets/vyper/ICounterTemplate.sol new file mode 100644 index 0000000000000..a3219a951776f --- /dev/null +++ b/crates/forge/assets/vyper/ICounterTemplate.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.13; + +interface ICounter { + function number() external view returns (uint256); + function setNumber(uint256 newNumber) external; + function increment() external; +} diff --git a/crates/forge/assets/vyper/workflowTemplate.yml b/crates/forge/assets/vyper/workflowTemplate.yml new file mode 100644 index 0000000000000..7f4fc32ee5fbf --- /dev/null +++ b/crates/forge/assets/vyper/workflowTemplate.yml @@ -0,0 +1,52 @@ +name: CI + +on: + push: + pull_request: + workflow_dispatch: + +env: + FOUNDRY_PROFILE: ci + +jobs: + check: + name: Foundry project + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v5 + with: + submodules: recursive + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + + - name: Show Forge version + run: | + forge --version + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: "3.13" + architecture: x64 + + - name: Install Vyper + run: pip install git+https://github.com/vyperlang/vyper.git@master + + - name: Show the Vyper version + run: vyper --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/crates/forge/src/cmd/init.rs b/crates/forge/src/cmd/init.rs index 52b0b6a6a3b78..5790135fc753e 100644 --- a/crates/forge/src/cmd/init.rs +++ b/crates/forge/src/cmd/init.rs @@ -37,6 +37,10 @@ pub struct InitArgs { #[arg(long, conflicts_with = "template")] pub vscode: bool, + /// Initialize a Vyper project template. + #[arg(long, conflicts_with = "template")] + pub vyper: bool, + /// Use the parent git repository instead of initializing a new one. /// Only valid if the target is in a git repository. #[arg(long, conflicts_with = "template")] @@ -48,7 +52,8 @@ pub struct InitArgs { impl InitArgs { pub fn run(self) -> Result<()> { - let Self { root, template, branch, install, offline, force, vscode, use_parent_git } = self; + let Self { root, template, branch, install, offline, force, vscode, use_parent_git, vyper } = + self; let DependencyInstallOpts { shallow, no_git, commit } = install; // create the root dir if it does not exist @@ -123,15 +128,41 @@ impl InitArgs { let script = root.join("script"); fs::create_dir_all(&script)?; - // write the contract file - let contract_path = src.join("Counter.sol"); - fs::write(contract_path, include_str!("../../assets/CounterTemplate.sol"))?; - // write the tests - let contract_path = test.join("Counter.t.sol"); - fs::write(contract_path, include_str!("../../assets/CounterTemplate.t.sol"))?; - // write the script - let contract_path = script.join("Counter.s.sol"); - fs::write(contract_path, include_str!("../../assets/CounterTemplate.s.sol"))?; + if vyper { + // write the contract file + let contract_path = src.join("Counter.vy"); + fs::write(contract_path, include_str!("../../assets/vyper/CounterTemplate.vy"))?; + + // write the tests + let contract_path = test.join("Counter.t.sol"); + fs::write(contract_path, include_str!("../../assets/vyper/CounterTemplate.t.sol"))?; + + // write the script + let contract_path = script.join("Counter.s.sol"); + fs::write(contract_path, include_str!("../../assets/vyper/CounterTemplate.s.sol"))?; + } else { + // write the contract file + let contract_path = src.join("Counter.sol"); + fs::write( + contract_path, + include_str!("../../assets/solidity/CounterTemplate.sol"), + )?; + + // write the tests + let contract_path = test.join("Counter.t.sol"); + fs::write( + contract_path, + include_str!("../../assets/solidity/CounterTemplate.t.sol"), + )?; + + // write the script + let contract_path = script.join("Counter.s.sol"); + fs::write( + contract_path, + include_str!("../../assets/solidity/CounterTemplate.s.sol"), + )?; + } + // Write the default README file let readme_path = root.join("README.md"); fs::write(readme_path, include_str!("../../assets/README.md"))?; @@ -146,7 +177,7 @@ impl InitArgs { // set up the repo if !no_git { - init_git_repo(git, commit, use_parent_git)?; + init_git_repo(git, commit, use_parent_git, vyper)?; } // install forge-std @@ -177,7 +208,7 @@ impl InitArgs { /// Creates `.gitignore` and `.github/workflows/test.yml`, if they don't exist already. /// /// Commits everything in `root` if `commit` is true. -fn init_git_repo(git: Git<'_>, commit: bool, use_parent_git: bool) -> Result<()> { +fn init_git_repo(git: Git<'_>, commit: bool, use_parent_git: bool, vyper: bool) -> Result<()> { // `git init` if !git.is_in_repo()? || (!use_parent_git && !git.is_repo_root()?) { git.init()?; @@ -193,7 +224,12 @@ fn init_git_repo(git: Git<'_>, commit: bool, use_parent_git: bool) -> Result<()> let workflow = git.root.join(".github/workflows/test.yml"); if !workflow.exists() { fs::create_dir_all(workflow.parent().unwrap())?; - fs::write(workflow, include_str!("../../assets/workflowTemplate.yml"))?; + + if vyper { + fs::write(workflow, include_str!("../../assets/vyper/workflowTemplate.yml"))?; + } else { + fs::write(workflow, include_str!("../../assets/solidity/workflowTemplate.yml"))?; + } } // commit everything diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index a7f88567580a5..3fc36ef1a2de7 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -775,6 +775,31 @@ forgetest!(can_clone_keep_directory_structure, |prj, cmd| { let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); +// checks that init works with vyper flag +forgetest!(can_init_vyper_project, |prj, cmd| { + prj.wipe(); + + cmd.args(["init", "--vyper"]).arg(prj.root()).assert_success().stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]); + + assert!(prj.root().join("foundry.toml").exists()); + assert!(prj.root().join("lib/forge-std").exists()); + + assert!(prj.root().join("src").exists()); + assert!(prj.root().join("src").join("Counter.vy").exists()); + + assert!(prj.root().join("test").exists()); + assert!(prj.root().join("test").join("Counter.t.sol").exists()); + + assert!(prj.root().join("script").exists()); + assert!(prj.root().join("script").join("Counter.s.sol").exists()); +}); + // checks that clone works with raw src containing `node_modules` // forgetest!(can_clone_with_node_modules, |prj, cmd| { From 7756dbd35c7e1d7cb7fd4055cb4b20f1c0ea5e25 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 21 Aug 2025 13:17:26 +0200 Subject: [PATCH 2/6] make sure to include interface --- crates/forge/src/cmd/init.rs | 2 ++ crates/forge/tests/cli/cmd.rs | 1 + 2 files changed, 3 insertions(+) diff --git a/crates/forge/src/cmd/init.rs b/crates/forge/src/cmd/init.rs index 5790135fc753e..f46840e6fdb53 100644 --- a/crates/forge/src/cmd/init.rs +++ b/crates/forge/src/cmd/init.rs @@ -132,6 +132,8 @@ impl InitArgs { // write the contract file let contract_path = src.join("Counter.vy"); fs::write(contract_path, include_str!("../../assets/vyper/CounterTemplate.vy"))?; + let interface_path = src.join("ICounter.sol"); + fs::write(interface_path, include_str!("../../assets/vyper/ICounterTemplate.sol"))?; // write the tests let contract_path = test.join("Counter.t.sol"); diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 3fc36ef1a2de7..07cd8a14cc5a6 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -792,6 +792,7 @@ Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std assert!(prj.root().join("src").exists()); assert!(prj.root().join("src").join("Counter.vy").exists()); + assert!(prj.root().join("src").join("ICounter.sol").exists()); assert!(prj.root().join("test").exists()); assert!(prj.root().join("test").join("Counter.t.sol").exists()); From 187179ca028c0630d05772bc7919f3dc1e920a34 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 21 Aug 2025 17:40:34 +0200 Subject: [PATCH 3/6] address feedback --- crates/forge/assets/solidity/workflowTemplate.yml | 12 ++++-------- crates/forge/assets/vyper/workflowTemplate.yml | 14 ++++---------- 2 files changed, 8 insertions(+), 18 deletions(-) diff --git a/crates/forge/assets/solidity/workflowTemplate.yml b/crates/forge/assets/solidity/workflowTemplate.yml index 7e97c03a36931..c000ae31bbe3e 100644 --- a/crates/forge/assets/solidity/workflowTemplate.yml +++ b/crates/forge/assets/solidity/workflowTemplate.yml @@ -21,20 +21,16 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 - name: Show Forge version - run: | - forge --version + run: forge --version - name: Run Forge fmt - run: | - forge fmt --check + run: forge fmt --check id: fmt - name: Run Forge build - run: | - forge build --sizes + run: forge build --sizes id: build - name: Run Forge tests - run: | - forge test -vvv + run: forge test -vvv id: test diff --git a/crates/forge/assets/vyper/workflowTemplate.yml b/crates/forge/assets/vyper/workflowTemplate.yml index 7f4fc32ee5fbf..6565ef30002b6 100644 --- a/crates/forge/assets/vyper/workflowTemplate.yml +++ b/crates/forge/assets/vyper/workflowTemplate.yml @@ -26,27 +26,21 @@ jobs: - name: Setup Python uses: actions/setup-python@v5 - with: - python-version: "3.13" - architecture: x64 - name: Install Vyper - run: pip install git+https://github.com/vyperlang/vyper.git@master + run: pip install git+https://github.com/vyperlang/vyper.git@v0.4.3 - name: Show the Vyper version run: vyper --version - name: Run Forge fmt - run: | - forge fmt --check + run: forge fmt --check id: fmt - name: Run Forge build - run: | - forge build --sizes + run: forge build --sizes id: build - name: Run Forge tests - run: | - forge test -vvv + run: forge test -vvv id: test From 81fc919b2794da4caf4efb518d01b4f3f68cf82a Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 21 Aug 2025 17:46:46 +0200 Subject: [PATCH 4/6] nits, python-version: "3.x" is required --- crates/forge/assets/vyper/workflowTemplate.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/forge/assets/vyper/workflowTemplate.yml b/crates/forge/assets/vyper/workflowTemplate.yml index 6565ef30002b6..4b3fa8a79c6e7 100644 --- a/crates/forge/assets/vyper/workflowTemplate.yml +++ b/crates/forge/assets/vyper/workflowTemplate.yml @@ -21,11 +21,12 @@ jobs: uses: foundry-rs/foundry-toolchain@v1 - name: Show Forge version - run: | - forge --version + run: forge --version - name: Setup Python uses: actions/setup-python@v5 + with: + python-version: "3.x" - name: Install Vyper run: pip install git+https://github.com/vyperlang/vyper.git@v0.4.3 From ea2e831ffa38ec0a37ffd93779b5fbf3fa11122f Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Thu, 21 Aug 2025 17:55:55 +0200 Subject: [PATCH 5/6] add basic smoke test for regular forge init to make sure no regression was introduced --- crates/forge/tests/cli/cmd.rs | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/crates/forge/tests/cli/cmd.rs b/crates/forge/tests/cli/cmd.rs index 07cd8a14cc5a6..0f0b040e8d3c4 100644 --- a/crates/forge/tests/cli/cmd.rs +++ b/crates/forge/tests/cli/cmd.rs @@ -775,7 +775,35 @@ forgetest!(can_clone_keep_directory_structure, |prj, cmd| { let _config: BasicConfig = parse_with_profile(&s).unwrap().unwrap().1; }); -// checks that init works with vyper flag +// checks that `forge init` works. +forgetest!(can_init_project, |prj, cmd| { + prj.wipe(); + + cmd.args(["init"]).arg(prj.root()).assert_success().stdout_eq(str![[r#" +Initializing [..]... +Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std"), tag: None) + Installed forge-std[..] + Initialized forge project + +"#]]); + + assert!(prj.root().join("foundry.toml").exists()); + assert!(prj.root().join("lib/forge-std").exists()); + + assert!(prj.root().join("src").exists()); + assert!(prj.root().join("src").join("Counter.sol").exists()); + + assert!(prj.root().join("test").exists()); + assert!(prj.root().join("test").join("Counter.t.sol").exists()); + + assert!(prj.root().join("script").exists()); + assert!(prj.root().join("script").join("Counter.s.sol").exists()); + + assert!(prj.root().join(".github").join("workflows").exists()); + assert!(prj.root().join(".github").join("workflows").join("test.yml").exists()); +}); + +// checks that `forge init --vyper` works. forgetest!(can_init_vyper_project, |prj, cmd| { prj.wipe(); @@ -799,6 +827,9 @@ Installing forge-std in [..] (url: Some("https://github.com/foundry-rs/forge-std assert!(prj.root().join("script").exists()); assert!(prj.root().join("script").join("Counter.s.sol").exists()); + + assert!(prj.root().join(".github").join("workflows").exists()); + assert!(prj.root().join(".github").join("workflows").join("test.yml").exists()); }); // checks that clone works with raw src containing `node_modules` From 3d2f3d91feff74bcd3deea61b5634ff2247207f8 Mon Sep 17 00:00:00 2001 From: zerosnacks Date: Fri, 22 Aug 2025 13:24:24 +0200 Subject: [PATCH 6/6] remove ids --- crates/forge/assets/solidity/workflowTemplate.yml | 3 --- crates/forge/assets/vyper/workflowTemplate.yml | 3 --- 2 files changed, 6 deletions(-) diff --git a/crates/forge/assets/solidity/workflowTemplate.yml b/crates/forge/assets/solidity/workflowTemplate.yml index c000ae31bbe3e..57bdb22cdfc13 100644 --- a/crates/forge/assets/solidity/workflowTemplate.yml +++ b/crates/forge/assets/solidity/workflowTemplate.yml @@ -25,12 +25,9 @@ jobs: - 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/crates/forge/assets/vyper/workflowTemplate.yml b/crates/forge/assets/vyper/workflowTemplate.yml index 4b3fa8a79c6e7..72fb01522a2b3 100644 --- a/crates/forge/assets/vyper/workflowTemplate.yml +++ b/crates/forge/assets/vyper/workflowTemplate.yml @@ -36,12 +36,9 @@ jobs: - 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