diff --git a/playground/src/examples/index.ts b/playground/src/examples/index.ts
index 38991ef1..f4bdd6f0 100644
--- a/playground/src/examples/index.ts
+++ b/playground/src/examples/index.ts
@@ -222,6 +222,8 @@ import * from std;
import * from std.Generic;
import * from std.StorageGeneric;
+export { Option(*), checkedDiv, unwrapOr, contains };
+
enum Option {
None,
Some(a)
@@ -248,8 +250,6 @@ function contains(option: Option, value: a) returns (bool) where a: Eq {
default { return false; }
}
}
-
-export { Option(*), checkedDiv, unwrapOr, contains };
`,
},
],
@@ -396,6 +396,8 @@ import * from std;
import * from std.Generic;
import * from std.StorageGeneric;
+export { Option(*), checkedDiv, unwrapOr, contains };
+
enum Option {
None,
Some(a)
@@ -422,8 +424,6 @@ function contains(option: Option, value: a) returns (bool) where a: Eq {
default { return false; }
}
}
-
-export { Option(*), checkedDiv, unwrapOr, contains };
`,
},
{
@@ -433,12 +433,229 @@ export { Option(*), checkedDiv, unwrapOr, contains };
import * from std;
import {caller} from std.opcodes;
+export { sender };
+
// msg.sender: the CALLER opcode lifted from word into address.
function sender() returns (address) {
return address(caller());
}
+`,
+ },
+ ],
+ },
+ {
+ id: "composition",
+ name: "Composition",
+ description: "Three deployable vaults share one engine module: traits compose where Classic Solidity builds inheritance diamonds.",
+ entry: "main.sol",
+ files: [
+ {
+ path: "main.sol",
+ content: `import * from std;
+import * from std.dispatch;
+import {settle, Direct, Signed, NoFee, FlatFee, BasisFee, Stacked} from engine;
+
+// Three deployable vaults sharing the engine module, each binding its own
+// context and fee choice. This file is the counterpart of an inheritance
+// diamond's leaf contracts; note what is absent: override lists, super
+// chains, and linearization order. The trade: each leaf repeats its two
+// storage lines, because storage stays contract-scoped. The vaults track
+// credits only; token custody is elided.
+
+contract VaultDirect {
+ balances : mapping(address => uint256);
+
+ function deposit(amount: uint256) public {
+ match (settle(Direct, NoFee, amount)) {
+ case (who, credited) { balances[who] = balances[who] + credited; }
+ }
+ }
+
+ function balanceOf(who: address) public returns (uint256) {
+ return balances[who];
+ }
+}
+
+// Gasless deposits: anyone may relay the call, and the credited account is
+// recovered from a signature over the amount. Real code would also bind a
+// nonce, the chain id, and the vault address into the digest to prevent
+// replay.
+contract VaultGasless {
+ balances : mapping(address => uint256);
+ collected : uint256;
+ flatFee : uint256;
+
+ constructor(fee: uint256) {
+ flatFee = fee;
+ }
+
+ function depositFor(amount: uint256, v: uint256, r: bytes32, s: bytes32) public {
+ let digest = bytes32(hash1(Num.toWord(amount)));
+ match (settle(Signed(digest, v, r, s), FlatFee(flatFee), amount)) {
+ case (who, credited) {
+ balances[who] = balances[who] + credited;
+ collected = collected + (amount - credited);
+ }
+ }
+ }
+
+ function balanceOf(who: address) public returns (uint256) {
+ return balances[who];
+ }
+
+ function feesCollected() public returns (uint256) {
+ return collected;
+ }
+}
+
+// Stacked fees: a protocol fee in basis points, then a flat tip, ordered by
+// the expression below.
+contract VaultPremium {
+ balances : mapping(address => uint256);
+
+ function deposit(amount: uint256) public {
+ let policy = Stacked(BasisFee(uint256(30)), FlatFee(uint256(2)));
+ match (settle(Direct, policy, amount)) {
+ case (who, credited) { balances[who] = balances[who] + credited; }
+ }
+ }
+
+ function balanceOf(who: address) public returns (uint256) {
+ return balances[who];
+ }
+}
+`,
+ },
+ {
+ path: "engine.sol",
+ content: `// The composition machinery shared by every vault in main.sol. In Classic
+// Solidity this role is played by base contracts and virtual functions; the
+// crossings then need override(...) lists. Traits have one impl per type,
+// so there is nothing to disambiguate.
+import * from std;
+import {sender} from context;
+
+export {
+ TxnContext,
+ FeePolicy,
+ settle,
+ Direct(*),
+ Signed(*),
+ NoFee(*),
+ FlatFee(*),
+ BasisFee(*),
+ Stacked(*)
+};
+
+// Axis one: where does the acting address come from?
+trait TxnContext {
+ function originator(ctx: c) returns (address);
+}
+
+// A direct call: the transaction caller acts for themselves.
+enum Direct { Direct }
+
+impl TxnContext {
+ function originator(ctx: Direct) returns (address) {
+ return sender();
+ }
+}
+
+// A relayed call: the acting address is recovered from a signature over
+// the digest the relayer hands in alongside it.
+enum Signed { Signed(bytes32, uint256, bytes32, bytes32) }
+
+impl TxnContext {
+ function originator(ctx: Signed) returns (address) {
+ match (ctx) {
+ case Signed(digest, v, r, s) {
+ // std's ecrecover reverts on malleable, failed, or
+ // zero-address recovery, so this can never return a bogus
+ // signer. The invariant lives in one place.
+ return ecrecover(digest, v, r, s);
+ }
+ }
+ }
+}
+
+// Axis two: how much of a deposit is credited?
+trait FeePolicy {
+ function afterFee(policy: f, amount: uint256) returns (uint256);
+}
+
+enum NoFee { NoFee }
+
+impl FeePolicy {
+ function afterFee(policy: NoFee, amount: uint256) returns (uint256) {
+ return amount;
+ }
+}
+
+enum FlatFee { FlatFee(uint256) }
+
+impl FeePolicy {
+ function afterFee(policy: FlatFee, amount: uint256) returns (uint256) {
+ match (policy) {
+ case FlatFee(fee) {
+ if (amount > fee) { return amount - fee; }
+ return uint256(0);
+ }
+ }
+ }
+}
+
+// A percentage fee in basis points (parts per ten thousand).
+enum BasisFee { BasisFee(uint256) }
+
+impl FeePolicy {
+ function afterFee(policy: BasisFee, amount: uint256) returns (uint256) {
+ match (policy) {
+ case BasisFee(bps) {
+ // std uint256 arithmetic is unchecked today: the multiply
+ // wraps for amounts above 2^256 / bps.
+ return amount - amount * bps / uint256(10000);
+ }
+ }
+ }
+}
+
+// Policies compose as values, applied left to right. The order is the
+// expression written at the use site, not the C3 linearization of an
+// inheritance list.
+enum Stacked { Stacked(f, g) }
+
+impl FeePolicy> where f: FeePolicy, g: FeePolicy {
+ function afterFee(policy: Stacked, amount: uint256) returns (uint256) {
+ match (policy) {
+ case Stacked(first, second) {
+ return FeePolicy.afterFee(second, FeePolicy.afterFee(first, amount));
+ }
+ }
+ }
+}
+
+// The engine, written once for every context and fee policy: who gets
+// credited with how much. Storage stays with each contract.
+function settle(ctx: c, policy: f, amount: uint256) returns ((address, uint256))
+ where c: TxnContext, f: FeePolicy
+{
+ return (TxnContext.originator(ctx), FeePolicy.afterFee(policy, amount));
+}
+`,
+ },
+ {
+ path: "context.sol",
+ content: `// Candidate standard-library inventory: this module should disappear once
+// std provides transaction context helpers.
+import * from std;
+import {caller} from std.opcodes;
export { sender };
+
+// msg.sender: the CALLER opcode lifted from word into address.
+function sender() returns (address) {
+ return address(caller());
+}
`,
},
],