Assets & Payments
Arno Contracts
Arno is StableChain's own contract language, written to read like plain English with the shape of Java. It is built for public money: coins in an Arno contract can be moved but never copied or silently lost, and every contract states up front what it is allowed to do.
01 Why a new language
02 From source to chain
- 1 Written The contract is written in Arno.
- 2 Compiled The compiler checks types, coins and effects, then emits bytecode.
- 3 Verified The chain verifies the bytecode again before accepting it.
- 4 Deployed A deploy transaction installs it at its own address.
A contract runs only when a transaction calls one of its public functions. It runs in a sandbox, metered in execution units: when the units run out, it stops at exactly that point and everything it did is undone, except the fee.
03 An example: escrow
This contract holds AEY for a buyer until an arbiter releases it to the seller, or refunds the buyer after a deadline. It is one of the reference contracts the Arno compiler is tested against. Note how the deposit is moved into the vault and can never be touched again, and how each function lists its effects.
// Escrow: holds AEY until the arbiter releases it, or refunds the buyer after the deadline.
contract Escrow {
version 0.1;
uses Coins, Auth, Chain;
enum Phase { Open, Funded, Released, Refunded }
error WrongPhase "The escrow is not in the right phase for this action.";
error NotTheBuyer "Only the buyer can do this.";
error NotArbiter "Only the arbiter can do this.";
error TooEarly "The refund deadline has not passed yet.";
event EscrowFunded(Address buyer, UInt128 total);
event EscrowReleased(Address seller, UInt128 total);
event EscrowRefunded(Address buyer, UInt128 total);
private Address buyer, seller, arbiter;
private Slot deadline;
private Vault of AEY safe;
private Phase state;
constructor(Signer caller, Address theSeller, Address theArbiter, Slot theDeadline)
changes storage
{
buyer = caller.address;
seller = theSeller;
arbiter = theArbiter;
deadline = theDeadline;
safe = Vault.Empty;
state = Open;
}
public function fund(Signer caller, Coins of AEY deposit)
changes storage, receives coins, emits events
{
require state is Open otherwise WrongPhase;
require caller.address is buyer otherwise NotTheBuyer;
UInt128 total = deposit.value;
safe.deposit(move deposit); // deposit is used up here and can never be touched again
state = Funded;
emit EscrowFunded(buyer, total);
}
public function release(Signer caller)
changes storage, sends coins, emits events
{
require state is Funded otherwise WrongPhase;
require caller.address is arbiter otherwise NotArbiter;
Coins of AEY payout = safe.withdrawAll;
UInt128 total = payout.value;
transfer(move payout, seller);
state = Released;
emit EscrowReleased(seller, total);
}
public function refund(Signer caller)
changes storage, sends coins, emits events
{
require state is Funded otherwise WrongPhase;
require caller.address is buyer otherwise NotTheBuyer;
require Chain.slot > deadline otherwise TooEarly;
Coins of AEY back = safe.withdrawAll;
UInt128 total = back.value;
transfer(move back, buyer);
state = Refunded;
emit EscrowRefunded(buyer, total);
}
public view function status() returns Phase {
return state;
}
}