Skip to content
Closed
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
84 changes: 84 additions & 0 deletions .github/workflows/cdt.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: CDT

# Builds examples/cdt with AntelopeIO CDT and runs it against VeRT. CDT ships
# Linux x86_64 packages only, so this is the supported way to exercise the
# suite. The blanc-built example suites are not run here, since blanc has to be
# installed separately.

on:
push:
branches: [master]
pull_request:
workflow_dispatch:

env:
# v4.1.0 is the first release that exports the wasm memory VeRT reads.
CDT_VERSION: 4.1.1

jobs:
test:
runs-on: ubuntu-latest

steps:
- uses: actions/checkout@v4

- uses: pnpm/action-setup@v4
with:
version: 11

- uses: actions/setup-node@v4
with:
node-version-file: .nvmrc
cache: pnpm

- name: Install AntelopeIO CDT
run: |
sudo apt-get update

# CDT publishes its package from an older Ubuntu than the runner
# image, so its clang needs an ncurses release that newer images no
# longer package.
if ! sudo apt-get install -y libtinfo5; then
pool=http://archive.ubuntu.com/ubuntu/pool/universe/n/ncurses
deb=$(curl -fsSL "$pool/" | grep -o 'libtinfo5_[^"]*_amd64\.deb' | sort -uV | tail -1)
curl -fsSL -O "$pool/$deb"
sudo apt-get install -y "./$deb"
fi

curl -fsSL -O "https://github.com/AntelopeIO/cdt/releases/download/v${CDT_VERSION}/cdt_${CDT_VERSION}-1_amd64.deb"
sudo apt-get install -y "./cdt_${CDT_VERSION}-1_amd64.deb"

- run: pnpm install --frozen-lockfile

# The examples resolve @proton/vert through dist, so build the package first.
- run: pnpm run build

- run: pnpm run lint

- name: Test library
run: pnpm exec mocha

- name: Build examples/cdt with cdt-cpp
run: pnpm --filter examples run build:cdt

- name: Report the exports cdt-cpp produced
run: |
node -e '
const fs = require("fs");
const module_ = new WebAssembly.Module(fs.readFileSync("examples/cdt/cdt.wasm"));
console.log(WebAssembly.Module.exports(module_));
'

- name: Test examples/cdt
run: pnpm --filter examples run test:cdt

# Lets contributors without a Linux toolchain download a CDT build and run
# the suite locally.
- uses: actions/upload-artifact@v4
if: always()
with:
name: cdt-contract
path: |
examples/cdt/cdt.wasm
examples/cdt/cdt.abi
if-no-files-found: warn
28 changes: 23 additions & 5 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,9 +1,27 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist*
yarn-error.log
package-lock.json
.idea
build
dist
dist-ssr
*.local

examples/**/*.wasm
examples/**/*.abi

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
6 changes: 6 additions & 0 deletions .mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"extension": ["ts"],
"require": "ts-node/register",
"node-option": ["no-experimental-strip-types"],
"spec": "./src/**/*.spec.ts"
}
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v22
1 change: 1 addition & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@

Copyright (c) 2021 Jeeyong "conr2d" Um

The MIT License
Expand Down
65 changes: 59 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,29 +13,82 @@ The focus of VeRT is on the better compatibility than the performance, so it can

## Requirement

