blockchainkit.contracts#

Contracts: designing, breaking and verifying smart contracts.

A world-state model of accounts and contracts calling each other, the contracts and standards that shaped the field, the bugs behind its largest losses, and the methods that check contracts before they hold money.

A world-state model of accounts and contracts calling each other, the contracts and standards that shaped the field, the bugs behind its largest losses, and the tools that check contracts: Hoare triples, symbolic execution, design by contract, and fuzzing.

Every public name below is re-exported by the subpackage: import it as bk.contracts.<name>. The plotting helpers are the exception: import them explicitly from blockchainkit.contracts.visualizers, which loads Matplotlib.

Types and results#

Errors, expression type, and result containers for blockchainkit.contracts.

blockchainkit.contracts.core.base.Expr = int | str | tuple[typing.Any, ...]#

An integer constant, a variable name, or (operator, operand, ...) with operators such as "+", "<", "and" and "not": a symbolic expression.

exception blockchainkit.contracts.core.base.ContractError[source]#

Bases: ValueError

A revert: the call fails, and every state change it made is undone.

class blockchainkit.contracts.core.base.CallRecord(depth, kind, sender, to, context, function, value, gas_used, error)[source]#

Bases: object

One call in a transaction’s call tree, in the order the calls began.

Variables:
  • depth (int) – Nesting depth; the transaction’s own call is 0.

  • kind (str) – "call", "delegatecall" or "create".

  • sender (str) – msg.sender of the call.

  • to (str) – The address whose code ran.

  • context (str) – The address whose storage and balance the code used: to, except for a delegatecall, where it is the caller.

  • function (str or None) – The function called; None for a plain payment.

  • value (int) – Ether sent with the call.

  • gas_used (int) – Gas the call consumed, including its own nested calls.

  • error (str or None) – Why the call reverted, if it did.

Parameters:
depth: int#
kind: str#
sender: str#
to: str#
context: str#
function: str | None#
value: int#
gas_used: int#
error: str | None#
property ok: bool#

True if the call returned without reverting.

class blockchainkit.contracts.core.base.Event(emitter, name, fields)[source]#

Bases: object

A log entry emitted by a contract.

Variables:
  • emitter (str) – The address in whose context the code emitted it.

  • name (str) – The event name, such as "Transfer".

  • fields (collections.abc.Mapping) – Its named values.

Parameters:
emitter: str#
name: str#
fields: Mapping[str, Any]#
class blockchainkit.contracts.core.base.Receipt(success, result, error, gas_used, calls, events)[source]#

Bases: object

What a transaction did.

Variables:
  • success (bool) – False if the top-level call reverted; its state changes were then undone.

  • result (Any) – The called function’s return value (None on failure).

  • error (str or None) – The revert reason, on failure.

  • gas_used (int) – Gas consumed, including the fixed transaction cost.

  • calls (tuple of blockchainkit.contracts.core.base.CallRecord) – Every call made, in the order they began, reverted ones included.

  • events (tuple of blockchainkit.contracts.core.base.Event) – Events emitted by calls that did not revert; empty on failure.

Parameters:
success: bool#
result: Any#
error: str | None#
gas_used: int#
calls: tuple[CallRecord, ...]#
events: tuple[Event, ...]#
class blockchainkit.contracts.core.base.TripleResult(holds, checked, counterexample)[source]#

Bases: object

Outcome of checking a Hoare triple {P} S {Q} on every state of a finite domain.

Variables:
  • holds (bool) – True if every state satisfying P that runs S to completion satisfies Q.

  • checked (int) – States of the domain that satisfy P.

  • counterexample (collections.abc.Mapping or None) – A starting state for which the triple fails.

Parameters:
holds: bool#
checked: int#
counterexample: Mapping[str, int] | None#
class blockchainkit.contracts.core.base.SymbolicPath(conditions, outcome, state, decisions)[source]#

Bases: object

One execution path found by symbolic execution.

Variables:
  • conditions (tuple of Expr) – The path condition: every branch decision taken, as expressions over the symbolic inputs. Inputs satisfying all of them follow this path.

  • outcome (str) – How the path ends: "ok", "reverted" or "assertion" for programs, "stop", "revert", "error" or "bound" for bytecode.

  • state (collections.abc.Mapping) – The variables the path assigned (for bytecode, the storage slots it read or wrote, as slot<k>), as expressions over the inputs.

  • decisions (tuple of bool) – The branch taken at each fork, in order, for drawing the tree.

Parameters:
conditions: tuple[int | str | tuple[Any, ...], ...]#
outcome: str#
state: Mapping[str, int | str | tuple[Any, ...]]#
decisions: tuple[bool, ...]#
class blockchainkit.contracts.core.base.SymbolicResult(paths, variables, complete)[source]#

Bases: object

Every path through a program, up to the exploration bound.

Variables:
Parameters:
paths: tuple[SymbolicPath, ...]#
variables: tuple[str, ...]#
complete: bool#
property outcomes: dict[str, int]#

Number of paths ending in each outcome.

class blockchainkit.contracts.core.base.FuzzCall(sender, function, args)[source]#

Bases: object

One transaction of a fuzzing campaign.

Variables:
  • sender (str) – Who sends it.

  • function (str) – The target contract’s function.

  • args (tuple) – Its arguments.

Parameters:
sender: str#
function: str#
args: tuple[Any, ...]#
class blockchainkit.contracts.core.base.FuzzResult(failed, sequence, found_after, runs, transactions, original_length)[source]#

Bases: object

Outcome of a property-based fuzzing campaign.

Variables:
  • failed (bool) – True if some sequence of transactions broke the property.

  • sequence (tuple of blockchainkit.contracts.core.base.FuzzCall) – The shrunk failing sequence; empty if none was found.

  • found_after (int or None) – Transactions executed, over all runs, until the first failure.

  • runs (int) – Random sequences executed.

  • transactions (int) – Transactions executed in total, shrinking excluded.

  • original_length (int) – Length of the failing sequence before shrinking (0 if none).

Parameters:
failed: bool#
sequence: tuple[FuzzCall, ...]#
found_after: int | None#
runs: int#
transactions: int#
original_length: int#
class blockchainkit.contracts.core.base.RicardianContract(text, parameters, issuer, signature, digest)[source]#

Bases: object

A Ricardian contract: prose and parameters, signed, and named by their hash.

Variables:
Parameters:
text: str#
parameters: Mapping[str, int | str]#
issuer: tuple[int, int]#
signature: Any#
digest: bytes#
property identifier: str#

The digest in hex, as payments cite it.

property rendered: str#

The prose with every parameter filled in, as a person reads it.

The world-state model#

A world-state model: accounts, contracts, and the calls between them.

Contracts are short Python classes deriving from Contract. Their public methods are the contract’s functions, and they reach the world only through the methods of Contract: storage per contract, msg.sender and msg.value, call and delegatecall, send with its gas stipend, create, events and selfdestruct. A World runs transactions against them, metering gas and undoing the whole call tree, or only the failing sub-call, on a revert.

Storage follows Solidity’s layout in spirit: field i of a contract’s layout lives in slot i, and entry k of a mapping field in slot (i, k). A delegatecall runs the callee’s code on the caller’s storage, through the callee’s layout, which is exactly how proxy storage collisions arise.

This is a model of the EVM’s call semantics, not the EVM: values are Python objects rather than 256-bit words, arithmetic is unbounded (as with Solidity 0.8’s checked arithmetic, but without overflow), functions are dispatched by name rather than by selector, gas costs are a short teaching schedule, and selfdestruct takes effect immediately rather than at the end of the transaction.

