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

feat: add support for basic vyper project using forge init --vyper #9930

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
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
23 changes: 23 additions & 0 deletions crates/forge/assets/vyper/CounterTemplate.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Script} from "forge-std/Script.sol";
import {VyperDeployer} from "../src/utils/VyperDeployer.sol";

contract CounterScript is Script {
uint256 public constant INITIAL_COUNTER = 42;

function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");

VyperDeployer vyperDeployer = new VyperDeployer();

vm.startBroadcast(deployerPrivateKey);

address deployedAddress = vyperDeployer.deployContract("Counter", abi.encode(INITIAL_COUNTER));

require(deployedAddress != address(0), "Could not deploy contract");

vm.stopBroadcast();
}
}
36 changes: 36 additions & 0 deletions crates/forge/assets/vyper/CounterTemplate.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Test} from "forge-std/Test.sol";
import {ICounter} from "../src/interface/ICounter.sol";
import {VyperDeployer} from "../src/utils/VyperDeployer.sol";

contract CounterTest is Test {
VyperDeployer public vyperDeployer;
ICounter public counterContract;
uint256 public constant INITIAL_COUNTER = 42;

function setUp() public {
vyperDeployer = new VyperDeployer();
counterContract = ICounter(vyperDeployer.deployContract("Counter", abi.encode(INITIAL_COUNTER)));
}

function test_getCounter() public view {
uint256 counter = counterContract.counter();
assertEq(counter, INITIAL_COUNTER);
}

function test_setCounter() public {
uint256 newCounter = 100;
counterContract.set_counter(newCounter);
uint256 counter = counterContract.counter();
assertEq(counter, newCounter);
}

function test_increment() public {
uint256 counterBefore = counterContract.counter();
counterContract.increment();
uint256 counterAfter = counterContract.counter();
assertEq(counterAfter, counterBefore + 1);
}
}
14 changes: 14 additions & 0 deletions crates/forge/assets/vyper/CounterTemplate.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
counter: public(uint256)

@deploy
@payable
def __init__(initial_counter: uint256):
self.counter = initial_counter

@external
def set_counter(new_counter: uint256):
self.counter = new_counter

@external
def increment():
self.counter += 1
8 changes: 8 additions & 0 deletions crates/forge/assets/vyper/ICounterTemplate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

interface ICounter {
function counter() external view returns (uint256);
function set_counter(uint256 new_counter) external;
function increment() external;
}
67 changes: 67 additions & 0 deletions crates/forge/assets/vyper/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
## 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/

## Getting Started

### Prerequisites

- [Vyper](https://vyper.readthedocs.io/)


## Usage

### Build

```shell
$ forge build
```

### Test

```shell
$ forge test
```

### Format

```shell
$ forge fmt
```

### Anvil

```shell
$ anvil
```

### Deploy

```shell
$ forge script script/Counter.s.sol:CounterScript --rpc-url <your_rpc_url> --private-key <your_private_key>
```

### Cast

```shell
$ cast <subcommand>
```

### Help

```shell
$ forge --help
$ anvil --help
$ cast --help
```
87 changes: 87 additions & 0 deletions crates/forge/assets/vyper/VyperDeployerTemplate.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.13;

import {Vm} from "forge-std/Vm.sol";

contract VyperDeployer {
address private constant HEVM_ADDRESS = address(uint160(uint256(keccak256("hevm cheat code"))));
Vm private constant vm = Vm(HEVM_ADDRESS);
// Base directory for Vyper contracts
string private constant BASE_PATH = "src/";

/**
* Compiles a Vyper contract and returns the `CREATE` address.
* @param fileName The file name of the Vyper contract.
* @return deployedAddress The address calculated through create operation.
*/
function deployContract(string memory fileName) public returns (address) {
return deployContract(BASE_PATH, fileName, "");
}

/**
* Compiles a Vyper contract and returns the `CREATE` address.
* @param fileName The file name of the Vyper contract.
* @param args The constructor arguments for the contract
* @return deployedAddress The address calculated through create operation.
*/
function deployContract(string memory fileName, bytes memory args) public returns (address) {
return deployContract(BASE_PATH, fileName, args);
}

/**
* Compiles a Vyper contract with constructor arguments and returns the `CREATE` address.
* @param basePath The base directory path where the Vyper contract is located
* @param fileName The file name of the Vyper contract.
* @param args The constructor arguments for the contract
* @return deployedAddress The address calculated through create operation.
*/
function deployContract(string memory basePath, string memory fileName, bytes memory args)
public
returns (address)
{
// Compile the Vyper contract
bytes memory bytecode = compileVyperContract(basePath, fileName);

// Add constructor arguments if provided
if (args.length > 0) {
bytecode = abi.encodePacked(bytecode, args);
}

// Deploy the contract
address deployedAddress = deployBytecode(bytecode);

// Return the deployed address
return deployedAddress;
}

/**
* Compiles a Vyper contract and returns the bytecode
* @param basePath The base directory path where the Vyper contract is located
* @param fileName The file name of the Vyper contract
* @return The compiled bytecode of the contract
*/
function compileVyperContract(string memory basePath, string memory fileName) internal returns (bytes memory) {
// create a list of strings with the commands necessary to compile Vyper contracts
string[] memory cmds = new string[](2);
cmds[0] = "vyper";
cmds[1] = string.concat(basePath, fileName, ".vy");

// compile the Vyper contract and return the bytecode
return vm.ffi(cmds);
}

/**
* Deploys bytecode using the create instruction
* @param bytecode - The bytecode to deploy
* @return deployedAddress The address calculated through create operation.
*/
function deployBytecode(bytes memory bytecode) internal returns (address deployedAddress) {
// deploy the bytecode with the create instruction
assembly {
deployedAddress := create(0, add(bytecode, 0x20), mload(bytecode))
}

// check that the deployment was successful
require(deployedAddress != address(0), "VyperDeployer could not deploy contract");
}
}
79 changes: 79 additions & 0 deletions crates/forge/assets/vyper/workflowTemplate.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
name: test

on: [push, pull_request, workflow_dispatch]

env:
FOUNDRY_PROFILE: ci

jobs:
check:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os:
- ubuntu-latest
python_version:
- 3.12
architecture:
- x64
node_version:
- 20

steps:
- name: Checkout
uses: actions/checkout@v4
with:
submodules: recursive

- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python_version }}
architecture: ${{ matrix.architecture }}

- name: Install latest Vyper
run: pip install --force-reinstall vyper

- name: Show the Vyper version
run: vyper --version

- name: Install pnpm
uses: pnpm/action-setup@v3
with:
version: latest
run_install: false

- name: Get pnpm cache directory path
id: pnpm-cache-dir-path
run: echo "dir=$(pnpm store path --silent)" >> $GITHUB_OUTPUT

- name: Restore pnpm cache
uses: actions/cache@v4
id: pnpm-cache
with:
path: ${{ steps.pnpm-cache-dir-path.outputs.dir }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
restore-keys: ${{ runner.os }}-pnpm-store-

- name: Use Node.js ${{ matrix.node_version }}
uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node_version }}

- name: Install pnpm project with a clean slate
run: pnpm install --prefer-offline --frozen-lockfile

- name: Install Foundry
uses: foundry-rs/foundry-toolchain@v1
with:
version: nightly

- name: Show the Foundry CI config
run: forge config
env:
FOUNDRY_PROFILE: ci

- name: Foundry tests
run: forge test
env:
FOUNDRY_PROFILE: ci
Loading