mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-20 04:05:52 +00:00
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
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
# ripple-binary-codec Release History
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
- Support for the DID amendment (XLS-40).
|
||||
|
||||
## 1.10.0 (2023-09-27)
|
||||
### Added
|
||||
- Support for the XChainBridge amendment.
|
||||
- Support for the XChainBridge amendment (XLS-38).
|
||||
|
||||
## 1.9.0 (2023-08-24)
|
||||
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
"NFTokenPage": 80,
|
||||
"NFTokenOffer": 55,
|
||||
"AMM": 121,
|
||||
"DID": 73,
|
||||
"Any": -3,
|
||||
"Child": -2,
|
||||
"Nickname": 110,
|
||||
@@ -140,40 +141,40 @@
|
||||
[
|
||||
"LedgerEntry",
|
||||
{
|
||||
"nth": 1,
|
||||
"nth": 257,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": false,
|
||||
"isSigningField": true,
|
||||
"isSigningField": false,
|
||||
"type": "LedgerEntry"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Transaction",
|
||||
{
|
||||
"nth": 1,
|
||||
"nth": 257,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": false,
|
||||
"isSigningField": true,
|
||||
"isSigningField": false,
|
||||
"type": "Transaction"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Validation",
|
||||
{
|
||||
"nth": 1,
|
||||
"nth": 257,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": false,
|
||||
"isSigningField": true,
|
||||
"isSigningField": false,
|
||||
"type": "Validation"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Metadata",
|
||||
{
|
||||
"nth": 1,
|
||||
"nth": 257,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"isSerialized": false,
|
||||
"isSigningField": false,
|
||||
"type": "Metadata"
|
||||
}
|
||||
],
|
||||
@@ -1897,6 +1898,26 @@
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
[
|
||||
"DIDDocument",
|
||||
{
|
||||
"nth": 26,
|
||||
"isVLEncoded": true,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Data",
|
||||
{
|
||||
"nth": 27,
|
||||
"isVLEncoded": true,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Account",
|
||||
{
|
||||
@@ -2681,6 +2702,7 @@
|
||||
"temXCHAIN_BRIDGE_NONDOOR_OWNER": -257,
|
||||
"temXCHAIN_BRIDGE_BAD_MIN_ACCOUNT_CREATE_AMOUNT": -256,
|
||||
"temXCHAIN_BRIDGE_BAD_REWARD_AMOUNT": -255,
|
||||
"temEMPTY_DID": -254,
|
||||
|
||||
"tefFAILURE": -199,
|
||||
"tefALREADY": -198,
|
||||
@@ -2759,7 +2781,7 @@
|
||||
"tecKILLED": 150,
|
||||
"tecHAS_OBLIGATIONS": 151,
|
||||
"tecTOO_SOON": 152,
|
||||
"tecHOOK_ERROR": 153,
|
||||
"tecHOOK_REJECTED": 153,
|
||||
"tecMAX_SEQUENCE_REACHED": 154,
|
||||
"tecNO_SUITABLE_NFTOKEN_PAGE": 155,
|
||||
"tecNFTOKEN_BUY_SELL_MISMATCH": 156,
|
||||
@@ -2792,7 +2814,8 @@
|
||||
"tecXCHAIN_PAYMENT_FAILED": 183,
|
||||
"tecXCHAIN_SELF_COMMIT": 184,
|
||||
"tecXCHAIN_BAD_PUBLIC_KEY_ACCOUNT_PAIR": 185,
|
||||
"tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186
|
||||
"tecXCHAIN_CREATE_ACCOUNT_DISABLED": 186,
|
||||
"tecEMPTY_DID": 187
|
||||
},
|
||||
"TRANSACTION_TYPES": {
|
||||
"Invalid": -1,
|
||||
@@ -2839,6 +2862,8 @@
|
||||
"XChainAddAccountCreateAttestation": 46,
|
||||
"XChainModifyBridge": 47,
|
||||
"XChainCreateBridge": 48,
|
||||
"DIDSet": 49,
|
||||
"DIDDelete": 50,
|
||||
"EnableAmendment": 100,
|
||||
"SetFee": 101,
|
||||
"UNLModify": 102
|
||||
|
||||
@@ -4841,6 +4841,33 @@
|
||||
"SigningPubKey": "ED7453D2572A2104E7B266A45888C53F503CEB1F11DC4BB3710EB2995238EC65B8",
|
||||
"TxnSignature": "BC2F6E76969E3747E9BDE183C97573B086212F09D5387460E6EE2F32953E85EAEB9618FBBEF077276E30E59D619FCF7C7BDCDDDD9EB94D7CE1DD5CE9246B2107"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binary": "1200322280000000240000000468400000000000000A7321ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347744071E28B12465A1B47162C22E121DF61089DCD9AAF5773704B76179E771666886C8AAD5A33A87E34CC381A7D924E3FE3645F0BF98D565DE42C81E1A7A7E7981802811401476926B590BA3245F63C829116A0A3AF7F382D",
|
||||
"json": {
|
||||
"Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 4,
|
||||
"SigningPubKey": "ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347",
|
||||
"TransactionType": "DIDDelete",
|
||||
"TxnSignature": "71E28B12465A1B47162C22E121DF61089DCD9AAF5773704B76179E771666886C8AAD5A33A87E34CC381A7D924E3FE3645F0BF98D565DE42C81E1A7A7E7981802"
|
||||
}
|
||||
},
|
||||
{
|
||||
"binary": "1200312280000000240000000368400000000000000A7321ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A933477440AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD772999325667A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C08750B6469645F6578616D706C65701A03646F63701B06617474657374811401476926B590BA3245F63C829116A0A3AF7F382D",
|
||||
"json": {
|
||||
"Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8",
|
||||
"Data": "617474657374",
|
||||
"DIDDocument": "646F63",
|
||||
"Fee": "10",
|
||||
"Flags": 2147483648,
|
||||
"Sequence": 3,
|
||||
"SigningPubKey": "ED9861C4CB029C0DA737B823D7D3459A70F227958D5C0C111CC7CF947FC5A93347",
|
||||
"TransactionType": "DIDSet",
|
||||
"TxnSignature": "AACD31A04CAE14670FC483A1382F393AA96B49C84479B58067F049FBD772999325667A6AA2520A63756EE84F3657298815019DD56A1AECE796B08535C4009C08",
|
||||
"URI": "6469645F6578616D706C65"
|
||||
}
|
||||
}
|
||||
],
|
||||
"ledgerData": [{
|
||||
|
||||
@@ -3,6 +3,8 @@
|
||||
Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release.
|
||||
|
||||
## Unreleased
|
||||
### Added
|
||||
- Support for the DID amendment (XLS-40).
|
||||
|
||||
### Added
|
||||
* Support for `server_definitions` RPC
|
||||
|
||||
@@ -44,6 +44,7 @@ type LedgerEntryFilter =
|
||||
| 'bridge'
|
||||
| 'check'
|
||||
| 'deposit_preauth'
|
||||
| 'did'
|
||||
| 'directory'
|
||||
| 'escrow'
|
||||
| 'fee'
|
||||
|
||||
20
packages/xrpl/src/models/transactions/DIDDelete.ts
Normal file
20
packages/xrpl/src/models/transactions/DIDDelete.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { BaseTransaction, validateBaseTransaction } from './common'
|
||||
|
||||
// TODO: add docs
|
||||
|
||||
/**
|
||||
* @category Transaction Models
|
||||
*/
|
||||
export interface DIDDelete extends BaseTransaction {
|
||||
TransactionType: 'DIDDelete'
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a DIDDelete at runtime.
|
||||
*
|
||||
* @param tx - A DIDDelete Transaction.
|
||||
* @throws When the DIDDelete is malformed.
|
||||
*/
|
||||
export function validateDIDDelete(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
}
|
||||
37
packages/xrpl/src/models/transactions/DIDSet.ts
Normal file
37
packages/xrpl/src/models/transactions/DIDSet.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import {
|
||||
BaseTransaction,
|
||||
isString,
|
||||
validateBaseTransaction,
|
||||
validateOptionalField,
|
||||
} from './common'
|
||||
|
||||
// TODO: add docs
|
||||
|
||||
/**
|
||||
* @category Transaction Models
|
||||
*/
|
||||
export interface DIDSet extends BaseTransaction {
|
||||
TransactionType: 'DIDSet'
|
||||
|
||||
Data?: string
|
||||
|
||||
DIDDocument?: string
|
||||
|
||||
URI?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a DIDSet at runtime.
|
||||
*
|
||||
* @param tx - A DIDSet Transaction.
|
||||
* @throws When the DIDSet is malformed.
|
||||
*/
|
||||
export function validateDIDSet(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateOptionalField(tx, 'Data', isString)
|
||||
|
||||
validateOptionalField(tx, 'DIDDocument', isString)
|
||||
|
||||
validateOptionalField(tx, 'URI', isString)
|
||||
}
|
||||
@@ -31,6 +31,8 @@ export { CheckCancel } from './checkCancel'
|
||||
export { CheckCash } from './checkCash'
|
||||
export { CheckCreate } from './checkCreate'
|
||||
export { Clawback } from './clawback'
|
||||
export { DIDDelete } from './DIDDelete'
|
||||
export { DIDSet } from './DIDSet'
|
||||
export { DepositPreauth } from './depositPreauth'
|
||||
export { EscrowCancel } from './escrowCancel'
|
||||
export { EscrowCreate } from './escrowCreate'
|
||||
|
||||
@@ -20,6 +20,8 @@ import { CheckCreate, validateCheckCreate } from './checkCreate'
|
||||
import { Clawback, validateClawback } from './clawback'
|
||||
import { isIssuedCurrency } from './common'
|
||||
import { DepositPreauth, validateDepositPreauth } from './depositPreauth'
|
||||
import { DIDDelete, validateDIDDelete } from './DIDDelete'
|
||||
import { DIDSet, validateDIDSet } from './DIDSet'
|
||||
import { EnableAmendment } from './enableAmendment'
|
||||
import { EscrowCancel, validateEscrowCancel } from './escrowCancel'
|
||||
import { EscrowCreate, validateEscrowCreate } from './escrowCreate'
|
||||
@@ -91,18 +93,20 @@ import {
|
||||
* @category Transaction Models
|
||||
*/
|
||||
export type Transaction =
|
||||
| AccountDelete
|
||||
| AccountSet
|
||||
| AMMBid
|
||||
| AMMCreate
|
||||
| AMMDelete
|
||||
| AMMDeposit
|
||||
| AMMCreate
|
||||
| AMMVote
|
||||
| AMMWithdraw
|
||||
| AccountDelete
|
||||
| AccountSet
|
||||
| CheckCancel
|
||||
| CheckCash
|
||||
| CheckCreate
|
||||
| Clawback
|
||||
| DIDDelete
|
||||
| DIDSet
|
||||
| DepositPreauth
|
||||
| EscrowCancel
|
||||
| EscrowCreate
|
||||
@@ -122,13 +126,13 @@ export type Transaction =
|
||||
| SignerListSet
|
||||
| TicketCreate
|
||||
| TrustSet
|
||||
| XChainAccountCreateCommit
|
||||
| XChainAddAccountCreateAttestation
|
||||
| XChainAddClaimAttestation
|
||||
| XChainClaim
|
||||
| XChainCommit
|
||||
| XChainCreateBridge
|
||||
| XChainCreateClaimID
|
||||
| XChainAccountCreateCommit
|
||||
| XChainModifyBridge
|
||||
|
||||
export type PseudoTransaction = EnableAmendment | SetFee | UNLModify
|
||||
@@ -210,18 +214,14 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- okay here
|
||||
setTransactionFlagsToNumber(tx as unknown as Transaction)
|
||||
switch (tx.TransactionType) {
|
||||
case 'AccountDelete':
|
||||
validateAccountDelete(tx)
|
||||
break
|
||||
|
||||
case 'AccountSet':
|
||||
validateAccountSet(tx)
|
||||
break
|
||||
|
||||
case 'AMMBid':
|
||||
validateAMMBid(tx)
|
||||
break
|
||||
|
||||
case 'AMMCreate':
|
||||
validateAMMCreate(tx)
|
||||
break
|
||||
|
||||
case 'AMMDelete':
|
||||
validateAMMDelete(tx)
|
||||
break
|
||||
@@ -230,10 +230,6 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateAMMDeposit(tx)
|
||||
break
|
||||
|
||||
case 'AMMCreate':
|
||||
validateAMMCreate(tx)
|
||||
break
|
||||
|
||||
case 'AMMVote':
|
||||
validateAMMVote(tx)
|
||||
break
|
||||
@@ -242,6 +238,14 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateAMMWithdraw(tx)
|
||||
break
|
||||
|
||||
case 'AccountDelete':
|
||||
validateAccountDelete(tx)
|
||||
break
|
||||
|
||||
case 'AccountSet':
|
||||
validateAccountSet(tx)
|
||||
break
|
||||
|
||||
case 'CheckCancel':
|
||||
validateCheckCancel(tx)
|
||||
break
|
||||
@@ -258,6 +262,14 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateClawback(tx)
|
||||
break
|
||||
|
||||
case 'DIDDelete':
|
||||
validateDIDDelete(tx)
|
||||
break
|
||||
|
||||
case 'DIDSet':
|
||||
validateDIDSet(tx)
|
||||
break
|
||||
|
||||
case 'DepositPreauth':
|
||||
validateDepositPreauth(tx)
|
||||
break
|
||||
@@ -334,6 +346,10 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateTrustSet(tx)
|
||||
break
|
||||
|
||||
case 'XChainAccountCreateCommit':
|
||||
validateXChainAccountCreateCommit(tx)
|
||||
break
|
||||
|
||||
case 'XChainAddAccountCreateAttestation':
|
||||
validateXChainAddAccountCreateAttestation(tx)
|
||||
break
|
||||
@@ -358,10 +374,6 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateXChainCreateClaimID(tx)
|
||||
break
|
||||
|
||||
case 'XChainAccountCreateCommit':
|
||||
validateXChainAccountCreateCommit(tx)
|
||||
break
|
||||
|
||||
case 'XChainModifyBridge':
|
||||
validateXChainModifyBridge(tx)
|
||||
break
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { DIDSet, DIDDelete } from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { testTransaction } from '../utils'
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000
|
||||
|
||||
describe('DIDDelete', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeEach(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
it(
|
||||
'base',
|
||||
async () => {
|
||||
const setupTx: DIDSet = {
|
||||
TransactionType: 'DIDSet',
|
||||
Account: testContext.wallet.address,
|
||||
Data: '617474657374',
|
||||
DIDDocument: '646F63',
|
||||
URI: '6469645F6578616D706C65',
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, setupTx, testContext.wallet)
|
||||
|
||||
// double check the DID was properly created
|
||||
const initialAccountOffersResponse = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.address,
|
||||
type: 'did',
|
||||
})
|
||||
assert.lengthOf(
|
||||
initialAccountOffersResponse.result.account_objects,
|
||||
1,
|
||||
'Should be exactly one DID on the ledger after a DIDSet transaction',
|
||||
)
|
||||
|
||||
// actual test - cancel the check
|
||||
const tx: DIDDelete = {
|
||||
TransactionType: 'DIDDelete',
|
||||
Account: testContext.wallet.address,
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, tx, testContext.wallet)
|
||||
|
||||
// confirm that the DID no longer exists
|
||||
const accountOffersResponse = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.address,
|
||||
type: 'did',
|
||||
})
|
||||
assert.lengthOf(
|
||||
accountOffersResponse.result.account_objects,
|
||||
0,
|
||||
'Should be no DID on the ledger after a DIDDelete transaction',
|
||||
)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
50
packages/xrpl/test/integration/transactions/didSet.test.ts
Normal file
50
packages/xrpl/test/integration/transactions/didSet.test.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { DIDSet } from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { testTransaction } from '../utils'
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000
|
||||
|
||||
describe('DIDSet', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeEach(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
it(
|
||||
'base',
|
||||
async () => {
|
||||
const tx: DIDSet = {
|
||||
TransactionType: 'DIDSet',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
Data: '617474657374',
|
||||
DIDDocument: '646F63',
|
||||
URI: '6469645F6578616D706C65',
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, tx, testContext.wallet)
|
||||
|
||||
// confirm that the DID was actually created
|
||||
const accountOffersResponse = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
type: 'did',
|
||||
})
|
||||
assert.lengthOf(
|
||||
accountOffersResponse.result.account_objects,
|
||||
1,
|
||||
'Should be exactly one DID on the ledger after a DIDSet transaction',
|
||||
)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
34
packages/xrpl/test/models/DIDDelete.test.ts
Normal file
34
packages/xrpl/test/models/DIDDelete.test.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate } from '../../src'
|
||||
import { validateDIDDelete } from '../../src/models/transactions/DIDDelete'
|
||||
|
||||
/**
|
||||
* DIDDelete Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('DIDDelete', function () {
|
||||
let tx
|
||||
|
||||
beforeEach(function () {
|
||||
tx = {
|
||||
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
|
||||
Fee: '10',
|
||||
Flags: 2147483648,
|
||||
Sequence: 4,
|
||||
TransactionType: 'DIDDelete',
|
||||
} as any
|
||||
})
|
||||
|
||||
it('verifies valid DIDDelete', function () {
|
||||
assert.doesNotThrow(() => validateDIDDelete(tx))
|
||||
assert.doesNotThrow(() => validate(tx))
|
||||
})
|
||||
|
||||
it('throws on invalid DIDDelete', function () {
|
||||
tx.FakeField = 'blah'
|
||||
assert.doesNotThrow(() => validateDIDDelete(tx))
|
||||
assert.doesNotThrow(() => validate(tx))
|
||||
})
|
||||
})
|
||||
76
packages/xrpl/test/models/DIDSet.test.ts
Normal file
76
packages/xrpl/test/models/DIDSet.test.ts
Normal file
@@ -0,0 +1,76 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import { validateDIDSet } from '../../src/models/transactions/DIDSet'
|
||||
|
||||
/**
|
||||
* DIDSet Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('DIDSet', function () {
|
||||
let tx
|
||||
|
||||
beforeEach(function () {
|
||||
tx = {
|
||||
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
|
||||
Data: '617474657374',
|
||||
DIDDocument: '646F63',
|
||||
Fee: '10',
|
||||
Flags: 2147483648,
|
||||
Sequence: 3,
|
||||
TransactionType: 'DIDSet',
|
||||
URI: '6469645F6578616D706C65',
|
||||
} as any
|
||||
})
|
||||
|
||||
it('verifies valid DIDSet', function () {
|
||||
assert.doesNotThrow(() => validateDIDSet(tx))
|
||||
assert.doesNotThrow(() => validate(tx))
|
||||
})
|
||||
|
||||
it('throws w/ invalid Data', function () {
|
||||
tx.Data = 123
|
||||
|
||||
assert.throws(
|
||||
() => validateDIDSet(tx),
|
||||
ValidationError,
|
||||
'DIDSet: invalid field Data',
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(tx),
|
||||
ValidationError,
|
||||
'DIDSet: invalid field Data',
|
||||
)
|
||||
})
|
||||
|
||||
it('throws w/ invalid DIDDocument', function () {
|
||||
tx.DIDDocument = 123
|
||||
|
||||
assert.throws(
|
||||
() => validateDIDSet(tx),
|
||||
ValidationError,
|
||||
'DIDSet: invalid field DIDDocument',
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(tx),
|
||||
ValidationError,
|
||||
'DIDSet: invalid field DIDDocument',
|
||||
)
|
||||
})
|
||||
|
||||
it('throws w/ invalid URI', function () {
|
||||
tx.URI = 123
|
||||
|
||||
assert.throws(
|
||||
() => validateDIDSet(tx),
|
||||
ValidationError,
|
||||
'DIDSet: invalid field URI',
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(tx),
|
||||
ValidationError,
|
||||
'DIDSet: invalid field URI',
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -4,19 +4,18 @@
|
||||
* 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() {
|
||||
if (process.argv.length < 3) {
|
||||
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} TxName`)
|
||||
process.exit(1)
|
||||
}
|
||||
const modelName = process.argv[2]
|
||||
const filename = `./src/models/transactions/${modelName}.ts`
|
||||
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)
|
||||
}
|
||||
@@ -144,4 +143,12 @@ ${output}`
|
||||
return output
|
||||
}
|
||||
|
||||
main().then(console.log)
|
||||
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
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
const fs = require('fs')
|
||||
const fixtures = require('ripple-binary-codec/test/fixtures/codec-fixtures.json')
|
||||
const path = require('path')
|
||||
const fixtures = require('../../ripple-binary-codec/test/fixtures/codec-fixtures.json')
|
||||
|
||||
const NORMAL_TYPES = ['number', 'string']
|
||||
const NUMBERS = ['0', '1']
|
||||
@@ -9,15 +10,20 @@ function getTx(txName) {
|
||||
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() {
|
||||
const modelName = process.argv[2]
|
||||
const filename = `./packages/xrpl/src/models/transactions/${modelName}.ts`
|
||||
function main(modelName) {
|
||||
const filename = path.join(
|
||||
path.dirname(__filename),
|
||||
`../src/models/transactions/${modelName}.ts`,
|
||||
)
|
||||
const [model, txName] = getModel(filename)
|
||||
return processModel(model, txName)
|
||||
}
|
||||
@@ -61,6 +67,8 @@ function getInvalidValue(paramTypes) {
|
||||
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') {
|
||||
@@ -184,4 +192,12 @@ describe('${txName}', function () {
|
||||
return output
|
||||
}
|
||||
|
||||
console.log(main())
|
||||
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
|
||||
|
||||
273
packages/xrpl/tools/generateModels.js
Normal file
273
packages/xrpl/tools/generateModels.js
Normal file
@@ -0,0 +1,273 @@
|
||||
/**
|
||||
* A script that generates models and model unit tests.
|
||||
* To run it, clone the rippled branch with the source code and run this script against that repo.
|
||||
*/
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const createValidate = require('./createValidate')
|
||||
const createValidateTests = require('./createValidateTests')
|
||||
|
||||
function readFile(filename) {
|
||||
return fs.readFileSync(filename, 'utf-8')
|
||||
}
|
||||
|
||||
let jsTransactionFile
|
||||
|
||||
function processRippledSource(folder) {
|
||||
const sfieldCpp = readFile(
|
||||
path.join(folder, 'src/ripple/protocol/impl/SField.cpp'),
|
||||
)
|
||||
const sfieldHits = sfieldCpp.match(
|
||||
/^ *CONSTRUCT_[^\_]+_SFIELD *\( *[^,\n]*,[ \n]*"([^\"\n ]+)"[ \n]*,[ \n]*([^, \n]+)[ \n]*,[ \n]*([0-9]+)(,.*?(notSigning))?/gm,
|
||||
)
|
||||
const sfields = {}
|
||||
for (const hit of sfieldHits) {
|
||||
const matches = hit.match(
|
||||
/^ *CONSTRUCT_[^\_]+_SFIELD *\( *[^,\n]*,[ \n]*"([^\"\n ]+)"[ \n]*,[ \n]*([^, \n]+)[ \n]*,[ \n]*([0-9]+)(,.*?(notSigning))?/,
|
||||
)
|
||||
sfields[matches[1]] = matches.slice(2)
|
||||
}
|
||||
|
||||
const txFormatsCpp = readFile(
|
||||
path.join(folder, 'src/ripple/protocol/impl/TxFormats.cpp'),
|
||||
)
|
||||
const txFormatsHits = txFormatsCpp.match(
|
||||
/^ *add\(jss::([^\"\n, ]+),[ \n]*tt[A-Z_]+,[ \n]*{[ \n]*(({sf[A-Za-z0-9]+, soe(OPTIONAL|REQUIRED|DEFAULT)},[ \n]+)*)},[ \n]*[pseudocC]+ommonFields\);/gm,
|
||||
)
|
||||
const txFormats = {}
|
||||
for (const hit of txFormatsHits) {
|
||||
const matches = hit.match(
|
||||
/^ *add\(jss::([^\"\n, ]+),[ \n]*tt[A-Z_]+,[ \n]*{[ \n]*(({sf[A-Za-z0-9]+, soe(OPTIONAL|REQUIRED|DEFAULT)},[ \n]+)*)},[ \n]*[pseudocC]+ommonFields\);/,
|
||||
)
|
||||
txFormats[matches[1]] = formatTxFormat(matches[2])
|
||||
}
|
||||
|
||||
jsTransactionFile = readFile(
|
||||
path.join(
|
||||
path.dirname(__filename),
|
||||
'../src/models/transactions/transaction.ts',
|
||||
),
|
||||
)
|
||||
const transactionMatch = jsTransactionFile.match(
|
||||
/export type Transaction =([| \nA-Za-z]+)\nexport/,
|
||||
)[0]
|
||||
const existingLibraryTxs = transactionMatch
|
||||
.replace('\n\nexport', '')
|
||||
.split('\n | ')
|
||||
.filter((value) => !value.includes('export type'))
|
||||
.map((value) => value.trim())
|
||||
existingLibraryTxs.push('EnableAmendment', 'SetFee', 'UNLModify')
|
||||
|
||||
const txsToAdd = []
|
||||
|
||||
for (const tx in txFormats) {
|
||||
if (!existingLibraryTxs.includes(tx)) {
|
||||
txsToAdd.push(tx)
|
||||
}
|
||||
}
|
||||
|
||||
return [txsToAdd, txFormats, sfields, transactionMatch]
|
||||
}
|
||||
|
||||
function formatTxFormat(rawTxFormat) {
|
||||
return rawTxFormat
|
||||
.trim()
|
||||
.split('\n')
|
||||
.map((element) => element.trim().replace(/[{},]/g, '').split(' '))
|
||||
}
|
||||
|
||||
const typeMap = {
|
||||
UINT8: 'number',
|
||||
UINT16: 'number',
|
||||
UINT32: 'number',
|
||||
UINT64: 'number | string',
|
||||
UINT128: 'string',
|
||||
UINT160: 'string',
|
||||
UINT256: 'string',
|
||||
AMOUNT: 'Amount',
|
||||
VL: 'string',
|
||||
ACCOUNT: 'string',
|
||||
VECTOR256: 'string[]',
|
||||
PATHSET: 'Path[]',
|
||||
ISSUE: 'Currency',
|
||||
XCHAIN_BRIDGE: 'XChainBridge',
|
||||
OBJECT: 'any',
|
||||
ARRAY: 'any[]',
|
||||
}
|
||||
|
||||
const allCommonImports = ['Amount', 'Currency', 'Path', 'XChainBridge']
|
||||
const additionalValidationImports = ['string', 'number']
|
||||
|
||||
function updateTransactionFile(transactionMatch, tx) {
|
||||
const transactionMatchSplit = transactionMatch.split('\n | ')
|
||||
const firstLine = transactionMatchSplit[0]
|
||||
const allTransactions = transactionMatchSplit.slice(1)
|
||||
allTransactions.push(tx)
|
||||
allTransactions.sort()
|
||||
const newTransactionMatch =
|
||||
firstLine + '\n | ' + allTransactions.join('\n | ')
|
||||
let newJsTxFile = jsTransactionFile.replace(
|
||||
transactionMatch,
|
||||
newTransactionMatch,
|
||||
)
|
||||
|
||||
// Adds the imports to the end of the imports
|
||||
newJsTxFile = newJsTxFile.replace(
|
||||
`import {
|
||||
XChainModifyBridge,
|
||||
validateXChainModifyBridge,
|
||||
} from './XChainModifyBridge'`,
|
||||
`import {
|
||||
XChainModifyBridge,
|
||||
validateXChainModifyBridge,
|
||||
} from './XChainModifyBridge'
|
||||
import {
|
||||
${tx},
|
||||
validate${tx},
|
||||
} from './${tx}'`,
|
||||
)
|
||||
|
||||
const validationMatch = newJsTxFile.match(
|
||||
/switch \(tx.TransactionType\) {\n([ \nA-Za-z':()]+)default/,
|
||||
)[1]
|
||||
const caseValidations = validationMatch.split('\n\n')
|
||||
caseValidations.push(
|
||||
` case '${tx}':\n validate${tx}(tx)\n break`,
|
||||
)
|
||||
caseValidations.sort()
|
||||
newJsTxFile = newJsTxFile.replace(
|
||||
validationMatch,
|
||||
caseValidations.join('\n\n') + '\n\n ',
|
||||
)
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(
|
||||
path.dirname(__filename),
|
||||
'../src/models/transactions/transaction.ts',
|
||||
),
|
||||
newJsTxFile,
|
||||
)
|
||||
|
||||
transactionMatch = newTransactionMatch
|
||||
jsTransactionFile = newJsTxFile
|
||||
}
|
||||
|
||||
function updateIndexFile(tx) {
|
||||
const filename = path.join(
|
||||
path.dirname(__filename),
|
||||
'../src/models/transactions/index.ts',
|
||||
)
|
||||
let indexFile = readFile(filename)
|
||||
indexFile = indexFile.replace(
|
||||
`} from './XChainModifyBridge'`,
|
||||
`} from './XChainModifyBridge'
|
||||
export { ${tx} } from './${tx}'`,
|
||||
)
|
||||
fs.writeFileSync(filename, indexFile)
|
||||
}
|
||||
|
||||
function generateParamLine(sfields, param, isRequired) {
|
||||
const paramName = param.slice(2)
|
||||
const paramType = sfields[paramName][0]
|
||||
const paramTypeOutput = typeMap[paramType]
|
||||
return ` ${paramName}${isRequired ? '' : '?'}: ${paramTypeOutput}\n`
|
||||
}
|
||||
|
||||
async function main(folder) {
|
||||
const [txsToAdd, txFormats, sfields, transactionMatch] =
|
||||
processRippledSource(folder)
|
||||
txsToAdd.forEach(async (tx) => {
|
||||
const txFormat = txFormats[tx]
|
||||
const paramLines = txFormat
|
||||
.filter((param) => param[0] !== '')
|
||||
.sort((a, b) => a[0].localeCompare(b[0]))
|
||||
.map((param) =>
|
||||
generateParamLine(sfields, param[0], param[1] === 'soeREQUIRED'),
|
||||
)
|
||||
paramLines.sort((a, b) => !a.includes('REQUIRED'))
|
||||
const params = paramLines.join('\n')
|
||||
let model = `/**
|
||||
* @category Transaction Models
|
||||
*/
|
||||
export interface ${tx} extends BaseTransaction {
|
||||
TransactionType: '${tx}'
|
||||
|
||||
${params}
|
||||
}`
|
||||
|
||||
const commonImports = []
|
||||
const validationImports = ['BaseTransaction', 'validateBaseTransaction']
|
||||
for (const item of allCommonImports) {
|
||||
if (params.includes(item)) {
|
||||
commonImports.push(item)
|
||||
validationImports.push('is' + item)
|
||||
}
|
||||
}
|
||||
for (const item of additionalValidationImports) {
|
||||
if (params.includes(item)) {
|
||||
validationImports.push(
|
||||
'is' + item.substring(0, 1).toUpperCase() + item.substring(1),
|
||||
)
|
||||
}
|
||||
}
|
||||
if (params.includes('?')) {
|
||||
validationImports.push('validateOptionalField')
|
||||
}
|
||||
if (/[A-Za-z0-9]+:/.test(params)) {
|
||||
validationImports.push('validateRequiredField')
|
||||
}
|
||||
validationImports.sort()
|
||||
const commonImportLine =
|
||||
commonImports.length > 0
|
||||
? `import { ${commonImports.join(', ')} } from '../common'`
|
||||
: ''
|
||||
const validationImportLine = `import { ${validationImports.join(
|
||||
', ',
|
||||
)} } from './common'`
|
||||
let imported_models = `${commonImportLine}
|
||||
|
||||
${validationImportLine}`
|
||||
imported_models = imported_models.replace('\n\n\n\n', '\n\n')
|
||||
imported_models = imported_models.replace('\n\n\n', '\n\n')
|
||||
model = model.replace('\n\n\n\n', '\n\n')
|
||||
fs.writeFileSync(
|
||||
path.join(
|
||||
path.dirname(__filename),
|
||||
`../src/models/transactions/${tx}.ts`,
|
||||
),
|
||||
imported_models + '\n\n' + model,
|
||||
)
|
||||
|
||||
const validate = await createValidate(tx)
|
||||
fs.appendFileSync(
|
||||
path.join(
|
||||
path.dirname(__filename),
|
||||
`../src/models/transactions/${tx}.ts`,
|
||||
),
|
||||
'\n\n' + validate,
|
||||
)
|
||||
|
||||
const validateTests = createValidateTests(tx)
|
||||
fs.writeFileSync(
|
||||
path.join(path.dirname(__filename), `../test/models/${tx}.test.ts`),
|
||||
validateTests,
|
||||
)
|
||||
|
||||
updateTransactionFile(transactionMatch, tx)
|
||||
|
||||
updateIndexFile(tx)
|
||||
|
||||
console.log(`Added ${tx}`)
|
||||
})
|
||||
console.log(
|
||||
'Future steps: Adding docstrings to the models and adding integration tests',
|
||||
)
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
if (process.argv.length < 3) {
|
||||
console.log(`Usage: ${process.argv[0]} ${process.argv[1]} path/to/rippled`)
|
||||
process.exit(1)
|
||||
}
|
||||
main(process.argv[2])
|
||||
}
|
||||
Reference in New Issue
Block a user