Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 22 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,13 +239,17 @@ struct State {

#### Mutations

Each mutation is a Solidity library with:
- **`struct [Mutation]` definition**. The app's `dispatch` callback ABI-decodes
`mutationData` into this struct. Field names and order define the params
extracted into the manifest.
- **`execute` function**. Applies the mutation to app state using the
authenticated `accountID`. Account lookup, permissions, expiration, nonce,
and signature checks have already happened in the base contract.
Each `Mutation` member `X` needs exactly one library named `XMutation`, in the
entrypoint file or a file it imports, with:
- **`struct X`**, the mutation's params. Field names and order define the
params in the manifest. Fields can be any ABI type: value types, `bytes`,
`string`, arrays, and nested structs. Enums become `uint8`, user-defined
value types their underlying type, and contracts and interfaces `address`.
- **`function execute(State storage state, X memory mutation, bytes32 accountID) internal`**,
with no return value, where `State` is the type of the app's `state`
variable. Applies the mutation to app state using the authenticated
`accountID`. Account lookup, permissions, expiration, nonce, and signature
checks have already happened in the base contract.

```solidity
library AddMutation {
Expand Down Expand Up @@ -481,6 +485,17 @@ through the fixed authorization envelope, consumes the nonce, and calls
mutation ID and ABI-decodes the mutation struct. Built-in account mutations are
handled entirely by the base contract.

The app contract must:

- inherit only `Typewriter`;
- declare exactly `constructor(address scheduler)` and pass `scheduler` to `Typewriter(scheduler)`; the body may do more, but it takes no other arguments, so typewriter can deploy any app;
- declare `enum Mutation` inside itself;
- keep all its storage in `state` (`constant`s are allowed);
- add no `public` or `external` functions or variables;
- implement `dispatch` by calling `internal` library functions only, such as each mutation library's `execute`.

Typewriter checks these rules at startup and names the one that's broken.

#### Determinism

Opcodes whose values can't be reproduced offchain produce divergence between offchain and onchain state, so they are incompatible with Typewriter:
Expand Down
2 changes: 1 addition & 1 deletion apps/order-book/contracts/src/AddInstrument.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ library AddInstrumentMutation {
uint8 quoteLotExp;
}

function executeAddInstrument(State storage state, AddInstrument memory instrument) internal {
function execute(State storage state, AddInstrument memory instrument, bytes32) internal {
if (instrument.baseLotExp > 128 || instrument.quoteLotExp > 128) revert LotExpTooLarge();

Instrument storage stored = state.instruments[instrument.instrumentId];
Expand Down
4 changes: 2 additions & 2 deletions apps/order-book/contracts/src/ChangeOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ library ChangeOrderMutation {
uint64 price;
}

function executeChangeOrder(State storage state, ChangeOrder memory changeOrder, bytes32 accountID) internal {
function execute(State storage state, ChangeOrder memory changeOrder, bytes32 accountID) internal {
Account storage account = state.accounts[accountID];
Order storage order = account.orders[changeOrder.orderId];
uint64 orderQuantity = order.quantity;
Expand Down Expand Up @@ -53,7 +53,7 @@ library ChangeOrderMutation {
}

delete account.orders[changeOrder.orderId];
LimitOrderMutation.executeLimitOrder(
LimitOrderMutation.execute(
state,
LimitOrderMutation.LimitOrder({
quantity: uint256(orderQuantity) << instrument.baseLotExp,
Expand Down
2 changes: 1 addition & 1 deletion apps/order-book/contracts/src/CloseOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ library CloseOrderMutation {
uint64 orderId;
}

function executeCloseOrder(State storage state, CloseOrder memory closeOrder, bytes32 accountID) internal {
function execute(State storage state, CloseOrder memory closeOrder, bytes32 accountID) internal {
Account storage account = state.accounts[accountID];
Order storage order = account.orders[closeOrder.orderId];
uint64 orderQuantity = order.quantity;
Expand Down
2 changes: 1 addition & 1 deletion apps/order-book/contracts/src/Deposit.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ library DepositMutation {
uint256 amount;
}

function executeDeposit(State storage state, Deposit memory deposit, bytes32 accountID) internal {
function execute(State storage state, Deposit memory deposit, bytes32 accountID) internal {
state.accounts[accountID].balances[deposit.asset] += deposit.amount;
}
}
2 changes: 1 addition & 1 deletion apps/order-book/contracts/src/LimitOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ library LimitOrderMutation {
uint8 bidOrAsk;
}

function executeLimitOrder(State storage state, LimitOrder memory order, bytes32 accountID) internal {
function execute(State storage state, LimitOrder memory order, bytes32 accountID) internal {
Instrument storage instrument = state.instruments[order.instrumentId];
address base = instrument.base;
if (base == address(0)) revert InvalidInstrument();
Expand Down
2 changes: 1 addition & 1 deletion apps/order-book/contracts/src/MarketOrder.sol
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ library MarketOrderMutation {
uint8 bidOrAsk;
}

function executeMarketOrder(State storage state, MarketOrder memory order, bytes32 accountID) internal {
function execute(State storage state, MarketOrder memory order, bytes32 accountID) internal {
Instrument storage instrument = state.instruments[order.instrumentId];
if (instrument.base == address(0)) revert InvalidInstrument();

Expand Down
14 changes: 7 additions & 7 deletions apps/order-book/contracts/src/OrderBook.sol
Original file line number Diff line number Diff line change
Expand Up @@ -162,27 +162,27 @@ contract OrderBook is Typewriter {
function dispatch(uint8 mutation, bytes memory mutationData, bytes32 accountID) internal override {
if (mutation == uint8(Mutation.CloseOrder)) {
CloseOrderMutation.CloseOrder memory closeOrder = abi.decode(mutationData, (CloseOrderMutation.CloseOrder));
CloseOrderMutation.executeCloseOrder(state, closeOrder, accountID);
CloseOrderMutation.execute(state, closeOrder, accountID);
} else if (mutation == uint8(Mutation.ChangeOrder)) {
ChangeOrderMutation.ChangeOrder memory changeOrder =
abi.decode(mutationData, (ChangeOrderMutation.ChangeOrder));
ChangeOrderMutation.executeChangeOrder(state, changeOrder, accountID);
ChangeOrderMutation.execute(state, changeOrder, accountID);
} else if (mutation == uint8(Mutation.LimitOrder)) {
LimitOrderMutation.LimitOrder memory order = abi.decode(mutationData, (LimitOrderMutation.LimitOrder));
LimitOrderMutation.executeLimitOrder(state, order, accountID);
LimitOrderMutation.execute(state, order, accountID);
} else if (mutation == uint8(Mutation.MarketOrder)) {
MarketOrderMutation.MarketOrder memory order = abi.decode(mutationData, (MarketOrderMutation.MarketOrder));
MarketOrderMutation.executeMarketOrder(state, order, accountID);
MarketOrderMutation.execute(state, order, accountID);
} else if (mutation == uint8(Mutation.AddInstrument)) {
AddInstrumentMutation.AddInstrument memory instrument =
abi.decode(mutationData, (AddInstrumentMutation.AddInstrument));
AddInstrumentMutation.executeAddInstrument(state, instrument);
AddInstrumentMutation.execute(state, instrument, accountID);
} else if (mutation == uint8(Mutation.Deposit)) {
DepositMutation.Deposit memory deposit = abi.decode(mutationData, (DepositMutation.Deposit));
DepositMutation.executeDeposit(state, deposit, accountID);
DepositMutation.execute(state, deposit, accountID);
} else if (mutation == uint8(Mutation.Withdrawal)) {
WithdrawalMutation.Withdrawal memory withdrawal = abi.decode(mutationData, (WithdrawalMutation.Withdrawal));
WithdrawalMutation.executeWithdrawal(state, withdrawal, accountID);
WithdrawalMutation.execute(state, withdrawal, accountID);
} else {
revert UnknownMutation(mutation);
}
Expand Down
Loading
Loading