State Mutability
@Pure
The @Pure decorator indicates that a method doesn't read or modify contract state. Pure functions only use their parameters and constants.
import { Contract, Pure, U256, U256Factory } from "@wakeuplabs/as-stylus";
@Contract
export class MathLibrary {
@Pure
add(a: U256, b: U256): U256 {
return a.add(b); // Only uses parameters
}
@Pure
factorial(n: U256): U256 {
let result = U256Factory.fromString("1");
let i = U256Factory.fromString("1");
while (i.lessThanOrEqual(n)) {
result = result.mul(i);
i = i.add(U256Factory.fromString("1"));
}
return result;
}
@Pure
max(a: U256, b: U256): U256 {
return a.greaterThan(b) ? a : b;
}
}
@Payable
The @Payable decorator allows a method to receive Ether along with the function call.
import {
Contract,
U256,
Mapping,
Address,
U256Factory,
Payable,
Pure,
External,
} from "@wakeuplabs/as-stylus";
// Define events
@Contract
export class PayableContract {
balance: U256;
deposits: Mapping<Address, U256>;
@Payable
@External
deposit(): void {
const sender = msg.sender();
const value = msg.value();
this.balance = this.balance.add(value);
this.deposits.set(sender, this.deposits.get(sender).add(value));
DepositReceived.emit(sender, value);
}
@Payable
@External
buyTokens(amount: U256): void {
const payment = msg.value();
const required = amount.mul(U256Factory.fromString("1000")); // 1000 wei per token
if (payment.lessThan(required)) {
InsufficientPayment.revert(payment, required);
}
// Issue tokens logic...
TokensPurchased.emit(msg.sender(), amount, payment);
}
}
@Nonpayable (Default)
Methods without payment decorators are nonpayable by default and will revert if sent Ether:
import { Contract, Payable, External, Nonpayable } from "@wakeuplabs/as-stylus";
@Contract
export class NonpayableExample {
@External // Implicitly @Nonpayable
normalFunction(): void {
// This will revert if called with Ether
}
@Nonpayable // Explicit declaration
@External
explicitNonpayable(): void {
// Also rejects Ether payments
}
}
Decorator Combinations
Valid Combinations
import { Contract, Public, View, External, Pure, Payable, U256 } from "@wakeuplabs/as-stylus";
@Contract
export class CombinationExamples {
// External + View (read-only external access)
@External
@View
getPublicData(): U256 {
return SomeContract.publicValue;
}
// External + Pure (stateless external function)
@External
@Pure
calculateHash(data: Str): U256 {
return U256.hash(data);
}
// External + Payable (accepts payments)
@External
@Payable
acceptPayment(): void {
// Handle payment
}
// Public + View (internal + external read-only)
@Public
@View
internalOrExternalRead(): U256 {
return SomeContract.value;
}
}
Invalid Combinations
// ❌ Cannot combine View with Payable
@View
@Payable
@External
invalidCombination(): void { }
// ❌ Cannot combine Pure with Payable
@Pure
@Payable
@External
anotherInvalidCombination(): U256 { }
// ❌ Cannot have multiple visibility decorators
@External
@Public
multipleVisibility(): void { }
Decorator Hierarchy
State Mutability Order (from most to least restrictive):
- @Pure - No state access, only parameters and constants
- @View - Read-only state access
- @Nonpayable - Can modify state, no Ether accepted
- @Payable - Can modify state and accept Ether
import {
Contract,
Pure,
View,
External,
Payable,
Public,
U256,
U256Factory,
} from "@wakeuplabs/as-stylus";
@Contract
export class StateHierarchy {
value: U256;
@Pure
pureFunction(x: U256): U256 {
return x.mul(U256Factory.fromString("2")); // ✅ Only uses parameters
// return this.value; // ❌ Cannot read state
}
@View
viewFunction(): U256 {
return this.value; // ✅ Can read state
// this.value = U256Factory.create(); // ❌ Cannot modify state
}
@External
nonpayableFunction(): void {
this.value = U256Factory.fromString("100"); // ✅ Can modify state
// const payment = msg.value(); // ❌ Will be zero
}
@Payable
@External
payableFunction(): void {
this.value = U256Factory.fromString("200"); // ✅ Can modify state
const payment = msg.value(); // ✅ Can receive Ether
}
}
Advanced Usage Patterns
Access Control with Visibility
import {
Contract,
Pure,
View,
External,
Payable,
Public,
U256,
Address,
Mapping,
U256Factory,
} from "@wakeuplabs/as-stylus";
// Define errors
@Contract
export class AccessControlledContract {
owner: Address;
admins: Mapping<Address, boolean>;
// Internal helper for access control
requireOwner(): void {
if (!msg.sender().equals(this.owner)) {
NotOwner.revert(msg.sender(), this.owner);
}
}
// Internal helper for admin access
requireAdmin(): void {
if (!this.admins.get(msg.sender()).toValue()) {
NotAdmin.revert(msg.sender());
}
}
@External
ownerOnlyFunction(): void {
this.requireOwner();
// Owner-only logic
}
@Public
adminFunction(): void {
this.requireAdmin();
// Admin function that can also be called internally
}
@External
publicFunction(): void {
// Call admin function internally
this.adminFunction();
}
}