Skip to content

Commit e6ad903

Browse files
authored
Merge pull request #3408 from OffchainLabs/stylus-docs-fundamentals
Update Stylus fundamentals pages for SDK 0.10.7
2 parents a420eb7 + b5bf0bd commit e6ad903

4 files changed

Lines changed: 99 additions & 595 deletions

File tree

docs/stylus/fundamentals/contracts.mdx

Lines changed: 83 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ pub struct HelloWorld;
3434

3535
#[public]
3636
impl HelloWorld {
37-
fn user_main(_input: Vec<u8>) -> ArbResult {
38-
Ok(Vec::new())
37+
pub fn greet(&self) -> String {
38+
"Hello, Stylus!".to_string()
3939
}
4040
}
4141
```
@@ -44,8 +44,8 @@ This contract:
4444

4545
- Uses `#[storage]` to define the contract struct (empty in this case)
4646
- Uses `#[entrypoint]` to mark it as the contract's entry point
47-
- Uses `#[public]` to expose the `user_main` function
48-
- Returns `ArbResult`, which is `Result<Vec<u8>, Vec<u8>>`
47+
- Uses `#[public]` to expose the `greet` method
48+
- Exposes a single read-only method callable from Solidity or other contracts
4949

5050
## Storage definition
5151

@@ -101,7 +101,7 @@ The `#[entrypoint]` macro marks a struct as the contract's main entry point. It
101101

102102
- Routing incoming calls to public methods
103103
- Managing contract storage
104-
- Handling reentrancy protection (unless the `reentrant` feature is enabled)
104+
- Flushing the storage cache automatically on cross-contract calls, which protects against reentrancy by default; follow the checks-effects-interactions pattern for additional safety
105105

106106
**Key requirements:**
107107

@@ -187,7 +187,8 @@ impl MyContract {
187187
let sender = self.vm().msg_sender();
188188
let balance = self.balances.get(sender);
189189
self.balances.setter(sender).set(balance - amount);
190-
self.balances.setter(to).set(self.balances.get(to) + amount);
190+
let to_balance = self.balances.get(to);
191+
self.balances.setter(to).set(to_balance + amount);
191192
}
192193

193194
// Pure: no state access at all
@@ -219,7 +220,7 @@ sol_storage! {
219220
impl Token {
220221
#[constructor]
221222
pub fn constructor(&mut self, initial_supply: U256) {
222-
let deployer = self.vm().msg_sender();
223+
let deployer = self.vm().tx_origin();
223224
self.owner.set(deployer);
224225
self.total_supply.set(initial_supply);
225226
}
@@ -242,7 +243,7 @@ impl Token {
242243
pub fn constructor(&mut self, initial_supply: U256) {
243244
// Contract can receive ETH during deployment
244245
let received = self.vm().msg_value();
245-
self.owner.set(self.vm().msg_sender());
246+
self.owner.set(self.vm().tx_origin());
246247
self.total_supply.set(initial_supply);
247248
}
248249
}
@@ -562,7 +563,8 @@ impl Token {
562563
return false;
563564
}
564565
self.balances.setter(from).set(from_balance - value);
565-
self.balances.setter(to).set(self.balances.get(to) + value);
566+
let to_balance = self.balances.get(to);
567+
self.balances.setter(to).set(to_balance + value);
566568

567569
// Emit event
568570
self.vm().log(Transfer { from, to, value });
@@ -625,7 +627,7 @@ sol_interface! {
625627
#### View calls (read-only)
626628

627629
```rust
628-
use stylus_sdk::call::Call;
630+
use stylus_sdk::prelude::*; // brings `Call` into scope
629631

630632
#[public]
631633
impl MyContract {
@@ -687,6 +689,7 @@ For maximum flexibility, use raw calls:
687689

688690
```rust
689691
use stylus_sdk::call::{call, static_call, RawCall};
692+
use stylus_sdk::prelude::*; // brings `Call` into scope
690693

691694
#[public]
692695
impl MyContract {
@@ -695,12 +698,12 @@ impl MyContract {
695698
let config = Call::new_mutating(self)
696699
.gas(self.vm().evm_gas_left());
697700

698-
call(self.vm(), config, target, &calldata)
701+
Ok(call(self.vm(), config, target, &calldata)?)
699702
}
700703

701704
// Static call (read-only)
702705
pub fn execute_static_call(&self, target: Address, calldata: Vec<u8>) -> Result<Vec<u8>, Vec<u8>> {
703-
static_call(self.vm(), Call::new(), target, &calldata)
706+
Ok(static_call(self.vm(), Call::new(), target, &calldata)?)
704707
}
705708

706709
// Unsafe raw call with advanced options
@@ -767,7 +770,8 @@ impl Token {
767770
}
768771

769772
self.balances.setter(from).set(balance - amount);
770-
self.balances.setter(to).set(self.balances.get(to) + amount);
773+
let to_balance = self.balances.get(to);
774+
self.balances.setter(to).set(to_balance + amount);
771775

772776
Ok(true)
773777
}
@@ -829,7 +833,7 @@ impl SimpleToken {
829833
// Constructor
830834
#[constructor]
831835
pub fn constructor(&mut self, initial_supply: U256) {
832-
let deployer = self.vm().msg_sender();
836+
let deployer = self.vm().tx_origin();
833837
self.owner.set(deployer);
834838
self.balances.setter(deployer).set(initial_supply);
835839
self.total_supply.set(initial_supply);
@@ -906,7 +910,8 @@ impl SimpleToken {
906910
return Err(TokenError::Unauthorized(Unauthorized {}));
907911
}
908912

909-
self.balances.setter(to).set(self.balances.get(to) + value);
913+
let to_balance = self.balances.get(to);
914+
self.balances.setter(to).set(to_balance + value);
910915
self.total_supply.set(self.total_supply.get() + value);
911916

912917
self.vm().log(Transfer {
@@ -930,7 +935,8 @@ impl SimpleToken {
930935
}
931936

932937
self.balances.setter(from).set(from_balance - value);
933-
self.balances.setter(to).set(self.balances.get(to) + value);
938+
let to_balance = self.balances.get(to);
939+
self.balances.setter(to).set(to_balance + value);
934940

935941
self.vm().log(Transfer { from, to, value });
936942
Ok(())
@@ -1074,7 +1080,7 @@ pub fn withdraw(&mut self, amount: U256) -> Result<(), VaultError> {
10741080
self.balances.setter(caller).set(balance - amount);
10751081

10761082
// Interactions (external calls last)
1077-
// self.transfer_eth(caller, amount)?;
1083+
// transfer_eth(self.vm(), caller, amount)?;
10781084

10791085
Ok(())
10801086
}
@@ -1115,7 +1121,8 @@ This pattern is essential for building upgradeable contracts, proxy patterns, an
11151121
The `delegate_call` function is a low-level operation similar to `call` and `static_call`. It is considered unsafe because it relies on an external contract to ensure safety.
11161122

11171123
```rust
1118-
pub unsafe fn delegate_call(
1124+
pub unsafe fn delegate_call<H: Host + ?Sized>(
1125+
host: &H,
11191126
context: impl MutatingCallContext,
11201127
to: Address,
11211128
data: &[u8],
@@ -1126,14 +1133,16 @@ pub unsafe fn delegate_call(
11261133

11271134
```rust
11281135
use stylus_sdk::call::delegate_call;
1136+
use stylus_sdk::prelude::*; // brings `Call` into scope
11291137

11301138
pub fn low_level_delegate_call(
11311139
&mut self,
11321140
calldata: Vec<u8>,
11331141
target: Address,
11341142
) -> Result<Vec<u8>, DelegateCallErrors> {
11351143
unsafe {
1136-
let result = delegate_call(self, target, &calldata)
1144+
let config = Call::new_mutating(self);
1145+
let result = delegate_call(self.vm(), config, target, &calldata)
11371146
.map_err(|_| DelegateCallErrors::DelegateCallFailed(DelegateCallFailed {}))?;
11381147
Ok(result)
11391148
}
@@ -1152,20 +1161,24 @@ pub fn raw_delegate_call(
11521161
calldata: Vec<u8>,
11531162
target: Address,
11541163
) -> Result<Vec<u8>, Vec<u8>> {
1155-
let data = RawCall::new_delegate() // Configure a delegate call
1156-
.gas(2100) // Supply 2100 gas
1157-
.limit_return_data(0, 32) // Only read the first 32 bytes back
1158-
.call(target, &calldata)?;
1164+
let data = unsafe {
1165+
RawCall::new_delegate(self.vm()) // Configure a delegate call
1166+
.gas(2100) // Supply 2100 gas
1167+
.limit_return_data(0, 32) // Only read the first 32 bytes back
1168+
.call(target, &calldata)?
1169+
};
11591170

11601171
Ok(data)
11611172
}
11621173
```
11631174

11641175
### Safety considerations
11651176

1166-
:::caution
1177+
<VanillaAdmonition type="warning">
1178+
11671179
Delegate calls are inherently unsafe and should be used with caution.
1168-
:::
1180+
1181+
</VanillaAdmonition>
11691182

11701183
- **Trust requirement**: The calling contract must trust the external contract to uphold safety requirements
11711184
- **Storage modification**: The external contract can arbitrarily change the calling contract's storage
@@ -1207,7 +1220,8 @@ impl DelegateExample {
12071220
target: Address,
12081221
) -> Result<Vec<u8>, DelegateCallErrors> {
12091222
unsafe {
1210-
let result = delegate_call(self, target, &calldata)
1223+
let config = Call::new_mutating(self);
1224+
let result = delegate_call(self.vm(), config, target, &calldata)
12111225
.map_err(|_| DelegateCallErrors::DelegateCallFailed(DelegateCallFailed {}))?;
12121226

12131227
Ok(result)
@@ -1220,10 +1234,12 @@ impl DelegateExample {
12201234
calldata: Vec<u8>,
12211235
target: Address,
12221236
) -> Result<Vec<u8>, Vec<u8>> {
1223-
let data = RawCall::new_delegate()
1224-
.gas(2100)
1225-
.limit_return_data(0, 32)
1226-
.call(target, &calldata)?;
1237+
let data = unsafe {
1238+
RawCall::new_delegate(self.vm())
1239+
.gas(2100)
1240+
.limit_return_data(0, 32)
1241+
.call(target, &calldata)?
1242+
};
12271243

12281244
Ok(data)
12291245
}
@@ -1247,7 +1263,7 @@ Stylus provides multiple ways to send ether from a contract. Unlike Solidity's `
12471263
The simplest way to send ether:
12481264

12491265
```rust
1250-
use stylus_sdk::call::transfer_eth;
1266+
use stylus_sdk::call::transfer::transfer_eth;
12511267

12521268
#[public]
12531269
impl SendEther {
@@ -1264,13 +1280,16 @@ impl SendEther {
12641280
For more control over the transfer:
12651281

12661282
```rust
1267-
use stylus_sdk::call::{call, Call};
1283+
use stylus_sdk::call::call;
1284+
use stylus_sdk::prelude::*; // brings `Call` into scope
12681285

12691286
#[public]
12701287
impl SendEther {
12711288
#[payable]
12721289
pub fn send_via_call(&mut self, to: Address) -> Result<(), Vec<u8>> {
1273-
call(Call::new_in(self).value(self.vm().msg_value()), to, &[])?;
1290+
let value = self.vm().msg_value();
1291+
let context = Call::new_payable(self, value);
1292+
call(self.vm(), context, to, &[])?;
12741293
Ok(())
12751294
}
12761295
}
@@ -1281,7 +1300,8 @@ These two approaches are equivalent under the hood:
12811300
```rust
12821301
// These are equivalent:
12831302
transfer_eth(self.vm(), recipient, value)?;
1284-
call(Call::new_in(self).value(value), recipient, &[])?;
1303+
let context = Call::new_payable(self, value);
1304+
call(self.vm(), context, recipient, &[])?;
12851305
```
12861306

12871307
### Sending with a gas limit
@@ -1291,11 +1311,9 @@ To cap the gas forwarded to the recipient (similar to Solidity's `transfer`):
12911311
```rust
12921312
#[payable]
12931313
pub fn send_via_call_gas_limit(&mut self, to: Address, gas_amount: u64) -> Result<(), Vec<u8>> {
1294-
call(
1295-
Call::new_in(self).value(self.vm().msg_value()).gas(gas_amount),
1296-
to,
1297-
&[],
1298-
)?;
1314+
let value = self.vm().msg_value();
1315+
let context = Call::new_payable(self, value).gas(gas_amount);
1316+
call(self.vm(), context, to, &[])?;
12991317
Ok(())
13001318
}
13011319
```
@@ -1313,7 +1331,9 @@ pub fn send_via_call_with_calldata(
13131331
to: Address,
13141332
data: Bytes,
13151333
) -> Result<(), Vec<u8>> {
1316-
call(Call::new_in(self).value(self.vm().msg_value()), to, data.as_slice())?;
1334+
let value = self.vm().msg_value();
1335+
let context = Call::new_payable(self, value);
1336+
call(self.vm(), context, to, &data)?;
13171337
Ok(())
13181338
}
13191339
```
@@ -1334,8 +1354,9 @@ impl SendEther {
13341354
#[payable]
13351355
pub fn send_to_contract(&mut self, to: Address) -> Result<(), Vec<u8>> {
13361356
let target = ITarget::new(to);
1337-
let config = Call::new_in(self).value(self.vm().msg_value());
1338-
target.receive_ether(config)?;
1357+
let value = self.vm().msg_value();
1358+
let context = Call::new_payable(self, value);
1359+
target.receive_ether(self.vm(), context)?;
13391360
Ok(())
13401361
}
13411362
}
@@ -1357,7 +1378,7 @@ extern crate alloc;
13571378
use alloy_primitives::Address;
13581379
use stylus_sdk::{
13591380
abi::Bytes,
1360-
call::{call, transfer_eth, Call},
1381+
call::{call, transfer::transfer_eth},
13611382
prelude::*,
13621383
};
13631384

@@ -1375,26 +1396,27 @@ pub struct SendEther;
13751396
impl SendEther {
13761397
// Simple transfer
13771398
#[payable]
1378-
pub fn send_via_transfer(&self, to: Address) -> Result<(), Vec<u8>> {
1379-
transfer_eth(self.vm(), to, self.vm().msg_value())?;
1399+
pub fn send_via_transfer(&mut self, to: Address) -> Result<(), Vec<u8>> {
1400+
let value = self.vm().msg_value();
1401+
transfer_eth(self.vm(), to, value)?;
13801402
Ok(())
13811403
}
13821404

13831405
// Low-level call
13841406
#[payable]
13851407
pub fn send_via_call(&mut self, to: Address) -> Result<(), Vec<u8>> {
1386-
call(Call::new_in(self).value(self.vm().msg_value()), to, &[])?;
1408+
let value = self.vm().msg_value();
1409+
let context = Call::new_payable(self, value);
1410+
call(self.vm(), context, to, &[])?;
13871411
Ok(())
13881412
}
13891413

13901414
// With gas limit
13911415
#[payable]
13921416
pub fn send_via_call_gas_limit(&mut self, to: Address, gas_amount: u64) -> Result<(), Vec<u8>> {
1393-
call(
1394-
Call::new_in(self).value(self.vm().msg_value()).gas(gas_amount),
1395-
to,
1396-
&[],
1397-
)?;
1417+
let value = self.vm().msg_value();
1418+
let context = Call::new_payable(self, value).gas(gas_amount);
1419+
call(self.vm(), context, to, &[])?;
13981420
Ok(())
13991421
}
14001422

@@ -1405,16 +1427,19 @@ impl SendEther {
14051427
to: Address,
14061428
data: Bytes,
14071429
) -> Result<(), Vec<u8>> {
1408-
call(Call::new_in(self).value(self.vm().msg_value()), to, data.as_slice())?;
1430+
let value = self.vm().msg_value();
1431+
let context = Call::new_payable(self, value);
1432+
call(self.vm(), context, to, &data)?;
14091433
Ok(())
14101434
}
14111435

14121436
// To payable contract method
14131437
#[payable]
14141438
pub fn send_to_contract(&mut self, to: Address) -> Result<(), Vec<u8>> {
14151439
let target = ITarget::new(to);
1416-
let config = Call::new_in(self).value(self.vm().msg_value());
1417-
target.receive_ether(config)?;
1440+
let value = self.vm().msg_value();
1441+
let context = Call::new_payable(self, value);
1442+
target.receive_ether(self.vm(), context)?;
14181443
Ok(())
14191444
}
14201445
}
@@ -1424,14 +1449,16 @@ impl SendEther {
14241449

14251450
The factory pattern allows a contract to deploy other contracts programmatically. This is useful for creating contract instances on demand, such as deploying new token contracts or creating user-specific vaults.
14261451

1427-
:::note
1452+
<VanillaAdmonition type="note">
1453+
14281454
Advanced deployment patterns documentation is in development. This section will cover:
14291455

14301456
- Deploying contracts from within a contract
14311457
- Passing constructor arguments
14321458
- Deterministic deployment with CREATE2
14331459
- Handling deployment failures
1434-
:::
1460+
1461+
</VanillaAdmonition>
14351462

14361463
**Constructor considerations for factory-deployed contracts:**
14371464

0 commit comments

Comments
 (0)