Skip to content
This repository has been archived by the owner on Oct 15, 2024. It is now read-only.

Pst implementation #2

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
209 changes: 209 additions & 0 deletions index_pst.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
const fs = require("fs");
const loader = require("@assemblyscript/loader");
const metering = require('wasm-metering');
const {Benchmark} = require("redstone-smartweave");

const wasmBinary = fs.readFileSync(__dirname + "/build/pst.wasm");
const meteredWasmBinary = metering.meterWASM(wasmBinary, {
meterType: "i32",
});
const wasm2json = require('wasm-json-toolkit').wasm2json
const json = wasm2json(meteredWasmBinary);
fs.writeFileSync("wasm_module.json", JSON.stringify(json, null, 2))

let limit = 5100000000;
let gasUsed = 0;

const imports = {
metering: {
usegas: (gas) => {
gasUsed += gas;
if (gasUsed > limit) {
throw new Error(`[RE:OOG] Out of gas! Limit: ${formatGas(limit)}, used: ${formatGas(gasUsed)}`);
}
}
},
console: {
"console.log": function (msgPtr) {
console.log(`Contract: ${wasmExports.__getString(msgPtr)}`);
},
"console.logO": function (msgPtr, objPtr) {
console.log(`Contract: ${wasmExports.__getString(msgPtr)}`, JSON.parse(wasmExports.__getString(objPtr)));
},
},
block: {
"Block.height": function () {
return 875290;
},
"Block.indep_hash": function () {
return wasmExports.__newString("iIMsQJ1819NtkEUEMBRl6-7I6xkeDipn1tK4w_cDFczRuD91oAZx5qlgSDcqq1J1");
},
"Block.timestamp": function () {
return 123123123;
},
},
transaction: {
"Transaction.id": function () {
return wasmExports.__newString("Transaction.id");
},
"Transaction.owner": function () {
return wasmExports.__newString("0x123");
},
"Transaction.target": function () {
return wasmExports.__newString("Transaction.target");
},
},
contract: {
"Contract.id": function () {
return wasmExports.__newString("Contract.id");
},
"Contract.owner": function () {
return wasmExports.__newString("Contract.owner");
},
},
msg: {
"msg.sender": function () {
return wasmExports.__newString("msg.sender");
},
},
api: {
_readContractState: (fnIndex, contractTxIdPtr) => {
const contractTxId = wasmExports.__getString(contractTxIdPtr);
const callbackFn = getFn(fnIndex);
console.log("Simulating read state of", contractTxId);
return setTimeout(() => {
console.log('calling callback');
callbackFn(__newString(JSON.stringify(
{
dummyVal: 777
}
)));
}, 1000);
},
clearTimeout,
},
env: {
abort(messagePtr, fileNamePtr, line, column) {
console.error("--------------------- Error message from AssemblyScript ----------------------");
console.error(" " + wasmExports.__getString(messagePtr));
console.error(
' In file "' + wasmExports.__getString(fileNamePtr) + '"'
);
console.error(` on line ${line}, column ${column}.`);
console.error("------------------------------------------------------------------------------\n");
},
}
}

function getFn(idx) {
return wasmExports.table.get(idx);
}

const wasmModule = loader.instantiateSync(
meteredWasmBinary,
imports
);

const wasmExports = wasmModule.exports;

const {handle, lang, initState, currentState} = wasmModule.exports;
const {__newString, __getString, __collect} = wasmModule.exports;

function safeHandle(action) {
try {
//FIXME: Please add to refactored handle (This is my great contribution to WASM's correctness ;))
return doHandle(action)
} catch (e) {
// note: as exceptions handling in WASM is currently somewhat non-existent
// https://www.assemblyscript.org/status.html#exceptions
// and since we have to somehow differentiate different types of exceptions
// - each exception message has to have a proper prefix added.

// exceptions with prefix [RE:] ("Runtime Exceptions") should break the execution immediately
// - eg: [RE:OOG] - [RuntimeException: OutOfGas]

// exception with prefix [CE:] ("Contract Exceptions") should be logged, but should not break
// the state evaluation - as they are considered as contracts' business exception (eg. validation errors)
// - eg: [CE:WTF] - [ContractException: WhatTheFunction] ;-)
if (e.message.startsWith('[RE:')) {
throw e;
} else {
console.error(e.message);
}
}
}