blockchainkit.contracts.systems.world.GAS_COSTS: Mapping[str, int] = mappingproxy({'transaction': 21000, 'read': 800, 'write': 5000, 'call': 700, 'create': 32000, 'log': 375, 'selfdestruct': 5000})#

The teaching gas schedule, rounded from Ethereum’s. A storage write costs 5,000 here; Ethereum charges 20,000 to fill an empty slot and 2,900 to change one.

blockchainkit.contracts.systems.world.CALL_STIPEND = 2300#

enough to log an event, not enough to write storage.

Type:

Gas given to the recipient of Contract.send()

blockchainkit.contracts.systems.world.MAX_CALL_DEPTH = 128#

Default call-depth limit. Ethereum’s is 1024; a lower default keeps nested Python calls within the interpreter’s recursion limit.

blockchainkit.contracts.systems.world.BLOCK_GAS_LIMIT = 30000000#

Default gas limit of a block, and so of one transaction.

blockchainkit.contracts.systems.world.code_hash(code, *args)[source]#

Hash identifying a contract’s creation code: its class and constructor arguments.

Stands in for the hash of EVM init code, which also includes the constructor’s arguments.

Parameters:
Return type:

bytes

blockchainkit.contracts.systems.world.create_address(deployer, nonce)[source]#

Address of the contract that deployer creates with its nonce-th creation (CREATE).

Examples

>>> from blockchainkit.contracts import create_address
>>> create_address("alice", 0) == create_address("alice", 0) != create_address("alice", 1)
True
Parameters:
Return type:

str

blockchainkit.contracts.systems.world.create2_address(deployer, salt, code, *args)[source]#

Address of the contract that deployer creates with salt (CREATE2, EIP-1014).

It depends only on the deployer, the salt and the creation code, so it is known before anything is deployed.

Parameters:
Return type:

str

class blockchainkit.contracts.systems.world.Contract[source]#

Bases: object

Base class of every contract: subclass it, declare layout, and write methods.

Public methods (names not starting with _) are the contract’s functions. Optional hooks: constructor(*args) runs once at deployment; receive() runs on a plain payment; fallback(function, *args) runs when no function matches; invariant() is checked after every call and a False result reverts it (Meyer’s class invariant).

A contract never holds state in Python attributes: everything it keeps goes through read() and write(), so that the world can meter, roll back and share it.

Variables:

layout (tuple of str) – Storage field names; field i lives in slot i.

layout: ClassVar[tuple[str, ...]] = ()#
property address: str#

The address whose storage and balance this code is using (address(this)).

property sender: str#

The immediate caller (msg.sender).

property value: int#

Ether sent with this call (msg.value).

property origin: str#

The account that signed the transaction (tx.origin).

property block_number: int#

Number of the block being built.

property timestamp: int#

Timestamp of the block being built.

property gas_left: int#

Gas remaining in this call.

blockhash(number)[source]#

Hash of one of the 256 previous blocks as an integer, 0 for any other block.

Parameters:

number (int)

Return type:

int

read(name, *keys)[source]#

Read field name, or its entry at keys for a mapping; 0 if never written.

Parameters:
Return type:

Any

write(name, *keys_and_value)[source]#

Write field name: write("owner", x), or write("balances", key, x).

Parameters:
  • name (str)

  • keys_and_value (Any)

Return type:

None

read_slot(slot)[source]#

Read a raw storage slot, ignoring the layout.

Parameters:

slot (int | tuple[Any, ...])

Return type:

Any

write_slot(slot, value)[source]#

Write a raw storage slot, ignoring the layout; writing 0 clears it.

Parameters:
Return type:

None

entries(name)[source]#

Every nonzero entry of mapping field name, keyed as written.

The EVM cannot enumerate a mapping; this is for invariants and tests.

Parameters:

name (str)

Return type:

dict[Any, Any]

ether_balance(account=None)[source]#

Ether held by account, by default this contract.

Parameters:

account (str | None)

Return type:

int

code_at(account)[source]#

The code deployed at account, None for an account without code.

Parameters:

account (str)

Return type:

type[Contract] | None

call(to, function, *args, value=0, gas=None)[source]#

Call function on to, forwarding at most 63/64 of the remaining gas.

function=None makes a plain payment, handled by receive. A revert in the callee undoes the callee’s changes and propagates, as a Solidity high-level call does.

Parameters:
Return type:

Any

delegatecall(to, function, *args)[source]#

Run to’s code on this contract’s storage, keeping msg.sender and msg.value.

If to has no code, nothing runs and None is returned, as in the EVM.

Parameters:
Return type:

Any

send(to, amount)[source]#

Pay amount to to with a gas stipend of 2,300; return False instead of reverting.

Solidity’s send and transfer: the stipend is too small for a recipient whose receive writes storage.

Parameters:
Return type:

bool

create(code, *args, value=0, salt=None)[source]#

Deploy code from here (CREATE, or CREATE2 with salt) and return its address.

Parameters:
Return type:

str

selfdestruct(beneficiary)[source]#

Send all of this account’s ether to beneficiary and delete its code and storage.

Parameters:

beneficiary (str)

Return type:

None

emit(name, **fields)[source]#

Log an event; it survives only if no enclosing call reverts.

Parameters:
Return type:

None

require(condition, message='requirement failed')[source]#

Revert with message unless condition is true.

Parameters:
Return type:

None

use_gas(amount)[source]#

Consume gas, reverting with "out of gas" if too little is left.

Parameters:

amount (int)

Return type:

None

invariant()[source]#

Override to state what must hold after every call; checked without gas.

Return type:

bool

class blockchainkit.contracts.systems.world.World(*, block_gas_limit=30000000, max_call_depth=128, seed=0)[source]#

Bases: object

Accounts, contract code and storage, and the block the next transaction lands in.

Parameters:
  • block_gas_limit (int) – The most gas any transaction may use.

  • max_call_depth (int) – Calls nested deeper than this revert.

  • seed (int) – Seeds the block hashes.

Examples

>>> from blockchainkit.contracts import ERC20, World
>>> world = World()
>>> token = world.deploy("alice", ERC20, 1_000)
>>> world.transact("alice", token, "transfer", "bob", 250).success
True
>>> world.view(token, "balance_of", "bob")
250
fund(account, amount)[source]#

Credit amount of new ether to account, like a genesis allocation.

Parameters:
Return type:

None

balance(account)[source]#

Ether held by account.

Parameters:

account (str)

Return type:

int

code(account)[source]#

The contract class deployed at account, or None.

Parameters:

account (str)

Return type:

type[Contract] | None

storage(account)[source]#

A read-only copy of account’s storage, slot to value.

Parameters:

account (str)

Return type:

Mapping[Any, Any]

read(account, name, *keys, layout=None)[source]#

Read a storage field of account without gas, through its code’s layout.

Pass layout to read through another contract’s layout, such as a proxy’s storage through its implementation’s.

Parameters:
Return type:

Any

nonce(account)[source]#

Transactions sent by an account, or contracts created by a contract.

Parameters:

account (str)

Return type:

int

name(account)[source]#

The label given at deployment, or the address itself.

Parameters:

account (str)

Return type:

str

blockhash(number)[source]#

Hash of block number as an integer, if it is one of the 256 before the current one.

Like the EVM’s BLOCKHASH, it returns 0 for the current block, for future blocks and for older ones.

Parameters:

