Inheritance
The AssemblyScript Stylus SDK supports single inheritance, allowing contracts to extend other contracts using standard TypeScript/AssemblyScript syntax. This enables code reuse, modular design, and familiar object-oriented programming patterns.
Overview
Inheritance in the SDK:
- Code Reuse: Child contracts inherit methods, storage, events, structs, and errors from parent contracts
- Method Overriding: Child contracts can override parent methods with the same signature
- Constructor Chaining: Parent constructors are called before child constructors
- Storage Layout Compatibility: Maintains Solidity-compatible storage layout (parent storage first, then child storage)
Syntax
// Parent contract
@Contract
export class ParentContract {
// Parent implementation
}
// Child contract extends parent
@Contract
export class ChildContract extends ParentContract {
// Child implementation
}
Basic Example
Parent Contract
import { Contract, External, U256, U256Factory } from "@wakeuplabs/as-stylus";
@Contract
export class Parent {
sum: U256;
constructor(init: U256) {
this.sum = init;
}
@External
setValue(a: U256, b: U256): void {
this.sum = a.add(b);
}
@External
getValue(): U256 {
return this.sum;
}
}
Child Contract
import { Contract, External, U256 } from "@wakeuplabs/as-stylus";
import { Parent } from "./parent.js";
@Contract
export class Child extends Parent {
constructor(init: U256) {
super(init); // Call parent constructor
}
@External
getSum(): U256 {
return this.sum; // Access parent storage
}
}
How Inheritance Works
1. Method Inheritance
Child contracts automatically inherit all @External, @Public, and @Internal methods from the parent.
@Contract
export class Parent {
@External
parentMethod(): U256 {
return U256Factory.fromString("100");
}
}
@Contract
export class Child extends Parent {
// Inherits parentMethod() automatically
// Can be called on instances of Child
}
2. Storage Inheritance
Parent storage variables occupy the first storage slots, followed by child storage variables. This maintains Solidity-compatible storage layout.
@Contract
export class Parent {
value1: U256; // Slot 0
value2: U256; // Slot 1
}
@Contract
export class Child extends Parent {
value3: U256; // Slot 2 (after parent storage)
value4: U256; // Slot 3
}
3. Constructor Chaining
Parent constructors run first, then child constructors execute. Always call super() in the child constructor to initialize the parent.
@Contract
export class Parent {
value: U256;
constructor(initialValue: U256) {
this.value = initialValue;
}
}
@Contract
export class Child extends Parent {
multiplier: U256;
constructor(initialValue: U256, mult: U256) {
super(initialValue); // Initialize parent first
this.multiplier = mult; // Then initialize child
}
}
Method Overriding
Child contracts can override parent methods by declaring a method with the exact same signature.
@Contract
export class Parent {
@External
calculate(): U256 {
return U256Factory.fromString("50");
}
}
@Contract
export class Child extends Parent {
@External
calculate(): U256 {
// Override parent method
return U256Factory.fromString("100");
}
}
When a method is overridden, the child's implementation is used in the final contract.
Calling Parent Methods
Internal Methods
Child contracts can call @Internal methods from the parent directly:
@Contract
export class Parent {
@Internal
add(a: U256, b: U256): U256 {
return a.add(b);
}
}
@Contract
export class Child extends Parent {
@External
calculate(x: U256, y: U256): U256 {
// Call parent's internal method
return this.add(x, y);
}
}
Rules and Constraints
Single Inheritance Only
Currently, only single inheritance is supported. A contract can extend only one parent contract.
// ✅ Valid - Single inheritance
@Contract
export class Child extends Parent {
// Implementation
}
// ❌ Invalid - Multiple inheritance not supported
@Contract
export class Child extends Parent1, Parent2 { // Error
// Implementation
}
Both Parent and Child Must Use @Contract
Both the parent and child classes must be decorated with @Contract.
// ✅ Valid
@Contract
export class Parent {}
@Contract
export class Child extends Parent {}
// ❌ Invalid - Parent missing @Contract
export class Parent {}
@Contract
export class Child extends Parent {} // Error
Import Requirements
When importing parent contracts, use the .ts extension in the import path:
import { Parent } from "./parent.ts"; // ✅ Correct
Method Signature Matching
When overriding methods, the signature must match exactly (same parameter types and return type):
@Contract
export class Parent {
@External
calculate(value: U256): U256 {
return value;
}
}
// ✅ Valid - Exact signature match
@Contract
export class Child1 extends Parent {
@External
calculate(value: U256): U256 {
return value.add(value);
}
}
// ❌ Invalid - Different signature
@Contract
export class Child2 extends Parent {
@External
calculate(value: U256, extra: U256): U256 {
// Error: signature mismatch
return value.add(extra);
}
}
Inherited Components
When a contract extends a parent, it inherits:
- Methods: All
@External,@Public, and@Internalmethods - Storage: All storage variables (placed before child storage)
- Events: All
@Eventdecorated events - Structs: All custom struct definitions
- Errors: All
@Errordecorated custom errors
Best Practices
1. Use Internal Methods for Shared Logic
Extract common logic into @Internal methods that both parent and child can use:
@Contract
export class Parent {
@Internal
validateAmount(amount: U256): bool {
const zero = U256Factory.create();
return amount.gt(zero);
}
@External
deposit(amount: U256): void {
if (!this.validateAmount(amount)) {
// Handle error
}
// Process deposit
}
}
2. Initialize Parents in Constructors
Always call super() to properly initialize parent state:
@Contract
export class Child extends Parent {
constructor(value: U256) {
super(value); // Required for parent initialization
// Child initialization follows
}
}
3. Document Override Behavior
When overriding methods, document the differences in behavior:
@Contract
export class Child extends Parent {
/**
* Overrides parent calculate() method.
* Returns double the value instead of the original calculation.
*/
@External
calculate(value: U256): U256 {
return value.add(value);
}
}
4. Organize Contracts Logically
Structure parent contracts to be reusable across multiple children:
// Base contract with common functionality
@Contract
export class BaseToken {
// Common token logic
}
// Specialized implementations
@Contract
export class RewardToken extends BaseToken {
// Reward-specific logic
}
@Contract
export class GovernanceToken extends BaseToken {
// Governance-specific logic
}