Arbitrum Stylus logo

Stylus by Example

Unit Testing

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.

1. HostAccess and 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.

2. Basic Unit Test

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 isolation
  • Counter::from(&vm) wires up storage against that VM
  • Calls like increment() and add_from_msg_value() run instantly—no blockchain needed
  • vm.set_value(...) overrides the msg.value() for that test
  • assert_eq!(...) checks the contract state after each call to verify logic

3. Inspecting & Mocking Host I/O

Stylus’s TestVM provides methods to override and inspect every host function. Use the table below as a quick reference:

Scenario

TestVM API

Override Ether attachedTestVM.set_value(U256)
Override Caller addressTestVM.set_sender(Address)
Read raw storage slotTestVM.storage_load_bytes32(slot)
Write raw storage slot & commitunsafe { TestVM.storage_cache_bytes32(slot, val) }; TestVM.flush_cache(false)
Override block parametersTestVM.set_block_number(n)
TestVM.set_block_timestamp(ts)
Inspect emitted logs & eventsTestVM.get_emitted_logs()
Mock external call responseTestVM.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.

4. Event & Log Testing

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) pairs
  • Topics[0] is always the keccak-256 of the event signature
  • Subsequent topics hold indexed parameters, ABI-encoded

5. Mocking External Calls

Use 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
  • Subsequent .call(&self, target, &data) picks up the mock

6. Raw Storage Testing

Directly 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 VM
  • vm.storage_cache_bytes32(slot, value) writes to the VM cache
  • vm.flush_cache(false) commits the cache to the VM

7. Block-Context Testing

Simulate 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 timestamp
  • vm.set_block_number(...) overrides the block number

8. Customizing the VM

You can extend TestVM to add custom instrumentation or helpers without re-implementing the entire Host trait. This is useful for test-specific behaviors.

8-a. 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);

8-b. Wrapping 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

  • You never touch the sealed Host trait—simply delegate to TestVM.
  • Add any helper you like: call counters, argument recorders, custom fail patterns, etc.

8-c. Implementing Your Own 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.

9. Cheat-Sheet & Best Practices

What to test…

TestVM API

msg.value()vm.set_value(U256)
msg.sender()vm.set_sender(Address)
Raw storagevm.storage_load_bytes32(k)
unsafe { vm.storage_cache_bytes32(k, v) }; vm.flush_cache(false)
Block paramsvm.set_block_number(n)
vm.set_block_timestamp(ts)
Events & logsvm.get_emitted_logs()
External callsvm.mock_call(addr, data, Ok/Err)
Custom instrumentationWrap TestVM in your own struct and expose helpers
  • Group tests by feature or behavior
  • Keep contract logic pure; tests mock all side-effects
  • Use TestVMBuilder for forked or pre-configured state
  • Wrap TestVM 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.

10. Complete Test Coverage Example

The contract below demonstrates a comprehensive test suite that covers all aspects of Stylus contract testing:

  • Basic state manipulation tests
  • Event emission verification
  • External call mocking (both success and failure cases)
  • Direct storage slot access
  • Time-dependent logic with block context manipulation
  • Access control and ownership tests
  • Edge case handling and error conditions
  • Custom test VM extensions for advanced instrumentation

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.

src/lib.rs

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}

main.rs

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}

Cargo.toml

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