import { z } from "zod"; import { DataParser, RJSVM_Builder, RJSVM_Implementations, RJSVM_Interface, RJSVM, RJSVM_Config } from "../src/main"; //Test requirements import { assert, expect } from 'chai'; import { describe, it } from "mocha"; import { makeTestnetWallet } from "./tools"; import { Wallet } from "xrpio"; import { Datawriter } from "../src/RJSVM/datawriter/datawriter"; import { InsufficientFeeError, RestrictedAccessError } from "../src/util/errors"; var should = require('chai').should(); var chai = require('chai'); var chaiAsPromised = require('chai-as-promised'); chai.use(chaiAsPromised); const xrpNode = "wss://s.altnet.rippletest.net:51233" let ownerWallet: Wallet //the RJSVM owner let userWallet: Wallet //a RJSVM user let listeningWallet: Wallet //wallet the RJSVM listens to let drainWallet: Wallet //random wallet to send stuff to, could be any user-controlled secondary wallet let rjsvm: RJSVM let user_datawriter: Datawriter let owner_datawriter: Datawriter const setup = async () => { [ownerWallet, listeningWallet, userWallet, drainWallet] = await Promise.all([makeTestnetWallet(), makeTestnetWallet(), makeTestnetWallet(), makeTestnetWallet()]) owner_datawriter = new Datawriter({ receiveAddress: drainWallet.address, sendWallet: ownerWallet, xrpNode: xrpNode, contractAddress: listeningWallet.address }) user_datawriter = new Datawriter({ receiveAddress: drainWallet.address, sendWallet: userWallet, xrpNode: xrpNode, contractAddress: listeningWallet.address }) //await owner_dw.callEndpoint('submit', { title: "1", body: "2", from: "3" }, 100) //await owner_dw.callEndpoint('submit', { title: "1", body: "2", from: "3" }, 100) //await user_dw.callEndpoint('restricted', { title: "user", body: "user", from: "user" }) //await owner_dw.callEndpoint('restricted', { title: "owner", body: "owner", from: "owner" }) // ######################### // Define parameter types // ######################### const shoutSchema = z.object({ title: z.string(), body: z.string(), from: z.string(), id: z.optional(z.string()) }) type Shout = z.infer type State = { shouts: Shout[] } // ######################### // Define endpoints // ######################### type RJSVM_Endpoints = { submit: (data: Shout) => void restricted: (data: Shout) => void } // ######################### // Define init state // ######################### abstract class RJSVM_Base extends RJSVM implements RJSVM_Interface { owner = ownerWallet.address state: State = { shouts: [] } } // ######################### // Implement logic // ######################### const RJSVM_Contract: RJSVM_Implementations = { submit: { implementation: function (env, shout) { this.state.shouts.unshift(shout) }, visibility: 'public', fee: 10, parameterSchema: shoutSchema }, restricted: { implementation: function (env, shout) { this.state.shouts.unshift(shout) }, visibility: 'owner', parameterSchema: shoutSchema }, } // ######################### // Build and connect // ######################### const Rjsvm = RJSVM_Builder.from(RJSVM_Base, RJSVM_Contract); const conf: RJSVM_Config = { listeningAddress: listeningWallet.address, rippleNode: xrpNode } rjsvm = new Rjsvm(conf) await rjsvm.connect() } describe('RJSVM basic functions', () => { before(async function () { this.timeout(10000) await setup() }) after(async () => { await rjsvm.disconnect() }) it('Env contains the payload carrying transaction', function (done) { this.timeout(30000) makeTestnetWallet().then(async testWallet => { //create a mock RJSVM abstract class RJSVM_Base extends RJSVM void }> implements RJSVM_Interface { owner = ownerWallet.address state = undefined } //Implementation of the 'test' endpoint const RJSVM_Contract: RJSVM_Implementations = { testEndpoint: { implementation: function (env) { expect(env.Account).to.be.equal(userWallet.address) expect(env.Amount).to.be.equal('1') expect(env.Destination).to.be.equal(testWallet.address) expect(Number(env.Fee)).to.be.greaterThanOrEqual(10) expect(env.LastLedgerSequence).to.be.a('number') expect(env.Memos).to.be.an('Array') const memo = JSON.parse(DataParser.hex_to_ascii(env.Memos[0].Memo.MemoData)) expect(memo.endpoint).to.be.equal('testEndpoint') expect(env.Sequence).to.be.a('number') expect(env.SigningPubKey).to.be.a('string') expect(env.TransactionType).to.be.equal('Payment') expect(env.TxnSignature).to.be.a('string') expect(env.date).to.be.a('number') expect(env.hash).to.be.a('string') expect(env.inLedger).to.be.a('number') expect(env.ledger_index).to.be.a('number') runnable.disconnect() done() }, visibility: 'public', parameterSchema: z.any() }, } const Rjsvm = RJSVM_Builder.from(RJSVM_Base, RJSVM_Contract) const runnable = new Rjsvm({listeningAddress: testWallet.address,rippleNode: xrpNode}) await runnable.connect() const dw = new Datawriter({ contractAddress: testWallet.address, sendWallet: userWallet, receiveAddress: drainWallet.address, xrpNode: xrpNode }) dw.callEndpoint('testEndpoint', "") }) }) it('Called endpoint triggers in RJSVM', function (done) { this.timeout(30000) const data = { title: "1", body: "2", from: "3" }; rjsvm.once('submit', (payload) => { expect(payload).to.be.an('object') expect(payload).to.deep.equal(data) done() }) owner_datawriter.callEndpoint('submit', data, 10) }) it('Calling an endpoint with insufficient fee fails', function (done) { this.timeout(30000) const data = { title: "f", body: "f", from: "f" }; rjsvm.once('error', (err) => { expect(err).to.be.instanceOf(InsufficientFeeError) done() }) owner_datawriter.callEndpoint('submit', data, 1) }) it('Restricted endpoint can be called by owner', function (done) { this.timeout(30000) const data = { title: "11", body: "22", from: "33" }; rjsvm.once('restricted', (payload) => { expect(payload).to.be.an('object') expect(payload).to.deep.equal(data) done() }) owner_datawriter.callEndpoint('restricted', data) }) it('Restricted endpoint cannot be called by non-owner', function (done) { this.timeout(30000) const data = { title: "e", body: "e", from: "e" }; rjsvm.once('error', (err) => { expect(err).to.be.instanceOf(RestrictedAccessError) done() }) user_datawriter.callEndpoint('restricted', data) }) })