number (int)

Return type:

int

fork()[source]#

An independent copy of this world, to try transactions on without changing this one.

Like a local fork of a live chain, it is how a wallet or a scanner can check what a transaction would do before anyone sends it.

Examples

>>> from blockchainkit.contracts import World
>>> world = World()
>>> world.fund("alice", 10)
>>> trial = world.fork()
>>> trial.transact("alice", "bob", value=10).success
True
>>> world.balance("bob"), trial.balance("bob")
(0, 10)
Return type:

World

advance(blocks=1, seconds=None)[source]#

Move to a later block; seconds defaults to 12 per block.

Parameters:
  • blocks (int)

  • seconds (int | None)

Return type:

None

transact(sender, to, function=None, *args, value=0, gas=None)[source]#

Send a transaction calling function on to; a revert is reported, not raised.

function=None makes a plain payment.

Parameters:
Return type:

Receipt

deploy(deployer, code, *args, value=0, salt=None, name=None, gas=None)[source]#

Deploy code in a transaction from deployer and return the new address.

Parameters:
  • deployer (str) – The sending account.

  • code (type) – A subclass of Contract.

  • *args – The constructor’s arguments.

  • value (int) – Ether sent to the new contract.

  • salt (int, optional) – Use CREATE2 with this salt instead of CREATE.

  • name (str, optional) – A label for traces and plots.

  • gas (int, optional) – Gas limit; defaults to the block gas limit.

Raises:

ContractError – The constructor reverted or the address is taken; nothing is deployed.

Return type:

str

view(to, function, *args)[source]#

Call function and return its result, then undo everything it did.

Raises:

ContractError – The call reverted.

Parameters:
Return type:

Any

Contracts#

Ricardian contracts: legal prose bound to code by a hash (Grigg, 1996).

Ian Grigg designed the Ricardian contract for issuing bonds and currencies on the Ricardo payment system. One document is at once a legal contract a person can read and a set of parameters a program can parse; the issuer signs it, and its hash names the instrument. Every payment cites that hash, so there is never a question which terms a payment was made under, and changing one word of the prose creates a different instrument.

Here a RicardianContract holds the prose, with {name} placeholders for its parameters, and the issuer’s Schnorr signature over its digest. RicardianToken is an ERC-20 token that stores the digest and accepts only payments that cite it.

blockchainkit.contracts.systems.ricardian.ricardian_digest(text, parameters)[source]#

SHA-256 of the domain tag and the canonical encoding of the prose and parameters.

Parameters:
Return type:

bytes

blockchainkit.contracts.systems.ricardian.ricardian_contract(text, parameters, issuer_key)[source]#

Write and sign a Ricardian contract.

Parameters:
  • text (str) – The terms, with a {name} placeholder for each parameter.

  • parameters (collections.abc.Mapping) – Values the program reads, such as the supply.

  • issuer_key (int) – The issuer’s private key; signing is deterministic (RFC 6979 style).

Return type:

RicardianContract

Examples

>>> from blockchainkit.contracts import ricardian_contract, verify_ricardian
>>> bond = ricardian_contract("Pays {coupon}% a year.", {"coupon": 5}, issuer_key=7)
>>> bond.rendered, verify_ricardian(bond)
('Pays 5% a year.', True)
blockchainkit.contracts.systems.ricardian.verify_ricardian(contract)[source]#

True if the digest matches the prose and parameters and the issuer signed it.

Parameters:

contract (RicardianContract)

Return type:

bool

class blockchainkit.contracts.systems.ricardian.RicardianToken[source]#

Bases: ERC20

An ERC-20 token issued under a Ricardian contract, whose payments must cite its hash.

layout: ClassVar[tuple[str, ...]] = ('total_supply', 'balances', 'allowances', 'terms')#
constructor(terms, supply)[source]#
Parameters:
Return type:

None

terms()[source]#

The identifier of the contract the token was issued under.

Return type:

str

transfer_under(terms, to, amount)[source]#

Transfer, citing the terms; a payment citing other terms reverts.

Parameters:
Return type:

bool

Capability-based security: authority by reference, not by identity (Miller, 1997-2006).

In an object-capability system, the only way to act on an object is to hold a reference to it, and the only ways to get a reference are to be given one, to create the object, or to have had it from the start. There is no ambient authority to look up. Mark Miller’s E language built distributed programs, and money, on this rule: with the Mint and Purse of Miller, Morningstar and Frantz (2000), paying someone means handing them a purse holding exactly the payment, and nothing they do with it can reach the rest of your funds.

Ambient authority is the root of the confused deputy (Hardy, 1988): a program that acts with authority it holds for one party, on behalf of another. Ethereum’s tx.origin is ambient authority: a wallet that trusts it pays out whenever its owner’s transaction passes through, even when the owner was lured into calling an attacker’s contract. OriginWallet makes that mistake; SenderWallet checks the immediate caller instead.

Python cannot hide an object’s attributes the way E does, so the purses here keep the discipline by convention rather than by enforcement.

class blockchainkit.contracts.systems.capabilities.Mint(name)[source]#

Bases: object

Creates a currency; holding the mint is the authority to create money in it.

Examples

>>> from blockchainkit.contracts import Mint
>>> mint = Mint("carol-bucks")
>>> alice = mint.make_purse(100)
>>> payment = alice.sprout()
>>> payment.deposit(10, alice)
>>> alice.balance, payment.balance
(90, 10)
Parameters:

name (str)

make_purse(balance)[source]#

A new purse holding balance of freshly created money.

Parameters:

balance (int)

Return type:

Purse

class blockchainkit.contracts.systems.capabilities.Purse(mint)[source]#

Bases: object

A holder of money in one currency: whoever holds the purse can spend from it.

Parameters:

mint (Mint)

property balance: int#

Money in the purse.

property currency: str#

The name of the mint’s currency.

sprout()[source]#

A new, empty purse of the same currency, to pay with.

Return type:

Purse

deposit(amount, source)[source]#

Move amount from source into this purse.

Holding source is the authority to take from it: no identity is checked. It must be a purse of the same mint, holding enough.

Parameters:
Return type:

None

class blockchainkit.contracts.systems.capabilities.OriginWallet[source]#

Bases: Contract

An ether wallet that authorizes withdrawals by tx.origin: a confused deputy.

layout: ClassVar[tuple[str, ...]] = ('owner',)#
constructor()[source]#
Return type:

None

receive()[source]#
Return type:

None

withdraw(to, amount)[source]#

Pay amount to to, if the caller is authorized.

Parameters:
Return type:

None

class blockchainkit.contracts.systems.capabilities.SenderWallet[source]#

Bases: OriginWallet

The same wallet, authorizing by the immediate caller (msg.sender).

class blockchainkit.contracts.systems.capabilities.AirdropPhisher[source]#

Bases: Contract

Lures a wallet owner into calling it, then withdraws from the owner’s wallet.

layout: ClassVar[tuple[str, ...]] = ('thief',)#
constructor()[source]#
Return type:

None

claim_airdrop(wallet)[source]#

The bait: “claim your free tokens”. The call it makes is the theft.

Parameters:

wallet (str)

Return type:

None

Token standards: ERC-20 (2015) and ERC-721 (2018).

ERC-20 fixed one interface for fungible tokens (transfer, approve, transfer_from, balance_of), so that any wallet or exchange could hold any token. Its approve overwrites an allowance, which opens a race: a spender who sees the owner lower an allowance from N to M can spend N first, then M as well. ERC20.increase_allowance() and ERC20.decrease_allowance() change it relative to its current value instead.

