Skip to content
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
2 changes: 1 addition & 1 deletion Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ foundry-wallets = { path = "crates/wallets" }
foundry-linking = { path = "crates/linking" }

# arbos-revm
arbos-revm = { git = "https://github.com/iosiro/arbos-revm", rev = "39794a701235cdb835efc185e3920ebd032f1bd6", default-features = false }
arbos-revm = { git = "https://github.com/iosiro/arbos-revm", version = "0.1.0", default-features = false }

# solc & compilation utilities
foundry-block-explorers = { version = "0.22.0", default-features = false }
Expand Down
43 changes: 40 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ This project was developed by [iosiro](https://www.iosiro.com/) as part of the [
## Features

- **Native Stylus Execution**: Execute Stylus WASM programs directly in Forge tests without requiring a network fork
- **Stylus Deployment Cheatcodes**: Deploy Stylus contracts using `vm.deployStylusCode()` and `vm.getStylusCode()`
- **Stylus Deployment Cheatcodes**: Deploy Stylus contracts using `vm.deployStylusCode()`, `vm.getStylusCode()`, and `vm.getStylusInitCode()`
- **Brotli Compression**: Built-in `vm.brotliCompress()` and `vm.brotliDecompress()` cheatcodes for Stylus bytecode handling
- **ArbOS State**: Automatic initialization of ArbOS state with configurable parameters
- **Arbitrum Precompiles**: Full support for 13 Arbitrum-specific precompiles (see [Supported Precompiles](#supported-precompiles))
Expand Down Expand Up @@ -141,8 +141,45 @@ address deployed = vm.deployStylusCode(string artifactPath, bytes32 salt);

// Get Stylus bytecode (compressed with magic prefix)
bytes memory code = vm.getStylusCode(string artifactPath);

// Get init code for CREATE/CREATE2 deployment
bytes memory initCode = vm.getStylusInitCode(string artifactPath);
```

#### How `deployStylusCode` Works

`deployStylusCode` delegates to the **StylusDeployer** contract (at `0xcEcba2F1DC234f70Dd89F2041029807F8D03A990` on Arbitrum), which atomically:

1. Deploys the compressed bytecode via CREATE (or CREATE2 when a `salt` is provided)
2. Activates the program via the ARB_WASM precompile (paying the activation data fee)
3. Calls the Stylus constructor if `constructorArgs` are provided

This produces a **single CALL transaction** when broadcasting, ensuring on-chain replay deploys a fully activated contract.

In local tests (no broadcast), the StylusDeployer is deployed on-demand. When broadcasting, the StylusDeployer must already exist on-chain (it is pre-deployed on Arbitrum networks). A custom deployer address can be configured via:

```toml
# foundry.toml
[profile.default.stylus]
deployer_address = "0x..."
```

#### Broadcasting Stylus Deployments

`deployStylusCode` is fully compatible with `forge script --broadcast`:

```solidity
contract DeployStylus is Script {
function run() external {
vm.startBroadcast();
address deployed = vm.deployStylusCode("path/to/program.wasm");
vm.stopBroadcast();
}
}
```

The broadcast captures a single transaction: a CALL to the StylusDeployer with the activation data fee automatically estimated (actual fee + 20% buffer). The StylusDeployer refunds any excess ETH to the sender.

### Brotli Compression

```solidity
Expand All @@ -155,7 +192,7 @@ bytes memory decompressed = vm.brotliDecompress(bytes compressed);

## WASM Processing

When you use `vm.deployStylusCode()` or `vm.getStylusCode()`, the WASM binary is automatically processed to match the behavior of `cargo stylus deploy`:
When you use `vm.deployStylusCode()`, `vm.getStylusCode()`, or `vm.getStylusInitCode()`, the WASM binary is automatically processed to match the behavior of `cargo stylus deploy`:

### 1. Metadata Stripping

Expand Down Expand Up @@ -390,7 +427,7 @@ This fork is based on Foundry v1.5.1 with the following changes:

- **Added**: Native Stylus/WASM execution via [arbos-revm](https://github.com/iosiro/arbos-revm)
- **Added**: ArbOS state initialization with configurable parameters
- **Added**: Stylus deployment cheatcodes (`deployStylusCode`, `getStylusCode`)
- **Added**: Stylus deployment cheatcodes (`deployStylusCode`, `getStylusCode`, `getStylusInitCode`)
- **Added**: Brotli compression cheatcodes (`brotliCompress`, `brotliDecompress`)
- **Added**: 13 Arbitrum precompiles (ArbSys, ArbWasm, ArbGasInfo, etc.)
- **Added**: Stylus configuration options (CLI, foundry.toml, inline)
Expand Down
22 changes: 21 additions & 1 deletion crates/cheatcodes/assets/cheatcodes.json

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

10 changes: 9 additions & 1 deletion crates/cheatcodes/spec/src/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2040,12 +2040,20 @@ interface Vm {
#[cheatcode(group = Filesystem)]
function deployStylusCode(string calldata artifactPath, bytes calldata constructorArgs, uint256 value, bytes32 salt) external returns (address deployedAddress);

/// Returns the deployment bytecode for a Stylus contract suitable for use with the StylusDeployer contract.
/// Returns the compressed and prefixed Stylus bytecode (runtime code) for a contract.
/// Takes in the relative path to the WASM or Brotli compressed WASM binary.
/// Applies the same compression and prefixing logic as `deployStylusCode`.
/// This returns raw runtime bytecode without an init code wrapper, suitable for use with `vm.etch`.
#[cheatcode(group = Filesystem)]
function getStylusCode(string calldata artifactPath) external view returns (bytes memory);

/// Returns init code for a Stylus contract suitable for CREATE/CREATE2 or the StylusDeployer contract.
/// Takes in the relative path to the WASM or Brotli compressed WASM binary.
/// The returned bytecode wraps the compressed Stylus code in EVM init code that
/// deploys it as contract code when executed via CREATE or CREATE2.
#[cheatcode(group = Filesystem)]
function getStylusInitCode(string calldata artifactPath) external view returns (bytes memory);

/// Compresses the given data using Brotli compression (quality: 11, window: 22).
#[cheatcode(group = String)]
function brotliCompress(bytes calldata data) external pure returns (bytes memory compressed);
Expand Down
14 changes: 11 additions & 3 deletions crates/cheatcodes/src/inspector.rs
Original file line number Diff line number Diff line change
Expand Up @@ -890,7 +890,7 @@ impl Cheatcodes {
self.apply_accesslist(ecx);

// Apply our broadcast
if let Some(broadcast) = &self.broadcast {
if let Some(broadcast) = &mut self.broadcast {
// Additional check as transfers in forge scripts seem to be estimated at 2300
// by revm leading to "Intrinsic gas too low" failure when simulated on chain.
let is_fixed_gas_limit = call.gas_limit >= 21_000 && !self.dynamic_gas_limit;
Expand All @@ -899,8 +899,16 @@ impl Cheatcodes {
// We only apply a broadcast *to a specific depth*.
//
// We do this because any subsequent contract calls *must* exist on chain and
// we only want to grab *this* call, not internal ones
if curr_depth == broadcast.depth && call.caller == broadcast.original_caller {
// we only want to grab *this* call, not internal ones.
// Additionally, `call_from_code` allows cheatcode-internal exec_call to be
// captured as a broadcastable CALL (e.g., deployStylusCode via StylusDeployer).
let is_call_from_code = broadcast.call_from_code;
if is_call_from_code {
broadcast.call_from_code = false;
}
if (curr_depth == broadcast.depth && call.caller == broadcast.original_caller)
|| is_call_from_code
{
// At the target depth we set `msg.sender` & tx.origin.
// We are simulating the caller as being an EOA, so *both* must be set to the
// broadcast.origin.
Expand Down
4 changes: 4 additions & 0 deletions crates/cheatcodes/src/script.rs
Original file line number Diff line number Diff line change
Expand Up @@ -287,6 +287,9 @@ pub struct Broadcast {
pub single_call: bool,
/// Whether `vm.deployCode` cheatcode is used to deploy from code.
pub deploy_from_code: bool,
/// Whether a cheatcode's internal `exec_call` should be captured as a broadcastable CALL.
/// Analogous to `deploy_from_code` but for CALLs instead of CREATEs.
pub call_from_code: bool,
}

/// Contains context for wallet management.
Expand Down Expand Up @@ -386,6 +389,7 @@ fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bo
depth,
single_call,
deploy_from_code: false,
call_from_code: false,
};
debug!(target: "cheatcodes", ?broadcast, "started");
ccx.state.broadcast = Some(broadcast);
Expand Down
Loading
Loading