diff --git a/contracts/firewall/base.c b/contracts/firewall/base.c new file mode 100644 index 0000000..8c1f174 --- /dev/null +++ b/contracts/firewall/base.c @@ -0,0 +1,14 @@ +/** + * + */ +#include "hookapi.h" + +int64_t hook(uint32_t reserved) { + + TRACESTR("Base.c: Called."); + accept(SBUF("base: Finished."), __LINE__); + + _g(1,1); + // unreachable + return 0; +} \ No newline at end of file diff --git a/contracts/firewall/firewall_base.c b/contracts/firewall/firewall_base.c new file mode 100644 index 0000000..343f229 --- /dev/null +++ b/contracts/firewall/firewall_base.c @@ -0,0 +1,109 @@ +#include "hookapi.h" +#include +/** + This hook is an omnibus hook that contains 2 different hooks' functionalities. Each of these + can be enabled or disabled and configured using the provided install-time hook parameter as + described below: + + All integer values are little endian unless otherwise marked + + Firewall Hook + Parameter Name: 0x4650 ('FP') + Parameter Value: <20 byte account ID of blocklist provider> + Parameter Name: 0x4649 ('FI') + Parameter Value: + Parameter Name: 0x464F ('FO') + Parameter Value: + Parameter Name: 0x4644 ('FD') + Parameter Value: minimum drops threshold for incoming XRP payments (xfl LE) + Parameter Name: 0x4654 ('FT') + Parameter Value: minimum threshold for incoming trustline payments (xfl LE) + +**/ + +uint8_t tts[32] = { + 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, + 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU,0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU, 0xFFU +}; + +int64_t hook(uint32_t r) +{ + _g(1,1); + + // get the account id + uint8_t otxn_account[20]; + otxn_field(SBUF(otxn_account), sfAccount); + + uint8_t hook_acc[20]; + hook_account(SBUF(hook_acc)); + + uint8_t outgoing = BUFFER_EQUAL_20(hook_acc, otxn_account); + + uint32_t tt = otxn_type(); + + if (tt == 22) + accept(SBUF("Firewall: Ignoring SetHook txn"), __LINE__); + + // get the relevant amount, if any + int64_t amount = -1; + int64_t amount_native; + otxn_slot(1); + + if (slot_subfield(1, sfAmount, 1) == 1) + { + amount = slot_float(1); + amount_native = slot_size(1) == 8; + } + + // check flags + uint8_t flagbuf[4]; + otxn_field(SBUF(flagbuf), sfFlags); + + // Blocklist + { + uint8_t param_name[2] = {'F', 'P'}; + uint8_t provider[20]; + hook_param(SBUF(provider), SBUF(param_name)); + uint8_t dummy[64]; + if (state_foreign(dummy, 32, SBUF(otxn_account), dummy + 32, 32, SBUF(provider)) > 0) + rollback(SBUF("Firewall: Blocklist match"), __LINE__); + } + + // Firewall + { + // check allowable txn types + { + uint8_t param_name[2] = {'F', outgoing ? 'O' : 'I'}; + + hook_param(tts, 32, SBUF(param_name)); + + // check if its on the list of blocked txn types + if (tts[tt >> 3] & (tt % 8)) + rollback(SBUF("Firewall: Txn type"), __LINE__); + + } + + // if its an incoming payment ensure it passes the threshold + if (!outgoing && amount >= 0) + { + + if (flagbuf[2] & 2U) + rollback(SBUF("Firewall: Incoming partial payment"), __LINE__); + + TRACEVAR(amount_native); + // threshold for drops + uint8_t param_name[2] = {'F', amount_native ? 'D' : 'T'}; + TRACEHEX(param_name); + + // if the parameter doesn't exist then the threshold is unlimited or rather 9.999999999999999e+95 + int64_t threshold = 7810234554605699071LL; + hook_param(&threshold, 8, SBUF(param_name)); + TRACEVAR(amount); + TRACEVAR(threshold); + if (float_compare(amount, threshold, COMPARE_LESS) == 1) + rollback(SBUF("Firewall: Amount below threshold"), __LINE__); + } + } + + return accept(SBUF("Firewall: Txn within thresholds"), __LINE__); +} \ No newline at end of file diff --git a/contracts/firewall/firewall_provider.c b/contracts/firewall/firewall_provider.c new file mode 100644 index 0000000..91139d0 --- /dev/null +++ b/contracts/firewall/firewall_provider.c @@ -0,0 +1,89 @@ +#include "hookapi.h" +#include + +#define ASSERT(x)\ +{\ + if (!(x))\ + rollback(0,0,__LINE__);\ +} + +#define DONE()\ + accept(0,0,__LINE__) + +/** + * Account Owner can: + * ttINVOKE: + * Blob: (packed data) (up to 32) + * ( 1 byte action type + 20 byte account id ) + + * + * Action Type: 0 to remove account, 1 to add account + */ +int64_t hook(uint32_t r) +{ + _g(1,1); + + // pass anything that isn't a ttINVOKE + if (otxn_type() != ttINVOKE) + DONE(); + + // get the account id + uint8_t account_field[20]; + ASSERT(otxn_field(SBUF(account_field), sfAccount) == 20); + + uint8_t hook_accid[20]; + hook_account(SBUF(hook_accid)); + + // ignore anything that isn't a self invoke + if (!BUFFER_EQUAL_20(hook_accid, account_field)) + DONE(); + + // if there's a destination set + uint8_t dest[20]; + if (otxn_field(SBUF(dest), sfDestination) == 20) + { + if (!BUFFER_EQUAL_20(hook_accid, dest)) + { + // .. then they are invoking someone else's hook + // and we need to not interfere with that. + DONE(); + } + } + + uint8_t txn_id[32]; + ASSERT(otxn_id(txn_id, 32, 0) == 32); + +#define sfBlob ((7U << 16U) + 26U) + + ASSERT(otxn_slot(1) == 1); + ASSERT(slot_subfield(1, sfBlob, 2) == 2); + + uint8_t buffer[676]; + + ASSERT(slot(SBUF(buffer), 2) > 0); + + trace(SBUF("blob-buffer"), buffer, 676, 1); + + uint16_t len = (uint16_t)buffer[0]; + uint8_t* ptr = buffer + 1; + if (len > 192) + { + len = 193 + ((len - 193) * 256) + ((uint16_t)(buffer[1])); + ptr++; + } + + uint8_t* end = ptr + len; + + // execution to here means it's a valid modification instruction + while (ptr < end) + { + GUARD(32); + + uint8_t* dptr = *ptr == 0 ? txn_id : 0; + uint64_t dlen = *ptr == 0 ? 32 : 0; + ASSERT(state_set(dptr, dlen, ptr+1, 20) == dlen); + ptr += 21; + } + + DONE(); + +} \ No newline at end of file diff --git a/contracts/utils/hookapi.h b/contracts/utils/hookapi.h index a97a488..9627f8a 100644 --- a/contracts/utils/hookapi.h +++ b/contracts/utils/hookapi.h @@ -53,5 +53,6 @@ #include "sfcodes.h" #include "macro.h" #include "date.h" +#include "tts.h" #endif diff --git a/contracts/utils/macro.h b/contracts/utils/macro.h index 7039092..f25c21f 100644 --- a/contracts/utils/macro.h +++ b/contracts/utils/macro.h @@ -301,43 +301,6 @@ int out_len = 0;\ if (i < 0) buf[0] |= 0x80U;\ } -#define ttPAYMENT 0 -#define ttESCROW_CREATE 1 -#define ttESCROW_FINISH 2 -#define ttACCOUNT_SET 3 -#define ttESCROW_CANCEL 4 -#define ttREGULAR_KEY_SET 5 -#define ttOFFER_CREATE 7 -#define ttOFFER_CANCEL 8 -#define ttTICKET_CREATE 10 -#define ttSIGNER_LIST_SET 12 -#define ttPAYCHAN_CREATE 13 -#define ttPAYCHAN_FUND 14 -#define ttPAYCHAN_CLAIM 15 -#define ttCHECK_CREATE 16 -#define ttCHECK_CASH 17 -#define ttCHECK_CANCEL 18 -#define ttDEPOSIT_PREAUTH 19 -#define ttTRUST_SET 20 -#define ttACCOUNT_DELETE 21 -#define ttSET_HOOK 22 -#define ttNFTOKEN_MINT 25 -#define ttNFTOKEN_BURN 26 -#define ttNFTOKEN_CREATE_OFFER 27 -#define ttNFTOKEN_CANCEL_OFFER 28 -#define ttNFTOKEN_ACCEPT_OFFER 29 -#define ttURITOKEN_MINT 45 -#define ttURITOKEN_BURN 46 -#define ttURITOKEN_BUY 47 -#define ttURITOKEN_CREATE_SELL_OFFER 48 -#define ttURITOKEN_CANCEL_SELL_OFFER 49 -#define ttIMPORT 97 -#define ttCLAIM_REWARD 98 -#define ttINVOKE 99 -#define ttAMENDMENT 100 -#define ttFEE 101 -#define ttUNL_MODIFY 102 -#define ttEMIT_FAILURE 103 #define tfCANONICAL 0x80000000UL #define atACCOUNT 1U diff --git a/contracts/utils/tts.h b/contracts/utils/tts.h new file mode 100644 index 0000000..304685c --- /dev/null +++ b/contracts/utils/tts.h @@ -0,0 +1,42 @@ +// For documentation please see: https://xrpl-hooks.readme.io/reference/ +#define ttPAYMENT 0 +#define ttESCROW_CREATE 1 +#define ttESCROW_FINISH 2 +#define ttACCOUNT_SET 3 +#define ttESCROW_CANCEL 4 +#define ttREGULAR_KEY_SET 5 +// #define ttNICKNAME_SET 6 // deprecated +#define ttOFFER_CREATE 7 +#define ttOFFER_CANCEL 8 +#define ttTICKET_CREATE 10 +// #define ttSPINAL_TAP 11 // deprecated +#define ttSIGNER_LIST_SET 12 +#define ttPAYCHAN_CREATE 13 +#define ttPAYCHAN_FUND 14 +#define ttPAYCHAN_CLAIM 15 +#define ttCHECK_CREATE 16 +#define ttCHECK_CASH 17 +#define ttCHECK_CANCEL 18 +#define ttDEPOSIT_PREAUTH 19 +#define ttTRUST_SET 20 +#define ttACCOUNT_DELETE 21 +#define ttHOOK_SET 22 +#define ttNFTOKEN_MINT 25 +#define ttNFTOKEN_BURN 26 +#define ttNFTOKEN_CREATE_OFFER 27 +#define ttNFTOKEN_CANCEL_OFFER 28 +#define ttNFTOKEN_ACCEPT_OFFER 29 +#define ttURITOKEN_MINT 45 +#define ttURITOKEN_BURN 46 +#define ttURITOKEN_BUY 47 +#define ttURITOKEN_CREATE_SELL_OFFER 48 +#define ttURITOKEN_CANCEL_SELL_OFFER 49 +#define ttGENESIS_MINT 96 +#define ttIMPORT 97 +#define ttCLAIM_REWARD 98 +#define ttINVOKE 99 +#define ttAMENDMENT 100 +#define ttFEE 101 +#define ttUNL_MODIFY 102 +#define ttEMIT_FAILURE 103 +#define ttUNL_REPORT 104 \ No newline at end of file diff --git a/test/integration/firewall/firewallBase.test.ts b/test/integration/firewall/firewallBase.test.ts new file mode 100644 index 0000000..039ec31 --- /dev/null +++ b/test/integration/firewall/firewallBase.test.ts @@ -0,0 +1,340 @@ +import { + Payment, + SetHook, + SetHookFlags, + TransactionMetadata, + calculateHookOn, + xrpToDrops, +} from '@transia/xrpl' +import { IssuedCurrencyAmount } from '@transia/xrpl/dist/npm/models/common' +import { + XrplIntegrationTestContext, + serverUrl, + setupClient, + teardownClient, +} from '@transia/hooks-toolkit/dist/npm/src/libs/xrpl-helpers' +import { + Xrpld, + ExecutionUtility, + createHookPayload, + floatToLEXfl, + setHooksV3, + SetHookParams, + iHookParamEntry, + iHookParamName, + iHookParamValue, + // formatAccountBlob, +} from '@transia/hooks-toolkit' + +// Firewall.Base: ACCEPT: Firewall: Ignoring SetHook txn +// Firewall.Base: ROLLBACK: Firewall: Blocklist match: TODO +// Firewall.Base: ROLLBACK: Firewall: blocking txn type (In): FIX +// Firewall.Base: ROLLBACK: Firewall: blocking txn type (Out): FIX +// Firewall.Base: ROLLBACK: Firewall: blocked incoming partial payment +// Firewall.Base: ROLLBACK: Firewall: Amount below threshold (XRP) +// Firewall.Base: ROLLBACK: Firewall: Amount below threshold (IC) +// Firewall.Base: ACCEPT: Passthrough + +describe('firewall.base - Success Group', () => { + let testContext: XrplIntegrationTestContext + + beforeAll(async () => { + testContext = await setupClient(serverUrl) + }) + afterAll(async () => teardownClient(testContext)) + + it('firewall base - passing sethook tt', async () => { + const param1 = new iHookParamEntry( + new iHookParamName('FI'), + new iHookParamValue(calculateHookOn(['SetHook']), true) + ) + const hook = createHookPayload({ + version: 0, + createFile: 'firewall_base', + namespace: 'firewall_base', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['SetHook'], + hookParams: [param1.toXrpl()], + }) + await setHooksV3({ + client: testContext.client, + seed: testContext.alice.seed, + hooks: [{ Hook: hook }], + } as SetHookParams) + + const aliceWallet = testContext.alice + const builtTx: SetHook = { + TransactionType: 'SetHook', + Account: aliceWallet.address, + Hooks: [ + { + Hook: createHookPayload({ + version: 0, + createFile: 'base', + namespace: 'base', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['Payment'], + }), + }, + ], + } + + const result = await Xrpld.submit(testContext.client, { + wallet: aliceWallet, + tx: builtTx, + }) + + const hookEmitted = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + expect(hookEmitted.executions[0].HookReturnString).toMatch( + 'Firewall: Ignoring SetHook txn' + ) + }) + + it('firewall base - block in tt', async () => { + const param1 = new iHookParamEntry( + new iHookParamName('FI'), + new iHookParamValue(calculateHookOn(['SetHook']), true) + ) + console.log(param1.toXrpl()) + + const hook = createHookPayload({ + version: 0, + createFile: 'firewall_base', + namespace: 'firewall_base', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['Payment'], + hookParams: [param1.toXrpl()], + }) + await setHooksV3({ + client: testContext.client, + seed: testContext.alice.seed, + hooks: [{ Hook: hook }], + } as SetHookParams) + + try { + // PAYMENT IN + const aliceWallet = testContext.alice + const bobWallet = testContext.bob + const builtTx: Payment = { + TransactionType: 'Payment', + Account: bobWallet.classicAddress, + Destination: aliceWallet.classicAddress, + Amount: xrpToDrops(100), + } + await Xrpld.submit(testContext.client, { + wallet: bobWallet, + tx: builtTx, + }) + throw Error('Expected Failure') + } catch (error: unknown) { + if (error instanceof Error) { + expect(error.message).toMatch('Firewall: Txn type') + } + } + }) + + it('firewall base - block out tt', async () => { + const param1 = new iHookParamEntry( + new iHookParamName('FO'), + new iHookParamValue(calculateHookOn(['SetHook']), true) + ) + + const hook = createHookPayload({ + version: 0, + createFile: 'firewall_base', + namespace: 'firewall_base', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['Payment'], + hookParams: [param1.toXrpl()], + }) + await setHooksV3({ + client: testContext.client, + seed: testContext.alice.seed, + hooks: [{ Hook: hook }], + } as SetHookParams) + + try { + // PAYMENT OUT + const aliceWallet = testContext.alice + const carolWallet = testContext.carol + const builtTx: Payment = { + TransactionType: 'Payment', + Account: aliceWallet.classicAddress, + Destination: carolWallet.classicAddress, + Amount: xrpToDrops(100), + } + await Xrpld.submit(testContext.client, { + wallet: aliceWallet, + tx: builtTx, + }) + throw Error('Expected Failure') + } catch (error: unknown) { + if (error instanceof Error) { + expect(error.message).toMatch('Firewall: Txn type') + } + } + }) + + it('firewall base - Firewall: Amount below threshold (XRP)', async () => { + const param1 = new iHookParamEntry( + new iHookParamName('FI'), + new iHookParamValue(calculateHookOn(['SetHook', 'Payment']), true) + ) + const param2 = new iHookParamEntry( + new iHookParamName('FD'), + new iHookParamValue(floatToLEXfl('100'), true) + ) + + const hook = createHookPayload({ + version: 0, + createFile: 'firewall_base', + namespace: 'firewall_base', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['Payment'], + hookParams: [param1.toXrpl(), param2.toXrpl()], + }) + await setHooksV3({ + client: testContext.client, + seed: testContext.alice.seed, + hooks: [{ Hook: hook }], + } as SetHookParams) + + try { + // PAYMENT IN + const aliceWallet = testContext.alice + const carolWallet = testContext.carol + const builtTx: Payment = { + TransactionType: 'Payment', + Account: carolWallet.classicAddress, + Destination: aliceWallet.classicAddress, + Amount: xrpToDrops(99), + } + await Xrpld.submit(testContext.client, { + wallet: carolWallet, + tx: builtTx, + }) + throw Error('Expected Failure') + } catch (error: unknown) { + if (error instanceof Error) { + console.log(error.message) + expect(error.message).toMatch('Firewall: Amount below threshold') + } + } + }) + + it('firewall base - Firewall: Amount below threshold (IC)', async () => { + const param1 = new iHookParamEntry( + new iHookParamName('FI'), + new iHookParamValue(calculateHookOn(['SetHook', 'Payment']), true) + ) + const param2 = new iHookParamEntry( + new iHookParamName('FT'), + new iHookParamValue(floatToLEXfl('100'), true) + ) + + const hook = createHookPayload({ + version: 0, + createFile: 'firewall_base', + namespace: 'firewall_base', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['Payment'], + hookParams: [param1.toXrpl(), param2.toXrpl()], + }) + await setHooksV3({ + client: testContext.client, + seed: testContext.alice.seed, + hooks: [{ Hook: hook }], + } as SetHookParams) + + const amount: IssuedCurrencyAmount = { + value: '99', + currency: 'USD', + issuer: testContext.gw.classicAddress, + } + + try { + // PAYMENT IN + const aliceWallet = testContext.alice + const carolWallet = testContext.carol + const builtTx: Payment = { + TransactionType: 'Payment', + Account: carolWallet.classicAddress, + Destination: aliceWallet.classicAddress, + Amount: amount, + } + await Xrpld.submit(testContext.client, { + wallet: carolWallet, + tx: builtTx, + }) + throw Error('Expected Failure') + } catch (error: unknown) { + if (error instanceof Error) { + expect(error.message).toMatch('Firewall: Amount below threshold') + } + } + }) + + it('firewall - passthrough', async () => { + const param1 = new iHookParamEntry( + new iHookParamName('FI'), + new iHookParamValue(calculateHookOn(['SetHook', 'Payment']), true) + ) + const param2 = new iHookParamEntry( + new iHookParamName('FO'), + new iHookParamValue(calculateHookOn(['SetHook']), true) + ) + const param3 = new iHookParamEntry( + new iHookParamName('FD'), + new iHookParamValue(floatToLEXfl('99'), true) + ) + const param4 = new iHookParamEntry( + new iHookParamName('FT'), + new iHookParamValue(floatToLEXfl('99'), true) + ) + + const hook = createHookPayload({ + version: 0, + createFile: 'firewall_base', + namespace: 'firewall_base', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['Payment'], + hookParams: [ + param1.toXrpl(), + param2.toXrpl(), + param3.toXrpl(), + param4.toXrpl(), + ], + }) + + await setHooksV3({ + client: testContext.client, + seed: testContext.alice.seed, + hooks: [{ Hook: hook }], + } as SetHookParams) + + // PAYMENT IN + const aliceWallet = testContext.alice + const carolWallet = testContext.carol + const builtTx: Payment = { + TransactionType: 'Payment', + Account: carolWallet.classicAddress, + Destination: aliceWallet.classicAddress, + Amount: xrpToDrops(99), + } + const result = await Xrpld.submit(testContext.client, { + wallet: carolWallet, + tx: builtTx, + }) + + const hookEmitted = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + expect(hookEmitted.executions[0].HookReturnString).toMatch( + 'Firewall: Txn within thresholds' + ) + }) +}) diff --git a/test/integration/firewall/firewallProvider.test.ts b/test/integration/firewall/firewallProvider.test.ts new file mode 100644 index 0000000..f193850 --- /dev/null +++ b/test/integration/firewall/firewallProvider.test.ts @@ -0,0 +1,190 @@ +import { + Invoke, + LedgerEntryRequest, + Payment, + SetHookFlags, + TransactionMetadata, + xrpToDrops, +} from '@transia/xrpl' +import { + HookDefinition as LeHookDefinition, + Hook as LeHook, +} from '@transia/xrpl/dist/npm/models/ledger' +import { + XrplIntegrationTestContext, + setupClient, + teardownClient, + serverUrl, +} from '@transia/hooks-toolkit/dist/npm/src/libs/xrpl-helpers' +import { + Xrpld, + ExecutionUtility, + createHookPayload, + setHooksV3, + SetHookParams, + formatAccountBlob, + StateUtility, + padHexString, + // padHexString, +} from '@transia/hooks-toolkit' +import { xrpAddressToHex } from '@transia/hooks-toolkit/dist/npm/src/libs/binary-models' + +// Firewall.Provider: ACCEPT: TT != Invoke +// Firewall.Provider: ACCEPT: TX Destination != HOOK + +// Firewall.Provider: ACCEPT: Add Account +// Firewall.Provider: ACCEPT: Remove Account +// Firewall.Provider: ACCEPT: Add Mutliple - TODO +// Firewall.Provider: ACCEPT: Remove Mutliple - TODO + +// Firewall.Provider: ACCEPT: Test Max - TODO +// Firewall.Provider: ACCEPT: Test Fee for exponential growth - TODO + +describe('firewall provider - Success Group', () => { + let testContext: XrplIntegrationTestContext + + beforeAll(async () => { + testContext = await setupClient(serverUrl) + const hook = createHookPayload({ + version: 0, + createFile: 'firewall_provider', + namespace: 'firewall_provider', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['Invoke'], + }) + await setHooksV3({ + client: testContext.client, + seed: testContext.alice.seed, + hooks: [{ Hook: hook }], + } as SetHookParams) + }) + afterAll(async () => teardownClient(testContext)) + + it('firewall provider - tt != invoke', async () => { + // PAYMENT IN + const aliceWallet = testContext.alice + const bobWallet = testContext.bob + const builtTx: Payment = { + TransactionType: 'Payment', + Account: bobWallet.classicAddress, + Destination: aliceWallet.classicAddress, + Amount: xrpToDrops(100), + } + const result = await Xrpld.submit(testContext.client, { + wallet: bobWallet, + tx: builtTx, + }) + + const metadata = result.meta as TransactionMetadata + expect(metadata.HookExecutions).toBe(undefined) + }) + + it('firewall provider - tx dest != hook', async () => { + // INVOKE OUT + const aliceWallet = testContext.alice + const bobWallet = testContext.bob + const builtTx: Invoke = { + TransactionType: 'Invoke', + Account: bobWallet.classicAddress, + Destination: aliceWallet.classicAddress, + } + const result = await Xrpld.submit(testContext.client, { + wallet: bobWallet, + tx: builtTx, + }) + const hookEmitted = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + expect(hookEmitted.executions[0].HookReturnString).toMatch('') + }) + + it('firewall provider - add account state', async () => { + // INVOKE OUT + const aliceWallet = testContext.alice + const blob = formatAccountBlob([testContext.carol.classicAddress]) + const builtTx: Invoke = { + TransactionType: 'Invoke', + Account: aliceWallet.classicAddress, + Blob: blob, + } + const result = await Xrpld.submit(testContext.client, { + wallet: aliceWallet, + tx: builtTx, + }) + const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + // DONEEMPTY + expect(hookExecutions.executions[0].HookReturnString).toEqual('') + + const leHook = await StateUtility.getHook( + testContext.client, + testContext.alice.classicAddress + ) + const hookDefRequest: LedgerEntryRequest = { + command: 'ledger_entry', + hook_definition: leHook.Hooks[0].Hook.HookHash, + } + const hookDefRes = await testContext.client.request(hookDefRequest) + const leHookDef = hookDefRes.result.node as LeHookDefinition + const hookState = await StateUtility.getHookState( + testContext.client, + testContext.alice.classicAddress, + padHexString(xrpAddressToHex(testContext.carol.classicAddress)), + leHookDef.HookNamespace as string + ) + expect(hookState.HookStateData).toBeDefined() + }) + + it('firewall provider - remove account state', async () => { + // INVOKE OUT + const aliceWallet = testContext.alice + const blob = formatAccountBlob([], [testContext.carol.classicAddress]) + const builtTx: Invoke = { + TransactionType: 'Invoke', + Account: aliceWallet.classicAddress, + Blob: blob, + } + const result = await Xrpld.submit(testContext.client, { + wallet: aliceWallet, + tx: builtTx, + }) + + const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + // DONEEMPTY + expect(hookExecutions.executions[0].HookReturnString).toEqual('') + + try { + const hookReq: LedgerEntryRequest = { + command: 'ledger_entry', + hook: { + account: testContext.alice.classicAddress, + }, + } + const hookRes = await testContext.client.request(hookReq) + const leHook = hookRes.result.node as LeHook + const hookDefRequest: LedgerEntryRequest = { + command: 'ledger_entry', + hook_definition: leHook.Hooks[0].Hook.HookHash, + } + const hookDefRes = await testContext.client.request(hookDefRequest) + const leHookDef = hookDefRes.result.node as LeHookDefinition + await StateUtility.getHookState( + testContext.client, + testContext.alice.classicAddress, + padHexString(xrpAddressToHex(testContext.carol.classicAddress)), + leHookDef.HookNamespace as string + ) + throw Error('Expected Failure') + } catch (error: unknown) { + if (error instanceof Error) { + expect(error.message).toEqual('entryNotFound') + } + } + }) +}) diff --git a/test/integration/router/README.md b/test/integration/router/README.md index 16e93b1..90846c6 100644 --- a/test/integration/router/README.md +++ b/test/integration/router/README.md @@ -51,7 +51,7 @@ The router hook logic is implemented in `router_base.c`. It reads the hook posit To debug the router hook, you can monitor the `xrpld` logs for `HookTrace` entries: ```shell -tail -f xrpld/log/debug.log | grep HookTrace +tail -f xahau/log/debug.log | grep HookTrace ``` ## Contributing