Files
xahau.js/packages/xrpl/tools/createValidateTests.js
Mayukha Vadari c8f25a6347 feat: add support for XLS-40d + add script to auto-generate models from rippled code (#2491)
Add support for XLS-40 and adds a script to automatically
generate transaction models from rippled source code.

### Context of Change

https://github.com/XRPLF/XRPL-Standards/pull/136
https://github.com/XRPLF/rippled/pull/4636
2023-11-15 16:19:50 -06:00

204 lines
4.9 KiB
JavaScript

const fs = require('fs')
const path = require('path')
const fixtures = require('../../ripple-binary-codec/test/fixtures/codec-fixtures.json')
const NORMAL_TYPES = ['number', 'string']
const NUMBERS = ['0', '1']
function getTx(txName) {
transactions = fixtures.transactions
const validTxs = fixtures.transactions
.filter((tx) => tx.json.TransactionType === txName)
.map((tx) => tx.json)
if (validTxs.length == 0) {
throw new Error(`Must have ripple-binary-codec fixture for ${txName}`)
}
const validTx = validTxs[0]
delete validTx.TxnSignature
delete validTx.SigningPubKey
return JSON.stringify(validTx, null, 2)
}
function main(modelName) {
const filename = path.join(
path.dirname(__filename),
`../src/models/transactions/${modelName}.ts`,
)
const [model, txName] = getModel(filename)
return processModel(model, txName)
}
// Extract just the model from the file
function getModel(filename) {
let model = ''
let started = false
let ended = false
const data = fs.readFileSync(filename, 'utf8')
const lines = data.split('\n')
for (let line of lines) {
if (ended) {
continue
}
if (!started && !line.startsWith('export')) {
continue
}
if (!started && line.includes('Flags')) {
continue
}
if (!started) {
started = true
}
model += line + '\n'
if (line === '}') {
ended = true
}
}
const name_line = model.split('\n')[0].split(' ')
const txName = name_line[2]
return [model, txName]
}
function getInvalidValue(paramTypes) {
if (paramTypes.length === 1) {
const paramType = paramTypes[0]
if (paramType == 'number') {
return "'number'"
} else if (paramType == 'string') {
return 123
} else if (paramType == 'IssuedCurrency') {
return JSON.stringify({ test: 'test' })
} else if (paramType == 'Currency') {
return JSON.stringify({ test: 'test' })
} else if (paramType == 'Amount') {
return JSON.stringify({ currency: 'ETH' })
} else if (paramType == 'XChainBridge') {
return JSON.stringify({ XChainDoor: 'test' })
} else {
throw Error(`${paramType} not supported yet`)
}
}
const simplifiedParamTypes = paramTypes.filter(
(_paramType, index) => index % 2 == 0,
)
if (JSON.stringify(simplifiedParamTypes) === '["0","1"]') {
return 2
} else if (JSON.stringify(simplifiedParamTypes) === '["number","string"]') {
return JSON.stringify({ currency: 'ETH' })
} else {
throw Error(`${simplifiedParamTypes} not supported yet`)
}
}
// Process the model and build the tests
function processModel(model, txName) {
let output = ''
for (let line of model.split('\n')) {
if (line == '') {
continue
}
if (line.startsWith('export')) {
continue
}
if (line == '}') {
continue
}
line = line.trim()
if (line.startsWith('TransactionType')) {
continue
}
if (line.startsWith('Flags')) {
// TODO: support flag checking
continue
}
if (line.startsWith('/**')) {
continue
}
if (line.startsWith('*')) {
continue
}
const split = line.split(' ')
const param = split[0].replace('?:', '').replace(':', '').trim()
const paramTypes = split.slice(1)
const optional = split[0].endsWith('?:')
if (!optional) {
output += ` it("throws w/ missing ${param}", function () {
delete tx.${param}
assert.throws(
() => validate${txName}(tx),
ValidationError,
'${txName}: missing field ${param}',
)
assert.throws(
() => validate(tx),
ValidationError,
'${txName}: missing field ${param}',
)
})
`
}
const fakeValue = getInvalidValue(paramTypes)
output += ` it('throws w/ invalid ${param}', function () {
tx.${param} = ${fakeValue}
assert.throws(
() => validate${txName}(tx),
ValidationError,
'${txName}: invalid field ${param}',
)
assert.throws(
() => validate(tx),
ValidationError,
'${txName}: invalid field ${param}',
)
})
`
}
output = output.substring(0, output.length - 2)
output += '\n})\n'
output =
`import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validate${txName} } from '../../src/models/transactions/${txName}'
/**
* ${txName} Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('${txName}', function () {
let tx
beforeEach(function () {
tx = ${getTx(txName)} as any
})
it('verifies valid ${txName}', function () {
assert.doesNotThrow(() => validate${txName}(tx))
assert.doesNotThrow(() => validate(tx))
})
` + output
return output
}
if (require.main === module) {
if (process.argv.length < 3) {
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} TxName`)
process.exit(1)
}
console.log(main(process.argv[2]))
}
module.exports = main