Sophia & FATE
Sophia is æternity's smart-contract language: functional, strongly and statically typed, with no mutable global state. FATE (the Fast æternity Transaction Engine) is the virtual machine that executes it. Every Superhero contract is written in Sophia and runs on FATE.
If you are arriving from Ethereum, most of your instincts transfer. The table covers the ones that do not.
Coming from Solidity
| Solidity | Sophia | Note |
|---|---|---|
contract C with a body in braces | contract C = with an indented body | Layout is significant, like Python or Haskell |
public / external function | entrypoint | Only entrypoints are callable from outside |
internal / private function | function | A local function, invisible off-chain |
view / pure | a plain entrypoint | Non-stateful is the default |
| state-mutating function | stateful entrypoint | You must opt in; see below |
payable | payable entrypoint | Contracts are non-payable by default |
msg.sender | Call.caller | The calling entity, possibly another contract |
msg.value | Call.value | Coins transferred in this call |
block.number | Chain.block_height | Height of the block including this call |
block.timestamp | Chain.timestamp | Unix time in milliseconds, not seconds |
emit Transfer(...) | Chain.event(Transfer(...)) | Events are a datatype, declared once as event |
mapping | map(k, v) | A first-class value in Sophia, not a storage-only construct |
struct | record | |
enum | datatype | Sophia datatypes carry payloads, so they cover tagged unions too |
uint256 | int | Arbitrary precision on FATE, and signed |
address | address | Same idea, ak_/ct_ encoded off-chain |
| scattered storage variables | one state record, updated with put | There is no ambient mutable storage |
require(cond, "msg") | require(cond, "msg") | Also abort("msg") |
| ABI | ACI | See below |
Three differences do real work:
No mutable global state. A Sophia contract has exactly one state value. A stateful function
reads it and calls put with a new one; there is no assignment to a storage slot anywhere. This
is what reduces the surface for reentrancy and state-corruption bugs.
No floating point. Sophia omits it. Every price, fee and curve value in Superhero is an integer in aettos or token-decimals. See AE & Gas.
Types are checked, then checked again. The compiler is strict, and FATE re-checks at runtime: each FATE operation is typed, and a type mismatch raises an exception that reverts the call. A malformed call does not corrupt state; it fails.
Entrypoint annotations
Three orthogonal properties, all opt-in, all visible in the ACI:
| Annotation | Meaning | Omit it and… |
|---|---|---|
| (none) | Read-only. Can be dry-run without a transaction. | This is the default |
stateful | May modify contract state, emit events, or call another contract with a non-zero value | The compiler rejects any put, any Chain.event, any value-carrying call |
payable | May receive coins in the call | Spending to the contract fails outright |
payable also applies to the contract itself: a contract is non-payable unless declared
otherwise, so a plain AE transfer to a ct_ address fails by default.
These map straight onto how you call a contract. A non-stateful entrypoint can be answered by a
dry-run with no transaction, no gas and no wallet prompt. That is how Superhero prices a trade before
you confirm it. A stateful entrypoint needs a signed contract-call transaction. A payable one
also takes an amount option:
// Calling a payable, stateful entrypoint
await factory.create_community(
collectionId,
tokenName,
BigInt(initialBuyTokenDecimals),
false, // is_private
metaInfoMap,
{ amount: initialBuyPriceAetto }, // the coins this call carries, in aettos
);ACI: the ABI equivalent
ACI (æternity Contract Interface) is the JSON description of a contract's interface: its name,
its type definitions, its event datatype, and every function with argument names, argument types,
return type, and the stateful and payable flags. It is what a client needs to encode a call
and decode a result, the role Ethereum's ABI plays.
The ACI carries named, typed arguments and the annotations, so a client can tell a read from a write without being told. From the Tipping v3 ACI:
{
"arguments": [
{ "name": "title", "type": "string" },
{ "name": "media", "type": { "list": ["string"] } },
{ "name": "author", "type": "address" },
{ "name": "sig", "type": "signature" }
],
"name": "post_without_tip_sig",
"payable": false,
"returns": "int",
"stateful": true
}You can read off that this is a write (stateful: true), that it takes no coins
(payable: false), and that sig is a first-class signature type. That is how delegated
posting passes an off-chain signature into the contract for Crypto.verify_sig to check.
Generate an ACI
ACI is generated from Sophia source by the compiler:
aesophia_cli --create_json_aci Tipping.aes -o Tipping.aci.jsonThe same generation is available over HTTP from a compiler service at its /aci endpoint;
Superhero's web app uses the hosted compiler at https://v7.compiler.aepps.com for both
networks. Calling an already-deployed contract needs only its ACI, not a compiler. The agent
skill vendors the JSON files and reads them from disk:
const ACI = JSON.parse(fs.readFileSync('./contracts/FungibleTokenFull.aci.json', 'utf8'));
await Contract.initialize({ aci: ACI, address: tokenAddress, onAccount: aeSdk, onNode: aeSdk.api });Keep a vendored ACI in step with the deployed contract. A stale ACI does not fail loudly; it encodes arguments against the wrong signature.
The @compiler >= N pragma at the top of a .aes file is a minimum compiler version, not the
version the contract was built with.
FATE, briefly
FATE runs directly on æternity's state tree rather than on a general byte-code machine bolted on top of one.
- Typed instructions. Type errors raise an exception and revert, rather than reinterpreting bytes.
- A restricted memory model and high-level instructions. Contracts are smaller and use less gas than equivalent EVM contracts.
- Native access to chain features. AENS names and oracles are reachable from contract code as protocol primitives, not through a precompile.
Related
- Smart Contracts: the Superhero contracts written in this language.
- Calling contracts: using an ACI to make a real call.
- Contract security: where Sophia's guarantees stop and yours start.
- AEX Standards: AEX-9 read from a Sophia ACI.
- Sophia language features (opens in a new tab): the canonical reference for
stateful,payable, state and events. - FATE VM (opens in a new tab): the official description of the VM.