Files
xahau.js/packages/xrpl/tools/createValidate.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

155 lines
3.9 KiB
JavaScript

/* eslint-disable no-continue -- unneeded here */
/**
* This file writes the `validate` function for a transaction, when provided the model name in the `src/models/transactions`
* folder.
*/
const fs = require('fs')
const path = require('path')
const NORMAL_TYPES = ['number', 'string']
const NUMBERS = ['0', '1']
// TODO: rewrite this to use regex
async function main(modelName) {
const filename = path.join(
path.dirname(__filename),
`../src/models/transactions/${modelName}.ts`,
)
const [model, txName] = await getModel(filename)
return processModel(model, txName)
}
async function getModel(filename) {
let model = ''
let started = false
let ended = false
const data = await fs.promises.readFile(filename, { encoding: 'utf8' })
const lines = data.split('\n')
for (const 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 getValidationFunction(paramType) {
if (NORMAL_TYPES.includes(paramType)) {
const paramTypeCapitalized =
paramType.substring(0, 1).toUpperCase() + paramType.substring(1)
return `is${paramTypeCapitalized}(inp)`
}
if (NUMBERS.includes(paramType)) {
return `inp === ${paramType}`
}
return `is${paramType}(inp)`
}
function getValidationLine(validationFns) {
if (validationFns.length === 1) {
if (!validationFns[0].includes('===')) {
// Example: `validateRequiredFields(tx, 'Amount', isAmount)`
const validationFn = validationFns[0]
// strip the `(inp)` in e.g. `isAmount(inp)`
return validationFn.substring(0, validationFn.length - 5)
}
}
// Example:
// `validateRequiredFields(tx, 'XChainAccountCreateCount',
// (inp) => isNumber(inp) || isString(inp)))`
return `(inp) => ${validationFns.join(' || ')}`
}
function processModel(model, txName) {
let output = ''
// process the TS model and get the types of each parameter
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')) {
continue
}
if (line.startsWith('/**')) {
continue
}
if (line.startsWith('*')) {
continue
}
// process the line with a type
const split = line.split(' ')
const param = split[0].replace('?:', '').replace(':', '').trim()
const paramTypes = split.slice(1)
const optional = split[0].endsWith('?:')
const functionName = optional
? 'validateOptionalField'
: 'validateRequiredField'
// process the types and turn them into a validation function
let idx = 0
const if_outputs = []
while (idx < paramTypes.length) {
const paramType = paramTypes[idx]
if_outputs.push(getValidationFunction(paramType))
idx += 2
}
output += ` ${functionName}(tx, '${param}', ${getValidationLine(
if_outputs,
)})\n\n`
}
output = output.substring(0, output.length - 1)
output += '}\n'
// initial output content
output = `/**
* Verify the form and type of a ${txName} at runtime.
*
* @param tx - A ${txName} Transaction.
* @throws When the ${txName} is malformed.
*/
export function validate${txName}(tx: Record<string, unknown>): void {
validateBaseTransaction(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)
}
main(process.argv[2]).then(console.log)
}
module.exports = main