ERC-721 gives each token its own identity, for collectibles and deeds. safe_transfer_from asks a contract recipient to confirm, through on_erc721_received, that it can handle the token, so that tokens are not locked forever in a contract that cannot move them.

Function names are written in Python style (balance_of for balanceOf), amounts are unbounded integers, and functions are matched by name rather than by 4-byte selector.

blockchainkit.contracts.systems.tokens.ERC721_RECEIVED = 'on_erc721_received'#

What on_erc721_received returns to accept a token (the selector in ERC-721).

class blockchainkit.contracts.systems.tokens.ERC20[source]#

Bases: Contract

A fungible token: the creator receives the whole fixed supply.

The invariant total_supply == sum of balances is checked after every call.

layout: ClassVar[tuple[str, ...]] = ('total_supply', 'balances', 'allowances')#
constructor(supply)[source]#
Parameters:

supply (int)

Return type:

None

total_supply()[source]#

Tokens in existence.

Return type:

int

balance_of(owner)[source]#

Tokens held by owner.

Parameters:

owner (str)

Return type:

int

allowance(owner, spender)[source]#

Tokens spender may still move out of owner’s balance.

Parameters:
Return type:

int

transfer(to, amount)[source]#

Move amount from the caller to to.

Parameters:
Return type:

bool

approve(spender, amount)[source]#

Set spender’s allowance over the caller’s tokens to amount, whatever it was.

Parameters:
Return type:

bool

increase_allowance(spender, added)[source]#

Raise spender’s allowance by added.

Parameters:
Return type:

bool

decrease_allowance(spender, subtracted)[source]#

Lower spender’s allowance by subtracted, reverting below zero.

If the spender has already spent part of it, the call reverts instead of granting the old allowance on top.

Parameters:
  • spender (str)

  • subtracted (int)

Return type:

bool

transfer_from(owner, to, amount)[source]#

Move amount of owner’s tokens to to, spending the caller’s allowance.

Parameters:
Return type:

bool

invariant()[source]#

Override to state what must hold after every call; checked without gas.

Return type:

bool

class blockchainkit.contracts.systems.tokens.ERC721[source]#

Bases: Contract

A non-fungible token: each token_id has exactly one owner.

Only the deployer (the minter) creates tokens.

layout: ClassVar[tuple[str, ...]] = ('minter', 'owners', 'balances', 'approvals')#
constructor()[source]#
Return type:

None

mint(to, token_id)[source]#

Create token_id for to.

Parameters:
Return type:

None

owner_of(token_id)[source]#

The owner of token_id.

Parameters:

token_id (int)

Return type:

str

balance_of(owner)[source]#

Number of tokens owner holds.

Parameters:

owner (str)

Return type:

int

approve(spender, token_id)[source]#

Let spender transfer token_id once.

Parameters:
  • spender (str)

  • token_id (int)

Return type:

None

transfer_from(owner, to, token_id)[source]#

Move token_id from owner to to, whatever to is.

Parameters:
Return type:

None

safe_transfer_from(owner, to, token_id)[source]#

Like transfer_from(), but a contract recipient must accept the token.

The recipient’s on_erc721_received must return ERC721_RECEIVED; a contract without the function makes the call, and so the transfer, revert.

Parameters:
Return type:

None

class blockchainkit.contracts.systems.tokens.NFTVault[source]#

Bases: Contract

A contract that accepts ERC-721 tokens and lets its owner send them on.

layout: ClassVar[tuple[str, ...]] = ('owner',)#
constructor()[source]#
Return type:

None

on_erc721_received(operator, owner, token_id)[source]#

Accept any token.

Parameters:
Return type:

str

send_token(token, to, token_id)[source]#

Move a held token to to; owner only.

Parameters:
Return type:

None

Paying out safely: unchecked sends and unbounded loops (2016).

King of the Ether sold a throne: each claimant paid more than the last, and the deposed king received the payment. The contract paid with send, which gives the recipient only 2,300 gas and returns False on failure instead of reverting. A king whose wallet was itself a contract, with a receive that wrote storage, could not be paid in 2,300 gas; the contract ignored the False and crowned the next king anyway, keeping the money. Checking the result instead lets any king who refuses payment block the throne forever. The fix is the pull-payment pattern: record what is owed and let each recipient withdraw it.

GovernMental was a Ponzi game that paid its jackpot to the last investor after 12 hours of silence, then cleared its list of creditors in a loop. By April 2016 the list was so long that clearing it needed more gas than a block allowed, so the payout could never execute. Any loop over a list that users can grow is a denial of service waiting to happen.

blockchainkit.contracts.systems.payments.COMMISSION_PERCENT = 1#

King of the Ether’s cut of each claim.

blockchainkit.contracts.systems.payments.GOVERNMENTAL_TIMEOUT = 43200#

Seconds without investment (12 hours) after which GovernMental pays its jackpot.

class blockchainkit.contracts.systems.payments.KingOfTheEther[source]#

Bases: Contract

The 2016 throne game, paying the deposed king with an unchecked send.

Each claim must pay at least the current price; the old king receives the claim minus a 1% commission, and the price rises by half.

layout: ClassVar[tuple[str, ...]] = ('king', 'price', 'owed')#
constructor(price)[source]#
Parameters:

price (int)

Return type:

None

king()[source]#

The current king.

Return type:

str

price()[source]#

What the next claim must pay at least.

Return type:

int

claim_throne()[source]#

Pay at least the price to become king; the old king is compensated.

Return type:

None

class blockchainkit.contracts.systems.payments.CheckedKingOfTheEther[source]#

Bases: KingOfTheEther

Reverts the claim when paying the old king fails: a king refusing payment reigns forever.

class blockchainkit.contracts.systems.payments.PullKingOfTheEther[source]#

Bases: KingOfTheEther

Records what each deposed king is owed; they withdraw it themselves (pull payment).

owed(account)[source]#

Compensation waiting for account.

Parameters:

account (str)

Return type:

int

withdraw()[source]#

Send the caller what it is owed, with all the gas it needs.

Return type:

None

class blockchainkit.contracts.systems.payments.ContractWallet[source]#

Bases: Contract

A wallet that is a contract, as Mist’s were: its receive counts deposits in storage.

Writing storage costs more than the 2,300-gas stipend of send, so send to this wallet always fails.

layout: ClassVar[tuple[str, ...]] = ('owner', 'deposits')#
constructor()[source]#
Return type:

None

receive()[source]#
Return type:

None

forward(to, function, amount)[source]#

Call function on to with amount of the wallet’s ether; owner only.

Parameters:
Return type:

None

class blockchainkit.contracts.systems.payments.Usurper[source]#

Bases: Contract

Claims a throne from a contract that refuses every payment, so it cannot be deposed.

layout: ClassVar[tuple[str, ...]] = ()#
claim(throne)[source]#

Claim throne with all the ether sent to this call.

Parameters:

throne (str)

Return type:

None

receive()[source]#
Return type:

None

class blockchainkit.contracts.systems.payments.GovernMental[source]#

Bases: Contract

The 2016 Ponzi game: the last investor wins the jackpot after 12 quiet hours.

Parameters:

lazy_reset (bool) – False: the payout clears every creditor, one storage write each. True: it starts a new round instead, at a constant cost.

layout: ClassVar[tuple[str, ...]] = ('creditors', 'count', 'last_investment', 'round', 'lazy_reset')#
constructor(lazy_reset=False)[source]#
Parameters:

lazy_reset (bool)

Return type:

None

creditors()[source]#

Investors in the current round.

Return type:

int

invest()[source]#

Join the round; the whole investment goes to the jackpot.

Return type:

None

payout()[source]#

After 12 hours without investment, pay the jackpot to the last investor and reset.

Return type:

None

Randomness on a deterministic machine: block hashes and commit-reveal (2016).

Every node must compute the same result, so a contract has no private source of randomness. Early lotteries drew from the previous block’s hash, but any contract called in the same block reads the same hash: an attacker contract computes the winning number and plays only when it wins. (Miners could also withhold blocks with unwelcome hashes.)

A commit-reveal scheme removes the shortcut. Each player first publishes a hash of a secret with a random salt, then, after every commitment is in, reveals the secret; the draw combines all of them. Nobody can choose a secret after seeing the others, but the last player to reveal still sees the outcome first and can refuse to reveal, a bias that verifiable delay functions and RANDAO’s penalties aim to remove.

blockchainkit.contracts.systems.randomness.LOTTERY_OUTCOMES = 10#

The block-hash lottery draws a number in range(10).

blockchainkit.contracts.systems.randomness.commitment(player, secret, salt)[source]#

Hash binding player to secret; the salt stops a guess-and-check of small secrets.

The player’s address is hashed in, so that copying someone else’s commitment does not let a second player reveal the same secret.

Examples

>>> from blockchainkit.contracts import commitment
>>> commitment("alice", 7, 1) == commitment("alice", 7, 1) != commitment("bob", 7, 1)
True
Parameters:
Return type:

int

class blockchainkit.contracts.systems.randomness.BlockhashLottery[source]#

Bases: Contract

Pays the whole pot to a player who guesses blockhash(previous block) % 10.

layout: ClassVar[tuple[str, ...]] = ('stake',)#
constructor(stake)[source]#
Parameters:

stake (int)

Return type:

None

play(guess)[source]#

Pay the stake and guess; win the pot if the guess is right.

Parameters:

guess (int)

Return type:

bool

class blockchainkit.contracts.systems.randomness.LotteryPredictor[source]#

Bases: Contract

Computes the block-hash lottery’s draw in the same block, and plays only to win.

layout: ClassVar[tuple[str, ...]] = ('owner',)#
constructor()[source]#
Return type:

None

attack(lottery, stake)[source]#

Play lottery with the number it is about to draw.

Parameters:
Return type:

bool

receive()[source]#
Return type:

None

class blockchainkit.contracts.systems.randomness.CommitRevealLottery[source]#

Bases: Contract

A lottery drawn from every player’s secret, committed first and revealed later.

Players commit (paying the stake) until block commit_end, reveal until reveal_end, and anyone may then settle: the winner is player xor of revealed secrets % players. A player who never reveals forfeits the stake.

layout: ClassVar[tuple[str, ...]] = ('stake', 'commit_end', 'reveal_end', 'players', 'count', 'commitments', 'seed')#
constructor(stake, commit_blocks, reveal_blocks)[source]#
Parameters:
  • stake (int)

  • commit_blocks (int)

  • reveal_blocks (int)

Return type:

None

commit(digest)[source]#

Join with a commitment() and the stake, during the commit phase.

Parameters:

digest (int)

Return type:

None

reveal(secret, salt)[source]#

Open the commitment, during the reveal phase.

Parameters:
Return type:

None

settle()[source]#

After the reveal phase, pay the pot to the drawn player and return it.

Return type:

str

Multisig wallets behind a shared library: the two Parity incidents (2017).

Parity’s multisig wallet kept its logic in one library contract, and each wallet was a small stub that forwarded every unknown call to it with DELEGATECALL, running the library’s code on the wallet’s own storage.

July 2017. The library’s initWallet, which sets the owners, had no guard against being called twice, and the stub forwarded it like any other call. An attacker called it on three wallets, made itself sole owner, and withdrew about 153,000 ether.

November 2017. The fixed library could only be initialized once, but the library contract itself had never been initialized. A user called initWallet on the library directly, became its owner, and called kill, which ran selfdestruct. Every wallet’s DELEGATECALL now reached an address with no code, which succeeds and does nothing: about 513,000 ether were frozen for good.

The wallets here are m-of-n: an action runs when enough owners have signed it with this package’s Schnorr signatures. Parity’s owners instead confirmed by sending transactions.

blockchainkit.contracts.systems.wallets.wallet_message(wallet, nonce, action, *params)[source]#

The bytes an owner signs to approve action with params on wallet at nonce.

The wallet address and nonce make every approval single-use.

Parameters:
Return type:

bytes

blockchainkit.contracts.systems.wallets.approve_action(private_key, wallet, nonce, action, *params)[source]#

Sign an action as one owner; returns (public_key, signature).

Examples

>>> from blockchainkit.contracts import approve_action
>>> from blockchainkit.crypto import public_key
>>> owner, signature = approve_action(7, "0xwallet", 0, "execute", "bob", 10)
>>> owner == public_key(7)
True
Parameters:
Return type:

tuple[tuple[int, int], SchnorrSignature]

class blockchainkit.contracts.systems.wallets.WalletLibrary[source]#

Bases: Contract

The shared m-of-n wallet logic, with Parity’s July 2017 bug: anyone can re-initialize.

Meant to run only through Wallet’s DELEGATECALL, on the wallet’s storage.

layout: ClassVar[tuple[str, ...]] = ('library', 'owners', 'threshold', 'nonce')#
init_wallet(owners, threshold)[source]#

Set the owners and how many must sign. Nothing stops a second call.

Parameters:
Return type:

None

execute(to, amount, approvals)[source]#

Pay amount to to, approved by enough owners.

Parameters:
Return type:

None

kill(beneficiary, approvals)[source]#

Self-destruct, sending the ether to beneficiary, approved by enough owners.

Parameters:
Return type:

None

nonce()[source]#

Approvals must sign this nonce; it grows with every authorized action.

Return type:

int

blockchainkit.contracts.systems.wallets.threshold_met(signers, threshold)[source]#

True if signers distinct valid signatures meet a configured, nonzero threshold.

Parameters:
  • signers (int)

  • threshold (int)

Return type:

bool

class blockchainkit.contracts.systems.wallets.PatchedWalletLibrary[source]#

Bases: WalletLibrary

The July fix: init_wallet runs only on uninitialized storage, like the library’s own.

init_wallet(owners, threshold)[source]#

Set the owners and how many must sign. Nothing stops a second call.

Parameters:
Return type:

None

class blockchainkit.contracts.systems.wallets.Wallet[source]#

Bases: Contract

A wallet stub: it holds the ether and storage, and delegates all logic to a library.

layout: ClassVar[tuple[str, ...]] = ('library', 'owners', 'threshold', 'nonce')#
constructor(library, owners, threshold)[source]#
Parameters:
Return type:

None

receive()[source]#
Return type:

None

fallback(function, *args)[source]#
Parameters:
  • function (str | None)

  • args (Any)

Return type:

Any

Upgradeable proxies and storage collisions (2018).

Contract code cannot change, so upgradeable contracts split in two: a proxy that holds the address, the ether and the storage, and forwards every call by DELEGATECALL to an implementation holding the code. Upgrading points the proxy at a new implementation, and the state stays.

