Skip to main content

Function Modifiers

SolidCash functions support a rich set of modifiers that control visibility, state access, and lifecycle behavior.

Custom Modifiers

Custom modifiers encapsulate reusable validation logic. The _ (underscore) placeholder marks where the function body is inserted.

contract Token(pubkey owner) {
modifier onlyOwner(sig s) {
require(checkSig(s, owner));
_;
}

modifier minAmount(int amount, int minimum) {
require(amount >= minimum);
_;
}

function transfer(bytes20 to, int amount, sig s)
onlyOwner(s) minAmount(amount, 1)
{
// This body replaces _ in each modifier
// Execution order: onlyOwner check → minAmount check → body
}
}

Multiple Placeholders

Unlike Solidity (which supports only one _), SolidCash allows multiple placeholders in a single modifier:

modifier logged(string label) {
console.log("Before:", label);
_;
console.log("After:", label);
_; // body can execute multiple times
}

Modifier Parameters

Modifiers accept parameters just like functions. Use different parameter names than the function to avoid VariableRedefinitionError:

modifier charged(int fee) {
require(msg.value >= fee);
_;
}

function doSomething(int amount, sig s) charged(100) onlyOwner(s) {
// ...
}

Visibility Modifiers

ModifierScope
externalOnly callable from outside via function selector
publicCallable from anywhere; generates selector
internalCallable within contract and inheriting contracts
privateCallable only within the declaring contract
contract Example() {
function publicFunc() public { } // anyone can call
function externalFunc() external { } // only from outside
function helper() internal { } // this + children
function secret() private { } // this only
}
note

External functions use a 4-byte selector (SHA-256-based) for dispatch. Internal and private functions are inlined at the call site.

State Access Modifiers

ModifierMeaning
pureNo state access; deterministic output
viewCan read state, cannot modify it
payableFunction can receive BCH
function compute(int a, int b) pure returns (int) {
return a + b; // no state access
}

function getCounter() view returns (int) {
return this.counter; // read-only
}

receive() external payable {
// accept BCH
}

Inheritance Modifiers

ModifierMeaning
virtualFunction can be overridden by child contracts
overrideOverrides a parent contract's function
contract Base() {
function foo() virtual { require(true); }
}

contract Child() is Base {
function foo() override { require(1 == 1); }
}

Lifecycle Modifiers

These modifiers control the lifecycle of stateful contracts. They require the contract to have state variables.

deploy

Creates a new child stateful contract from a factory:

contract Factory(pubkey admin) {
function createChild(sig s) deploy {
require(checkSig(s, admin));
}
}

upgrade

Deploys a new version of the contract code while preserving the category ID and state:

contract TokenV1(pubkey admin) {
int counter;

function migrate(sig s) upgrade {
require(checkSig(s, admin));
}
}

State layout can be extended in the new version (append new fields). Existing state values are preserved.

selfdestruct

Permanently destroys the contract by burning all NFTs and returning satoshis:

contract Killable(pubkey admin) {
int counter;

function destroy(sig s) selfdestruct {
require(checkSig(s, admin));
}
}
caution

Selfdestruct is irreversible. All state, entries, and token containers are permanently lost.

split

Creates N parallel contract instances (threads) from a single master:

contract Parallel(pubkey admin) {
int counter;

function fork(int n, sig s) split(n) {
require(n >= 1);
require(checkSig(s, admin));
}
}

The split modifier takes a parameter name that specifies the thread count.

merge

Consolidates N threads back into a single master:

function consolidate(int n, sig s) merge(n) {
require(n >= 1);
require(checkSig(s, admin));
}

delegate

Executes the function in the calling proxy's state context (delegatecall pattern):

contract Implementation() {
int counter;

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

unsafe

Allows unrestricted transaction introspection. Three tiers are available:

function authorize(pubkey pk, sig s) unsafe {
// unsafe(0): full access to all inputs/outputs
require(checkSig(s, pk));
}

function limitedAccess() unsafe(1) {
// Tier 1: cannot read contract state variables
}

function readOnlyNft() unsafe(2) {
// Tier 2: cannot write NFTs
}

Modifier Compatibility

Not all modifiers can be combined. The compiler enforces these rules:

CombinationAllowed?
pure + viewNo
internal + externalNo
split + mergeNo
delegate + unsafeNo
delegate + upgradeNo
delegate + selfdestructNo
delegate + deployNo
split + selfdestructNo
split + upgradeNo
upgrade + selfdestructNo

All lifecycle modifiers (deploy, upgrade, selfdestruct, split, merge, delegate) require the contract to have state variables.

Fallback & Receive

Two special functions handle unmatched calls:

contract Flexible(bytes20 admin) {
// Handles calls with unknown function selectors
fallback(bytes input) external {
// `input` contains the raw calldata
}

// Handles BCH transfers with no data
receive() external payable {
// accept value
}
}
  • fallback: at most one per contract; receives raw calldata as bytes
  • receive: at most one per contract; must be external payable; no parameters
  • Both support virtual/override for inheritance