convert existing xrpl library into a lerna monorepo

This commit is contained in:
Greg Weisbrod
2021-11-04 23:18:23 -04:00
parent 9647145150
commit 2b42427634
500 changed files with 24357 additions and 10294 deletions

View File

@@ -0,0 +1,26 @@
import { assert } from 'chai'
import { deriveXAddress } from 'xrpl-local'
describe('deriveXAddress', function () {
it('returns address for public key', function () {
assert.equal(
deriveXAddress({
publicKey:
'035332FBA71D705BD5D97014A833BE2BBB25BEFCD3506198E14AFEA241B98C2D06',
tag: false,
test: false,
}),
'XVZVpQj8YSVpNyiwXYSqvQoQqgBttTxAZwMcuJd4xteQHyt',
)
assert.equal(
deriveXAddress({
publicKey:
'035332FBA71D705BD5D97014A833BE2BBB25BEFCD3506198E14AFEA241B98C2D06',
tag: false,
test: true,
}),
'TVVrSWtmQQssgVcmoMBcFQZKKf56QscyWLKnUyiuZW8ALU4',
)
})
})

View File

@@ -0,0 +1,133 @@
import BigNumber from 'bignumber.js'
import { assert } from 'chai'
import { dropsToXrp } from 'xrpl-local/utils'
describe('dropsToXrp', function () {
it('works with a typical amount', function () {
const xrp = dropsToXrp('2000000')
assert.strictEqual(xrp, '2', '2 million drops equals 2 XRP')
})
it('works with fractions', function () {
let xrp = dropsToXrp('3456789')
assert.strictEqual(xrp, '3.456789', '3,456,789 drops equals 3.456789 XRP')
xrp = dropsToXrp('3400000')
assert.strictEqual(xrp, '3.4', '3,400,000 drops equals 3.4 XRP')
xrp = dropsToXrp('1')
assert.strictEqual(xrp, '0.000001', '1 drop equals 0.000001 XRP')
xrp = dropsToXrp('1.0')
assert.strictEqual(xrp, '0.000001', '1.0 drops equals 0.000001 XRP')
xrp = dropsToXrp('1.00')
assert.strictEqual(xrp, '0.000001', '1.00 drops equals 0.000001 XRP')
})
it('works with zero', function () {
let xrp = dropsToXrp('0')
assert.strictEqual(xrp, '0', '0 drops equals 0 XRP')
// negative zero is equivalent to zero
xrp = dropsToXrp('-0')
assert.strictEqual(xrp, '0', '-0 drops equals 0 XRP')
xrp = dropsToXrp('0.00')
assert.strictEqual(xrp, '0', '0.00 drops equals 0 XRP')
xrp = dropsToXrp('000000000')
assert.strictEqual(xrp, '0', '000000000 drops equals 0 XRP')
})
it('works with a negative value', function () {
const xrp = dropsToXrp('-2000000')
assert.strictEqual(xrp, '-2', '-2 million drops equals -2 XRP')
})
it('works with a value ending with a decimal point', function () {
let xrp = dropsToXrp('2000000.')
assert.strictEqual(xrp, '2', '2000000. drops equals 2 XRP')
xrp = dropsToXrp('-2000000.')
assert.strictEqual(xrp, '-2', '-2000000. drops equals -2 XRP')
})
it('works with BigNumber objects', function () {
let xrp = dropsToXrp(new BigNumber(2000000))
assert.strictEqual(xrp, '2', '(BigNumber) 2 million drops equals 2 XRP')
xrp = dropsToXrp(new BigNumber(-2000000))
assert.strictEqual(xrp, '-2', '(BigNumber) -2 million drops equals -2 XRP')
xrp = dropsToXrp(new BigNumber(2345678))
assert.strictEqual(
xrp,
'2.345678',
'(BigNumber) 2,345,678 drops equals 2.345678 XRP',
)
xrp = dropsToXrp(new BigNumber(-2345678))
assert.strictEqual(
xrp,
'-2.345678',
'(BigNumber) -2,345,678 drops equals -2.345678 XRP',
)
})
it('works with a number', function () {
// This is not recommended. Use strings or BigNumber objects to avoid precision errors.
let xrp = dropsToXrp(2000000)
assert.strictEqual(xrp, '2', '(number) 2 million drops equals 2 XRP')
xrp = dropsToXrp(-2000000)
assert.strictEqual(xrp, '-2', '(number) -2 million drops equals -2 XRP')
})
it('works with scientific notation', function () {
const xrp = dropsToXrp('1e6')
assert.strictEqual(
xrp,
'1',
'(scientific notation string) 1e6 drops equals 1 XRP',
)
})
it('throws with an amount with too many decimal places', function () {
assert.throws(() => {
dropsToXrp('1.2')
}, /has too many decimal places/u)
assert.throws(() => {
dropsToXrp('0.10')
}, /has too many decimal places/u)
})
it('throws with an invalid value', function () {
assert.throws(() => {
dropsToXrp('FOO')
}, /invalid value/u)
assert.throws(() => {
dropsToXrp('1e-7')
}, /decimal place/u)
assert.throws(() => {
dropsToXrp('2,0')
}, /invalid value/u)
assert.throws(() => {
dropsToXrp('.')
}, /dropsToXrp: invalid value '\.', should be a BigNumber or string-encoded number\./u)
})
it('throws with an amount more than one decimal point', function () {
assert.throws(() => {
dropsToXrp('1.0.0')
}, /dropsToXrp: invalid value '1\.0\.0'/u)
assert.throws(() => {
dropsToXrp('...')
}, /dropsToXrp: invalid value '\.\.\.'/u)
})
})