function doHandle(action) {
// TODO: consider NOT using @assemblyscript/loader and handle conversions manually
// - as @assemblyscript/loader adds loads of crap to the output binary.
const actionPtr = __newString(JSON.stringify(action));
const resultPtr = handle(actionPtr);
const result = __getString(resultPtr);
return JSON.parse(result);
}

function doInitState() {
let statePtr = __newString(JSON.stringify({
balances: {"0x123": 100}
}
));

initState(statePtr);

gasUsed = 0;
}

function doGetCurrentState() {
const currentStatePtr = currentState();
return JSON.parse(wasmExports.__getString(currentStatePtr));
}


const actions = [
// {function: 'foreignRead', contractTxId: 'sdfsdf23423sdfsdfsdfsdfsdfsdfsdfsdf'}
{function: 'transfer', target: '0x777', qty: 1},
{function: 'transfer', target: '0x777', qty: 2},
{function: 'transfer', target: '0x333', qty: 3},
{function: 'balance', target: '0x123'}
]

// note: this will be useful in SDK to prepare the wasm execution env. properly
// for contracts written in different langs (eg. in assemblyscript we can use the
// built-in @assemblyscript/loader to simplify the communication - but obv. it wont' be available
// in Rust or Go)
console.log("Contract language:", __getString(lang));

//(o) initialize the state in the wasm contract
doInitState();

//(o) evaluate all actions
for (const action of actions) {
console.log("==============================================================================")
const handlerResult = safeHandle(action);
console.log({
handlerResult,
state: doGetCurrentState(),
gas: `${formatGas(gasUsed)}`,
gasLimit: `${formatGas(limit)}`
});
}

// (o) re-init the state
/*doInitState();
console.log("Current state", doGetCurrentState());
limit = limit * 100000;

const benchmark = Benchmark.measure();
for (let i = 0; i < 1_000_000; i++) {
if (i % 100_000 == 0) {
console.log('calling', i + 1);
}
safeHandle({function: 'increment'});
}
console.log("Computed 1M interactions in", benchmark.elapsed());
console.log("Current state", doGetCurrentState());
console.log("Gas used", formatGas(gasUsed));*/

