Skip to content

Commit

Permalink
Merge pull request #128 from permaweb/twilson63/feat-cli-loader-integ…
Browse files Browse the repository at this point in the history
…rate-126

Twilson63/feat cli loader integrate 126
  • Loading branch information
twilson63 authored Nov 7, 2023
2 parents d783212 + cd54a5a commit cf2305a
Show file tree
Hide file tree
Showing 12 changed files with 159 additions and 90 deletions.
16 changes: 8 additions & 8 deletions dev-cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,10 @@ You can also run `ao [command] --help` for command-lvl help.
## Testing example

Once you have built your Lua into Wasm using `ao build`, the output will be a
`contract.js` and a `contract.wasm` file.
`process.js` and a `process.wasm` file.

The `contract.js` file is the JS interop that allows invoking Wasm from a JS
program, while the `contract.wasm` is your Lua code compiled into Wasm.
The `process.js` file is the JS interop that allows invoking Wasm from a JS
program, while the `process.wasm` is your Lua code compiled into Wasm.

## For Developers

Expand All @@ -127,8 +127,8 @@ commands to run lua and emscripten as well as build tools.
You will still need `docker`. Learn how to
[Install Docker](https://www.docker.com/get-started/)

Run `deno task build-binaries` to compile the CLI binaries into the `dist` folder.
There are 4 binaries built:
Run `deno task build-binaries` to compile the CLI binaries into the `dist`
folder. There are 4 binaries built:

- Windows
- Linux
Expand Down Expand Up @@ -160,6 +160,6 @@ Workflow Dispatch that will:
> `lCA-1KVTuBxbUgUyeT_50tzrt1RZkiEpY-FFDcxmvps`, that has funded Irys Node 2
> with a very small amount of funds (`CI_WALLET` env variable). If the funds are
> depleted, then the CLI will no longer be able to publish the CLI to Arweave.
> For now, if the Irys Node needs more funding, contact `@TillaTheHun0`.
> (Maybe eventually we add a Workflow Dispatch script to automatically fund the
> Irys Node)
> For now, if the Irys Node needs more funding, contact `@TillaTheHun0`. (Maybe
> eventually we add a Workflow Dispatch script to automatically fund the Irys
> Node)
3 changes: 1 addition & 2 deletions dev-cli/container/src/definition.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ functions:
args:
- string
- string
- string

entry_file: loader.lua
output_file: contract.js
output_file: process.js
17 changes: 8 additions & 9 deletions dev-cli/container/src/loader.lua
Original file line number Diff line number Diff line change
@@ -1,24 +1,23 @@
local json = require "json"
local contract = require ".src.contract"
local process = require ".src.process"

function handle(stateJSON, actionJSON, SmartWeaveJSON)
function handle(msgJSON, aoJSON)
-- decode inputs
local state = json.decode(stateJSON)
local action = json.decode(actionJSON)
local SmartWeave = json.decode(SmartWeaveJSON)
local msg = json.decode(msgJSON)
local ao = json.decode(aoJSON)

-- handle contract
-- handle process
--
-- The contract may throw an error, either intentionally or unintentionally
-- The process may throw an error, either intentionally or unintentionally
-- So we need to be able to catch these unhandled errors and bubble them
-- across the interop with some indication that it was unhandled
--
-- To do this, we wrap the contract.handle with pcall(), and return both the status
-- To do this, we wrap the process.handle with pcall(), and return both the status
-- and response as JSON. The caller can examine the status boolean and decide how to
-- handle the error
--
-- See pcall https://www.lua.org/pil/8.4.html
local status, response = pcall(function() return (contract.handle(state, action, SmartWeave)) end)
local status, response = pcall(function() return (process.handle(msg, ao)) end)

-- encode output
local responseJSON = json.encode({ ok = status, response = response })
Expand Down
2 changes: 1 addition & 1 deletion dev-cli/container/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ int main(void) {
lua_close(wasm_lua_state);
return 1;
}
printf("Boot Lua Webassembly!\n");
//printf("Boot Lua Webassembly!\n");
return 0;
}

Expand Down
15 changes: 8 additions & 7 deletions dev-cli/src/commands/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,19 @@
import { Command } from '../deps.js'

const LUA = `
local contract = { _version = "0.0.1" }
local process = { _version = "0.0.1" }
function contract.handle(state, action, SmartWeave)
function process.handle(msg, env)
-- do stuff
local response = {
state = state,
result = { messages = {} }
output = "Hello World",
messages = {}
spawns = {}
}
return response
end
return contract
return process
`

