Skip to content
Merged
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
22 changes: 17 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,24 @@ The focus of VeRT is on the better compatibility than the performance, so it can
## Chain compatibility

Antelope chains do not all expose the same host functions, and a harness that offers more than the
target chain will link a contract that the chain rejects at `setcode`.
target chain will link a contract that the chain rejects at `setcode`. To keep a passing suite
meaningful, chain-specific host functions are withheld unless the emulated chain provides them.

`verify_rsa_sha256_sig` is currently registered for every `Blockchain`. It exists on WAX; it does not
exist on EOS, Jungle4, or Vaulta. A contract that calls it therefore passes here and fails to load on
those chains. Until the host function set is selectable per chain, treat a passing suite as evidence
for WAX only when RSA is involved.
A `Blockchain` emulates generic Antelope by default, which exposes no chain-specific host functions.
Name a chain to add the ones unique to it:

```typescript
const bc = new Blockchain(); // generic Antelope
const wax = new Blockchain({ chain: 'wax' }); // adds verify_rsa_sha256_sig
```

`verify_rsa_sha256_sig` exists on WAX and not on EOS, Jungle4, or Vaulta. A contract that imports it
instantiates under a `wax` blockchain and fails to instantiate under any other, which mirrors how
`setcode` accepts it on WAX and rejects it elsewhere. Test WAX contracts that use RSA against a
`wax` blockchain, and test everything else against the default.

The chain-specific host functions are declared in `CHAIN_SPECIFIC_HOST_FUNCTIONS`; add an entry
there to model a new one.

## Installation

Expand Down
2 changes: 1 addition & 1 deletion examples/rsa/rsa.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ const modulus1 =
'b67b5732be0888309dfba35e310eee09641a3f609ec94fdfb45aeaec1231e08268f2a065fffb00aa41eaec560af2bedc0d48cd647b89a8a44b4e0a5fef365640ad379d05112e063467f973c0053657534b1c76cbed8aae705d3453b1581b6badbff41ea2ff5a84e84b06e4293978f7d5389180803f5b27c13290f209c647ee0a8de4184d39f6d4e66a01ffd13ac0740a997b9e05023a51b9c281485685c0cfe3743dbc788cc3aac31c2f35a53414ff236ed2a998aa3617f3bda2f6163aa5254cf60f7d73b4d553b1d2fbd057299a297832cd9e8d2a1786b4260188889e9f7dd713dc1c22c6dda8e001ed76114e41529caa575ff6bc54a79d7ed6f6442b9fe84712ec2bae06560eb3fe40292143f69ae67e72ef7a010d95879df4edfb0ed74a2a7b9aeaade0c02a73a9a27c710dba0020891a9585cae9b6937f82c56c20017107990101a86c71b6c759abc5be23eb790c795e138363c40c29c8ec0fae65ad1de30bd2a5b0bbedc633caf21a8eae0d5afced68fb1a2a1cf5a175d5207ffcfad69de17cb839ab82f6ac1833fbe641eb869be9d9cd5e742bd79b7472eed3d39956c4b5eb9578cf92ba9202ddab1b0f81dc05c85380fb85a67adc88ae295de66cdc2977c2f6273acd65f234684cb9b5e60ab75cb6f433eb12961afe295247d7819d5ba4213d4902039234506f5109534734e65c28e2a8078afc3b59b92e7f329f791b';

// Initialize blockchain
const blockchain = new Blockchain();
const blockchain = new Blockchain({ chain: 'wax' }); // verify_rsa_sha256_sig is a WAX host function

// Create RSA contract
const contractName = Name.from('rsa');
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@atomichub/vert",
"version": "2.1.1",
"version": "2.2.0",
"homepage": "https://github.com/atomicassets/vert",
"description": "Testing library for Antelope smart contracts, with per-chain host function parity",
"main": "dist/index.js",
Expand Down
32 changes: 30 additions & 2 deletions src/antelope/blockchain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,20 @@ import colors from 'colors'
import { ACTIVATED_PROTOCOL_FEATURES } from "../utils/activatedFeatures";
import Buffer from '../buffer'

// Host functions that only some Antelope chains expose. A contract importing one
// fails to load (setcode) on a chain that lacks it, so the VM must withhold it
// on those chains too, or a passing test would not reflect the target chain.
// Keyed by chain, valued by the host functions unique to it.
export const CHAIN_SPECIFIC_HOST_FUNCTIONS: Record<string, readonly string[]> = {
wax: ['verify_rsa_sha256_sig'],
}

// The union of every chain-specific host function, used by the VM to withhold
// any that the target chain does not provide.
export const ALL_CHAIN_SPECIFIC_HOST_FUNCTIONS: ReadonlySet<string> = new Set(
Object.values(CHAIN_SPECIFIC_HOST_FUNCTIONS).flat()
)