Both contracts then read the same storage through different layouts. If the proxy keeps its implementation address in slot 0 and the implementation keeps its owner there, setting the owner overwrites the implementation pointer: a storage collision. EIP-1967 (2019) moved the proxy’s own fields to slots derived from a hash, keccak256(label) - 1, where no compiled layout will ever land. A new implementation version must also keep the old fields in the same order and only append new ones; reordering them silently reinterprets the stored values.

Slots here are derived with SHA-256 rather than Keccak-256.

blockchainkit.contracts.systems.proxies.IMPLEMENTATION_SLOT = 85813119341215798126747492895011888101748610325755075380487357323095745972379#

Storage slot of an EIP1967Proxy’s implementation address.

blockchainkit.contracts.systems.proxies.ADMIN_SLOT = 4570316746107151243403647339842087450507241303126666312957956371494397612491#

Storage slot of an EIP1967Proxy’s admin address.

class blockchainkit.contracts.systems.proxies.NaiveProxy[source]#

Bases: Contract

A proxy keeping its implementation in slot 0 and its admin in slot 1.

layout: ClassVar[tuple[str, ...]] = ('implementation', 'admin')#
constructor(implementation)[source]#
Parameters:

implementation (str)

Return type:

None

upgrade_to(implementation)[source]#

Point the proxy at new code; admin only.

Parameters:

implementation (str)

Return type:

None

fallback(function, *args)[source]#
Parameters:
  • function (str | None)

  • args (Any)

Return type:

Any

class blockchainkit.contracts.systems.proxies.EIP1967Proxy[source]#

Bases: Contract

A proxy keeping its fields in hashed slots that no implementation layout reaches.

layout: ClassVar[tuple[str, ...]] = ()#
constructor(implementation)[source]#
Parameters:

implementation (str)

Return type:

None

upgrade_to(implementation)[source]#

Point the proxy at new code; admin only.

Parameters:

implementation (str)

Return type:

None

fallback(function, *args)[source]#
Parameters:
  • function (str | None)

  • args (Any)

Return type:

Any

class blockchainkit.contracts.systems.proxies.BoxV1[source]#

Bases: Contract

An implementation storing one value that only its owner may set.

Behind a proxy there is no constructor call, so initialize sets the owner.

layout: ClassVar[tuple[str, ...]] = ('owner', 'value', 'initialized')#
initialize()[source]#

Make the caller the owner, once.

Return type:

None

owner()[source]#

The owner, as this layout reads it.

Return type:

Any

store(value)[source]#

Set the value; owner only.

Parameters:

value (int)

Return type:

None

retrieve()[source]#

The stored value.

Return type:

Any

class blockchainkit.contracts.systems.proxies.BoxV2[source]#

Bases: BoxV1

A compatible upgrade: the old fields keep their slots and a new one is appended.

layout: ClassVar[tuple[str, ...]] = ('owner', 'value', 'initialized', 'changes')#
store(value)[source]#

Set the value and count the change; owner only.

Parameters:

value (int)

Return type:

None

changes()[source]#

How many times the value was set since the upgrade.

Return type:

int

class blockchainkit.contracts.systems.proxies.BoxV2Reordered[source]#

Bases: BoxV1

An incompatible upgrade: the same fields declared in another order.

layout: ClassVar[tuple[str, ...]] = ('value', 'owner', 'initialized')#

CREATE2 and counterfactual addresses (EIP-1014, 2019).

CREATE derives a new contract’s address from the deployer and its nonce, so the address depends on everything the deployer did before. CREATE2 derives it from the deployer, a chosen salt and the hash of the creation code alone:

address = H(0xff || deployer || salt || H(init_code))[12:]

The address is therefore known, and can receive funds, before the contract exists: it is counterfactual. State channels and smart-contract wallets rely on this: a wallet is deployed only when it is first needed, by whoever pays for it, at the address its owner has already been using.

class blockchainkit.contracts.systems.factory.Forwarder[source]#

Bases: Contract

A minimal wallet that sends everything it holds to its owner.

layout: ClassVar[tuple[str, ...]] = ('owner',)#
constructor(owner)[source]#
Parameters:

owner (str)

Return type:

None

receive()[source]#
Return type:

None

sweep()[source]#

Send the whole balance to the owner; anyone may call it.

Return type:

int

class blockchainkit.contracts.systems.factory.Factory[source]#

Bases: Contract

Deploys Forwarder wallets with CREATE2.

layout: ClassVar[tuple[str, ...]] = ()#
predict(salt, owner)[source]#

The address deploy() will use for salt and owner.

Parameters:
Return type:

str

deploy(salt, owner)[source]#

Deploy a forwarder for owner at its predicted address.

Parameters:
Return type:

str

Flash loans and governance attacks: Beanstalk (2022).

A flash loan lends any amount with no collateral, provided it is repaid before the transaction ends; otherwise the whole transaction, loan included, reverts. Within one transaction, anyone can briefly be as rich as the lender.

Beanstalk let token holders pass a proposal at once, by an emergencyCommit with two thirds of the voting power, one day after it was submitted. In April 2022 an attacker submitted a proposal sending the treasury to itself, waited the day, then in one transaction flash-borrowed about a billion dollars of assets, converted them into voting power, voted, committed the proposal, and repaid the loans, netting about 80 million.

The defense is to count votes at a snapshot: the balances at the block the proposal was made, as Compound’s governance token records them. No flash loan can change the past.

blockchainkit.contracts.systems.governance.PROPOSAL_DELAY = 86400#

Seconds between a proposal and its earliest emergency commit (one day).

class blockchainkit.contracts.systems.governance.VotesToken[source]#

Bases: ERC20

An ERC-20 token that also records every balance change by block, for snapshot votes.

layout: ClassVar[tuple[str, ...]] = ('total_supply', 'balances', 'allowances', 'checkpoints')#
constructor(supply)[source]#
Parameters:

supply (int)

Return type:

None

balance_at(account, block)[source]#

account’s balance at the end of block block.

Parameters:
Return type:

int

class blockchainkit.contracts.systems.governance.FlashLender[source]#

Bases: Contract

Lends its tokens within one transaction; the borrower’s callback must repay them.

layout: ClassVar[tuple[str, ...]] = ('token',)#
constructor(token)[source]#
Parameters:

token (str)

Return type:

None

flash_loan(amount)[source]#

Lend amount, call the borrower’s on_flash_loan, and require the tokens back.

Parameters:

amount (int)

Return type:

None

class blockchainkit.contracts.systems.governance.Governance[source]#

Bases: Contract

Token-weighted governance holding an ether treasury.

Parameters:
  • token (str) – The VotesToken whose holders vote.

  • snapshot (bool) – False: a vote weighs the voter’s current balance, as Beanstalk’s voting power was in effect. True: its balance at the end of the block before the proposal.

layout: ClassVar[tuple[str, ...]] = ('token', 'snapshot', 'count', 'proposals', 'votes', 'voted', 'executed')#
constructor(token, snapshot=False)[source]#
Parameters:
Return type:

None

receive()[source]#
Return type:

None

propose(beneficiary, amount)[source]#

Propose paying amount of the treasury to beneficiary; returns the proposal id.

Parameters:
  • beneficiary (str)

  • amount (int)

Return type:

int

vote(pid)[source]#

Vote for proposal pid with the caller’s weight; returns the weight.

Parameters:

pid (int)

Return type:

int

emergency_commit(pid)[source]#

Execute pid at once if a day has passed and two thirds of the supply voted for it.

Parameters:

pid (int)

Return type:

None

class blockchainkit.contracts.systems.governance.GovernanceAttacker[source]#