export function init (_, name) {
Expand All @@ -24,10 +25,10 @@ export function init (_, name) {
// output: `${name}.lua`
// }
return Deno.mkdir(`./${name}`, { recursive: true })
.then((_) => Deno.writeTextFile(`./${name}/contract.lua`, LUA))
.then((_) => Deno.writeTextFile(`./${name}/process.lua`, LUA))
}

export const command = new Command()
.description('Create an Ao Lua Contract Source Project')
.description('Create an ao Process Source Project')
.arguments('<name:string>')
.action(init)
2 changes: 1 addition & 1 deletion dev-cli/src/commands/publish.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ function contractSourceArgs (contractWasmPath) {
/**
* Use contract.wasm in pwd by default
*/
contractWasmPath = contractWasmPath || 'contract.wasm'
contractWasmPath = contractWasmPath || 'process.wasm'
const contractName = basename(contractWasmPath)
const contractWasmDest = `/src/${contractName}`

Expand Down
52 changes: 41 additions & 11 deletions loader/README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
# ao Wasm Loader

This module takes an `ao` Wasm `ArrayBuffer` and returns a `handle` function,
that given `SmartWeaveContract` inputs, will produce a `result`.
that given `ao-process` message, will produce a `result`.

The handle function can be invoked just like any other `SmartWeaveContract`
<!-- toc -->

- [Usage](#usage)
- [Using a File](#using-a-file)
- [Using `fetch`](#using-fetch)

<!-- tocstop -->

## Usage

Expand All @@ -13,21 +19,45 @@ to be invoked:
```js
import AoLoader from "@permaweb/ao-loader";

/* SmartWeave READ-ONLY Env Variables */
const SmartWeave = {
transaction: {
/* ao READ-ONLY Env Variables */
const env = {
message: {
id: "1",
},
process: {
id: "2",
},
};

// Create the handle function that executes the Wasm
const handle = AoLoader(wasmBinary);
const handle = await AoLoader(wasmBinary);

// To spawn a process, pass null as the buffer
const result = await handle(null, {
owner: "OWNER_ADDRESS",
tags: [
{ name: "function", value: "balance" },
{ name: "target", value: "vh-NTHVvlKZqRxc8LyyTNok65yQ55a_PJ1zWLb9G2JI" },
],
}, env);
```

// To evaluate a message on an existing process

// Now invoke the handle
const result = await handle({ balances: 1 }, {
caller: "1",
input: { function: "balance" },
}, SmartWeave);
```js
const handle = await AoLoader(wasmBinary);
const buffer = await LoadFromCache();

const result = await handle(buffer, {
owner: "OWNER_ADDRESS",
tags: [
{ name: "function", value: "balance" },
{ name: "target", value: "vh-NTHVvlKZqRxc8LyyTNok65yQ55a_PJ1zWLb9G2JI" },
],
}, env);

saveToCache(result.buffer);
console.log(result.output);
```

### Using a File
Expand Down
22 changes: 11 additions & 11 deletions loader/package.json
Original file line number Diff line number Diff line change
@@ -1,26 +1,26 @@
{
"name": "@permaweb/ao-loader",
"version": "0.0.4",
"license": "MIT",
"sideEffects": false,
"type": "module",
"main": "./dist/index.cjs",
"types": "./dist/index.d.ts",
"license": "MIT",
"engines": {
"node": ">=18"
},
"sideEffects": false,
"devDependencies": {
"esbuild": "^0.19.2",
"typescript": "^5.2.2"
},
"files": [
"./dist"
],
"scripts": {
"build:types": "tsc src/index.cjs --declaration --allowJs --emitDeclarationOnly --outDir dist",
"build:src": "node esbuild.js",
"build": "npm run build:types && npm run build:src",
"build:src": "node esbuild.js",
"build:types": "tsc src/index.cjs --declaration --allowJs --emitDeclarationOnly --outDir dist",
"test": "node --test",
"test:integration": "npm run build:src && MODULE_PATH='../dist/index.cjs' node --test"
},
"devDependencies": {
"esbuild": "^0.19.2",
"typescript": "^5.2.2"
},
"engines": {
"node": ">=18"
}
}
72 changes: 53 additions & 19 deletions loader/src/index.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -18,32 +18,66 @@ var Module = (() => {
})();
/* eslint-enable */

// load wasm and return handle function
/**
* @typedef Tag
* @property {string} name
* @property {string} value
*/

/**
* @typedef Message
* @property {string} owner
* @property {string} target
* @property {Tag[]} tags
* @property {DataItem} data
*/

/**
* @typedef Environment
* @property {{id: string, owner: string, tags: Tag[]}} process
* @property {{id: string, owner: string, tags: Tag[]}} message
* @property {{height: string, timestamp: string}} block
*/

/**
* @typedef HandleResponse
* @property {ArrayBuffer} buffer
* @property {DataItem} output
* @property {Message[]} messages
* @property {Message[]} spawns
*/

/**
* @callback handleFunction
* @param {unknown} state
* @param {unknown} action
* @param {unknown} SmartWeave
* @param {ArrayBuffer | NULL} buffer
* @param {Message} msg
* @param {Environment} env
* @returns {HandleResponse}
*/

/**
* @param {ArrayBuffer} binary
* @returns {handleFunction}
*/
module.exports = function (binary) {
// execute handle function
return (state, action, SmartWeave) => Module(binary).then(i => {
const handle = i.cwrap('handle', 'string', ['string', 'string', 'string'])

const { ok, response } = JSON.parse(handle(JSON.stringify(state), JSON.stringify(action), JSON.stringify(SmartWeave)))

if (ok) return response
/**
* An unhandled error was received across the interop, so throw it,
* causing the Promise to reject.
*
* The caller should handle accordingly
*/
module.exports = async function (binary) {
const instance = await Module(binary)
const doHandle = instance.cwrap('handle', 'string', ['string', 'string'])

return (buffer, msg, env) => {
if (buffer) {
instance.HEAPU8.set(buffer)
}
const { ok, response } = JSON.parse(doHandle(JSON.stringify(msg), JSON.stringify(env)))

if (ok) {
buffer = instance.HEAPU8.slice()
return {
buffer,
output: response.output,
messages: response.messages,
spawns: response.spawns
}
}
throw response
})
}
}
Binary file added loader/test/contracts/process.wasm
Binary file not shown.
Binary file added loader/test/contracts/process2.wasm
Binary file not shown.
48 changes: 27 additions & 21 deletions loader/test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,39 @@ describe('loader', async () => {
it('load and execute message passing contract', async () => {
const { default: hyperbeamLoader } = await import(MODULE_PATH)

const wasmBinary = fs.readFileSync('./test/contracts/message/contract.wasm')
const mainHandler = hyperbeamLoader(wasmBinary)
const wasmBinary = fs.readFileSync('./test/contracts/process.wasm')
const mainHandler = await hyperbeamLoader(wasmBinary)
const mainResult = await mainHandler(
null,
{
balances: { 1: 1 },
sendToContract: 'ctr-id-123'
},
{
input: { function: 'noop' }
owner: 'tom',
target: '',
tags: [
{ name: 'function', value: 'count' }
]
},
{
transaction: { id: 'tx-id-123' },
contract: { id: 'ctr-id-456' }
process: { id: 'ctr-id-456' }
}
)
console.log(mainResult.result)
const { result: { messages: [message] } } = mainResult
assert.deepStrictEqual(message, {
target: 'ctr-id-123',
txId: 'tx-id-123',
message: {
caller: 'ctr-id-456',
qty: 10,
type: 'transfer',
from: 'ctr-id-456',
to: 'ctr-id-123'
}
})
assert.equal(mainResult.output, 'Hello World')

assert.ok(true)
})

it('should load previous memory', async () => {
const { default: hyperbeamLoader } = await import(MODULE_PATH)

const wasmBinary = fs.readFileSync('./test/contracts/process2.wasm')
const mainHandler = await hyperbeamLoader(wasmBinary)
// spawn
const result = await mainHandler(null, { owner: 'tom', tags: [{ name: 'function', value: 'count' }] }, {})
assert.equal(result.output, 'count: 1')

const nextHandler = await hyperbeamLoader(wasmBinary)
const result2 = await nextHandler(result.buffer, { owner: 'tom', tags: [{ name: 'function', value: 'count' }] }, {})
assert.equal(result2.output, 'count: 2')
assert.ok(true)
})
})

0 comments on commit cf2305a

Please sign in to comment.