View File

@@ -0,0 +1,455 @@
import { assert } from 'chai'
import { getBalanceChanges } from 'xrpl-local/utils'
import paymentToken from '../fixtures/utils/paymentToken.json'
import paymentTokenDestinationNoBalance from '../fixtures/utils/paymentTokenDestinationNoBalance.json'
import paymentTokenMultipath from '../fixtures/utils/paymentTokenMultipath.json'
import paymentTokenRedeem from '../fixtures/utils/paymentTokenRedeem.json'
import paymentTokenRedeemThenIssue from '../fixtures/utils/paymentTokenRedeemThenIssue.json'
import paymentTokenSpendFullBalance from '../fixtures/utils/paymentTokenSpendFullBalance.json'
import paymentXrpCreateAccount from '../fixtures/utils/paymentXrpCreateAccount.json'
import trustlineCreate from '../fixtures/utils/trustlineCreate.json'
import trustlineDelete from '../fixtures/utils/trustlineDelete.json'
import trustlineSetLimit from '../fixtures/utils/trustlineSetLimit.json'
import trustlineSetLimit2 from '../fixtures/utils/trustlineSetLimit2.json'
import trustlineSetLimitZero from '../fixtures/utils/trustlineSetLimitZero.json'
describe('getBalanceChanges', function () {
it('XRP create account', function () {
const result = getBalanceChanges(paymentXrpCreateAccount.metadata)
const expected = [
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [{ currency: 'XRP', value: '100' }],
},
{
account: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
balances: [{ currency: 'XRP', value: '-100.012' }],
},
]
assert.deepStrictEqual(result, expected)
})
it('USD payment to account with no USD', function () {
const result = getBalanceChanges(paymentTokenDestinationNoBalance.metadata)
const expected = [
{
account: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
balances: [
{
value: '-0.01',
currency: 'USD',
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
},
{
value: '-0.012',
currency: 'XRP',
},
],
},
{
account: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
balances: [
{
issuer: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
currency: 'USD',
value: '0.01',
},
{
issuer: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
currency: 'USD',
value: '-0.01',
},
],
},
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [
{
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
currency: 'USD',
value: '0.01',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('USD payment of all USD in source account', function () {
const result = getBalanceChanges(paymentTokenSpendFullBalance.metadata)
const expected = [
{
account: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
balances: [
{
value: '0.2',
currency: 'USD',
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
},
],
},
{
account: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
balances: [
{
value: '-0.2',
currency: 'USD',
issuer: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
},
{
value: '0.2',
currency: 'USD',
issuer: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
},
],
},
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [
{
value: '-0.2',
currency: 'USD',
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
},
{
value: '-0.012',
currency: 'XRP',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('USD payment to account with USD', function () {
const result = getBalanceChanges(paymentToken.metadata)
const expected = [
{
account: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
balances: [
{
value: '-0.01',
currency: 'USD',
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
},
{
value: '-0.012',
currency: 'XRP',
},
],
},
{
account: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
balances: [
{
issuer: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
currency: 'USD',
value: '0.01',
},
{
issuer: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
currency: 'USD',
value: '-0.01',
},
],
},
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [
{
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
currency: 'USD',
value: '0.01',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('Set trust limit to 0 with balance remaining', function () {
const result = getBalanceChanges(trustlineSetLimitZero.metadata)
const expected = [
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [
{
value: '-0.012',
currency: 'XRP',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('Create trustline', function () {
const result = getBalanceChanges(trustlineCreate.metadata)
const expected = [
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [
{
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
currency: 'USD',
value: '10',
},
{
currency: 'XRP',
value: '-0.012',
},
],
},
{
account: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
balances: [
{
issuer: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
currency: 'USD',
value: '-10',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('Set trustline', function () {
const result = getBalanceChanges(trustlineSetLimit.metadata)
const expected = [
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [
{
value: '-0.012',
currency: 'XRP',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('Set trustline 2', function () {
const result = getBalanceChanges(trustlineSetLimit2.metadata)
const expected = [
{
account: 'rsApBGKJmMfExxZBrGnzxEXyq7TMhMRg4e',
balances: [
{
currency: 'XRP',
value: '-0.00001',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('Delete trustline', function () {
const result = getBalanceChanges(trustlineDelete.metadata)
const expected = [
{
account: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
balances: [
{
value: '0.02',
currency: 'USD',
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
},
],
},
{
account: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
balances: [
{
value: '-0.02',
currency: 'USD',
issuer: 'rKmBGxocj9Abgy25J51Mk1iqFzW9aVF9Tc',
},
{
value: '0.02',
currency: 'USD',
issuer: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
},
],
},
{
account: 'rLDYrujdKUfVx28T9vRDAbyJ7G2WVXKo4K',
balances: [
{
value: '-0.02',
currency: 'USD',
issuer: 'rMwjYedjc7qqtKYVLiAccJSmCwih4LnE2q',
},
{
value: '-0.012',
currency: 'XRP',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('Redeem USD', function () {
const result = getBalanceChanges(paymentTokenRedeem.result.meta)
const expected = [
{
account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
balances: [
{
currency: 'USD',
issuer: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK',
value: '100',
},
],
},
{
account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK',
balances: [
{
currency: 'USD',
issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
value: '-100',
},
{
currency: 'XRP',
value: '-0.00001',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('Redeem then issue USD', function () {
const result = getBalanceChanges(paymentTokenRedeemThenIssue.result.meta)
const expected = [
{
account: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
balances: [
{
currency: 'USD',
issuer: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK',
value: '200',
},
],
},
{
account: 'rPMh7Pi9ct699iZUTWaytJUoHcJ7cgyziK',
balances: [
{
currency: 'USD',
issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
value: '-200',
},
{
currency: 'XRP',
value: '-0.00001',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
it('Multipath USD payment', function () {
const result = getBalanceChanges(paymentTokenMultipath.result.meta)
const expected = [
{
account: 'rrnsYgWn13Z28GtRgznrSUsLfMkvsXCZSu',
balances: [
{
issuer: 'r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf',
currency: 'USD',
value: '100',
},
{
issuer: 'rnYDWQaRdMb5neCGgvFfhw3MBoxmv5LtfH',
currency: 'USD',
value: '-100',
},
],
},
{
account: 'r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf',
balances: [
{
issuer: 'rrnsYgWn13Z28GtRgznrSUsLfMkvsXCZSu',
currency: 'USD',
value: '-100',
},
{
currency: 'XRP',
value: '-0.00001',
},
{
issuer: 'rJsaPnGdeo7BhMnHjuc3n44Mf7Ra1qkSVJ',
currency: 'USD',
value: '-100',
},
{
issuer: 'rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD',
currency: 'USD',
value: '-100',
},
],
},
{
account: 'rJsaPnGdeo7BhMnHjuc3n44Mf7Ra1qkSVJ',
balances: [
{
issuer: 'r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf',
currency: 'USD',
value: '100',
},
{
issuer: 'rnYDWQaRdMb5neCGgvFfhw3MBoxmv5LtfH',
currency: 'USD',
value: '-100',
},
],
},
{
account: 'rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD',
balances: [
{
issuer: 'r4nmQNH4Fhjfh6cHDbvVSsBv7KySbj4cBf',
currency: 'USD',
value: '100',
},
{
issuer: 'rnYDWQaRdMb5neCGgvFfhw3MBoxmv5LtfH',
currency: 'USD',
value: '-100',
},
],
},
{
account: 'rnYDWQaRdMb5neCGgvFfhw3MBoxmv5LtfH',
balances: [
{
issuer: 'rJsaPnGdeo7BhMnHjuc3n44Mf7Ra1qkSVJ',
currency: 'USD',
value: '100',
},
{
issuer: 'rrnsYgWn13Z28GtRgznrSUsLfMkvsXCZSu',
currency: 'USD',
value: '100',
},
{
issuer: 'rGpeQzUWFu4fMhJHZ1Via5aqFC3A5twZUD',
currency: 'USD',
value: '100',
},
],
},
]
assert.deepStrictEqual(result, expected)
})
})

View File

@@ -0,0 +1,17 @@
import { assert } from 'chai'
import { hasNextPage } from 'xrpl-local'
import fixtures from '../fixtures/rippled'
describe('hasNextPage', function () {
it('returns true when response has marker', function () {
const firstPage = fixtures.ledger_data.first_page
assert.isTrue(hasNextPage(firstPage))
})
it('returns false when response does not have marker', function () {
const lastPage = fixtures.ledger_data.last_page
assert.isFalse(hasNextPage(lastPage))
})
})

View File

@@ -0,0 +1,154 @@
import { assert } from 'chai'
import { ValidationError, XrplError } from 'xrpl-local'
import { hashes } from 'xrpl-local/utils'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
const { hashLedger } = hashes
const { hashLedger: REQUEST_FIXTURES } = requests
describe('hashLedger', function () {
let ledger
beforeEach(function () {
ledger = JSON.parse(JSON.stringify(responses.getLedger.full))
if (ledger.rawState != null) {
ledger.accountState = JSON.parse(ledger.rawState)
}
})
it('given corrupt data - should fail', function () {
ledger.transactions[0] = JSON.parse(
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"12000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","metaData":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}',
)
ledger.parent_close_time = ledger.close_time
let hash: string
try {
hash = hashLedger(ledger, { computeTreeHashes: true })
} catch (error) {
if (!(error instanceof XrplError)) {
throw error
}
assert(error instanceof ValidationError)
if (error instanceof ValidationError) {
assert.strictEqual(
error.message,
'transactionHash in header does not match computed hash of transactions',
)
assert.deepStrictEqual(error.data, {
transactionHashInHeader:
'DB83BF807416C5B3499A73130F843CF615AB8E797D79FE7D330ADF1BFA93951A',
computedHashOfTransactions:
'EAA1ADF4D627339450F0E95EA88B7069186DD64230BAEBDCF3EEC4D616A9FC68',
})
}
return
}
assert(
false,
`Should throw ValidationError instead of producing hash: ${hash}`,
)
})
it('given ledger without raw transactions - should throw', function () {
delete ledger.transactions
ledger.parentCloseTime = ledger.closeTime
assert.throws(
() => hashLedger(ledger, { computeTreeHashes: true }),
ValidationError,
'transactions is missing from the ledger',
)
})
it('given ledger without state or transactions - only compute ledger hash', function () {
ledger.transactions[0] = JSON.parse(
'{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Amount":"10000000000","Destination":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Fee":"10","Flags":0,"Sequence":62,"SigningPubKey":"034AADB09CFF4A4804073701EC53C3510CDC95917C2BB0150FB742D0C66E6CEE9E","TransactionType":"Payment","TxnSignature":"3045022022EB32AECEF7C644C891C19F87966DF9C62B1F34BABA6BE774325E4BB8E2DD62022100A51437898C28C2B297112DF8131F2BB39EA5FE613487DDD611525F1796264639","hash":"3B1A4E1C9BB6A7208EB146BCDB86ECEA6068ED01466D933528CA2B4C64F753EF","metaData":{"AffectedNodes":[{"CreatedNode":{"LedgerEntryType":"AccountRoot","LedgerIndex":"4C6ACBD635B0F07101F7FA25871B0925F8836155462152172755845CE691C49E","NewFields":{"Account":"rLQBHVhFnaC5gLEkgr6HgBJJ3bgeZHg9cj","Balance":"10000000000","Sequence":1}}},{"ModifiedNode":{"FinalFields":{"Account":"r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV","Balance":"981481999380","Flags":0,"OwnerCount":0,"Sequence":63},"LedgerEntryType":"AccountRoot","LedgerIndex":"B33FDD5CF3445E1A7F2BE9B06336BEBD73A5E3EE885D3EF93F7E3E2992E46F1A","PreviousFields":{"Balance":"991481999390","Sequence":62},"PreviousTxnID":"2485FDC606352F1B0785DA5DE96FB9DBAF43EB60ECBB01B7F6FA970F512CDA5F","PreviousTxnLgrSeq":31317}}],"TransactionIndex":0,"TransactionResult":"tesSUCCESS"},"ledger_index":38129}',
)
ledger.parent_close_time = ledger.close_time
function testCompute(ledgerToTest, expectedError): void {
const hash = hashLedger(ledgerToTest)
assert.strictEqual(
hash,
'E6DB7365949BF9814D76BCC730B01818EB9136A89DB224F3F9F5AAE4569D758E',
)
// fail if required to compute tree hashes
assert.throws(
() => hashLedger(ledgerToTest, { computeTreeHashes: true }),
ValidationError,
expectedError,
)
}
const transactions = ledger.transactions
delete ledger.transactions
testCompute(ledger, 'transactions is missing from the ledger')
delete ledger.accountState
testCompute(ledger, 'transactions is missing from the ledger')
ledger.transactions = transactions
testCompute(ledger, 'accountState is missing from the ledger')
})
it('wrong hash', function () {
const newLedger = {
...ledger,
parent_close_time: ledger.close_time,
account_hash:
'D9ABF622DA26EEEE48203085D4BC23B0F77DC6F8724AC33D975DA3CA492D2E44',
}
assert.throws(
() => {
hashLedger(newLedger, { computeTreeHashes: true })
},
ValidationError,
'does not match computed hash of state',
)
})
it('hashLedger', function () {
const header = REQUEST_FIXTURES.header
const ledgerHash = hashLedger(header)
assert.strictEqual(
ledgerHash,
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349',
)
})
it('hashLedger - with transactions', function () {
const header = {
...REQUEST_FIXTURES.header,
transactionHash: undefined,
rawTransactions: REQUEST_FIXTURES.transactions,
}
const ledgerHash = hashLedger(header)
assert.strictEqual(
ledgerHash,
'F4D865D83EB88C1A1911B9E90641919A1314F36E1B099F8E95FE3B7C77BE3349',
)
})
it('hashLedger - incorrect transaction_hash', function () {
const header = {
...REQUEST_FIXTURES.header,
transaction_hash:
'325EACC5271322539EEEC2D6A5292471EF1B3E72AE7180533EFC3B8F0AD435C9',
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- okay for tests
transactions: REQUEST_FIXTURES.transactions as any,
}
assert.throws(
() => hashLedger(header, { computeTreeHashes: true }),
ValidationError,
'transactionHash in header does not match computed hash of transactions',
)
})
})

View File

@@ -0,0 +1,193 @@
import fs from 'fs'
import path from 'path'
import { assert } from 'chai'
import { encode } from 'ripple-binary-codec'
import { OfferCreate, Transaction, ValidationError } from 'xrpl-local'
import {
hashStateTree,
hashTxTree,
hashTrustline,
hashEscrow,
hashPaymentChannel,
hashSignedTx,
hashAccountRoot,
hashOfferId,
hashSignerListId,
} from 'xrpl-local/utils/hashes'
import fixtures from '../fixtures/rippled'
import { assertResultMatch } from '../testUtils'
/**
* Expects a corresponding ledger dump in $repo/test/fixtures/rippled folder.
*
* @param ledgerIndex - The ledger index of the desired dump.
*/
function createLedgerTest(ledgerIndex: number): void {
const ledgerIndexString = String(ledgerIndex)
const fileLocation = path.join(
__dirname,
'..',
`fixtures/rippled/ledgerFull${ledgerIndex}.json`,
)
// eslint-disable-next-line node/no-sync -- must be sync version when not in async method
const ledgerRaw = fs.readFileSync(fileLocation, { encoding: 'utf8' })
const ledgerJSON = JSON.parse(ledgerRaw)
const hasAccounts =
Array.isArray(ledgerJSON.accountState) && ledgerJSON.accountState.length > 0
describe(`ledger hashes ${ledgerIndexString}`, function () {
if (hasAccounts) {
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- known to be a string
it(`has account_hash of ${ledgerJSON.account_hash}`, function () {
assert.equal(
ledgerJSON.account_hash,
hashStateTree(ledgerJSON.accountState),
)
})
}
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions -- known to be a string
it(`has transaction_hash of ${ledgerJSON.transaction_hash}`, function () {
assert.equal(
ledgerJSON.transaction_hash,
hashTxTree(ledgerJSON.transactions),
)
})
})
}
describe('Hashes', function () {
// This is the first recorded ledger with a non empty transaction set
// eslint-disable-next-line mocha/no-setup-in-describe -- runs tests
createLedgerTest(38129)
// Because, why not.
// eslint-disable-next-line mocha/no-setup-in-describe -- runs tests
createLedgerTest(40000)
// 1311 AffectedNodes, no accounts
// eslint-disable-next-line mocha/no-setup-in-describe -- runs tests
createLedgerTest(7501326)
it('calcAccountRootEntryHash', function () {
const account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
const expectedEntryHash =
'2B6AC232AA4C4BE41BF49D2459FA4A0347E1B543A4C92FCEE0821C0201E2E9A8'
const actualEntryHash = hashAccountRoot(account)
assert.equal(actualEntryHash, expectedEntryHash)
})
it('calcRippleStateEntryHash', function () {
const account1 = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
const account2 = 'rB5TihdPbKgMrkFqrqUC3yLdE8hhv4BdeY'
const currency = 'USD'
const expectedEntryHash =
'C683B5BB928F025F1E860D9D69D6C554C2202DE0D45877ADB3077DA4CB9E125C'
const actualEntryHash1 = hashTrustline(account1, account2, currency)
const actualEntryHash2 = hashTrustline(account2, account1, currency)
assert.equal(actualEntryHash1, expectedEntryHash)
assert.equal(actualEntryHash2, expectedEntryHash)
})
it('will calculate the RippleState entry hash for r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV and rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj in UAM', function () {
const account1 = 'r3kmLJN5D28dHuH8vZNUZpMC43pEHpaocV'
const account2 = 'rUAMuQTfVhbfqUDuro7zzy4jj4Wq57MPTj'
const currency = 'UAM'
const expectedEntryHash =
'AE9ADDC584358E5847ADFC971834E471436FC3E9DE6EA1773DF49F419DC0F65E'
const actualEntryHash1 = hashTrustline(account1, account2, currency)
const actualEntryHash2 = hashTrustline(account2, account1, currency)
assert.equal(actualEntryHash1, expectedEntryHash)
assert.equal(actualEntryHash2, expectedEntryHash)
})
it('calcOfferEntryHash', function () {
const account = 'r32UufnaCGL82HubijgJGDmdE5hac7ZvLw'
const sequence = 137
const expectedEntryHash =
'03F0AED09DEEE74CEF85CD57A0429D6113507CF759C597BABB4ADB752F734CE3'
const actualEntryHash = hashOfferId(account, sequence)
assert.equal(actualEntryHash, expectedEntryHash)
})
it('hashSignerListId', function () {
const account = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
const expectedEntryHash =
'778365D5180F5DF3016817D1F318527AD7410D83F8636CF48C43E8AF72AB49BF'
const actualEntryHash = hashSignerListId(account)
assert.equal(actualEntryHash, expectedEntryHash)
})
it('calcEscrowEntryHash', function () {
const account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x'
const sequence = 84
const expectedEntryHash =
'61E8E8ED53FA2CEBE192B23897071E9A75217BF5A410E9CB5B45AAB7AECA567A'
const actualEntryHash = hashEscrow(account, sequence)
assert.equal(actualEntryHash, expectedEntryHash)
})
it('calcPaymentChannelEntryHash', function () {
const account = 'rDx69ebzbowuqztksVDmZXjizTd12BVr4x'
const dstAccount = 'rLFtVprxUEfsH54eCWKsZrEQzMDsx1wqso'
const sequence = 82
const expectedEntryHash =
'E35708503B3C3143FB522D749AAFCC296E8060F0FB371A9A56FAE0B1ED127366'
const actualEntryHash = hashPaymentChannel(account, dstAccount, sequence)
assert.equal(actualEntryHash, expectedEntryHash)
})
it('Hash a signed transaction correctly', function () {
const expected_hash =
'458101D51051230B1D56E9ACAFAA34451BF65FA000F95DF6F0FF5B3A62D83FC2'
assertResultMatch(
hashSignedTx(fixtures.tx.OfferCreateSell.result as Transaction),
expected_hash,
)
})
it('Hash a signed transaction blob correctly', function () {
const expected_hash =
'458101D51051230B1D56E9ACAFAA34451BF65FA000F95DF6F0FF5B3A62D83FC2'
assertResultMatch(
hashSignedTx(encode(fixtures.tx.OfferCreateSell.result)),
expected_hash,
)
})
it('Throw an error when hashing an unsigned transaction', function () {
const offerCreateWithNoSignature: OfferCreate = {
...(fixtures.tx.OfferCreateSell.result as OfferCreate),
TxnSignature: undefined,
}
assert.throws(
() => hashSignedTx(offerCreateWithNoSignature),
ValidationError,
)
})
it('Throw when hashing an unsigned transaction blob', function () {
const encodedOfferCreateWithNoSignature: string = encode({
...fixtures.tx.OfferCreateSell.result,
TxnSignature: undefined,
})
assert.throws(
() => hashSignedTx(encodedOfferCreateWithNoSignature),
ValidationError,
)
})
})

View File

@@ -0,0 +1,29 @@
import { assert } from 'chai'
import { convertHexToString, convertStringToHex } from 'xrpl-local/utils'
describe('convertHexToString and convertStringToHex', function () {
it('converts "example.com"', function () {
const str = 'example.com'
const hex = convertStringToHex(str)
assert.strictEqual(
hex,
'6578616D706C652E636F6D',
'should convert to hex equivalent',
)
const result = convertHexToString(hex)
assert.strictEqual(
result,
'example.com',
'should convert back to example.com',
)
})
it('converts "你好"', function () {
const str = '你好'
const hex = convertStringToHex(str)
assert.strictEqual(hex, 'E4BDA0E5A5BD', 'should convert to hex equivalent')
const result = convertHexToString(hex)
assert.strictEqual(result, '你好', 'should convert back to 你好')
})
})

View File

@@ -0,0 +1,25 @@
import { assert } from 'chai'
import { isValidAddress } from 'xrpl-local'
describe('isValidAddress', function () {
it('Validates valid classic address', function () {
const classic = 'r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W'
assert(isValidAddress(classic))
})
it('Does not validate invalid classic address', function () {
const classic = 'r3rhWeE31Jt5sWmi4QiGLMZnY3ENhqw96W'
assert(!isValidAddress(classic))
})
it('Validates valid X-Address', function () {
const xAddress = 'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD28Sq49uo34VyjnmK5H'
assert(isValidAddress(xAddress))
})
it('Does not validate invalid X-Address', function () {
const xAddress = 'XV5sbjUmgPpvXv4ixFWZ5pfAYZ6PD28Sq49uo34VyjnmK5H'
assert(!isValidAddress(xAddress))
})
})

View File

@@ -0,0 +1,72 @@
import { assert } from 'chai'
import { ValidationError } from 'xrpl-local'
import { decimalToQuality, percentToQuality, qualityToDecimal } from '../../src'
describe('Quality utils', function () {
it('converts 101 percent to valid Quality', function () {
const billionths = percentToQuality('101%')
assert.equal(billionths, 1010000000)
})
it('converts 1.01 to valid Quality', function () {
assert.equal(decimalToQuality('1.01'), 1010000000)
assert.equal(qualityToDecimal(1010000000), '1.01')
})
it('converts 99 percent to valid Quality', function () {
const billionths = percentToQuality('99%')
assert.equal(billionths, 990000000)
})
it('converts .99 to valid Quality', function () {
assert.equal(decimalToQuality('.99'), 990000000)
assert.equal(qualityToDecimal(990000000), '0.99')
})
it('converts 100 percent to 0', function () {
const billionths = percentToQuality('100%')
assert.equal(billionths, 0)
})
it('converts 1.00 percent to 0', function () {
assert.equal(decimalToQuality('1.00'), 0)
assert.equal(qualityToDecimal(0), '1')
})
it('Throws when percent Quality greater than maximum precision', function () {
assert.throws(
() => percentToQuality('.0000000000000011221%'),
ValidationError,
'Decimal exceeds maximum precision.',
)
})
it('Throws when decimal Quality greater than maximum precision', function () {
assert.throws(
() => decimalToQuality('.000000000000000011221'),
ValidationError,
'Decimal exceeds maximum precision.',
)
})
it('percentToQuality throws with gibberish', function () {
assert.throws(
() => percentToQuality('3dsadflk%'),
ValidationError,
'Value is not a number',
)
})
it('decimalToQuality throws with gibberish', function () {
assert.throws(
() => decimalToQuality('3dsadflk%'),
ValidationError,
'Value is not a number',
)
})
})

View File

@@ -0,0 +1,22 @@
import signPaymentChannelClaim from 'xrpl-local/utils/signPaymentChannelClaim'
import responses from '../fixtures/responses'
import { assertResultMatch } from '../testUtils'
describe('signPaymentChannelClaim', function () {
it('basic signature matches', function () {
const channel =
'3E18C05AD40319B809520F1A136370C4075321B285217323396D6FD9EE1E9037'
const amount = '.00001'
const privateKey =
'ACCD3309DB14D1A4FC9B1DAE608031F4408C85C73EE05E035B7DC8B25840107A'
const result = signPaymentChannelClaim(channel, amount, privateKey)
assertResultMatch(
result,
responses.signPaymentChannelClaim,
'signPaymentChannelClaim',
)
})
})

View File

@@ -0,0 +1,48 @@
import { assert } from 'chai'
import {
rippleTimeToISOTime,
isoTimeToRippleTime,
unixTimeToRippleTime,
rippleTimeToUnixTime,
} from '../../src'
describe('time conversion', function () {
describe('rippleTimeToISOTime', function () {
it('converts ripple time to ISO time', function () {
const rippleTime = 0
const isoTime = '2000-01-01T00:00:00.000Z'
assert.equal(rippleTimeToISOTime(rippleTime), isoTime)
})
})
describe('isoTimeToRippleTime', function () {
it('converts ISO time to ripple time', function () {
const rippleTime = 0
const isoTime = '2000-01-01T00:00:00.000Z'
assert.equal(isoTimeToRippleTime(isoTime), rippleTime)
})
it('converts from Date', function () {
const rippleTime = 0
const isoTime = '2000-01-01T00:00:00.000Z'
assert.equal(isoTimeToRippleTime(new Date(isoTime)), rippleTime)
})
})
describe('unixTimeToRippleTime', function () {
it('converts unix time to ripple time', function () {
const unixTime = 946684801000
const rippleTime = 1
assert.equal(unixTimeToRippleTime(unixTime), rippleTime)
})
})
describe('rippleTimeToUnixTime', function () {
it('converts ripple time to unix time', function () {
const unixTime = 946684801000
const rippleTime = 1
assert.equal(rippleTimeToUnixTime(rippleTime), unixTime)
})
})
})

View File

@@ -0,0 +1,81 @@
import { assert } from 'chai'
import { ValidationError } from 'xrpl-local'
import {
percentToTransferRate,
decimalToTransferRate,
transferRateToDecimal,
} from '../../src'
describe('TransferRate utils', function () {
it('converts 1 percent to valid TransferRate', function () {
const billionths = percentToTransferRate('1%')
assert.equal(billionths, 1010000000)
})
it('converts .01 percent to valid TransferRate', function () {
assert.equal(decimalToTransferRate('.01'), 1010000000)
assert.equal(transferRateToDecimal(1010000000), '0.01')
})
it('Throws when TransferRate < 0%', function () {
assert.throws(
() => percentToTransferRate('-1%'),
ValidationError,
'Decimal value must be between 0 and 1.00.',
)
})
it('Throws when TransferRate < 0', function () {
assert.throws(
() => decimalToTransferRate('-.01'),
ValidationError,
'Decimal value must be between 0 and 1.00.',
)
})
it('Throws when TransferRate >100%', function () {
assert.throws(
() => percentToTransferRate('101%'),
ValidationError,
'Decimal value must be between 0 and 1.00.',
)
})
it('Throws when TransferRate >1.00', function () {
assert.throws(
() => decimalToTransferRate('1.01'),
ValidationError,
'Decimal value must be between 0 and 1.00.',
)
})
it('percentToTransferRate greater than maximum precision', function () {
assert.throws(
() => percentToTransferRate('.0000000000000011221%'),
ValidationError,
'Decimal exceeds maximum precision.',
)
})
it('decimalToTransferRate greater than maximum precision', function () {
assert.throws(
() => decimalToTransferRate('.000000000000000011221'),
ValidationError,
'Decimal exceeds maximum precision.',
)
})
it('converts 0 percent to valid 0', function () {
const billionths = percentToTransferRate('0%')
assert.equal(billionths, 0)
})
it('converts 0 to valid 0', function () {
assert.equal(decimalToTransferRate('0'), 0)
assert.equal(transferRateToDecimal(0), '0')
})
})

View File

@@ -0,0 +1,31 @@
import { verifyPaymentChannelClaim } from 'xrpl-local'
import requests from '../fixtures/requests'
import responses from '../fixtures/responses'
import { assertResultMatch } from '../testUtils'
describe('verifyPaymentChannelClaim', function () {
it('basic verification works', function () {
const publicKey =
'02F89EAEC7667B30F33D0687BBA86C3FE2A08CCA40A9186C5BDE2DAA6FA97A37D8'
const result = verifyPaymentChannelClaim(
requests.signPaymentChannelClaim.channel,
requests.signPaymentChannelClaim.amount,
responses.signPaymentChannelClaim,
publicKey,
)
assertResultMatch(result, true, 'verifyPaymentChannelClaim')
})
it('invalid payment channel claim fails', function () {
const publicKey =
'03A6523FE4281DA48A6FD77FAF3CB77F5C7001ABA0B32BCEDE0369AC009758D7D9'
const result = verifyPaymentChannelClaim(
requests.signPaymentChannelClaim.channel,
requests.signPaymentChannelClaim.amount,
responses.signPaymentChannelClaim,
publicKey,
)
assertResultMatch(result, false, 'verifyPaymentChannelClaim')
})
})

View File

@@ -0,0 +1,119 @@
import BigNumber from 'bignumber.js'
import { assert } from 'chai'
import { xrpToDrops } from 'xrpl-local/utils'
describe('xrpToDrops', function () {
it('works with a typical amount', function () {
const drops = xrpToDrops('2')
assert.strictEqual(drops, '2000000', '2 XRP equals 2 million drops')
})
it('works with fractions', function () {
let drops = xrpToDrops('3.456789')
assert.strictEqual(drops, '3456789', '3.456789 XRP equals 3,456,789 drops')
drops = xrpToDrops('3.400000')
assert.strictEqual(drops, '3400000', '3.400000 XRP equals 3,400,000 drops')
drops = xrpToDrops('0.000001')
assert.strictEqual(drops, '1', '0.000001 XRP equals 1 drop')
drops = xrpToDrops('0.0000010')
assert.strictEqual(drops, '1', '0.0000010 XRP equals 1 drop')
})
it('works with zero', function () {
let drops = xrpToDrops('0')
assert.strictEqual(drops, '0', '0 XRP equals 0 drops')
// negative zero is equivalent to zero
drops = xrpToDrops('-0')
assert.strictEqual(drops, '0', '-0 XRP equals 0 drops')
drops = xrpToDrops('0.000000')
assert.strictEqual(drops, '0', '0.000000 XRP equals 0 drops')
drops = xrpToDrops('0.0000000')
assert.strictEqual(drops, '0', '0.0000000 XRP equals 0 drops')
})
it('works with a negative value', function () {
const drops = xrpToDrops('-2')
assert.strictEqual(drops, '-2000000', '-2 XRP equals -2 million drops')
})
it('works with a value ending with a decimal point', function () {
let drops = xrpToDrops('2.')
assert.strictEqual(drops, '2000000', '2. XRP equals 2000000 drops')
drops = xrpToDrops('-2.')
assert.strictEqual(drops, '-2000000', '-2. XRP equals -2000000 drops')
})
it('works with BigNumber objects', function () {
let drops = xrpToDrops(new BigNumber(2))
assert.strictEqual(
drops,
'2000000',
'(BigNumber) 2 XRP equals 2 million drops',
)
drops = xrpToDrops(new BigNumber(-2))
assert.strictEqual(
drops,
'-2000000',
'(BigNumber) -2 XRP equals -2 million drops',
)
})
it('works with a number', function () {
// This is not recommended. Use strings or BigNumber objects to avoid precision errors.
const drops = xrpToDrops(2)
assert.strictEqual(
drops,
'2000000',
'(number) 2 XRP equals 2 million drops',
)
const drops2 = xrpToDrops(-2)
assert.strictEqual(
drops2,
'-2000000',
'(number) -2 XRP equals -2 million drops',
)
})
it('works with scientific notation', function () {
const drops = xrpToDrops('1e-6')
assert.strictEqual(
drops,
'1',
'(scientific notation string) 1e-6 XRP equals 1 drop',
)
})
it('throws with an amount with too many decimal places', function () {
assert.throws(() => {
xrpToDrops('1.1234567')
}, /has too many decimal places/u)
assert.throws(() => {
xrpToDrops('0.0000001')
}, /has too many decimal places/u)
})
it('throws with an invalid value', function () {
assert.throws(() => {
xrpToDrops('FOO')
}, /invalid value/u)
assert.throws(() => {
xrpToDrops('1e-7')
}, /decimal place/u)
assert.throws(() => {
xrpToDrops('2,0')
}, /invalid value/u)
assert.throws(() => {
xrpToDrops('.')
}, /xrpToDrops: invalid value '\.', should be a BigNumber or string-encoded number\./u)
})
it('throws with an amount more than one decimal point', function () {
assert.throws(() => {
xrpToDrops('1.0.0')
}, /xrpToDrops: invalid value '1\.0\.0'/u)
assert.throws(() => {
xrpToDrops('...')
}, /xrpToDrops: invalid value '\.\.\.'/u)
})
})