Bases: Contract

Borrows voting power with a flash loan, passes its own proposal, and repays.

layout: ClassVar[tuple[str, ...]] = ('owner', 'lender', 'governance', 'pid')#
constructor()[source]#
Return type:

None

receive()[source]#
Return type:

None

attack(lender, governance, pid, amount)[source]#

Borrow amount tokens from lender and use them to pass proposal pid.

Parameters:
Return type:

None

on_flash_loan(token, amount)[source]#

Vote, commit, and repay, all inside the loan.

Parameters:
Return type:

None

Verification#

Floyd-Hoare logic: proving a program correct with assertions (1967-1969).

Floyd attached assertions to the edges of a flowchart; Hoare turned them into triples {P} S {Q}: if P holds before S runs and S finishes, Q holds after. The assignment axiom {Q[e/x]} x = e {Q} lets a postcondition be pushed backwards through code. Dijkstra (1975) named the result the weakest precondition wp(S, Q), and proving the triple reduces to showing that P implies it.

Programs here are straight-line code with branches (see blockchainkit.contracts.utils.expressions). require(c) reverts when c is false, so a triple says nothing about states that revert; assert c must hold, as a Solidity assert must.

blockchainkit.contracts.systems.hoare.weakest_precondition(program, postcondition, *, modulus=None)[source]#

The weakest condition on the inputs under which program establishes postcondition.

Assignments substitute (wp(x = e, Q) = Q[e/x]), require(c) adds not c or ... (a revert proves nothing), assert c adds c and ..., and a branch takes both sides under their conditions.

Parameters:
  • program (str or collections.abc.Iterable of Statement) – Source text, or statements from parse_program().

  • postcondition (str or Expr) – What must hold afterwards.

  • modulus (int, optional) – Word size, for wrapping arithmetic such as 2**256.

Return type:

int | str | tuple[Any, …]

Examples

>>> from blockchainkit.contracts import to_source, weakest_precondition
>>> to_source(weakest_precondition("balance = balance - amount", "balance >= 0"))
'balance - amount >= 0'
blockchainkit.contracts.systems.hoare.run_program(program, env, *, modulus=None)[source]#

Run a program on concrete values.

Returns:

(outcome, variables): the outcome is "ok", "reverted" (a require failed) or "assertion" (an assert failed); the variables are their values when the program stopped.

Return type:

tuple

Parameters:
blockchainkit.contracts.systems.hoare.check_triple(precondition, program, postcondition, domain, *, modulus=None)[source]#

Check the Hoare triple {P} S {Q} on every combination of input values.

A starting state that reverts satisfies the triple; one that fails an assert, or finishes without Q, refutes it.

Parameters:
Return type:

TripleResult

Examples

>>> from blockchainkit.contracts import check_triple
>>> program = "require(amount <= balance)\nbalance = balance - amount"
>>> check_triple("balance >= 0", program, "balance >= 0",
...              {"balance": range(5), "amount": range(5)}).holds
True

Symbolic execution: King (1976) and Oyente (2016).

King ran programs on symbols instead of numbers. Each variable holds an expression over the inputs, and each branch on an expression forks the run. Every path then carries a path condition, the conjunction of the branch decisions it took, and any input that satisfies the condition drives a concrete run down that path. Solving the condition of a path that fails an assertion produces an input that triggers the bug.

Oyente (Luu et al., 2016) applied the idea to EVM bytecode with the Z3 solver, and found thousands of vulnerable contracts on the main network. symbolic_bytecode() does the same for the stack machine of blockchainkit.vm.

There is no SMT solver here: solve() searches a small set of candidate values, the boundary values that bugs tend to hide behind (0, 1, the constants of the program and their neighbors, and, for words, half the modulus and the largest word). It finds what such a set contains and reports nothing otherwise, so a None answer is not a proof.

blockchainkit.contracts.systems.symbolic.symbolic_execute(program, *, modulus=None)[source]#

Explore every path of a program with symbolic inputs.

Each if, require and assert on a non-constant condition forks the run; a require that fails ends its path as "reverted" and an assert that fails as "assertion". Paths are not checked for feasibility: use solve() on their conditions.

Parameters:
  • program (str or collections.abc.Iterable of Statement) – Source text or parsed statements.

  • modulus (int, optional) – Word size for wrapping arithmetic.

Return type:

SymbolicResult

Examples

>>> from blockchainkit.contracts import symbolic_execute
>>> result = symbolic_execute("require(x > 10)\nif x > 100:\n    y = 1\nelse:\n    y = 2")
>>> result.outcomes
{'reverted': 1, 'ok': 2}
blockchainkit.contracts.systems.symbolic.boundary_values(conditions, *, modulus=None)[source]#

Candidate inputs for solve(): 0, 1, 2, every constant and its neighbors, and, for words, the largest value, half the modulus and its neighbors.

Examples

>>> from blockchainkit.contracts import boundary_values, parse_expression
>>> boundary_values([parse_expression("x > 100")])
(0, 1, 2, 99, 100, 101)
Parameters:
Return type:

tuple[int, …]

blockchainkit.contracts.systems.symbolic.solve(conditions, *, modulus=None, candidates=None, max_assignments=1000000)[source]#

Find values of the variables that satisfy every condition, by bounded search.

Parameters:
Returns:

A satisfying assignment, or None if no candidate combination works.

Return type:

dict or None

Examples

>>> from blockchainkit.contracts import parse_expression, solve
>>> solve([parse_expression("2 * x == 0"), parse_expression("x > 0")], modulus=2**256)
{'x': 57896044618658097711785492504343953926634992332820282019728792003956564819968}
blockchainkit.contracts.systems.symbolic.symbolic_bytecode(program, *, arguments=(), max_steps=1000, max_paths=1000)[source]#

Execute a blockchainkit.vm program on symbolic arguments and storage.

The arguments are symbols named by arguments (bottom of the stack first); storage slot k starts as the symbol s<k>. JZ on a symbolic value forks, and so does DIV by a symbolic divisor, whose zero case is an error. Arithmetic wraps modulo 2**256 as in the machine. Each path’s state holds every slot it read or wrote, as slot<k>.

Parameters:
Return type:

SymbolicResult

Examples

>>> from blockchainkit.contracts import symbolic_bytecode
>>> program = [("PUSH", 10), ("LT", None), ("JZ", 4), ("REVERT", None), ("STOP", None)]
>>> symbolic_bytecode(program, arguments=("x",)).outcomes
{'stop': 1, 'revert': 1}

Design by contract: preconditions, postconditions and invariants (Meyer, 1986).

Meyer’s Eiffel treats a routine as a contract between caller and supplier: the caller must establish the precondition, the routine then guarantees the postcondition, and every public routine preserves the class invariant. Checked at run time, a violated clause is a bug in whichever side broke its part, caught where it happens rather than where it shows.

Here requires() and ensures() decorate a contract’s functions, and invariant() is the class invariant, checked by the world after every call. Any violation reverts, which is how Solidity’s require (precondition) and assert (internal invariant) behave. Checking costs no gas here; on chain it would.

blockchainkit.contracts.systems.design_by_contract.requires(predicate, message='precondition failed')[source]#

Revert with message unless predicate(self, *args) holds when the function is called.

Examples

>>> from blockchainkit.contracts import Contract, World, requires
>>> class Counter(Contract):
...     layout = ("count",)
...     @requires(lambda self, step: step > 0, "step must be positive")
...     def add(self, step):
...         self.write("count", self.read("count") + step)
>>> world = World()
>>> counter = world.deploy("alice", Counter)
>>> world.transact("alice", counter, "add", 0).error
'step must be positive'
Parameters:
Return type:

Callable[[F], F]

blockchainkit.contracts.systems.design_by_contract.ensures(predicate, message='postcondition failed')[source]#

Revert with message unless predicate(self, old, result, *args) holds on return.

old(name, *keys) reads a storage field as it was when the function was called, Eiffel’s old expression; result is the return value.

Examples

>>> from blockchainkit.contracts import Contract, World, ensures
>>> class Counter(Contract):
...     layout = ("count",)
...     @ensures(lambda self, old, result: self.read("count") == old("count") + 1)
...     def increment(self):
...         self.write("count", self.read("count") + 2)  # A bug.
>>> world = World()
>>> world.transact("alice", world.deploy("alice", Counter), "increment").error
'postcondition failed'
Parameters:
Return type:

Callable[[F], F]

Property-based fuzzing of contracts: Echidna (2020).

Echidna, from Trail of Bits, tests a contract the way QuickCheck (Claessen and Hughes, 2000) tests a function: the author writes properties that must always hold, and the fuzzer sends random sequences of transactions, with random senders and arguments, checking every property after each one. A failing sequence is then shrunk, by dropping transactions while it still fails, to a short counterexample a person can read.

Unlike symbolic execution, fuzzing needs no model of the code, scales to any contract, and never reports a bug that is not real; it only finds what random sequences reach.

blockchainkit.contracts.systems.fuzzing.shrink(setup, sequence, prop)[source]#

Drop transactions from a failing sequence, one at a time, while it still fails.

The result is 1-minimal: removing any single transaction makes it pass.

Parameters:
Return type:

tuple[FuzzCall, …]

blockchainkit.contracts.systems.fuzzing.fuzz(setup, functions, prop, *, senders, runs=100, depth=10, seed=0)[source]#

Send random transaction sequences to a contract until a property fails.

Parameters:
  • setup (collections.abc.Callable) – Given a fresh World, deploy the contract under test and return its address.

  • functions (collections.abc.Mapping) – For each function to call, the candidate values of each argument.

  • prop (collections.abc.Callable) – prop(world, target) must stay True; it is checked after every transaction, reverted or not.

  • senders (collections.abc.Sequence of str) – Accounts that send the transactions.

  • runs (int) – Random sequences to try.

  • depth (int) – Transactions per sequence.

  • seed (int) – Seeds the choice of functions, arguments and senders.

Returns:

The shrunk counterexample, if one was found, and the effort spent.

Return type:

blockchainkit.contracts.core.base.FuzzResult

Examples

>>> from blockchainkit.contracts import ERC20, fuzz
>>> def setup(world):
...     return world.deploy("alice", ERC20, 100)
>>> def conserved(world, token):
...     return world.view(token, "total_supply") == 100
>>> fuzz(setup, {"transfer": [["alice", "bob"], [0, 50]]}, conserved,
...      senders=["alice", "bob"], runs=5).failed
False

A small expression and statement language shared by the verification tools.

Expressions are written in Python syntax and parsed with ast into tuples: "balance - amount" becomes ("-", "balance", "amount"). Supported: integer constants, names, + - * // %, comparisons, and, or and not. Booleans are the integers 0 and 1. Division and modulo by zero give 0, as in the EVM.

Programs are Python source restricted to assignments to a name, if/else, assert condition and require(condition): straight-line code with branches, and no loops.

blockchainkit.contracts.utils.expressions.Statement#

("assign", name, expr), ("require", expr), ("assert", expr), or ("if", expr, body, orelse) with statement tuples as bodies.

alias of tuple[Any, …]

blockchainkit.contracts.utils.expressions.parse_expression(source)[source]#

Parse one expression.

Examples

>>> from blockchainkit.contracts import parse_expression
>>> parse_expression("amount <= balance and amount > 0")
('and', ('<=', 'amount', 'balance'), ('>', 'amount', 0))
Parameters:

source (str)

Return type:

int | str | tuple[Any, …]

blockchainkit.contracts.utils.expressions.parse_program(source)[source]#

Parse a program of assignments, if/else, assert and require(...).

Examples

>>> from blockchainkit.contracts import parse_program
>>> parse_program("require(amount <= balance)\nbalance = balance - amount")
(('require', ('<=', 'amount', 'balance')), ('assign', 'balance', ('-', 'balance', 'amount')))
Parameters:

source (str)

Return type:

tuple[tuple[Any, …], …]

blockchainkit.contracts.utils.expressions.make(op, *operands, modulus=None)[source]#

Build (op, *operands), folding it to a constant when every operand is one.

Parameters:
Return type:

int | str | tuple[Any, …]

blockchainkit.contracts.utils.expressions.evaluate(expr, env, modulus=None)[source]#

Evaluate expr on the values in env; arithmetic wraps modulo modulus if given.

Examples

>>> from blockchainkit.contracts import evaluate, parse_expression
>>> evaluate(parse_expression("2 * x"), {"x": 2**255}, modulus=2**256)
0
Parameters:
Return type:

int

blockchainkit.contracts.utils.expressions.substitute(expr, bindings, modulus=None)[source]#

Replace variables by expressions, folding constants: Q[e/x] in Hoare’s notation.

Parameters:
Return type:

int | str | tuple[Any, …]

blockchainkit.contracts.utils.expressions.expression(source, **bindings)[source]#

Parse source and substitute bindings, such as a symbolic path’s final state.

Examples

>>> from blockchainkit.contracts import expression
>>> expression("after > before", after=("+", "before", "x"))
('>', ('+', 'before', 'x'), 'before')
Parameters:
Return type:

int | str | tuple[Any, …]

blockchainkit.contracts.utils.expressions.variables(expr)[source]#

The variable names in expr.

Parameters:

expr (int | str | tuple[Any, ...])

Return type:

set[str]

blockchainkit.contracts.utils.expressions.constants(expr)[source]#

The integer constants in expr.

Parameters:

expr (int | str | tuple[Any, ...])

Return type:

set[int]

blockchainkit.contracts.utils.expressions.to_source(expr)[source]#

Write expr back in Python syntax, with only the parentheses it needs.

Examples

>>> from blockchainkit.contracts import parse_expression, to_source
>>> to_source(parse_expression("(a - b) - (c - d)"))
'a - b - (c - d)'
Parameters:

expr (int | str | tuple[Any, ...])

Return type:

str

Plotting#

Plotting helpers for blockchainkit.contracts: call trees, symbolic paths, and storage.

blockchainkit.contracts.visualizers.plots.plot_call_tree(receipt, *, names=None, ax=None)[source]#

Draw a transaction’s calls in the order they began, indented by depth.

Reverted calls are struck through in red, so a failure deep in the tree and the calls it undid are visible at once.

Parameters:
Return type:

matplotlib.axes.Axes

blockchainkit.contracts.visualizers.plots.plot_storage(storage, *, fields=None, title='Storage', ax=None)[source]#

Tabulate storage slots and their values, optionally naming each slot.

Parameters:
Return type:

matplotlib.axes.Axes

blockchainkit.contracts.visualizers.plots.plot_symbolic_paths(result, *, ax=None)[source]#

Draw the execution tree of a symbolic run, each leaf colored by its outcome.

Each fork is a branch decision on a symbolic condition; each leaf is one path, labelled by its outcome.

Parameters:
Return type:

matplotlib.axes.Axes