Skip to content

Instantly share code, notes, and snippets.

@stackdump
Last active July 14, 2023 14:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save stackdump/4fb5cd9ec2e4e265942020f774d60075 to your computer and use it in GitHub Desktop.
Save stackdump/4fb5cd9ec2e4e265942020f774d60075 to your computer and use it in GitHub Desktop.
Metamodel-v3
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/access/AccessControl.sol";
// import "hardhat/console.sol";
library Uint8Model {
event Action(uint256 indexed session, uint8 indexed sequence, uint8 actionId, uint8 role, uint256 when);
struct PetriNet {
Place[] places;
Transition[] transitions;
}
struct Transition {
uint8 offset;
uint8 role;
int8[] delta;
// int8[] guard; // REVIEW: we don't make use of guards in tic-tac-toe example
}
struct Place {
uint8 offset;
int8 initial;
// int8 capacity; // REVIEW: capacity check not used
}
}
// interface Uint8ModelFactory {
// function declaration() external returns (Uint8Model.PetriNet memory);
// }
abstract contract MetamodelUint8 {
Uint8Model.Place[] internal places;
Uint8Model.Transition[] internal transitions;
function model() external view returns (Uint8Model.PetriNet memory) {
return Uint8Model.PetriNet(places, transitions);
}
function cell(int8 initial) internal returns (Uint8Model.Place memory) {
Uint8Model.Place memory p = Uint8Model.Place(uint8(places.length), initial);
places.push(p);
return p;
}
function fn(uint8 vectorSize, uint8 action, uint8 role) internal returns (Uint8Model.Transition memory) {
require(uint8(transitions.length) == action, "Transition offset must match Actions enum");
Uint8Model.Transition memory t = Uint8Model.Transition(action, role, new int8[](vectorSize));
transitions.push(t);
return t;
}
function txn(uint8 weight, Uint8Model.Place memory p, Uint8Model.Transition memory t) internal {
transitions[t.offset].delta[p.offset] = 0-int8(weight);
}
function txn(uint8 weight, Uint8Model.Transition memory t, Uint8Model.Place memory p) internal {
transitions[t.offset].delta[p.offset] = int8(weight);
}
// function guard(uint8 weight, Uint8Model.Place memory p, Uint8Model.Transition memory t) internal {
// transitions[t.offset].guard[p.offset] = 0-int8(weight);
// }
}
library TicTacToeModel {
enum Roles{ X, O, HALT }
enum Properties {
_00, _01, _02,
_10, _11, _12,
_20, _21, _22,
SIZE
}
enum Actions {
// x moves
X00, X01, X02,
X10, X11, X12,
X20, X21, X22,
// O moves
O00, O01, O02,
O10, O11, O12,
O20, O21, O22,
HALT
}
// function declaration() external returns (Uint8Model.PetriNet memory) {
// return new TicTacToeMetaModel().model();
// }
}
abstract contract TicTacToeMetaModel is MetamodelUint8 {
// add a new action
function _action(TicTacToeModel.Properties prop, TicTacToeModel.Actions action, TicTacToeModel.Roles role) internal {
txn(1, places[uint8(prop)], fn(uint8(TicTacToeModel.Properties.SIZE), uint8(action), uint8(role)));
}
// add a property to the model
function _prop() internal {
cell(1);
}
constructor() {
_prop(); // _00
_prop(); // _01
_prop(); // _02
_prop(); // _10
_prop(); // _11
_prop(); // _12
_prop(); // _20
_prop(); // _21
_prop(); // _22
_action(TicTacToeModel.Properties._00, TicTacToeModel.Actions.X00, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._01, TicTacToeModel.Actions.X01, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._02, TicTacToeModel.Actions.X02, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._10, TicTacToeModel.Actions.X10, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._11, TicTacToeModel.Actions.X11, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._12, TicTacToeModel.Actions.X12, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._20, TicTacToeModel.Actions.X20, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._21, TicTacToeModel.Actions.X21, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._22, TicTacToeModel.Actions.X22, TicTacToeModel.Roles.X);
_action(TicTacToeModel.Properties._00, TicTacToeModel.Actions.O00, TicTacToeModel.Roles.O);
_action(TicTacToeModel.Properties._01, TicTacToeModel.Actions.O01, TicTacToeModel.Roles.O);
_action(TicTacToeModel.Properties._02, TicTacToeModel.Actions.O02, TicTacToeModel.Roles.O);
_action(TicTacToeModel.Properties._10, TicTacToeModel.Actions.O10, TicTacToeModel.Roles.O);
_action(TicTacToeModel.Properties._11, TicTacToeModel.Actions.O11, TicTacToeModel.Roles.O);
_action(TicTacToeModel.Properties._12, TicTacToeModel.Actions.O12, TicTacToeModel.Roles.O);
_action(TicTacToeModel.Properties._20, TicTacToeModel.Actions.O20, TicTacToeModel.Roles.O);
_action(TicTacToeModel.Properties._21, TicTacToeModel.Actions.O21, TicTacToeModel.Roles.O);
_action(TicTacToeModel.Properties._22, TicTacToeModel.Actions.O22, TicTacToeModel.Roles.O);
}
}
/// @custom:security-contact admin@stackdump.com
contract TicTacToe is TicTacToeMetaModel, AccessControl {
address internal owner;
bool internal paused = false;
uint256 internal gameId = 0;
uint8 internal sequence = 0;
int8[] public state = new int8[](9);
bytes32 public constant PLAYER_X = keccak256("PLAYER_X");
bytes32 public constant PLAYER_O = keccak256("PLAYER_O");
constructor(address p0, address p1) {
owner = msg.sender;
require(p0 != p1, "Players must not have the same address.");
_setupRole(DEFAULT_ADMIN_ROLE, owner);
_grantRole(PLAYER_X, p0);
_grantRole(PLAYER_O, p1);
resetGame(TicTacToeModel.Roles.HALT);
}
function _init(Uint8Model.Place memory p) internal {
if (state[p.offset] != p.initial) {
state[p.offset] = p.initial;
}
}
// no news is good news - revert if game is paused
function testIsGameOpen() public view {
require(!paused, "Game is paused.");
}
function pause() public onlyRole(DEFAULT_ADMIN_ROLE) {
paused = true;
}
function unpause() public onlyRole(DEFAULT_ADMIN_ROLE) {
paused = false;
}
modifier startGame() {
sequence = 0;
gameId++;
_;
Uint8Model.Place[] memory p = places;
_init(p[0]); // _00
_init(p[1]); // _01
_init(p[2]); // _02
_init(p[3]); // _10
_init(p[4]); // _11
_init(p[5]); // _12
_init(p[6]); // _20
_init(p[7]); // _21
_init(p[8]); // _22
}
function resetGame(TicTacToeModel.Roles role) internal startGame {
emit Uint8Model.Action(gameId, sequence, uint8(TicTacToeModel.Actions.HALT), uint8(role), block.timestamp);
}
function transform(uint8 i, Uint8Model.Transition memory t) internal {
if (t.delta[i] != 0) {
state[i] = state[i] + t.delta[i];
require(state[i] >= 0, "Invalid state");
}
}
// no news is good news - revert if it is not the caller's turn
function testIsMyTurn() public view {
testIsGameOpen();
require(!hasRole(DEFAULT_ADMIN_ROLE, msg.sender), "Gameplay from contract admin is forbidden.");
if (sequence % 2 == 0) {
require(getRole() == TicTacToeModel.Roles.X, "X's turn");
} else {
require(getRole() == TicTacToeModel.Roles.O, "O's turn");
}
}
modifier takeTurns() {
testIsMyTurn();
_;
sequence++;
}
function move(TicTacToeModel.Actions action) internal takeTurns {
uint8 txnId = uint8(action);
Uint8Model.Transition memory t = transitions[txnId];
assert(txnId == t.offset);
transform(0, t); // _00
transform(1, t); // _01
transform(2, t); // _02
transform(3, t); // _10
transform(4, t); // _11
transform(5, t); // _12
transform(6, t); // _20
transform(7, t); // _21
transform(8, t); // _22
emit Uint8Model.Action(gameId, sequence, txnId, t.role, block.timestamp);
}
function reset() public {
resetGame(getRole());
}
function getRole() public view returns (TicTacToeModel.Roles) {
if (hasRole(PLAYER_X, msg.sender)) {
return TicTacToeModel.Roles.X;
} else if (hasRole(PLAYER_O, msg.sender)) {
return TicTacToeModel.Roles.O;
} else {
revert("Unexpected caller");
}
}
function X00() public {
move(TicTacToeModel.Actions.X00);
}
function X01() public {
move(TicTacToeModel.Actions.X01);
}
function X02() public {
move(TicTacToeModel.Actions.X02);
}
function X10() public {
move(TicTacToeModel.Actions.X10);
}
function X11() public {
move(TicTacToeModel.Actions.X11);
}
function X12() public {
move(TicTacToeModel.Actions.X12);
}
function X20() public {
move(TicTacToeModel.Actions.X20);
}
function X21() public {
move(TicTacToeModel.Actions.X21);
}
function X22() public {
move(TicTacToeModel.Actions.X22);
}
function O00() public {
move(TicTacToeModel.Actions.O00);
}
function O01() public {
move(TicTacToeModel.Actions.O01);
}
function O02() public {
move(TicTacToeModel.Actions.O02);
}
function O10() public {
move(TicTacToeModel.Actions.O10);
}
function O11() public {
move(TicTacToeModel.Actions.O11);
}
function O12() public {
move(TicTacToeModel.Actions.O12);
}
function O20() public {
move(TicTacToeModel.Actions.O20);
}
function O21() public {
move(TicTacToeModel.Actions.O21);
}
function O22() public {
move(TicTacToeModel.Actions.O22);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment