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
casevalues withbreakto exit - Optional
defaultbranch - 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.
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
fallbackand onereceiveper contract - Both support
virtual/overridefor inheritance
Inline Assembly
Direct Bitcoin Script opcodes for low-level control:
assembly {
OP_DUP OP_ADD 0x01
}
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
| Unit | Value |
|---|---|
satoshis / sats | 1 |
finney | 10 |
bits | 100 |
bitcoin | 100,000,000 |
seconds | 1 |
minutes | 60 |
hours | 3,600 |
days | 86,400 |
weeks | 604,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
| Global | Type | Description |
|---|---|---|
msg.sender | pubkey | Public key of the signing input |
msg.value | int | Satoshis in the active input |
msg.sig | sig | Signature of the active input |
msg.data | bytes | Unlocking bytecode |
tx.origin | pubkey | First input's public key |