diff --git a/.github/workflows/smoke-test.yml b/.github/workflows/smoke-test.yml index fbb5b1d..419f86b 100644 --- a/.github/workflows/smoke-test.yml +++ b/.github/workflows/smoke-test.yml @@ -14,6 +14,7 @@ jobs: name: 'Test (Smoke)(${{ matrix.cfg_release_channel }})' env: CFG_RELEASE_CHANNEL: ${{ matrix.cfg_release_channel }} + PRIV_KEY: ${{ secrets.SEPOLIA_PRIVATE_KEY }} strategy: matrix: target: [wasm32-unknown-unknown] diff --git a/Cargo.lock b/Cargo.lock index ef6b96c..6d6fa83 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -18,7 +18,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "66f73f11dcfbf8bb763d88fb1d082fe7cca0a00d3227d9921bdbd52ce5e013e2" dependencies = [ "bytes", - "cfg-if", + "cfg-if 1.0.0", "const-hex", "derive_more", "hex-literal", @@ -74,6 +74,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "bumpalo" +version = "3.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f30e7476521f6f8af1a1c4c0b8cc94f0bee37d91763d0ca2665f299b6cd8aec" + [[package]] name = "byteorder" version = "1.4.3" @@ -86,19 +92,35 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "console_error_panic_hook" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a06aeb73f470f66dcdbf7223caeebb85984942f22f1adb2a088cf9668146bbbc" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen", +] + [[package]] name = "const-hex" version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "hex", "serde", @@ -224,6 +246,15 @@ version = "1.0.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" +[[package]] +name = "js-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cee9c64da59eae3b50095c18d3e74f8b73c0b86d2792824ff01bbce68ba229ca" +dependencies = [ + "wasm-bindgen", +] + [[package]] name = "keccak" version = "0.1.4" @@ -257,12 +288,33 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "log" +version = "0.4.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6163cb8c49088c2c36f57875e58ccd8c87c7427f7fbd50ea6710b2f3f2e8f" + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mini-alloc" +version = "0.4.2" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-test", + "wee_alloc", +] + [[package]] name = "num-traits" version = "0.2.16" @@ -273,6 +325,12 @@ dependencies = [ "libm", ] +[[package]] +name = "once_cell" +version = "1.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd8b5dd2ae5ed71462c540258bedcb51965123ad7e7ccf4b9a8cafaa4a63576d" + [[package]] name = "paste" version = "1.0.14" @@ -410,6 +468,12 @@ dependencies = [ "semver", ] +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + [[package]] name = "semver" version = "1.0.18" @@ -438,7 +502,7 @@ version = "0.4.2" dependencies = [ "alloy-primitives", "alloy-sol-types", - "cfg-if", + "cfg-if 1.0.0", "convert_case 0.6.0", "lazy_static", "proc-macro2", @@ -455,7 +519,7 @@ version = "0.4.2" dependencies = [ "alloy-primitives", "alloy-sol-types", - "cfg-if", + "cfg-if 1.0.0", "derivative", "fnv", "hex", @@ -545,6 +609,141 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wasm-bindgen" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ed0d4f68a3015cc185aff4db9506a015f4b96f95303897bfa23f846db54064e" +dependencies = [ + "cfg-if 1.0.0", + "wasm-bindgen-macro", +] + +[[package]] +name = "wasm-bindgen-backend" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1b56f625e64f3a1084ded111c4d5f477df9f8c92df113852fa5a374dbda78826" +dependencies = [ + "bumpalo", + "log", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.27", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac36a15a220124ac510204aec1c3e5db8a22ab06fd6706d881dc6149f8ed9a12" +dependencies = [ + "cfg-if 1.0.0", + "js-sys", + "wasm-bindgen", + "web-sys", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0162dbf37223cd2afce98f3d0785506dcb8d266223983e4b5b525859e6e182b2" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0eb82fcb7930ae6219a7ecfd55b217f5f0893484b7a13022ebb2b2bf20b5283" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", + "wasm-bindgen-backend", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.89" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7ab9b36309365056cd639da3134bf87fa8f3d86008abf99e612384a6eecd459f" + +[[package]] +name = "wasm-bindgen-test" +version = "0.3.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2cf9242c0d27999b831eae4767b2a146feb0b27d332d553e605864acd2afd403" +dependencies = [ + "console_error_panic_hook", + "js-sys", + "scoped-tls", + "wasm-bindgen", + "wasm-bindgen-futures", + "wasm-bindgen-test-macro", +] + +[[package]] +name = "wasm-bindgen-test-macro" +version = "0.3.39" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "794645f5408c9a039fd09f4d113cdfb2e7eba5ff1956b07bcf701cf4b394fe89" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.27", +] + +[[package]] +name = "web-sys" +version = "0.3.66" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "50c24a44ec86bb68fbecd1b3efed7e85ea5621b39b35ef2766b66cd984f8010f" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + [[package]] name = "zeroize" version = "1.6.0" diff --git a/Cargo.toml b/Cargo.toml index c21d398..b38370b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["stylus-sdk", "stylus-proc"] +members = ["stylus-sdk", "stylus-proc", "mini-alloc"] resolver = "2" [workspace.package] diff --git a/ci/smoke_test.sh b/ci/smoke_test.sh index 6c8b56c..fa23fd4 100755 --- a/ci/smoke_test.sh +++ b/ci/smoke_test.sh @@ -11,4 +11,4 @@ cargo stylus new counter cd counter echo "[workspace]" >> Cargo.toml -cargo stylus deploy -e http://localhost:8547 --private-key 0xb6b15c8cb491557369f3c7d2c287b053eb229daa9c22138887752191c9520659 +cargo stylus deploy --private-key $PRIV_KEY diff --git a/examples/erc20/Cargo.lock b/examples/erc20/Cargo.lock index 52f131b..7988a62 100644 --- a/examples/erc20/Cargo.lock +++ b/examples/erc20/Cargo.lock @@ -19,7 +19,7 @@ checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" dependencies = [ "alloy-rlp", "bytes", - "cfg-if 1.0.0", + "cfg-if", "const-hex", "derive_more", "hex-literal", @@ -137,12 +137,6 @@ dependencies = [ "libc", ] -[[package]] -name = "cfg-if" -version = "0.1.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" - [[package]] name = "cfg-if" version = "1.0.0" @@ -155,7 +149,7 @@ version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08849ed393c907c90016652a01465a12d86361cd38ad2a7de026c56a520cc259" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "cpufeatures", "hex", "serde", @@ -247,8 +241,8 @@ version = "0.1.0" dependencies = [ "alloy-primitives", "alloy-sol-types", + "mini-alloc", "stylus-sdk", - "wee_alloc", ] [[package]] @@ -300,7 +294,7 @@ version = "0.2.10" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be4136b2a15dd319360be1c07d9933517ccf0be8f16bf62a3bee4f0d618df427" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "libc", "wasi", ] @@ -375,10 +369,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5486aed0026218e61b8a01d5fbd5a0a134649abb71a0e53b7bc088529dced86e" [[package]] -name = "memory_units" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" +name = "mini-alloc" +version = "0.4.2" +dependencies = [ + "cfg-if", +] [[package]] name = "num-traits" @@ -624,10 +619,11 @@ dependencies = [ [[package]] name = "stylus-proc" -version = "0.1.0" +version = "0.4.2" dependencies = [ "alloy-primitives", "alloy-sol-types", + "cfg-if", "convert_case 0.6.0", "lazy_static", "proc-macro2", @@ -640,10 +636,11 @@ dependencies = [ [[package]] name = "stylus-sdk" -version = "0.1.0" +version = "0.4.2" dependencies = [ "alloy-primitives", "alloy-sol-types", + "cfg-if", "derivative", "fnv", "hex", @@ -692,7 +689,7 @@ version = "3.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" dependencies = [ - "cfg-if 1.0.0", + "cfg-if", "fastrand", "redox_syscall", "rustix", @@ -759,40 +756,6 @@ version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" -[[package]] -name = "wee_alloc" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" -dependencies = [ - "cfg-if 0.1.10", - "libc", - "memory_units", - "winapi", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-sys" version = "0.48.0" diff --git a/examples/erc20/Cargo.toml b/examples/erc20/Cargo.toml index e02c8f5..b55e876 100644 --- a/examples/erc20/Cargo.toml +++ b/examples/erc20/Cargo.toml @@ -7,7 +7,7 @@ edition = "2021" alloy-primitives = "0.3.1" alloy-sol-types = "0.3.1" stylus-sdk = { path = "../../../stylus-sdk-rs/stylus-sdk" } -wee_alloc = "0.4.5" +mini-alloc = { path = "../../mini-alloc" } [features] export-abi = ["stylus-sdk/export-abi"] @@ -19,4 +19,4 @@ lto = true panic = "abort" opt-level = "s" -[workspace] \ No newline at end of file +[workspace] diff --git a/examples/erc20/src/main.rs b/examples/erc20/src/main.rs index e12bf2f..34aaa7a 100644 --- a/examples/erc20/src/main.rs +++ b/examples/erc20/src/main.rs @@ -5,8 +5,9 @@ use crate::erc20::{Erc20, Erc20Params}; use alloc::{string::String, vec::Vec}; use stylus_sdk::{alloy_primitives::U256, call, msg, prelude::*}; +#[cfg(target_arch = "wasm32")] #[global_allocator] -static ALLOC: wee_alloc::WeeAlloc = wee_alloc::WeeAlloc::INIT; +static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; mod erc20; @@ -48,7 +49,7 @@ impl Weth { self.erc20.burn(msg::sender(), amount)?; // send the user their funds - call::transfer_eth(self, msg::sender(), amount) + call::transfer_eth(msg::sender(), amount) } // sums numbers diff --git a/mini-alloc/.cargo/config.toml b/mini-alloc/.cargo/config.toml new file mode 100644 index 0000000..bc5bd0b --- /dev/null +++ b/mini-alloc/.cargo/config.toml @@ -0,0 +1,7 @@ +[build] +target = "wasm32-unknown-unknown" + +[target.wasm32-unknown-unknown] +rustflags = [ + "-C", "link-arg=-zstack-size=8192", +] diff --git a/mini-alloc/Cargo.toml b/mini-alloc/Cargo.toml new file mode 100644 index 0000000..9d25951 --- /dev/null +++ b/mini-alloc/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "mini-alloc" +keywords = ["wasm", "stylus", "allocator"] +description = "Very simple global allocator" +readme = "README.md" + +authors.workspace = true +edition.workspace = true +license.workspace = true +repository.workspace = true +version.workspace = true + +[dev-dependencies] +wasm-bindgen-test = "0.3.0" + +[dependencies] +cfg-if = "1.0.0" + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +wee_alloc = "0.4.5" diff --git a/mini-alloc/README.md b/mini-alloc/README.md new file mode 100644 index 0000000..59e84dd --- /dev/null +++ b/mini-alloc/README.md @@ -0,0 +1,47 @@ +# mini-alloc + +`mini-alloc` is a small and performant allocator optimized for `wasm32` targets like [Arbitrum Stylus][Stylus]. You can use it in your program as follows. +```rust +#[global_allocator] +static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; +``` + +## Benchmarks + +`mini-alloc` implements a minimal bump allocator strategy. It never deallocates memory -- that is, `dealloc` does nothing. It's suitable for cases where binary size is at a premium and it's acceptable to leak all allocations. The simplicity of this model makes it very efficient, as seen in the following benchmarks. + +| | `MiniAlloc` | [`WeeAlloc`][WeeAlloc] | Std Library | +|--------------|-------------|------------------------|----------------| +| alloc | 333 gas | 721 gas | 516 gas | +| alloc_zeroed | 329 gas | 95 million gas | 48 million gas | + +The benchmarks compare the performance of this crate's `edge_cases` test in the [Stylus VM][StylusVM]. Normal allocations are over **2x** cheaper than when using [`WeeAlloc`][WeeAlloc], a common WASM alternative that this crate defaults to when built for non-WASM targets. + +Replacing each instance of `alloc` in the test with `alloc_zeroed` reveals an over **99%** improvement for zero-filled allocations. Unlike [`WeeAlloc`][WeeAlloc] and the standard library, `MiniAlloc` takes advantage of the fact that WASM pages are zero-filled at initialization, and uses fewer of them due to the layout of Rust's memory. + +In the above tests we disable memory expansion costs, which unfairly penelize `WeeAlloc` and the standard library due to their increased resource consumption. + +## Notice + +`MiniAlloc` should not be used in `wasm32` environments that enable the multithreading proposal. Although `MiniAlloc` implements `Sync` since Rust requires it for the global allocator, this crate should not be used in this way. This should not be a concern in [`Stylus`][Stylus]. + +Also, `core::arch::wasm32::memory_grow` must never be called by any code outside this crate. + +On targets other than wasm32, `MiniAlloc` simply forwards to the allocator from another crate, `wee_alloc::WeeAlloc`. + +## License + +© 2023 Offchain Labs, Inc. + +This project is licensed under either of + +- [Apache License, Version 2.0](https://www.apache.org/licenses/LICENSE-2.0) ([licenses/Apache-2.0](../licenses/Apache-2.0)) +- [MIT license](https://opensource.org/licenses/MIT) ([licenses/MIT](../licenses/MIT)) + +at your option. + +The [SPDX](https://spdx.dev) license identifier for this project is `MIT OR Apache-2.0`. + +[Stylus]: https://github.com/OffchainLabs/stylus-sdk-rs +[StylusVM]: https://github.com/OffchainLabs/stylus +[WeeAlloc]: https://github.com/rustwasm/wee_alloc diff --git a/mini-alloc/src/imp.rs b/mini-alloc/src/imp.rs new file mode 100644 index 0000000..a68eb8d --- /dev/null +++ b/mini-alloc/src/imp.rs @@ -0,0 +1,76 @@ +// Copyright 2023, Offchain Labs, Inc. +// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md + +use core::{ + alloc::{GlobalAlloc, Layout}, + arch::wasm32, + num::NonZeroUsize as NonZero, +}; + +pub struct MiniAlloc; + +/// This is not a valid implementation of [`Sync`] but is ok in single-threaded WASM. +unsafe impl Sync for MiniAlloc {} + +impl MiniAlloc { + pub const INIT: Self = MiniAlloc; + + /// The WASM page size, or 2^16 bytes. + pub const PAGE_SIZE: usize = 1 << 16; +} + +unsafe impl GlobalAlloc for MiniAlloc { + unsafe fn alloc(&self, layout: Layout) -> *mut u8 { + alloc_impl(layout).unwrap_or(core::ptr::null_mut()) + } + + #[inline] + unsafe fn alloc_zeroed(&self, layout: Layout) -> *mut u8 { + self.alloc(layout) + } + + #[inline] + unsafe fn dealloc(&self, _ptr: *mut u8, _layout: Layout) {} +} + +extern "C" { + /// This symbol is created by the LLVM linker. + static __heap_base: u8; +} + +/// Represents the negation of the allocator's bump offset and boundary +/// +/// We store the negation because we can align the negative offset in fewer +/// instructions than the positive offset. +static mut STATE: Option<(NonZero, usize)> = None; + +unsafe fn alloc_impl(layout: Layout) -> Option<*mut u8> { + let (neg_offset, neg_bound) = STATE.get_or_insert_with(|| { + let heap_base = &__heap_base as *const u8 as usize; + let bound = MiniAlloc::PAGE_SIZE * wasm32::memory_size(0) - 1; + ( + NonZero::new_unchecked(heap_base.wrapping_neg()), + bound.wrapping_neg(), + ) + }); + + let neg_aligned = make_aligned(neg_offset.get(), layout.align()); + let next_neg_offset = neg_aligned.checked_sub(layout.size())?; + let bytes_needed = neg_bound.saturating_sub(next_neg_offset + 1); + if bytes_needed != 0 { + let pages_needed = 1 + (bytes_needed - 1) / MiniAlloc::PAGE_SIZE; + if wasm32::memory_grow(0, pages_needed) == usize::MAX { + return None; + } + *neg_bound -= MiniAlloc::PAGE_SIZE * pages_needed; + } + *neg_offset = NonZero::new_unchecked(next_neg_offset); + Some(neg_aligned.wrapping_neg() as *mut u8) +} + +/// Returns `value` rounded down to the next multiple of `align`. +/// Note: `align` must be a power of two, which is guaranteed by [`Layout::align`]. +#[inline(always)] +fn make_aligned(value: usize, align: usize) -> usize { + value & align.wrapping_neg() +} diff --git a/mini-alloc/src/lib.rs b/mini-alloc/src/lib.rs new file mode 100644 index 0000000..69b1aa9 --- /dev/null +++ b/mini-alloc/src/lib.rs @@ -0,0 +1,15 @@ +// Copyright 2023, Offchain Labs, Inc. +// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md + +#![no_std] + +use cfg_if::cfg_if; + +cfg_if! { + if #[cfg(target_arch = "wasm32")] { + mod imp; + pub use imp::MiniAlloc; + } else { + pub use wee_alloc::WeeAlloc as MiniAlloc; + } +} diff --git a/mini-alloc/tests/misc.rs b/mini-alloc/tests/misc.rs new file mode 100644 index 0000000..fe17956 --- /dev/null +++ b/mini-alloc/tests/misc.rs @@ -0,0 +1,139 @@ +// Copyright 2023, Offchain Labs, Inc. +// For licensing, see https://github.com/OffchainLabs/stylus-sdk-rs/blob/stylus/licenses/COPYRIGHT.md + +#![no_std] + +extern crate alloc; + +#[cfg(target_arch = "wasm32")] +use wasm_bindgen_test::*; + +#[cfg(target_arch = "wasm32")] +#[global_allocator] +static ALLOC: mini_alloc::MiniAlloc = mini_alloc::MiniAlloc::INIT; + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +fn vec_test() { + use alloc::vec::Vec; + + let p1 = Vec::::with_capacity(700); + let p2 = Vec::::with_capacity(65536); + let p3 = Vec::::with_capacity(700000); + let p4 = Vec::::with_capacity(1); + let p5 = Vec::::with_capacity(1); + let p6 = Vec::::with_capacity(1); + assert_eq!(p1.as_ptr() as usize + 700, p2.as_ptr() as usize); + assert_eq!(p2.as_ptr() as usize + 65536, p3.as_ptr() as usize); + assert!((p4.as_ptr() as usize) < p3.as_ptr() as usize + 700004); + assert!((p4.as_ptr() as usize) >= p3.as_ptr() as usize + 700000); + assert_eq!(p4.as_ptr() as usize & 3, 0); + assert_eq!(p4.as_ptr() as usize + 4, p5.as_ptr() as usize); + assert_eq!(p5.as_ptr() as usize + 2, p6.as_ptr() as usize); +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +fn vec_test_loop() { + use alloc::vec::Vec; + + let mut size = 1usize; + let mut p = Vec::::with_capacity(size); + for _ in 0..22 { + let new_size = size * 2; + let new_p = Vec::::with_capacity(new_size); + assert_eq!(p.as_ptr() as usize + size, new_p.as_ptr() as usize); + size = new_size; + p = new_p; + } +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +#[should_panic] +fn vec_test_overallocate() { + use alloc::vec::Vec; + + let _ = Vec::::with_capacity(0xFFFFFFFF); +} + +#[cfg(target_arch = "wasm32")] +#[wasm_bindgen_test] +fn edge_cases() { + use alloc::alloc::{alloc, Layout}; + use core::arch::wasm32; + use mini_alloc::MiniAlloc; + + const PAGE_SIZE: usize = MiniAlloc::PAGE_SIZE; + const PAGE_LIMIT: usize = 65536; + + fn size() -> usize { + wasm32::memory_size(0) as usize + } + + fn size_bytes() -> usize { + size() * PAGE_SIZE + } + + fn next(size: usize) -> usize { + let align = 1; + let layout = Layout::from_size_align(size, align).unwrap(); + unsafe { alloc(layout) as usize } + } + + assert_eq!(size(), 1); + + // check that zero-allocs don't bump + let start = next(0); + assert_eq!(start, next(0)); + assert_eq!(start / PAGE_SIZE, 0); + assert_eq!(size(), 1); + + // fill the rest of the page + let rest = size_bytes() - start; + let end = next(rest); + assert_eq!(end / PAGE_SIZE, 0); + assert_eq!(end, start); + assert_eq!(size(), 1); + + // allocate a second page + let first = next(1); + assert_eq!(first / PAGE_SIZE, 1); + assert_eq!(first, PAGE_SIZE); + assert_eq!(size(), 2); + + // fill the rest of the second page + let rest = size_bytes() - (first + 1); + let end = next(rest); + assert_eq!(end, first + 1); + assert_eq!(size(), 2); + + // jump 4 pages + let jump = next(4 * PAGE_SIZE); + assert_eq!(jump, 2 * PAGE_SIZE); + assert_eq!(size(), 6); + + // allocate many pages + let mut rng: usize = 0; + while size() < PAGE_LIMIT / 2 { + rng = rng.wrapping_mul(1664525).wrapping_add(1013904223); + + let rest = usize::MAX - next(0) + 1; + let bytes = match rng % 4 { + 0 => rng % 1024, + 1 => rng % PAGE_SIZE, + 2 => next(size_bytes() - next(0)), // rest of page + _ => rng % (PAGE_SIZE * 200), + }; + + let offset = next(bytes.min(rest)); + + if offset == size_bytes() { + assert_eq!(bytes, 0); + } else { + assert!(offset < size_bytes()); + } + } + + // TODO: test allocating all 4GB +} diff --git a/stylus-sdk/src/hostio.rs b/stylus-sdk/src/hostio.rs index 0e78148..bca4701 100644 --- a/stylus-sdk/src/hostio.rs +++ b/stylus-sdk/src/hostio.rs @@ -36,6 +36,12 @@ extern "C" { /// [`EXT_CODEHASH`]: https://www.evm.codes/#3F pub fn account_codehash(address: *const u8, dest: *mut u8); + /// Write the code associated with the given address into `dest`. + pub fn account_code(address: *const u8, dest: *mut u8, size: usize); + + /// Get the size of the code associated with the given address. + pub fn account_code_size(address: *const u8) -> usize; + /// Reads a 32-byte value from permanent storage. Stylus's storage format is identical to /// that of the EVM. This means that, under the hood, this hostio is accessing the 32-byte /// value stored in the EVM state trie at offset `key`, which will be `0` when not previously diff --git a/stylus-sdk/src/types.rs b/stylus-sdk/src/types.rs index 9569220..94b40f7 100644 --- a/stylus-sdk/src/types.rs +++ b/stylus-sdk/src/types.rs @@ -13,6 +13,8 @@ //! let balance = account.balance(); //! ``` +use alloc::vec::Vec; + use crate::hostio; use alloy_primitives::{b256, Address, B256, U256}; @@ -21,6 +23,9 @@ pub trait AddressVM { /// The balance in wei of the account. fn balance(&self) -> U256; + /// The code of the contract at the given address. + fn code(&self, data: &mut Vec); + /// The codehash of the contract or [`EOA`] at the given address. /// /// [`EOA`]: https://ethereum.org/en/developers/docs/accounts/#types-of-account @@ -41,6 +46,19 @@ impl AddressVM for Address { U256::from_be_bytes(data) } + fn code(&self, data: &mut Vec) { + let size = unsafe { hostio::account_code_size(self.0.as_ptr()) }; + let initial_len = data.len(); + data.resize(size + initial_len, 0); + unsafe { + hostio::account_code( + self.0.as_ptr(), + data.as_mut_ptr().offset(initial_len as isize), + size, + ); + } + } + fn codehash(&self) -> B256 { let mut data = [0; 32]; unsafe { hostio::account_codehash(self.0.as_ptr(), data.as_mut_ptr()) }; @@ -49,7 +67,7 @@ impl AddressVM for Address { fn has_code(&self) -> bool { let hash = self.codehash(); - hash.is_zero() - || hash == b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") + !hash.is_zero() + && hash != b256!("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470") } }