export class Blockchain {
accounts: { [key: string]: Account }
timestamp: TimePoint
Expand All @@ -30,22 +44,36 @@ export class Blockchain {
postStorage: any
storageDeltaChangesets: any
_storageDeltas: any


// The chain being emulated, if any. Undefined means generic Antelope, which
// exposes no chain-specific host functions. Naming a chain (e.g. 'wax') adds
// the host functions unique to it.
chain?: string

constructor ({
accounts,
timestamp,
blockNum,
store,
chain,
}: {
accounts?: { [key: string]: Account },
timestamp?: TimePoint,
blockNum?: number,
store?: TableStore
store?: TableStore,
chain?: string
} = {}) {
this.accounts = accounts || {}
this.timestamp = timestamp || TimePoint.fromMilliseconds(0)
this.blockNum = blockNum || 0
this.store = store || new TableStore()
this.chain = chain
}

// The chain-specific host functions this chain provides. Generic Antelope
// provides none.
enabledChainHostFunctions (): ReadonlySet<string> {
return new Set(this.chain ? (CHAIN_SPECIFIC_HOST_FUNCTIONS[this.chain] ?? []) : [])
}

private applyTransactionActions(transaction: Transaction, decodedData?: any) {
Expand Down
36 changes: 33 additions & 3 deletions src/antelope/tests/vm.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,23 @@ const NodeRSA = require('node-rsa');
const crypto = require('crypto');

const bc = new Blockchain()
// A WAX chain exposes verify_rsa_sha256_sig, which generic Antelope withholds.
const waxBc = new Blockchain({ chain: 'wax' })

let vm;
let waxVm;
let memory;

beforeEach(() => {
bc.clearConsole()
vm = VM.from(new Uint8Array(), bc);
waxBc.clearConsole()
memory = Memory.create(256);
vm = VM.from(new Uint8Array(), bc);
waxVm = VM.from(new Uint8Array(), waxBc);
// @ts-ignore
vm._memory = memory;
// @ts-ignore
waxVm._memory = memory;
});

describe('eos-vm imports', () => {
Expand Down Expand Up @@ -108,7 +115,7 @@ describe('eos-vm imports', () => {
const modulusLen = modulus.length;

// Call assert_valid_rsa_sig - should not throw
let valid = vm.imports.env.verify_rsa_sha256_sig(
let valid = waxVm.imports.env.verify_rsa_sha256_sig(
messageHashOffset, messageHashLen,
signatureOffset, signatureLen,
exponentOffset, exponentLen,
Expand Down Expand Up @@ -169,7 +176,7 @@ describe('eos-vm imports', () => {
const modulusLen = modulus.length;

// Call assert_invalid_rsa_sig - should not throw because signature is indeed invalid
let result = vm.imports.env.verify_rsa_sha256_sig(
let result = waxVm.imports.env.verify_rsa_sha256_sig(
messageHashOffset, messageHashLen,
signatureOffset, signatureLen,
exponentOffset, exponentLen,
Expand Down Expand Up @@ -201,6 +208,29 @@ describe('eos-vm imports', () => {
});
});

describe('chain-specific host functions', () => {
it('generic Antelope withholds verify_rsa_sha256_sig', () => {
// A contract importing it would fail to instantiate here, exactly as
// setcode rejects it on EOS, Jungle4 and Vaulta.
expect(vm.imports.env.verify_rsa_sha256_sig).to.equal(undefined);
});

it('a WAX chain provides verify_rsa_sha256_sig', () => {
expect(waxVm.imports.env.verify_rsa_sha256_sig).to.be.a('function');
});

it('an unknown chain name withholds it', () => {
const other = VM.from(new Uint8Array(), new Blockchain({ chain: 'not-a-chain' }));
expect(other.imports.env.verify_rsa_sha256_sig).to.equal(undefined);
});

it('withholding a chain-specific function does not disturb the shared ones', () => {
// recover_key exists on every chain, so it must survive the gate on both.
expect(vm.imports.env.recover_key).to.be.a('function');
expect(waxVm.imports.env.recover_key).to.be.a('function');
});
});

describe('print', () => {
it('prints', () => {
const buffer = Buffer.from_(memory.buffer);
Expand Down
12 changes: 11 additions & 1 deletion src/antelope/vm.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { Action, Name, NameType, PermissionLevel, PublicKey, Serializer, Signatu
import { sha256, sha512, sha1, ripemd160 } from "hash.js";
import { sha3_256, keccak256 } from "js-sha3"
import { bigIntToName, nameToBigInt, nameTypeToBigInt } from "./bn";
import { Blockchain } from "./blockchain";
import { Blockchain, ALL_CHAIN_SPECIFIC_HOST_FUNCTIONS } from "./blockchain";
import { Account } from "./account";
import { antelopeAssert, antelopeAssertMessage, antelopeAssertCode } from "./errors";
import { isAuthoritySatisfied } from "./utils";
Expand Down Expand Up @@ -1474,6 +1474,16 @@ class VM extends Vert {
},
};

// Withhold chain-specific host functions the target chain does not provide,
// so a contract importing one fails to instantiate here exactly as setcode
// would reject it on that chain. Generic Antelope withholds all of them.
const enabledChainHostFunctions = bc.enabledChainHostFunctions();
for (const fn of ALL_CHAIN_SPECIFIC_HOST_FUNCTIONS) {
if (!enabledChainHostFunctions.has(fn)) {
delete (imports.env as any)[fn];
}
}

super(imports, wasm);
this.imports = imports;
this.bc = bc;
Expand Down
Loading