diff --git a/playground/src/examples/index.ts b/playground/src/examples/index.ts index a7e88d8d..1887acad 100644 --- a/playground/src/examples/index.ts +++ b/playground/src/examples/index.ts @@ -118,6 +118,44 @@ function second(pair: Pair) returns (b) { function main() returns (word) { return second(Pair(true, 42)); } +`, + }, + ], + }, + { + id: "option", + name: "Option", + description: "A generic optional value: checked division returns an Option instead of reverting.", + entry: "main.sol", + files: [ + { + path: "main.sol", + content: `import * from std; + +enum Option { + None, + Some(a) +} + +// Division by zero yields Option.None instead of reverting. +function checkedDiv(a: word, b: word) returns (Option) { + if (b == 0) { + return Option.None; + } + return Option.Some(a / b); +} + +function unwrapOr(option: Option, orElse: word) returns (word) { + match (option) { + case Option.Some(value) { return value; } + default { return orElse; } + } +} + +function main() returns (word) { + // 84 / 2 succeeds with Some(42); 84 / 0 would fall back to 0. + return unwrapOr(checkedDiv(84, 2), 0); +} `, }, ], @@ -182,6 +220,96 @@ contract Escrow { } } } +`, + }, + ], + }, + { + id: "mini-nft", + name: "Mini NFT", + description: "An NFT with typed ownership: tokens either have an owner or do not exist, with no zero-address sentinels.", + entry: "main.sol", + files: [ + { + path: "main.sol", + content: `import * from std; +import * from std.dispatch; +import * from std.Generic; +import * from std.StorageGeneric; +import {caller} from std.opcodes; + +// A token either has an owner or does not exist: unset mapping entries +// read back as Option.None. +enum Option { + None, + Some(a) +} + +function contains(option: Option, value: a) returns (bool) where a: Eq { + match (option) { + case Option.Some(inner) { return inner == value; } + default { return false; } + } +} + +// msg.sender: the CALLER opcode lifted from word into address. +function sender() returns (address) { + return address(caller()); +} + +contract MiniNFT { + nextId : uint256; + owners : mapping(uint256 => Option
); + approvals : mapping(uint256 => Option
); + balances : mapping(address => uint256); + + constructor() {} + + function mint() public returns (uint256) { + let id = nextId; + nextId = nextId + uint256(1); + let to = sender(); + owners[id] = Option.Some(to); + balances[to] = balances[to] + uint256(1); + return id; + } + + function ownerOf(id: uint256) public returns (address) { + match (owners[id]) { + case Option.Some(owner) { return owner; } + default { + require(false, "no such token"); + return address(0); + } + } + } + + function approve(to: address, id: uint256) public { + require(sender() == ownerOf(id), "not the owner"); + approvals[id] = Option.Some(to); + } + + function transfer(to: address, id: uint256) public { + let owner = ownerOf(id); + let from = sender(); + require(from == owner || contains(approvals[id], from), "not authorized"); + approvals[id] = Option.None; + owners[id] = Option.Some(to); + balances[owner] = balances[owner] - uint256(1); + balances[to] = balances[to] + uint256(1); + } + + function balanceOf(who: address) public returns (uint256) { + return balances[who]; + } + + function exists(id: uint256) public returns (bool) { + match (owners[id]) { + case Option.Some(_) { return true; } + default { return false; } + } + } +} `, }, ],