Stylus unit tests run entirely in Rust, with a fully-mocked host—no EVM, no RPC, no gas. You can exercise pure logic, mock all host contexts, inspect side-effects, and even extend the VM to suit your needs.
vm()
Every Stylus contract automatically implements the HostAccess
trait, giving you a .vm()
handle inside your methods.
Use self.vm()
instead of global helpers so your code works both in WASM and in native unit tests:
1self.vm().msg_value() // returns the mocked msg.value() in tests
2self.vm().msg_sender() // returns the mocked msg.sender()
3self.vm().block_timestamp() // returns the mocked block timestamp
4self.vm().call(&ctx, addr, &data) // invokes a mocked external call
1self.vm().msg_value() // returns the mocked msg.value() in tests
2self.vm().msg_sender() // returns the mocked msg.sender()
3self.vm().block_timestamp() // returns the mocked block timestamp
4self.vm().call(&ctx, addr, &data) // invokes a mocked external call
In production WASM this maps to real host syscalls; in native tests it routes to TestVM
or your custom host.
With stylus_sdk::testing::*
imported, write tests just like any Rust project. Below is a simple test suite for a counter contract that can be found at the bottom of the page.
1#[cfg(test)]
2mod test {
3 use super::*;
4 use stylus_sdk::testing::*;
5
6 #[test]
7 fn test_counter_basic() {
8 // 1) Create a TestVM and contract
9 let vm = TestVM::default();
10 let mut c = Counter::from(&vm);
11
12 // 2) Assert initial state
13 assert_eq!(c.number(), U256::ZERO);
14
15 // 3) Call methods and assert logic
16 c.increment();
17 assert_eq!(c.number(), U256::ONE);
18
19 // 4) Mock msg.value() and test payable fn
20 vm.set_value(U256::from(5));
21 c.add_from_msg_value();
22 assert_eq!(c.number(), U256::from(6));
23 }
24}
1#[cfg(test)]
2mod test {
3 use super::*;
4 use stylus_sdk::testing::*;
5
6 #[test]
7 fn test_counter_basic() {
8 // 1) Create a TestVM and contract
9 let vm = TestVM::default();
10 let mut c = Counter::from(&vm);
11
12 // 2) Assert initial state
13 assert_eq!(c.number(), U256::ZERO);
14
15 // 3) Call methods and assert logic
16 c.increment();
17 assert_eq!(c.number(), U256::ONE);
18
19 // 4) Mock msg.value() and test payable fn
20 vm.set_value(U256::from(5));
21 c.add_from_msg_value();
22 assert_eq!(c.number(), U256::from(6));
23 }
24}
Explanation
TestVM::default()
seeds a clean in-memory VM, use it to create a new VM instance for each test, ensuring isolationCounter::from(&vm)
wires up storage against that VMincrement()
and add_from_msg_value()
run instantly—no blockchain neededvm.set_value(...)
overrides the msg.value()
for that testassert_eq!(...)
checks the contract state after each call to verify logicStylus’s TestVM
provides methods to override and inspect every host function. Use the table below as a quick reference:
Scenario | TestVM API |
---|---|
Override Ether attached | TestVM.set_value(U256) |
Override Caller address | TestVM.set_sender(Address) |
Read raw storage slot | TestVM.storage_load_bytes32(slot) |
Write raw storage slot & commit | unsafe { TestVM.storage_cache_bytes32(slot, val) }; TestVM.flush_cache(false) |
Override block parameters | TestVM.set_block_number(n) TestVM.set_block_timestamp(ts) |
Inspect emitted logs & events | TestVM.get_emitted_logs() |
Mock external call response | TestVM.mock_call(addr, data, Ok(res)/Err(revert)) |
Explanation These methods let you simulate any on-chain context or inspect every side-effect your contract produces.
To verify events and their indexed parameters you can use get_emitted_logs()
to inspect the logs emitted by your contract. This method returns a list of (topics, data)
pairs, where topics
is a list of indexed parameters and data
is the non-indexed data.
1#[test]
2fn test_event_emission() {
3 let vm = TestVM::new();
4 let mut c = Counter::from(&vm);
5
6 // Trigger events
7 c.increment(); // may emit multiple logs
8
9 let logs = vm.get_emitted_logs();
10 assert_eq!(logs.len(), 2);
11
12 // First topic is the event signature
13 let sig: B256 = hex!(
14 "c9d64952459b33e1dd10d284fe1e9336b8c514cbf51792a888ee7615ca3225d9"
15 ).into();
16 assert_eq!(logs[0].0[0], sig);
17
18 // Indexed address is in topic[1], last 20 bytes
19 let mut buf = [0u8;20];
20 buf.copy_from_slice(&logs[0].0[1].into()[12..]);
21 assert_eq!(Address::from(buf), vm.msg_sender());
22}
1#[test]
2fn test_event_emission() {
3 let vm = TestVM::new();
4 let mut c = Counter::from(&vm);
5
6 // Trigger events
7 c.increment(); // may emit multiple logs
8
9 let logs = vm.get_emitted_logs();
10 assert_eq!(logs.len(), 2);
11
12 // First topic is the event signature
13 let sig: B256 = hex!(
14 "c9d64952459b33e1dd10d284fe1e9336b8c514cbf51792a888ee7615ca3225d9"
15 ).into();
16 assert_eq!(logs[0].0[0], sig);
17
18 // Indexed address is in topic[1], last 20 bytes
19 let mut buf = [0u8;20];
20 buf.copy_from_slice(&logs[0].0[1].into()[12..]);
21 assert_eq!(Address::from(buf), vm.msg_sender());
22}
Explanation
get_emitted_logs()
returns a list of (topics, data)
pairsUse mock_call
to test cross-contract interactions without deploying dependencies. This powerful feature lets you simulate both successful responses and reverts from external contracts, allowing you to test your integration logic in complete isolation:
1#[test]
2fn test_external_call_behavior() {
3 let vm = TestVM::new();
4 let mut c = Counter::from(&vm);
5
6 // Only owner may call
7 let owner = vm.msg_sender();
8 c.transfer_ownership(owner).unwrap();
9
10 let target = Address::from([5u8;20]);
11 let data = vec![1,2,3];
12 let ok_ret = vec![7,7];
13 let err_ret= vec![9,9,9];
14
15 // 1) Successful call
16 vm.mock_call(target, data.clone(), Ok(ok_ret.clone()));
17 assert_eq!(c.call_external_contract(target, data.clone()), Ok(ok_ret));
18
19 // 2) Revert call
20 vm.mock_call(target, data.clone(), Err(err_ret.clone()));
21 let err = c.call_external_contract(target, data).unwrap_err();
22 let expected = format!("Revert({:?})", err_ret).as_bytes().to_vec();
23 assert_eq!(err, expected);
24}
1#[test]
2fn test_external_call_behavior() {
3 let vm = TestVM::new();
4 let mut c = Counter::from(&vm);
5
6 // Only owner may call
7 let owner = vm.msg_sender();
8 c.transfer_ownership(owner).unwrap();
9
10 let target = Address::from([5u8;20]);
11 let data = vec![1,2,3];
12 let ok_ret = vec![7,7];
13 let err_ret= vec![9,9,9];
14
15 // 1) Successful call
16 vm.mock_call(target, data.clone(), Ok(ok_ret.clone()));
17 assert_eq!(c.call_external_contract(target, data.clone()), Ok(ok_ret));
18
19 // 2) Revert call
20 vm.mock_call(target, data.clone(), Err(err_ret.clone()));
21 let err = c.call_external_contract(target, data).unwrap_err();
22 let expected = format!("Revert({:?})", err_ret).as_bytes().to_vec();
23 assert_eq!(err, expected);
24}
Explanation
mock_call(...)
primes the VM to return Ok
or Err
for that address+input.call(&self, target, &data)
picks up the mockDirectly inspect or override any storage slot. This is useful for testing storage layout, mappings, or verifying internal state after corner-case paths:
1#[test]
2fn test_storage_direct() {
3 let vm = TestVM::new();
4 let mut c = Counter::from(&vm);
5 c.set_number(U256::from(42));
6
7 let slot = U256::ZERO;
8
9 // Read the underlying B256
10 assert_eq!(
11 vm.storage_load_bytes32(slot),
12 B256::from_slice(&U256::from(42).to_be_bytes::<32>())
13 );
14
15 // Overwrite slot
16 let new = U256::from(100);
17 unsafe { vm.storage_cache_bytes32(slot, B256::from_slice(&new.to_be_bytes::<32>())); }
18 vm.flush_cache(false);
19
20 // Verify via getter
21 assert_eq!(c.number(), new);
22}
1#[test]
2fn test_storage_direct() {
3 let vm = TestVM::new();
4 let mut c = Counter::from(&vm);
5 c.set_number(U256::from(42));
6
7 let slot = U256::ZERO;
8
9 // Read the underlying B256
10 assert_eq!(
11 vm.storage_load_bytes32(slot),
12 B256::from_slice(&U256::from(42).to_be_bytes::<32>())
13 );
14
15 // Overwrite slot
16 let new = U256::from(100);
17 unsafe { vm.storage_cache_bytes32(slot, B256::from_slice(&new.to_be_bytes::<32>())); }
18 vm.flush_cache(false);
19
20 // Verify via getter
21 assert_eq!(c.number(), new);
22}
Explanation
vm.storage_load_bytes32(slot)
reads the raw bytes from the VMvm.storage_cache_bytes32(slot, value)
writes to the VM cachevm.flush_cache(false)
commits the cache to the VMSimulate block-dependent logic by overriding block number/timestamp. This is useful for testing timelocks, expiry logic, or height-based gating.
1#[test]
2fn test_block_dependent_logic() {
3 let vm: TestVM = TestVMBuilder::new()
4 .sender(my_addr)
5 .value(U256::ZERO)
6 .build();
7
8 let mut c = Counter::from(&vm);
9
10 vm.set_block_timestamp(1_234_567_890);
11 c.increment();
12 assert_eq!(c.last_updated(), U256::from(1_234_567_890u64));
13
14 vm.set_block_timestamp(2_000_000_000);
15 c.increment();
16 assert_eq!(c.last_updated(), U256::from(2_000_000_000u64));
17}
1#[test]
2fn test_block_dependent_logic() {
3 let vm: TestVM = TestVMBuilder::new()
4 .sender(my_addr)
5 .value(U256::ZERO)
6 .build();
7
8 let mut c = Counter::from(&vm);
9
10 vm.set_block_timestamp(1_234_567_890);
11 c.increment();
12 assert_eq!(c.last_updated(), U256::from(1_234_567_890u64));
13
14 vm.set_block_timestamp(2_000_000_000);
15 c.increment();
16 assert_eq!(c.last_updated(), U256::from(2_000_000_000u64));
17}
Explanation
TestVMBuilder
seeds initial values; vm.set_*
mutates them mid-test.vm.set_block_timestamp(...)
overrides the block timestampvm.set_block_number(...)
overrides the block numberYou can extend TestVM
to add custom instrumentation or helpers without re-implementing the entire Host
trait. This is useful for test-specific behaviors.
TestVMBuilder
Pre-seed context before deploying. This is useful for testing against a forked chain or pre-configured state:
1let vm: TestVM = TestVMBuilder::new()
2 .sender(my_addr)
3 .contract_address(ct_addr)
4 .value(U256::from(10))
5 .rpc_url("http://localhost:8547") // fork real state
6 .build();
7
8vm.set_balance(my_addr, U256::from(1_000));
9vm.set_block_number(123);
1let vm: TestVM = TestVMBuilder::new()
2 .sender(my_addr)
3 .contract_address(ct_addr)
4 .value(U256::from(10))
5 .rpc_url("http://localhost:8547") // fork real state
6 .build();
7
8vm.set_balance(my_addr, U256::from(1_000));
9vm.set_block_number(123);
TestVM
Add new instrumentation without re-implementing Host
. For example, you can track how many times mock_call
was invoked. In this example, we create a CustomVM
struct that wraps TestVM
and adds a counter for the number of times mock_call
is invoked.
1#[cfg(test)]
2mod custom_vm {
3 use super::*;
4 use stylus_sdk::testing::TestVM;
5 use alloy_primitives::Address;
6
7 pub struct CustomVM {
8 inner: TestVM,
9 pub mock_count: usize,
10 }
11
12 impl CustomVM {
13 pub fn new() -> Self { Self { inner: TestVM::default(), mock_count: 0 } }
14 pub fn mock_call(&mut self, tgt: Address, d: Vec<u8>, r: Result<_,_>) {
15 self.mock_count += 1;
16 self.inner.mock_call(tgt, d, r);
17 }
18 pub fn inner(&self) -> &TestVM { &self.inner }
19 }
20
21 #[test]
22 fn test_mock_counter() {
23 let mut vm = CustomVM::new();
24 let mut c = Counter::from(vm.inner());
25
26 assert_eq!(vm.mock_count, 0);
27 let addr = Address::from([5u8;20]);
28 vm.mock_call(addr, vec![1], Ok(vec![7]));
29 assert_eq!(vm.mock_count, 1);
30 }
31}
1#[cfg(test)]
2mod custom_vm {
3 use super::*;
4 use stylus_sdk::testing::TestVM;
5 use alloy_primitives::Address;
6
7 pub struct CustomVM {
8 inner: TestVM,
9 pub mock_count: usize,
10 }
11
12 impl CustomVM {
13 pub fn new() -> Self { Self { inner: TestVM::default(), mock_count: 0 } }
14 pub fn mock_call(&mut self, tgt: Address, d: Vec<u8>, r: Result<_,_>) {
15 self.mock_count += 1;
16 self.inner.mock_call(tgt, d, r);
17 }
18 pub fn inner(&self) -> &TestVM { &self.inner }
19 }
20
21 #[test]
22 fn test_mock_counter() {
23 let mut vm = CustomVM::new();
24 let mut c = Counter::from(vm.inner());
25
26 assert_eq!(vm.mock_count, 0);
27 let addr = Address::from([5u8;20]);
28 vm.mock_call(addr, vec![1], Ok(vec![7]));
29 assert_eq!(vm.mock_count, 1);
30 }
31}
Explanation
Host
trait—simply delegate to TestVM
.You can implement your own TestVM from scratch by implementing the Host
trait from stylus_core::host::Host
. This approach is useful for specialized testing scenarios or if you need complete control over the test environment.
What to test… | TestVM API |
---|---|
msg.value() | vm.set_value(U256) |
msg.sender() | vm.set_sender(Address) |
Raw storage | vm.storage_load_bytes32(k) unsafe { vm.storage_cache_bytes32(k, v) }; vm.flush_cache(false) |
Block params | vm.set_block_number(n) vm.set_block_timestamp(ts) |
Events & logs | vm.get_emitted_logs() |
External calls | vm.mock_call(addr, data, Ok/Err) |
Custom instrumentation | Wrap TestVM in your own struct and expose helpers |
TestVMBuilder
for forked or pre-configured stateTestVM
to instrument or extend behavior without re-implementing Host
With these patterns, your Stylus unit suite will be fast, deterministic, and comprehensive—covering logic, host I/O, events, storage, and more.
The contract below demonstrates a comprehensive test suite that covers all aspects of Stylus contract testing:
Each test demonstrates a different aspect of the testing framework, from simple value assertions to complex mock interactions. The example counter contract is intentionally designed with features that exercise all major testing capabilities.
You can use this pattern as a template for your own comprehensive test suites, ensuring your Stylus contracts are thoroughly verified before deployment.
1#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
2extern crate alloc;
3
4/// Import items from the SDK. The prelude contains common traits and macros.
5use stylus_sdk::{alloy_primitives::{Address, U256}, prelude::*};
6use stylus_sdk::alloy_sol_types::sol;
7// Define some persistent storage using the Solidity ABI.
8// `Counter` will be the entrypoint.
9sol_storage! {
10 #[entrypoint]
11 pub struct Counter {
12 uint256 number;
13 address owner;
14 uint256 last_updated;
15 }
16}
17
18// Define events
19sol! {
20 event CounterUpdated(address indexed user, uint256 prev_value, uint256 new_value);
21 }
22
23/// Declare that `Counter` is a contract with the following external methods.
24#[public]
25impl Counter {
26 pub fn owner(&self) -> Address {
27 self.owner.get()
28 }
29 pub fn number(&self) -> U256 {
30 self.number.get()
31 }
32 pub fn last_updated(&self) -> U256 {
33 self.last_updated.get()
34 }
35 /// Sets a number in storage to a user-specified value.
36 pub fn set_number(&mut self, new_number: U256) {
37 let prev = self.number.get();
38 self.number.set(new_number);
39 // Update the last updated timestamp.
40 self.last_updated.set(U256::from(self.vm().block_timestamp()));
41 // Emit an event
42 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
43 user: self.vm().msg_sender(),
44 prev_value: prev,
45 new_value: self.number.get(),
46 });
47 }
48 /// Sets a number in storage to a user-specified value.
49 pub fn mul_number(&mut self, new_number: U256) {
50 self.number.set(new_number * self.number.get());
51 let prev = self.number.get();
52 // Update the last updated timestamp.
53 self.last_updated.set(U256::from(self.vm().block_timestamp()));
54 // Emit an event
55 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
56 user: self.vm().msg_sender(),
57 prev_value: prev,
58 new_value: self.number.get(),
59 });
60 }
61 /// Sets a number in storage to a user-specified value.
62 pub fn add_number(&mut self, new_number: U256) {
63 self.number.set(new_number + self.number.get());
64 let prev = self.number.get();
65 // Update the last updated timestamp.
66 self.last_updated.set(U256::from(self.vm().block_timestamp()));
67 // Emit an event
68 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
69 user: self.vm().msg_sender(),
70 prev_value: prev,
71 new_value: self.number.get(),
72 });
73 }
74 /// Increments `number` and updates its value in storage.
75 pub fn increment(&mut self) {
76 // Increment the number in storage.
77 let prev = self.number.get();
78 self.set_number(prev + U256::from(1));
79 // Update the last updated timestamp.
80 self.last_updated.set(U256::from(self.vm().block_timestamp()));
81 // Emit an event
82 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
83 user: self.vm().msg_sender(),
84 prev_value: prev,
85 new_value: self.number.get(),
86 });
87 }
88 /// Decrements `number` and updates its value in storage.
89 /// Returns an error if the number is already zero.
90 pub fn decrement(&mut self) -> Result<(), Vec<u8>> {
91 let prev = self.number.get();
92 if prev == U256::ZERO {
93 return Err(b"Counter cannot go below zero".to_vec());
94 }
95
96 self.number.set(prev - U256::from(1));
97 // Update the last updated timestamp.
98 self.last_updated.set(U256::from(self.vm().block_timestamp()));
99 // Emit an event
100 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
101 user: self.vm().msg_sender(),
102 prev_value: prev,
103 new_value: self.number.get(),
104 });
105
106 Ok(())
107 }
108 /// Adds the wei value from msg_value to the number in storage.
109 #[payable]
110 pub fn add_from_msg_value(&mut self) {
111 let prev = self.number.get();
112 self.set_number(prev + self.vm().msg_value());
113 // Update the last updated timestamp.
114 self.last_updated.set(U256::from(self.vm().block_timestamp()));
115 // Emit an event
116 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
117 user: self.vm().msg_sender(),
118 prev_value: prev,
119 new_value: self.number.get(),
120 });
121 }
122 // External call example
123 pub fn call_external_contract(&mut self, target: Address, data: Vec<u8>) -> Result<Vec<u8>, Vec<u8>> {
124 if self.owner.get() != self.vm().msg_sender() {
125 return Err(b"Only owner can call this function".to_vec());
126 }
127 let return_data = self.vm().call(&self, target, &data)
128 .map_err(|err| format!("{:?}", err).as_bytes().to_vec())?;
129 Ok(return_data)
130 }
131 /// Transfers ownership of the contract to a new address.
132 pub fn transfer_ownership(&mut self, new_owner: Address) -> Result<(), Vec<u8>> {
133 if self.owner.get() == Address::ZERO {
134 self.owner.set(new_owner);
135 return Ok(());
136 }
137 // Check if the owner is already set.
138 if self.owner.get() != self.vm().msg_sender() {
139 return Err(b"Only owner can call this function".to_vec());
140 }
141 if new_owner == Address::ZERO {
142 return Err(b"Cannot transfer to zero address".to_vec());
143 }
144 self.owner.set(new_owner);
145 Ok(())
146 }
147}
148
149#[cfg(test)]
150mod test {
151 use super::*;
152
153 #[test]
154 fn test_counter() {
155 use stylus_sdk::testing::*;
156 let vm = TestVM::default();
157 let mut contract = Counter::from(&vm);
158
159 assert_eq!(U256::ZERO, contract.number());
160
161 contract.increment();
162 assert_eq!(U256::from(1), contract.number());
163
164 contract.add_number(U256::from(3));
165 assert_eq!(U256::from(4), contract.number());
166
167 contract.mul_number(U256::from(2));
168 assert_eq!(U256::from(8), contract.number());
169
170 contract.set_number(U256::from(100));
171 assert_eq!(U256::from(100), contract.number());
172
173 // Override the msg value for future contract method invocations.
174 vm.set_value(U256::from(2));
175
176 contract.add_from_msg_value();
177 assert_eq!(U256::from(102), contract.number());
178 }
179
180 #[test]
181 fn test_decrement() {
182 use stylus_sdk::testing::*;
183 let vm = TestVM::new();
184 let mut contract = Counter::from(&vm);
185
186 contract.set_number(U256::from(5));
187 // Decrement should succeed
188 assert!(contract.decrement().is_ok());
189 assert_eq!(contract.number(), U256::from(4));
190
191 // Multiple decrements
192 assert!(contract.decrement().is_ok());
193 assert_eq!(contract.number(), U256::from(3));
194
195 // Set to zero and try to decrement again
196 contract.set_number(U256::ZERO);
197 assert!(contract.decrement().is_err());
198 }
199
200 #[test]
201 fn test_logs() {
202 use stylus_sdk::testing::*;
203 use alloy_primitives::hex;
204 use stylus_sdk::alloy_primitives::B256;
205 let vm = TestVM::new();
206 let sender = vm.msg_sender();
207 let mut contract = Counter::from(&vm);
208 // Perform an action that emits an event
209 contract.increment();
210
211 // Get the emitted logs
212 let logs = vm.get_emitted_logs();
213 assert_eq!(logs.len(), 2);
214
215 // Check the event topic (first topic is the event signature)
216 // Precalculated the event signature for the event CounterUpdated(address indexed user, uint256 prev_value, uint256 new_value);
217 let event_signature: B256 = hex!("c9d64952459b33e1dd10d284fe1e9336b8c514cbf51792a888ee7615ca3225d9").into();
218 assert_eq!(logs[0].0[0], event_signature);
219 // Check that the indexed user address is in the topics
220 let user_topic = logs[0].0[1];
221 let user_bytes: [u8; 32] = user_topic.into();
222
223 // The indexed address is padded to 32 bytes, extract the last 20 bytes
224 let mut user_address = [0u8; 20];
225 user_address.copy_from_slice(&user_bytes[12..32]);
226 assert_eq!(Address::from(user_address), sender);
227 }
228 #[test]
229 fn test_external_call() {
230 use stylus_sdk::testing::*;
231 let vm = TestVM::new();
232 let mut contract = Counter::from(&vm);
233 let sender = vm.msg_sender();
234 assert!(contract.transfer_ownership(sender).is_ok());
235 // 2) Prepare inputs
236 let target = Address::from([0x05; 20]);
237 let call_data = vec![1, 2, 3, 4];
238 let success_ret = vec![5, 6, 7, 8];
239 let error_ret = vec![9, 9, 9];
240
241 // 3) Mock a successful external call
242 vm.mock_call(target, call_data.clone(), Ok(success_ret.clone()));
243 let got = contract.call_external_contract(target, call_data.clone());
244 assert_eq!(got, Ok(success_ret));
245
246 // 4) Mock a reverting external call
247 vm.mock_call(target, call_data.clone(), Err(error_ret.clone()));
248 let err = contract
249 .call_external_contract(target, call_data.clone())
250 .unwrap_err();
251 let expected = format!("Revert({:?})", error_ret).as_bytes().to_vec();
252 assert_eq!(err, expected);
253 }
254
255 #[test]
256 fn test_storage_direct_access() {
257 use stylus_sdk::testing::*;
258 use stylus_sdk::alloy_primitives::{U256, B256};
259
260 // 1) Create the VM and your Counter instance
261 let vm = TestVM::new();
262 let mut contract = Counter::from(&vm);
263
264 // 2) Initialize slot 0 to 42 via your setter
265 contract.set_number(U256::from(42u64));
266
267 // 2) Storage slot for `count` is the first field → slot 0
268 let slot = U256::ZERO;
269
270 // 3) Read it directly — should reflect the constructor value (42)
271 let raw = vm.storage_load_bytes32(slot);
272 assert_eq!(
273 raw,
274 B256::from_slice(&U256::from(42u64).to_be_bytes::<32>())
275 );
276
277 // 4) Overwrite the slot in the cache, then flush it
278 let new_val = U256::from(100u64);
279 unsafe {
280 vm.storage_cache_bytes32(
281 slot,
282 B256::from_slice(&new_val.to_be_bytes::<32>()),
283 );
284 }
285 vm.flush_cache(false);
286
287 // 5) Now your getter should see the updated value
288 assert_eq!(contract.number(), new_val);
289 }
290
291 #[test]
292 fn test_block_data() {
293 use stylus_sdk::testing::*;
294 use alloy_primitives::{Address, U256};
295
296 let vm: TestVM = TestVMBuilder::new()
297 .sender(Address::from([1u8; 20]))
298 .contract_address(Address::from([2u8; 20]))
299 .value(U256::from(10))
300 .build();
301
302 let mut contract = Counter::from(&vm);
303
304 // 2) Set initial block timestamp & number on the VM
305 vm.set_block_timestamp(1_234_567_890);
306 vm.set_block_number(100);
307 // Increment to trigger timestamp update
308 contract.increment();
309
310 // 4) First increment: just call it, then check `last_updated`
311 contract.increment();
312 assert_eq!(
313 contract.last_updated(),
314 U256::from(1_234_567_890u64),
315 "after first increment, timestamp should be the initial VM timestamp"
316 );
317
318 // Update block number and timestamp
319 vm.set_block_timestamp(2000000000);
320 vm.set_block_number(200);
321
322 // 6) Second increment: call again, then check updated timestamp
323 contract.increment();
324 assert_eq!(
325 contract.last_updated(),
326 U256::from(2_000_000_000u64),
327 "after second increment, timestamp should reflect VM update"
328 );
329 }
330 #[test]
331 fn test_ownership() {
332 use stylus_sdk::testing::*;
333 // 1) Create the VM and the contract instance
334 let vm = TestVM::new();
335 let sender = vm.msg_sender();
336 let mut contract = Counter::from(&vm);
337 let target = Address::from([0x05; 20]);
338 let call_data = vec![1, 2, 3, 4];
339 let success_ret = vec![5, 6, 7, 8];
340 let error_ret = vec![9, 9, 9];
341 // 2) Set the contract owner to the sender
342 assert!(contract.transfer_ownership(sender).is_ok());
343
344 // Change sender to non-owner
345 let non_owner = Address::from([3u8; 20]);
346 vm.set_sender(non_owner);
347
348 // Check if non-owner can call external call function before changing ownership
349 vm.mock_call(target, call_data.clone(), Err(error_ret.clone()));
350 assert!(contract.call_external_contract(target, call_data.clone()).is_err());
351
352 // Non-owner should not be able to transfer ownership
353 assert!(contract.transfer_ownership(non_owner).is_err());
354
355 // Change back to owner
356 vm.set_sender(sender);
357
358 // Owner should be able to transfer ownership
359 assert!(contract.transfer_ownership(non_owner).is_ok());
360 assert_eq!(contract.owner(), non_owner);
361 // Check if non-owner can call external call function after changing ownership
362 vm.set_sender(non_owner);
363 vm.mock_call(target, call_data.clone(), Ok(success_ret.clone()));
364 let got = contract.call_external_contract(target, call_data.clone());
365 assert_eq!(got, Ok(success_ret));
366 }
367}
368
369//Writing your Own Custom TestVM
370// A TestVM is a simple struct implemented in the stylus-test crate that implements the Host trait from stylus_core::host::Host. Anyone can implement the trait and allow for rich testing experiences for Stylus contracts. The TestVM is not the only way to unit test your projects, as you can extend or implement your own.
371
372// Here’s a “general-purpose” extension to TestVM that ix just a way to track how many times someone has called into mock_call, so you can assert on how many external calls you set up:
373// This is a simple example, but you can imagine more complex scenarios where you might want to track how many times a function was called, or what the arguments were, etc. You can also use this to set up more complex test cases where you need to mock multiple calls with different arguments.
374// This is a simple example, but you can imagine more complex scenarios where you might want to track how many times a function was called, or what the arguments were, etc. You can also use this to set up more complex test cases where you need to mock multiple calls with different arguments.
375
376#[cfg(test)]
377mod custom_vm_tests {
378 use super::*;
379 use stylus_sdk::testing::TestVM;
380 use alloy_primitives::{Address};
381
382 /// A thin wrapper around TestVM that counts how many times
383 /// `mock_call` has been invoked.
384 pub struct CustomVM {
385 inner: TestVM,
386 mock_call_count: usize,
387 }
388
389 impl CustomVM {
390 /// Start with the default TestVM
391 pub fn new() -> Self {
392 Self { inner: TestVM::default(), mock_call_count: 0 }
393 }
394 /// **Wrapped** mock_call: increments our counter, then delegates.
395 pub fn mock_call(
396 &mut self,
397 target: Address,
398 data: Vec<u8>,
399 ret: Result<Vec<u8>, Vec<u8>>,
400 ) {
401 self.mock_call_count += 1;
402 self.inner.mock_call(target, data, ret);
403 }
404
405 /// New helper: how many mocks have been defined so far?
406 pub fn mock_call_count(&self) -> usize {
407 self.mock_call_count
408 }
409
410 /// Expose the raw TestVM when you need it for `Counter::from(&…)`
411 pub fn inner(&self) -> &TestVM {
412 &self.inner
413 }
414 }
415
416 #[test]
417 fn test_tracking_number_of_mocks() {
418 // 1) Build our custom VM wrapper
419 let mut vm = CustomVM::new();
420
421 // 2) Deploy a Counter (or any contract) against the inner TestVM
422 let mut contract = Counter::from(vm.inner());
423
424 // 3) Before any mocks, count should be zero
425 assert_eq!(vm.mock_call_count(), 0);
426
427 // 4) Define two mock calls
428 let addr = Address::from([0x05; 20]);
429 let data = vec![1, 2, 3];
430 vm.mock_call(addr, data.clone(), Ok(vec![0xAA]));
431 vm.mock_call(addr, data, Err(vec![0xBB]));
432
433 // 5) Now our helper sees exactly two mocks
434 assert_eq!(vm.mock_call_count(), 2);
435
436 // 6) And of course external calls still work through inner:
437 let _ = contract.call_external_contract(addr, vec![1, 2, 3]);
438 let _ = contract.call_external_contract(addr, vec![1, 2, 3]);
439
440 // 7) But the number of *defined* mocks remains the same
441 assert_eq!(vm.mock_call_count(), 2);
442 }
443}
1#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
2extern crate alloc;
3
4/// Import items from the SDK. The prelude contains common traits and macros.
5use stylus_sdk::{alloy_primitives::{Address, U256}, prelude::*};
6use stylus_sdk::alloy_sol_types::sol;
7// Define some persistent storage using the Solidity ABI.
8// `Counter` will be the entrypoint.
9sol_storage! {
10 #[entrypoint]
11 pub struct Counter {
12 uint256 number;
13 address owner;
14 uint256 last_updated;
15 }
16}
17
18// Define events
19sol! {
20 event CounterUpdated(address indexed user, uint256 prev_value, uint256 new_value);
21 }
22
23/// Declare that `Counter` is a contract with the following external methods.
24#[public]
25impl Counter {
26 pub fn owner(&self) -> Address {
27 self.owner.get()
28 }
29 pub fn number(&self) -> U256 {
30 self.number.get()
31 }
32 pub fn last_updated(&self) -> U256 {
33 self.last_updated.get()
34 }
35 /// Sets a number in storage to a user-specified value.
36 pub fn set_number(&mut self, new_number: U256) {
37 let prev = self.number.get();
38 self.number.set(new_number);
39 // Update the last updated timestamp.
40 self.last_updated.set(U256::from(self.vm().block_timestamp()));
41 // Emit an event
42 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
43 user: self.vm().msg_sender(),
44 prev_value: prev,
45 new_value: self.number.get(),
46 });
47 }
48 /// Sets a number in storage to a user-specified value.
49 pub fn mul_number(&mut self, new_number: U256) {
50 self.number.set(new_number * self.number.get());
51 let prev = self.number.get();
52 // Update the last updated timestamp.
53 self.last_updated.set(U256::from(self.vm().block_timestamp()));
54 // Emit an event
55 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
56 user: self.vm().msg_sender(),
57 prev_value: prev,
58 new_value: self.number.get(),
59 });
60 }
61 /// Sets a number in storage to a user-specified value.
62 pub fn add_number(&mut self, new_number: U256) {
63 self.number.set(new_number + self.number.get());
64 let prev = self.number.get();
65 // Update the last updated timestamp.
66 self.last_updated.set(U256::from(self.vm().block_timestamp()));
67 // Emit an event
68 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
69 user: self.vm().msg_sender(),
70 prev_value: prev,
71 new_value: self.number.get(),
72 });
73 }
74 /// Increments `number` and updates its value in storage.
75 pub fn increment(&mut self) {
76 // Increment the number in storage.
77 let prev = self.number.get();
78 self.set_number(prev + U256::from(1));
79 // Update the last updated timestamp.
80 self.last_updated.set(U256::from(self.vm().block_timestamp()));
81 // Emit an event
82 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
83 user: self.vm().msg_sender(),
84 prev_value: prev,
85 new_value: self.number.get(),
86 });
87 }
88 /// Decrements `number` and updates its value in storage.
89 /// Returns an error if the number is already zero.
90 pub fn decrement(&mut self) -> Result<(), Vec<u8>> {
91 let prev = self.number.get();
92 if prev == U256::ZERO {
93 return Err(b"Counter cannot go below zero".to_vec());
94 }
95
96 self.number.set(prev - U256::from(1));
97 // Update the last updated timestamp.
98 self.last_updated.set(U256::from(self.vm().block_timestamp()));
99 // Emit an event
100 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
101 user: self.vm().msg_sender(),
102 prev_value: prev,
103 new_value: self.number.get(),
104 });
105
106 Ok(())
107 }
108 /// Adds the wei value from msg_value to the number in storage.
109 #[payable]
110 pub fn add_from_msg_value(&mut self) {
111 let prev = self.number.get();
112 self.set_number(prev + self.vm().msg_value());
113 // Update the last updated timestamp.
114 self.last_updated.set(U256::from(self.vm().block_timestamp()));
115 // Emit an event
116 stylus_sdk::stylus_core::log(self.vm(), CounterUpdated {
117 user: self.vm().msg_sender(),
118 prev_value: prev,
119 new_value: self.number.get(),
120 });
121 }
122 // External call example
123 pub fn call_external_contract(&mut self, target: Address, data: Vec<u8>) -> Result<Vec<u8>, Vec<u8>> {
124 if self.owner.get() != self.vm().msg_sender() {
125 return Err(b"Only owner can call this function".to_vec());
126 }
127 let return_data = self.vm().call(&self, target, &data)
128 .map_err(|err| format!("{:?}", err).as_bytes().to_vec())?;
129 Ok(return_data)
130 }
131 /// Transfers ownership of the contract to a new address.
132 pub fn transfer_ownership(&mut self, new_owner: Address) -> Result<(), Vec<u8>> {
133 if self.owner.get() == Address::ZERO {
134 self.owner.set(new_owner);
135 return Ok(());
136 }
137 // Check if the owner is already set.
138 if self.owner.get() != self.vm().msg_sender() {
139 return Err(b"Only owner can call this function".to_vec());
140 }
141 if new_owner == Address::ZERO {
142 return Err(b"Cannot transfer to zero address".to_vec());
143 }
144 self.owner.set(new_owner);
145 Ok(())
146 }
147}
148
149#[cfg(test)]
150mod test {
151 use super::*;
152
153 #[test]
154 fn test_counter() {
155 use stylus_sdk::testing::*;
156 let vm = TestVM::default();
157 let mut contract = Counter::from(&vm);
158
159 assert_eq!(U256::ZERO, contract.number());
160
161 contract.increment();
162 assert_eq!(U256::from(1), contract.number());
163
164 contract.add_number(U256::from(3));
165 assert_eq!(U256::from(4), contract.number());
166
167 contract.mul_number(U256::from(2));
168 assert_eq!(U256::from(8), contract.number());
169
170 contract.set_number(U256::from(100));
171 assert_eq!(U256::from(100), contract.number());
172
173 // Override the msg value for future contract method invocations.
174 vm.set_value(U256::from(2));
175
176 contract.add_from_msg_value();
177 assert_eq!(U256::from(102), contract.number());
178 }
179
180 #[test]
181 fn test_decrement() {
182 use stylus_sdk::testing::*;
183 let vm = TestVM::new();
184 let mut contract = Counter::from(&vm);
185
186 contract.set_number(U256::from(5));
187 // Decrement should succeed
188 assert!(contract.decrement().is_ok());
189 assert_eq!(contract.number(), U256::from(4));
190
191 // Multiple decrements
192 assert!(contract.decrement().is_ok());
193 assert_eq!(contract.number(), U256::from(3));
194
195 // Set to zero and try to decrement again
196 contract.set_number(U256::ZERO);
197 assert!(contract.decrement().is_err());
198 }
199
200 #[test]
201 fn test_logs() {
202 use stylus_sdk::testing::*;
203 use alloy_primitives::hex;
204 use stylus_sdk::alloy_primitives::B256;
205 let vm = TestVM::new();
206 let sender = vm.msg_sender();
207 let mut contract = Counter::from(&vm);
208 // Perform an action that emits an event
209 contract.increment();
210
211 // Get the emitted logs
212 let logs = vm.get_emitted_logs();
213 assert_eq!(logs.len(), 2);
214
215 // Check the event topic (first topic is the event signature)
216 // Precalculated the event signature for the event CounterUpdated(address indexed user, uint256 prev_value, uint256 new_value);
217 let event_signature: B256 = hex!("c9d64952459b33e1dd10d284fe1e9336b8c514cbf51792a888ee7615ca3225d9").into();
218 assert_eq!(logs[0].0[0], event_signature);
219 // Check that the indexed user address is in the topics
220 let user_topic = logs[0].0[1];
221 let user_bytes: [u8; 32] = user_topic.into();
222
223 // The indexed address is padded to 32 bytes, extract the last 20 bytes
224 let mut user_address = [0u8; 20];
225 user_address.copy_from_slice(&user_bytes[12..32]);
226 assert_eq!(Address::from(user_address), sender);
227 }
228 #[test]
229 fn test_external_call() {
230 use stylus_sdk::testing::*;
231 let vm = TestVM::new();
232 let mut contract = Counter::from(&vm);
233 let sender = vm.msg_sender();
234 assert!(contract.transfer_ownership(sender).is_ok());
235 // 2) Prepare inputs
236 let target = Address::from([0x05; 20]);
237 let call_data = vec![1, 2, 3, 4];
238 let success_ret = vec![5, 6, 7, 8];
239 let error_ret = vec![9, 9, 9];
240
241 // 3) Mock a successful external call
242 vm.mock_call(target, call_data.clone(), Ok(success_ret.clone()));
243 let got = contract.call_external_contract(target, call_data.clone());
244 assert_eq!(got, Ok(success_ret));
245
246 // 4) Mock a reverting external call
247 vm.mock_call(target, call_data.clone(), Err(error_ret.clone()));
248 let err = contract
249 .call_external_contract(target, call_data.clone())
250 .unwrap_err();
251 let expected = format!("Revert({:?})", error_ret).as_bytes().to_vec();
252 assert_eq!(err, expected);
253 }
254
255 #[test]
256 fn test_storage_direct_access() {
257 use stylus_sdk::testing::*;
258 use stylus_sdk::alloy_primitives::{U256, B256};
259
260 // 1) Create the VM and your Counter instance
261 let vm = TestVM::new();
262 let mut contract = Counter::from(&vm);
263
264 // 2) Initialize slot 0 to 42 via your setter
265 contract.set_number(U256::from(42u64));
266
267 // 2) Storage slot for `count` is the first field → slot 0
268 let slot = U256::ZERO;
269
270 // 3) Read it directly — should reflect the constructor value (42)
271 let raw = vm.storage_load_bytes32(slot);
272 assert_eq!(
273 raw,
274 B256::from_slice(&U256::from(42u64).to_be_bytes::<32>())
275 );
276
277 // 4) Overwrite the slot in the cache, then flush it
278 let new_val = U256::from(100u64);
279 unsafe {
280 vm.storage_cache_bytes32(
281 slot,
282 B256::from_slice(&new_val.to_be_bytes::<32>()),
283 );
284 }
285 vm.flush_cache(false);
286
287 // 5) Now your getter should see the updated value
288 assert_eq!(contract.number(), new_val);
289 }
290
291 #[test]
292 fn test_block_data() {
293 use stylus_sdk::testing::*;
294 use alloy_primitives::{Address, U256};
295
296 let vm: TestVM = TestVMBuilder::new()
297 .sender(Address::from([1u8; 20]))
298 .contract_address(Address::from([2u8; 20]))
299 .value(U256::from(10))
300 .build();
301
302 let mut contract = Counter::from(&vm);
303
304 // 2) Set initial block timestamp & number on the VM
305 vm.set_block_timestamp(1_234_567_890);
306 vm.set_block_number(100);
307 // Increment to trigger timestamp update
308 contract.increment();
309
310 // 4) First increment: just call it, then check `last_updated`
311 contract.increment();
312 assert_eq!(
313 contract.last_updated(),
314 U256::from(1_234_567_890u64),
315 "after first increment, timestamp should be the initial VM timestamp"
316 );
317
318 // Update block number and timestamp
319 vm.set_block_timestamp(2000000000);
320 vm.set_block_number(200);
321
322 // 6) Second increment: call again, then check updated timestamp
323 contract.increment();
324 assert_eq!(
325 contract.last_updated(),
326 U256::from(2_000_000_000u64),
327 "after second increment, timestamp should reflect VM update"
328 );
329 }
330 #[test]
331 fn test_ownership() {
332 use stylus_sdk::testing::*;
333 // 1) Create the VM and the contract instance
334 let vm = TestVM::new();
335 let sender = vm.msg_sender();
336 let mut contract = Counter::from(&vm);
337 let target = Address::from([0x05; 20]);
338 let call_data = vec![1, 2, 3, 4];
339 let success_ret = vec![5, 6, 7, 8];
340 let error_ret = vec![9, 9, 9];
341 // 2) Set the contract owner to the sender
342 assert!(contract.transfer_ownership(sender).is_ok());
343
344 // Change sender to non-owner
345 let non_owner = Address::from([3u8; 20]);
346 vm.set_sender(non_owner);
347
348 // Check if non-owner can call external call function before changing ownership
349 vm.mock_call(target, call_data.clone(), Err(error_ret.clone()));
350 assert!(contract.call_external_contract(target, call_data.clone()).is_err());
351
352 // Non-owner should not be able to transfer ownership
353 assert!(contract.transfer_ownership(non_owner).is_err());
354
355 // Change back to owner
356 vm.set_sender(sender);
357
358 // Owner should be able to transfer ownership
359 assert!(contract.transfer_ownership(non_owner).is_ok());
360 assert_eq!(contract.owner(), non_owner);
361 // Check if non-owner can call external call function after changing ownership
362 vm.set_sender(non_owner);
363 vm.mock_call(target, call_data.clone(), Ok(success_ret.clone()));
364 let got = contract.call_external_contract(target, call_data.clone());
365 assert_eq!(got, Ok(success_ret));
366 }
367}
368
369//Writing your Own Custom TestVM
370// A TestVM is a simple struct implemented in the stylus-test crate that implements the Host trait from stylus_core::host::Host. Anyone can implement the trait and allow for rich testing experiences for Stylus contracts. The TestVM is not the only way to unit test your projects, as you can extend or implement your own.
371
372// Here’s a “general-purpose” extension to TestVM that ix just a way to track how many times someone has called into mock_call, so you can assert on how many external calls you set up:
373// This is a simple example, but you can imagine more complex scenarios where you might want to track how many times a function was called, or what the arguments were, etc. You can also use this to set up more complex test cases where you need to mock multiple calls with different arguments.
374// This is a simple example, but you can imagine more complex scenarios where you might want to track how many times a function was called, or what the arguments were, etc. You can also use this to set up more complex test cases where you need to mock multiple calls with different arguments.
375
376#[cfg(test)]
377mod custom_vm_tests {
378 use super::*;
379 use stylus_sdk::testing::TestVM;
380 use alloy_primitives::{Address};
381
382 /// A thin wrapper around TestVM that counts how many times
383 /// `mock_call` has been invoked.
384 pub struct CustomVM {
385 inner: TestVM,
386 mock_call_count: usize,
387 }
388
389 impl CustomVM {
390 /// Start with the default TestVM
391 pub fn new() -> Self {
392 Self { inner: TestVM::default(), mock_call_count: 0 }
393 }
394 /// **Wrapped** mock_call: increments our counter, then delegates.
395 pub fn mock_call(
396 &mut self,
397 target: Address,
398 data: Vec<u8>,
399 ret: Result<Vec<u8>, Vec<u8>>,
400 ) {
401 self.mock_call_count += 1;
402 self.inner.mock_call(target, data, ret);
403 }
404
405 /// New helper: how many mocks have been defined so far?
406 pub fn mock_call_count(&self) -> usize {
407 self.mock_call_count
408 }
409
410 /// Expose the raw TestVM when you need it for `Counter::from(&…)`
411 pub fn inner(&self) -> &TestVM {
412 &self.inner
413 }
414 }
415
416 #[test]
417 fn test_tracking_number_of_mocks() {
418 // 1) Build our custom VM wrapper
419 let mut vm = CustomVM::new();
420
421 // 2) Deploy a Counter (or any contract) against the inner TestVM
422 let mut contract = Counter::from(vm.inner());
423
424 // 3) Before any mocks, count should be zero
425 assert_eq!(vm.mock_call_count(), 0);
426
427 // 4) Define two mock calls
428 let addr = Address::from([0x05; 20]);
429 let data = vec![1, 2, 3];
430 vm.mock_call(addr, data.clone(), Ok(vec![0xAA]));
431 vm.mock_call(addr, data, Err(vec![0xBB]));
432
433 // 5) Now our helper sees exactly two mocks
434 assert_eq!(vm.mock_call_count(), 2);
435
436 // 6) And of course external calls still work through inner:
437 let _ = contract.call_external_contract(addr, vec![1, 2, 3]);
438 let _ = contract.call_external_contract(addr, vec![1, 2, 3]);
439
440 // 7) But the number of *defined* mocks remains the same
441 assert_eq!(vm.mock_call_count(), 2);
442 }
443}
1#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
2
3#[cfg(not(any(test, feature = "export-abi")))]
4#[no_mangle]
5pub extern "C" fn main() {}
6
7#[cfg(feature = "export-abi")]
8fn main() {
9 stylus_test::print_abi("MIT-OR-APACHE-2.0", "pragma solidity ^0.8.23;");
10}
1#![cfg_attr(not(any(test, feature = "export-abi")), no_main)]
2
3#[cfg(not(any(test, feature = "export-abi")))]
4#[no_mangle]
5pub extern "C" fn main() {}
6
7#[cfg(feature = "export-abi")]
8fn main() {
9 stylus_test::print_abi("MIT-OR-APACHE-2.0", "pragma solidity ^0.8.23;");
10}
1[package]
2name = name = "stylus-test"
3version = "0.1.11"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6homepage = "https://github.com/OffchainLabs/stylus-hello-world"
7repository = "https://github.com/OffchainLabs/stylus-hello-world"
8keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
9description = "Stylus hello world example"
10
11[dependencies]
12alloy-primitives = "=0.8.20"
13alloy-sol-types = "=0.8.20"
14mini-alloc = "0.8.4"
15stylus-sdk = "0.8.4"
16hex = "0.4.3"
17dotenv = "0.15.0"
18
19[dev-dependencies]
20tokio = { version = "1.12.0", features = ["full"] }
21ethers = "2.0"
22eyre = "0.6.8"
23stylus-sdk = { version = "0.8.4", features = ["stylus-test"] }
24
25[features]
26export-abi = ["stylus-sdk/export-abi"]
27debug = ["stylus-sdk/debug"]
28
29[[bin]]
30name = "stylus-hello-world"
31path = "src/main.rs"
32
33[lib]
34crate-type = ["lib", "cdylib"]
35
36[profile.release]
37codegen-units = 1
38strip = true
39lto = true
40panic = "abort"
41
42# If you need to reduce the binary size, it is advisable to try other
43# optimization levels, such as "s" and "z"
44opt-level = 3
1[package]
2name = name = "stylus-test"
3version = "0.1.11"
4edition = "2021"
5license = "MIT OR Apache-2.0"
6homepage = "https://github.com/OffchainLabs/stylus-hello-world"
7repository = "https://github.com/OffchainLabs/stylus-hello-world"
8keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
9description = "Stylus hello world example"
10
11[dependencies]
12alloy-primitives = "=0.8.20"
13alloy-sol-types = "=0.8.20"
14mini-alloc = "0.8.4"
15stylus-sdk = "0.8.4"
16hex = "0.4.3"
17dotenv = "0.15.0"
18
19[dev-dependencies]
20tokio = { version = "1.12.0", features = ["full"] }
21ethers = "2.0"
22eyre = "0.6.8"
23stylus-sdk = { version = "0.8.4", features = ["stylus-test"] }
24
25[features]
26export-abi = ["stylus-sdk/export-abi"]
27debug = ["stylus-sdk/debug"]
28
29[[bin]]
30name = "stylus-hello-world"
31path = "src/main.rs"
32
33[lib]
34crate-type = ["lib", "cdylib"]
35
36[profile.release]
37codegen-units = 1
38strip = true
39lto = true
40panic = "abort"
41
42# If you need to reduce the binary size, it is advisable to try other
43# optimization levels, such as "s" and "z"
44opt-level = 3