HookOnV2 Amendment (#51)

This commit is contained in:
tequ
2026-06-18 21:11:57 +09:00
committed by GitHub
parent 0c6578156f
commit 18d343fcb5
6 changed files with 108 additions and 24 deletions

View File

@@ -5,6 +5,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
## Unreleased Changes
### Added
* Support for HookOnV2 Amendment
* Support for IOUClaimReward Amendment
* Support for NamedHooks
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))

View File

@@ -86,6 +86,14 @@ export interface Hook {
* The transactions that triggers the hook. Represented as a 256Hash
*/
HookOn?: string
/**
* The incoming transactions that triggers to the hook. Represented as a 256Hash
*/
HookOnIncoming?: string
/**
* The outgoing transactions that triggers from the hook. Represented as a 256Hash
*/
HookOnOutgoing?: string
/**
* The transactions that can emit from the hook. Represented as a 256Hash
*/

View File

@@ -27,6 +27,16 @@ export default interface HookDefintion
*/
HookOn?: string
/**
* The incoming transactions that triggers to the hook. Represented as a 256Hash
*/
HookOnIncoming?: string
/**
* The outgoing transactions that triggers from the hook. Represented as a 256Hash
*/
HookOnOutgoing?: string
/**
* The namespace of the hook.
*/

View File

@@ -29,6 +29,7 @@ const HOOKNAME_REGEX = /^[0-9A-Fa-f]{8,32}$/u
* @param tx - An SetHook Transaction.
* @throws When the SetHook is Malformed.
*/
// eslint-disable-next-line max-lines-per-function -- okay for this method
export function validateSetHook(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
@@ -45,12 +46,29 @@ export function validateSetHook(tx: Record<string, unknown>): void {
for (const hook of tx.Hooks) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be a Hook
const hookObject = hook as Hook
const { HookOn, HookCanEmit, HookNamespace, HookName } = hookObject.Hook
const {
HookOn,
HookOnIncoming,
HookOnOutgoing,
HookCanEmit,
HookNamespace,
HookName,
} = hookObject.Hook
if (HookOn !== undefined && !HEX_REGEX.test(HookOn)) {
throw new ValidationError(
`SetHook: HookOn in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookOnIncoming !== undefined && !HEX_REGEX.test(HookOnIncoming)) {
throw new ValidationError(
`SetHook: HookOnIncoming in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookOnOutgoing !== undefined && !HEX_REGEX.test(HookOnOutgoing)) {
throw new ValidationError(
`SetHook: HookOnOutgoing in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookCanEmit !== undefined && !HEX_REGEX.test(HookCanEmit)) {
throw new ValidationError(
`SetHook: HookCanEmit in Hook must be a 256-bit (32-byte) hexadecimal value`,

View File

@@ -93,4 +93,46 @@ describe('SetHook', function () {
},
TIMEOUT,
)
it(
'hook on incoming/outgoing',
async () => {
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: wallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOnIncoming: '00'.repeat(32),
// eslint-disable-next-line no-inline-comments -- for readability
HookOnOutgoing: `01${'00'.repeat(31)}`, // should be different from HookOnIncoming
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, wallet)
const ledgerEntryResponse = await testContext.client.request({
command: 'ledger_entry',
hook: { account: wallet.classicAddress },
})
const node = ledgerEntryResponse.result.node as Hook
const hook = node.Hooks[0].Hook
const hookHash = hook.HookHash!
const hookDefinitionResponse = await testContext.client.request({
command: 'ledger_entry',
hook_definition: hookHash,
})
const hookDefinitionNode = hookDefinitionResponse.result
.node as HookDefinition
expect(hookDefinitionNode.HookOn).toBeUndefined()
expect(hookDefinitionNode.HookOnIncoming).toBeDefined()
expect(hookDefinitionNode.HookOnOutgoing).toBeDefined()
},
TIMEOUT,
)
})

View File

@@ -67,6 +67,10 @@ describe('SetHook', function () {
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
HookOn:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
HookOnIncoming:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
HookOnOutgoing:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
@@ -94,30 +98,31 @@ describe('SetHook', function () {
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
})
it(`throws w/ invalid HookOn in Hooks`, function () {
setHookTx.SignerQuorum = 2
setHookTx.Hooks = [
{
Hook: {
CreateCode:
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
HookOn: '',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
'4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9',
it.each(['HookOn', 'HookOnIncoming', 'HookOnOutgoing'])(
`throws w/ invalid %s in Hooks`,
function (field: string) {
setHookTx.Hooks = [
{
Hook: {
CreateCode:
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
[field]: '',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
'4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9',
},
},
},
]
const errorMessage =
'SetHook: HookOn in Hook must be a 256-bit (32-byte) hexadecimal value'
assert.throws(
() => validateSetHook(setHookTx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
})
]
const errorMessage = `SetHook: ${field} in Hook must be a 256-bit (32-byte) hexadecimal value`
assert.throws(
() => validateSetHook(setHookTx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
},
)
it(`throws w/ invalid HookCanEmit in Hooks`, function () {
setHookTx.Hooks = [