Created
June 2, 2023 00:42
-
-
Save stackdump/a7237241850bb7841eb8764f9cd6ee17 to your computer and use it in GitHub Desktop.
Metamodel implementation in solidity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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