Skip to main content

Advanced Features

This page covers advanced SolidCash language features including control flow, ABI encoding, external contract interaction, delegatecall, and inline assembly.

Switch Statement

switch (action) {
case 0:
this.counter = this.counter + 1;
break;
case 1:
this.counter = this.counter - 1;
break;
default:
revert("Unknown action");
}
  • Multiple case values with break to exit
  • Optional default branch
  • Works in stateful contracts (state tracking across branches)

For Loops

SolidCash supports C-style for loops with no compiler-imposed iteration limits:

for (int i = 0; i < n; i = i + 1) {
total = total + values[i];
}

break and continue are supported:

for (int i = 0; i < n; i = i + 1) {
if (values[i] == 0) continue; // skip zeros
if (total > limit) break; // stop early
total = total + values[i];
}

While & Do-While

while (remaining > 0) {
remaining = remaining - 1;
}

do {
attempts = attempts + 1;
} while (!success && attempts < maxAttempts);

Collection Iteration

See Stateful Contracts — Mapping Iteration for the three iteration modes (for-of, for-in, for-of .entries).

Unchecked Arithmetic

By default, bounded integer arithmetic checks for overflow/underflow. The unchecked block disables these checks for wrapping behavior:

unchecked {
uint8 a = uint8(255) + uint8(1); // wraps to 0
uint8 b = uint8(0) - uint8(1); // wraps to 255
uint16 c = uint16(65535) + uint16(1); // wraps to 0
}

Only applies within the block. Outside unchecked, overflow/underflow causes a script failure.

ABI Encoding & Decoding

Basic Encoding

bytes data = abi.encode(42, true, hex"abcd");
bytes packed = abi.encodePacked(sender, amount); // no padding

Decoding

(int x, bool y) = abi.decode(data, (int, bool));
int value = abi.decodeAt(data, (int), 4); // decode at byte offset 4

Advanced Encoding

// Encode with a specific 4-byte selector prefix
bytes call = abi.encodeWithSelector(hex"12345678", arg1, arg2);

// Encode with selector derived from function signature string
bytes call = abi.encodeWithSignature("transfer(address,uint)", to, amount);

// Encode with selector from an interface function (type-safe)
bytes call = abi.encodeCall(IToken.transfer, (recipient, amount));

External Contract Interaction

External State Reads

Read another contract's public state variables using an interface and category ID:

interface ICounter {
int public counter;
mapping(bytes20 => int) public balances;
}

contract Reader(bytes32 counterCategory) {
int lastSeen;

function sync() {
// Read current state of external contract
this.lastSeen = ICounter(counterCategory).counter;
}

function readBalance(bytes20 account) returns (int) {
return ICounter(counterCategory).balances[account];
}
}

Output State Reads

Read the state that an external contract will have after its function executes in the same transaction:

function readAfterCall() {
int newVal = ICounter(counterCategory).outState.counter;
this.lastSeen = newVal;
}

External Function Calls

interface ITarget {
function increment(pubkey pk, sig s);
}

contract Caller(bytes32 targetCategory) {
function callTarget() {
ITarget(targetCategory).increment.call();
}

function readTarget() {
ITarget(targetCategory).increment.staticcall(); // read-only
}
}
  • .call() — stateful call (target can modify its own state)
  • .staticcall() — read-only (target state changes are not committed)

Delegatecall & Proxy Pattern

The delegatecall pattern separates state (proxy) from logic (implementation):

// Implementation — holds logic, state access redirected to proxy
contract Implementation() {
int counter;

function increment() delegate {
this.counter = this.counter + 1;
// `this` refers to the PROXY's state, not implementation's
}
}

// Proxy — holds state, forwards unknown calls to implementation
contract Proxy() {
int counter;

fallback(bytes input) external {
address(implAddress).delegatecall(input);
}
}

The delegate modifier causes this. state access to target the calling proxy's NFT commitments instead of the implementation's.

caution

The delegate modifier cannot be combined with unsafe, upgrade, selfdestruct, deploy, split, or merge.

Fallback & Receive Functions

contract Flexible() {
// Catches calls with unknown/no function selectors
fallback(bytes input) external {
// input contains raw calldata
}

// Catches pure BCH value transfers
receive() external payable {
// no parameters
}
}
  • At most one fallback and one receive per contract
  • Both support virtual/override for inheritance

Inline Assembly

Direct Bitcoin Script opcodes for low-level control:

assembly {
OP_DUP OP_ADD 0x01
}
caution

Inline assembly bypasses all compiler safety checks (type checking, stack verification). Use only when you fully understand the stack effects.

Exponentiation

int result = base ** exponent;
  • Right-associative: 2 ** 3 ** 2 = 2 ** 9 = 512
  • Exponent must be unsigned (non-negative)

Date Literals

int deadline = date("2025-12-31");
int precise = date("2025-06-15T14:30:00");
require(tx.locktime >= deadline);

Compiles to a Unix timestamp integer at compile time.

Number Units

Compile-time multipliers for readability:

int fee = 1000 satoshis;          // 1000
int amount = 1 bitcoin; // 100_000_000
int timeout = 7 days; // 604_800
int delay = 2 hours; // 7_200
UnitValue
satoshis / sats1
finney10
bits100
bitcoin100,000,000
seconds1
minutes60
hours3,600
days86,400
weeks604,800

String Features

string greeting = "Hello, World!";
string combined = string.concat("Hello", " ", "World");
int len = greeting.length;
string sub = greeting.slice(0, 5); // "Hello"
string reversed = greeting.reverse();
(string a, string b) = greeting.split(5); // ("Hello", ", World!")

Try-Catch

try externalContract.someFunc() returns (int result) {
process(result);
} catch Error(string memory reason) {
handleError(reason);
} catch {
handleUnknown();
}

msg and block Globals

GlobalTypeDescription
msg.senderpubkeyPublic key of the signing input
msg.valueintSatoshis in the active input
msg.sigsigSignature of the active input
msg.databytesUnlocking bytecode
tx.originpubkeyFirst input's public key