Skip to content

Commit

Permalink
Merge pull request #236 from OffchainLabs/stylus-nitro-conflicts
Browse files Browse the repository at this point in the history
Resolve Stylus Nitro Conflicts
  • Loading branch information
rachel-bousfield authored May 3, 2024
2 parents 46c29f5 + 328b83c commit 3b3a1a4
Show file tree
Hide file tree
Showing 100 changed files with 2,540 additions and 756 deletions.
10 changes: 10 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -316,5 +316,15 @@ RUN export DEBIAN_FRONTEND=noninteractive && \

USER user

FROM nitro-node-dev as nitro-node-split
USER root

RUN export DEBIAN_FRONTEND=noninteractive && \
apt-get update && \
apt-get install -y xxd netcat-traditional
COPY scripts/split-val-entry.sh /usr/local/bin
ENTRYPOINT [ "/usr/local/bin/split-val-entry.sh" ]
USER user

FROM nitro-node as nitro-node-default
# Just to ensure nitro-node-dist is default
89 changes: 29 additions & 60 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,89 +1,58 @@
<br />
<p align="center">
<a href="https://arbitrum.io/">
<img src="https://arbitrum.io/assets/stylus/stylus_with_paint_bg.png" alt="Logo" width="100%">
<img src="https://arbitrum.io/assets/arbitrum/logo_color.png" alt="Logo" width="80" height="80">
</a>

<h3 align="center">Arbitrum Nitro</h3>

<p align="center">
<a href="https://developer.arbitrum.io/"><strong>Next Generation Ethereum L2 Technology »</strong></a>
<br />
</p>
</p>

## About Arbitrum Stylus

Stylus is a next-gen programming environment for Arbitrum chains. Through the power of WebAssembly smart contracts, users can deploy programs written in their favorite programming languages, including Rust, C, and C++, to run alongside EVM smart contracts on Arbitrum. It's over an order of magnitude faster, slashes fees, and is fully interoperable with the Ethereum Virtual Machine.

This repo is a fork of [Arbitrum Nitro][Nitro] and is designed as an upgrade for all Arbitrum chains. Included is the Stylus VM and working fraud prover. If you are looking to write and deploy Stylus programs, please see the following SDKs.

| Repo | Use cases | License |
|:-------------------------------|:----------------------------|:------------------|
| [Rust SDK][Rust] | Everything! | Apache 2.0 or MIT |
| [C/C++ SDK][C] | Cryptography and algorithms | Apache 2.0 or MIT |
| [Bf SDK][Bf] | Educational | Apache 2.0 or MIT |
| [Cargo Stylus CLI Tool][Cargo] | Program deployment | Apache 2.0 or MIT |

[Nitro]: https://github.com/OffchainLabs/nitro
[Orbit]: https://docs.arbitrum.io/launch-orbit-chain/orbit-gentle-introduction

Stylus is entirely opt-in. Devs familiar with Solidity can continue to enjoy Arbitrum's EVM-equivalent experience without any changes. This is because Stylus is entirely additive &mdash; a model we call EVM+. Stylus introduces a second, fully composible virtual machine for executing WebAssembly that coordinates with the EVM to produce state transitions. And since the Stylus SDK uses solidity ABIs, a contract written in one language can call out to any other.

For example, existing Solidity DEXs can &mdash; without modifications &mdash; list Rust ERC20 tokens, which might call out to C programs to do cryptography. Everything is fully interoperable, so users never have to care about the specific language or implementation details of the contracts they call.

## Roadmap
## About Arbitrum Nitro

Stylus is currently testnet-only and not recommended for production use. This will change as we complete an audit and add additional features.
<img src="https://arbitrum.io/assets/arbitrum/logo_color.png" alt="Logo" width="80" height="80">

Arbitrum [Orbit L3s][Orbit] may opt into Stylus at any time. Arbitrum One and Arbitrum Nova will upgrade to Stylus should the DAO vote for it.
Nitro is the latest iteration of the Arbitrum technology. It is a fully integrated, complete
layer 2 optimistic rollup system, including fraud proofs, the sequencer, the token bridges,
advanced calldata compression, and more.

