diff --git a/contracts/portfolio/trailing_stop.c b/contracts/portfolio/trailing_stop.c new file mode 100644 index 0000000..61f83ae --- /dev/null +++ b/contracts/portfolio/trailing_stop.c @@ -0,0 +1,385 @@ +//------------------------------------------------------------------------------ +/* + Copyright (c) 2024 Transia, LLC + + This financial tool is designed for use by individuals or organizations + that hold the appropriate licenses and qualifications to engage with such + products in a manner that complies with all relevant laws and regulations. + Unauthorized use or redistribution of this tool may result in legal action. + + The content provided in this financial tool is for informational purposes + only and should not be interpreted as financial advice or an endorsement of + any particular investment or financial strategy. Users should seek advice + from a certified professional or financial advisor before making any + investment choices. + + By using this financial tool, the user agrees to indemnify Transia, LLC from + any claims, damages, or losses that may arise from their use or reliance on + the information contained within. The user acknowledges that they are utilizing + this tool at their own risk and that Transia, LLC will not be held responsible + for any direct, indirect, incidental, punitive, special, or consequential + damages, including but not limited to, damages for lost profits, goodwill, + usage, data, or other intangible losses. + +*/ +//============================================================================== + +#include +#include "hookapi.h" + +#define NOPE(x)\ + rollback(SBUF(x), __LINE__); + +#define ACCOUNT_TO_BUF(buf_raw, i) \ + { \ + unsigned char *buf = (unsigned char *)buf_raw; \ + *(uint64_t *)(buf + 0) = *(uint64_t *)(i + 0); \ + *(uint64_t *)(buf + 8) = *(uint64_t *)(i + 8); \ + *(uint32_t *)(buf + 16) = *(uint32_t *)(i + 16); \ + } + +uint8_t sell_txn[305] = + { + /* size,upto */ + /* 3, 0 */ 0x12U, 0x00U, 0x07U, /* tt = OfferCreate */ + /* 5, 3 */ 0x22U, 0x00U, 0x01U, 0x00U, 0x00U, /* flags = tfCanonical */ + /* 5, 8 */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, /* sequence = 0 */ + /* 6, 13 */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, /* first ledger seq */ + /* 6, 19 */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, /* last ledger seq */ + /* 49, 25 */ 0x64U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* taker pays field 9 or 49 bytes */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99, /* cont... */ + /* 49, 74 */ 0x65U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* taker gets field 9 or 49 bytes */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99, /* cont... */ + /* 9, 123 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */ + 0x00U, /* cont... */ + /* 35, 132 */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* pubkey */ + /* 22, 167 */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* src acc */ + /* 116, 189 */ /* emit details */ + /* 34, 305 */ 0x50U, 0x23U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* hash256 id */ + /* 0, 339 */ +}; + +// TX BUILDER +#define SFLAGS_OUT (sell_txn + 4U) +#define SFLS_OUT (sell_txn + 15U) +#define SLLS_OUT (sell_txn + 21U) +#define ST_GETS_OUT (sell_txn + 74U) +#define ST_PAYS_OUT (sell_txn + 25U) +#define SFEE_OUT (sell_txn + 124U) +#define SACCOUNT_OUT (sell_txn + 169U) +#define SEMIT_OUT (sell_txn + 189U) +#define SOID_OUT (sell_txn + 307U) + +uint8_t buy_txn[305] = + { + /* size,upto */ + /* 3, 0 */ 0x12U, 0x00U, 0x07U, /* tt = OfferCreate */ + /* 5, 3 */ 0x22U, 0x00U, 0x01U, 0x00U, 0x00U, /* flags = tfCanonical */ + /* 5, 8 */ 0x24U, 0x00U, 0x00U, 0x00U, 0x00U, /* sequence = 0 */ + /* 6, 13 */ 0x20U, 0x1AU, 0x00U, 0x00U, 0x00U, 0x00U, /* first ledger seq */ + /* 6, 19 */ 0x20U, 0x1BU, 0x00U, 0x00U, 0x00U, 0x00U, /* last ledger seq */ + /* 49, 25 */ 0x64U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* taker pays field 9 or 49 bytes */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99, /* cont... */ + /* 49, 74 */ 0x65U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* taker gets field 9 or 49 bytes */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, 0x99U, /* cont... */ + 0x99, /* cont... */ + /* 9, 123 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */ + 0x00U, /* cont... */ + /* 35, 132 */ 0x73U, 0x21U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* pubkey */ + /* 22, 167 */ 0x81U, 0x14U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* src acc */ + /* 116, 189 */ /* emit details */ + /* 34, 305 */ 0x50U, 0x23U, 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* hash256 id */ + /* 0, 339 */ +}; + +// TX BUILDER +#define BFLAGS_OUT (buy_txn + 4U) +#define BFLS_OUT (buy_txn + 15U) +#define BLLS_OUT (buy_txn + 21U) +#define BT_GETS_OUT (buy_txn + 74U) +#define BT_PAYS_OUT (buy_txn + 25U) +#define BFEE_OUT (buy_txn + 124U) +#define BACCOUNT_OUT (buy_txn + 169U) +#define BEMIT_OUT (buy_txn + 189U) +#define BOID_OUT (buy_txn + 307U) + +#define PREPARE_BUY_TXN(acct_buf, currency_buf, issuer_buf, taker_gets_xfl, taker_pays_xfl) do { \ + uint32_t fls = (uint32_t)ledger_seq() + 1; \ + *((uint32_t *)(BFLS_OUT)) = FLIP_ENDIAN(fls); \ + uint32_t lls = fls + 4; \ + *((uint32_t *)(BLLS_OUT)) = FLIP_ENDIAN(lls); \ + float_sto(BT_PAYS_OUT, 49, currency_buf, 20, issuer_buf, 20, taker_pays_xfl, sfTakerPays); \ + uint64_t drops = float_int(taker_gets_xfl, 6, 1); \ + uint8_t* tp = BT_GETS_OUT + 1; \ + *tp++ = 0b01000000 + (( drops >> 56 ) & 0b00111111 ); \ + *tp++ = (drops >> 48) & 0xFFU; \ + *tp++ = (drops >> 40) & 0xFFU; \ + *tp++ = (drops >> 32) & 0xFFU; \ + *tp++ = (drops >> 24) & 0xFFU; \ + *tp++ = (drops >> 16) & 0xFFU; \ + *tp++ = (drops >> 8) & 0xFFU; \ + *tp++ = (drops >> 0) & 0xFFU; \ + ACCOUNT_TO_BUF(BACCOUNT_OUT, acct_buf); \ + etxn_details(BEMIT_OUT, 116U); \ + int64_t fee = etxn_fee_base(SBUF(buy_txn)); \ + uint8_t *b = BFEE_OUT; \ + *b++ = 0b01000000 + ((fee >> 56) & 0b00111111); \ + *b++ = (fee >> 48) & 0xFFU; \ + *b++ = (fee >> 40) & 0xFFU; \ + *b++ = (fee >> 32) & 0xFFU; \ + *b++ = (fee >> 24) & 0xFFU; \ + *b++ = (fee >> 16) & 0xFFU; \ + *b++ = (fee >> 8) & 0xFFU; \ + *b++ = (fee >> 0) & 0xFFU; \ + TRACEHEX(buy_txn); \ +} while(0) + +#define PREPARE_SELL_TXN(acct_buf, currency_buf, issuer_buf, taker_gets_xfl, taker_pays_xfl) do { \ + uint32_t fls = (uint32_t)ledger_seq() + 1; \ + *((uint32_t *)(SFLS_OUT)) = FLIP_ENDIAN(fls); \ + uint32_t lls = fls + 4; \ + *((uint32_t *)(SLLS_OUT)) = FLIP_ENDIAN(lls); \ + float_sto(ST_GETS_OUT, 49, currency_buf, 20, issuer_buf, 20, taker_gets_xfl, sfTakerGets); \ + uint64_t drops = float_int(taker_pays_xfl, 6, 1); \ + uint8_t* tp = ST_PAYS_OUT + 1; \ + *tp++ = 0b01000000 + (( drops >> 56 ) & 0b00111111 ); \ + *tp++ = (drops >> 48) & 0xFFU; \ + *tp++ = (drops >> 40) & 0xFFU; \ + *tp++ = (drops >> 32) & 0xFFU; \ + *tp++ = (drops >> 24) & 0xFFU; \ + *tp++ = (drops >> 16) & 0xFFU; \ + *tp++ = (drops >> 8) & 0xFFU; \ + *tp++ = (drops >> 0) & 0xFFU; \ + ACCOUNT_TO_BUF(SACCOUNT_OUT, acct_buf); \ + etxn_details(SEMIT_OUT, 116U); \ + int64_t fee = etxn_fee_base(SBUF(sell_txn)); \ + uint8_t *b = SFEE_OUT; \ + *b++ = 0b01000000 + ((fee >> 56) & 0b00111111); \ + *b++ = (fee >> 48) & 0xFFU; \ + *b++ = (fee >> 40) & 0xFFU; \ + *b++ = (fee >> 32) & 0xFFU; \ + *b++ = (fee >> 24) & 0xFFU; \ + *b++ = (fee >> 16) & 0xFFU; \ + *b++ = (fee >> 8) & 0xFFU; \ + *b++ = (fee >> 0) & 0xFFU; \ + TRACEHEX(sell_txn); \ +} while(0) + +int64_t ten_xfl = 6107881094714392576; + +int64_t hook(uint32_t r) +{ + etxn_reserve(2); + + // HookOn: OfferCreate + int64_t tt = otxn_type(); + if (tt != ttINVOKE && tt != ttOFFER_CREATE) + DONE("trailing_stop.da: Passing invalid txn type. HookOn should be changed to avoid this."); + + // ACCOUNT: Originating Transaction + uint8_t otxn_accid[32]; + otxn_field(otxn_accid + 12, 20, sfAccount); + + // ACCOUNT: Hook + uint8_t hook_accid[32]; + hook_account(hook_accid + 12, 20); + + if (BUFFER_EQUAL_20(hook_accid, otxn_accid) && tt != ttINVOKE) + { + accept(SBUF("trailing_stop.da: Outgoing Non Invoke Tx"), __LINE__); + } + + uint8_t op; + otxn_param(&op, 1, "OP", 2); + + // OTXN PARAMATER: Market Price + uint8_t mp_buffer[8]; + uint8_t mp_key[2] = {'M', 'P'}; + int64_t mp_result = otxn_param(SBUF(mp_buffer), mp_key, 2); + int64_t xfl_mp = *((int64_t*)mp_buffer); + + // OTXN PARAMATER: Quantity + uint8_t qty_buffer[8]; + uint8_t qty_key[3] = {'Q', 'T', 'Y'}; + int64_t qty_result = otxn_param(SBUF(qty_buffer), qty_key, 3); + int64_t xfl_qty = *((int64_t*)qty_buffer); + + // OTXN PARAMATER: Issuer (Currency + Issuer) + uint8_t issue_buffer[40]; + uint8_t issue_key[3] = {'I', 'S', 'S'}; + int64_t issue_result = otxn_param(SBUF(issue_buffer), issue_key, 3); + + // OTXN PARAMATER: Lower Percentage + uint8_t lp_buffer[8]; + uint8_t lp_key[2] = {'L', 'P'}; + int64_t lp_result = otxn_param(SBUF(lp_buffer), lp_key, 2); + int64_t xfl_lp = *((int64_t*)lp_buffer); + + // OTXN PARAMATER: Upper Percentage + uint8_t up_buffer[8]; + uint8_t up_key[2] = {'U', 'P'}; + int64_t up_result = otxn_param(SBUF(up_buffer), up_key, 2); + int64_t xfl_up = *((int64_t*)up_buffer); + + uint8_t hash[32]; + util_sha512h(SBUF(hash), issue_buffer, 40); + int64_t sup_result = state_foreign(SBUF(lp_buffer), lp_key, 2, SBUF(hash), hook_accid + 12, 20); + int64_t slp_result = state_foreign(SBUF(up_buffer), up_key, 2, SBUF(hash), hook_accid + 12, 20); + + // action + switch (op) + { + case 'I': + { + if (sup_result != DOESNT_EXIST && slp_result != DOESNT_EXIST) + { + DONE("trailing_stop.da: Issue already initialized."); + } + + int64_t xs_rate_xfl = float_sum(xfl_mp, float_multiply(xfl_mp, xfl_up)); + int64_t sell_tg_xfl = float_divide(ten_xfl, xs_rate_xfl); + TRACEVAR(xs_rate_xfl); + TRACEVAR(sell_tg_xfl); + + // (TakerGets / 1'000'000) / TakerPays.value + + PREPARE_SELL_TXN(hook_accid + 12, issue_buffer, issue_buffer + 20, sell_tg_xfl, ten_xfl); + uint8_t s_emithash[32]; + int64_t s_emit_result = emit(SBUF(s_emithash), SBUF(sell_txn)); + if (s_emit_result < 0) + { + rollback(SBUF("trailing_stop.da: Emit `Upper` Failure."), __LINE__); + } + + + int64_t xb_rate_xfl = float_sum(xfl_mp, float_negate(float_multiply(xfl_mp, xfl_lp))); + int64_t buy_tp_xfl = float_divide(ten_xfl, xb_rate_xfl); + TRACEVAR(xb_rate_xfl); + TRACEVAR(buy_tp_xfl); + + // (TakerPays / 1'000'000) / TakerGets.value + + PREPARE_BUY_TXN(hook_accid + 12, issue_buffer, issue_buffer + 20, ten_xfl, buy_tp_xfl); + uint8_t b_emithash[32]; + int64_t b_emit_result = emit(SBUF(b_emithash), SBUF(buy_txn)); + if (b_emit_result < 0) + { + rollback(SBUF("trailing_stop.da: Emit `Lower` Failure."), __LINE__); + } + + + int64_t _qty_result = state_foreign_set(SBUF(qty_buffer), qty_key, 3, SBUF(hash), hook_accid + 12, 20); + int64_t _up_result = state_foreign_set(SBUF(lp_buffer), lp_key, 2, SBUF(hash), hook_accid + 12, 20); + int64_t _lp_result = state_foreign_set(SBUF(up_buffer), up_key, 2, SBUF(hash), hook_accid + 12, 20); + accept(SBUF("trailing_stop.da: Initialized."), __LINE__); + } + } + + ASSERT(otxn_slot(1) == 1); + ASSERT(slot_subfield(1, sfTakerGets, 2) == 2); + ASSERT(slot_subfield(1, sfTakerPays, 3) == 3); + + uint8_t tg_buffer[48]; + int64_t tg_result = slot(SBUF(tg_buffer), 2); + + uint8_t tp_buffer[48]; + int64_t tp_result = slot(SBUF(tp_buffer), 3); + + int64_t tg_is_native = tg_result == 8; + + uint8_t _hash[32]; + util_sha512h(SBUF(_hash), tg_is_native ? tp_buffer + 8 : tg_buffer + 8, 40); + + int64_t _qty_result = state_foreign(SBUF(qty_buffer), qty_key, 2, SBUF(_hash), hook_accid + 12, 20); + int64_t _xfl_qty = *((int64_t*)qty_buffer); + + int64_t _up_result = state_foreign(SBUF(up_buffer), up_key, 2, SBUF(_hash), hook_accid + 12, 20); + int64_t _xfl_up = *((int64_t*)up_buffer); + + int64_t _lp_result = state_foreign(SBUF(lp_buffer), lp_key, 2, SBUF(_hash), hook_accid + 12, 20); + int64_t _xfl_lp = *((int64_t*)lp_buffer); + + TRACEVAR(_xfl_qty); + TRACEVAR(_xfl_up); + TRACEVAR(_xfl_lp); + + if (tg_is_native) + { + // (sfTakerGets / sfTakerPays.value) / 1000000 + // Buy (Asset Appreciated) + int64_t tg_drops = AMOUNT_TO_DROPS(tg_buffer); + int64_t tg_xfl = float_set(-6, tg_drops); + int64_t tp_xfl = -INT64_FROM_BUF(tp_buffer); + int64_t mp_xfl = float_divide(tg_xfl, tp_xfl); + TRACEVAR(mp_xfl); + + // Update Upper Trail + int64_t xs_rate_xfl = float_sum(mp_xfl, float_multiply(mp_xfl, _xfl_up)); + int64_t sell_tg_xfl = float_divide(ten_xfl, xs_rate_xfl); + TRACEVAR(xs_rate_xfl); + PREPARE_SELL_TXN(hook_accid + 12, tp_buffer + 8, tp_buffer + 28, sell_tg_xfl, ten_xfl); + uint8_t _emithash[32]; + int64_t _emit_result = emit(SBUF(_emithash), SBUF(sell_txn)); + if (_emit_result < 0) + { + rollback(SBUF("trailing_stop.da: Emit `Upper` Failure."), __LINE__); + } + + // Update Lower Trail + int64_t xb_rate_xfl = float_sum(mp_xfl, float_negate(float_multiply(mp_xfl, _xfl_lp))); + int64_t buy_tp_xfl = float_divide(ten_xfl, xb_rate_xfl); + TRACEVAR(xb_rate_xfl); + PREPARE_BUY_TXN(hook_accid + 12, tp_buffer + 8, tp_buffer + 28, ten_xfl, buy_tp_xfl); + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), SBUF(buy_txn)); + if (emit_result < 0) + { + rollback(SBUF("trailing_stop.da: Emit `Lower` Failure."), __LINE__); + } + accept(SBUF("trailing_stop.da: Emit `Update`."), __LINE__); + } + else + { + // (sfTakerPays / sfTakerGets.value) / 1000000 + // Sell (Asset Depreciated) + int64_t tp_drops = AMOUNT_TO_DROPS(tp_buffer); + int64_t tp_xfl = float_set(-6, tp_drops); + int64_t tg_xfl = -INT64_FROM_BUF(tg_buffer); + int64_t mp_xfl = float_divide(tp_xfl, tg_xfl); + int64_t xs_rate_xfl = mp_xfl; + int64_t sell_tp_xfl = float_divide(_xfl_qty, xs_rate_xfl); + PREPARE_SELL_TXN(hook_accid + 12, tg_buffer + 8, tg_buffer + 28, _xfl_qty, sell_tp_xfl); + uint8_t s_emithash[32]; + int64_t s_emit_result = emit(SBUF(s_emithash), SBUF(sell_txn)); + if (s_emit_result < 0) + { + rollback(SBUF("trailing_stop.da: Emit `Sell` Failure."), __LINE__); + } + accept(SBUF("trailing_stop.da: Emit `Sell`."), __LINE__); + } + + rollback(SBUF("trailing_stop.da: Failure."), __LINE__); + + _g(1, 1); + // unreachable + return 0; +} \ No newline at end of file diff --git a/test/integration/portfolio/portfolio.test.ts b/test/integration/portfolio/portfolio.test.ts new file mode 100644 index 0000000..8e62c44 --- /dev/null +++ b/test/integration/portfolio/portfolio.test.ts @@ -0,0 +1,164 @@ +// xrpl +import { + AccountSet, + Invoke, + SetHookFlags, + TransactionMetadata, + AccountSetAsfFlags, +} from '@transia/xrpl' +// xrpl-helpers +import { + XrplIntegrationTestContext, + setupClient, + teardownClient, + serverUrl, + IC, + close, + sell, + trust, + pay, +} from '@transia/hooks-toolkit/dist/npm/src/libs/xrpl-helpers' +// src +import { + Xrpld, + SetHookParams, + setHooksV3, + createHookPayload, + ExecutionUtility, + hexNamespace, + clearHookStateV3, + clearAllHooksV3, + iHook, + iHookParamEntry, + iHookParamName, + iHookParamValue, + floatToLEXfl, +} from '@transia/hooks-toolkit' +import { + currencyToHex, + xrpAddressToHex, +} from '@transia/hooks-toolkit/dist/npm/src/libs/binary-models' + +describe('portfolio', () => { + let testContext: XrplIntegrationTestContext + + beforeAll(async () => { + testContext = await setupClient(serverUrl) + const hookWallet = testContext.hook1 + + // Setup Hook + // Set Weak TSH + const asTx: AccountSet = { + TransactionType: 'AccountSet', + Account: hookWallet.classicAddress, + SetFlag: AccountSetAsfFlags.asfTshCollect, + } + await Xrpld.submit(testContext.client, { + wallet: hookWallet, + tx: asTx, + }) + const XYZ = new IC(testContext.gw.classicAddress, 'XYZ', 100000) + await trust(testContext.client, XYZ.set(100000), ...[testContext.hook1]) + await sell(testContext.client, XYZ.set(20000), testContext.gw, 100) + await pay( + testContext.client, + XYZ.set(10000), + testContext.gw, + ...[testContext.hook1.classicAddress] + ) + + const hook1 = createHookPayload({ + version: 0, + createFile: 'trailing_stop', + namespace: 'portfolio', + flags: SetHookFlags.hsfCollect + SetHookFlags.hsfOverride, + hookOnArray: ['OfferCreate', 'Invoke'], + }) + + await setHooksV3({ + client: testContext.client, + seed: hookWallet.seed, + hooks: [{ Hook: hook1 }], + } as SetHookParams) + }) + afterAll(async () => { + const clearHook = { + Flags: SetHookFlags.hsfNSDelete, + HookNamespace: hexNamespace('portfolio'), + } as iHook + await clearHookStateV3({ + client: testContext.client, + seed: testContext.hook1.seed, + hooks: [{ Hook: clearHook }], + } as SetHookParams) + + await clearAllHooksV3({ + client: testContext.client, + seed: testContext.hook1.seed, + } as SetHookParams) + teardownClient(testContext) + }) + + it('portfolio - success', async () => { + // 100 @ 10% + const quantity = 100 + const marketPrice = 100 + const lowerLimit = 0.1 + const upperLimit = 0.05 + const hookWallet = testContext.hook1 + const txParam1 = new iHookParamEntry( + new iHookParamName('OP'), + new iHookParamValue('I') + ) + const txParam2 = new iHookParamEntry( + new iHookParamName('MP'), + new iHookParamValue(floatToLEXfl(String(marketPrice)), true) + ) + const txParam3 = new iHookParamEntry( + new iHookParamName('QTY'), + new iHookParamValue(floatToLEXfl(String(quantity)), true) + ) + const txParam4 = new iHookParamEntry( + new iHookParamName('ISS'), + new iHookParamValue( + currencyToHex('XYZ') + xrpAddressToHex(testContext.gw.classicAddress), + true + ) + ) + const txParam5 = new iHookParamEntry( + new iHookParamName('LP'), + new iHookParamValue(floatToLEXfl(String(lowerLimit)), true) + ) + const txParam6 = new iHookParamEntry( + new iHookParamName('UP'), + new iHookParamValue(floatToLEXfl(String(upperLimit)), true) + ) + + const builtTx: Invoke = { + TransactionType: 'Invoke', + Account: hookWallet.classicAddress, + HookParameters: [ + txParam1.toXrpl(), + txParam2.toXrpl(), + txParam3.toXrpl(), + txParam4.toXrpl(), + txParam5.toXrpl(), + txParam6.toXrpl(), + ], + } + + const result = await Xrpld.submit(testContext.client, { + wallet: hookWallet, + tx: builtTx, + }) + const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + expect(hookExecutions.executions[0].HookReturnString).toMatch( + 'trailing_stop.da: Initialized.' + ) + + await close(testContext.client) + }) +}) 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