function formatGas(gas) {
return gas * 1e-4;
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"asbuild2:optimized": "asc assembly/fancy/RedStoneToken.ts --exportRuntime --sourceMap --importMemory --exportTable --target release",
"asbuild2": "yarn asbuild2:untouched && yarn asbuild2:optimized",
"asbuild:handle": "asc assembly/handle.ts --sourceMap --runtime stub --exportRuntime --transform ./ContractTransform --exportTable --target release",
"asbuild:pst": "asc pst/handle.ts --sourceMap --runtime stub --exportRuntime --transform ./ContractTransform --exportTable --target release --binaryFile ./build/pst.wasm",
"run:pst": "yarn asbuild:pst && node index_pst",
"test": "node tests"
},
"engines": {
Expand Down
20 changes: 20 additions & 0 deletions pst/actions/balance.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import {ActionSchema, HandlerResultSchema, StateSchema} from "../schemas";

export function balance(state: StateSchema, action: ActionSchema): HandlerResultSchema {
const target = action.target;

if (!target) {
throw new Error('[CE:NOB] Must specify target to get balance for');
}

if (!state.balances.has(target)) {
throw new Error('[CE:TNE] Cannot get balance, target does not exist');
}

return {
state,
result: {
balance: state.balances.get(target)
}
}
}
13 changes: 13 additions & 0 deletions pst/actions/mint.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import {ActionSchema, HandlerResultSchema, StateSchema} from "../schemas";
import {Transaction} from "../imports/smartweave/transaction";

export function mint(state: StateSchema, action: ActionSchema): HandlerResultSchema {
const caller = Transaction.owner();

state.balances.set(caller, 10000000);

return {
state,
result: null
};
}
29 changes: 29 additions & 0 deletions pst/actions/transfer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import {ActionSchema, HandlerResultSchema, StateSchema} from "../schemas";
import {Transaction} from "../imports/smartweave/transaction";

export function transfer(state: StateSchema, action: ActionSchema): HandlerResultSchema {
const target = action.target;
const qty = action.qty;
const caller = Transaction.owner();

if (qty <= 0 || caller == target) {
throw new Error('[CE:ITT] Invalid token transfer');
}

if (!state.balances.has(caller) || state.balances.get(caller) < qty) {
throw new Error(`[CE:NEB] Caller balance not high enough to send ${qty} token(s)!`);
}

// Lower the token balance of the caller
state.balances.set(caller, state.balances.get(caller) - qty);
if (!state.balances.has(target)) {
state.balances.set(target,qty);
} else {
state.balances.set(target, state.balances.get(target) + qty);
}

return {
state,
result: null
};
}
1 change: 1 addition & 0 deletions pst/contract.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare function contract(a: any): any;
35 changes: 35 additions & 0 deletions pst/handle.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// TODO: add all those imports by default in "transform" - but how?
import {parse, stringify} from "@serial-as/json";
import {console} from "./imports/console";
import {msg} from "./imports/smartweave/msg";
import {Block} from "./imports/smartweave/block";
import {Transaction} from "./imports/smartweave/transaction";
import {Contract} from "./imports/smartweave/contract";
import {ActionSchema, HandlerResultSchema, ResultSchema, SmartweaveSchema, StateSchema} from "./schemas";
import {balance} from "./actions/balance";
import {transfer} from "./actions/transfer";

type ContractFn = (state: StateSchema, action: ActionSchema) => HandlerResultSchema;

const functions: Map<string, ContractFn> = new Map();
// note: inline "array" map initializer does not work in AS.
functions.set("balance", balance);
functions.set("transfer", transfer);

let contractState: StateSchema;

@contract
function handle(action: ActionSchema): ResultSchema | null {
console.log(`Function called: "${action.function}"`);

const fn = action.function;
if (functions.has(fn)) {
const handlerResult = functions.get(fn)(contractState, action);
if (handlerResult.state != null) {
contractState = handlerResult.state;
}
return handlerResult.result;
} else {
throw new Error(`[CE:WTF] Unknown function ${action.function}`);
}
}
15 changes: 15 additions & 0 deletions pst/imports/api.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
export declare function _setTimeout(fn: usize, milliseconds: f32): i32

export function setTimeout<T = unknown>(fn: (t: T) => void, milliseconds: f32 = 0.0): i32 {
return _setTimeout(fn.index, milliseconds)
}

export declare function clearTimeout(id: i32): void


export declare function _readContractState(fn: usize, contractTxId: string): i32;

// note: this requires adding --exportTable to asc
export function readContractState(fn: (t: string) => void, contractTxId: string): i32 {
return _readContractState(fn.index, contractTxId);
}
9 changes: 9 additions & 0 deletions pst/imports/console.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export declare namespace console {
function logO(msg: string, data: string /* stringified json */): void;
function log(msg: string): void;
}


declare namespace core {
function setTimeout(cb: () => void, ms: i32): i32;
}
16 changes: 16 additions & 0 deletions pst/imports/smartweave.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export declare interface Contract {
id(): string;
owner: string;
};

declare function contract(a: any): any;

export declare namespace SmartWeave {
function id(): string;
function contract(): Contract;
}





5 changes: 5 additions & 0 deletions pst/imports/smartweave/block.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export declare namespace Block {
function height(): i32;
function indep_hash(): string;
function timestamp(): i32;
}
4 changes: 4 additions & 0 deletions pst/imports/smartweave/contract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export declare namespace Contract {
function id(): string;
function owner(): string;
}
3 changes: 3 additions & 0 deletions pst/imports/smartweave/msg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export declare namespace msg {
function sender(): string;
}
11 changes: 11 additions & 0 deletions pst/imports/smartweave/transaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export declare namespace Transaction {
function id(): string;
function owner(): string;
function target(): string;
function tags(): Tag[];
}


export interface Tag {

}
Loading