- WebAssembly binary with the exported memory ([blanc](https://github.com/haderech/blanc) v0.9.2 or higher)
- WebAssembly binary with the exported memory, built with either [AntelopeIO CDT](https://github.com/AntelopeIO/cdt) v4.1.0 or higher, or [blanc](https://github.com/haderech/blanc) v0.9.2 or higher
- JavaScript runtime with WebAssembly BigInt support (nodejs v16 or higher)

## Contract toolchains

VeRT runs an action by calling the `apply` export of the contract binary, and reads contract state
by looking straight into the module's linear memory. Both of those have to be exported from the
WebAssembly binary, and that requirement is what ties VeRT to a particular toolchain version:
neither AntelopeIO CDT before v4.1.0 nor the original `eosio.cdt` exports the memory, so a binary
built with them is rejected with a message asking for a rebuild.

Apart from the memory export, the two supported toolchains are interchangeable as far as VeRT is
concerned. Both compile the same C++ contract sources against the same host API, and both emit the
`.abi` next to the `.wasm`.

| | AntelopeIO CDT | blanc |
| ------------------------------ | ------------------------------ | ---------------------------------------------------- |
| Compiler | `cdt-cpp` | `blanc++` |
| Minimum version VeRT can load | v4.1.0 | v0.9.2 |
| Exports in the built binary | `apply`, `memory` | `apply`, `memory`, `__heap_base`, `__data_end` |
| Example suite | [examples/cdt](./examples/cdt) | [examples/foo](./examples/foo), and the other folders |

The extra exports blanc emits are unused by VeRT, which is why AntelopeIO CDT became usable the
moment it started exporting the memory: v4.1.0 links contracts with `--only-export *:memory` in
addition to the `--only-export apply:function` it already used.

Two differences are worth keeping in mind when writing tests against an AntelopeIO CDT build:

- CDT defaults to ABI version `eosio::abi/1.2`, so actions that return a value are declared as
`action_results` in the ABI. VeRT captures the serialized value per action in
`blockchain.actionTraces[n].returnValue`, but does not resolve the result type from the ABI, so
decode it with an explicit type.
- VeRT implements the Antelope host API, excluding the BLS intrinsics added in CDT v4.x. A contract
that calls them fails to instantiate with a `LinkError` naming the missing `env` import.

## Installation

```shell
npm install @proton/vert
```

## Test
## Development

- nodejs v16 or higher

```shell
npm run test
pnpm install
```

- nodejs v14
## Test

```shell
pnpm run test
```

This runs the library tests, which include the toolchain compatibility checks in
[src/proton/tests/cdt.spec.ts](./src/proton/tests/cdt.spec.ts), followed by the example suites.

Example binaries are not checked in, so build them first. Each suite is built by the toolchain it
demonstrates, which means `pnpm --filter examples run build` needs both compilers on the `PATH`;
build a single suite instead if you only have one of them installed:

```shell
node --experimental-wasm-bigint node_modules/mocha/bin/_mocha src/**/*.spec.ts -r ts-node/register
# examples/cdt, built with cdt-cpp; its suite is skipped while the binary is missing.
# When invoking cdt-cpp by hand, pass the source path without a leading ./ — with one,
# cdt-cpp loses track of the dispatcher it generates and the link aborts.
pnpm --filter examples run build:cdt

# examples/foo, built with blanc++
pnpm --filter examples run build:foo
```

CDT is distributed as a Linux x86_64 package only, so the
[CDT workflow](./.github/workflows/cdt.yml) builds `examples/cdt` on a runner and uploads the
resulting `cdt.wasm` and `cdt.abi`. Dropping those two files into `examples/cdt` is enough to run
the suite on a machine without the toolchain.

## License

[MIT](./LICENSE)
Expand Down
27 changes: 0 additions & 27 deletions bundle.js

This file was deleted.

1 change: 1 addition & 0 deletions env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module 'rustbn.js'
21 changes: 21 additions & 0 deletions esbuild.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import esbuild from 'esbuild';

// Create the esbuild build configuration
esbuild.build({
entryPoints: ['dist/index.js'], // Main entry point (change as needed)
bundle: true, // Enable bundling
format: 'esm',
outfile: 'dist/bundle.js', // Output file
sourcemap: true, // Generate sourcemaps
minify: true, // Minify the output for production
external: [], // Exclude specific modules from being bundled (e.g., node_modules)
platform: 'node', //node
sourcemap: true,
define: {
global: 'global',
process: 'process',
Buffer: 'Buffer'
},
// format: 'iife',
globalName: 'Vert'
}).catch(() => process.exit(1)); // Exit on error
29 changes: 29 additions & 0 deletions eslint.config.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import js from "@eslint/js";
import globals from "globals";
import tseslint from "typescript-eslint";
import json from "@eslint/json";
import { defineConfig } from "eslint/config";

export default defineConfig([
{ files: ["**/*.{js,mjs,cjs,ts,mts,cts}"], plugins: { js }, extends: ["js/recommended"], languageOptions: { globals: globals.node } },
tseslint.configs.recommended,
{ files: ["**/*.json"], plugins: { json }, language: "json/json", extends: ["json/recommended"] },
{ files: ["**/*.jsonc"], plugins: { json }, language: "json/jsonc", extends: ["json/recommended"] },
{
rules: {
"@typescript-eslint/no-explicit-any": "warn",
'@typescript-eslint/no-unused-vars': [
'error',
{
args: 'all',
argsIgnorePattern: '^_',
caughtErrors: 'all',
caughtErrorsIgnorePattern: '^_',
destructuredArrayIgnorePattern: '^_',
varsIgnorePattern: '^_',
ignoreRestSiblings: true,
},
],
}
}
]);
7 changes: 7 additions & 0 deletions examples/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extension": ["js", "ts"],
"require": ["./register-loader.js", "ts-node/register"],
"node-option": [
"experimental-specifier-resolution=node"
]
}
56 changes: 56 additions & 0 deletions examples/cdt/cdt.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#include <eosio/eosio.hpp>

#include <string>

using namespace eosio;

class [[eosio::contract]] cdt : public contract {
public:
using contract::contract;

struct [[eosio::table]] data {
name owner;
int64_t value;

uint64_t primary_key() const { return owner.value; }
uint64_t by_value() const { return (uint64_t)value; }
};

typedef multi_index<"data"_n, data,
indexed_by<"byvalue"_n, const_mem_fun<data, uint64_t, &data::by_value>>
> data_index;

[[eosio::action]]
void store(name owner, int64_t value)
{
require_auth(owner);

// const char* messages are reported through eosio_assert
check(value >= 0, "require non-negative value");
// std::string messages are reported through eosio_assert_message
check(value <= 100, std::string("value is out of range"));

data_index di(get_self(), get_self().value);
auto it = di.find(owner.value);

if (it == di.end()) {
di.emplace(owner, [&](auto& d) {
d.owner = owner;
d.value = value;
});
} else {
di.modify(it, same_payer, [&](auto& d) {
d.value = value;
});
}
}

// Action return values are serialized with set_action_return_value and
// declared as action_results in the ABI, which CDT emits at version 1.2.
[[eosio::action]]
int64_t sum(int64_t a, int64_t b)
{
print(a + b);
return a + b;
}
};
Loading
Loading