If you'd like to be a part of this journey, join us in the `#stylus` channel on [Discord][discord]!
See the live docs-site [here](https://developer.arbitrum.io/) (or [here](https://github.com/OffchainLabs/arbitrum-docs) for markdown docs source.)

## Gas Pricing
See [here](./audits) for security audit reports.

Stylus introduces new pricing models for WASM programs. Intended for high-compute applications, Stylus makes the following more affordable:
The Nitro stack is built on several innovations. At its core is a new prover, which can do Arbitrum’s classic
interactive fraud proofs over WASM code. That means the L2 Arbitrum engine can be written and compiled using
standard languages and tools, replacing the custom-designed language and compiler used in previous Arbitrum
versions. In normal execution,
validators and nodes run the Nitro engine compiled to native code, switching to WASM if a fraud proof is needed.
We compile the core of Geth, the EVM engine that practically defines the Ethereum standard, right into Arbitrum.
So the previous custom-built EVM emulator is replaced by Geth, the most popular and well-supported Ethereum client.

- Compute, which is generally **10-100x** cheaper depending on the program. This is primarily due to the efficiency of the WASM runtime relative to the EVM, and the quality of the code produced by Rust, C, and C++ compilers. Another factor that matters is the quality of the code itself. For example, highly optimized and audited C libraries that implement a particular cryptographic operation are usually deployable without modification and perform exceptionally well. The fee reduction may be smaller for highly optimized Solidity that makes heavy use of native precompiles vs an unoptimized Stylus equivalent that doesn't do the same.
The last piece of the stack is a slimmed-down version of our ArbOS component, rewritten in Go, which provides the
rest of what’s needed to run an L2 chain: things like cross-chain communication, and a new and improved batching
and compression system to minimize L1 costs.

- Memory, which is **100-500x** cheaper due to Stylus's novel exponential pricing mechanism intended to address Vitalik's concerns with the EVM's per-call, [quadratic memory][quadratic] pricing policy. For the first time ever, high-memory applications are possible on an EVM-equivalent chain.
Essentially, Nitro runs Geth at layer 2 on top of Ethereum, and can prove fraud over the core engine of Geth
compiled to WASM.

- Storage, for which the Rust SDK promotes better access patterns and type choices. Note that while the underlying [`SLOAD`][SLOAD] and [`SSTORE`][SSTORE] operations cost as they do in the EVM, the Rust SDK implements an optimal caching policy that minimizes their use. Exact savings depends on the program.

- VM affordances, including common operations like keccak and reentrancy detection. No longer is it expensive to make safety the default.

There are, however, minor overheads to using Stylus that may matter to your application:

- The first time a WASM is deployed, it must be _activated_. This is generally a few million gas, though to avoid testnet DoS, we've set it to a fixed 14 million. Note that you do not have to activate future copies of the same program. For example, the same NFT template can be deployed many times without paying this cost more than once. We will soon make the fees paid depend on the program, so that the gas used is based on the complexity of the WASM instead of this very conservative, worst-case estimate.

- Calling a Stylus program costs 200-2000 gas. We're working with Wasmer to improve setup costs, but there will likely always be some amount of gas one pays to jump into WASM execution. This means that if a contract does next to nothing, it may be cheaper in Solidity. However if a contract starts doing interesting work, the dynamic fees will quickly make up for this fixed-cost overhead.

Though conservative bounds have been chosen for testnet, all of this is subject to change as pricing models mature and further optimizations are made. Since gas numbers will vary across updates, it may make more sense to clock the time it takes to perform an operation rather than going solely by the numbers reported in receipts.

[quadratic]: https://notes.ethereum.org/@vbuterin/proposals_to_adjust_memory_gas_costs
[SLOAD]: https://www.evm.codes/#54
[SSTORE]: https://www.evm.codes/#55
Arbitrum One successfully migrated from the Classic Arbitrum stack onto Nitro on 8/31/22. (See [state migration](https://developer.arbitrum.io/migration/state-migration) and [dapp migration](https://developer.arbitrum.io/migration/dapp_migration) for more info).

## License

We currently have the Stylus VM and fraud prover (the contents of this repo) [licensed](./LICENSE) under a Business Source License, similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains.
Nitro is currently licensed under a [Business Source License](./LICENSE.md), similar to our friends at Uniswap and Aave, with an "Additional Use Grant" to ensure that everyone can have full comfort using and running nodes on all public Arbitrum chains.

The Stylus SDK, however, is licensed under different terms. Please see each repo below for more information.
The Additional Use Grant also permits the deployment of the Nitro software, in a permissionless fashion and without cost, as a new blockchain provided that the chain settles to either Arbitrum One or Arbitrum Nova.

| Repo | Use cases | License |
|:-------------------------------|:----------------------------|:------------------|
| [Rust SDK][Rust] | Everything! | Apache 2.0 or MIT |
| [C/C++ SDK][C] | Cryptography and algorithms | Apache 2.0 or MIT |
| [Bf SDK][Bf] | Educational | Apache 2.0 or MIT |
| [Cargo Stylus CLI Tool][Cargo] | Program deployment | Apache 2.0 or MIT |

[Rust]: https://github.com/OffchainLabs/stylus-sdk-rs
[C]: https://github.com/OffchainLabs/stylus-sdk-c
[Bf]: https://github.com/OffchainLabs/stylus-sdk-bf
[Cargo]: https://github.com/OffchainLabs/cargo-stylus
For those that prefer to deploy the Nitro software either directly on Ethereum (i.e. an L2) or have it settle to another Layer-2 on top of Ethereum, the [Arbitrum Expansion Program (the "AEP")](https://docs.arbitrum.foundation/assets/files/Arbitrum%20Expansion%20Program%20Jan182024-4f08b0c2cb476a55dc153380fa3e64b0.pdf) was recently established. The AEP allows for the permissionless deployment in the aforementioned fashion provided that 10% of net revenue (as more fully described in the AEP) is contributed back to the Arbitrum community in accordance with the requirements of the AEP.

## Contact

Discord - [Arbitrum][discord]
Discord - [Arbitrum](https://discord.com/invite/5KE54JwyTs)

Twitter: [Arbitrum](https://twitter.com/arbitrum)

Twitter - [OffchainLabs](https://twitter.com/OffchainLabs)

[discord]: https://discord.com/invite/5KE54JwyTs
1 change: 1 addition & 0 deletions arbitrator/Cargo.lock

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

6 changes: 6 additions & 0 deletions arbitrator/caller-env/src/guest_ptr.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,9 @@ impl Deref for GuestPtr {
&self.0
}
}

impl GuestPtr {
pub fn to_u64(self) -> u64 {
self.into()
}
}
1 change: 1 addition & 0 deletions arbitrator/prover/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ num-traits = "0.2.17"
c-kzg = { version = "0.4.0", optional = true } # TODO: look into switching to rust-kzg (no crates.io release or hosted rustdoc yet)
sha2 = "0.9.9"
lru = "0.12.3"
once_cell = "1.19.0"

[lib]
name = "prover"
Expand Down
74 changes: 40 additions & 34 deletions arbitrator/prover/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ use machine::{
argument_data_to_inbox, get_empty_preimage_resolver, GlobalState, MachineStatus,
PreimageResolver,
};
use once_cell::sync::OnceCell;
use static_assertions::const_assert_eq;
use std::{
ffi::CStr,
Expand All @@ -45,7 +46,7 @@ use std::{
use utils::CBytes;

lazy_static::lazy_static! {
static ref BLOBHASH_PREIMAGE_CACHE: Mutex<LruCache<Bytes32, CBytes>> = Mutex::new(LruCache::new(NonZeroUsize::new(12).unwrap()));
static ref BLOBHASH_PREIMAGE_CACHE: Mutex<LruCache<Bytes32, Arc<OnceCell<CBytes>>>> = Mutex::new(LruCache::new(NonZeroUsize::new(12).unwrap()));
}

#[repr(C)]
Expand Down Expand Up @@ -332,28 +333,32 @@ pub struct ResolvedPreimage {
pub len: isize, // negative if not found
}

macro_rules! handle_preimage_resolution {
($context:expr, $ty:expr, $hash:expr, $resolver:expr) => {{
let res = $resolver($context, $ty.into(), $hash.as_ptr());
if res.len < 0 {
return None;
}
let data = CBytes::from_raw_parts(res.ptr, res.len as usize);
// Check if preimage rehashes to the provided hash
match crate::utils::hash_preimage(&data, $ty) {
Ok(have_hash) if have_hash.as_slice() == *$hash => {}
Ok(got_hash) => panic!(
"Resolved incorrect data for hash {} (rehashed to {})",
$hash,
Bytes32(got_hash),
),
Err(err) => panic!(
"Failed to hash preimage from resolver (expecting hash {}): {}",
$hash, err,
),
}
Some(data)
}};
#[cfg(feature = "native")]
unsafe fn handle_preimage_resolution(
context: u64,
ty: PreimageType,
hash: Bytes32,
resolver: unsafe extern "C" fn(u64, u8, *const u8) -> ResolvedPreimage,
) -> Option<CBytes> {
let res = resolver(context, ty.into(), hash.as_ptr());
if res.len < 0 {
return None;
}
let data = CBytes::from_raw_parts(res.ptr, res.len as usize);
// Check if preimage rehashes to the provided hash
match crate::utils::hash_preimage(&data, ty) {
Ok(have_hash) if have_hash.as_slice() == *hash => {}
Ok(got_hash) => panic!(
"Resolved incorrect data for hash {} (rehashed to {})",
hash,
Bytes32(got_hash),
),
Err(err) => panic!(
"Failed to hash preimage from resolver (expecting hash {}): {}",
hash, err,
),
}
Some(data)
}

#[no_mangle]
Expand All @@ -364,18 +369,19 @@ pub unsafe extern "C" fn arbitrator_set_preimage_resolver(
) {
(*mach).set_preimage_resolver(Arc::new(
move |context: u64, ty: PreimageType, hash: Bytes32| -> Option<CBytes> {
if let PreimageType::EthVersionedHash = ty {
let mut cache = BLOBHASH_PREIMAGE_CACHE.lock().unwrap();
if cache.contains(&hash) {
return cache.get(&hash).cloned();
}
if let Some(data) = handle_preimage_resolution!(context, ty, hash, resolver) {
cache.put(hash, data.clone());
return Some(data);
}
return None;
if ty == PreimageType::EthVersionedHash {
let cache: Arc<OnceCell<CBytes>> = {
let mut locked = BLOBHASH_PREIMAGE_CACHE.lock().unwrap();
locked.get_or_insert(hash, Default::default).clone()
};
return cache
.get_or_try_init(|| {
handle_preimage_resolution(context, ty, hash, resolver).ok_or(())
})
.ok()
.cloned();
}
handle_preimage_resolution!(context, ty, hash, resolver)
handle_preimage_resolution(context, ty, hash, resolver)
},
) as PreimageResolver);
}
Expand Down
2 changes: 1 addition & 1 deletion arbitrator/prover/src/programs/meter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ pub fn pricing_v1(op: &Operator, tys: &HashMap<SignatureIndex, FunctionType>) ->
dot!(I32Store, I32Store8, I32Store16) => 825,
dot!(I64Store, I64Store8, I64Store16, I64Store32) => 950,
dot!(MemorySize) => 3000,
dot!(MemoryGrow) => 1, // cost handled by memory pricer
dot!(MemoryGrow) => 8050, // rest of cost handled by memory pricer

op!(I32Eqz, I32Eq, I32Ne, I32LtS, I32LtU, I32GtS, I32GtU, I32LeS, I32LeU, I32GeS, I32GeU) => 170,
op!(I64Eqz, I64Eq, I64Ne, I64LtS, I64LtU, I64GtS, I64GtU, I64LeS, I64LeU, I64GeS, I64GeU) => 225,
Expand Down
25 changes: 25 additions & 0 deletions arbitrator/stylus/tests/grow/fixed.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
;; Copyright 2023-2024, Offchain Labs, Inc.
;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE

(module
(import "console" "tee_i32" (func $tee_i32 (param i32) (result i32)))
(func (export "user_entrypoint") (param $args_len i32) (result i32)
;; fail to grow the memory a non-zero number of pages
i32.const -65537
call $tee_i32
memory.grow
call $tee_i32
i32.const -1
i32.eq
i32.eqz
(if (then unreachable))

;; succeed growing 0 pages
i32.const 0
memory.grow
call $tee_i32
i32.eqz
i32.eqz
)
(memory (export "memory") 0 0)
)
File renamed without changes.
File renamed without changes.
45 changes: 45 additions & 0 deletions arbitrator/stylus/tests/grow/mem-write.wat
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
;; Copyright 2023, Offchain Labs, Inc.
;; For license information, see https://github.com/OffchainLabs/nitro/blob/master/LICENSE

(module
(import "vm_hooks" "pay_for_memory_grow" (func $pay_for_memory_grow (param i32)))
(import "vm_hooks" "read_args" (func $read_args (param i32)))
(import "vm_hooks" "write_result" (func $write_result (param i32 i32)))
(import "console" "tee_i32" (func $tee_i32 (param i32) (result i32)))
(func (export "user_entrypoint") (param $args_len i32) (result i32)
local.get $args_len
i32.eqz
(if (then
;; write an empty result to offset 0
(call $write_result (i32.const 0) (i32.const 0))
(return (i32.const 0))
))

;; grow 1 page so that we can read our args
i32.const 1
memory.grow
drop

;; store the size argument at offset 0
i32.const 0
call $read_args

;; read the argument and grow the remainder
i32.const 0
i32.load8_u
i32.const 1
i32.sub
memory.grow
drop

;; write a result (should panic if out of bounds)
i32.const 1
i32.load
i32.const 5
i32.load
call $write_result

i32.const 0
)
(memory (export "memory") 0)
)
1 change: 1 addition & 0 deletions arbitrator/wasm-libraries/Cargo.lock

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

17 changes: 11 additions & 6 deletions arbitrator/wasm-libraries/user-host/src/program.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use eyre::{eyre, Result};
use prover::programs::prelude::*;
use std::fmt::Display;
use user_host_trait::UserHost;
use wasmer_types::WASM_PAGE_SIZE;
use wasmer_types::{Pages, WASM_PAGE_SIZE};

// allows introspection into user modules
#[link(wasm_import_module = "hostio")]
Expand Down Expand Up @@ -186,9 +186,14 @@ impl Program {
unsafe { PROGRAMS.last_mut().expect("no program") }
}

/// Reads the program's memory size in pages
fn memory_size(&self) -> u32 {
unsafe { program_memory_size(self.module) }
/// Reads the program's memory size in pages.
fn memory_size(&self) -> Pages {
unsafe { Pages(program_memory_size(self.module)) }
}

/// Reads the program's memory size in bytes.
fn memory_size_bytes(&self) -> u64 {
self.memory_size().0 as u64 * WASM_PAGE_SIZE as u64
}

/// Provides the length of the program's calldata in bytes.
Expand All @@ -198,8 +203,8 @@ impl Program {

/// Ensures an access is within bounds
fn check_memory_access(&self, ptr: GuestPtr, bytes: u32) -> Result<(), MemoryBoundsError> {
let last_page = ptr.saturating_add(bytes) / (WASM_PAGE_SIZE as u32);
if last_page > self.memory_size() {
let end = ptr.to_u64() + bytes as u64;
if end > self.memory_size_bytes() {
return Err(MemoryBoundsError);
}
Ok(())
Expand Down
Loading

0 comments on commit 3b3a1a4

Please sign in to comment.