Skip to content

Instantly share code, notes, and snippets.

@stackdump
Created June 2, 2023 00:42
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/a7237241850bb7841eb8764f9cd6ee17 to your computer and use it in GitHub Desktop.
Save stackdump/a7237241850bb7841eb8764f9cd6ee17 to your computer and use it in GitHub Desktop.
Metamodel implementation in solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.18;
import "@openzeppelin/contracts/access/AccessControl.sol";
contract MetamodelUint8 {
Place[] internal places;
Transition[] internal transitions;
struct PetriNet {
Place[] places;
Transition[] transitions;
}
struct Transition {
uint8 offset;
uint8 action;
uint8 role;
int8[] guard;
int8[] delta;
}
struct Place {
uint8 offset;
int8 initial;
int8 capacity;
}
struct Response {
int8[] output;
uint8 action;
uint8 role;
uint8 multiple;
bool ok;
}
function cell(int8 initial, int8 capacity) public returns (Place memory) {
Place memory p = Place(uint8(places.length), initial, capacity);
places.push(p);
return p;
}
function fn(uint8 vectorSize, uint8 action, uint8 role) public returns (Transition memory) {
Transition memory t = Transition(uint8(transitions.length), action, role, new int8[](vectorSize), new int8[](vectorSize));
transitions.push(t);
return t;
}
function txn(uint8 weight, Place memory p, Transition memory t) public {
transitions[t.offset].delta[p.offset] = 0-int8(weight);
}
function txn(uint8 weight, Transition memory t, Place memory p) public {
transitions[t.offset].delta[p.offset] = int8(weight);
}
function guard(uint8 weight, Place memory p, Transition memory t) public {
transitions[t.offset].guard[p.offset] = 0-int8(weight);
}
}
// REVIEW visual model design in JS:
// https://pflow.dev/chainlink2023/tictactoe/
contract TicTacToeModel is MetamodelUint8 {
enum Roles{ X, O }
// Board is a 3x3 matrix
// used to index into state vector
//
// 00 01 02
// 10 11 12
// 20 21 22
enum Properties {
_00, _01, _02,
_10, _11, _12,
_20, _21, _22,
SIZE // props upper bound
}
enum Actions {
X00, X01, X02, X10, X11, X12, X20, X21, X22,
O00, O01, O02, O10, O11, O12, O20, O21, O22,
HALT // actions upper bound
}
function addMove(Properties mark, Actions action, Roles role) internal {
txn(1, places[uint8(mark)], fn(uint8(Properties.SIZE), uint8(action), uint8(role)));
}
function invalidAction(uint8 action ) public pure returns (bool) {
return action >= uint8(Actions.HALT);
}
function declaration() public returns (PetriNet memory) {
cell(1, 1);
cell(1, 1);
cell(1, 1);
cell(1, 1);
cell(1, 1);
cell(1, 1);
cell(1, 1);
cell(1, 1);
cell(1, 1);
addMove(Properties._00, Actions.X00, Roles.X);
addMove(Properties._01, Actions.X01, Roles.X);
addMove(Properties._02, Actions.X02, Roles.X);
addMove(Properties._10, Actions.X10, Roles.X);
addMove(Properties._11, Actions.X11, Roles.X);
addMove(Properties._12, Actions.X12, Roles.X);
addMove(Properties._20, Actions.X20, Roles.X);
addMove(Properties._21, Actions.X21, Roles.X);
addMove(Properties._22, Actions.X22, Roles.X);
addMove(Properties._00, Actions.O00, Roles.O);
addMove(Properties._01, Actions.O01, Roles.O);
addMove(Properties._02, Actions.O02, Roles.O);
addMove(Properties._10, Actions.O10, Roles.O);
addMove(Properties._11, Actions.O11, Roles.O);
addMove(Properties._12, Actions.O12, Roles.O);
addMove(Properties._20, Actions.O20, Roles.O);
addMove(Properties._21, Actions.O21, Roles.O);
addMove(Properties._22, Actions.O22, Roles.O);
return PetriNet(places, transitions);
}
}
contract TicTacToe is AccessControl {
bool public halted = false;
uint8 internal sequence = 0;
int8[] public state;
event Action(uint8 seq, uint8 txnId, uint8 multiple, uint8 role, uint when);
bytes32 public constant PLAYER_X = keccak256("PLAYER_X");
bytes32 public constant PLAYER_O = keccak256("PLAYER_O");
constructor(address p0, address p1) {
// TODO: add random flip too assign roles
_grantRole(PLAYER_X, p0);
_grantRole(PLAYER_O, p1);
// initialize the game
MetamodelUint8.Place[] memory places = new TicTacToeModel().declaration().places;
state.push(places[0].initial);
state.push(places[1].initial);
state.push(places[2].initial);
state.push(places[3].initial);
state.push(places[4].initial);
state.push(places[5].initial);
state.push(places[6].initial);
state.push(places[7].initial);
state.push(places[8].initial);
}
function fire(uint8 txnId, uint8 role) private returns (MetamodelUint8.Response memory) {
MetamodelUint8.Transition memory t = new TicTacToeModel().declaration().transitions[txnId];
if (txnId != t.offset) {
revert("Invalid action index");
}
if (t.role != role) {
// TODO: lookup ACH from sender
revert("Invalid role assertion");
}
for (uint8 i = 0; i < t.delta.length; i++) {
if (t.delta[i] != 0) {
state[i] += t.delta[i];
if (state[i] < 0) {
revert("Invalid state");
}
}
}
sequence++;
return MetamodelUint8.Response(state, t.action, t.role, 1, true);
}
function move(TicTacToeModel.Actions action) private {
if (halted) {
revert("Game is over");
}
MetamodelUint8.Response memory t;
if (sequence % 2 == 0) { // alternate X and O
t = fire(uint8(action), uint8(TicTacToeModel.Roles.X));
} else {
t = fire(uint8(action), uint8(TicTacToeModel.Roles.O));
}
if (t.ok) {
sequence++;
state = t.output;
emit Action(sequence, uint8(action), t.role, 1, block.timestamp);
}
}
function X00() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X00);
}
function X01() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X01);
}
function X02() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X02);
}
function X10() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X10);
}
function X11() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X11);
}
function X12() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X12);
}
function X20() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X20);
}
function X21() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X21);
}
function X22() public onlyRole(PLAYER_X) {
move(TicTacToeModel.Actions.X22);
}
function O00() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O00);
}
function O01() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O01);
}
function O02() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O02);
}
function O10() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O10);
}
function O11() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O11);
}
function O12() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O12);
}
function O20() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O20);
}
function O21() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O21);
}
function O22() public onlyRole(PLAYER_O) {
move(TicTacToeModel.Actions.O22);
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment