diff --git a/hook/examples/accept/accept.c b/hook/examples/accept/accept.c new file mode 100644 index 000000000..1c48fbda0 --- /dev/null +++ b/hook/examples/accept/accept.c @@ -0,0 +1,18 @@ +/** + * This hook just accepts any transaction coming through it + */ +#include "../hookapi.h" + +int64_t cbak(uint32_t reserved) { + return 0; +} + +int64_t hook(uint32_t reserved ) { + + TRACESTR("Accept.c: Called."); + accept (0,0,0); + + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/examples/accept/accept.js b/hook/examples/accept/accept.js new file mode 100644 index 000000000..cd73000ef --- /dev/null +++ b/hook/examples/accept/accept.js @@ -0,0 +1,34 @@ +const wasm = 'accept.wasm' +if (process.argv.length < 3) +{ + console.log("Usage: node accept ") + process.exit(1); +} + +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const secret = process.argv[2]; + const account = t.xrpljs.Wallet.fromSeed(secret) + t.feeSubmit(process.argv[2], + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm(wasm), + HookApiVersion: 0, + HookNamespace: "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE", + HookOn: "0000000000000000", + Flags: t.hsfOVERRIDE + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + process.exit(0); + + }).catch(e=>console.log(e)); +}).catch(e=>console.log(e)); diff --git a/hook/examples/accept/makefile b/hook/examples/accept/makefile new file mode 100644 index 000000000..829a0b5ea --- /dev/null +++ b/hook/examples/accept/makefile @@ -0,0 +1,4 @@ +all: + wasmcc accept.c -o accept.wasm -O0 -Wl,--allow-undefined -I../ + hook-cleaner accept.wasm + diff --git a/hook/examples/accept/pay.js b/hook/examples/accept/pay.js new file mode 100644 index 000000000..3c7f7c63b --- /dev/null +++ b/hook/examples/accept/pay.js @@ -0,0 +1,19 @@ +if (process.argv.length < 5) +{ + console.log("Usage: node pay ") + process.exit(1) +} +const secret = process.argv[2]; +const amount = BigInt(process.argv[3]) * 1000000n +const dest = process.argv[4]; +const server = 'ws://localhost:6005' +require('../../utils-tests.js').TestRig(server).then(t=> +{ + t.pay(secret, amount, dest).then(x=> + { + console.log(x); + process.exit(0); + }); +}); + + diff --git a/hook/examples/carbon/carbon.c b/hook/examples/carbon/carbon.c new file mode 100644 index 000000000..2fdb0b467 --- /dev/null +++ b/hook/examples/carbon/carbon.c @@ -0,0 +1,95 @@ +#include +#include "hookapi.h" + +int64_t cbak(uint32_t reserved) +{ + TRACESTR("Carbon: callback called."); + return 0; +} + +int64_t hook(uint32_t reserved) +{ + + TRACESTR("Carbon: started"); + + // before we start calling hook-api functions we should tell the hook how many tx we intend to create + etxn_reserve(1); // we are going to emit 1 transaction + + // hooks communicate accounts via the 20 byte account ID, this can be generated from an raddr like so + // a more efficient way to do this is precompute the account-id from the raddr (if the raddr never changes) + uint8_t carbon_accid[20]; + int64_t ret = util_accid( + SBUF(carbon_accid), /* <-- generate into this buffer */ + SBUF("rfCarbonVNTuXckX6x2qTMFmFSnm6dEWGX") ); /* <-- from this r-addr */ + TRACEVAR(ret); + + // this api fetches the AccountID of the account the hook currently executing is installed on + // since hooks can be triggered by both incoming and ougoing transactions this is important to know + unsigned char hook_accid[20]; + hook_account((uint32_t)hook_accid, 20); + + // NB: + // almost all of the hook apis require a buffer pointer and buffer length to be supplied ... to make this a + // little easier to code a macro: `SBUF(your_buffer)` expands to `your_buffer, sizeof(your_buffer)` + + // next fetch the sfAccount field from the originating transaction + uint8_t account_field[20]; + int32_t account_field_len = otxn_field(SBUF(account_field), sfAccount); + TRACEVAR(account_field_len); + if (account_field_len < 20) // negative values indicate errors from every api + rollback(SBUF("Carbon: sfAccount field missing!!!"), 1); // this code could never be hit in prod + // but it's here for completeness + + // compare the "From Account" (sfAccount) on the transaction with the account the hook is running on + int equal = 0; BUFFER_EQUAL(equal, hook_accid, account_field, 20); + if (!equal) + { + // if the accounts are not equal (memcmp != 0) the otxn was sent to the hook account by someone else + // accept() it and end the hook execution here + accept(SBUF("Carbon: Incoming transaction"), 2); + } + + // execution to here means the user has sent a valid transaction FROM the account the hook is installed on + + // fetch the sent Amount + // Amounts can be 384 bits or 64 bits. If the Amount is an XRP value it will be 64 bits. + unsigned char amount_buffer[48]; + int64_t amount_len = otxn_field(SBUF(amount_buffer), sfAmount); + int64_t drops_to_send = 1000; // this will be the default + + + if (amount_len != 8) + { + // you can trace the behaviour of your hook using the trace(buf, size, as_hex) api + // which will output to xrpld's trace log + TRACESTR("Carbon: Non-xrp transaction detected, sending default 1000 drops to rfCarbon"); + } else + { + TRACESTR("Carbon: XRP transaction detected, computing 1% to send to rfCarbon"); + int64_t otxn_drops = AMOUNT_TO_DROPS(amount_buffer); + TRACEVAR(otxn_drops); + if (otxn_drops > 100000) // if its less we send the default amount. or if there was an error we send default + drops_to_send = (int64_t)((double)otxn_drops * 0.01f); // otherwise we send 1% + } + + TRACEVAR(drops_to_send); + + + // create a buffer to write the emitted transaction into + unsigned char tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + + // we will use an XRP payment macro, this will populate the buffer with a serialized binary transaction + // Parameter list: ( buf_out, drops_amount, to_address, dest_tag, src_tag ) + PREPARE_PAYMENT_SIMPLE(tx, drops_to_send++, carbon_accid, 0, 0); + + + // emit the transaction + uint8_t emithash[32]; + int64_t emit_result = emit(SBUF(emithash), SBUF(tx)); + TRACEVAR(emit_result); + + // accept and allow the original transaction through + accept(SBUF("Carbon: Emitted transaction"), 0); + return 0; + +} diff --git a/hook/examples/carbon/carbon.js b/hook/examples/carbon/carbon.js new file mode 100644 index 000000000..0790858ca --- /dev/null +++ b/hook/examples/carbon/carbon.js @@ -0,0 +1,38 @@ +const wasm = 'carbon.wasm' +if (process.argv.length < 3) +{ + console.log("Usage: node carbon ") + process.exit(1); +} + + +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + t.fundFromGenesis(["rfCarbonVNTuXckX6x2qTMFmFSnm6dEWGX"]).then(()=> + { + const secret = process.argv[2]; + const account = t.xrpljs.Wallet.fromSeed(secret) + t.feeSubmit(process.argv[2], + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm(wasm), + HookApiVersion: 0, + HookNamespace: "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE", + HookOn: "0000000000000000", + Flags: t.hsfOVERRIDE + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + process.exit(0); + + }).catch(t.err); + }).catch(t.err); +}).catch(e=>console.log(e)); diff --git a/hook/examples/carbon/makefile b/hook/examples/carbon/makefile new file mode 100644 index 000000000..f3f93053b --- /dev/null +++ b/hook/examples/carbon/makefile @@ -0,0 +1,5 @@ +all: + wasmcc carbon.c -o carbon.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner carbon.wasm + + diff --git a/hook/examples/carbon/pay.js b/hook/examples/carbon/pay.js new file mode 100644 index 000000000..8165fcf28 --- /dev/null +++ b/hook/examples/carbon/pay.js @@ -0,0 +1,19 @@ +if (process.argv.length < 5) +{ + console.log("Usage: node pay ") + process.exit(1) +} +const secret = process.argv[2]; +const amount = BigInt(process.argv[3]) * 1000000n +const dest = process.argv[4]; + +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + t.pay(secret, amount, dest).then(x=> + { + console.log(x); + process.exit(0); + }); +}); + + diff --git a/hook/examples/doubler/doubler.c b/hook/examples/doubler/doubler.c new file mode 100644 index 000000000..b1b48af30 --- /dev/null +++ b/hook/examples/doubler/doubler.c @@ -0,0 +1,72 @@ +//Authors: NeilH, RichardAH +// (joke) test hook that doubles incoming XRP payments and sends it back +// April 1st 2021: Added (unfair) coin flip + +#include +#include "../hookapi.h" + +int64_t hook(uint32_t reserved) +{ + + uint8_t hook_accid[20]; + if (hook_account(SBUF(hook_accid)) < 0) + rollback(SBUF("Doubler: Could not fetch hook account id."), 1); + + // next fetch the sfAccount field from the originating transaction + uint8_t account_field[20]; + int32_t account_field_len = otxn_field(SBUF(account_field), sfAccount); + + // compare the "From Account" (sfAccount) on the transaction with the account the hook is running on + int equal = 0; BUFFER_EQUAL(equal, hook_accid, account_field, 20); + if (equal) + { + accept(SBUF("Doubler: Outgoing transaction. Passing."), 2); + return 0; + } + + uint8_t digest[96]; + if (ledger_last_hash(digest, 32) != 32) + rollback(SBUF("Doubler: Failed to fetch last closed ledger."), 3); + + uint8_t key[32]; // left as 0...0 + state(digest + 32, 32, SBUF(key)); // if this load fails then we don't care, the hash is just 0 + etxn_nonce(digest + 64, 32); // todo: if we enforce sfFirstLedgerSequence = +1 then this will be impossible to cheat + + uint8_t hash[32]; + if (util_sha512h(SBUF(hash), SBUF(digest)) != 32) + rollback(SBUF("Doubler: Could not compute digest for coin flip."), 4); + + if (state_set(SBUF(hash), SBUF(key)) != 32) + rollback(SBUF("Doubler: Could not set state."), 5); + + // first digit of lcl hash is our biased coin flip, you lose 60% of the time :P + if (hash[0] % 10 < 6) + accept(SBUF("Doubler: Tails, you lose. Om nom nom xrp."), 4); + + // before we start calling hook-api functions we should tell the hook how many tx we intend to create + etxn_reserve(1); // we are going to emit 1 transaction + + // fetch the sent Amount + // Amounts can be 384 bits or 64 bits. If the Amount is an XRP value it will be 64 bits. + unsigned char amount_buffer[48]; + int64_t amount_len = otxn_field(SBUF(amount_buffer), sfAmount); + int64_t drops_to_send = AMOUNT_TO_DROPS(amount_buffer) * 2; // doubler pays back 2x received + + if (amount_len != 8) + rollback(SBUF("Doubler: Rejecting incoming non-XRP transaction"), 5); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + + // we will use an XRP payment macro, this will populate the buffer with a serialized binary transaction + // Parameter list: ( buf_out, drops_amount, drops_fee, to_address, dest_tag, src_tag ) + PREPARE_PAYMENT_SIMPLE(tx, drops_to_send, account_field, 0, 0); + + // emit the transaction + uint8_t emithash[32]; + emit(SBUF(emithash), SBUF(tx)); + + // accept and allow the original transaction through + accept(SBUF("Doubler: Heads, you won! Funds emitted!"), 0); + return 0; + +} diff --git a/hook/examples/doubler/doubler.js b/hook/examples/doubler/doubler.js new file mode 100644 index 000000000..32795b7ff --- /dev/null +++ b/hook/examples/doubler/doubler.js @@ -0,0 +1,35 @@ +const wasm = 'doubler.wasm' +if (process.argv.length < 3) +{ + console.log("Usage: node doubler ") + process.exit(1); +} + + +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const secret = process.argv[2]; + const account = t.xrpljs.Wallet.fromSeed(secret) + t.feeSubmit(process.argv[2], + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm(wasm), + HookApiVersion: 0, + HookNamespace: "CAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFECAFE", + HookOn: "0000000000000000", + Flags: t.hsfOVERRIDE + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + process.exit(0); + + }).catch(t.err); +}).catch(e=>console.log(e)); diff --git a/hook/examples/doubler/makefile b/hook/examples/doubler/makefile new file mode 100644 index 000000000..e8280314d --- /dev/null +++ b/hook/examples/doubler/makefile @@ -0,0 +1,4 @@ +all: + wasmcc doubler.c -o doubler.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner doubler.wasm + diff --git a/hook/examples/doubler/pay.js b/hook/examples/doubler/pay.js new file mode 100644 index 000000000..3cfe9aed7 --- /dev/null +++ b/hook/examples/doubler/pay.js @@ -0,0 +1,19 @@ +if (process.argv.length < 5) +{ + console.log("Usage: node pay ") + process.exit(1) +} +const secret = process.argv[2]; +const amount = BigInt(process.argv[3]) * 1000000n +const dest = process.argv[4]; + +require('../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + t.pay(secret, amount, dest).then(x=> + { + console.log(x); + process.exit(0); + }); +}); + + diff --git a/hook/examples/error.h b/hook/examples/error.h new file mode 100644 index 000000000..8ea4c3fa4 --- /dev/null +++ b/hook/examples/error.h @@ -0,0 +1,46 @@ +// For documentation please see: https://xrpl-hooks.readme.io/reference/ +// Generated using generate_error.sh +#ifndef HOOK_ERROR_CODES +#define SUCCESS 0 +#define OUT_OF_BOUNDS -1 +#define INTERNAL_ERROR -2 +#define TOO_BIG -3 +#define TOO_SMALL -4 +#define DOESNT_EXIST -5 +#define NO_FREE_SLOTS -6 +#define INVALID_ARGUMENT -7 +#define ALREADY_SET -8 +#define PREREQUISITE_NOT_MET -9 +#define FEE_TOO_LARGE -10 +#define EMISSION_FAILURE -11 +#define TOO_MANY_NONCES -12 +#define TOO_MANY_EMITTED_TXN -13 +#define NOT_IMPLEMENTED -14 +#define INVALID_ACCOUNT -15 +#define GUARD_VIOLATION -16 +#define INVALID_FIELD -17 +#define PARSE_ERROR -18 +#define RC_ROLLBACK -19 +#define RC_ACCEPT -20 +#define NO_SUCH_KEYLET -21 +#define NOT_AN_ARRAY -22 +#define NOT_AN_OBJECT -23 +#define INVALID_FLOAT -10024 +#define DIVISION_BY_ZERO -25 +#define MANTISSA_OVERSIZED -26 +#define MANTISSA_UNDERSIZED -27 +#define EXPONENT_OVERSIZED -28 +#define EXPONENT_UNDERSIZED -29 +#define OVERFLOW -30 +#define NOT_IOU_AMOUNT -31 +#define NOT_AN_AMOUNT -32 +#define CANT_RETURN_NEGATIVE -33 +#define NOT_AUTHORIZED -34 +#define PREVIOUS_FAILURE_PREVENTS_RETRY -35 +#define TOO_MANY_PARAMS -36 +#define INVALID_TXN -37 +#define RESERVE_INSUFFICIENT -38 +#define COMPLEX_NOT_SUPPORTED -39 +#define DOES_NOT_MATCH -40 +#define HOOK_ERROR_CODES +#endif //HOOK_ERROR_CODES diff --git a/hook/examples/extern.h b/hook/examples/extern.h new file mode 100644 index 000000000..f85857d8b --- /dev/null +++ b/hook/examples/extern.h @@ -0,0 +1,539 @@ +// For documentation please see: https://xrpl-hooks.readme.io/reference/ +// Generated using generate_extern.sh +#include +#ifndef HOOK_EXTERN + +extern int32_t +_g( + uint32_t guard_id, + uint32_t maxiter +); + +extern int64_t +accept( + uint32_t read_ptr, + uint32_t read_len, + int64_t error_code +); + +extern int64_t +emit( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len +); + +extern int64_t +etxn_burden ( + void +); + +extern int64_t +etxn_details( + uint32_t write_ptr, + uint32_t write_len +); + +extern int64_t +etxn_fee_base( + uint32_t read_ptr, + uint32_t read_len +); + +extern int64_t +etxn_generation ( + void +); + +extern int64_t +etxn_nonce( + uint32_t write_ptr, + uint32_t write_len +); + +extern int64_t +etxn_reserve( + uint32_t count +); + +extern int64_t +fee_base ( + void +); + +extern int64_t +float_compare( + int64_t float1, + int64_t float2, + uint32_t mode +); + +extern int64_t +float_divide( + int64_t float1, + int64_t float2 +); + +extern int64_t +float_exponent( + int64_t float1 +); + +extern int64_t +float_exponent_set( + int64_t float1, + int32_t exponent +); + +extern int64_t +float_int( + int64_t float1, + uint32_t decimal_places, + uint32_t abs +); + +extern int64_t +float_invert( + int64_t float1 +); + +extern int64_t +float_log( + int64_t float1 +); + +extern int64_t +float_mantissa( + int64_t float1 +); + +extern int64_t +float_mantissa_set( + int64_t float1, + int64_t mantissa +); + +extern int64_t +float_mulratio( + int64_t float1, + uint32_t round_up, + uint32_t numerator, + uint32_t denominator +); + +extern int64_t +float_multiply( + int64_t float1, + int64_t float2 +); + +extern int64_t +float_negate( + int64_t float1 +); + +extern int64_t +float_one ( + void +); + +extern int64_t +float_root( + int64_t float1, + uint32_t n +); + +extern int64_t +float_set( + int32_t exponent, + int64_t mantissa +); + +extern int64_t +float_sign( + int64_t float1 +); + +extern int64_t +float_sign_set( + int64_t float1, + uint32_t negative +); + +extern int64_t +float_sto( + uint32_t write_ptr, + uint32_t write_len, + uint32_t cread_ptr, + uint32_t cread_len, + uint32_t iread_ptr, + uint32_t iread_len, + int64_t float1, + uint32_t field_code +); + +extern int64_t +float_sto_set( + uint32_t read_ptr, + uint32_t read_len +); + +extern int64_t +float_sum( + int64_t float1, + int64_t float2 +); + +extern int64_t +hook_account( + uint32_t write_ptr, + uint32_t write_len +); + +extern int64_t +hook_again ( + void +); + +extern int64_t +hook_hash( + uint32_t write_ptr, + uint32_t write_len, + int32_t hook_no +); + +extern int64_t +hook_param( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len +); + +extern int64_t +hook_param_set( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len, + uint32_t hread_ptr, + uint32_t hread_len +); + +extern int64_t +hook_pos ( + void +); + +extern int64_t +hook_skip( + uint32_t read_ptr, + uint32_t read_len, + uint32_t flags +); + +extern int64_t +ledger_keylet( + uint32_t write_ptr, + uint32_t write_len, + uint32_t lread_ptr, + uint32_t lread_len, + uint32_t hread_ptr, + uint32_t hread_len +); + +extern int64_t +ledger_last_hash( + uint32_t write_ptr, + uint32_t write_len +); + +extern int64_t +ledger_last_time ( + void +); + +extern int64_t +ledger_nonce( + uint32_t write_ptr, + uint32_t write_len +); + +extern int64_t +ledger_seq ( + void +); + +extern int64_t +meta_slot( + uint32_t slot_no +); + +extern int64_t +otxn_burden ( + void +); + +extern int64_t +otxn_field( + uint32_t write_ptr, + uint32_t write_len, + uint32_t field_id +); + +extern int64_t +otxn_field_txt( + uint32_t write_ptr, + uint32_t write_len, + uint32_t field_id +); + +extern int64_t +otxn_generation ( + void +); + +extern int64_t +otxn_id( + uint32_t write_ptr, + uint32_t write_len, + uint32_t flags +); + +extern int64_t +otxn_slot( + uint32_t slot_no +); + +extern int64_t +otxn_type ( + void +); + +extern int64_t +rollback( + uint32_t read_ptr, + uint32_t read_len, + int64_t error_code +); + +extern int64_t +slot( + uint32_t write_ptr, + uint32_t write_len, + uint32_t slot +); + +extern int64_t +slot_clear( + uint32_t slot +); + +extern int64_t +slot_count( + uint32_t slot +); + +extern int64_t +slot_float( + uint32_t slot_no +); + +extern int64_t +slot_id( + uint32_t write_ptr, + uint32_t write_len, + uint32_t slot +); + +extern int64_t +slot_set( + uint32_t read_ptr, + uint32_t read_len, + int32_t slot +); + +extern int64_t +slot_size( + uint32_t slot +); + +extern int64_t +slot_subarray( + uint32_t parent_slot, + uint32_t array_id, + uint32_t new_slot +); + +extern int64_t +slot_subfield( + uint32_t parent_slot, + uint32_t field_id, + uint32_t new_slot +); + +extern int64_t +slot_type( + uint32_t slot_no, + uint32_t flags +); + +extern int64_t +state( + uint32_t write_ptr, + uint32_t write_len, + uint32_t kread_ptr, + uint32_t kread_len +); + +extern int64_t +state_foreign( + uint32_t write_ptr, + uint32_t write_len, + uint32_t kread_ptr, + uint32_t kread_len, + uint32_t nread_ptr, + uint32_t nread_len, + uint32_t aread_ptr, + uint32_t aread_len +); + +extern int64_t +state_foreign_set( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len, + uint32_t nread_ptr, + uint32_t nread_len, + uint32_t aread_ptr, + uint32_t aread_len +); + +extern int64_t +state_set( + uint32_t read_ptr, + uint32_t read_len, + uint32_t kread_ptr, + uint32_t kread_len +); + +extern int64_t +sto_emplace( + uint32_t write_ptr, + uint32_t write_len, + uint32_t sread_ptr, + uint32_t sread_len, + uint32_t fread_ptr, + uint32_t fread_len, + uint32_t field_id +); + +extern int64_t +sto_erase( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len, + uint32_t field_id +); + +extern int64_t +sto_subarray( + uint32_t read_ptr, + uint32_t read_len, + uint32_t array_id +); + +extern int64_t +sto_subfield( + uint32_t read_ptr, + uint32_t read_len, + uint32_t field_id +); + +extern int64_t +sto_validate( + uint32_t tread_ptr, + uint32_t tread_len +); + +extern int64_t +trace( + uint32_t mread_ptr, + uint32_t mread_len, + uint32_t dread_ptr, + uint32_t dread_len, + uint32_t as_hex +); + +extern int64_t +trace_float( + uint32_t read_ptr, + uint32_t read_len, + int64_t float1 +); + +extern int64_t +trace_num( + uint32_t read_ptr, + uint32_t read_len, + int64_t number +); + +extern int64_t +trace_slot( + uint32_t read_ptr, + uint32_t read_len, + uint32_t slot +); + +extern int64_t +util_accid( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len +); + +extern int64_t +util_keylet( + uint32_t write_ptr, + uint32_t write_len, + uint32_t keylet_type, + uint32_t a, + uint32_t b, + uint32_t c, + uint32_t d, + uint32_t e, + uint32_t f +); + +extern int64_t +util_raddr( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len +); + +extern int64_t +util_sha512h( + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len +); + +extern int64_t +util_verify( + uint32_t dread_ptr, + uint32_t dread_len, + uint32_t sread_ptr, + uint32_t sread_len, + uint32_t kread_ptr, + uint32_t kread_len +); +#define HOOK_EXTERN +#endif //HOOK_EXTERN diff --git a/hook/examples/genheaders.sh b/hook/examples/genheaders.sh new file mode 100755 index 000000000..22092243d --- /dev/null +++ b/hook/examples/genheaders.sh @@ -0,0 +1,5 @@ +#!/bin/bash +headergen/generate_error.sh > error.h +headergen/generate_extern.sh > extern.h +headergen/generate_sfcodes.sh > sfcodes.h + diff --git a/hook/examples/headergen/README.md b/hook/examples/headergen/README.md new file mode 100644 index 000000000..f222bde6d --- /dev/null +++ b/hook/examples/headergen/README.md @@ -0,0 +1,2 @@ +Bash scripts herein will generate up to date headers from the Hooks source. +You must pipe them into the desired output file. diff --git a/hook/examples/headergen/generate_error.sh b/hook/examples/headergen/generate_error.sh new file mode 100755 index 000000000..8e98d1f0f --- /dev/null +++ b/hook/examples/headergen/generate_error.sh @@ -0,0 +1,8 @@ +#!/bin/bash +RIPPLED_ROOT="`git rev-parse --show-toplevel`/src/ripple" +echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/' +echo '// Generated using generate_error.sh' +echo '#ifndef HOOK_ERROR_CODES' +cat $RIPPLED_ROOT/app/hook/Enum.h | tr -d '\n' | grep -Eo 'hook_return_code : int64_t *{[^}]+}' | grep -Eo '[A-Z_]+ *= *[0-9-]+' | sed -E 's/ *= */ /g' | sed -E 's/^/#define /g' +echo '#define HOOK_ERROR_CODES' +echo '#endif //HOOK_ERROR_CODES' diff --git a/hook/examples/headergen/generate_extern.sh b/hook/examples/headergen/generate_extern.sh new file mode 100755 index 000000000..2b39b32a1 --- /dev/null +++ b/hook/examples/headergen/generate_extern.sh @@ -0,0 +1,9 @@ +#!/bin/bash +RIPPLED_ROOT="`git rev-parse --show-toplevel`/src/ripple" +echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/' +echo '// Generated using generate_extern.sh' +echo '#include ' +echo '#ifndef HOOK_EXTERN' +cat $RIPPLED_ROOT/app/hook/applyHook.h | tr -d '\n' | grep -Eo 'DECLARE_HOOK[^\(]+\([^\)]+\)' | grep DECLARE_HOOK | cut -d'(' -f2 | sed -E 's/_t,/_t/g' | sed -E 's/ */ /g' | sort | grep -Ev '^$' | sed -E s'/\)/ \)/g' | tr '\t' ' ' | sed -E 's/^([^ ]+ [^ ]+)/\1,/g' | sed -E 's/,,*/,/g' | sed -E 's/\)/\)\n/g' | sort | grep -vE '^$' | sed -E 's/^([^,]+),/\1 (/g' | sed -E 's/\( *\)/(void)/g' | sed -E 's/, *\(/(/g' | sed -E 's/ */ /g' | sed -E 's/ *\( /(/g' | sed -E 's/ \)/)/g' | sed -E 's/\)([^;]?)/);\1/g' | sed -E 's/^int/extern int/g' | sed -E 's/^(extern [^ ]+ )/\1\n/g' | grep -Ev '^,+$' | sed -E 's/\(/\(\n /g' | sed -E 's/, */,\n /g' | sed -E 's/^extern/\nextern/g' | sed -E 's/\);/\n);/g' +echo '#define HOOK_EXTERN' +echo '#endif //HOOK_EXTERN' diff --git a/hook/examples/headergen/generate_sfcodes.sh b/hook/examples/headergen/generate_sfcodes.sh new file mode 100755 index 000000000..a5b3d112e --- /dev/null +++ b/hook/examples/headergen/generate_sfcodes.sh @@ -0,0 +1,30 @@ +#/bin/bash +RIPPLED_ROOT="`git rev-parse --show-toplevel`/src/ripple" +echo '// For documentation please see: https://xrpl-hooks.readme.io/reference/' +echo '// Generated using generate_sfcodes.sh' +cat $RIPPLED_ROOT/protocol/impl/SField.cpp | grep -E '^CONSTRUCT_' | + sed 's/UINT16/1/g' | + sed 's/UINT32/2/g' | + sed 's/UINT64/3/g' | + sed 's/HASH128/4/g' | + sed 's/HASH256/5/g' | + sed 's/UINT128/4/g' | + sed 's/UINT256/5/g' | + sed 's/AMOUNT/6/g' | + sed 's/VL/7/g' | + sed 's/ACCOUNT/8/g' | + sed 's/OBJECT/14/g' | + sed 's/ARRAY/15/g' | + sed 's/UINT8/16/g' | + sed 's/HASH160/17/g' | + sed 's/UINT160/17/g' | + sed 's/PATHSET/18/g' | + sed 's/VECTOR256/19/g' | + sed 's/UINT96/20/g' | + sed 's/UINT192/21/g' | + sed 's/UINT384/22/g' | + sed 's/UINT512/23/g' | + grep -Eo '"([^"]+)", *([0-9]+), *([0-9]+)' | + sed 's/"//g' | sed 's/ *//g' | sed 's/,/ /g' | + awk '{print ("#define sf"$1" (("$2"U << 16U) + "$3"U)")}' + diff --git a/hook/examples/hookapi.h b/hook/examples/hookapi.h new file mode 100644 index 000000000..20a4adee0 --- /dev/null +++ b/hook/examples/hookapi.h @@ -0,0 +1,48 @@ +/** + * Hook API include file + * + * Note to the reader: + * This include defines two types of things: external functions and macros + * Functions are used sparingly because a non-inlining compiler may produce + * undesirable output. + * + * Find documentation here: https://xrpl-hooks.readme.io/reference/ + */ + +#ifndef HOOKAPI_INCLUDED +#define HOOKAPI_INCLUDED 1 + + +#define KEYLET_HOOK 1 +#define KEYLET_HOOK_STATE 2 +#define KEYLET_ACCOUNT 3 +#define KEYLET_AMENDMENTS 4 +#define KEYLET_CHILD 5 +#define KEYLET_SKIP 6 +#define KEYLET_FEES 7 +#define KEYLET_NEGATIVE_UNL 8 +#define KEYLET_LINE 9 +#define KEYLET_OFFER 10 +#define KEYLET_QUALITY 11 +#define KEYLET_EMITTED_DIR 12 +#define KEYLET_TICKET 13 +#define KEYLET_SIGNERS 14 +#define KEYLET_CHECK 15 +#define KEYLET_DEPOSIT_PREAUTH 16 +#define KEYLET_UNCHECKED 17 +#define KEYLET_OWNER_DIR 18 +#define KEYLET_PAGE 19 +#define KEYLET_ESCROW 20 +#define KEYLET_PAYCHAN 21 +#define KEYLET_EMITTED 22 + +#define COMPARE_EQUAL 1U +#define COMPARE_LESS 2U +#define COMPARE_GREATER 4U + +#include "error.h" +#include "extern.h" +#include "sfcodes.h" +#include "macro.h" + +#endif diff --git a/hook/examples/macro.h b/hook/examples/macro.h new file mode 100644 index 000000000..dc42dda41 --- /dev/null +++ b/hook/examples/macro.h @@ -0,0 +1,544 @@ +/** + * These are helper macros for writing hooks, all of them are optional as is including hookmacro.h at all + */ + +#include +#include "hookapi.h" +#include "sfcodes.h" + +#ifndef HOOKMACROS_INCLUDED +#define HOOKMACROS_INCLUDED 1 + +// hook developers should use this guard macro, simply GUARD() +#define GUARD(maxiter) _g(__LINE__, (maxiter)+1) +#define GUARDM(maxiter, n) _g(((__LINE__ << 16) + n), (maxiter)+1) + +#define SBUF(str) (uint32_t)(str), sizeof(str) + +#define REQUIRE(cond, str)\ +{\ + if (!(cond))\ + rollback(SBUF(str), __LINE__);\ +} + +// make a report buffer as a c-string +// provide a name for a buffer to declare (buf) +// provide a static string +// provide an integer to print after the string +#define RBUF(buf, out_len, str, num)\ +unsigned char buf[sizeof(str) + 21];\ +int out_len = 0;\ +{\ + int i = 0;\ + for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\ + (buf)[i] = str[i];\ + if ((buf)[sizeof(str)-1] == 0) i--;\ + if ((num) < 0) (buf)[i++] = '-';\ + uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\ + uint64_t j = 10000000000000000000ULL;\ + int start = 1;\ + for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\ + {\ + unsigned char digit = ( unsigned_num / j ) % 10;\ + if (digit == 0 && start)\ + continue;\ + start = 0;\ + (buf)[i++] = '0' + digit;\ + }\ + (buf)[i] = '\0';\ + out_len = i;\ +} + +#define RBUF2(buff, out_len, str, num, str2, num2)\ +unsigned char buff[sizeof(str) + sizeof(str2) + 42];\ +int out_len = 0;\ +{\ + unsigned char* buf = buff;\ + int i = 0;\ + for (; GUARDM(sizeof(str),1),i < sizeof(str); ++i)\ + (buf)[i] = str[i];\ + if ((buf)[sizeof(str)-1] == 0) i--;\ + if ((num) < 0) (buf)[i++] = '-';\ + uint64_t unsigned_num = (uint64_t)( (num) < 0 ? (num) * -1 : (num) );\ + uint64_t j = 10000000000000000000ULL;\ + int start = 1;\ + for (; GUARDM(20,2), unsigned_num > 0 && j > 0; j /= 10)\ + {\ + unsigned char digit = ( unsigned_num / j ) % 10;\ + if (digit == 0 && start)\ + continue;\ + start = 0;\ + (buf)[i++] = '0' + digit;\ + }\ + buf += i;\ + out_len += i;\ + i = 0;\ + for (; GUARDM(sizeof(str2),3),i < sizeof(str2); ++i)\ + (buf)[i] = str2[i];\ + if ((buf)[sizeof(str2)-1] == 0) i--;\ + if ((num2) < 0) (buf)[i++] = '-';\ + unsigned_num = (uint64_t)( (num2) < 0 ? (num2) * -1 : (num2) );\ + j = 10000000000000000000ULL;\ + start = 1;\ + for (; GUARDM(20,4), unsigned_num > 0 && j > 0; j /= 10)\ + {\ + unsigned char digit = ( unsigned_num / j ) % 10;\ + if (digit == 0 && start)\ + continue;\ + start = 0;\ + (buf)[i++] = '0' + digit;\ + }\ + (buf)[i] = '\0';\ + out_len += i;\ +} + +#define TRACEVAR(v) trace_num((uint32_t)(#v), (uint32_t)(sizeof(#v) - 1), (int64_t)v); +#define TRACEHEX(v) trace((uint32_t)(#v), (uint32_t)(sizeof(#v)), (uint32_t)(v), (uint32_t)(sizeof(v)), 1); +#define TRACEXFL(v) trace_float((uint32_t)(#v), (uint32_t)(sizeof(#v)), (int64_t)v); +#define TRACESTR(v) trace((uint32_t)(#v), (uint32_t)(sizeof(#v)), (uint32_t)(v), sizeof(v), 0); + +#define CLEARBUF(b)\ +{\ + for (int x = 0; GUARD(sizeof(b)), x < sizeof(b); ++x)\ + b[x] = 0;\ +} + +// returns an in64_t, negative if error, non-negative if valid drops +#define AMOUNT_TO_DROPS(amount_buffer)\ + (((amount_buffer)[0] >> 7) ? -2 : (\ + ((((uint64_t)((amount_buffer)[0])) & 0xb00111111) << 56) +\ + (((uint64_t)((amount_buffer)[1])) << 48) +\ + (((uint64_t)((amount_buffer)[2])) << 40) +\ + (((uint64_t)((amount_buffer)[3])) << 32) +\ + (((uint64_t)((amount_buffer)[4])) << 24) +\ + (((uint64_t)((amount_buffer)[5])) << 16) +\ + (((uint64_t)((amount_buffer)[6])) << 8) +\ + (((uint64_t)((amount_buffer)[7]))))) + +#define SUB_OFFSET(x) ((int32_t)(x >> 32)) +#define SUB_LENGTH(x) ((int32_t)(x & 0xFFFFFFFFULL)) + +// when using this macro buf1len may be dynamic but buf2len must be static +// provide n >= 1 to indicate how many times the macro will be hit on the line of code +// e.g. if it is in a loop that loops 10 times n = 10 + +#define BUFFER_EQUAL_GUARD(output, buf1, buf1len, buf2, buf2len, n)\ +{\ + output = ((buf1len) == (buf2len) ? 1 : 0);\ + for (int x = 0; GUARDM( (buf2len) * (n), 1 ), output && x < (buf2len);\ + ++x)\ + output = (buf1)[x] == (buf2)[x];\ +} + +#define BUFFER_SWAP(x,y)\ +{\ + uint8_t* z = x;\ + x = y;\ + y = z;\ +} + +#define ACCOUNT_COMPARE(compare_result, buf1, buf2)\ +{\ + compare_result = 0;\ + for (int i = 0; GUARD(20), i < 20; ++i)\ + {\ + if (buf1[i] > buf2[i])\ + {\ + compare_result = 1;\ + break;\ + }\ + else if (buf1[i] < buf2[i])\ + {\ + compare_result = -1;\ + break;\ + }\ + }\ +} + +#define BUFFER_EQUAL_STR_GUARD(output, buf1, buf1len, str, n)\ + BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), n) + +#define BUFFER_EQUAL_STR(output, buf1, buf1len, str)\ + BUFFER_EQUAL_GUARD(output, buf1, buf1len, str, (sizeof(str)-1), 1) + +#define BUFFER_EQUAL(output, buf1, buf2, compare_len)\ + BUFFER_EQUAL_GUARD(output, buf1, compare_len, buf2, compare_len, 1) + +#define UINT16_TO_BUF(buf_raw, i)\ +{\ + unsigned char* buf = (unsigned char*)buf_raw;\ + buf[0] = (((uint64_t)i) >> 8) & 0xFFUL;\ + buf[1] = (((uint64_t)i) >> 0) & 0xFFUL;\ +} + +#define UINT16_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 8) +\ + ((uint64_t)((buf)[1]) << 0)) + +#define UINT32_TO_BUF(buf_raw, i)\ +{\ + unsigned char* buf = (unsigned char*)buf_raw;\ + buf[0] = (((uint64_t)i) >> 24) & 0xFFUL;\ + buf[1] = (((uint64_t)i) >> 16) & 0xFFUL;\ + buf[2] = (((uint64_t)i) >> 8) & 0xFFUL;\ + buf[3] = (((uint64_t)i) >> 0) & 0xFFUL;\ +} + + +#define UINT32_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 24) +\ + ((uint64_t)((buf)[1]) << 16) +\ + ((uint64_t)((buf)[2]) << 8) +\ + ((uint64_t)((buf)[3]) << 0)) + +#define UINT64_TO_BUF(buf_raw, i)\ +{\ + unsigned char* buf = (unsigned char*)buf_raw;\ + buf[0] = (((uint64_t)i) >> 56) & 0xFFUL;\ + buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\ + buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\ + buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\ + buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\ + buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\ + buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\ + buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\ +} + + +#define UINT64_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 56) +\ + ((uint64_t)((buf)[1]) << 48) +\ + ((uint64_t)((buf)[2]) << 40) +\ + ((uint64_t)((buf)[3]) << 32) +\ + ((uint64_t)((buf)[4]) << 24) +\ + ((uint64_t)((buf)[5]) << 16) +\ + ((uint64_t)((buf)[6]) << 8) +\ + ((uint64_t)((buf)[7]) << 0)) + + +#define INT64_FROM_BUF(buf)\ + ((((uint64_t)((buf)[0]&7FU) << 56) +\ + ((uint64_t)((buf)[1]) << 48) +\ + ((uint64_t)((buf)[2]) << 40) +\ + ((uint64_t)((buf)[3]) << 32) +\ + ((uint64_t)((buf)[4]) << 24) +\ + ((uint64_t)((buf)[5]) << 16) +\ + ((uint64_t)((buf)[6]) << 8) +\ + ((uint64_t)((buf)[7]) << 0)) * (buf[0] & 0x80U ? -1 : 1)) + +#define INT64_TO_BUF(buf_raw, i)\ +{\ + unsigned char* buf = (unsigned char*)buf_raw;\ + buf[0] = (((uint64_t)i) >> 56) & 0x7FUL;\ + buf[1] = (((uint64_t)i) >> 48) & 0xFFUL;\ + buf[2] = (((uint64_t)i) >> 40) & 0xFFUL;\ + buf[3] = (((uint64_t)i) >> 32) & 0xFFUL;\ + buf[4] = (((uint64_t)i) >> 24) & 0xFFUL;\ + buf[5] = (((uint64_t)i) >> 16) & 0xFFUL;\ + buf[6] = (((uint64_t)i) >> 8) & 0xFFUL;\ + buf[7] = (((uint64_t)i) >> 0) & 0xFFUL;\ + if (i < 0) buf[0] |= 0x80U;\ +} + +#define ttPAYMENT 0 +#define ttCHECK_CREATE 16 +#define ttNFT_ACCEPT_OFFER 29 +#define tfCANONICAL 0x80000000UL + +#define atACCOUNT 1U +#define atOWNER 2U +#define atDESTINATION 3U +#define atISSUER 4U +#define atAUTHORIZE 5U +#define atUNAUTHORIZE 6U +#define atTARGET 7U +#define atREGULARKEY 8U +#define atPSEUDOCALLBACK 9U + +#define amAMOUNT 1U +#define amBALANCE 2U +#define amLIMITAMOUNT 3U +#define amTAKERPAYS 4U +#define amTAKERGETS 5U +#define amLOWLIMIT 6U +#define amHIGHLIMIT 7U +#define amFEE 8U +#define amSENDMAX 9U +#define amDELIVERMIN 10U +#define amMINIMUMOFFER 16U +#define amRIPPLEESCROW 17U +#define amDELIVEREDAMOUNT 18U + +/** + * RH NOTE -- PAY ATTENTION + * + * ALL 'ENCODE' MACROS INCREMENT BUF_OUT + * THIS IS TO MAKE CHAINING EASY + * BUF_OUT IS A SACRIFICIAL POINTER + * + * 'ENCODE' MACROS WITH CONSTANTS HAVE + * ALIASING TO ASSIST YOU WITH ORDER + * _TYPECODE_FIELDCODE_ENCODE_MACRO + * TO PRODUCE A SERIALIZED OBJECT + * IN CANONICAL FORMAT YOU MUST ORDER + * FIRST BY TYPE CODE THEN BY FIELD CODE + * + * ALL 'PREPARE' MACROS PRESERVE POINTERS + * + **/ + + +#define ENCODE_TL_SIZE 49 +#define ENCODE_TL(buf_out, tlamt, amount_type)\ +{\ + uint8_t uat = amount_type; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + for (int i = 1; GUARDM(48, 1), i < 49; ++i)\ + buf_out[i] = tlamt[i-1];\ + buf_out += ENCODE_TL_SIZE;\ +} +#define _06_XX_ENCODE_TL(buf_out, drops, amount_type )\ + ENCODE_TL(buf_out, drops, amount_type ); +#define ENCODE_TL_AMOUNT(buf_out, drops )\ + ENCODE_TL(buf_out, drops, amAMOUNT ); +#define _06_01_ENCODE_TL_AMOUNT(buf_out, drops )\ + ENCODE_TL_AMOUNT(buf_out, drops ); + + +// Encode drops to serialization format +// consumes 9 bytes +#define ENCODE_DROPS_SIZE 9 +#define ENCODE_DROPS(buf_out, drops, amount_type ) \ + {\ + uint8_t uat = amount_type; \ + uint64_t udrops = drops; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \ + buf_out[2] = (udrops >> 48) & 0xFFU; \ + buf_out[3] = (udrops >> 40) & 0xFFU; \ + buf_out[4] = (udrops >> 32) & 0xFFU; \ + buf_out[5] = (udrops >> 24) & 0xFFU; \ + buf_out[6] = (udrops >> 16) & 0xFFU; \ + buf_out[7] = (udrops >> 8) & 0xFFU; \ + buf_out[8] = (udrops >> 0) & 0xFFU; \ + buf_out += ENCODE_DROPS_SIZE; \ + } + +#define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\ + ENCODE_DROPS(buf_out, drops, amount_type ); + +#define ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amAMOUNT ); +#define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS_AMOUNT(buf_out, drops ); + +#define ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amFEE ); +#define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS_FEE(buf_out, drops ); + +#define ENCODE_TT_SIZE 3 +#define ENCODE_TT(buf_out, tt )\ + {\ + uint8_t utt = tt;\ + buf_out[0] = 0x12U;\ + buf_out[1] =(utt >> 8 ) & 0xFFU;\ + buf_out[2] =(utt >> 0 ) & 0xFFU;\ + buf_out += ENCODE_TT_SIZE; \ + } +#define _01_02_ENCODE_TT(buf_out, tt)\ + ENCODE_TT(buf_out, tt); + + +#define ENCODE_ACCOUNT_SIZE 22 +#define ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + {\ + uint8_t uat = account_type;\ + buf_out[0] = 0x80U + uat;\ + buf_out[1] = 0x14U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\ + *(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\ + buf_out += ENCODE_ACCOUNT_SIZE;\ + } +#define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + ENCODE_ACCOUNT(buf_out, account_id, account_type); + +#define ENCODE_ACCOUNT_SRC_SIZE 22 +#define ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT); +#define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT_SRC(buf_out, account_id); + +#define ENCODE_ACCOUNT_DST_SIZE 22 +#define ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION); +#define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT_DST(buf_out, account_id); + +#define ENCODE_ACCOUNT_OWNER_SIZE 22 +#define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT(buf_out, account_id, atOWNER); +#define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT_OWNER(buf_out, account_id); + +#define ENCODE_UINT32_COMMON_SIZE 5U +#define ENCODE_UINT32_COMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U +(uf & 0x0FU); \ + buf_out[1] =(ui >> 24 ) & 0xFFU; \ + buf_out[2] =(ui >> 16 ) & 0xFFU; \ + buf_out[3] =(ui >> 8 ) & 0xFFU; \ + buf_out[4] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_COMMON_SIZE; \ + } +#define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\ + ENCODE_UINT32_COMMON(buf_out, i, field)\ + +#define ENCODE_UINT32_UNCOMMON_SIZE 6U +#define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U; \ + buf_out[1] = uf; \ + buf_out[2] =(ui >> 24 ) & 0xFFU; \ + buf_out[3] =(ui >> 16 ) & 0xFFU; \ + buf_out[4] =(ui >> 8 ) & 0xFFU; \ + buf_out[5] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \ + } +#define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + +#define ENCODE_LLS_SIZE 6U +#define ENCODE_LLS(buf_out, lls )\ + ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B ); +#define _02_27_ENCODE_LLS(buf_out, lls )\ + ENCODE_LLS(buf_out, lls ); + +#define ENCODE_FLS_SIZE 6U +#define ENCODE_FLS(buf_out, fls )\ + ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A ); +#define _02_26_ENCODE_FLS(buf_out, fls )\ + ENCODE_FLS(buf_out, fls ); + +#define ENCODE_TAG_SRC_SIZE 5 +#define ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x3U ); +#define _02_03_ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_TAG_SRC(buf_out, tag ); + +#define ENCODE_TAG_DST_SIZE 5 +#define ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0xEU ); +#define _02_14_ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_TAG_DST(buf_out, tag ); + +#define ENCODE_SEQUENCE_SIZE 5 +#define ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U ); +#define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_SEQUENCE(buf_out, sequence ); + +#define ENCODE_FLAGS_SIZE 5 +#define ENCODE_FLAGS(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x2U ); +#define _02_02_ENCODE_FLAGS(buf_out, tag )\ + ENCODE_FLAGS(buf_out, tag ); + +#define ENCODE_SIGNING_PUBKEY_SIZE 35 +#define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\ + *(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\ + *(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\ + buf[34] = pkey[32];\ + buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\ + } + +#define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + ENCODE_SIGNING_PUBKEY(buf_out, pkey ); + +#define ENCODE_SIGNING_PUBKEY_NULL_SIZE 35 +#define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out+2) = 0;\ + *(uint64_t*)(buf_out+10) = 0;\ + *(uint64_t*)(buf_out+18) = 0;\ + *(uint64_t*)(buf_out+25) = 0;\ + buf_out += ENCODE_SIGNING_PUBKEY_NULL_SIZE;\ + } + +#define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + ENCODE_SIGNING_PUBKEY_NULL(buf_out ); + + +#define PREPARE_PAYMENT_SIMPLE_SIZE 270 +#define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = buf_out_master;\ + uint8_t acc[20];\ + uint64_t drops_amount = (drops_amount_raw);\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + uint32_t cls = (uint32_t)ledger_seq();\ + hook_account(SBUF(acc));\ + _01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \ + _02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \ + _02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \ + _02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \ + _06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \ + uint8_t* fee_ptr = buf_out;\ + _06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \ + _07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \ + _08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \ + etxn_details((uint32_t)buf_out, 138); /* emitdet | size 138 */ \ + int64_t fee = etxn_fee_base(buf_out_master, 270); \ + _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ + } + +#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE_SIZE 309 +#define PREPARE_PAYMENT_SIMPLE_TRUSTLINE(buf_out_master, tlamt, drops_fee_raw, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = buf_out_master;\ + uint8_t acc[20];\ + uint64_t drops_fee = (drops_fee_raw);\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + uint32_t cls = (uint32_t)ledger_seq();\ + hook_account(SBUF(acc));\ + _01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \ + _02_04_ENCODE_SEQUENCE (buf_out, 0 ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \ + _02_26_ENCODE_FLS (buf_out, cls + 1 ); /* uint32 | size 6 */ \ + _02_27_ENCODE_LLS (buf_out, cls + 5 ); /* uint32 | size 6 */ \ + _06_01_ENCODE_TL_AMOUNT (buf_out, tlamt ); /* amount | size 48 */ \ + uint8_t* fee_ptr = buf_out;\ + _06_08_ENCODE_DROPS_FEE (buf_out, drops_fee ); /* amount | size 9 */ \ + _07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \ + _08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \ + etxn_details((uint32_t)buf_out, 138); /* emitdet | size 138 */ \ + int64_t fee = etxn_fee_base(buf_out_master, 309); \ + _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ + } + + + +#endif + + diff --git a/hook/examples/sfcodes.h b/hook/examples/sfcodes.h new file mode 100644 index 000000000..29080ad2a --- /dev/null +++ b/hook/examples/sfcodes.h @@ -0,0 +1,203 @@ +// For documentation please see: https://xrpl-hooks.readme.io/reference/ +// Generated using generate_sfcodes.sh +#define sfCloseResolution ((16U << 16U) + 1U) +#define sfMethod ((16U << 16U) + 2U) +#define sfTransactionResult ((16U << 16U) + 3U) +#define sfTickSize ((16U << 16U) + 16U) +#define sfUNLModifyDisabling ((16U << 16U) + 17U) +#define sfHookResult ((16U << 16U) + 18U) +#define sfLedgerEntryType ((1U << 16U) + 1U) +#define sfTransactionType ((1U << 16U) + 2U) +#define sfSignerWeight ((1U << 16U) + 3U) +#define sfTransferFee ((1U << 16U) + 4U) +#define sfVersion ((1U << 16U) + 16U) +#define sfHookStateChangeCount ((1U << 16U) + 17U) +#define sfHookEmitCount ((1U << 16U) + 18U) +#define sfHookExecutionIndex ((1U << 16U) + 19U) +#define sfHookApiVersion ((1U << 16U) + 20U) +#define sfFlags ((2U << 16U) + 2U) +#define sfSourceTag ((2U << 16U) + 3U) +#define sfSequence ((2U << 16U) + 4U) +#define sfPreviousTxnLgrSeq ((2U << 16U) + 5U) +#define sfLedgerSequence ((2U << 16U) + 6U) +#define sfCloseTime ((2U << 16U) + 7U) +#define sfParentCloseTime ((2U << 16U) + 8U) +#define sfSigningTime ((2U << 16U) + 9U) +#define sfExpiration ((2U << 16U) + 10U) +#define sfTransferRate ((2U << 16U) + 11U) +#define sfWalletSize ((2U << 16U) + 12U) +#define sfOwnerCount ((2U << 16U) + 13U) +#define sfDestinationTag ((2U << 16U) + 14U) +#define sfHighQualityIn ((2U << 16U) + 16U) +#define sfHighQualityOut ((2U << 16U) + 17U) +#define sfLowQualityIn ((2U << 16U) + 18U) +#define sfLowQualityOut ((2U << 16U) + 19U) +#define sfQualityIn ((2U << 16U) + 20U) +#define sfQualityOut ((2U << 16U) + 21U) +#define sfStampEscrow ((2U << 16U) + 22U) +#define sfBondAmount ((2U << 16U) + 23U) +#define sfLoadFee ((2U << 16U) + 24U) +#define sfOfferSequence ((2U << 16U) + 25U) +#define sfFirstLedgerSequence ((2U << 16U) + 26U) +#define sfLastLedgerSequence ((2U << 16U) + 27U) +#define sfTransactionIndex ((2U << 16U) + 28U) +#define sfOperationLimit ((2U << 16U) + 29U) +#define sfReferenceFeeUnits ((2U << 16U) + 30U) +#define sfReserveBase ((2U << 16U) + 31U) +#define sfReserveIncrement ((2U << 16U) + 32U) +#define sfSetFlag ((2U << 16U) + 33U) +#define sfClearFlag ((2U << 16U) + 34U) +#define sfSignerQuorum ((2U << 16U) + 35U) +#define sfCancelAfter ((2U << 16U) + 36U) +#define sfFinishAfter ((2U << 16U) + 37U) +#define sfSignerListID ((2U << 16U) + 38U) +#define sfSettleDelay ((2U << 16U) + 39U) +#define sfTicketCount ((2U << 16U) + 40U) +#define sfTicketSequence ((2U << 16U) + 41U) +#define sfNFTokenTaxon ((2U << 16U) + 42U) +#define sfMintedNFTokens ((2U << 16U) + 43U) +#define sfBurnedNFTokens ((2U << 16U) + 44U) +#define sfHookStateCount ((2U << 16U) + 45U) +#define sfEmitGeneration ((2U << 16U) + 46U) +#define sfIndexNext ((3U << 16U) + 1U) +#define sfIndexPrevious ((3U << 16U) + 2U) +#define sfBookNode ((3U << 16U) + 3U) +#define sfOwnerNode ((3U << 16U) + 4U) +#define sfBaseFee ((3U << 16U) + 5U) +#define sfExchangeRate ((3U << 16U) + 6U) +#define sfLowNode ((3U << 16U) + 7U) +#define sfHighNode ((3U << 16U) + 8U) +#define sfDestinationNode ((3U << 16U) + 9U) +#define sfCookie ((3U << 16U) + 10U) +#define sfServerVersion ((3U << 16U) + 11U) +#define sfNFTokenOfferNode ((3U << 16U) + 12U) +#define sfEmitBurden ((3U << 16U) + 13U) +#define sfHookOn ((3U << 16U) + 16U) +#define sfHookInstructionCount ((3U << 16U) + 17U) +#define sfHookReturnCode ((3U << 16U) + 18U) +#define sfReferenceCount ((3U << 16U) + 19U) +#define sfEmailHash ((4U << 16U) + 1U) +#define sfTakerPaysCurrency ((10U << 16U) + 1U) +#define sfTakerPaysIssuer ((10U << 16U) + 2U) +#define sfTakerGetsCurrency ((10U << 16U) + 3U) +#define sfTakerGetsIssuer ((10U << 16U) + 4U) +#define sfLedgerHash ((5U << 16U) + 1U) +#define sfParentHash ((5U << 16U) + 2U) +#define sfTransactionHash ((5U << 16U) + 3U) +#define sfAccountHash ((5U << 16U) + 4U) +#define sfPreviousTxnID ((5U << 16U) + 5U) +#define sfLedgerIndex ((5U << 16U) + 6U) +#define sfWalletLocator ((5U << 16U) + 7U) +#define sfRootIndex ((5U << 16U) + 8U) +#define sfAccountTxnID ((5U << 16U) + 9U) +#define sfNFTokenID ((5U << 16U) + 10U) +#define sfEmitParentTxnID ((5U << 16U) + 11U) +#define sfEmitNonce ((5U << 16U) + 12U) +#define sfEmitHookHash ((5U << 16U) + 13U) +#define sfBookDirectory ((5U << 16U) + 16U) +#define sfInvoiceID ((5U << 16U) + 17U) +#define sfNickname ((5U << 16U) + 18U) +#define sfAmendment ((5U << 16U) + 19U) +#define sfDigest ((5U << 16U) + 21U) +#define sfChannel ((5U << 16U) + 22U) +#define sfConsensusHash ((5U << 16U) + 23U) +#define sfCheckID ((5U << 16U) + 24U) +#define sfValidatedHash ((5U << 16U) + 25U) +#define sfPreviousPageMin ((5U << 16U) + 26U) +#define sfNextPageMin ((5U << 16U) + 27U) +#define sfNFTokenBuyOffer ((5U << 16U) + 28U) +#define sfNFTokenSellOffer ((5U << 16U) + 29U) +#define sfHookStateKey ((5U << 16U) + 30U) +#define sfHookHash ((5U << 16U) + 31U) +#define sfHookNamespace ((5U << 16U) + 32U) +#define sfHookSetTxnID ((5U << 16U) + 33U) +#define sfAmount ((6U << 16U) + 1U) +#define sfBalance ((6U << 16U) + 2U) +#define sfLimitAmount ((6U << 16U) + 3U) +#define sfTakerPays ((6U << 16U) + 4U) +#define sfTakerGets ((6U << 16U) + 5U) +#define sfLowLimit ((6U << 16U) + 6U) +#define sfHighLimit ((6U << 16U) + 7U) +#define sfFee ((6U << 16U) + 8U) +#define sfSendMax ((6U << 16U) + 9U) +#define sfDeliverMin ((6U << 16U) + 10U) +#define sfMinimumOffer ((6U << 16U) + 16U) +#define sfRippleEscrow ((6U << 16U) + 17U) +#define sfDeliveredAmount ((6U << 16U) + 18U) +#define sfNFTokenBrokerFee ((6U << 16U) + 19U) +#define sfHookCallbackFee ((6U << 16U) + 20U) +#define sfPublicKey ((7U << 16U) + 1U) +#define sfMessageKey ((7U << 16U) + 2U) +#define sfSigningPubKey ((7U << 16U) + 3U) +#define sfTxnSignature ((7U << 16U) + 4U) +#define sfURI ((7U << 16U) + 5U) +#define sfSignature ((7U << 16U) + 6U) +#define sfDomain ((7U << 16U) + 7U) +#define sfFundCode ((7U << 16U) + 8U) +#define sfRemoveCode ((7U << 16U) + 9U) +#define sfExpireCode ((7U << 16U) + 10U) +#define sfCreateCode ((7U << 16U) + 11U) +#define sfMemoType ((7U << 16U) + 12U) +#define sfMemoData ((7U << 16U) + 13U) +#define sfMemoFormat ((7U << 16U) + 14U) +#define sfFulfillment ((7U << 16U) + 16U) +#define sfCondition ((7U << 16U) + 17U) +#define sfMasterSignature ((7U << 16U) + 18U) +#define sfUNLModifyValidator ((7U << 16U) + 19U) +#define sfValidatorToDisable ((7U << 16U) + 20U) +#define sfValidatorToReEnable ((7U << 16U) + 21U) +#define sfHookStateData ((7U << 16U) + 22U) +#define sfHookReturnString ((7U << 16U) + 23U) +#define sfHookParameterName ((7U << 16U) + 24U) +#define sfHookParameterValue ((7U << 16U) + 25U) +#define sfAccount ((8U << 16U) + 1U) +#define sfOwner ((8U << 16U) + 2U) +#define sfDestination ((8U << 16U) + 3U) +#define sfIssuer ((8U << 16U) + 4U) +#define sfAuthorize ((8U << 16U) + 5U) +#define sfUnauthorize ((8U << 16U) + 6U) +#define sfRegularKey ((8U << 16U) + 8U) +#define sfNFTokenMinter ((8U << 16U) + 9U) +#define sfEmitCallback ((8U << 16U) + 10U) +#define sfHookAccount ((8U << 16U) + 16U) +#define sfIndexes ((19U << 16U) + 1U) +#define sfHashes ((19U << 16U) + 2U) +#define sfAmendments ((19U << 16U) + 3U) +#define sfNFTokenOffers ((19U << 16U) + 4U) +#define sfHookNamespaces ((19U << 16U) + 5U) +#define sfPaths ((18U << 16U) + 1U) +#define sfTransactionMetaData ((14U << 16U) + 2U) +#define sfCreatedNode ((14U << 16U) + 3U) +#define sfDeletedNode ((14U << 16U) + 4U) +#define sfModifiedNode ((14U << 16U) + 5U) +#define sfPreviousFields ((14U << 16U) + 6U) +#define sfFinalFields ((14U << 16U) + 7U) +#define sfNewFields ((14U << 16U) + 8U) +#define sfTemplateEntry ((14U << 16U) + 9U) +#define sfMemo ((14U << 16U) + 10U) +#define sfSignerEntry ((14U << 16U) + 11U) +#define sfNFToken ((14U << 16U) + 12U) +#define sfEmitDetails ((14U << 16U) + 13U) +#define sfHook ((14U << 16U) + 14U) +#define sfSigner ((14U << 16U) + 16U) +#define sfMajority ((14U << 16U) + 18U) +#define sfDisabledValidator ((14U << 16U) + 19U) +#define sfEmittedTxn ((14U << 16U) + 20U) +#define sfHookExecution ((14U << 16U) + 21U) +#define sfHookDefinition ((14U << 16U) + 22U) +#define sfHookParameter ((14U << 16U) + 23U) +#define sfHookGrant ((14U << 16U) + 24U) +#define sfSigners ((15U << 16U) + 3U) +#define sfSignerEntries ((15U << 16U) + 4U) +#define sfTemplate ((15U << 16U) + 5U) +#define sfNecessary ((15U << 16U) + 6U) +#define sfSufficient ((15U << 16U) + 7U) +#define sfAffectedNodes ((15U << 16U) + 8U) +#define sfMemos ((15U << 16U) + 9U) +#define sfNFTokens ((15U << 16U) + 10U) +#define sfHooks ((15U << 16U) + 11U) +#define sfMajorities ((15U << 16U) + 16U) +#define sfDisabledValidators ((15U << 16U) + 17U) +#define sfHookExecutions ((15U << 16U) + 18U) +#define sfHookParameters ((15U << 16U) + 19U) +#define sfHookGrants ((15U << 16U) + 20U) diff --git a/hook/tests/hookapi/README.md b/hook/tests/hookapi/README.md new file mode 100644 index 000000000..0df89b672 --- /dev/null +++ b/hook/tests/hookapi/README.md @@ -0,0 +1,2 @@ +# HookAPI Tests +This test-set pertains to the behaviour of Hook APIs, which are tested by installing and running hooks that invoke those APIs. diff --git a/hook/tests/hookapi/makefile b/hook/tests/hookapi/makefile new file mode 100644 index 000000000..6755ed244 --- /dev/null +++ b/hook/tests/hookapi/makefile @@ -0,0 +1,2 @@ +all: + (cd wasm; touch *.c ; make) diff --git a/hook/tests/hookapi/run-all.sh b/hook/tests/hookapi/run-all.sh new file mode 100755 index 000000000..eadccfc66 --- /dev/null +++ b/hook/tests/hookapi/run-all.sh @@ -0,0 +1,21 @@ +#!/bin/bash +RESULT="" +FAILCOUNT=0 +PASSCOUNT=0 +for i in `ls test-*.js`; do + echo Running $i + node $i 2>/dev/null >/dev/null; + if [ "$?" -eq "0" ]; + then + RESULT=`echo $RESULT'~'$i' -- PASS'` + PASSCOUNT="`echo $PASSCOUNT + 1 | bc`" + else + RESULT=`echo $RESULT'~'$i' -- FAIL'` + FAILCOUNT="`echo $FAILCOUNT + 1 | bc`" + fi +done +echo +echo "Results:" +RESULT=$RESULT~ +echo Passed: $PASSCOUNT, Failed: $FAILCOUNT, Total: `echo $PASSCOUNT + $FAILCOUNT | bc` +echo $RESULT | sed 's/.js//g' | tr '~' '\n' diff --git a/hook/tests/hookapi/test-log.js b/hook/tests/hookapi/test-log.js new file mode 100644 index 000000000..0d83dad2b --- /dev/null +++ b/hook/tests/hookapi/test-log.js @@ -0,0 +1,40 @@ +const wasmFn = 'log.wasm'; + +require('../hookset/utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm(wasmFn), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account.seed, + { + TransactionType: "AccountSet", + Account: account.classicAddress + }).then(x=> + { + t.assertTxnSuccess(x) + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookapi/test-root.js b/hook/tests/hookapi/test-root.js new file mode 100644 index 000000000..7110b7ce3 --- /dev/null +++ b/hook/tests/hookapi/test-root.js @@ -0,0 +1,40 @@ +const wasmFn = 'root.wasm'; + +require('../hookset/utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm(wasmFn), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account.seed, + { + TransactionType: "AccountSet", + Account: account.classicAddress + }).then(x=> + { + t.assertTxnSuccess(x) + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookapi/wasm/api.h b/hook/tests/hookapi/wasm/api.h new file mode 100644 index 000000000..97549a73c --- /dev/null +++ b/hook/tests/hookapi/wasm/api.h @@ -0,0 +1,7 @@ +#include +#include "../../../examples/hookapi.h" +#define ASSERT(x)\ +{\ + if (!(x))\ + rollback(SBUF(#x), __LINE__);\ +} diff --git a/hook/tests/hookapi/wasm/log.c b/hook/tests/hookapi/wasm/log.c new file mode 100644 index 000000000..5784419a4 --- /dev/null +++ b/hook/tests/hookapi/wasm/log.c @@ -0,0 +1,19 @@ +#include "api.h" + +int64_t hook(uint32_t reserved ) +{ + int64_t x = float_set(0, 1234567890); + int64_t l = float_log(x); + int64_t i = float_int(l, 15, 1); + + ASSERT(i == 9091514977169268ULL); + + ASSERT(float_log(float_one()) == 0); + + // RH TODO: more tests + + accept (0,0,0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookapi/wasm/makefile b/hook/tests/hookapi/wasm/makefile new file mode 100644 index 000000000..5adcc2b40 --- /dev/null +++ b/hook/tests/hookapi/wasm/makefile @@ -0,0 +1,7 @@ +all: log.wasm root.wasm +log.wasm: log.c + wasmcc log.c -o log.wasm -O0 -Wl,--allow-undefined -I../ + hook-cleaner log.wasm +root.wasm: root.c + wasmcc root.c -o root.wasm -O0 -Wl,--allow-undefined -I../ + hook-cleaner root.wasm diff --git a/hook/tests/hookapi/wasm/root.c b/hook/tests/hookapi/wasm/root.c new file mode 100644 index 000000000..388c4fae4 --- /dev/null +++ b/hook/tests/hookapi/wasm/root.c @@ -0,0 +1,21 @@ +#include "api.h" + +int64_t hook(uint32_t reserved ) +{ + int64_t x = float_set(0, 1234567890); + int64_t l = float_root(x, 2); + TRACEXFL(l); + int64_t i = float_int(l, 6, 1); + + TRACEVAR(i); + + ASSERT(i == 35136418286444ULL); + + + // RH TODO: more tests + + accept (0,0,0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookset/makefile b/hook/tests/hookset/makefile new file mode 100644 index 000000000..5ded44cfa --- /dev/null +++ b/hook/tests/hookset/makefile @@ -0,0 +1,2 @@ +all: + (cd wasm; make) diff --git a/hook/tests/hookset/package.json b/hook/tests/hookset/package.json new file mode 100644 index 000000000..c48a295c9 --- /dev/null +++ b/hook/tests/hookset/package.json @@ -0,0 +1,18 @@ +{ + "dependencies": { + "ripple-keypairs": "^1.1.3", + "ripple-lib": "^1.10.0", + "xrpl": "^2.1.1", + "xrpl-binary-codec": "^1.4.2", + "xrpl-hooks": "^2.2.1" + }, + "name": "hookset", + "description": "This test set pertains to testing the functionality of installing / creating / updating / deleting hooks. In short: everything except running hooks.", + "version": "1.0.0", + "main": "''", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "", + "license": "ISC" +} diff --git a/hook/tests/hookset/run-all.sh b/hook/tests/hookset/run-all.sh new file mode 100755 index 000000000..eadccfc66 --- /dev/null +++ b/hook/tests/hookset/run-all.sh @@ -0,0 +1,21 @@ +#!/bin/bash +RESULT="" +FAILCOUNT=0 +PASSCOUNT=0 +for i in `ls test-*.js`; do + echo Running $i + node $i 2>/dev/null >/dev/null; + if [ "$?" -eq "0" ]; + then + RESULT=`echo $RESULT'~'$i' -- PASS'` + PASSCOUNT="`echo $PASSCOUNT + 1 | bc`" + else + RESULT=`echo $RESULT'~'$i' -- FAIL'` + FAILCOUNT="`echo $FAILCOUNT + 1 | bc`" + fi +done +echo +echo "Results:" +RESULT=$RESULT~ +echo Passed: $PASSCOUNT, Failed: $FAILCOUNT, Total: `echo $PASSCOUNT + $FAILCOUNT | bc` +echo $RESULT | sed 's/.js//g' | tr '~' '\n' diff --git a/hook/tests/hookset/test-aaw.js b/hook/tests/hookset/test-aaw.js new file mode 100644 index 000000000..ae6234c8a --- /dev/null +++ b/hook/tests/hookset/test-aaw.js @@ -0,0 +1,42 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account1 = t.randomAccount(); + const account2 = t.randomAccount(); + t.fundFromGenesis(account1).then(()=> + { + t.fundFromGenesis(account2).then(()=> + { + t.feeSubmit(account1.seed, + { + Account: account1.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('aaw.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account2.seed, + { + Account: account2.classicAddress, + TransactionType: "AccountSet" + }).then(x=> + { + t.assertTxnSuccess(x) + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-create-3.js b/hook/tests/hookset/test-create-3.js new file mode 100644 index 000000000..54ce2f469 --- /dev/null +++ b/hook/tests/hookset/test-create-3.js @@ -0,0 +1,30 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('blacklist.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + process.exit(0); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-create.js b/hook/tests/hookset/test-create.js new file mode 100644 index 000000000..3a0136bd2 --- /dev/null +++ b/hook/tests/hookset/test-create.js @@ -0,0 +1,48 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('accept.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('accept.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnFailure(x) + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-delete.js b/hook/tests/hookset/test-delete.js new file mode 100644 index 000000000..de9b68ca6 --- /dev/null +++ b/hook/tests/hookset/test-delete.js @@ -0,0 +1,61 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = (process.argv.length < 3 ? t.randomAccount() : + t.xrpljs.Wallet.fromSeed(process.argv[2])); + + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [{Hook:{}}], + Fee: "100000" + }, {wallet: account}).then(x=> + { + t.assertTxnSuccess(x) + console.log(x) + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + Flags: t.hsfOVERRIDE, + CreateCode: t.wasm('accept.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + Flags: t.hsfOVERRIDE, + CreateCode: "" // hook delete + } + } + ], + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-fee-2.js b/hook/tests/hookset/test-fee-2.js new file mode 100644 index 000000000..4aa5ee654 --- /dev/null +++ b/hook/tests/hookset/test-fee-2.js @@ -0,0 +1,53 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + let txn_to_send = + { + SigningPubKey: '', + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('accept.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ], + SigningPubKey: '' + }; + + let wal = t.xrpljs.Wallet.fromSeed(account.seed); + t.api.prepareTransaction(txn_to_send, {wallet: wal}).then(txn => + { + let ser = t.rbc.encode(txn); + t.fee(ser).then(fees => + { + console.log(fees) + let base_drops = fees.base_fee + console.log("base_drops", base_drops) + + delete txn_to_send['SigningPubKey'] + txn_to_send['Fee'] = base_drops + ''; + + t.api.prepareTransaction(txn_to_send, {wallet: wal}).then(txn => + { + console.log(txn) + t.api.submit(txn, {wallet: wal}).then(s=> + { + t.assertTxnSuccess(s); + console.log(s); + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }); + + + }).catch(t.err); + }).catch(t.err); +}); + diff --git a/hook/tests/hookset/test-fee-4.js b/hook/tests/hookset/test-fee-4.js new file mode 100644 index 000000000..514c51475 --- /dev/null +++ b/hook/tests/hookset/test-fee-4.js @@ -0,0 +1,27 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('accept.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(s => { + t.assertTxnSuccess(s); + console.log(s); + process.exit(0); + }).catch(t.err); + }).catch(t.err); +}); + diff --git a/hook/tests/hookset/test-guard-1.js b/hook/tests/hookset/test-guard-1.js new file mode 100644 index 000000000..e199ad07a --- /dev/null +++ b/hook/tests/hookset/test-guard-1.js @@ -0,0 +1,38 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('multiguard.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "AccountSet" + }).then(x=> + { + t.assertTxnSuccess(x) + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-install.js b/hook/tests/hookset/test-install.js new file mode 100644 index 000000000..71ee4ae68 --- /dev/null +++ b/hook/tests/hookset/test-install.js @@ -0,0 +1,99 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + const account2 = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.fundFromGenesis(account2).then(()=> + { + + let hash = t.hookHash('checkstate.wasm') + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { Hook: { + CreateCode: t.wasm('checkstate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x) + t.feeSubmit(account2.seed, + { + Account: account2.classicAddress, + TransactionType: "SetHook", + Fee: "100000", + Hooks: [ + { Hook: { + HookHash: hash + }}, + { Hook: { + + }}, + { Hook: { + HookHash: hash + }} + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account2.seed, + { + Account: account2.classicAddress, + TransactionType: "SetHook", + Flags: 0, + Hooks: [ + { Hook: { + "Flags": t.hsfOVERRIDE, + "CreateCode": "", + }} + ] + }).then(x=> + { + console.log(x); + t.assertTxnSuccess(x); + + t.feeSubmit(account2.seed, + { + Account: account2.classicAddress, + TransactionType: "SetHook", + Flags: 0, + Hooks: [ + {Hook:{}}, + {Hook:{}}, + { Hook: { + HookParameters: + [ + {HookParameter: { + HookParameterName: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA", + HookParameterValue: "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAB" + }} + ] + }} + ] + }).then(x=> + { + console.log(x); + t.assertTxnSuccess(x); + + console.log("account 1 has the creation: ", account.classicAddress); + console.log("account 2 has the install and delete and update: ", account2.classicAddress); + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-nft-accdel.js b/hook/tests/hookset/test-nft-accdel.js new file mode 100644 index 000000000..5dad681fa --- /dev/null +++ b/hook/tests/hookset/test-nft-accdel.js @@ -0,0 +1,74 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account1 = t.randomAccount(); + const account2 = t.randomAccount(); + t.fundFromGenesis([account1, account2]).then(()=> + { + t.ledgerAccept(256).then(x=> + { + t.feeSubmitAccept(account1.seed, + { + NFTokenTaxon: 0, + TransactionType: "NFTokenMint", + Account: account1.classicAddress, + TransferFee: 314, + Flags: 8, + URI: "697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469", + Memos: [ + { + "Memo": { + "MemoType": + "687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963", + "MemoData": "72656E74" + } + } + ] + }).then(x=> + { + console.log(x) + t.assertTxnSuccess(x) + + const id = t.nftid(account1.classicAddress, 8, 314, 0, 0); + + t.feeSubmit(account1.seed, + { + TransactionType: "NFTokenCreateOffer", + Account: account1.classicAddress, + NFTokenID: id, + Amount: "1", + Flags: 1 + }).then(x=> + { + + console.log(x); + t.assertTxnSuccess(x) + t.fetchMeta(x.result.tx_json.hash).then(m=> + { + // RH UPTO + t.log(m); + + process.exit(0); + }).catch(t.err); +/* + t.feeSubmit(account1.seed, + { + TransactionType: "AccountDelete", + Account: account1.classicAddress, + Destination: account2.classicAddress, + Flags: 2147483648 + }).then(x=> + { + t.assertTxnSuccess(x) + + process.exit(0); + }); + */ + + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-nft-mint.js b/hook/tests/hookset/test-nft-mint.js new file mode 100644 index 000000000..46ad8ec36 --- /dev/null +++ b/hook/tests/hookset/test-nft-mint.js @@ -0,0 +1,34 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account1 = t.randomAccount(); + t.fundFromGenesis(account1).then(()=> + { + t.feeSubmit(account1.seed, + { + NFTokenTaxon: 0, + TransactionType: "NFTokenMint", + Account: account1.classicAddress, + TransferFee: 314, + Flags: 8, + URI: "697066733A2F2F62616679626569676479727A74357366703775646D37687537367568377932366E6634646675796C71616266336F636C67747179353566627A6469", + Memos: [ + { + "Memo": { + "MemoType": + "687474703A2F2F6578616D706C652E636F6D2F6D656D6F2F67656E65726963", + "MemoData": "72656E74" + } + } + ] + }).then(x=> + { + + console.log(x); + t.assertTxnSuccess(x) + process.exit(0); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-noop.js b/hook/tests/hookset/test-noop.js new file mode 100644 index 000000000..1bce1e58d --- /dev/null +++ b/hook/tests/hookset/test-noop.js @@ -0,0 +1,45 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('accept.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { Hook: { } }, + { Hook: { } }, + { Hook: { } }, + { Hook: { } } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-nsdelete-empty.js b/hook/tests/hookset/test-nsdelete-empty.js new file mode 100644 index 000000000..d0baa819f --- /dev/null +++ b/hook/tests/hookset/test-nsdelete-empty.js @@ -0,0 +1,59 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('makestate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + }, + { Hook: {} }, + { Hook: {} }, + { Hook: { + CreateCode: t.wasm('checkstate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ], + Fee: "100000" + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + Flags: t.hsfNSDELETE, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-nsdelete.js b/hook/tests/hookset/test-nsdelete.js new file mode 100644 index 000000000..f822f4b91 --- /dev/null +++ b/hook/tests/hookset/test-nsdelete.js @@ -0,0 +1,67 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('makestate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + }, + { Hook: {} }, + { Hook: {} }, + { Hook: { + CreateCode: t.wasm('checkstate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + t.api.submit( + { + Account: account.classicAddress, + TransactionType: "AccountSet", // trigger hooks + Fee: "100000" + }, {wallet: account}).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + Flags: t.hsfNSDELETE, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-state-accdel.js b/hook/tests/hookset/test-state-accdel.js new file mode 100644 index 000000000..d81fb5688 --- /dev/null +++ b/hook/tests/hookset/test-state-accdel.js @@ -0,0 +1,71 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + const account2 = t.randomAccount(); + t.fundFromGenesis([account, account2]).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('makestate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + }, + { + Hook: { + CreateCode: t.wasm('makestate2.wasm'), + HookApiVersion: 0, + HookNamespace: "CAFEF00DCAFEF00DCAFEF00DCAFEF00DCAFEF00DCAFEF00DCAFEF00DCAFEF00D", + HookOn: "0000000000000000" + } + }, + { Hook: {} }, + { Hook: { + CreateCode: t.wasm('checkstate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + t.api.submit( + { + Account: account.classicAddress, + TransactionType: "AccountSet", // trigger hooks + Fee: "100000" + }, {wallet: account}).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.ledgerAccept(255).then(x=> + { + // account delete + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "AccountDelete", + Destination: account2.classicAddress, + Flags: 2147483648 + }).then(x=> + { + console.log(x); + t.assertTxnSuccess(x); + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-state-rm.js b/hook/tests/hookset/test-state-rm.js new file mode 100644 index 000000000..831643bfe --- /dev/null +++ b/hook/tests/hookset/test-state-rm.js @@ -0,0 +1,83 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('makestate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + }, + { Hook: { + CreateCode: t.wasm('checkstate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + }, + { Hook: {} }, + { Hook: {} } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + t.api.submit( + { + Account: account.classicAddress, + TransactionType: "AccountSet", // trigger hooks + Fee: "100000" + }, {wallet: account}).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm("rmstate.wasm"), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000", + Flags: t.hsfOVERRIDE + }, + }, + { + Hook: { + Flags: t.hsfOVERRIDE, + CreateCode: "" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + t.api.submit( + { + Account: account.classicAddress, + TransactionType: "AccountSet", // trigger hooks + Fee: "100000" + }, {wallet: account}).then(x=> + { + t.assertTxnSuccess(x); + process.exit(0); + }); + }); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-state.js b/hook/tests/hookset/test-state.js new file mode 100644 index 000000000..163cc16df --- /dev/null +++ b/hook/tests/hookset/test-state.js @@ -0,0 +1,55 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account = t.randomAccount(); + t.fundFromGenesis(account).then(()=> + { + t.feeSubmit(account.seed, + { + Account: account.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('makestate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + }, + { + Hook: { + CreateCode: t.wasm('makestate2.wasm'), + HookApiVersion: 0, + HookNamespace: "CAFEF00DCAFEF00DCAFEF00DCAFEF00DCAFEF00DCAFEF00DCAFEF00DCAFEF00D", + HookOn: "0000000000000000" + } + }, + { Hook: {} }, + { Hook: { + CreateCode: t.wasm('checkstate.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + t.api.submit( + { + Account: account.classicAddress, + TransactionType: "AccountSet", // trigger hooks + Fee: "100000" + }, {wallet: account}).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-tsh-2.js b/hook/tests/hookset/test-tsh-2.js new file mode 100644 index 000000000..64cce81d1 --- /dev/null +++ b/hook/tests/hookset/test-tsh-2.js @@ -0,0 +1,53 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account1 = t.randomAccount(); + const account2 = t.randomAccount(); + t.fundFromGenesis(account1).then(()=> + { + t.fundFromGenesis(account2).then(()=> + { + t.feeSubmit(account1.seed, + { + Account: account1.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('rollback.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account2.seed, + { + Account: account2.classicAddress, + TransactionType: "SignerListSet", + SignerQuorum: 1, + SignerEntries: + [ + { + SignerEntry: + { + Account: account1.classicAddress, + SignerWeight: 1 + } + } + ] + }).then(x=> + { + t.assertTxnFailure(x) + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-tsh-dex.js b/hook/tests/hookset/test-tsh-dex.js new file mode 100644 index 000000000..1304ebc30 --- /dev/null +++ b/hook/tests/hookset/test-tsh-dex.js @@ -0,0 +1,115 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=>{ + const holder1 = t.randomAccount(); + const holder2 = t.randomAccount(); + const holder3 = t.randomAccount(); + const holder4 = t.randomAccount(); + const issuer = t.randomAccount(); + + + t.fundFromGenesis([issuer, holder1, holder2, holder3, holder4]).then(()=> + { + t.setTshCollect([holder1, holder2, holder3, holder4]).then(()=> + { + t.trustSet(issuer, "IOU", 10000, [holder1,holder2,holder3,holder4]).then(()=> + { + t.feeSubmitAcceptMultiple( + { + TransactionType: "SetHook", + Hooks: [ + { + Hook: + { + CreateCode: t.wasm('aaw.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000", + Flags: t.hsfCOLLECT + } + } + ] + }, [holder1,holder2,holder3,holder4]).then(()=> + { + + req = {}; + + req[holder1.classicAddress] = 500; + req[holder2.classicAddress] = 200; + req[holder3.classicAddress] = 80; + + t.issueTokens(issuer, "IOU", req).then(()=> + { + + const coffer = (offerer, issuer, currency, iouamount, dropsamount, buying) => + { + if (typeof(issuer.classicAddress) != 'undefined') + issuer = issuer.classicAddress; + + return new Promise((resolve, reject) => + { + let txn = + { + Account: offerer.classicAddress, + TransactionType: "OfferCreate", + }; + + let key1 = (buying ? "TakerGets" : "TakerPays"); + let key2 = (buying ? "TakerPays" : "TakerGets"); + + txn[key1] = dropsamount + ""; + txn[key2] = { + "currency": currency, + "issuer": issuer, + "value": iouamount + "" + }; + + t.feeSubmitAccept(offerer.seed, txn).then(x=> + { + console.log(x); + t.assertTxnSuccess(x); + resolve(x); + }).catch(e=>reject(e)); + }); + }; + + coffer(holder1, issuer, "IOU", 10, 250000, false).then(()=> // q= 25000 + { + coffer(holder2, issuer, "IOU", 12, 100000, false).then(()=> // q= 8333 + { + coffer(holder3, issuer, "IOU", 14, 80000, false).then(()=> // q= 5714 + { + coffer(holder4, issuer, "IOU", 30, 350000, true).then((x)=> + { + t.ledgerAccept().then(()=> + { + t.fetchMetaHookExecutions(x).then(h=> + { + t.fetchMeta(x).then(m=> + { + t.log(x); + delete m.HookExecutions; + t.log(m); + t.log(h); + console.log("Issuer:", issuer.classicAddress); + console.log("Holder 1: ", holder1.classicAddress); + console.log("Holder 2: ", holder2.classicAddress); + console.log("Holder 3: ", holder3.classicAddress); + console.log("Buyer: ", holder4.classicAddress); + console.log("(cd b; ./rippled book_offers XRP 'IOU/" + issuer.classicAddress + "');"); + console.log("(cd b; ./rippled account_lines " + issuer.classicAddress + ");"); + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-tsh-trust-nocollect-1.js b/hook/tests/hookset/test-tsh-trust-nocollect-1.js new file mode 100644 index 000000000..9d4a3cae6 --- /dev/null +++ b/hook/tests/hookset/test-tsh-trust-nocollect-1.js @@ -0,0 +1,55 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=>{ + const holder1 = t.randomAccount(); + const issuer = t.randomAccount(); + + + + t.fundFromGenesis([issuer, holder1]).then(()=> + { + t.feeSubmitAccept(issuer.seed, + { + Account: issuer.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: + { + CreateCode: t.wasm('aaw.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000", + Flags: t.hsfCOLLECT + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + + t.feeSubmitAccept(holder1.seed, + { + Account: holder1.classicAddress, + TransactionType: "TrustSet", + LimitAmount: { + "currency": "IOU", + "issuer": issuer.classicAddress, + "value": "1000" + } + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.fetchMetaHookExecutions(x, t.wasmHash('aaw.wasm')).then(m=> + { + t.assert(m.length == 0, "hook executed when it should not"); + console.log(m); + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-tsh-trust-nocollect-2.js b/hook/tests/hookset/test-tsh-trust-nocollect-2.js new file mode 100644 index 000000000..c722cccab --- /dev/null +++ b/hook/tests/hookset/test-tsh-trust-nocollect-2.js @@ -0,0 +1,65 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=>{ + const holder1 = t.randomAccount(); + const issuer = t.randomAccount(); + + + + t.fundFromGenesis([issuer, holder1]).then(()=> + { + t.feeSubmitAccept(issuer.seed, + { + Account: issuer.classicAddress, + TransactionType: "AccountSet", + SetFlag: t.asfTshCollect + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + + t.feeSubmitAccept(issuer.seed, + { + Account: issuer.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: + { + CreateCode: t.wasm('aaw.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x); + console.log(x); + + t.feeSubmitAccept(holder1.seed, + { + Account: holder1.classicAddress, + TransactionType: "TrustSet", + LimitAmount: { + "currency": "IOU", + "issuer": issuer.classicAddress, + "value": "1000" + } + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.fetchMetaHookExecutions(x, t.wasmHash('aaw.wasm')).then(m=> + { + console.log(m); + t.assert(m.length == 0, "hook executed when it should not"); + process.exit(0); + }); + }).catch(t.err); + }).catch(t.err); + }); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-tsh-trust.js b/hook/tests/hookset/test-tsh-trust.js new file mode 100644 index 000000000..6ab7cf33a --- /dev/null +++ b/hook/tests/hookset/test-tsh-trust.js @@ -0,0 +1,67 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=>{ + const holder1 = t.randomAccount(); + const issuer = t.randomAccount(); + + + + t.fundFromGenesis([issuer, holder1]).then(()=> + { + t.feeSubmitAccept(issuer.seed, + { + Account: issuer.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: + { + CreateCode: t.wasm('aaw.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000", + Flags: t.hsfCOLLECT + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + + t.feeSubmitAccept(issuer.seed, + { + Account: issuer.classicAddress, + TransactionType: "AccountSet", + SetFlag: t.asfTshCollect + }).then(x=> + { + t.assertTxnSuccess(x); + console.log(x); + + t.feeSubmitAccept(holder1.seed, + { + Account: holder1.classicAddress, + TransactionType: "TrustSet", + LimitAmount: { + "currency": "IOU", + "issuer": issuer.classicAddress, + "value": "1000" + } + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.fetchMetaHookExecutions(x, t.wasmHash('aaw.wasm')).then(m=> + { + t.assert(m.length == 1, "needed exactly one hook execution"); + t.assert(m[0].HookReturnCode == 100, "non-weak execution"); + console.log(m); + process.exit(0); + }); + }).catch(t.err); + }).catch(t.err); + }); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/test-tsh.js b/hook/tests/hookset/test-tsh.js new file mode 100644 index 000000000..191086571 --- /dev/null +++ b/hook/tests/hookset/test-tsh.js @@ -0,0 +1,53 @@ +require('../../utils-tests.js').TestRig('ws://localhost:6005').then(t=> +{ + const account1 = t.randomAccount(); + const account2 = t.randomAccount(); + t.fundFromGenesis(account1).then(()=> + { + t.fundFromGenesis(account2).then(()=> + { + t.feeSubmit(account1.seed, + { + Account: account1.classicAddress, + TransactionType: "SetHook", + Hooks: [ + { + Hook: { + CreateCode: t.wasm('accept.wasm'), + HookApiVersion: 0, + HookNamespace: "DEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEFDEADBEEF", + HookOn: "0000000000000000" + } + } + ], + }).then(x=> + { + t.assertTxnSuccess(x) + console.log(x); + t.feeSubmit(account2.seed, + { + Account: account2.classicAddress, + TransactionType: "SignerListSet", + SignerQuorum: 1, + SignerEntries: + [ + { + SignerEntry: + { + Account: account1.classicAddress, + SignerWeight: 1 + } + } + ] + }).then(x=> + { + t.assertTxnSuccess(x) + process.exit(0); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); + }).catch(t.err); +}) + + + diff --git a/hook/tests/hookset/wasm/aaw.c b/hook/tests/hookset/wasm/aaw.c new file mode 100644 index 000000000..8151ef792 --- /dev/null +++ b/hook/tests/hookset/wasm/aaw.c @@ -0,0 +1,52 @@ +// RC: 50 : strong execution +// RC: 100 : weak execution (collect call) +// RC: 150 : again as weak (not collect / after strong) +// RC: 200 : callback execution + +#include + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); +extern int64_t hook_again (void); +extern int64_t trace( uint32_t, uint32_t, uint32_t, uint32_t, uint32_t); +extern int64_t trace_num (uint32_t, uint32_t, uint64_t); +extern int64_t meta_slot(uint32_t); +extern int64_t slot(uint32_t, uint32_t, uint32_t); +#define SBUF(x) (uint32_t)((void*)x), sizeof(x) +int64_t cbak(uint32_t what) +{ + accept (SBUF("Callback execution"),200); +} + +int64_t hook(uint32_t reserved ) +{ + if (reserved == 0) + { + hook_again(); + + accept (SBUF("Strong execution"),50); + } + + + int64_t result = + meta_slot(1); + + trace_num(SBUF("meta_slot(1): "), result); + + + uint8_t dump[1024]; + result = slot(SBUF(dump), 1); + trace_num(SBUF("slot(1): "), result); + + trace(SBUF("dumped txmeta:"), dump, result, 1); + + if (reserved == 1) + accept(SBUF("Weak execution (COLLECT)"), 100); + else + accept(SBUF("AAW execution"), 150); + + + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookset/wasm/accept.c b/hook/tests/hookset/wasm/accept.c new file mode 100644 index 000000000..1ae41d732 --- /dev/null +++ b/hook/tests/hookset/wasm/accept.c @@ -0,0 +1,20 @@ +/** + * This hook just accepts any transaction coming through it + */ +#include + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + +int64_t cbak(uint32_t what) +{ + return 0; +} + +int64_t hook(uint32_t reserved ) +{ + accept (0,0,0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookset/wasm/accept64.c b/hook/tests/hookset/wasm/accept64.c new file mode 100644 index 000000000..192bf8b76 --- /dev/null +++ b/hook/tests/hookset/wasm/accept64.c @@ -0,0 +1,20 @@ +/** + * This hook just accepts any transaction coming through it + */ +#include + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + +int64_t cbak(int64_t what) +{ + return 0; +} + +int64_t hook(int64_t reserved ) +{ + accept (0,0,0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookset/wasm/checkstate.c b/hook/tests/hookset/wasm/checkstate.c new file mode 100644 index 000000000..31cc2bf4f --- /dev/null +++ b/hook/tests/hookset/wasm/checkstate.c @@ -0,0 +1,42 @@ +#include + + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); +extern int64_t state (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len); +extern int64_t trace_num (uint32_t a, uint32_t b, uint64_t i); +#define SBUF(x) x, sizeof(x) +#define GUARD(n) _g(__LINE__, n+1) +int64_t cbak(uint32_t what) +{ + return 0; +} + +int64_t hook(uint32_t reserved ) +{ + + uint8_t test[] = "hello world!"; + + uint8_t test_key[32]; + for (int i = 0; GUARD(32), i < 32; ++i) + test_key[i] = i; + + uint8_t buf[128]; + int64_t result = state(SBUF(buf), SBUF(test_key)); + + if (result <= 0) + { + trace_num(SBUF("state call failed"), result); + rollback(SBUF("state call failed"), 2); + } + for (int i = 0; GUARD(sizeof(test)+1), i < sizeof(test); ++i) + if (test[i] != buf[i]) + rollback(SBUF("hook state did not match expected value"), 1); + + + accept(SBUF("hook state matched expected value"),0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookset/wasm/ginv.c b/hook/tests/hookset/wasm/ginv.c new file mode 100644 index 000000000..92870bfd1 --- /dev/null +++ b/hook/tests/hookset/wasm/ginv.c @@ -0,0 +1,16 @@ +#include + +extern int32_t volatile _g (uint32_t id, uint32_t maxiter); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); +extern int64_t trace_num(uint32_t, uint32_t, int64_t); +int64_t hook(uint32_t r) +{ + const int x = r; + for (int i = 0; trace_num(0,0,0), _g(11, 12), i < 5; ++i) + { + trace_num("hi", 2, i); + } + + accept(0,0,0); + return 0; +} diff --git a/hook/tests/hookset/wasm/makefile b/hook/tests/hookset/wasm/makefile new file mode 100644 index 000000000..0b04f505b --- /dev/null +++ b/hook/tests/hookset/wasm/makefile @@ -0,0 +1,31 @@ +all: accept.wasm makestate.wasm makestate2.wasm checkstate.wasm rollback.wasm aaw.wasm rmstate.wasm ginv.wasm multiguard.wasm +accept.wasm: accept.c + wasmcc accept.c -o accept.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner accept.wasm +rollback.wasm: rollback.c + wasmcc rollback.c -o rollback.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner rollback.wasm +makestate.wasm: makestate.c + wasmcc makestate.c -o makestate.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner makestate.wasm +makestate2.wasm: makestate2.c + wasmcc makestate2.c -o makestate2.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner makestate2.wasm +checkstate.wasm: checkstate.c + wasmcc checkstate.c -o checkstate.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner checkstate.wasm +rmstate.wasm: rmstate.c + wasmcc rmstate.c -o rmstate.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner rmstate.wasm +aaw.wasm: aaw.c + wasmcc aaw.c -o aaw.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner aaw.wasm +ginv.wasm: ginv.c + wasmcc ginv.c -o ginv.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner ginv.wasm +multiguard.wasm: multiguard.c + wasmcc multiguard.c -o multiguard.wasm -O2 -Wl,--allow-undefined -I../ + hook-cleaner multiguard.wasm + + + diff --git a/hook/tests/hookset/wasm/makestate.c b/hook/tests/hookset/wasm/makestate.c new file mode 100644 index 000000000..721ba855e --- /dev/null +++ b/hook/tests/hookset/wasm/makestate.c @@ -0,0 +1,31 @@ +#include + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); +extern int64_t state_set (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len); +extern int64_t trace_num (uint32_t a, uint32_t b, uint64_t i); +#define SBUF(x) x, sizeof(x) +#define GUARD(n) _g(__LINE__, n+1) +int64_t cbak(uint32_t what) +{ + return 0; +} + +int64_t hook(uint32_t reserved ) +{ + + uint8_t test[] = "hello world!"; + trace_num(SBUF("location of test[]:"), test); + uint8_t test_key[32]; + for (int i = 0; GUARD(32), i < 32; ++i) + test_key[i] = i; + + int64_t result = state_set(SBUF(test), SBUF(test_key)); + + trace_num(SBUF("state_set result:"), result); + + accept (0,0,0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookset/wasm/makestate2.c b/hook/tests/hookset/wasm/makestate2.c new file mode 100644 index 000000000..84d8883bf --- /dev/null +++ b/hook/tests/hookset/wasm/makestate2.c @@ -0,0 +1,37 @@ +#include + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); +extern int64_t state_set (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len); +extern int64_t trace_num (uint32_t a, uint32_t b, uint64_t i); +#define SBUF(x) x, sizeof(x) +#define GUARD(n) _g(__LINE__, n+1) +int64_t cbak(uint32_t what) +{ + return 0; +} + +int64_t hook(uint32_t reserved ) +{ + + uint8_t test[] = "hello world!"; + uint8_t test2[] = "this is a much longer test string"; + trace_num(SBUF("location of test[]:"), test); + uint8_t test_key[32]; + for (int i = 0; GUARD(32), i < 32; ++i) + test_key[i] = i; + + int64_t result = state_set(SBUF(test), SBUF(test_key)); + + trace_num(SBUF("state_set result:"), result); + + uint8_t zero_key[32]; + result = state_set(SBUF(test2), SBUF(zero_key)); + + trace_num(SBUF("state_set result:"), result); + + accept (0,0,0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookset/wasm/multiguard.c b/hook/tests/hookset/wasm/multiguard.c new file mode 100644 index 000000000..897426103 --- /dev/null +++ b/hook/tests/hookset/wasm/multiguard.c @@ -0,0 +1,31 @@ +/** + * This hook just accepts any transaction coming through it + */ +#include + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t trace_num(uint32_t, uint32_t, int64_t); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + +int64_t cbak(uint32_t what) +{ + return 0; +} + +int64_t hook(uint32_t reserved ) +{ + for (int i = 0; i < 5; ++i) + { + _g(1,60); // every hook needs to import guard function and use it at least once + int c = i * 2; + while(c--) + { + trace_num("hi", 2, c); + _g(2, 60); + } + + } + accept (0,0,0); + // unreachable + return 0; +} diff --git a/hook/tests/hookset/wasm/rmstate.c b/hook/tests/hookset/wasm/rmstate.c new file mode 100644 index 000000000..afa34513e --- /dev/null +++ b/hook/tests/hookset/wasm/rmstate.c @@ -0,0 +1,29 @@ +#include + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); +extern int64_t state_set (uint32_t read_ptr, uint32_t read_len, uint32_t kread_ptr, uint32_t kread_len); +extern int64_t trace_num (uint32_t a, uint32_t b, uint64_t i); +#define SBUF(x) x, sizeof(x) +#define GUARD(n) _g(__LINE__, n+1) +int64_t cbak(uint32_t what) +{ + return 0; +} + +int64_t hook(uint32_t reserved ) +{ + + uint8_t test_key[32]; + for (int i = 0; GUARD(32), i < 32; ++i) + test_key[i] = i; + + int64_t result = state_set(0,0, SBUF(test_key)); + + trace_num(SBUF("state_set result:"), result); + + accept (0,0,0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/tests/hookset/wasm/rollback.c b/hook/tests/hookset/wasm/rollback.c new file mode 100644 index 000000000..b250233be --- /dev/null +++ b/hook/tests/hookset/wasm/rollback.c @@ -0,0 +1,15 @@ +/** + * This hook just accepts any transaction coming through it + */ +#include + +extern int32_t _g (uint32_t id, uint32_t maxiter); +extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + +int64_t hook(uint32_t reserved ) +{ + rollback (0,0,0); + _g(1,1); // every hook needs to import guard function and use it at least once + // unreachable + return 0; +} diff --git a/hook/utils-tests.js b/hook/utils-tests.js new file mode 100644 index 000000000..0794dc0f2 --- /dev/null +++ b/hook/utils-tests.js @@ -0,0 +1,583 @@ +const fs = require('fs') +const xrpljs = require('xrpl-hooks'); +const kp = require('ripple-keypairs'); +const crypto = require('crypto') + +const rbc = require('xrpl-binary-codec') +const rac = require('ripple-address-codec'); + +const err = (x) => +{ + console.log(x); process.exit(1); +} +// Fails via process.exit +module.exports = { + TestRig: (endpoint)=> + { + return new Promise((resolve, reject)=> + { + const api = new xrpljs.Client(endpoint); + + const nftid = (acc, flags, fee, taxon, mintseq) => + { + if (typeof(acc.classicAddress) != "undefined") + acc = acc.classicAddress; + + acc = rac.decodeAccountID(acc); + const ts = mintseq; + const tax =(taxon ^ ((384160001 * ts) + 2459)); + const id = Buffer.from([ + (flags >> 8) & 0xFF, + flags & 0xFF, + (fee >> 8) & 0xFF, + fee & 0xFF, + acc[0], + acc[1], + acc[2], + acc[3], + acc[4], + acc[5], + acc[6], + acc[7], + acc[8], + acc[9], + acc[10], + acc[11], + acc[12], + acc[13], + acc[14], + acc[15], + acc[16], + acc[17], + acc[18], + acc[19], + (tax >> 24) & 0xFF, + (tax >> 16) & 0xFF, + (tax >> 8) & 0xFF, + tax & 0xFF, + (ts >> 24) & 0xFF, + (ts >> 16) & 0xFF, + (ts >> 8) & 0xFF, + ts & 0xFF + ], 'binary').toString('hex').toUpperCase() + return id; + + }; + + + + const fee = (tx_blob) => + { + return new Promise((resolve, reject) => + { + let req = {command: 'fee'}; + if (tx_blob) + req['tx_blob'] = tx_blob; + + api.request(req).then(resp => + { + resolve(resp.result.drops); + }).catch(e => + { + reject(e); + }); + }); + }; + + const ledgerAccept = (n) => + { + return new Promise((resolve, reject) => + { + const la = (remaining) => + { + let req = {command: 'ledger_accept'}; + api.request(req).then(resp => + { + if (remaining <= 0) + resolve(resp); + la(remaining - 1); + }).catch(e=>reject(e)); + }; + + la(typeof(n) == 'undefined' ? 1 : n); + }); + }; + + const assertTxnSuccess = x => + { + if (!x || !x.result || x.result.engine_result_code != 0) + { + console.log("Transaction failed:", x) + process.exit(1); + } + }; + + const assert = (x, m) => + { + if (!(x)) + { + console.log("Assertion failed: ", m); + console.log(new Error().stack); + process.exit(1); + } + }; + + const fetchMeta = (hash) => + { + if (typeof(hash) != 'string') + hash = hash.result.tx_json.hash + + return new Promise((resolve, reject) => + { + api.request( + { + command:"tx", + transaction: hash + }).then(e=>{ + resolve(e.result.meta) + }).catch(e=>reject(e)); + }); + }; + + + const fetchMetaHookExecutions = (hash, hookhash) => + { + return new Promise((resolve, reject) => + { + fetchMeta(hash).then(m=> + { + if (typeof(m) == 'undefined' || + typeof(m.HookExecutions) == 'undefined' || + typeof(m.HookExecutions.length) == 'undefined') + { + return resolve([]) + } + + let ret = []; + + for (let i = 0; i < m.HookExecutions.length; ++i) + { + if (typeof(hookhash) == 'undefined' || + m.HookExecutions[i].HookExecution.HookHash == hookhash) + m.HookExecutions[i].HookExecution.HookReturnCode = + parseInt(m.HookExecutions[i].HookExecution.HookReturnCode, 16); + m.HookExecutions[i].HookExecution.HookInstructionCount = + parseInt(m.HookExecutions[i].HookExecution.HookInstructionCount, 16); + + let s = m.HookExecutions[i].HookExecution.HookReturnString; + if (s != '') + m.HookExecutions[i].HookExecution.HookReturnString = + Buffer.from(s, 'hex').toString('utf-8') + + ret.push(m.HookExecutions[i].HookExecution); + } + + resolve(ret); + }).catch(e=>reject(e)); + }); + }; + + + + const assertTxnFailure = x => + { + if (!x || !x.result || x.result.engine_result_code == 0) + { + console.log("Transaction failed:", x) + process.exit(1); + } + }; + + + const wasm = (x) => + { + console.log('wasm(' + x + ')'); + try + { + return fs.readFileSync(x).toString('hex').toUpperCase(); + } + catch (e) {} + + try + { + return fs.readFileSync('wasm/' + x).toString('hex').toUpperCase(); + } + catch (e) {} + + console.log("Could not find " + x) + process.exit(1); + }; + + + const wasmHash = (x)=> + { + const blob = wasm(x); + return crypto.createHash('SHA512'). + update(Buffer.from(blob, 'hex')). + digest().slice(0,32).toString('hex').toUpperCase(); + } + + const feeCompute = (account_seed, txn_org) => + { + return new Promise((resolve, reject) => + { + txn_to_send = { ... txn_org }; + txn_to_send['SigningPubKey'] = ''; + + let wal = xrpljs.Wallet.fromSeed(account_seed); + api.prepareTransaction(txn_to_send, {wallet: wal}).then(txn => + { + let ser = rbc.encode(txn); + fee(ser).then(fees => + { + let base_drops = fees.base_fee + + delete txn_to_send['SigningPubKey'] + txn_to_send['Fee'] = base_drops + ''; + + api.request( + { + command: "account_info", + account: txn.Account + }).then(y=> + { + let seq = (y.result.account_data.Sequence); + txn_to_send.Sequence = seq; + api.prepareTransaction(txn_to_send, {wallet: wal}).then(txn => + { + resolve(txn); + }).catch(e=>{reject(e);}); + }).catch(e=>{reject(e);}); + }).catch(e=>{reject(e);}); + }).catch(e=>{reject(e);}); + }); + } + + const feeSubmitAccept = (seed, txn) => + { + return new Promise((resolve, reject) => + { + feeSubmit(seed, txn).then(x=> + { + ledgerAccept().then(()=> + { + resolve(x); + }).catch(e=> + { + reject(e); + }); + }).catch(e => + { + reject(e); + }); + }); + } + + + const feeSubmit = (seed, txn) => + { + return new Promise((resolve, reject) => + { + feeCompute(seed, txn).then(txn=> + { + api.submit(txn, + {wallet: xrpljs.Wallet.fromSeed(seed)}).then(s=> + { + resolve(s); + }).catch(e=>{reject(e);}); + }).catch(e=>{reject(e);}); + }); + } + + const genesisseed = 'snoPBrXtMeMyMHUVTgbuqAfg1SUTb'; + const genesisaddr = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'; + + + const genesis = xrpljs.Wallet.fromSeed(genesisseed); + + const randomAccount = ()=> + { + const acc = xrpljs.Wallet.fromSeed(kp.generateSeed()); + console.log(acc) + return acc + }; + + const pay_mock = (seed, amt, dest) => + { + if (dest.classicAddress != undefined) + dest = dest.classicAddress; + + return new Promise((resolve, reject) => + { + + let wal = xrpljs.Wallet.fromSeed(seed); + api.prepareTransaction({ + Account: wal.classicAddress, + TransactionType: "Payment", + Amount: ''+amt, + Destination: dest, + SigningPubKey: '' + }, {wallet: wal}).then(txn => + { + resolve(rbc.encode(txn)); + }).catch(e=> + { + reject(e); + }); + }); + + } + + const pay = (seed, amt, dest) => + { + if (dest.classicAddress != undefined) + dest = dest.classicAddress; + + return new Promise((resolve, reject) => + { + let wal = xrpljs.Wallet.fromSeed(seed); + + feeSubmit(seed, { + Account: wal.classicAddress, + TransactionType: "Payment", + Amount: ''+amt, + Destination: dest + }).then(x=> + { + assertTxnSuccess(x); + resolve(x); + }).catch(err); + }); + }; + + const hookHash = fn => + { + let b = fs.readFileSync('wasm/' + fn); + return crypto.createHash('SHA512').update(b).digest().slice(0,32).toString('hex').toUpperCase() + } + + const fundFromGenesis = (acc) => + { + return new Promise((resolve, reject) => + { + const ffg = (acc, after) => + { + if (typeof(acc) != 'string') + acc = acc.classicAddress; + + console.log('ffg: ' + acc); + feeSubmitAccept(genesis.seed, { + Account: genesis.classicAddress, // fund account from genesis + TransactionType: "Payment", + Amount: "1000000000", + Destination: acc, + }).then(x=> + { + assertTxnSuccess(x); + if (after) + return after(); + else + resolve(); + }).catch(err); + }; + + const doFfg = (acc) => + { + + if (typeof(acc.length) == 'undefined') + return ffg(acc); + else if (acc.length == 1) + return ffg(acc[0]); + else + { + return ffg(acc[0], + ((acc)=>{ + return ()=>{ + acc.shift(); + return doFfg(acc); + }; + })(acc)); + } + } + + return doFfg(acc); + + }); + }; + + + + const trustSet = (issuer, currency, limit, holders) => + { + if (typeof(issuer.classicAddress) != 'undefined') + issuer = issuer.classicAddress; + + return new Promise((resolve, reject)=> + { + const doTs = (holder) => + { + if (holder.length == 0) + return resolve(); + let h = holder.shift(); + feeSubmitAccept(h.seed, + { + Account: h.classicAddress, + TransactionType: "TrustSet", + LimitAmount: { + "currency": currency + "", + "issuer": issuer, + "value": limit + "" + } + }).then(x=> + { + console.log(x) + assertTxnSuccess(x); + return doTs(holder); + }).catch(e=>reject(e)); + }; + + doTs(holders); + }); + }; + + const issueTokens = (issuer, currency, toWhom) => + { + return new Promise((resolve, reject) => + { + const itf = (issuer, currency, toWhom) => + { + let c = 0; + for (let next in toWhom) + { + c++; + + let addr = next; + let amt = toWhom[addr]; + delete toWhom[addr]; + let txn = + { + Account: issuer.classicAddress, + TransactionType: "Payment", + Amount: { + "currency": currency, + "value": amt + "", + "issuer": issuer.classicAddress + }, + Destination: addr + }; + + feeSubmitAccept(issuer.seed, txn).then(x=> + { + console.log(x); + assertTxnSuccess(x); + return itf(issuer, currency, toWhom); + }).catch(e=>reject(e)); + break; + } + if (c == 0) + resolve(); + }; + return itf(issuer, currency, toWhom); + }); + }; + + const setTshCollect = (accounts) => + { + return new Promise((resolve, reject) => + { + const stc = (accounts) => + { + if (accounts.length == 0) + return resolve(); + let acc = accounts.shift(); + + feeSubmitAccept(acc.seed, + { + Account: acc.classicAddress, + TransactionType: "AccountSet", + SetFlag: 11 + }).then(x=> + { + console.log(x); + assertTxnSuccess(x); + return stc(accounts); + }).catch(e=>reject(e)); + }; + stc(accounts); + }); + } + + const feeSubmitAcceptMultiple = (txn, accounts) => + { + return new Promise((resolve, reject) => + { + const stc = (accounts) => + { + if (accounts.length == 0) + return resolve(); + let acc = accounts.shift(); + + let txn_to_submit = { ... txn }; + + txn_to_submit['Account'] = acc.classicAddress; + feeSubmitAccept(acc.seed, txn_to_submit).then(x=> + { + console.log(x); + assertTxnSuccess(x); + return stc(accounts); + }).catch(e=>reject(e)); + }; + stc(accounts); + }); + } + + const log = m => + { +// console.log(JSON.stringify(m, null, 4)); + console.dir(m, {depth:null}); + } + + api.connect().then(()=> + { + resolve({ + rbc: rbc, + api: api, + xrpljs: xrpljs, + assertTxnSuccess: assertTxnSuccess, + assertTxnFailure: assertTxnFailure, + wasm: wasm, + kp: kp, + genesis: genesis, + randomAccount: randomAccount, + fundFromGenesis: fundFromGenesis, + err: err, + hsfOVERRIDE: 1, + hsfNSDELETE: 2, + hsfCOLLECT: 4, + asfTshCollect: 11, + hookHash: hookHash, + pay: pay, + pay_mock: pay_mock, + fee: fee, + genesisseed: genesisseed, + genesisaddr: genesisaddr, + feeCompute: feeCompute, + feeSubmit: feeSubmit, + feeSubmitAccept: feeSubmitAccept, + ledgerAccept: ledgerAccept, + fetchMeta: fetchMeta, + fetchMetaHookExecutions: fetchMetaHookExecutions, + wasmHash: wasmHash, + assert: assert, + trustSet: trustSet, + issueTokens: issueTokens, + log: log, + setTshCollect: setTshCollect, + feeSubmitAcceptMultiple: feeSubmitAcceptMultiple, + nftid: nftid + + }); + }).catch(err); + }); + } +};