diff --git a/contracts/funds/funds.c b/contracts/funds/funds.c deleted file mode 100644 index ca7a305..0000000 --- a/contracts/funds/funds.c +++ /dev/null @@ -1,452 +0,0 @@ -//------------------------------------------------------------------------------ -/* - 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 DONE(x)\ - return accept(SBUF(x), __LINE__) - -#define NOPE(x)\ - return rollback(SBUF(x), __LINE__) - - -uint8_t txn_out[300] = -{ -/* size,upto */ -/* 3, 0 */ 0x12U, 0x00U, 0x00U, /* tt = Payment */ -/* 5, 3 */ 0x22U, 0x80U, 0x00U, 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 */ -/* 1, 25 */ 0x61U, -/* 8, 26 */ 0,0,0,0,0,0,0,0, /* amt val */ -/* 20, 34 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* amt cur */ -// vvvvvvvvvvvvvvvvvv ISSUER ACC ID vvvvvvvvvvvvvvvvvvvvvvv -/* 20, 54 */ - 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, - -/* 9, 74 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */ -/* 35, 83 */ 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 */ -/* 2, 118 */ 0x81U, 0x14U, /* src acc preamble */ - -/* 20, 120 */ 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, /* src acc */ -/* 2, 140 */ 0x83U, 0x14U, /* dest acc preamble */ -/* 20, 142 */ 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, /* dest acc */ - -/* 138, 162 */ 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,0,0,0,0,0,0,0, /* emit detail */ - 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,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,0,0,0,0,0,0,0,0,0,0 -/* 0, 300 */ -}; - -#define TLACC (txn_out + 54) /* set it above!! */ - -#define TXNLEN 300 -#define FEEOUT (txn_out + 75) -#define EMITDET (txn_out + 162) -#define HOOKACC (txn_out + 120) -#define OTXNACC (txn_out + 142) -#define DESTACC (txn_out + 142) -#define OUTAMT (txn_out + 25) -#define OUTCUR (txn_out + 34) -#define OUTISS (txn_out + 54) -#define CURSHORT (txn_out + 46) - -// if we make a TrustSet instead (used for initialisation) then we'll truncate the template above -#define TTOUT (txn_out + 2) // when it's a TrustSet we set this to 0x14 -#define OUTAMT_TL (txn_out + 25) // when it's a TrustSet, we set this to 0x63 -#define EMITDET_TL (txn_out + 140) // when it's a TrustSet Emit Details occurs sooner -#define TXNLEN_TL 278 // .. and the txn is smaller - -#define BE_DROPS(drops)\ -{\ - uint64_t drops_tmp = drops;\ - uint8_t* b = (uint8_t*)&drops;\ - *b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\ - *b++ = (drops_tmp >> 48) & 0xFFU;\ - *b++ = (drops_tmp >> 40) & 0xFFU;\ - *b++ = (drops_tmp >> 32) & 0xFFU;\ - *b++ = (drops_tmp >> 24) & 0xFFU;\ - *b++ = (drops_tmp >> 16) & 0xFFU;\ - *b++ = (drops_tmp >> 8) & 0xFFU;\ - *b++ = (drops_tmp >> 0) & 0xFFU;\ -} - -#define COPY_20(src, dst)\ -{\ - *((uint64_t*)(((uint8_t*)(dst)) + 0)) = \ - *((uint64_t*)(((uint8_t*)(src)) + 0));\ - *((uint64_t*)(((uint8_t*)(dst)) + 8)) = \ - *((uint64_t*)(((uint8_t*)(src)) + 8));\ - *((uint32_t*)(((uint8_t*)(dst)) + 16)) = \ - *((uint32_t*)(((uint8_t*)(src)) + 16));\ -} - -int64_t cbak(uint32_t f) -{ - // TODO track withdrawal txns to see if they successfully executed - return 0; -} - -int64_t hook(uint32_t r) -{ - _g(1,1); - - etxn_reserve(1); - - hook_account(HOOKACC, 20); - - otxn_field(OTXNACC, 20, sfAccount); - - if (BUFFER_EQUAL_20(HOOKACC, OTXNACC)) - DONE("Funds: passing outgoing txn"); - - int64_t tt = otxn_type(); - if (tt != ttINVOKE && tt != ttPAYMENT) - NOPE("Funds: Rejecting non-Invoke, non-Payment txn."); - - otxn_slot(1); - - slot_subfield(1, sfAmount, 2); - - uint8_t amt[48]; - - if (slot_size(2) == 8) - DONE("Funds: Passing incoming XAH payment."); - - // get admin account - uint8_t admin[20]; - if (hook_param(SBUF(admin), "ADM", 3) != 20) - NOPE("Funds: Misconfigured. Missing ADM install parameter."); - - // get currency - if (hook_param(OUTCUR, 20, "CUR", 3) != 20) - NOPE("Funds: Misconfigured. Missing CUR install parameter."); - - // get currency issuer - if (hook_param(OUTISS, 20, "ISS", 3) != 20) - NOPE("Funds: Misconfigured. Missing ISS install parameter."); - - // get settlement address - uint8_t stl[20]; - if (hook_param(SBUF(stl), "STL", 3) != 20) - NOPE("Funds: Misconfigured. Missing STL install parameter."); - - // get the withdrawal signing key - uint8_t key[33]; - if (hook_param(SBUF(key), "KEY", 3) != 33) - NOPE("Funds: Misconfigured. Missing KEY install parameter."); - - // get signature if any - // Signature format is packed binary data of the form: - // <20 byte dest accid><8 byte le xfl amount><4 byte le int expiry timestamp><4 byte le int nonce> - uint8_t sig_buf[256]; - int64_t sig_len = otxn_param(SBUF(sig_buf), "SIG", 3); - - // place pointers according to packed data - uint8_t* sig_acc = sig_buf; - uint64_t sig_amt = *((uint64_t*)(sig_buf + 20)); - uint32_t sig_exp = *((uint32_t*)(sig_buf + 28)); - uint32_t sig_nce = *((uint32_t*)(sig_buf + 32)); - uint8_t* sig = sig_buf + 36; - - if (sig_len > 0) - { - if (sig_len < 80) - NOPE("Funds: Signature too short."); - - if (!util_verify(sig_buf, 36, sig_buf + 36, sig_len - 36, SBUF(key))) - NOPE("Funds: Signature verification failed."); - } - - uint8_t op; - - if (otxn_param(&op, 1, "OP", 2) != 1) - NOPE("Funds: Missing OP parameter on Invoke."); - - - int64_t xfl_in; - uint32_t flags; - - if (tt == ttPAYMENT) - { - // this will fail if flags isn't in the txn, that's also ok. - otxn_field(&flags, 4, sfFlags); - - // check for partial payments (0x00020000) -> (0x00000200 LE) - if (flags & 0x200U) - NOPE("Funds: Partial payments are not supported."); - - otxn_field(SBUF(amt), sfAmount); - - if (!BUFFER_EQUAL_20(amt + 8, OUTCUR)) - NOPE("Funds: Wrong currency."); - - if (!BUFFER_EQUAL_20(amt + 28, OUTISS)) - NOPE("Funds: Wrong issuer."); - - xfl_in = slot_float(2); - - if (xfl_in < 0 || !float_compare(xfl_in, 0, COMPARE_GREATER)) - NOPE("Funds: Invalid sfAmount."); - } - - - // invoke ops are: I - initalize (admin), S - settle (admin), U/P - un/pause (admin), W - withdraw - - // payment ops are: D - deposit, R - refund (settlement addr) - - int64_t is_admin = BUFFER_EQUAL_20(OTXNACC, admin); - - int64_t is_stl = BUFFER_EQUAL_20(OTXNACC, stl); - - // sanity check - if ((op == 'D' || op == 'R') && tt != ttPAYMENT) - NOPE("Funds: Deposit/Refund operations must be a payment transaction."); - - // permission check - if (!is_admin && (op == 'U' || op == 'P' || op == 'S')) - NOPE("Funds: Admin only operation."); - else if (!is_stl && op == 'R') - NOPE("Funds: Settlement address only operation."); - - // enforced pausedness - if (op != 'U') - { - uint8_t paused; - state(&paused, 1, "P", 1); - if (paused) - NOPE("Funds: Paused."); - } - - // check if the trustline exists - uint8_t keylet[34]; - util_keylet(keylet, 34, KEYLET_LINE, HOOKACC, 20, OUTISS, 20, OUTCUR, 20); - - int64_t already_setup = (slot_set(SBUF(keylet), 10) == 10); - - // enforce initalisation - if (!already_setup && op != 'I') - NOPE("Funds: Send op=I initalisation first."); - - // current ledger seq is used when emitting a txn - int64_t seq = ledger_seq() + 1; - - int64_t time = ledger_last_time(); - - // action - switch (op) - { - case 'I': - { - if (already_setup) - DONE("Funds: Already setup trustline."); - - // create a trustline ... - uint8_t xfl_buffer[8]; - if (otxn_param(xfl_buffer, 8, "AMT", 3) != 8) - NOPE("Funds: Misconfigured. Missing AMT otxn parameter."); - - int64_t xfl_out = *((int64_t *)xfl_buffer); - - // write payment amount - float_sto(OUTAMT_TL, 49, OUTCUR, 20, OUTISS, 20, xfl_out, sfLimitAmount); - - // set the template transaction type to trustset - *TTOUT = 0x14U; - - etxn_details(EMITDET_TL, 138); - int64_t fee = etxn_fee_base(txn_out, TXNLEN_TL); - BE_DROPS(fee); - *((uint64_t*)(FEEOUT)) = fee; - - txn_out[15] = (seq >> 24U) & 0xFFU; - txn_out[16] = (seq >> 16U) & 0xFFU; - txn_out[17] = (seq >> 8U) & 0xFFU; - txn_out[18] = seq & 0xFFU; - - seq += 4; - txn_out[21] = (seq >> 24U) & 0xFFU; - txn_out[22] = (seq >> 16U) & 0xFFU; - txn_out[23] = (seq >> 8U) & 0xFFU; - txn_out[24] = seq & 0xFFU; - - trace(SBUF("emit:"), txn_out, TXNLEN_TL, 1); - - uint8_t emithash[32]; - int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN_TL); - TRACEVAR(emit_result); - DONE("Funds: Emitted TrustSet to initialize."); - } - - case 'U': - case 'P': - { - // pause - uint8_t paused = (op == 'P' ? 1 : 0); - state_set(&paused, 1, "P", 1); - DONE("Funds: Paused/Unpaused."); - } - - case 'R': - { - // refund is simple, do nothing - DONE("Funds: Refunded."); - } - - case 'D': - { - // deposit is simple, do nothing - DONE("Funds: Deposited."); - } - - - // settlement and withdrawal - case 'S': - case 'W': - { - - // check trustline balance - slot_subfield(10, sfBalance, 11); - if (slot_size(11) != 48) - NOPE("Funds: Could not fetch trustline balance."); - - uint8_t low_limit[48]; - if (slot_subfield(10, sfLowLimit, 13) != 13) - NOPE("Funds: Could not slot subfield `sfLowLimit`"); - - if (slot(SVAR(low_limit), 13) != 48) - NOPE("Funds: Could not slot `sfLowLimit`"); - - int64_t xfl_bal = slot_float(11); - - // balance is negative and issuer is not low - if (float_sign(xfl_bal) && !BUFFER_EQUAL_20(OUTISS, low_limit + 28)) - { - NOPE("Funds: Insane balance on trustline."); - } - - xfl_bal = float_sign(xfl_bal) ? float_negate(xfl_bal) : xfl_bal; - if (xfl_bal <= 0 || !float_compare(xfl_bal, 0, COMPARE_GREATER)) - NOPE("Funds: Insane balance on trustline."); - - int64_t xfl_out; - - // emit a txn either to the otxn acc or the settlement acc - if (op == 'W') - { - if (sig_len <= 0) - NOPE("Funds: Missing SIG parameter."); - - if (time > sig_exp) - NOPE("Funds: Ticket has expired."); - - if (!BUFFER_EQUAL_20(sig_acc, stl)) - NOPE("Funds: Wrong account for ticket."); - - // check the nonce - uint64_t upto; - state(&upto, 8, sig_acc, 20); - - if (upto != sig_nce) - NOPE("Funds: Nonce out of sequence."); - - // check bal can support withdraw - if (float_compare(sig_amt, xfl_bal, COMPARE_GREATER)) - NOPE("Funds: Balance not high enough for this withdrawal."); - - xfl_out = sig_amt; - - // update nonce - upto++; - if (state_set(&upto, 8, sig_acc, 20) != 8) - NOPE("Funds: Failed to set state."); - } - else - { - // set the destination addr to the settlement addr - COPY_20(stl, DESTACC); - - // if they are settling then they need an amt param - uint64_t xfl_stl; - otxn_param(&xfl_stl, 8, "AMT", 3); - - if (xfl_stl <= 0) - NOPE("Funds: Must provide AMT param when performing settlement."); - - if (float_compare(xfl_stl, xfl_bal, COMPARE_GREATER)) - NOPE("Funds: Balance not high enough for this settlement."); - - - xfl_out = xfl_stl; - } - - // write payment amount - float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, xfl_out, sfAmount); - - etxn_details(EMITDET, 138); - int64_t fee = etxn_fee_base(txn_out, TXNLEN); - BE_DROPS(fee); - *((uint64_t*)(FEEOUT)) = fee; - - txn_out[15] = (seq >> 24U) & 0xFFU; - txn_out[16] = (seq >> 16U) & 0xFFU; - txn_out[17] = (seq >> 8U) & 0xFFU; - txn_out[18] = seq & 0xFFU; - - seq += 4; - txn_out[21] = (seq >> 24U) & 0xFFU; - txn_out[22] = (seq >> 16U) & 0xFFU; - txn_out[23] = (seq >> 8U) & 0xFFU; - txn_out[24] = seq & 0xFFU; - - trace(SBUF("emit:"), txn_out, TXNLEN, 1); - - uint8_t emithash[32]; - int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); - if (emit_result < 0) - { - NOPE("Funds: Withdraw/Settle Emitted Failure."); - } - - if (op == 'W') - DONE("Funds: Emitted withdrawal."); - - DONE("Funds: Emitted settlement."); - - - break; - } - - default: - { - NOPE("Funds: Unknown operation."); - } - } - - return 0; -} \ No newline at end of file diff --git a/contracts/funds/modular/master.c b/contracts/funds/modular/master.c new file mode 100644 index 0000000..587bba9 --- /dev/null +++ b/contracts/funds/modular/master.c @@ -0,0 +1,526 @@ +//------------------------------------------------------------------------------ +/* + +HookParameters -> State: + +ADM: The admin account (role) +STL: The settler account (role) +RFD: The refunder account (role) +WKEY: The withdrawer pubkey (role) + +CUR: The currency allowed in the funding source +ISS: The issuer account allowed in the funding source +ACC: The settlement account (globally per asset) + +Operations: + +// invoke ops are: (role) + // I - initalize (any) + // S - settle (settler) + // U/P - un/pause (admin) + // M - modify (admin) + // R - refund (refunder) + +// invoke (Modify) sub ops are: + // A - modify admin account (role) + // S - modify settler account (role) + // W - modify withdraw pubkey (role) + +*/ +//============================================================================== + +#include "hookapi.h" + +#define SVAR(x) &(x), sizeof(x) + +#define DONE(x)\ + return accept(SBUF(x), __LINE__) + +#define NOPE(x)\ + return rollback(SBUF(x), __LINE__) + + +#define FLIP_ENDIAN_64(n) ((uint64_t)(((n & 0xFFULL) << 56ULL) | \ + ((n & 0xFF00ULL) << 40ULL) | \ + ((n & 0xFF0000ULL) << 24ULL) | \ + ((n & 0xFF000000ULL) << 8ULL) | \ + ((n & 0xFF00000000ULL) >> 8ULL) | \ + ((n & 0xFF0000000000ULL) >> 24ULL) | \ + ((n & 0xFF000000000000ULL) >> 40ULL) | \ + ((n & 0xFF00000000000000ULL) >> 56ULL))) + +uint8_t txn_out[300] = +{ +/* size,upto */ +/* 3, 0 */ 0x12U, 0x00U, 0x00U, /* tt = Payment */ +/* 5, 3 */ 0x22U, 0x80U, 0x00U, 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 */ +/* 1, 25 */ 0x61U, +/* 8, 26 */ 0,0,0,0,0,0,0,0, /* amt val */ +/* 20, 34 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* amt cur */ +// vvvvvvvvvvvvvvvvvv ISSUER ACC ID vvvvvvvvvvvvvvvvvvvvvvv +/* 20, 54 */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + +/* 9, 74 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */ +/* 35, 83 */ 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 */ +/* 2, 118 */ 0x81U, 0x14U, /* src acc preamble */ + +/* 20, 120 */ 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, /* src acc */ +/* 2, 140 */ 0x83U, 0x14U, /* dest acc preamble */ +/* 20, 142 */ 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, /* dest acc */ + +/* 138, 162 */ 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,0,0,0,0,0,0,0, /* emit detail */ + 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,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,0,0,0,0,0,0,0,0,0,0 +/* 0, 300 */ +}; + +#define TLACC (txn_out + 54) /* set it above!! */ + +#define TXNLEN 300 +#define FEEOUT (txn_out + 75) +#define EMITDET (txn_out + 162) +#define HOOKACC (txn_out + 120) +#define OTXNACC (txn_out + 142) +#define DTAG_OUT (txn_out + 142) +#define DESTACC (txn_out + 142) +#define OUTAMT (txn_out + 25) +#define OUTCUR (txn_out + 34) +#define OUTISS (txn_out + 54) +#define CURSHORT (txn_out + 46) + +// if we make a TrustSet instead (used for initialisation) then we'll truncate the template above +#define TTOUT (txn_out + 2) // when it's a TrustSet we set this to 0x14 +#define OUTAMT_TL (txn_out + 25) // when it's a TrustSet, we set this to 0x63 +#define EMITDET_TL (txn_out + 140) // when it's a TrustSet Emit Details occurs sooner +#define TXNLEN_TL 278 // .. and the txn is smaller + +#define BE_DROPS(drops)\ +{\ + uint64_t drops_tmp = drops;\ + uint8_t* b = (uint8_t*)&drops;\ + *b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\ + *b++ = (drops_tmp >> 48) & 0xFFU;\ + *b++ = (drops_tmp >> 40) & 0xFFU;\ + *b++ = (drops_tmp >> 32) & 0xFFU;\ + *b++ = (drops_tmp >> 24) & 0xFFU;\ + *b++ = (drops_tmp >> 16) & 0xFFU;\ + *b++ = (drops_tmp >> 8) & 0xFFU;\ + *b++ = (drops_tmp >> 0) & 0xFFU;\ +} + +#define COPY_20(src, dst)\ +{\ + *((uint64_t*)(((uint8_t*)(dst)) + 0)) = \ + *((uint64_t*)(((uint8_t*)(src)) + 0));\ + *((uint64_t*)(((uint8_t*)(dst)) + 8)) = \ + *((uint64_t*)(((uint8_t*)(src)) + 8));\ + *((uint32_t*)(((uint8_t*)(dst)) + 16)) = \ + *((uint32_t*)(((uint8_t*)(src)) + 16));\ +} + +#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); \ +} + +int64_t cbak(uint32_t f) +{ + // TODO track withdrawal txns to see if they successfully executed + return 0; +} + +int64_t hook(uint32_t r) +{ + _g(1,1); + + etxn_reserve(1); + + uint8_t hook_accid[32]; + hook_account(hook_accid + 12, 20); + + uint8_t otxn_accid[32]; + otxn_field(otxn_accid + 12, 20, sfAccount); + + if (BUFFER_EQUAL_20(hook_accid + 12, otxn_accid + 12)) + DONE("Master: passing outgoing txn"); + + int64_t tt = otxn_type(); + if (tt != ttINVOKE && tt != ttPAYMENT) + NOPE("Master: Rejecting non-Invoke, non-Payment txn."); + + otxn_slot(1); + + slot_subfield(1, sfAmount, 2); + + uint8_t amt[48]; + + if (slot_size(2) == 8) + DONE("Master: Passing incoming XAH payment."); + + // get admin account (role) + uint8_t _admin[20]; + if (hook_param(SBUF(_admin), "ADM", 3) != 20) + NOPE("Master: Misconfigured. Missing ADM install parameter."); + + // get settlement account (role) + uint8_t _stl[20]; + if (hook_param(SBUF(_stl), "STL", 3) != 20) + NOPE("Master: Misconfigured. Missing STL install parameter."); + + // get the withdrawal signing key + uint8_t _wkey[33]; + if (hook_param(SBUF(_wkey), "WKEY", 4) != 33) + NOPE("Master: Misconfigured. Missing WKEY install parameter."); + + // get currency + if (hook_param(OUTCUR, 20, "CUR", 3) != 20) + NOPE("Master: Misconfigured. Missing CUR install parameter."); + + // get currency issuer + if (hook_param(OUTISS, 20, "ISS", 3) != 20) + NOPE("Master: Misconfigured. Missing ISS install parameter."); + + // get settlement account (asset settlement account) + uint8_t _acc[20]; + if (hook_param(SBUF(_acc), "ACC", 3) != 20) + NOPE("Master: Misconfigured. Missing ACC install parameter."); + + // get withdraw delay + int64_t _delay; + if (hook_param(SVAR(_delay), "DLY", 3) != 8) + NOPE("Master: Misconfigured. Missing ACC install parameter."); + + // get partner account + // uint8_t _prt[20]; + // if (hook_param(SBUF(_prt), "PTR", 3) != 20) + // NOPE("Master: Misconfigured. Missing PTR install parameter."); + + // Operation + uint8_t op; + if (otxn_param(&op, 1, "OP", 2) != 1) + NOPE("Master: Missing OP parameter on Invoke."); + + // Sub Operation + uint8_t sop; + if (op == 'M' && otxn_param(&sop, 1, "SOP", 3) != 1) + NOPE("Master: Missing SOP parameter on Invoke."); + + int64_t xfl_in; + uint32_t flags; + uint32_t dtag; + + // enforced pausedness + if (op != 'U') + { + uint8_t paused; + state(&paused, 1, "P", 1); + if (paused) + NOPE("Master: Paused."); + } + + // check if the trustline exists + uint8_t keylet[34]; + util_keylet(keylet, 34, KEYLET_LINE, hook_accid + 12, 20, OUTISS, 20, OUTCUR, 20); + + int64_t already_setup = (slot_set(SBUF(keylet), 10) == 10); + + // enforce initalisation + if (!already_setup && op != 'I') + NOPE("Master: Send op=I initalisation first."); + + uint8_t admin[20]; + uint8_t stl[20]; + uint8_t wkey[33]; + uint8_t acc[20]; + int64_t delay; + if (already_setup && op != 'I') + { + state(SBUF(admin), "ADM", 3); + state(SBUF(stl), "STL", 3); + state(SBUF(wkey), "WKEY", 4); + state(SBUF(acc), "ACC", 3); + state(SVAR(delay), "DLY", 3); + } + + int64_t is_admin = BUFFER_EQUAL_20(otxn_accid + 12, admin); + int64_t is_stl = BUFFER_EQUAL_20(otxn_accid + 12, stl); + + // permission check + if (!is_admin && (op == 'U' || op == 'P' || op == 'M')) + NOPE("Master: Admin only operation."); + + if (!is_stl && (op == 'S' || op == 'R')) + NOPE("Master: Settler only operation."); + + // current ledger seq is used when emitting a txn + int64_t seq = ledger_seq() + 1; + + // action + switch (op) + { + case 'I': + { + if (already_setup) + DONE("Master: Already setup trustline."); + + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + + // create a trustline ... + uint8_t xfl_buffer[8]; + if (otxn_param(xfl_buffer, 8, "AMT", 3) != 8) + NOPE("Master: Misconfigured. Missing AMT otxn parameter."); + + int64_t xfl_out = *((int64_t *)xfl_buffer); + + // write limit amount + float_sto(OUTAMT_TL, 49, OUTCUR, 20, OUTISS, 20, xfl_out, sfLimitAmount); + + // set the template transaction type to trustset + *TTOUT = 0x14U; + + etxn_details(EMITDET_TL, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN_TL); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN_TL, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN_TL); + TRACEVAR(emit_result); + + state_set(_admin, 20, "ADM", 3); + state_set(_stl, 20, "STL", 3); + state_set(_wkey, 33, "WKEY", 4); + state_set(OUTCUR, 20, "CUR", 3); + state_set(OUTISS, 20, "ISS", 3); + state_set(_acc, 20, "ACC", 3); + state_set(SVAR(_delay), "DLY", 3); + DONE("Master: Emitted TrustSet to initialize."); + } + + case 'U': + case 'P': + { + // pause + uint8_t paused = (op == 'P' ? 1 : 0); + state_set(&paused, 1, "P", 1); + DONE("Master: Paused/Unpaused."); + } + + // settlement + case 'S': + { + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + + // check trustline balance + slot_subfield(10, sfBalance, 11); + if (slot_size(11) != 48) + NOPE("Master: Could not fetch trustline balance."); + + int64_t xfl_bal = slot_float(11); + + if (xfl_bal <= 0 || !float_compare(xfl_bal, 0, COMPARE_GREATER)) + NOPE("Master: Insane balance on trustline."); + + // set the destination addr to the settlement addr + COPY_20(acc, DESTACC); + + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) + NOPE("Master: Misconfigured. Missing AMT otxn parameter."); + + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) + NOPE("Master: Misconfigured. Missing SEQ otxn parameter."); + + // check the nonce + uint32_t stl_seq; + state(SVAR(stl_seq), otxn_accid + 12, 20); + + if (stl_seq != sig_nce) + NOPE("Master: Settlement nonce out of sequence."); + + if (sig_amt <= 0) + NOPE("Master: Settlement amount must be greater than 0."); + + TRACEVAR(sig_amt); + TRACEVAR(xfl_bal); + if (float_compare(sig_amt, xfl_bal, COMPARE_GREATER)) + NOPE("Master: Balance not high enough for this settlement."); + + // write payment amount + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, sig_amt, sfAmount); + + etxn_details(EMITDET, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); + if (emit_result < 0) + { + NOPE("Master: Settle Emitted Failure."); + } + + stl_seq++; + if (state_set(SVAR(stl_seq), otxn_accid + 12, 20) != 4) + NOPE("Master: Failed to set state."); + + DONE("Master: Emitted settlement."); + break; + } + + // refund + case 'R': + { + if (otxn_param(SVAR(dtag), "TAG", 3) != 4) + NOPE("Master: Misconfigured. Missing TAG otxn parameter."); + + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) + NOPE("Master: Misconfigured. Missing AMT otxn parameter."); + + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) + NOPE("Master: Misconfigured. Missing SEQ otxn parameter."); + + // check the nonce + uint32_t rfd_seq; + state(SVAR(rfd_seq), otxn_accid + 12, 20); + TRACEVAR(rfd_seq); + + if (rfd_seq != sig_nce) + NOPE("Master: Refund nonce out of sequence."); + + uint8_t dtag_bal[8]; + state(SBUF(dtag_bal), dtag + 28, 4); + int64_t dtag_bal_xfl = *((int64_t*)dtag_bal); + + // check dtag balance + if (dtag_bal_xfl <= 0 || !float_compare(dtag_bal_xfl, 0, COMPARE_GREATER)) + NOPE("Master: Insane balance on dtag."); + + if (sig_amt <= 0) + NOPE("Master: Must provide AMT param when performing refund."); + + if (float_compare(sig_amt, dtag_bal_xfl, COMPARE_GREATER)) + NOPE("Master: Balance not high enough for this debit."); + + int64_t add_dtag_bal_xfl = float_sum(dtag_bal_xfl, sig_amt); + if (state_set(SVAR(add_dtag_bal_xfl), SVAR(dtag)) != 8) + NOPE("Master: Insane balance on dtag."); + + int64_t sub_stl_bal_xfl = float_sum(dtag_bal_xfl, float_negate(sig_amt)); + if (state_set(SVAR(sub_stl_bal_xfl), hook_accid + 12, 20) != 8) + NOPE("Master: Insane balance on stl."); + + rfd_seq++; + if (state_set(SVAR(rfd_seq), otxn_accid + 12, 20) != 4) + NOPE("Master: Failed to set state."); + + DONE("Master: Refunded."); + break; + } + + case 'M': + { + switch (op) + { + case 'A': // admin (role) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Master: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "ADM", 3); + DONE("Master: ADM Modified."); + } + + case 'S': // settler (role) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Master: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "STL", 3); + DONE("Master: STL Modified."); + } + + case 'W': // withdraw (role) + { + uint8_t key[33]; + if (otxn_param(SBUF(key), "WKEY", 4) != 33) + NOPE("Master: Misconfigured. Missing KEY modify parameter."); + + state_set(key, 33, "WKEY", 4); + DONE("Master: WKEY Modified."); + } + + case 'D': // delay (time) + { + int64_t delay; + if (otxn_param(SVAR(delay), "DLY", 3) != 8) + NOPE("Master: Misconfigured. Missing DLY modify parameter."); + + state_set(SVAR(delay), "DLY", 3); + DONE("Master: DLY Modified."); + } + + case 'I': // settlement (address) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Master: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "ACC", 3); + DONE("Master: ACC Modified."); + } + + default: + { + NOPE("Master: Unknown operation."); + } + } + } + + default: + { + NOPE("Master: Unknown operation."); + } + } + + return 0; +} \ No newline at end of file diff --git a/contracts/funds/modular/user.c b/contracts/funds/modular/user.c new file mode 100644 index 0000000..87a468d --- /dev/null +++ b/contracts/funds/modular/user.c @@ -0,0 +1,640 @@ +//------------------------------------------------------------------------------ +/* + +Foreign State (Read): + +ADM: The admin account (role) +STL: The settler account (role) +RFD: The refunder account (role) +WKEY: The withdrawer pubkey (role) + +CUR: The currency allowed in the funding source +ISS: The issuer account allowed in the funding source +ACC: The settlement account (globally per asset) + +Operations: + +// payment ops are: + // D - deposit (any) + +// invoke ops are: (role) + // I - initalize (any) + // B - debit (settler) + // W - withdraw (user) + +*/ +//============================================================================== + +#include "hookapi.h" + +#define SVAR(x) &(x), sizeof(x) + +#define DONE(x)\ + return accept(SBUF(x), __LINE__) + +#define NOPE(x)\ + return rollback(SBUF(x), __LINE__) + + +#define FLIP_ENDIAN_64(n) ((uint64_t)(((n & 0xFFULL) << 56ULL) | \ + ((n & 0xFF00ULL) << 40ULL) | \ + ((n & 0xFF0000ULL) << 24ULL) | \ + ((n & 0xFF000000ULL) << 8ULL) | \ + ((n & 0xFF00000000ULL) >> 8ULL) | \ + ((n & 0xFF0000000000ULL) >> 24ULL) | \ + ((n & 0xFF000000000000ULL) >> 40ULL) | \ + ((n & 0xFF00000000000000ULL) >> 56ULL))) + +uint8_t txn_out[300] = +{ +/* size,upto */ +/* 3, 0 */ 0x12U, 0x00U, 0x00U, /* tt = Payment */ +/* 5, 3 */ 0x22U, 0x80U, 0x00U, 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 */ +/* 1, 25 */ 0x61U, +/* 8, 26 */ 0,0,0,0,0,0,0,0, /* amt val */ +/* 20, 34 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* amt cur */ +// vvvvvvvvvvvvvvvvvv ISSUER ACC ID vvvvvvvvvvvvvvvvvvvvvvv +/* 20, 54 */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + +/* 9, 74 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */ +/* 35, 83 */ 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 */ +/* 2, 118 */ 0x81U, 0x14U, /* src acc preamble */ + +/* 20, 120 */ 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, /* src acc */ +/* 2, 140 */ 0x83U, 0x14U, /* dest acc preamble */ +/* 20, 142 */ 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, /* dest acc */ + +/* 138, 162 */ 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,0,0,0,0,0,0,0, /* emit detail */ + 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,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,0,0,0,0,0,0,0,0,0,0 +/* 0, 300 */ +}; + +#define TLACC (txn_out + 54) /* set it above!! */ + +#define TXNLEN 300 +#define FEEOUT (txn_out + 75) +#define EMITDET (txn_out + 162) +#define HOOKACC (txn_out + 120) +#define OTXNACC (txn_out + 142) +#define DTAG_OUT (txn_out + 142) +#define DESTACC (txn_out + 142) +#define OUTAMT (txn_out + 25) +#define OUTCUR (txn_out + 34) +#define OUTISS (txn_out + 54) +#define CURSHORT (txn_out + 46) + +// if we make a TrustSet instead (used for initialisation) then we'll truncate the template above +#define TTOUT (txn_out + 2) // when it's a TrustSet we set this to 0x14 +#define OUTAMT_TL (txn_out + 25) // when it's a TrustSet, we set this to 0x63 +#define EMITDET_TL (txn_out + 140) // when it's a TrustSet Emit Details occurs sooner +#define TXNLEN_TL 278 // .. and the txn is smaller + +#define BE_DROPS(drops)\ +{\ + uint64_t drops_tmp = drops;\ + uint8_t* b = (uint8_t*)&drops;\ + *b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\ + *b++ = (drops_tmp >> 48) & 0xFFU;\ + *b++ = (drops_tmp >> 40) & 0xFFU;\ + *b++ = (drops_tmp >> 32) & 0xFFU;\ + *b++ = (drops_tmp >> 24) & 0xFFU;\ + *b++ = (drops_tmp >> 16) & 0xFFU;\ + *b++ = (drops_tmp >> 8) & 0xFFU;\ + *b++ = (drops_tmp >> 0) & 0xFFU;\ +} + +#define COPY_20(src, dst)\ +{\ + *((uint64_t*)(((uint8_t*)(dst)) + 0)) = \ + *((uint64_t*)(((uint8_t*)(src)) + 0));\ + *((uint64_t*)(((uint8_t*)(dst)) + 8)) = \ + *((uint64_t*)(((uint8_t*)(src)) + 8));\ + *((uint32_t*)(((uint8_t*)(dst)) + 16)) = \ + *((uint32_t*)(((uint8_t*)(src)) + 16));\ +} + +#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 master_accid[20] = { + 0x6D, 0xF9, 0x0E, 0xCA, 0x2B, 0x5C, 0x0B, 0x45, 0xAA, 0x2E, + 0x1D, 0x6E, 0x99, 0x3B, 0xF7, 0xD9, 0xDC, 0xB2, 0x26, 0xB3 +}; + +uint8_t ns[32] = { + 0x1D, 0xAE, 0x33, 0x31, 0xAD, 0x54, 0x4F, 0x4F, + 0xB6, 0x1F, 0x34, 0x75, 0x85, 0xDC, 0x39, 0x75, + 0xE5, 0xE3, 0xC1, 0x19, 0xFA, 0x6A, 0xA5, 0xED, + 0x4E, 0x6D, 0x9D, 0x58, 0x0C, 0x77, 0xB2, 0x25 +}; + +uint8_t nonce_ns[32] = { + 0x21, 0x2C, 0xCE, 0x06, 0x94, 0xF0, 0x63, 0xCC, + 0x1D, 0xB8, 0xCF, 0xA3, 0x46, 0xE2, 0x96, 0xF3, + 0xE1, 0xF9, 0xE1, 0xF0, 0xFE, 0x93, 0x12, 0xF6, + 0x87, 0x58, 0x00, 0xBA, 0x70, 0x57, 0x8F, 0xFE +}; + +int64_t cbak(uint32_t f) +{ + // TODO track withdrawal txns to see if they successfully executed + return 0; +} + +int64_t hook(uint32_t r) +{ + _g(1,1); + + etxn_reserve(1); + + uint8_t hook_accid[32]; + hook_account(hook_accid + 12, 20); + + uint8_t otxn_accid[32]; + otxn_field(otxn_accid + 12, 20, sfAccount); + + if (BUFFER_EQUAL_20(hook_accid + 12, otxn_accid + 12)) + DONE("User: passing outgoing txn"); + + int64_t tt = otxn_type(); + if (tt != ttINVOKE && tt != ttPAYMENT) + NOPE("User: Rejecting non-Invoke, non-Payment txn."); + + otxn_slot(1); + + slot_subfield(1, sfAmount, 2); + + uint8_t amt[48]; + + if (slot_size(2) == 8) + DONE("User: Passing incoming XAH payment."); + + // get admin account (role) + uint8_t admin[20]; + if (state_foreign(SBUF(admin), "ADM", 3, SBUF(ns), SBUF(master_accid)) != 20) + NOPE("User: Misconfigured. Missing ADM state."); + + // get settlement account (role) + uint8_t stl[20]; + if (state_foreign(SBUF(stl), "STL", 3, SBUF(ns), SBUF(master_accid)) != 20) + NOPE("User: Misconfigured. Missing STL state."); + + // get the withdrawal signing key + uint8_t wkey[33]; + if (state_foreign(SBUF(wkey), "WKEY", 4, SBUF(ns), SBUF(master_accid)) != 33) + NOPE("User: Misconfigured. Missing WKEY state."); + + // get currency + if (state_foreign(OUTCUR, 20, "CUR", 3, SBUF(ns), SBUF(master_accid)) != 20) + NOPE("User: Misconfigured. Missing CUR state."); + + // get currency issuer + if (state_foreign(OUTISS, 20, "ISS", 3, SBUF(ns), SBUF(master_accid)) != 20) + NOPE("User: Misconfigured. Missing ISS state."); + + // get settlement account (asset settlement account) + uint8_t acc[20]; + if (state_foreign(SBUF(acc), "ACC", 3, SBUF(ns), SBUF(master_accid)) != 20) + NOPE("User: Misconfigured. Missing ACC state."); + + // get withdraw delay + int64_t delay; + if (state_foreign(SVAR(delay), "DLY", 3, SBUF(ns), SBUF(master_accid)) != 20) + NOPE("User: Misconfigured. Missing DLY state."); + + // Operation + uint8_t op; + if (otxn_param(&op, 1, "OP", 2) != 1) + NOPE("User: Missing OP parameter on Invoke."); + + // Sub Operation + uint8_t sop; + if (op == 'W' && otxn_param(&sop, 1, "SOP", 3) != 1) + NOPE("User: Missing SOP parameter on Invoke."); + + int64_t xfl_in; + uint32_t flags; + + if (tt == ttPAYMENT) + { + // this will fail if flags isn't in the txn, that's also ok. + otxn_field(&flags, 4, sfFlags); + + // check for partial payments (0x00020000) -> (0x00000200 LE) + if (flags & 0x200U) + NOPE("User: Partial payments are not supported."); + + otxn_field(SBUF(amt), sfAmount); + + if (!BUFFER_EQUAL_20(amt + 8, OUTCUR)) + NOPE("User: Wrong currency."); + + if (!BUFFER_EQUAL_20(amt + 28, OUTISS)) + NOPE("User: Wrong issuer."); + + xfl_in = slot_float(2); + + if (xfl_in < 0 || !float_compare(xfl_in, 0, COMPARE_GREATER)) + NOPE("User: Invalid sfAmount."); + } + + // enforced pausedness + uint8_t paused; + state_foreign(&paused, 1, "P", 1, SBUF(ns), SBUF(master_accid)); + if (paused) + NOPE("User: Paused."); + + // check if the trustline exists + uint8_t keylet[34]; + util_keylet(keylet, 34, KEYLET_LINE, hook_accid + 12, 20, OUTISS, 20, OUTCUR, 20); + + int64_t already_setup = (slot_set(SBUF(keylet), 10) == 10); + + // enforce initalisation + if (!already_setup && op != 'I') + NOPE("User: Send op=I initalisation first."); + + int64_t is_stl = BUFFER_EQUAL_20(otxn_accid + 12, stl); + + // sanity check + if ((op == 'D') && tt != ttPAYMENT) + NOPE("User: Deposit operations must be a payment transaction."); + + // permission check + if (!is_stl && (op == 'B')) + NOPE("User: Settler only operation."); + + // current ledger seq is used when emitting a txn + int64_t seq = ledger_seq() + 1; + + // action + switch (op) + { + case 'I': + { + if (already_setup) + DONE("User: Already setup trustline."); + + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + + // create a trustline ... + uint8_t xfl_buffer[8]; + if (otxn_param(xfl_buffer, 8, "AMT", 3) != 8) + NOPE("User: Misconfigured. Missing AMT otxn parameter."); + + int64_t xfl_out = *((int64_t *)xfl_buffer); + + // write limit amount + float_sto(OUTAMT_TL, 49, OUTCUR, 20, OUTISS, 20, xfl_out, sfLimitAmount); + + // set the template transaction type to trustset + *TTOUT = 0x14U; + + etxn_details(EMITDET_TL, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN_TL); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN_TL, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN_TL); + TRACEVAR(emit_result); + DONE("User: Emitted TrustSet to initialize."); + } + + case 'D': + { + DONE("User: Deposited."); + } + + // debit + case 'B': + { + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) + NOPE("User: Misconfigured. Missing AMT otxn parameter."); + + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) + NOPE("User: Misconfigured. Missing SEQ otxn parameter."); + + // check the nonce + uint32_t dbt_seq; + state_foreign(SVAR(dbt_seq), otxn_accid + 12, 20, SBUF(nonce_ns), hook_accid + 12, 20); + + if (dbt_seq != sig_nce) + NOPE("User: Debit nonce out of sequence."); + + // check trustline balance + slot_subfield(10, sfBalance, 11); + if (slot_size(11) != 48) + NOPE("Funds: Could not fetch trustline balance."); + + int64_t xfl_bal = slot_float(11); + + if (xfl_bal <= 0 || !float_compare(xfl_bal, 0, COMPARE_GREATER)) + NOPE("Funds: Insane balance on trustline."); + + int64_t xfl_out; + + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + + // set the destination addr to the settlement addr + COPY_20(stl, DESTACC); + + // if they are settling then they need an amt param + uint64_t xfl_stl; + otxn_param(&xfl_stl, 8, "AMT", 3); + + if (xfl_stl <= 0) + NOPE("Funds: Must provide AMT param when performing settlement."); + + if (float_compare(xfl_stl, xfl_bal, COMPARE_GREATER)) + NOPE("Funds: Balance not high enough for this settlement."); + + // write payment amount + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, xfl_stl, sfAmount); + + etxn_details(EMITDET, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); + if (emit_result < 0) + { + NOPE("Funds: Debit Emitted Failure."); + } + + dbt_seq++; + if (state_foreign_set(SVAR(dbt_seq), otxn_accid + 12, 20, SBUF(nonce_ns), hook_accid + 12, 20) != 4) + NOPE("User: Failed to set state."); + + DONE("User: Emitted debit."); + break; + } + + // withdrawal + case 'W': + { + switch (sop) + { + case 'A': // approval (signature) + { + // get signature if any + // Signature format is packed binary data of the form: + // <20 byte dest accid><8 byte le xfl amount><4 byte le int expiry timestamp><4 byte le int nonce> + uint8_t sig_buf[256]; + int64_t sig_len = otxn_param(SBUF(sig_buf), "SIG", 3); + + // place pointers according to packed data + uint8_t* sig_acc = sig_buf; + uint64_t sig_amt = *((uint64_t*)(sig_buf + 20)); + uint32_t sig_exp = *((uint32_t*)(sig_buf + 28)); + uint32_t sig_nce = *((uint32_t*)(sig_buf + 32)); + uint8_t* sig = sig_buf + 36; + + if (sig_len > 0) + { + if (sig_len < 80) + NOPE("User: Signature too short."); + + if (!util_verify(sig_buf, 36, sig_buf + 36, sig_len - 36, SBUF(wkey))) + NOPE("User: Signature verification failed."); + } + + if (sig_len <= 0) + NOPE("User: Missing SIG parameter."); + + int64_t time = ledger_last_time(); + if (time > sig_exp) + NOPE("User: Ticket has expired."); + + if (!BUFFER_EQUAL_20(sig_acc, otxn_accid + 12)) + NOPE("User: Wrong account for ticket."); + + // check trustline balance + slot_subfield(10, sfBalance, 11); + if (slot_size(11) != 48) + NOPE("Funds: Could not fetch trustline balance."); + + int64_t xfl_bal = slot_float(11); + + if (xfl_bal <= 0 || !float_compare(xfl_bal, 0, COMPARE_GREATER)) + NOPE("Funds: Insane balance on trustline."); + + // check the nonce + uint32_t wth_seq; + state_foreign(SVAR(wth_seq), otxn_accid + 12, 20, SBUF(nonce_ns), hook_accid + 12, 20); + TRACEVAR(wth_seq); + + if (wth_seq != sig_nce) + NOPE("User: Nonce out of sequence."); + + // check bal can support withdraw + TRACEVAR(xfl_bal); + TRACEVAR(sig_amt); + if (float_compare(sig_amt, xfl_bal, COMPARE_GREATER)) + NOPE("User: Balance not high enough for this withdrawal."); + + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + COPY_20(sig_acc, DESTACC); + + // write payment amount + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, sig_amt, sfAmount); + + etxn_details(EMITDET, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); + if (emit_result < 0) + { + NOPE("User: Withdrawal Emitted Failure."); + } + + // update nonce + wth_seq++; + if (state_foreign_set(SVAR(wth_seq), otxn_accid + 12, 20, SBUF(nonce_ns), hook_accid + 12, 20) != 4) + NOPE("User: Failed to set state."); + + DONE("User: Emitted approval withdrawal."); + } + + case 'I': // permissionless (intent) + { + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) + NOPE("User: Misconfigured. Missing AMT otxn parameter."); + + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) + NOPE("User: Misconfigured. Missing SEQ otxn parameter."); + + // check trustline balance + slot_subfield(10, sfBalance, 11); + if (slot_size(11) != 48) + NOPE("Funds: Could not fetch trustline balance."); + + int64_t xfl_bal = slot_float(11); + + if (xfl_bal <= 0 || !float_compare(xfl_bal, 0, COMPARE_GREATER)) + NOPE("Funds: Insane balance on trustline."); + + // check the nonce + uint32_t wth_seq; + state_foreign(SVAR(wth_seq), otxn_accid + 12, 20, SBUF(nonce_ns), hook_accid + 12, 20); + TRACEVAR(wth_seq); + + if (wth_seq != sig_nce) + NOPE("User: Nonce out of sequence."); + + // check bal can support withdraw + if (float_compare(sig_amt, xfl_bal, COMPARE_GREATER)) + NOPE("User: Balance not high enough for this withdrawal."); + + int64_t time = ledger_last_time() + delay; + uint8_t pending_buff[24]; + COPY_20(otxn_accid + 12, pending_buff); + UINT32_TO_BUF(pending_buff + 20U, sig_nce); + + TRACEHEX(pending_buff); + + uint8_t amount_buff[16]; + INT64_TO_BUF(amount_buff, FLIP_ENDIAN_64(sig_amt)); + INT64_TO_BUF(amount_buff + 8U, time); + + if (state_set(SBUF(amount_buff), SBUF(pending_buff)) != 16) + NOPE("User: Could not save pending withdrawal intent."); + + // update nonce + wth_seq++; + if (state_foreign_set(SVAR(wth_seq), otxn_accid + 12, 20, SBUF(nonce_ns), hook_accid + 12, 20) != 4) + NOPE("User: Failed to set state."); + + DONE("User: Created permissionless withdraw intent."); + } + + case 'E': // permissionless (execution) + { + uint8_t pending_buff[24]; + if (otxn_param(SBUF(pending_buff), "WI", 2) != 24) + NOPE("User: Misconfigured. Missing WI otxn parameter."); + + TRACEHEX(pending_buff); + + uint8_t amount_buff[16]; + if (state(SBUF(amount_buff), SBUF(pending_buff)) == DOESNT_EXIST) + NOPE("User: Misconfigured. Withdraw Intent does not exist."); + + int64_t delay_time = UINT64_FROM_BUF(amount_buff + 8); + int64_t time = ledger_last_time(); + if (time < delay_time) + NOPE("User: Need to wait.."); + + uint64_t sig_amt = *((uint64_t*)(amount_buff + 0U)); + + TRACEVAR(sig_amt); + + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + COPY_20(pending_buff, DESTACC); + + // write payment amount + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, sig_amt, sfAmount); + + etxn_details(EMITDET, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); + if (emit_result < 0) + { + NOPE("User: Withdrawal Emitted Failure."); + } + + state_set(0, 0, SBUF(pending_buff)); + + DONE("User: Emitted permissionless withdrawal."); + } + + default: + { + NOPE("User: Unknown operation."); + } + } + } + + default: + { + NOPE("User: Unknown operation."); + } + } + + return 0; +} \ No newline at end of file diff --git a/contracts/funds/monolithic/monolithic.c b/contracts/funds/monolithic/monolithic.c new file mode 100644 index 0000000..35c1dfc --- /dev/null +++ b/contracts/funds/monolithic/monolithic.c @@ -0,0 +1,836 @@ +//------------------------------------------------------------------------------ +/* + +HookParameters -> State: + +ADM: The admin account (role) +STL: The settler account (role) +WKEY: The withdrawer pubkey (role) +WDLY: The withdraw delay (ledgers) + +CUR: The currency allowed in the funding source +ISS: The issuer account allowed in the funding source +ACC: The settlement account (globally per asset) + +Operations: + +// payment ops are: + // D - deposit (any) + +// invoke ops are: (role) + // I - initalize (any) + // C - create "Funding Source" (any) + // B - debit (settler) + // S - settle (settler) + // U/P - un/pause (admin) + // M - modify (admin) + // R - refund (refunder) + // W - withdraw (user) + // A - asset (admin) + +// invoke (Modify) sub ops are: + // A - modify admin account (role) + // S - modify settler account (role) + // W - modify withdraw pubkey (role) + +// invoke (Withdraw) sub ops are: + // A - approval withdrawal (signature) + // P - permissionless withdrawal (delay) + +*/ +//============================================================================== + +#include "hookapi.h" + +#define SVAR(x) &(x), sizeof(x) + +#define DONE(x)\ + return accept(SBUF(x), __LINE__) + +#define NOPE(x)\ + return rollback(SBUF(x), __LINE__) + + +#define FLIP_ENDIAN_64(n) ((uint64_t)(((n & 0xFFULL) << 56ULL) | \ + ((n & 0xFF00ULL) << 40ULL) | \ + ((n & 0xFF0000ULL) << 24ULL) | \ + ((n & 0xFF000000ULL) << 8ULL) | \ + ((n & 0xFF00000000ULL) >> 8ULL) | \ + ((n & 0xFF0000000000ULL) >> 24ULL) | \ + ((n & 0xFF000000000000ULL) >> 40ULL) | \ + ((n & 0xFF00000000000000ULL) >> 56ULL))) + +uint8_t txn_out[300] = +{ +/* size,upto */ +/* 3, 0 */ 0x12U, 0x00U, 0x00U, /* tt = Payment */ +/* 5, 3 */ 0x22U, 0x80U, 0x00U, 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 */ +/* 1, 25 */ 0x61U, +/* 8, 26 */ 0,0,0,0,0,0,0,0, /* amt val */ +/* 20, 34 */ 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, /* amt cur */ +// vvvvvvvvvvvvvvvvvv ISSUER ACC ID vvvvvvvvvvvvvvvvvvvvvvv +/* 20, 54 */ + 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, + +/* 9, 74 */ 0x68U, 0x40U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, 0x00U, /* fee */ +/* 35, 83 */ 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 */ +/* 2, 118 */ 0x81U, 0x14U, /* src acc preamble */ + +/* 20, 120 */ 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, /* src acc */ +/* 2, 140 */ 0x83U, 0x14U, /* dest acc preamble */ +/* 20, 142 */ 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, 0,0,0,0,0, /* dest acc */ + +/* 138, 162 */ 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,0,0,0,0,0,0,0, /* emit detail */ + 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,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,0,0,0,0,0,0,0,0,0,0 +/* 0, 300 */ +}; + +#define TLACC (txn_out + 54) /* set it above!! */ + +#define TXNLEN 300 +#define FEEOUT (txn_out + 75) +#define EMITDET (txn_out + 162) +#define HOOKACC (txn_out + 120) +#define OTXNACC (txn_out + 142) +#define DTAG_OUT (txn_out + 142) +#define DESTACC (txn_out + 142) +#define OUTAMT (txn_out + 25) +#define OUTCUR (txn_out + 34) +#define OUTISS (txn_out + 54) +#define CURSHORT (txn_out + 46) + +// if we make a TrustSet instead (used for initialisation) then we'll truncate the template above +#define TTOUT (txn_out + 2) // when it's a TrustSet we set this to 0x14 +#define OUTAMT_TL (txn_out + 25) // when it's a TrustSet, we set this to 0x63 +#define EMITDET_TL (txn_out + 140) // when it's a TrustSet Emit Details occurs sooner +#define TXNLEN_TL 278 // .. and the txn is smaller + +#define BE_DROPS(drops)\ +{\ + uint64_t drops_tmp = drops;\ + uint8_t* b = (uint8_t*)&drops;\ + *b++ = 0b01000000 + (( drops_tmp >> 56 ) & 0b00111111 );\ + *b++ = (drops_tmp >> 48) & 0xFFU;\ + *b++ = (drops_tmp >> 40) & 0xFFU;\ + *b++ = (drops_tmp >> 32) & 0xFFU;\ + *b++ = (drops_tmp >> 24) & 0xFFU;\ + *b++ = (drops_tmp >> 16) & 0xFFU;\ + *b++ = (drops_tmp >> 8) & 0xFFU;\ + *b++ = (drops_tmp >> 0) & 0xFFU;\ +} + +#define COPY_20(src, dst)\ +{\ + *((uint64_t*)(((uint8_t*)(dst)) + 0)) = \ + *((uint64_t*)(((uint8_t*)(src)) + 0));\ + *((uint64_t*)(((uint8_t*)(dst)) + 8)) = \ + *((uint64_t*)(((uint8_t*)(src)) + 8));\ + *((uint32_t*)(((uint8_t*)(dst)) + 16)) = \ + *((uint32_t*)(((uint8_t*)(src)) + 16));\ +} + +#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 ns[32] = { + 0x21, 0x2C, 0xCE, 0x06, 0x94, 0xF0, 0x63, 0xCC, + 0x1D, 0xB8, 0xCF, 0xA3, 0x46, 0xE2, 0x96, 0xF3, + 0xE1, 0xF9, 0xE1, 0xF0, 0xFE, 0x93, 0x12, 0xF6, + 0x87, 0x58, 0x00, 0xBA, 0x70, 0x57, 0x8F, 0xFE +}; + +#define DELAY 1U + +int64_t cbak(uint32_t f) +{ + // TODO track withdrawal txns to see if they successfully executed + return 0; +} + +int64_t hook(uint32_t r) +{ + _g(1,1); + + etxn_reserve(1); + + uint8_t hook_accid[32]; + hook_account(hook_accid + 12, 20); + + uint8_t otxn_accid[32]; + otxn_field(otxn_accid + 12, 20, sfAccount); + + if (BUFFER_EQUAL_20(hook_accid + 12, otxn_accid + 12)) + DONE("Monolithic: passing outgoing txn"); + + int64_t tt = otxn_type(); + if (tt != ttINVOKE && tt != ttPAYMENT) + NOPE("Monolithic: Rejecting non-Invoke, non-Payment txn."); + + otxn_slot(1); + + slot_subfield(1, sfAmount, 2); + + uint8_t amt[48]; + + if (slot_size(2) == 8) + DONE("Monolithic: Passing incoming XAH payment."); + + // get admin account (role) + uint8_t _admin[20]; + if (hook_param(SBUF(_admin), "ADM", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ADM install parameter."); + + // get settlement account (role) + uint8_t _stl[20]; + if (hook_param(SBUF(_stl), "STL", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing STL install parameter."); + + // get the withdrawal signing key + uint8_t _wkey[33]; + if (hook_param(SBUF(_wkey), "WKEY", 4) != 33) + NOPE("Monolithic: Misconfigured. Missing WKEY install parameter."); + + // get currency + if (hook_param(OUTCUR, 20, "CUR", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing CUR install parameter."); + + // get currency issuer + if (hook_param(OUTISS, 20, "ISS", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ISS install parameter."); + + // get settlement account (asset settlement account) + uint8_t acc[20]; + if (hook_param(SBUF(acc), "ACC", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ACC install parameter."); + + // Operation + uint8_t op; + if (otxn_param(&op, 1, "OP", 2) != 1) + NOPE("Monolithic: Missing OP parameter on Invoke."); + + // Sub Operation + uint8_t sop; + if ((op == 'M' || op == 'W') && otxn_param(&sop, 1, "SOP", 3) != 1) + NOPE("Monolithic: Missing SOP parameter on Invoke."); + + int64_t xfl_in; + uint32_t flags; + uint32_t dtag; + uint8_t fs[20]; + + if (tt == ttPAYMENT) + { + // this will fail if flags isn't in the txn, that's also ok. + otxn_field(&flags, 4, sfFlags); + + // check for partial payments (0x00020000) -> (0x00000200 LE) + if (flags & 0x200U) + NOPE("Monolithic: Partial payments are not supported."); + + otxn_field(SBUF(amt), sfAmount); + + if (!BUFFER_EQUAL_20(amt + 8, OUTCUR)) + NOPE("Monolithic: Wrong currency."); + + if (!BUFFER_EQUAL_20(amt + 28, OUTISS)) + NOPE("Monolithic: Wrong issuer."); + + xfl_in = slot_float(2); + + if (xfl_in < 0 || !float_compare(xfl_in, 0, COMPARE_GREATER)) + NOPE("Monolithic: Invalid sfAmount."); + } + + // enforced pausedness + if (op != 'U') + { + uint8_t paused; + state(&paused, 1, "P", 1); + if (paused) + NOPE("Monolithic: Paused."); + } + + // check if the trustline exists + uint8_t keylet[34]; + util_keylet(keylet, 34, KEYLET_LINE, hook_accid + 12, 20, OUTISS, 20, OUTCUR, 20); + + int64_t already_setup = (slot_set(SBUF(keylet), 10) == 10); + + // enforce initalisation + if (!already_setup && op != 'I') + NOPE("Monolithic: Send op=I initalisation first."); + + uint8_t admin[20]; + uint8_t stl[20]; + uint8_t wkey[33]; + if (already_setup && op != 'I') + { + state(SBUF(admin), "ADM", 3); + state(SBUF(stl), "STL", 3); + state(SBUF(wkey), "WKEY", 4); + } + + int64_t is_admin = BUFFER_EQUAL_20(otxn_accid + 12, admin); + int64_t is_stl = BUFFER_EQUAL_20(otxn_accid + 12, stl); + + // sanity check + if ((op == 'D') && tt != ttPAYMENT) + NOPE("Monolithic: Deposit operations must be a payment transaction."); + + // permission check + if (!is_admin && (op == 'U' || op == 'P' || op == 'M')) + NOPE("Monolithic: Admin only operation."); + + if (!is_stl && (op == 'B' || op == 'S' || op == 'R')) + NOPE("Monolithic: Settler only operation."); + + // current ledger seq is used when emitting a txn + int64_t seq = ledger_seq() + 1; + + uint8_t stl_bal[8]; + state(SBUF(stl_bal), hook_accid + 12, 20); + int64_t stl_bal_xfl = *((int64_t*)stl_bal); + + // action + switch (op) + { + case 'I': + { + if (already_setup) + DONE("Monolithic: Already setup trustline."); + + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + + // create a trustline ... + uint8_t xfl_buffer[8]; + if (otxn_param(xfl_buffer, 8, "AMT", 3) != 8) + NOPE("Monolithic: Misconfigured. Missing AMT otxn parameter."); + + int64_t xfl_out = *((int64_t *)xfl_buffer); + + // write limit amount + float_sto(OUTAMT_TL, 49, OUTCUR, 20, OUTISS, 20, xfl_out, sfLimitAmount); + + // set the template transaction type to trustset + *TTOUT = 0x14U; + + etxn_details(EMITDET_TL, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN_TL); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN_TL, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN_TL); + TRACEVAR(emit_result); + + state_set(_admin, 20, "ADM", 3); + state_set(_stl, 20, "STL", 3); + state_set(_wkey, 33, "WKEY", 4); + DONE("Monolithic: Emitted TrustSet to initialize."); + } + + case 'U': + case 'P': + { + // pause + uint8_t paused = (op == 'P' ? 1 : 0); + state_set(&paused, 1, "P", 1); + DONE("Monolithic: Paused/Unpaused."); + } + + case 'D': + { + int64_t test = otxn_param(SBUF(fs), "FS", 2); + TRACEVAR(test); + if (otxn_param(SBUF(fs), "FS", 2) != 20) + NOPE("Monolithic: Misconfigured. Missing FS otxn parameter."); + + int64_t dtag_bal_xfl; + state(SVAR(dtag_bal_xfl), SBUF(fs)); + + int64_t final_bal_xfl = float_sum(dtag_bal_xfl, xfl_in); + if (final_bal_xfl < 0) + NOPE("Monolithic: Insane balance on deposit."); + + if (state_set(SVAR(final_bal_xfl), SBUF(fs)) != 8) + NOPE("Monolithic: Failed to set state on deposit."); + + DONE("Monolithic: Deposited."); + } + + // debit + case 'B': + { + if (otxn_param(SBUF(fs), "FS", 2) != 20) + NOPE("Monolithic: Misconfigured. Missing FS otxn parameter."); + + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) + NOPE("Monolithic: Misconfigured. Missing AMT otxn parameter."); + + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) + NOPE("Monolithic: Misconfigured. Missing SEQ otxn parameter."); + + // check the nonce + uint32_t dbt_seq; + state_foreign(SVAR(dbt_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20); + + if (dbt_seq != sig_nce) + NOPE("Monolithic: Debit nonce out of sequence."); + + int64_t dtag_bal_xfl; + state(SVAR(dtag_bal_xfl), SBUF(fs)); + + // check dtag balance + if (dtag_bal_xfl <= 0 || !float_compare(dtag_bal_xfl, 0, COMPARE_GREATER)) + NOPE("Monolithic: Insane balance on dtag."); + + if (sig_amt <= 0) + NOPE("Monolithic: Must provide AMT param when performing debit."); + + if (float_compare(sig_amt, dtag_bal_xfl, COMPARE_GREATER)) + NOPE("Monolithic: Balance not high enough for this debit."); + + int64_t sub_dtag_bal_xfl = float_sum(dtag_bal_xfl, float_negate(sig_amt)); + if (state_set(SVAR(sub_dtag_bal_xfl), SBUF(fs)) != 8) + NOPE("Monolithic: Insane balance on fs."); + + int64_t add_stl_bal_xfl = float_sum(stl_bal_xfl, sig_amt); + if (state_set(SVAR(add_stl_bal_xfl), hook_accid + 12, 20) != 8) + NOPE("Monolithic: Insane balance on stl."); + + dbt_seq++; + if (state_foreign_set(SVAR(dbt_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20) != 4) + NOPE("Monolithic: Failed to set state."); + + DONE("Monolithic: Debited."); + break; + } + + // settlement + case 'S': + { + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + + // check trustline balance + slot_subfield(10, sfBalance, 11); + if (slot_size(11) != 48) + NOPE("Monolithic: Could not fetch trustline balance."); + + int64_t xfl_bal = slot_float(11); + + if (xfl_bal <= 0 || !float_compare(xfl_bal, 0, COMPARE_GREATER)) + NOPE("Monolithic: Insane balance on trustline."); + + // set the destination addr to the settlement addr + COPY_20(acc, DESTACC); + + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) + NOPE("Monolithic: Misconfigured. Missing AMT otxn parameter."); + + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) + NOPE("Monolithic: Misconfigured. Missing SEQ otxn parameter."); + + // check the nonce + uint32_t stl_seq; + state_foreign(SVAR(stl_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20); + + if (stl_seq != sig_nce) + NOPE("Monolithic: Settlement nonce out of sequence."); + + if (sig_amt <= 0) + NOPE("Monolithic: Settlement amount must be greater than 0."); + + TRACEVAR(sig_amt); + TRACEVAR(xfl_bal); + if (float_compare(sig_amt, xfl_bal, COMPARE_GREATER)) + NOPE("Monolithic: Balance not high enough for this settlement."); + + // write payment amount + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, sig_amt, sfAmount); + + etxn_details(EMITDET, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); + if (emit_result < 0) + { + NOPE("Monolithic: Settle Emitted Failure."); + } + + int64_t sub_stl_bal_xfl = float_sum(stl_bal_xfl, float_negate(sig_amt)); + if (state_set(SVAR(sub_stl_bal_xfl), hook_accid + 12, 20) != 8) + NOPE("Monolithic: Insane balance on stl."); + + stl_seq++; + if (state_foreign_set(SVAR(stl_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20) != 4) + NOPE("Monolithic: Failed to set state."); + + DONE("Monolithic: Emitted settlement."); + break; + } + + // withdrawal + case 'W': + { + switch (sop) + { + case 'A': // approval (signature) + { + // get signature if any + // Signature format is packed binary data of the form: + // <20 byte dest accid><8 byte le xfl amount><4 byte le int expiry timestamp><4 byte le int nonce> + uint8_t sig_buf[256]; + int64_t sig_len = otxn_param(SBUF(sig_buf), "SIG", 3); + + // place pointers according to packed data + uint8_t* sig_acc = sig_buf; + uint64_t sig_amt = *((uint64_t*)(sig_buf + 20)); + uint32_t sig_exp = *((uint32_t*)(sig_buf + 28)); + uint32_t sig_nce = *((uint32_t*)(sig_buf + 32)); + uint8_t* sig = sig_buf + 36; + + if (sig_len > 0) + { + if (sig_len < 80) + NOPE("Monolithic: Signature too short."); + + if (!util_verify(sig_buf, 36, sig_buf + 36, sig_len - 36, SBUF(wkey))) + NOPE("Monolithic: Signature verification failed."); + } + + if (sig_len <= 0) + NOPE("Monolithic: Missing SIG parameter."); + + int64_t time = ledger_last_time(); + if (time > sig_exp) + NOPE("Monolithic: Ticket has expired."); + + if (!BUFFER_EQUAL_20(sig_acc, otxn_accid + 12)) + NOPE("Monolithic: Wrong account for ticket."); + + // check dtag balance + int64_t dtag_bal_xfl; + state(SVAR(dtag_bal_xfl), sig_acc, 20); + + // check the nonce + uint32_t wth_seq; + state_foreign(SVAR(wth_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20); + TRACEVAR(wth_seq); + + if (wth_seq != sig_nce) + NOPE("Monolithic: Nonce out of sequence."); + + // check bal can support withdraw + TRACEVAR(dtag_bal_xfl); + TRACEVAR(sig_amt); + if (float_compare(sig_amt, dtag_bal_xfl, COMPARE_GREATER)) + NOPE("Monolithic: Balance not high enough for this withdrawal."); + + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + COPY_20(sig_acc, DESTACC); + + // write payment amount + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, sig_amt, sfAmount); + + etxn_details(EMITDET, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); + if (emit_result < 0) + { + NOPE("Monolithic: Withdrawal Emitted Failure."); + } + + int64_t sub_dtag_bal_xfl = float_sum(dtag_bal_xfl, float_negate(sig_amt)); + if (state_set(SVAR(sub_dtag_bal_xfl), sig_acc, 20) != 8) + NOPE("Monolithic: Insane balance on fs."); + + // update nonce + wth_seq++; + if (state_foreign_set(SVAR(wth_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20) != 4) + NOPE("Monolithic: Failed to set state."); + + DONE("Monolithic: Emitted approval withdrawal."); + } + + case 'I': // permissionless (intent) + { + if (otxn_param(SVAR(fs), "FS", 2) != 20) + NOPE("Monolithic: Misconfigured. Missing FS otxn parameter."); + + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) + NOPE("Monolithic: Misconfigured. Missing AMT otxn parameter."); + + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) + NOPE("Monolithic: Misconfigured. Missing SEQ otxn parameter."); + + // check dtag balance + int64_t dtag_bal_xfl; + state(SVAR(dtag_bal_xfl), SBUF(fs)); + + // check the nonce + uint32_t wth_seq; + state_foreign(SVAR(wth_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20); + TRACEVAR(wth_seq); + + if (wth_seq != sig_nce) + NOPE("Monolithic: Nonce out of sequence."); + + // check bal can support withdraw + if (float_compare(sig_amt, dtag_bal_xfl, COMPARE_GREATER)) + NOPE("Monolithic: Balance not high enough for this withdrawal."); + + int64_t sub_dtag_bal_xfl = float_sum(dtag_bal_xfl, float_negate(sig_amt)); + if (state_set(SVAR(sub_dtag_bal_xfl), SBUF(fs)) != 8) + NOPE("Monolithic: Insane balance on stl."); + + int64_t time = ledger_last_time() + DELAY; + uint8_t pending_buff[24]; + COPY_20(fs, pending_buff); + UINT32_TO_BUF(pending_buff + 20U, sig_nce); + + TRACEHEX(pending_buff); + + uint8_t amount_buff[16]; + INT64_TO_BUF(amount_buff, FLIP_ENDIAN_64(sig_amt)); + INT64_TO_BUF(amount_buff + 8U, time); + + if (state_set(SBUF(amount_buff), SBUF(pending_buff)) != 16) + NOPE("Monolithic: Could not save pending withdrawal intent."); + + // update nonce + wth_seq++; + if (state_foreign_set(SVAR(wth_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20) != 4) + NOPE("Monolithic: Failed to set state."); + + DONE("Monolithic: Created permissionless withdraw intent."); + } + + case 'E': // permissionless (execution) + { + uint8_t pending_buff[24]; + if (otxn_param(SBUF(pending_buff), "WI", 2) != 24) + NOPE("Monolithic: Misconfigured. Missing WI otxn parameter."); + + TRACEHEX(pending_buff); + + uint8_t amount_buff[16]; + if (state(SBUF(amount_buff), SBUF(pending_buff)) == DOESNT_EXIST) + NOPE("Monolithic: Misconfigured. Withdraw Intent does not exist."); + + int64_t delay_time = UINT64_FROM_BUF(amount_buff + 8); + int64_t time = ledger_last_time(); + if (time < delay_time) + NOPE("Monolithic: Need to wait.."); + + uint64_t sig_amt = *((uint64_t*)(amount_buff + 0U)); + + TRACEVAR(sig_amt); + + ACCOUNT_TO_BUF(HOOKACC, hook_accid + 12); + COPY_20(pending_buff, DESTACC); + + // write payment amount + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, sig_amt, sfAmount); + + etxn_details(EMITDET, 138); + int64_t fee = etxn_fee_base(txn_out, TXNLEN); + BE_DROPS(fee); + *((uint64_t*)(FEEOUT)) = fee; + + txn_out[15] = (seq >> 24U) & 0xFFU; + txn_out[16] = (seq >> 16U) & 0xFFU; + txn_out[17] = (seq >> 8U) & 0xFFU; + txn_out[18] = seq & 0xFFU; + + seq += 4; + txn_out[21] = (seq >> 24U) & 0xFFU; + txn_out[22] = (seq >> 16U) & 0xFFU; + txn_out[23] = (seq >> 8U) & 0xFFU; + txn_out[24] = seq & 0xFFU; + + trace(SBUF("emit:"), txn_out, TXNLEN, 1); + + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); + if (emit_result < 0) + { + NOPE("Monolithic: Withdrawal Emitted Failure."); + } + + state_set(0, 0, SBUF(pending_buff)); + + DONE("Monolithic: Emitted permissionless withdrawal."); + } + + default: + { + NOPE("Monolithic: Unknown operation."); + } + } + } + + // refund + case 'R': + { + if (otxn_param(SBUF(fs), "FS", 2) != 20) + NOPE("Monolithic: Misconfigured. Missing FS otxn parameter."); + + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) + NOPE("Monolithic: Misconfigured. Missing AMT otxn parameter."); + + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) + NOPE("Monolithic: Misconfigured. Missing SEQ otxn parameter."); + + // check the nonce + uint32_t rfd_seq; + state_foreign(SVAR(rfd_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20); + TRACEVAR(rfd_seq); + + if (rfd_seq != sig_nce) + NOPE("Monolithic: Refund nonce out of sequence."); + + // check dtag balance + int64_t dtag_bal_xfl; + state(SVAR(dtag_bal_xfl), SBUF(fs)); + + TRACEVAR(dtag_bal_xfl); + TRACEVAR(sig_amt); + + // check dtag balance + if (dtag_bal_xfl <= 0 || !float_compare(dtag_bal_xfl, 0, COMPARE_GREATER)) + NOPE("Monolithic: Insane balance on dtag."); + + if (sig_amt <= 0) + NOPE("Monolithic: Must provide AMT param when performing refund."); + + if (float_compare(sig_amt, dtag_bal_xfl, COMPARE_GREATER)) + NOPE("Monolithic: Balance not high enough for this debit."); + + int64_t add_dtag_bal_xfl = float_sum(dtag_bal_xfl, sig_amt); + if (state_set(SVAR(add_dtag_bal_xfl), SBUF(fs)) != 8) + NOPE("Monolithic: Insane balance on dtag."); + + int64_t sub_stl_bal_xfl = float_sum(stl_bal_xfl, float_negate(sig_amt)); + if (state_set(SVAR(sub_stl_bal_xfl), hook_accid + 12, 20) != 8) + NOPE("Monolithic: Insane balance on stl."); + + rfd_seq++; + if (state_foreign_set(SVAR(rfd_seq), otxn_accid + 12, 20, SBUF(ns), hook_accid + 12, 20) != 4) + NOPE("Monolithic: Failed to set state."); + + DONE("Monolithic: Refunded."); + break; + } + + case 'M': + { + switch (sop) + { + case 'A': // admin (role) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "ADM", 3); + DONE("Monolithic: ADM Modified."); + } + + case 'S': // settler (role) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "STL", 3); + DONE("Monolithic: STL Modified."); + } + + case 'W': // withdraw (role) + { + uint8_t key[33]; + if (otxn_param(SBUF(key), "WKEY", 4) != 33) + NOPE("Monolithic: Misconfigured. Missing KEY modify parameter."); + + state_set(key, 33, "WKEY", 4); + DONE("Monolithic: WKEY Modified."); + } + + default: + { + NOPE("Monolithic: Unknown operation."); + } + } + } + + default: + { + NOPE("Monolithic: Unknown operation."); + } + } + + return 0; +} \ No newline at end of file diff --git a/contracts/funds/monolithic.c b/contracts/funds/monolithic/monolithic_multiasset.c similarity index 79% rename from contracts/funds/monolithic.c rename to contracts/funds/monolithic/monolithic_multiasset.c index c0a8821..1bcf8c4 100644 --- a/contracts/funds/monolithic.c +++ b/contracts/funds/monolithic/monolithic_multiasset.c @@ -1,7 +1,7 @@ //------------------------------------------------------------------------------ /* -HookParameters: +HookParameters -> State: ADM: The admin account (role) STL: The settler account (role) @@ -12,6 +12,9 @@ CUR: The currency allowed in the funding source ISS: The issuer account allowed in the funding source ACC: The settlement account (globally per asset) +for each asset: + <20 byte currency><20 byte issuer>: <20 byte settlement address> + Operations: // payment ops are: @@ -25,20 +28,24 @@ ACC: The settlement account (globally per asset) // M - modify (admin) // R - refund (refunder) // W - withdraw (user) + // A - asset (admin) -// invoke sub ops are: +// invoke (Modify) sub ops are: // A - modify admin account (role) - // B - modify debit account (role) // S - modify settler account (role) // W - modify withdraw pubkey (role) - +// invoke (Asset) sub ops are: + // C - modify create asset + // D - modify delete asset */ //============================================================================== #include "hookapi.h" +#define SVAR(x) &(x), sizeof(x) + #define DONE(x)\ return accept(SBUF(x), __LINE__) @@ -171,23 +178,18 @@ int64_t hook(uint32_t r) DONE("Monolithic: Passing incoming XAH payment."); // get admin account (role) - uint8_t admin[20]; - if (hook_param(SBUF(admin), "ADM", 3) != 20) + uint8_t _admin[20]; + if (hook_param(SBUF(_admin), "ADM", 3) != 20) NOPE("Monolithic: Misconfigured. Missing ADM install parameter."); // get settlement account (role) - uint8_t stl[20]; - if (hook_param(SBUF(stl), "STL", 3) != 20) + uint8_t _stl[20]; + if (hook_param(SBUF(_stl), "STL", 3) != 20) NOPE("Monolithic: Misconfigured. Missing STL install parameter."); - - // get refund account (role) - uint8_t rfd[20]; - if (hook_param(SBUF(rfd), "RFD", 3) != 20) - NOPE("Monolithic: Misconfigured. Missing RFD install parameter."); // get the withdrawal signing key - uint8_t wkey[33]; - if (hook_param(SBUF(wkey), "WKEY", 4) != 33) + uint8_t _wkey[33]; + if (hook_param(SBUF(_wkey), "WKEY", 4) != 33) NOPE("Monolithic: Misconfigured. Missing WKEY install parameter."); // get currency @@ -215,7 +217,7 @@ int64_t hook(uint32_t r) int64_t xfl_in; uint32_t flags; - uint8_t dtag[32]; + uint32_t dtag; if (tt == ttPAYMENT) { @@ -228,7 +230,7 @@ int64_t hook(uint32_t r) otxn_field(SBUF(amt), sfAmount); - if (otxn_field(dtag + 28, 4, sfDestinationTag) != 4) + if (otxn_field(SVAR(dtag), sfDestinationTag) != 4) NOPE("Monolithic: Destination Tag is Required."); if (!BUFFER_EQUAL_20(amt + 8, OUTCUR)) @@ -243,24 +245,6 @@ int64_t hook(uint32_t r) NOPE("Monolithic: Invalid sfAmount."); } - int64_t is_admin = BUFFER_EQUAL_20(otxn_accid + 12, admin); - int64_t is_stl = BUFFER_EQUAL_20(otxn_accid + 12, stl); - int64_t is_rfd = BUFFER_EQUAL_20(otxn_accid + 12, rfd); - - // sanity check - if ((op == 'D') && tt != ttPAYMENT) - NOPE("Monolithic: Deposit operations must be a payment transaction."); - - // permission check - if (!is_admin && (op == 'U' || op == 'P' || op == 'M')) - NOPE("Monolithic: Admin only operation."); - - if (!is_stl && (op == 'B' || op == 'S')) - NOPE("Monolithic: Settler only operation."); - - if (!is_rfd && (op == 'R')) - NOPE("Monolithic: Refunder only operation."); - // enforced pausedness if (op != 'U') { @@ -280,6 +264,30 @@ int64_t hook(uint32_t r) if (!already_setup && op != 'I') NOPE("Monolithic: Send op=I initalisation first."); + uint8_t admin[20]; + uint8_t stl[20]; + uint8_t wkey[33]; + if (already_setup && op != 'I') + { + state(SBUF(admin), "ADM", 3); + state(SBUF(stl), "STL", 3); + state(SBUF(wkey), "WKEY", 4); + } + + int64_t is_admin = BUFFER_EQUAL_20(otxn_accid + 12, admin); + int64_t is_stl = BUFFER_EQUAL_20(otxn_accid + 12, stl); + + // sanity check + if ((op == 'D') && tt != ttPAYMENT) + NOPE("Monolithic: Deposit operations must be a payment transaction."); + + // permission check + if (!is_admin && (op == 'U' || op == 'P' || op == 'M')) + NOPE("Monolithic: Admin only operation."); + + if (!is_stl && (op == 'B' || op == 'S')) + NOPE("Monolithic: Settler only operation."); + // current ledger seq is used when emitting a txn int64_t seq = ledger_seq() + 1; @@ -332,6 +340,16 @@ int64_t hook(uint32_t r) uint8_t emithash[32]; int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN_TL); TRACEVAR(emit_result); + + state_set(_admin, 20, "ADM", 3); + state_set(_stl, 20, "STL", 3); + state_set(_wkey, 33, "WKEY", 4); + + // save asset + uint8_t asset[40]; + COPY_20(OUTCUR, asset); + COPY_20(OUTISS, asset); + state_set(acc, 20, asset, 40); DONE("Monolithic: Emitted TrustSet to initialize."); } @@ -346,14 +364,15 @@ int64_t hook(uint32_t r) case 'D': { - uint8_t dtag_bal[8]; - state(SBUF(dtag_bal), dtag + 28, 4); - int64_t dtag_bal_xfl = *((int64_t*)dtag_bal); - - int64_t total_dtag_bal_xfl = float_sum(dtag_bal_xfl, xfl_in); - INT64_TO_BUF(dtag_bal, FLIP_ENDIAN_64(total_dtag_bal_xfl)); - if (state_set(dtag_bal, 8, dtag + 28, 4) != 8) - NOPE("Monolithic: Insane balance on depositor."); + int64_t dtag_bal_xfl; + state(SVAR(dtag_bal_xfl), SVAR(dtag)); + + int64_t final_bal_xfl = float_sum(dtag_bal_xfl, xfl_in); + if (final_bal_xfl < 0) + NOPE("Monolithic: Insane balance on deposit."); + + if (state_set(SVAR(final_bal_xfl), SVAR(dtag)) != 8) + NOPE("Monolithic: Failed to set state on deposit."); DONE("Monolithic: Deposited."); } @@ -361,30 +380,26 @@ int64_t hook(uint32_t r) // debit case 'B': { - if (otxn_param(dtag + 28, 4, "TAG", 3) != 4) + if (otxn_param(SVAR(dtag), "TAG", 3) != 4) NOPE("Monolithic: Misconfigured. Missing TAG otxn parameter."); - uint8_t xfl_buffer[8]; - if (otxn_param(xfl_buffer, 8, "AMT", 3) != 8) + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) NOPE("Monolithic: Misconfigured. Missing AMT otxn parameter."); - uint64_t sig_amt = *((uint64_t*)(xfl_buffer)); - uint8_t nonce_buffer[4]; - if (otxn_param(nonce_buffer, 4, "SEQ", 3) != 4) + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) NOPE("Monolithic: Misconfigured. Missing SEQ otxn parameter."); - uint32_t sig_nce = *((uint32_t*)(nonce_buffer)); // check the nonce - uint64_t dbt_seq; - state(&dbt_seq, 8, otxn_accid + 12, 20); - TRACEVAR(dbt_seq); + uint32_t dbt_seq; + state(SVAR(dbt_seq), otxn_accid + 12, 20); if (dbt_seq != sig_nce) NOPE("Monolithic: Debit nonce out of sequence."); - uint8_t dtag_bal[8]; - state(SBUF(dtag_bal), dtag + 28, 4); - int64_t dtag_bal_xfl = *((int64_t*)dtag_bal); + int64_t dtag_bal_xfl; + state(SVAR(dtag_bal_xfl), SVAR(dtag)); // check dtag balance if (dtag_bal_xfl <= 0 || !float_compare(dtag_bal_xfl, 0, COMPARE_GREATER)) @@ -397,17 +412,15 @@ int64_t hook(uint32_t r) NOPE("Monolithic: Balance not high enough for this debit."); int64_t sub_dtag_bal_xfl = float_sum(dtag_bal_xfl, float_negate(sig_amt)); - INT64_TO_BUF(dtag_bal, FLIP_ENDIAN_64(sub_dtag_bal_xfl)); - if (state_set(dtag_bal, 8, dtag + 28, 4) != 8) + if (state_set(SVAR(sub_dtag_bal_xfl), SVAR(dtag)) != 8) NOPE("Monolithic: Insane balance on dtag."); int64_t add_stl_bal_xfl = float_sum(stl_bal_xfl, sig_amt); - INT64_TO_BUF(stl_bal, FLIP_ENDIAN_64(add_stl_bal_xfl)); - if (state_set(stl_bal, 8, hook_accid + 12, 20) != 8) + if (state_set(SVAR(add_stl_bal_xfl), hook_accid + 12, 20) != 8) NOPE("Monolithic: Insane balance on stl."); dbt_seq++; - if (state_set(&dbt_seq, 8, otxn_accid + 12, 20) != 8) + if (state_set(SVAR(dbt_seq), otxn_accid + 12, 20) != 4) NOPE("Monolithic: Failed to set state."); DONE("Monolithic: Debited."); @@ -432,20 +445,17 @@ int64_t hook(uint32_t r) // set the destination addr to the settlement addr COPY_20(acc, DESTACC); - uint8_t xfl_buffer[8]; - if (otxn_param(xfl_buffer, 8, "AMT", 3) != 8) + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) NOPE("Monolithic: Misconfigured. Missing AMT otxn parameter."); - uint64_t sig_amt = *((uint64_t*)(xfl_buffer)); - uint8_t nonce_buffer[4]; - if (otxn_param(dtag + 28, 4, "SEQ", 3) != 4) + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) NOPE("Monolithic: Misconfigured. Missing SEQ otxn parameter."); - uint32_t sig_nce = *((uint32_t*)(nonce_buffer)); // check the nonce - uint64_t stl_seq; - state(&stl_seq, 8, otxn_accid + 12, 20); - TRACEVAR(stl_seq); + uint32_t stl_seq; + state(SVAR(stl_seq), otxn_accid + 12, 20); if (stl_seq != sig_nce) NOPE("Monolithic: Settlement nonce out of sequence."); @@ -458,9 +468,8 @@ int64_t hook(uint32_t r) if (float_compare(sig_amt, xfl_bal, COMPARE_GREATER)) NOPE("Monolithic: Balance not high enough for this settlement."); - int64_t xfl_out = sig_amt; // write payment amount - float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, xfl_out, sfAmount); + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, sig_amt, sfAmount); etxn_details(EMITDET, 138); int64_t fee = etxn_fee_base(txn_out, TXNLEN); @@ -488,12 +497,11 @@ int64_t hook(uint32_t r) } int64_t sub_stl_bal_xfl = float_sum(stl_bal_xfl, float_negate(sig_amt)); - INT64_TO_BUF(stl_bal, FLIP_ENDIAN_64(sub_stl_bal_xfl)); - if (state_set(stl_bal, 8, hook_accid + 12, 20) != 8) + if (state_set(SVAR(sub_stl_bal_xfl), hook_accid + 12, 20) != 8) NOPE("Monolithic: Insane balance on stl."); stl_seq++; - if (state_set(&stl_seq, 8, otxn_accid + 12, 20) != 8) + if (state_set(SVAR(stl_seq), otxn_accid + 12, 20) != 4) NOPE("Monolithic: Failed to set state."); DONE("Monolithic: Emitted settlement."); @@ -533,12 +541,8 @@ int64_t hook(uint32_t r) TRACEVAR(sig_dtag); // check dtag balance - uint8_t dtag_bal[8]; - UINT32_TO_BUF(dtag + 28, sig_dtag); - TRACEHEX(dtag); - state(SBUF(dtag_bal), dtag + 28, 4); - int64_t dtag_bal_xfl = *((int64_t*)dtag_bal); - TRACEVAR(dtag_bal_xfl); + int64_t dtag_bal_xfl; + state(SVAR(dtag_bal_xfl), SVAR(sig_dtag)); if (sig_len <= 0) NOPE("Monolithic: Missing SIG parameter."); @@ -551,8 +555,9 @@ int64_t hook(uint32_t r) NOPE("Monolithic: Wrong account for ticket."); // check the nonce - uint64_t wth_seq; - state(&wth_seq, 8, otxn_accid + 12, 20); + uint32_t wth_seq; + state(SVAR(wth_seq), otxn_accid + 12, 20); + TRACEVAR(wth_seq); if (wth_seq != sig_nce) NOPE("Monolithic: Nonce out of sequence."); @@ -561,10 +566,8 @@ int64_t hook(uint32_t r) if (float_compare(sig_amt, dtag_bal_xfl, COMPARE_GREATER)) NOPE("Monolithic: Balance not high enough for this withdrawal."); - int64_t xfl_out = sig_amt; - // write payment amount - float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, xfl_out, sfAmount); + float_sto(OUTAMT, 49, OUTCUR, 20, OUTISS, 20, sig_amt, sfAmount); etxn_details(EMITDET, 138); int64_t fee = etxn_fee_base(txn_out, TXNLEN); @@ -588,17 +591,16 @@ int64_t hook(uint32_t r) int64_t emit_result = emit(SBUF(emithash), txn_out, TXNLEN); if (emit_result < 0) { - NOPE("Monolithic: Settle Emitted Failure."); + NOPE("Monolithic: Withdrawal Emitted Failure."); } int64_t sub_stl_bal_xfl = float_sum(stl_bal_xfl, float_negate(sig_amt)); - INT64_TO_BUF(stl_bal, FLIP_ENDIAN_64(sub_stl_bal_xfl)); - if (state_set(stl_bal, 8, hook_accid + 12, 20) != 8) + if (state_set(SVAR(sub_stl_bal_xfl), hook_accid + 12, 20) != 8) NOPE("Monolithic: Insane balance on stl."); // update nonce wth_seq++; - if (state_set(&wth_seq, 8, otxn_accid + 12, 20) != 8) + if (state_set(SVAR(wth_seq), otxn_accid + 12, 20) != 4) NOPE("Monolithic: Failed to set state."); DONE("Monolithic: Emitted withdrawal."); @@ -608,22 +610,20 @@ int64_t hook(uint32_t r) // refund case 'R': { - if (otxn_param(dtag + 28, 4, "TAG", 3) != 4) + if (otxn_param(SVAR(dtag), "TAG", 3) != 4) NOPE("Monolithic: Misconfigured. Missing TAG otxn parameter."); - uint8_t xfl_buffer[8]; - if (otxn_param(xfl_buffer, 8, "AMT", 3) != 8) + int64_t sig_amt; + if (otxn_param(SVAR(sig_amt), "AMT", 3) != 8 || sig_amt < 0) NOPE("Monolithic: Misconfigured. Missing AMT otxn parameter."); - uint64_t sig_amt = *((uint64_t*)(xfl_buffer)); - uint8_t nonce_buffer[4]; - if (otxn_param(nonce_buffer, 4, "SEQ", 3) != 4) + uint32_t sig_nce; + if (otxn_param(SVAR(sig_nce), "SEQ", 3) != 4) NOPE("Monolithic: Misconfigured. Missing SEQ otxn parameter."); - uint32_t sig_nce = *((uint32_t*)(nonce_buffer)); // check the nonce - uint64_t rfd_seq; - state(&rfd_seq, 8, otxn_accid + 12, 20); + uint32_t rfd_seq; + state(SVAR(rfd_seq), otxn_accid + 12, 20); TRACEVAR(rfd_seq); if (rfd_seq != sig_nce) @@ -644,17 +644,15 @@ int64_t hook(uint32_t r) NOPE("Monolithic: Balance not high enough for this debit."); int64_t add_dtag_bal_xfl = float_sum(dtag_bal_xfl, sig_amt); - INT64_TO_BUF(dtag_bal, FLIP_ENDIAN_64(add_dtag_bal_xfl)); - if (state_set(dtag_bal, 8, dtag + 28, 4) != 8) + if (state_set(SVAR(add_dtag_bal_xfl), SVAR(dtag)) != 8) NOPE("Monolithic: Insane balance on dtag."); int64_t sub_stl_bal_xfl = float_sum(stl_bal_xfl, float_negate(sig_amt)); - INT64_TO_BUF(stl_bal, FLIP_ENDIAN_64(sub_stl_bal_xfl)); - if (state_set(stl_bal, 8, hook_accid + 12, 20) != 8) + if (state_set(SVAR(sub_stl_bal_xfl), hook_accid + 12, 20) != 8) NOPE("Monolithic: Insane balance on stl."); rfd_seq++; - if (state_set(&rfd_seq, 8, otxn_accid + 12, 20) != 8) + if (state_set(SVAR(rfd_seq), otxn_accid + 12, 20) != 4) NOPE("Monolithic: Failed to set state."); DONE("Monolithic: Refunded."); @@ -666,15 +664,23 @@ int64_t hook(uint32_t r) switch (op) { case 'A': // admin (role) - case 'B': // debit (role) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "ADM", 3); + DONE("Monolithic: ADM Modified."); + } + case 'S': // settler (role) { uint8_t account[20]; if (otxn_param(SBUF(account), "ACC", 3) != 20) NOPE("Monolithic: Misconfigured. Missing ACC modify parameter."); - - // state_set(); - DONE("Monolithic: Modified."); + + state_set(account, 20, "STL", 3); + DONE("Monolithic: STL Modified."); } case 'W': // withdraw (role) @@ -683,8 +689,49 @@ int64_t hook(uint32_t r) if (otxn_param(SBUF(key), "WKEY", 4) != 33) NOPE("Monolithic: Misconfigured. Missing KEY modify parameter."); - // state_set(); - DONE("Monolithic: Modified."); + state_set(key, 33, "WKEY", 4); + DONE("Monolithic: WKEY Modified."); + } + + default: + { + NOPE("Monolithic: Unknown operation."); + } + } + } + + case 'A': + { + switch (op) + { + case 'C': // asset (create) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "ADM", 3); + DONE("Monolithic: ADM Modified."); + } + + case 'U': // asset (update) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "ADM", 3); + DONE("Monolithic: ADM Modified."); + } + + case 'D': // asset (destroy) + { + uint8_t account[20]; + if (otxn_param(SBUF(account), "ACC", 3) != 20) + NOPE("Monolithic: Misconfigured. Missing ACC modify parameter."); + + state_set(account, 20, "STL", 3); + DONE("Monolithic: STL Modified."); } default: diff --git a/test/integration/funds/funds.test.ts b/test/integration/funds/modular.test.ts similarity index 51% rename from test/integration/funds/funds.test.ts rename to test/integration/funds/modular.test.ts index 1204c1b..caf11ee 100644 --- a/test/integration/funds/funds.test.ts +++ b/test/integration/funds/modular.test.ts @@ -1,4 +1,5 @@ import { + Client, Invoke, Payment, SetHookFlags, @@ -23,16 +24,14 @@ import { iHookParamName, iHookParamValue, ExecutionUtility, - hexNamespace, - iHook, - clearAllHooksV3, - flipHex, StateUtility, padHexString, + hexNamespace, + flipHex, } from '@transia/hooks-toolkit' import { currencyToHex, - hexToUInt64, + hexToUInt32, uint32ToHex, xflToHex, xrpAddressToHex, @@ -40,80 +39,112 @@ import { import { IssuedCurrencyAmount } from '@transia/xrpl/dist/npm/models/common' import { sign, verify } from '@transia/ripple-keypairs' -// Success Group -// Failure Group +export async function getNextNonce( + client: Client, + hookAccount: string, + nonceAccount: string +): Promise { + try { + const state = await StateUtility.getHookState( + client, + hookAccount, + padHexString(xrpAddressToHex(nonceAccount)), + hexNamespace('nonces') + ) + return Number(hexToUInt32(flipHex(state.HookStateData))) + } catch (error: any) { + console.log(error.message) + return 0 + } +} -describe('funds - Success Group', () => { +describe('modular - Success Group', () => { let testContext: XrplIntegrationTestContext beforeAll(async () => { testContext = await setupClient(serverUrl) - const hookWallet = testContext.hook4 + const masterHook = testContext.hook1 + const userHook = testContext.hook2 + const settlementWallet = testContext.bob + const adminWallet = testContext.frank - const settleWallet = testContext.frank - const swKey = testContext.grace.publicKey + const settleInvoker = testContext.grace + const refundInvoker = testContext.heidi + const withdrawInvoker = testContext.ivan const hookParam1 = new iHookParamEntry( new iHookParamName('ADM'), new iHookParamValue(xrpAddressToHex(adminWallet.classicAddress), true) ) const hookParam2 = new iHookParamEntry( - new iHookParamName('CUR'), - new iHookParamValue(currencyToHex(testContext.ic.currency), true) + new iHookParamName('STL'), + new iHookParamValue(xrpAddressToHex(settleInvoker.classicAddress), true) ) const hookParam3 = new iHookParamEntry( - new iHookParamName('ISS'), - new iHookParamValue(xrpAddressToHex(testContext.ic.issuer), true) + new iHookParamName('RFD'), + new iHookParamValue(xrpAddressToHex(refundInvoker.classicAddress), true) ) const hookParam4 = new iHookParamEntry( - new iHookParamName('STL'), - new iHookParamValue(xrpAddressToHex(settleWallet.classicAddress), true) + new iHookParamName('WKEY'), + new iHookParamValue(withdrawInvoker.publicKey, true) ) const hookParam5 = new iHookParamEntry( - new iHookParamName('KEY'), - new iHookParamValue(swKey, true) + new iHookParamName('CUR'), + new iHookParamValue(currencyToHex(testContext.ic.currency), true) ) - const hook1 = createHookPayload({ + const hookParam6 = new iHookParamEntry( + new iHookParamName('ISS'), + new iHookParamValue(xrpAddressToHex(testContext.ic.issuer), true) + ) + const hookParam7 = new iHookParamEntry( + new iHookParamName('ACC'), + new iHookParamValue( + xrpAddressToHex(settlementWallet.classicAddress), + true + ) + ) + const mhook1 = createHookPayload({ version: 0, - createFile: 'funds', + createFile: 'master', namespace: 'funds', flags: SetHookFlags.hsfOverride, - hookOnArray: ['Invoke', 'Payment'], + hookOnArray: ['Invoke'], hookParams: [ hookParam1.toXrpl(), hookParam2.toXrpl(), hookParam3.toXrpl(), hookParam4.toXrpl(), hookParam5.toXrpl(), + hookParam6.toXrpl(), + hookParam7.toXrpl(), ], }) await setHooksV3({ client: testContext.client, - seed: hookWallet.seed, - hooks: [{ Hook: hook1 }], - } as SetHookParams) - }) - afterAll(async () => { - const hookWallet = testContext.hook4 - await clearAllHooksV3({ - client: testContext.client, - seed: hookWallet.seed, + seed: masterHook.seed, + hooks: [{ Hook: mhook1 }], } as SetHookParams) - const clearHook = { - Flags: SetHookFlags.hsfNSDelete, - HookNamespace: hexNamespace('funds'), - } as iHook + + const uhook1 = createHookPayload({ + version: 0, + createFile: 'user', + namespace: 'funds', + flags: SetHookFlags.hsfOverride, + hookOnArray: ['Invoke', 'Payment'], + }) await setHooksV3({ client: testContext.client, - seed: hookWallet.seed, - hooks: [{ Hook: clearHook }], + seed: userHook.seed, + hooks: [{ Hook: uhook1 }], } as SetHookParams) + }) + afterAll(async () => { teardownClient(testContext) }) - it('operation - initialize', async () => { - const hookWallet = testContext.hook4 + it('operation (master) - initialize', async () => { + const hookWallet = testContext.hook1 const aliceWallet = testContext.alice const otxnParam1 = new iHookParamEntry( new iHookParamName('OP'), @@ -139,12 +170,43 @@ describe('funds - Success Group', () => { ) await close(testContext.client) expect(hookExecutions.executions[0].HookReturnString).toEqual( - 'Funds: Emitted TrustSet to initialize.' + 'Master: Emitted TrustSet to initialize.' + ) + }) + + it('operation (user) - initialize', async () => { + const hookWallet = testContext.hook2 + const aliceWallet = testContext.alice + const otxnParam1 = new iHookParamEntry( + new iHookParamName('OP'), + new iHookParamValue('I') + ) + const otxnParam2 = new iHookParamEntry( + new iHookParamName('AMT'), + new iHookParamValue(xflToHex(100000), true) + ) + const builtTx: Invoke = { + TransactionType: 'Invoke', + Account: aliceWallet.classicAddress, + Destination: hookWallet.classicAddress, + HookParameters: [otxnParam1.toXrpl(), otxnParam2.toXrpl()], + } + const result = await Xrpld.submit(testContext.client, { + wallet: aliceWallet, + tx: builtTx, + }) + const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + await close(testContext.client) + expect(hookExecutions.executions[0].HookReturnString).toEqual( + 'User: Emitted TrustSet to initialize.' ) }) it('operation - pause/unpause', async () => { - const hookWallet = testContext.hook4 + const hookWallet = testContext.hook1 const adminWallet = testContext.frank const otxn1Param1 = new iHookParamEntry( new iHookParamName('OP'), @@ -165,7 +227,7 @@ describe('funds - Success Group', () => { result1.meta as TransactionMetadata ) expect(hookExecutions1.executions[0].HookReturnString).toEqual( - 'Funds: Paused/Unpaused.' + 'Master: Paused/Unpaused.' ) const otxn2Param1 = new iHookParamEntry( new iHookParamName('OP'), @@ -186,11 +248,11 @@ describe('funds - Success Group', () => { result2.meta as TransactionMetadata ) expect(hookExecutions2.executions[0].HookReturnString).toEqual( - 'Funds: Paused/Unpaused.' + 'Master: Paused/Unpaused.' ) }) it('operation - user deposit', async () => { - const hookWallet = testContext.hook4 + const hookWallet = testContext.hook2 const aliceWallet = testContext.alice const amount: IssuedCurrencyAmount = { @@ -218,47 +280,35 @@ describe('funds - Success Group', () => { result.meta as TransactionMetadata ) expect(hookExecutions.executions[0].HookReturnString).toEqual( - 'Funds: Deposited.' + 'User: Deposited.' ) }) - it('operation - creditcard auth', async () => { - // No Action - Funds are already locked - }) - - it('operation - creditcard cleared', async () => { - const hookWallet = testContext.hook4 - const adminWallet = testContext.frank - const settleWallet = testContext.frank - const privateKey = testContext.grace.privateKey + it('operation - debit', async () => { + const hookWallet = testContext.hook2 + const settlerInvoker = testContext.grace const amount = 10 - const expiration = unixTimeToRippleTime(Date.now() + 3600) - const sequence = 1234 - const hex = - xrpAddressToHex(settleWallet.classicAddress) + - xflToHex(amount) + - uint32ToHex(expiration) + - uint32ToHex(sequence) - - const signature = sign(hex, privateKey) - expect(verify(hex, signature, testContext.grace.publicKey)).toBe(true) - const otxnParam1 = new iHookParamEntry( new iHookParamName('OP'), - new iHookParamValue('S') + new iHookParamValue('B') ) const otxnParam2 = new iHookParamEntry( - new iHookParamName('SIG'), - new iHookParamValue(hex + signature, true) - ) - const otxnParam3 = new iHookParamEntry( new iHookParamName('AMT'), new iHookParamValue(xflToHex(amount), true) ) + const nonce = await getNextNonce( + testContext.client, + hookWallet.classicAddress, + settlerInvoker.classicAddress + ) + const otxnParam3 = new iHookParamEntry( + new iHookParamName('SEQ'), + new iHookParamValue(flipHex(uint32ToHex(nonce)), true) + ) const builtTx: Invoke = { TransactionType: 'Invoke', - Account: adminWallet.classicAddress, + Account: settlerInvoker.classicAddress, Destination: hookWallet.classicAddress, HookParameters: [ otxnParam1.toXrpl(), @@ -267,7 +317,7 @@ describe('funds - Success Group', () => { ], } const result = await Xrpld.submit(testContext.client, { - wallet: adminWallet, + wallet: settlerInvoker, tx: builtTx, }) const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( @@ -276,35 +326,40 @@ describe('funds - Success Group', () => { ) await close(testContext.client) expect(hookExecutions.executions[0].HookReturnString).toEqual( - 'Funds: Emitted settlement.' + 'User: Emitted debit.' ) }) - it('operation - user withdrawal', async () => { - const hookWallet = testContext.hook4 + it('operation - user withdrawal (approval)', async () => { + const hookWallet = testContext.hook2 const userWallet = testContext.alice - const settleWallet = testContext.frank - const privateKey = testContext.grace.privateKey + const withdrawInvoker = testContext.ivan - const amount = 10 + const amount = 5 const expiration = unixTimeToRippleTime(Date.now() + 3600000) - console.log(expiration) - - const sequence = 0 + const nonce = await getNextNonce( + testContext.client, + hookWallet.classicAddress, + userWallet.classicAddress + ) const hex = - xrpAddressToHex(settleWallet.classicAddress) + + xrpAddressToHex(userWallet.classicAddress) + xflToHex(amount) + flipHex(uint32ToHex(expiration)) + - flipHex(uint32ToHex(sequence)) + flipHex(uint32ToHex(nonce)) - const signature = sign(hex, privateKey) - expect(verify(hex, signature, testContext.grace.publicKey)).toBe(true) + const signature = sign(hex, withdrawInvoker.privateKey) + expect(verify(hex, signature, withdrawInvoker.publicKey)).toBe(true) const otxnParam1 = new iHookParamEntry( new iHookParamName('OP'), new iHookParamValue('W') ) const otxnParam2 = new iHookParamEntry( + new iHookParamName('SOP'), + new iHookParamValue('A') + ) + const otxnParam3 = new iHookParamEntry( new iHookParamName('SIG'), new iHookParamValue(hex + signature, true) ) @@ -312,7 +367,11 @@ describe('funds - Success Group', () => { TransactionType: 'Invoke', Account: userWallet.classicAddress, Destination: hookWallet.classicAddress, - HookParameters: [otxnParam1.toXrpl(), otxnParam2.toXrpl()], + HookParameters: [ + otxnParam1.toXrpl(), + otxnParam2.toXrpl(), + otxnParam3.toXrpl(), + ], } const result = await Xrpld.submit(testContext.client, { wallet: userWallet, @@ -324,219 +383,96 @@ describe('funds - Success Group', () => { ) await close(testContext.client) expect(hookExecutions.executions[0].HookReturnString).toEqual( - 'Funds: Emitted withdrawal.' + 'User: Emitted approval withdrawal.' ) }) - it('operation - user withdrawal - fail (signature)', async () => { - const hookWallet = testContext.hook4 + it('operation - user withdrawal (intent)', async () => { + const hookWallet = testContext.hook2 const userWallet = testContext.alice - const settleWallet = testContext.frank - const privateKey = testContext.alice.privateKey - - const amount = 10 - const expiration = unixTimeToRippleTime(Date.now() + 3600000) - const state = await StateUtility.getHookState( - testContext.client, - hookWallet.classicAddress, - padHexString(xrpAddressToHex(settleWallet.classicAddress)), - hexNamespace('funds') - ) - const sequence = hexToUInt64(flipHex(state.HookStateData)) - const hex = - xrpAddressToHex(settleWallet.classicAddress) + - xflToHex(amount) + - flipHex(uint32ToHex(expiration)) + - flipHex(uint32ToHex(Number(sequence.toString()))) - - const signature = sign(hex, privateKey) - expect(verify(hex, signature, testContext.alice.publicKey)).toBe(true) + const amount = 5 const otxnParam1 = new iHookParamEntry( new iHookParamName('OP'), new iHookParamValue('W') ) const otxnParam2 = new iHookParamEntry( - new iHookParamName('SIG'), - new iHookParamValue(hex + signature, true) + new iHookParamName('SOP'), + new iHookParamValue('I') ) - const builtTx: Invoke = { - TransactionType: 'Invoke', - Account: userWallet.classicAddress, - Destination: hookWallet.classicAddress, - HookParameters: [otxnParam1.toXrpl(), otxnParam2.toXrpl()], - } - try { - await Xrpld.submit(testContext.client, { - wallet: userWallet, - tx: builtTx, - }) - } catch (error: any) { - expect(error.message).toEqual('Funds: Signature verification failed.') - } - }) - it('operation - user withdrawal - fail (admin)', async () => { - const hookWallet = testContext.hook4 - const userWallet = testContext.alice - const settleWallet = testContext.frank - const privateKey = testContext.grace.privateKey - - const amount = 10 - const expiration = unixTimeToRippleTime(Date.now() + 3600000) - const state = await StateUtility.getHookState( + const otxnParam3 = new iHookParamEntry( + new iHookParamName('AMT'), + new iHookParamValue(xflToHex(amount), true) + ) + const nonce = await getNextNonce( testContext.client, hookWallet.classicAddress, - padHexString(xrpAddressToHex(settleWallet.classicAddress)), - hexNamespace('funds') - ) - const sequence = hexToUInt64(flipHex(state.HookStateData)) - const hex = - xrpAddressToHex(userWallet.classicAddress) + - xflToHex(amount) + - flipHex(uint32ToHex(expiration)) + - flipHex(uint32ToHex(Number(sequence.toString()))) - - const signature = sign(hex, privateKey) - expect(verify(hex, signature, testContext.grace.publicKey)).toBe(true) - - const otxnParam1 = new iHookParamEntry( - new iHookParamName('OP'), - new iHookParamValue('W') + userWallet.classicAddress ) - const otxnParam2 = new iHookParamEntry( - new iHookParamName('SIG'), - new iHookParamValue(hex + signature, true) + const otxnParam4 = new iHookParamEntry( + new iHookParamName('SEQ'), + new iHookParamValue(flipHex(uint32ToHex(nonce)), true) ) const builtTx: Invoke = { TransactionType: 'Invoke', Account: userWallet.classicAddress, Destination: hookWallet.classicAddress, - HookParameters: [otxnParam1.toXrpl(), otxnParam2.toXrpl()], - } - try { - await Xrpld.submit(testContext.client, { - wallet: userWallet, - tx: builtTx, - }) - } catch (error: any) { - expect(error.message).toEqual('Funds: Wrong account for ticket.') + HookParameters: [ + otxnParam1.toXrpl(), + otxnParam2.toXrpl(), + otxnParam3.toXrpl(), + otxnParam4.toXrpl(), + ], } - }) - it('operation - user withdrawal - fail (expiration)', async () => { - const hookWallet = testContext.hook4 - const userWallet = testContext.alice - const settleWallet = testContext.frank - const privateKey = testContext.grace.privateKey - - const amount = 10 - const expiration = unixTimeToRippleTime(Date.now()) - const state = await StateUtility.getHookState( + const result = await Xrpld.submit(testContext.client, { + wallet: userWallet, + tx: builtTx, + }) + const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( testContext.client, - hookWallet.classicAddress, - padHexString(xrpAddressToHex(settleWallet.classicAddress)), - hexNamespace('funds') + result.meta as TransactionMetadata ) - const sequence = hexToUInt64(flipHex(state.HookStateData)) - const hex = - xrpAddressToHex(settleWallet.classicAddress) + - xflToHex(amount) + - flipHex(uint32ToHex(expiration)) + - flipHex(uint32ToHex(Number(sequence.toString()))) - - const signature = sign(hex, privateKey) - expect(verify(hex, signature, testContext.grace.publicKey)).toBe(true) + await close(testContext.client) + expect(hookExecutions.executions[0].HookReturnString).toEqual( + 'User: Created permissionless withdraw intent.' + ) + }) + it('operation - user withdrawal (execute)', async () => { + const hookWallet = testContext.hook2 + const userWallet = testContext.alice const otxnParam1 = new iHookParamEntry( new iHookParamName('OP'), new iHookParamValue('W') ) const otxnParam2 = new iHookParamEntry( - new iHookParamName('SIG'), - new iHookParamValue(hex + signature, true) + new iHookParamName('SOP'), + new iHookParamValue('E') ) - const builtTx: Invoke = { - TransactionType: 'Invoke', - Account: userWallet.classicAddress, - Destination: hookWallet.classicAddress, - HookParameters: [otxnParam1.toXrpl(), otxnParam2.toXrpl()], - } - try { - await Xrpld.submit(testContext.client, { - wallet: userWallet, - tx: builtTx, - }) - } catch (error: any) { - expect(error.message).toEqual('Funds: Ticket has expired.') - } - }) - it('operation - user withdrawal - fail (nonce)', async () => { - const hookWallet = testContext.hook4 - const userWallet = testContext.alice - const settleWallet = testContext.frank - const privateKey = testContext.grace.privateKey - - const amount = 10 - const expiration = unixTimeToRippleTime(Date.now() + 3600000) - const state = await StateUtility.getHookState( + const nonce = await getNextNonce( testContext.client, hookWallet.classicAddress, - padHexString(xrpAddressToHex(settleWallet.classicAddress)), - hexNamespace('funds') + userWallet.classicAddress ) - const sequence = hexToUInt64(flipHex(state.HookStateData)) + BigInt(1) - const hex = - xrpAddressToHex(settleWallet.classicAddress) + - xflToHex(amount) + - flipHex(uint32ToHex(expiration)) + - flipHex(uint32ToHex(Number(sequence.toString()))) - - const signature = sign(hex, privateKey) - expect(verify(hex, signature, testContext.grace.publicKey)).toBe(true) - - const otxnParam1 = new iHookParamEntry( - new iHookParamName('OP'), - new iHookParamValue('W') - ) - const otxnParam2 = new iHookParamEntry( - new iHookParamName('SIG'), - new iHookParamValue(hex + signature, true) + const otxnParam3 = new iHookParamEntry( + new iHookParamName('WI'), + new iHookParamValue( + xrpAddressToHex(userWallet.classicAddress) + + uint32ToHex(nonce === 0 ? 0 : nonce - 1), + true + ) ) const builtTx: Invoke = { TransactionType: 'Invoke', Account: userWallet.classicAddress, Destination: hookWallet.classicAddress, - HookParameters: [otxnParam1.toXrpl(), otxnParam2.toXrpl()], - } - try { - await Xrpld.submit(testContext.client, { - wallet: userWallet, - tx: builtTx, - }) - } catch (error: any) { - expect(error.message).toEqual('Funds: Nonce out of sequence.') - } - }) - it('operation - creditcard refund', async () => { - const hookWallet = testContext.hook4 - const adminWallet = testContext.frank - - const amount: IssuedCurrencyAmount = { - issuer: testContext.ic.issuer, - currency: testContext.ic.currency, - value: '100', - } - const otxnParam1 = new iHookParamEntry( - new iHookParamName('OP'), - new iHookParamValue('R') - ) - const builtTx: Payment = { - TransactionType: 'Payment', - Account: adminWallet.classicAddress, - Destination: hookWallet.classicAddress, - Amount: amount, - HookParameters: [otxnParam1.toXrpl()], + HookParameters: [ + otxnParam1.toXrpl(), + otxnParam2.toXrpl(), + otxnParam3.toXrpl(), + ], } const result = await Xrpld.submit(testContext.client, { - wallet: adminWallet, + wallet: userWallet, tx: builtTx, }) const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( @@ -545,7 +481,7 @@ describe('funds - Success Group', () => { ) await close(testContext.client) expect(hookExecutions.executions[0].HookReturnString).toEqual( - 'Funds: Refunded.' + 'User: Emitted permissionless withdrawal.' ) }) }) diff --git a/test/integration/funds/monolithic.test.ts b/test/integration/funds/monolithic.test.ts index 29e6064..6caba6d 100644 --- a/test/integration/funds/monolithic.test.ts +++ b/test/integration/funds/monolithic.test.ts @@ -1,9 +1,9 @@ import { + Client, Invoke, Payment, SetHookFlags, TransactionMetadata, - AccountSetAsfFlags, unixTimeToRippleTime, } from '@transia/xrpl' // xrpl-helpers @@ -12,7 +12,6 @@ import { XrplIntegrationTestContext, setupClient, teardownClient, - accountSet, close, } from '@transia/hooks-toolkit/dist/npm/src/libs/xrpl-helpers' // src @@ -25,6 +24,9 @@ import { iHookParamName, iHookParamValue, ExecutionUtility, + StateUtility, + padHexString, + hexNamespace, flipHex, } from '@transia/hooks-toolkit' import { @@ -32,10 +34,30 @@ import { xflToHex, xrpAddressToHex, uint32ToHex, + hexToUInt32, } from '@transia/hooks-toolkit/dist/npm/src/libs/binary-models' import { IssuedCurrencyAmount } from '@transia/xrpl/dist/npm/models/common' import { sign, verify } from '@transia/ripple-keypairs' +export async function getNextNonce( + client: Client, + hookAccount: string, + nonceAccount: string +): Promise { + try { + const state = await StateUtility.getHookState( + client, + hookAccount, + padHexString(xrpAddressToHex(nonceAccount)), + hexNamespace('nonces') + ) + return Number(hexToUInt32(flipHex(state.HookStateData))) + } catch (error: any) { + console.log(error.message) + return 0 + } +} + describe('funds - Success Group', () => { let testContext: XrplIntegrationTestContext @@ -50,13 +72,6 @@ describe('funds - Success Group', () => { const refundInvoker = testContext.heidi const withdrawInvoker = testContext.ivan - await accountSet( - testContext.client, - hookWallet, - AccountSetAsfFlags.asfRequireDest - ) - await close(testContext.client) - const hookParam1 = new iHookParamEntry( new iHookParamName('ADM'), new iHookParamValue(xrpAddressToHex(adminWallet.classicAddress), true) @@ -204,13 +219,16 @@ describe('funds - Success Group', () => { new iHookParamName('OP'), new iHookParamValue('D') ) + const otxnParam2 = new iHookParamEntry( + new iHookParamName('FS'), + new iHookParamValue(xrpAddressToHex(userWallet.classicAddress), true) + ) const builtTx: Payment = { TransactionType: 'Payment', Account: userWallet.classicAddress, Destination: hookWallet.classicAddress, - DestinationTag: 1, Amount: amount, - HookParameters: [otxnParam1.toXrpl()], + HookParameters: [otxnParam1.toXrpl(), otxnParam2.toXrpl()], } const result = await Xrpld.submit(testContext.client, { wallet: userWallet, @@ -224,9 +242,9 @@ describe('funds - Success Group', () => { 'Monolithic: Deposited.' ) }) - it('operation - debit', async () => { const hookWallet = testContext.hook1 + const userWallet = testContext.alice const settlerInvoker = testContext.grace const otxnParam1 = new iHookParamEntry( @@ -234,16 +252,21 @@ describe('funds - Success Group', () => { new iHookParamValue('B') ) const otxnParam2 = new iHookParamEntry( - new iHookParamName('TAG'), - new iHookParamValue(uint32ToHex(1), true) + new iHookParamName('FS'), + new iHookParamValue(xrpAddressToHex(userWallet.classicAddress), true) ) const otxnParam3 = new iHookParamEntry( new iHookParamName('AMT'), new iHookParamValue(xflToHex(10), true) ) + const nonce = await getNextNonce( + testContext.client, + hookWallet.classicAddress, + settlerInvoker.classicAddress + ) const otxnParam4 = new iHookParamEntry( new iHookParamName('SEQ'), - new iHookParamValue(uint32ToHex(0), true) + new iHookParamValue(flipHex(uint32ToHex(nonce)), true) ) const builtTx: Invoke = { TransactionType: 'Invoke', @@ -272,6 +295,7 @@ describe('funds - Success Group', () => { it('operation - settlement', async () => { const hookWallet = testContext.hook1 + const userWallet = testContext.alice const settlerInvoker = testContext.grace const otxnParam1 = new iHookParamEntry( @@ -279,16 +303,21 @@ describe('funds - Success Group', () => { new iHookParamValue('S') ) const otxnParam2 = new iHookParamEntry( - new iHookParamName('TAG'), - new iHookParamValue(uint32ToHex(1), true) + new iHookParamName('FS'), + new iHookParamValue(xrpAddressToHex(userWallet.classicAddress), true) ) const otxnParam3 = new iHookParamEntry( new iHookParamName('AMT'), new iHookParamValue(xflToHex(10), true) ) + const nonce = await getNextNonce( + testContext.client, + hookWallet.classicAddress, + settlerInvoker.classicAddress + ) const otxnParam4 = new iHookParamEntry( new iHookParamName('SEQ'), - new iHookParamValue(uint32ToHex(0), true) + new iHookParamValue(flipHex(uint32ToHex(nonce)), true) ) const builtTx: Invoke = { TransactionType: 'Invoke', @@ -311,25 +340,27 @@ describe('funds - Success Group', () => { ) await close(testContext.client) expect(hookExecutions.executions[0].HookReturnString).toEqual( - 'Funds: Emitted settlement.' + 'Monolithic: Emitted settlement.' ) }) - it('operation - user withdrawal', async () => { + it('operation - user withdrawal (approval)', async () => { const hookWallet = testContext.hook1 const userWallet = testContext.alice const withdrawInvoker = testContext.ivan const amount = 5 - const dtag = 1 const expiration = unixTimeToRippleTime(Date.now() + 3600000) - const sequence = 0 + const nonce = await getNextNonce( + testContext.client, + hookWallet.classicAddress, + userWallet.classicAddress + ) const hex = xrpAddressToHex(userWallet.classicAddress) + xflToHex(amount) + - flipHex(uint32ToHex(dtag)) + flipHex(uint32ToHex(expiration)) + - flipHex(uint32ToHex(sequence)) + flipHex(uint32ToHex(nonce)) const signature = sign(hex, withdrawInvoker.privateKey) expect(verify(hex, signature, withdrawInvoker.publicKey)).toBe(true) @@ -339,6 +370,10 @@ describe('funds - Success Group', () => { new iHookParamValue('W') ) const otxnParam2 = new iHookParamEntry( + new iHookParamName('SOP'), + new iHookParamValue('A') + ) + const otxnParam3 = new iHookParamEntry( new iHookParamName('SIG'), new iHookParamValue(hex + signature, true) ) @@ -346,7 +381,113 @@ describe('funds - Success Group', () => { TransactionType: 'Invoke', Account: userWallet.classicAddress, Destination: hookWallet.classicAddress, - HookParameters: [otxnParam1.toXrpl(), otxnParam2.toXrpl()], + HookParameters: [ + otxnParam1.toXrpl(), + otxnParam2.toXrpl(), + otxnParam3.toXrpl(), + ], + } + const result = await Xrpld.submit(testContext.client, { + wallet: userWallet, + tx: builtTx, + }) + const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + await close(testContext.client) + expect(hookExecutions.executions[0].HookReturnString).toEqual( + 'Monolithic: Emitted approval withdrawal.' + ) + }) + it('operation - user withdrawal (intent)', async () => { + const hookWallet = testContext.hook1 + const userWallet = testContext.alice + + const amount = 5 + const otxnParam1 = new iHookParamEntry( + new iHookParamName('OP'), + new iHookParamValue('W') + ) + const otxnParam2 = new iHookParamEntry( + new iHookParamName('SOP'), + new iHookParamValue('I') + ) + const otxnParam3 = new iHookParamEntry( + new iHookParamName('FS'), + new iHookParamValue(xrpAddressToHex(userWallet.classicAddress), true) + ) + const otxnParam4 = new iHookParamEntry( + new iHookParamName('AMT'), + new iHookParamValue(xflToHex(amount), true) + ) + const nonce = await getNextNonce( + testContext.client, + hookWallet.classicAddress, + userWallet.classicAddress + ) + const otxnParam5 = new iHookParamEntry( + new iHookParamName('SEQ'), + new iHookParamValue(flipHex(uint32ToHex(nonce)), true) + ) + const builtTx: Invoke = { + TransactionType: 'Invoke', + Account: userWallet.classicAddress, + Destination: hookWallet.classicAddress, + HookParameters: [ + otxnParam1.toXrpl(), + otxnParam2.toXrpl(), + otxnParam3.toXrpl(), + otxnParam4.toXrpl(), + otxnParam5.toXrpl(), + ], + } + const result = await Xrpld.submit(testContext.client, { + wallet: userWallet, + tx: builtTx, + }) + const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + await close(testContext.client) + expect(hookExecutions.executions[0].HookReturnString).toEqual( + 'Monolithic: Created permissionless withdraw intent.' + ) + }) + it('operation - user withdrawal (execute)', async () => { + const hookWallet = testContext.hook1 + const userWallet = testContext.alice + + const otxnParam1 = new iHookParamEntry( + new iHookParamName('OP'), + new iHookParamValue('W') + ) + const otxnParam2 = new iHookParamEntry( + new iHookParamName('SOP'), + new iHookParamValue('E') + ) + const nonce = await getNextNonce( + testContext.client, + hookWallet.classicAddress, + userWallet.classicAddress + ) + const otxnParam3 = new iHookParamEntry( + new iHookParamName('WI'), + new iHookParamValue( + xrpAddressToHex(userWallet.classicAddress) + uint32ToHex(nonce - 1), + true + ) + ) + const builtTx: Invoke = { + TransactionType: 'Invoke', + Account: userWallet.classicAddress, + Destination: hookWallet.classicAddress, + HookParameters: [ + otxnParam1.toXrpl(), + otxnParam2.toXrpl(), + otxnParam3.toXrpl(), + ], } const result = await Xrpld.submit(testContext.client, { wallet: userWallet, @@ -358,7 +499,58 @@ describe('funds - Success Group', () => { ) await close(testContext.client) expect(hookExecutions.executions[0].HookReturnString).toEqual( - 'Monolithic: Emitted withdrawal.' + 'Monolithic: Emitted permissionless withdrawal.' + ) + }) + + it('operation - refund', async () => { + const hookWallet = testContext.hook1 + const userWallet = testContext.alice + const settlerInvoker = testContext.grace + + const otxnParam1 = new iHookParamEntry( + new iHookParamName('OP'), + new iHookParamValue('R') + ) + const otxnParam2 = new iHookParamEntry( + new iHookParamName('FS'), + new iHookParamValue(xrpAddressToHex(userWallet.classicAddress), true) + ) + const otxnParam3 = new iHookParamEntry( + new iHookParamName('AMT'), + new iHookParamValue(xflToHex(10), true) + ) + const nonce = await getNextNonce( + testContext.client, + hookWallet.classicAddress, + settlerInvoker.classicAddress + ) + const otxnParam4 = new iHookParamEntry( + new iHookParamName('SEQ'), + new iHookParamValue(flipHex(uint32ToHex(nonce)), true) + ) + const builtTx: Invoke = { + TransactionType: 'Invoke', + Account: settlerInvoker.classicAddress, + Destination: hookWallet.classicAddress, + HookParameters: [ + otxnParam1.toXrpl(), + otxnParam2.toXrpl(), + otxnParam3.toXrpl(), + otxnParam4.toXrpl(), + ], + } + const result = await Xrpld.submit(testContext.client, { + wallet: settlerInvoker, + tx: builtTx, + }) + const hookExecutions = await ExecutionUtility.getHookExecutionsFromMeta( + testContext.client, + result.meta as TransactionMetadata + ) + await close(testContext.client) + expect(hookExecutions.executions[0].HookReturnString).toEqual( + 'Monolithic: Refunded.' ) }) })