diff --git a/src/client/requestManager.ts b/src/client/requestManager.ts index 4d0bb34f..74f2fc0a 100644 --- a/src/client/requestManager.ts +++ b/src/client/requestManager.ts @@ -68,6 +68,7 @@ export default class RequestManager { throw new Error(`No existing promise with id ${id}`) } clearTimeout(promise.timer) + // TODO: figure out how to have a better stack trace for an error promise.reject(error) this.deletePromise(id) } diff --git a/src/models/utils/index.ts b/src/models/utils/index.ts index 38b0ab3a..def2d01e 100644 --- a/src/models/utils/index.ts +++ b/src/models/utils/index.ts @@ -58,7 +58,7 @@ export function setTransactionFlagsToNumber(tx: Transaction): void { switch (tx.TransactionType) { case 'AccountSet': - tx.Flags =convertAccountSetFlagsToNumber(tx.Flags) + tx.Flags = convertAccountSetFlagsToNumber(tx.Flags) return case 'OfferCreate': tx.Flags = convertOfferCreateFlagsToNumber(tx.Flags) @@ -77,11 +77,15 @@ export function setTransactionFlagsToNumber(tx: Transaction): void { } } -function convertAccountSetFlagsToNumber(flags: AccountSetFlagsInterface): number { +function convertAccountSetFlagsToNumber( + flags: AccountSetFlagsInterface, +): number { return reduceFlags(flags, AccountSetTransactionFlags) } -function convertOfferCreateFlagsToNumber(flags: OfferCreateFlagsInterface): number { +function convertOfferCreateFlagsToNumber( + flags: OfferCreateFlagsInterface, +): number { return reduceFlags(flags, OfferCreateTransactionFlags) } diff --git a/src/utils/timeConversion.ts b/src/utils/timeConversion.ts index afe84697..4e277c9e 100644 --- a/src/utils/timeConversion.ts +++ b/src/utils/timeConversion.ts @@ -6,7 +6,7 @@ const RIPPLE_EPOCH_DIFF = 0x386d4380 * @param rpepoch - (seconds since 1/1/2000 GMT). * @returns Milliseconds since unix epoch. */ - function rippleToUnixTimestamp(rpepoch: number): number { +function rippleToUnixTimestamp(rpepoch: number): number { return (rpepoch + RIPPLE_EPOCH_DIFF) * 1000 } @@ -40,7 +40,4 @@ function ISOTimeToRippleTime(iso8601: string): number { return unixToRippleTimestamp(Date.parse(iso8601)) } -export { - rippleTimeToISOTime, - ISOTimeToRippleTime, -} +export { rippleTimeToISOTime, ISOTimeToRippleTime } diff --git a/test/browser/browserIntegration.ts b/test/browser/browserIntegration.ts index c24aae29..aa28ca72 100644 --- a/test/browser/browserIntegration.ts +++ b/test/browser/browserIntegration.ts @@ -1,3 +1,5 @@ +import path from 'path' + import { expect, assert } from 'chai' import puppeteer from 'puppeteer' @@ -6,7 +8,9 @@ describe('Browser Tests', function () { const browser = await puppeteer.launch({ headless: true }) try { const page = await browser.newPage().catch() - await page.goto(`file:///${__dirname}/../localIntegrationRunner.html`) + await page.goto( + path.join('file:///', __dirname, '../localIntegrationRunner.html'), + ) await page.waitForFunction( 'document.querySelector("body").innerText.includes("submit multisigned transaction")', @@ -26,6 +30,7 @@ describe('Browser Tests', function () { expect(fails).to.equal('failures: 0') expect(passes).to.not.equal('passes: 0') } catch (err) { + // eslint-disable-next-line no-console -- only prints if something goes wrong console.log(err) assert(false) } finally { diff --git a/test/integration/README.md b/test/integration/README.md index 23a091a5..8a68a91e 100644 --- a/test/integration/README.md +++ b/test/integration/README.md @@ -3,3 +3,6 @@ To run integration tests: * With docker, run `docker run -p 6006:6006 -it natenichols/rippled-standalone:latest` * Or [download and build rippled](https://xrpl.org/install-rippled.html) and run `./rippled -a` 2. Run `yarn test:integration` or `yarn test:browser` + +When editing integration tests: +* All imports should be from `xrpl-local` instead of `../../src` (browser tests need this) diff --git a/test/integration/integration.ts b/test/integration/integration.ts index 7eec0d7e..dc7f1050 100644 --- a/test/integration/integration.ts +++ b/test/integration/integration.ts @@ -1,439 +1,53 @@ import assert from 'assert' import _ from 'lodash' -import { isValidXAddress } from 'ripple-address-codec' +import { encode } from 'ripple-binary-codec' -import { Client } from 'xrpl-local' +import { Client, Wallet } from 'xrpl-local' +import { AccountSet, SignerListSet } from 'xrpl-local/models/transactions' +import { convertStringToHex } from 'xrpl-local/utils' +import { sign, multisign } from 'xrpl-local/wallet/signer' + +import serverUrl from './serverUrl' +import { setupClient, suiteClientSetup, teardownClient } from './setup' import { - AccountSet, - OfferCreate, - SignerListSet, - TrustSet, -} from 'xrpl-local/models/transactions' -import { - isValidSecret, - generateXAddress, - xrpToDrops, - convertStringToHex, -} from 'xrpl-local/utils' - -// import requests from '../fixtures/requests' - -import { payTo, ledgerAccept } from './utils' -import wallet from './wallet' + ledgerAccept, + testTransaction, + verifySubmittedTransaction, + fundAccount, +} from './utils' // how long before each test case times out const TIMEOUT = 20000 -const INTERVAL = 1000 // how long to wait between checks for validated ledger - -const HOST = process.env.HOST ?? '0.0.0.0' -const PORT = process.env.PORT ?? '6006' -const serverUrl = `ws://${HOST}:${PORT}` - -console.log(serverUrl) - -function acceptLedger(client) { - return client.connection.request({ command: 'ledger_accept' }) -} - -function verifyTransaction(testcase, hash, type, options, txData, account) { - console.log('VERIFY...') - return testcase.client - .request({ - command: 'tx', - transaction: hash, - min_ledger: options.minLedgerVersion, - max_ledger: options.maxLedgerVersion, - }) - .then((data) => { - assert(data && data.result) - assert.strictEqual(data.result.TransactionType, type) - assert.strictEqual(data.result.Account, account) - assert.strictEqual(data.result.meta.TransactionResult, 'tesSUCCESS') - if (testcase.transactions != null) { - testcase.transactions.push(hash) - } - return { txJSON: JSON.stringify(txData), id: hash, tx: data } - }) - .catch((error) => { - console.log(error.stack) - assert(false, `Transaction not successful: ${error.message}`) - }) -} - -function testTransaction( - testcase, - type, - lastClosedLedgerVersion, - prepared, - address = wallet.getAddress(), - secret = wallet.getSecret(), -) { - const txJSON = prepared.txJSON - assert(txJSON, 'missing txJSON') - const txData = JSON.parse(txJSON) - assert.strictEqual(txData.Account, address) - const signedData = testcase.client.sign(txJSON, secret) - console.log('PREPARED...') - return testcase.client - .request({ command: 'submit', tx_blob: signedData.signedTransaction }) - .then((response) => - testcase.test.title.indexOf('multisign') !== -1 - ? acceptLedger(testcase.client).then(() => response) - : response, - ) - .then(async (response) => { - console.log('SUBMITTED...') - assert.strictEqual(response.result.engine_result, 'tesSUCCESS') - const options = { - minLedgerVersion: lastClosedLedgerVersion, - maxLedgerVersion: txData.LastLedgerSequence, - } - ledgerAccept(testcase.client) - return new Promise((resolve, reject) => { - setTimeout( - () => - verifyTransaction( - testcase, - signedData.id, - type, - options, - txData, - address, - ).then(resolve, reject), - INTERVAL, - ) - }) - }) -} - -function setup(this: any, server = serverUrl) { - this.client = new Client(server) - console.log('CONNECTING...') - return this.client.connect().then( - () => { - console.log('CONNECTED...') - }, - (error) => { - console.log('ERROR:', error) - throw error - }, - ) -} - -const masterAccount = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh' -const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb' - -function makeTrustLine(testcase, address, secret) { - const client = testcase.client - const trustSet: TrustSet = { - TransactionType: 'TrustSet', - Account: address, - LimitAmount: { - value: '1341.1', - issuer: masterAccount, - currency: 'USD', - }, - Flags: 0x00020000, - } - const trust = client - .autofill(trustSet) - .then(async (tx) => { - const signed = client.sign(JSON.stringify(tx), secret) - if (address === wallet.getAddress()) { - testcase.transactions.push(signed.id) - } - return client.request({ - command: 'submit', - tx_blob: signed.signedTransaction, - }) - }) - .then((response) => { - if ( - response.result.engine_result !== 'tesSUCCESS' && - response.result.engine_result !== 'tecPATH_PARTIAL' - ) { - console.log(response) - assert.fail(`Response not successful, ${response.result.engine_result}`) - } - ledgerAccept(client) - }) - return trust -} - -function makeOrder(client, offerCreate, secret) { - return client - .autofill(offerCreate) - .then((tx) => client.sign(JSON.stringify(tx), secret)) - .then((signed) => - client.request({ command: 'submit', tx_blob: signed.signedTransaction }), - ) - .then((response) => { - if ( - response.result.engine_result !== 'tesSUCCESS' && - response.result.engine_result !== 'tecPATH_PARTIAL' - ) { - console.log(response) - assert.fail(`Response not successful, ${response.result.engine_result}`) - } - ledgerAccept(client) - }) -} - -function setupAccounts(testcase) { - const client = testcase.client - let fundAmount = '20' - - const promise = client - .request({ command: 'server_info' }) - .then( - (response) => - (fundAmount = xrpToDrops( - Number(response.result.info.validated_ledger.reserve_base_xrp) * 2, - )), - ) - .then(() => payTo(client, 'rMH4UxPrbuMa1spCBR98hLLyNJp4d8p4tM', fundAmount)) - .then(() => payTo(client, wallet.getAddress(), fundAmount)) - .then(() => payTo(client, testcase.newWallet.classicAddress, fundAmount)) - .then(() => payTo(client, 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc', fundAmount)) - .then(() => payTo(client, 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q', fundAmount)) - .then(() => { - const accountSet: AccountSet = { - TransactionType: 'AccountSet', - Account: masterAccount, - // default ripple - SetFlag: 8, - } - return client - .autofill(accountSet) - .then((tx) => client.sign(JSON.stringify(tx), masterSecret)) - .then((signed) => - client.request({ - command: 'submit', - tx_blob: signed.signedTransaction, - }), - ) - .then(() => ledgerAccept(client)) - }) - .then(() => - makeTrustLine(testcase, wallet.getAddress(), wallet.getSecret()), - ) - .then(() => - makeTrustLine( - testcase, - testcase.newWallet.xAddress, - testcase.newWallet.secret, - ), - ) - .then(() => payTo(client, wallet.getAddress(), '123', 'USD', masterAccount)) - .then(() => payTo(client, 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q')) - .then(() => { - const offerCreate: OfferCreate = { - TransactionType: 'OfferCreate', - Account: testcase.newWallet.xAddress, - TakerPays: { - currency: 'USD', - value: '432', - issuer: masterAccount, - }, - TakerGets: xrpToDrops('432'), - } - return makeOrder(testcase.client, offerCreate, testcase.newWallet.secret) - }) - .then(() => { - const offerCreate: OfferCreate = { - TransactionType: 'OfferCreate', - Account: masterAccount, - TakerPays: xrpToDrops('1741'), - TakerGets: { - currency: 'USD', - value: '171', - issuer: masterAccount, - }, - } - return makeOrder(testcase.client, offerCreate, masterSecret) - }) - return promise -} - -function teardown(this: any) { - return this.client.disconnect() -} - -function suiteSetup(this: any) { - this.transactions = [] - - return ( - setup - .bind(this)(serverUrl) - .then(() => ledgerAccept(this.client)) - .then( - () => - (this.newWallet = generateXAddress({ includeClassicAddress: true })), - ) - // two times to give time to server to send `ledgerClosed` event - // so getLedgerVersion will return right value - .then(() => ledgerAccept(this.client)) - .then(() => - this.client - .request({ - command: 'ledger', - ledger_index: 'validated', - }) - .then((response) => response.result.ledger_index), - ) - .then((ledgerVersion) => { - this.startLedgerVersion = ledgerVersion - }) - .then(() => setupAccounts(this)) - .then(() => teardown.bind(this)()) - ) -} describe('integration tests', function () { - const address = wallet.getAddress() this.timeout(TIMEOUT) - before(suiteSetup) - beforeEach(_.partial(setup, serverUrl)) - afterEach(teardown) + before(suiteClientSetup) + beforeEach(_.partial(setupClient, serverUrl)) + afterEach(teardownClient) it('isConnected', function () { assert(this.client.isConnected()) }) - it('getFee', function () { - return this.client.getFee().then((fee) => { - assert.strictEqual(typeof fee, 'string') - assert(!isNaN(Number(fee))) - assert(parseFloat(fee) === Number(fee)) + it('submit multisigned transaction', async function () { + const client: Client = this.client + const multisignAccount = 'r5nx8ZkwEbFztnc8Qyi22DE9JYjRzNmvs' + const multisignSecret = 'ss6F8381Br6wwpy9p582H8sBt19J3' + const signer1address = 'rQDhz2ZNXmhxzCYwxU6qAbdxsHA4HV45Y2' + const signer1secret = 'shK6YXzwYfnFVn3YZSaMh5zuAddKx' + const signer2address = 'r3RtUvGw9nMoJ5FuHxuoVJvcENhKtuF9ud' + const signer2secret = 'shUHQnL4EH27V4EiBrj6EfhWvZngF' + await fundAccount(client, multisignAccount) + + const ledgerResponse = await client.request({ + command: 'ledger', + ledger_index: 'validated', }) - }) + const minLedgerVersion = ledgerResponse.result.ledger_index - // it('getTrustlines', function () { - // const fixture = requests.prepareTrustline.simple - // const { currency, counterparty } = fixture - // const options = { currency, counterparty } - // return this.client.getTrustlines(address, options).then((data) => { - // assert(data && data.length > 0 && data[0] && data[0].specification) - // const specification = data[0].specification - // assert.strictEqual(Number(specification.limit), Number(fixture.limit)) - // assert.strictEqual(specification.currency, fixture.currency) - // assert.strictEqual(specification.counterparty, fixture.counterparty) - // }) - // }) - - // it('getBalances', function () { - // const fixture = requests.prepareTrustline.simple - // const { currency, counterparty } = fixture - // const options = { currency, counterparty } - // return this.client.getBalances(address, options).then((data) => { - // assert(data && data.length > 0 && data[0]) - // assert.strictEqual(data[0].currency, fixture.currency) - // assert.strictEqual(data[0].counterparty, fixture.counterparty) - // }) - // }) - - it('getOrderbook', function () { - const orderbook = { - base: { - currency: 'XRP', - }, - counter: { - currency: 'USD', - counterparty: masterAccount, - }, - } - return this.client.getOrderbook(address, orderbook).then((book) => { - assert(book && book.bids && book.bids.length > 0) - assert(book.asks && book.asks.length > 0) - const bid = book.bids[0] - assert(bid && bid.specification && bid.specification.quantity) - assert(bid.specification.totalPrice) - assert.strictEqual(bid.specification.direction, 'buy') - assert.strictEqual(bid.specification.quantity.currency, 'XRP') - assert.strictEqual(bid.specification.totalPrice.currency, 'USD') - const ask = book.asks[0] - assert(ask && ask.specification && ask.specification.quantity) - assert(ask.specification.totalPrice) - assert.strictEqual(ask.specification.direction, 'sell') - assert.strictEqual(ask.specification.quantity.currency, 'XRP') - assert.strictEqual(ask.specification.totalPrice.currency, 'USD') - }) - }) - - // it('getPaths', function () { - // const pathfind = { - // source: { - // address: address - // }, - // destination: { - // address: this.newWallet.address, - // amount: { - // value: '1', - // currency: 'USD', - // counterparty: masterAccount - // } - // } - // } - // return this.client.getPaths(pathfind).then((data) => { - // assert(data && data.length > 0) - // const path = data[0] - // assert(path && path.source) - // assert.strictEqual(path.source.address, address) - // assert(path.paths && path.paths.length > 0) - // }) - // }) - - // it('getPaths - send all', function () { - // const pathfind = { - // source: { - // address: address, - // amount: { - // currency: 'USD', - // value: '0.005' - // } - // }, - // destination: { - // address: this.newWallet.address, - // amount: { - // currency: 'USD' - // } - // } - // } - - // return this.client.getPaths(pathfind).then((data) => { - // assert(data && data.length > 0) - // assert( - // data.every((path) => { - // return ( - // parseFloat(path.source.amount.value) <= - // parseFloat(pathfind.source.amount.value) - // ) - // }) - // ) - // const path = data[0] - // assert(path && path.source) - // assert.strictEqual(path.source.address, pathfind.source.address) - // assert(path.paths && path.paths.length > 0) - // }) - // }) - - it('generateWallet', function () { - const newWallet = generateXAddress() - assert(newWallet && newWallet.xAddress && newWallet.secret) - assert(isValidXAddress(newWallet.xAddress)) - assert(isValidSecret(newWallet.secret)) - }) - - const multisignAccount = 'r5nx8ZkwEbFztnc8Qyi22DE9JYjRzNmvs' - const multisignSecret = 'ss6F8381Br6wwpy9p582H8sBt19J3' - const signer1address = 'rQDhz2ZNXmhxzCYwxU6qAbdxsHA4HV45Y2' - const signer1secret = 'shK6YXzwYfnFVn3YZSaMh5zuAddKx' - const signer2address = 'r3RtUvGw9nMoJ5FuHxuoVJvcENhKtuF9ud' - const signer2secret = 'shUHQnL4EH27V4EiBrj6EfhWvZngF' - - it('submit multisigned transaction', function () { + // set up the multisigners for the account const signerListSet: SignerListSet = { TransactionType: 'SignerListSet', Account: multisignAccount, @@ -453,94 +67,33 @@ describe('integration tests', function () { ], SignerQuorum: 2, } - let minLedgerVersion = null - let fundAmount = '20' - return this.client - .request({ command: 'server_info' }) - .then( - (response) => - (fundAmount = xrpToDrops( - Number(response.result.info.validated_ledger.reserve_base_xrp) * 2, - )), - ) - .then(() => - payTo(this.client, multisignAccount, fundAmount) - .then(() => { - return this.client - .request({ - command: 'ledger', - ledger_index: 'validated', - }) - .then((response) => response.result.ledger_index) - .then((ledgerVersion) => { - minLedgerVersion = ledgerVersion - }) - .then(() => this.client.autofill(signerListSet, 2)) - .then((tx) => { - return testTransaction( - this, - 'SignerListSet', - minLedgerVersion, - { txJSON: JSON.stringify(tx) }, - multisignAccount, - multisignSecret, - ) - }) - }) - .then(() => { - const accountSet: AccountSet = { - TransactionType: 'AccountSet', - Account: multisignAccount, - Domain: convertStringToHex('example.com'), - } - return this.client.autofill(accountSet, 2).then((tx) => { - const signed1 = this.client.sign( - JSON.stringify(tx), - signer1secret, - { - signAs: signer1address, - }, - ) - const signed2 = this.client.sign( - JSON.stringify(tx), - signer2secret, - { - signAs: signer2address, - }, - ) - const combined = this.client.combine([ - signed1.signedTransaction, - signed2.signedTransaction, - ]) - return this.client - .request({ - command: 'submit', - tx_blob: combined.signedTransaction, - }) - .then((response) => - acceptLedger(this.client).then(() => response), - ) - .then((response) => { - assert.strictEqual( - response.result.engine_result, - 'tesSUCCESS', - ) - const options = { minLedgerVersion } - return verifyTransaction( - this, - combined.id, - 'AccountSet', - options, - {}, - multisignAccount, - ) - }) - .catch((error) => { - console.log(error.message) - throw error - }) - }) - }), - ) + const tx = await client.autofill(signerListSet, 2) + await testTransaction( + this, + minLedgerVersion, + tx, + multisignAccount, + multisignSecret, + ) + + // try to multisign + const accountSet: AccountSet = { + TransactionType: 'AccountSet', + Account: multisignAccount, + Domain: convertStringToHex('example.com'), + } + const accountSetTx = await client.autofill(accountSet, 2) + const signed1 = sign(Wallet.fromSeed(signer1secret), accountSetTx, true) + const signed2 = sign(Wallet.fromSeed(signer2secret), accountSetTx, true) + const combined = multisign([signed1, signed2]) + // TODO: replace with `client.submitSignedTransaction` + const submitResponse = await client.request({ + command: 'submit', + tx_blob: encode(combined), + }) + await ledgerAccept(client) + assert.strictEqual(submitResponse.result.engine_result, 'tesSUCCESS') + const options = { minLedgerVersion } + await verifySubmittedTransaction(this, combined, options) }) }) diff --git a/test/integration/serverUrl.ts b/test/integration/serverUrl.ts new file mode 100644 index 00000000..98adbc3d --- /dev/null +++ b/test/integration/serverUrl.ts @@ -0,0 +1,6 @@ +/* eslint-disable node/no-process-env -- needed to find standalone connection */ +const HOST = process.env.HOST ?? '0.0.0.0' +const PORT = process.env.PORT ?? '6006' +const serverUrl = `ws://${HOST}:${PORT}` + +export default serverUrl diff --git a/test/integration/setup.ts b/test/integration/setup.ts new file mode 100644 index 00000000..76cc9d31 --- /dev/null +++ b/test/integration/setup.ts @@ -0,0 +1,36 @@ +import { generateXAddress, Client } from 'xrpl-local' + +import serverUrl from './serverUrl' +import { ledgerAccept } from './utils' + +export async function teardownClient(this: Mocha.Context): Promise { + this.client.disconnect() +} + +export async function suiteClientSetup(this: Mocha.Context): Promise { + this.transactions = [] + + await setupClient.bind(this)(serverUrl) + ledgerAccept(this.client) + this.newWallet = generateXAddress({ includeClassicAddress: true }) + // two times to give time to server to send `ledgerClosed` event + // so getLedgerVersion will return right value + await ledgerAccept(this.client) + const response = await this.client.request({ + command: 'ledger', + ledger_index: 'validated', + }) + const ledgerVersion = response.result.ledger_index + this.startLedgerVersion = ledgerVersion + await teardownClient.bind(this)() +} + +export async function setupClient( + this: Mocha.Context, + server = serverUrl, +): Promise { + return new Promise((resolve, reject) => { + this.client = new Client(server) + this.client.connect().then(resolve).catch(reject) + }) +} diff --git a/test/integration/utils.js b/test/integration/utils.js deleted file mode 100644 index 5e7333da..00000000 --- a/test/integration/utils.js +++ /dev/null @@ -1,80 +0,0 @@ -const assert = require('chai').assert - -const models = require('xrpl-local/models/transactions') -const utils = require('xrpl-local/utils') - -const masterAccount = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh' -const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb' - -function ledgerAccept(client) { - const request = { command: 'ledger_accept' } - return client.connection.request(request) -} - -function pay(client, from, to, amount, secret, currency = 'XRP', issuer) { - const paymentAmount = - currency === 'XRP' ? amount : { value: amount, currency, issuer } - - const payment = { - TransactionType: 'Payment', - Account: from, - Destination: to, - Amount: paymentAmount, - } - - let id = null - return ( - client - .autofill(payment, 1) - .then((tx) => { - models.verifyPayment(payment) - return client.sign(JSON.stringify(tx), secret) - }) - .then((signed) => { - id = signed.id - return client.request({ - command: 'submit', - tx_blob: signed.signedTransaction, - }) - }) - // TODO: add better error handling here - // TODO: fix path issues - .then((response) => { - if ( - response.result.engine_result !== 'tesSUCCESS' && - response.result.engine_result !== 'tecPATH_PARTIAL' - ) { - console.log(response) - assert.fail( - `Response not successful, ${response.result.engine_result}`, - ) - } - ledgerAccept(client) - }) - .then(() => id) - ) -} - -function payTo( - client, - to, - amount = '40000000', - currency = 'XRP', - counterparty, -) { - return pay( - client, - masterAccount, - to, - amount, - masterSecret, - currency, - counterparty, - ) -} - -module.exports = { - pay, - payTo, - ledgerAccept, -} diff --git a/test/integration/utils.ts b/test/integration/utils.ts new file mode 100644 index 00000000..753e7b04 --- /dev/null +++ b/test/integration/utils.ts @@ -0,0 +1,135 @@ +/* eslint-disable max-params -- helper test functions */ +import { assert } from 'chai' +import _ from 'lodash' +import { decode } from 'ripple-binary-codec' + +import { Client, SubmitResponse, Wallet } from 'xrpl-local' +import { BaseResponse } from 'xrpl-local/models/methods/baseMethod' +import { + verifyPayment, + Payment, + Transaction, +} from 'xrpl-local/models/transactions' +import { computeSignedTransactionHash } from 'xrpl-local/utils/hashes' +import { sign } from 'xrpl-local/wallet/signer' + +import { walletAddress, walletSecret } from './wallet' + +const masterAccount = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh' +const masterSecret = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb' + +interface LedgerAcceptResponse extends BaseResponse { + result: { + ledger_current_index: number + } +} + +export async function ledgerAccept( + client: Client, +): Promise { + const request = { command: 'ledger_accept' } + return client.connection.request(request) as Promise +} + +// TODO: replace with `client.submitTransaction` once that has been merged +export async function submitTransaction( + client: Client, + secret: string, + transaction: Transaction, +): Promise { + const wallet = Wallet.fromSeed(secret) + const tx = await client.autofill(transaction) + const signedTxEncoded: string = sign(wallet, tx) + return client.request({ command: 'submit', tx_blob: signedTxEncoded }) +} + +export async function fundAccount( + client: Client, + account: string, +): Promise { + const payment: Payment = { + TransactionType: 'Payment', + Account: masterAccount, + Destination: account, + // 2 times the amount needed for a new account (20 XRP) + Amount: '400000000', + } + const paymentTx = await client.autofill(payment) + verifyPayment(paymentTx) + + const response = await submitTransaction(client, masterSecret, paymentTx) + if (response.result.engine_result !== 'tesSUCCESS') { + // eslint-disable-next-line no-console -- happens only when something goes wrong + console.log(response) + assert.fail(`Response not successful, ${response.result.engine_result}`) + } + + await ledgerAccept(client) +} + +export async function verifySubmittedTransaction( + testcase: Mocha.Context, + tx: Transaction, + options: { minLedgerVersion: number; maxLedgerVersion?: number }, +): Promise { + const hash = computeSignedTransactionHash(tx) + const data = await testcase.client.request({ + command: 'tx', + transaction: hash, + min_ledger: options.minLedgerVersion, + max_ledger: options.maxLedgerVersion, + }) + + assert(data.result) + assert.deepEqual( + _.omit(data.result, [ + 'date', + 'hash', + 'inLedger', + 'ledger_index', + 'meta', + 'validated', + ]), + tx, + ) + if (typeof data.result.meta === 'object') { + assert.strictEqual(data.result.meta.TransactionResult, 'tesSUCCESS') + } else { + assert.strictEqual(data.result.meta, 'tesSUCCESS') + } + if (testcase.transactions != null) { + testcase.transactions.push(hash) + } +} + +export async function testTransaction( + testcase: Mocha.Context, + lastClosedLedgerVersion: number, + txData: Transaction, + address = walletAddress, + secret = walletSecret, +): Promise { + assert.strictEqual(txData.Account, address) + const client: Client = testcase.client + const signedData = sign(Wallet.fromSeed(secret), txData) + + const attemptedResponse = await client.request({ + command: 'submit', + tx_blob: signedData, + }) + const submittedResponse = testcase.test?.title.includes('multisign') + ? await ledgerAccept(client).then(() => attemptedResponse) + : attemptedResponse + + assert.strictEqual(submittedResponse.result.engine_result, 'tesSUCCESS') + const options = { + minLedgerVersion: lastClosedLedgerVersion, + maxLedgerVersion: txData.LastLedgerSequence, + } + await ledgerAccept(testcase.client) + await verifySubmittedTransaction( + testcase, + decode(signedData) as unknown as Transaction, + options, + ) +} diff --git a/test/integration/wallet.js b/test/integration/wallet.js deleted file mode 100644 index bc37d674..00000000 --- a/test/integration/wallet.js +++ /dev/null @@ -1,14 +0,0 @@ -'use strict'; - -function getAddress() { - return 'rQDhz2ZNXmhxzCYwxU6qAbdxsHA4HV45Y2'; -} - -function getSecret() { - return 'shK6YXzwYfnFVn3YZSaMh5zuAddKx'; -} - -module.exports = { - getAddress, - getSecret -}; diff --git a/test/integration/wallet.ts b/test/integration/wallet.ts new file mode 100644 index 00000000..d40e5c65 --- /dev/null +++ b/test/integration/wallet.ts @@ -0,0 +1,2 @@ +export const walletAddress = 'rQDhz2ZNXmhxzCYwxU6qAbdxsHA4HV45Y2' +export const walletSecret = 'shK6YXzwYfnFVn3YZSaMh5zuAddKx' diff --git a/test/models/utils.ts b/test/models/utils.ts index 7bae0d98..23a9642e 100644 --- a/test/models/utils.ts +++ b/test/models/utils.ts @@ -131,7 +131,8 @@ describe('Models Utils', function () { }, } - const { tfSetfAuth, tfClearNoRipple, tfClearFreeze } = TrustSetTransactionFlags + const { tfSetfAuth, tfClearNoRipple, tfClearFreeze } = + TrustSetTransactionFlags const expected: number = tfSetfAuth | tfClearNoRipple | tfClearFreeze setTransactionFlagsToNumber(tx)