diff --git a/hookstests/hookset/test-state-rm.js b/hookstests/hookset/test-state-rm.js new file mode 100644 index 000000000..2a3065025 --- /dev/null +++ b/hookstests/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/hookstests/hookset/test-tsh-dex.js b/hookstests/hookset/test-tsh-dex.js new file mode 100644 index 000000000..0f490c373 --- /dev/null +++ b/hookstests/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/hookstests/hookset/test-tsh-trust-nocollect-1.js b/hookstests/hookset/test-tsh-trust-nocollect-1.js new file mode 100644 index 000000000..c5cde0d4c --- /dev/null +++ b/hookstests/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/hookstests/hookset/test-tsh-trust-nocollect-2.js b/hookstests/hookset/test-tsh-trust-nocollect-2.js new file mode 100644 index 000000000..eb9e69947 --- /dev/null +++ b/hookstests/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/hookstests/hookset/test-tsh-trust.js b/hookstests/hookset/test-tsh-trust.js index 9896b6068..28f87e276 100644 --- a/hookstests/hookset/test-tsh-trust.js +++ b/hookstests/hookset/test-tsh-trust.js @@ -52,7 +52,7 @@ require('./utils-tests.js').TestRig('ws://localhost:6005').then(t=>{ console.log(x); t.fetchMetaHookExecutions(x, t.wasmHash('aaw.wasm')).then(m=> { - t.assert(m.length == 1, "more than one execution"); + 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); diff --git a/hookstests/hookset/utils-tests.js b/hookstests/hookset/utils-tests.js index a940616d6..ce3711702 100644 --- a/hookstests/hookset/utils-tests.js +++ b/hookstests/hookset/utils-tests.js @@ -96,7 +96,9 @@ module.exports = { if (typeof(m) == 'undefined' || typeof(m.HookExecutions) == 'undefined' || typeof(m.HookExecutions.length) == 'undefined') - reject(m); + { + return resolve([]) + } let ret = []; @@ -109,6 +111,11 @@ module.exports = { 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); } @@ -334,6 +341,138 @@ module.exports = { }); }; + + + 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({ @@ -365,7 +504,13 @@ module.exports = { fetchMeta: fetchMeta, fetchMetaHookExecutions: fetchMetaHookExecutions, wasmHash: wasmHash, - assert: assert + assert: assert, + trustSet: trustSet, + issueTokens: issueTokens, + log: log, + setTshCollect: setTshCollect, + feeSubmitAcceptMultiple: feeSubmitAcceptMultiple + }); }).catch(err); }); diff --git a/hookstests/hookset/wasm/aaw.c b/hookstests/hookset/wasm/aaw.c new file mode 100644 index 000000000..8151ef792 --- /dev/null +++ b/hookstests/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/hookstests/hookset/wasm/makefile b/hookstests/hookset/wasm/makefile index e19d1af07..2b6b245b9 100644 --- a/hookstests/hookset/wasm/makefile +++ b/hookstests/hookset/wasm/makefile @@ -1,19 +1,22 @@ -all: accept.wasm makestate.wasm makestate2.wasm checkstate.wasm rollback.wasm aaw.wasm +all: accept.wasm makestate.wasm makestate2.wasm checkstate.wasm rollback.wasm aaw.wasm rmstate.wasm accept.wasm: accept.c wasmcc accept.c -o accept.wasm -O0 -Wl,--allow-undefined -I../ - /root/hook-cleaner-c/cleaner accept.wasm + hook-cleaner accept.wasm rollback.wasm: rollback.c wasmcc rollback.c -o rollback.wasm -O0 -Wl,--allow-undefined -I../ - /root/hook-cleaner-c/cleaner rollback.wasm + hook-cleaner rollback.wasm makestate.wasm: makestate.c wasmcc makestate.c -o makestate.wasm -O0 -Wl,--allow-undefined -I../ - /root/hook-cleaner-c/cleaner makestate.wasm + hook-cleaner makestate.wasm makestate2.wasm: makestate2.c wasmcc makestate2.c -o makestate2.wasm -O0 -Wl,--allow-undefined -I../ - /root/hook-cleaner-c/cleaner makestate2.wasm + hook-cleaner makestate2.wasm checkstate.wasm: checkstate.c wasmcc checkstate.c -o checkstate.wasm -O0 -Wl,--allow-undefined -I../ - /root/hook-cleaner-c/cleaner checkstate.wasm + hook-cleaner checkstate.wasm +rmstate.wasm: rmstate.c + wasmcc rmstate.c -o rmstate.wasm -O0 -Wl,--allow-undefined -I../ + hook-cleaner rmstate.wasm aaw.wasm: aaw.c wasmcc aaw.c -o aaw.wasm -O0 -Wl,--allow-undefined -I../ - /root/hook-cleaner-c/cleaner aaw.wasm + hook-cleaner aaw.wasm diff --git a/hookstests/hookset/wasm/makestate2.c b/hookstests/hookset/wasm/makestate2.c new file mode 100644 index 000000000..84d8883bf --- /dev/null +++ b/hookstests/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/hookstests/hookset/wasm/rmstate.c b/hookstests/hookset/wasm/rmstate.c new file mode 100644 index 000000000..afa34513e --- /dev/null +++ b/hookstests/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; +}