From 74abbc97f9a6bddef8c1b23a8d09e5fdb51d8aaf Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Wed, 9 Nov 2022 17:11:38 +0800 Subject: [PATCH 1/2] add a couple of test cases when cross contract call fails, promise then and promise and fails --- tests/__tests__/test_highlevel_promise.ava.js | 71 ++++++++++++++ tests/src/highlevel-promise.js | 92 +++++++++++++++++++ tests/src/promise_api.js | 11 +++ 3 files changed, 174 insertions(+) diff --git a/tests/__tests__/test_highlevel_promise.ava.js b/tests/__tests__/test_highlevel_promise.ava.js index fa3501804..09604ce9b 100644 --- a/tests/__tests__/test_highlevel_promise.ava.js +++ b/tests/__tests__/test_highlevel_promise.ava.js @@ -94,6 +94,77 @@ test("highlevel promise delete account", async (t) => { t.is(await highlevelPromise.getSubAccount("e").exists(), false); }); +test("cross contract call panic", async (t) => { + const { ali, highlevelPromise } = t.context.accounts; + let r = await ali.callRaw(highlevelPromise, "callee_panic", "", { + gas: "70 Tgas", + }); + t.assert( + r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( + "Smart contract panicked: it just panic" + ) + ); +}); + +test.only("before and after cross contract call panic", async (t) => { + const { ali, highlevelPromise } = t.context.accounts; + let r = await ali.callRaw( + highlevelPromise, + "before_and_after_callee_panic", + "", + { + gas: "70 Tgas", + } + ); + t.assert( + r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( + "Smart contract panicked: it just panic" + ) + ); + // full transaction is revert, no log + t.deepEqual(r.result.transaction_outcome.outcome.logs, []); +}); + +test.only("cross contract call panic then callback another contract method", async (t) => { + const { ali, highlevelPromise } = t.context.accounts; + let r = await ali.callRaw(highlevelPromise, "callee_panic_then", "", { + gas: "70 Tgas", + }); + // promise then will continue, even though the promise before promise.then failed + t.is(r.result.status.SuccessValue, ""); + let state = await highlevelPromise.viewStateRaw(); + t.is(state.length, 4); +}); + +test.only("cross contract call panic and cross contract call success then callback another contract method", async (t) => { + const { ali, highlevelPromise, calleeContract } = t.context.accounts; + let r = await ali.callRaw(highlevelPromise, "callee_panic_and", "", { + gas: "100 Tgas", + }); + // promise `and` promise `then` continues, even though one of two promise and was failed. Entire transaction also success + t.is(r.result.status.SuccessValue, ""); + let state = await calleeContract.viewStateRaw(); + t.is(state.length, 3); + state = await highlevelPromise.viewStateRaw(); + t.is(state.length, 4); +}); + +test.only("cross contract call success then call a panic method", async (t) => { + const { ali, highlevelPromise, calleeContract } = t.context.accounts; + let r = await ali.callRaw(highlevelPromise, "callee_success_then_panic", "", { + gas: "100 Tgas", + }); + // the last promise fail, cause the transaction fail + t.assert( + r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( + "Smart contract panicked: it just panic" + ) + ); + // but the first success cross contract call won't revert, the state is persisted + let state = await calleeContract.viewStateRaw(); + t.is(state.length, 3); +}); + test("highlevel promise then", async (t) => { const { ali, highlevelPromise, calleeContract } = t.context.accounts; let r = await ali.callRaw(highlevelPromise, "test_promise_then", "", { diff --git a/tests/src/highlevel-promise.js b/tests/src/highlevel-promise.js index 0743a0c4b..719ebef33 100644 --- a/tests/src/highlevel-promise.js +++ b/tests/src/highlevel-promise.js @@ -116,6 +116,7 @@ export class HighlevelPromiseContract { @call({}) cross_contract_callback({ callbackArg1 }) { + near.log("in callback"); return { ...callingData(), promiseResults: arrayN(near.promiseResultsCount()).map((i) => @@ -124,4 +125,95 @@ export class HighlevelPromiseContract { callbackArg1, }; } + + @call({}) + cross_contract_callback_write_state() { + // Attempt to write something in state. If this one is successfully executed and not revoked, these should be in state + near.storageWrite("aaa", "bbb"); + near.storageWrite("ccc", "ddd"); + near.storageWrite("eee", "fff"); + } + + @call({}) + callee_panic() { + let promise = NearPromise.new("callee-contract.test.near").functionCall( + "just_panic", + bytes(""), + 0, + 2 * Math.pow(10, 13) + ); + return promise; + } + + @call({}) + before_and_after_callee_panic() { + near.log("log before call the callee"); + let promise = NearPromise.new("callee-contract.test.near").functionCall( + "just_panic", + bytes(""), + 0, + 2 * Math.pow(10, 13) + ); + near.log("log after call the callee"); + return promise; + } + + @call({}) + callee_panic_then() { + let promise = NearPromise.new("callee-contract.test.near") + .functionCall("just_panic", bytes(""), 0, 2 * Math.pow(10, 13)) + .then( + NearPromise.new("highlevel-promise.test.near").functionCall( + "cross_contract_callback_write_state", + bytes(JSON.stringify({ callbackArg1: "def" })), + 0, + 2 * Math.pow(10, 13) + ) + ); + return promise; + } + + @call({}) + callee_panic_and() { + let promise = NearPromise.new("callee-contract.test.near").functionCall( + "just_panic", + bytes("abc"), + 0, + 2 * Math.pow(10, 13) + ); + let promise2 = NearPromise.new("callee-contract.test.near").functionCall( + "write_some_state", + bytes("def"), + 0, + 2 * Math.pow(10, 13) + ); + let retPromise = promise + .and(promise2) + .then( + NearPromise.new("highlevel-promise.test.near").functionCall( + "cross_contract_callback_write_state", + bytes(JSON.stringify({ callbackArg1: "ghi" })), + 0, + 3 * Math.pow(10, 13) + ) + ); + + return retPromise; + } + + @call({}) + callee_success_then_panic() { + let promise = NearPromise.new("callee-contract.test.near") + .functionCall("write_some_state", bytes("abc"), 0, 2 * Math.pow(10, 13)) + .then( + NearPromise.new("callee-contract.test.near").functionCall( + "just_panic", + bytes(JSON.stringify({ callbackArg1: "def" })), + 0, + 2 * Math.pow(10, 13) + ) + ); + near.storageWrite("aaa", "bbb"); + return promise; + } } diff --git a/tests/src/promise_api.js b/tests/src/promise_api.js index fadc67217..cb3b6a351 100644 --- a/tests/src/promise_api.js +++ b/tests/src/promise_api.js @@ -4,6 +4,17 @@ function arrayN(n) { return [...Array(Number(n)).keys()]; } +export function just_panic() { + throw new Error("it just panic"); +} + +export function write_some_state() { + // Attempt to write something in state. If this one is successfully executed and not revoked, these should be in state + near.storageWrite("aaa", "bbb"); + near.storageWrite("ccc", "ddd"); + near.storageWrite("eee", "fff"); +} + function callingData() { return { currentAccountId: near.currentAccountId(), From 2d5396562914b4c93c10ab88085dc043d17aed3a Mon Sep 17 00:00:00 2001 From: Bo Yao Date: Thu, 10 Nov 2022 15:27:36 +0800 Subject: [PATCH 2/2] add cases of handling errors --- tests/__tests__/test_highlevel_promise.ava.js | 77 +++++++++++++++---- tests/src/highlevel-promise.js | 67 ++++++++++++++-- 2 files changed, 124 insertions(+), 20 deletions(-) diff --git a/tests/__tests__/test_highlevel_promise.ava.js b/tests/__tests__/test_highlevel_promise.ava.js index 09604ce9b..f33abc988 100644 --- a/tests/__tests__/test_highlevel_promise.ava.js +++ b/tests/__tests__/test_highlevel_promise.ava.js @@ -20,10 +20,18 @@ test.before(async (t) => { // Test users const ali = await root.createSubAccount("ali"); const bob = await root.createSubAccount("bob"); + const carl = await root.createSubAccount("carl"); // Save state for test runs t.context.worker = worker; - t.context.accounts = { root, highlevelPromise, ali, bob, calleeContract }; + t.context.accounts = { + root, + highlevelPromise, + ali, + bob, + carl, + calleeContract, + }; }); test.after.always(async (t) => { @@ -106,9 +114,9 @@ test("cross contract call panic", async (t) => { ); }); -test.only("before and after cross contract call panic", async (t) => { - const { ali, highlevelPromise } = t.context.accounts; - let r = await ali.callRaw( +test("before and after cross contract call panic", async (t) => { + const { carl, highlevelPromise } = t.context.accounts; + let r = await carl.callRaw( highlevelPromise, "before_and_after_callee_panic", "", @@ -125,9 +133,9 @@ test.only("before and after cross contract call panic", async (t) => { t.deepEqual(r.result.transaction_outcome.outcome.logs, []); }); -test.only("cross contract call panic then callback another contract method", async (t) => { - const { ali, highlevelPromise } = t.context.accounts; - let r = await ali.callRaw(highlevelPromise, "callee_panic_then", "", { +test("cross contract call panic then callback another contract method", async (t) => { + const { carl, highlevelPromise } = t.context.accounts; + let r = await carl.callRaw(highlevelPromise, "callee_panic_then", "", { gas: "70 Tgas", }); // promise then will continue, even though the promise before promise.then failed @@ -136,9 +144,9 @@ test.only("cross contract call panic then callback another contract method", asy t.is(state.length, 4); }); -test.only("cross contract call panic and cross contract call success then callback another contract method", async (t) => { - const { ali, highlevelPromise, calleeContract } = t.context.accounts; - let r = await ali.callRaw(highlevelPromise, "callee_panic_and", "", { +test("cross contract call panic and cross contract call success then callback another contract method", async (t) => { + const { carl, highlevelPromise, calleeContract } = t.context.accounts; + let r = await carl.callRaw(highlevelPromise, "callee_panic_and", "", { gas: "100 Tgas", }); // promise `and` promise `then` continues, even though one of two promise and was failed. Entire transaction also success @@ -149,11 +157,16 @@ test.only("cross contract call panic and cross contract call success then callba t.is(state.length, 4); }); -test.only("cross contract call success then call a panic method", async (t) => { - const { ali, highlevelPromise, calleeContract } = t.context.accounts; - let r = await ali.callRaw(highlevelPromise, "callee_success_then_panic", "", { - gas: "100 Tgas", - }); +test("cross contract call success then call a panic method", async (t) => { + const { carl, highlevelPromise, calleeContract } = t.context.accounts; + let r = await carl.callRaw( + highlevelPromise, + "callee_success_then_panic", + "", + { + gas: "100 Tgas", + } + ); // the last promise fail, cause the transaction fail t.assert( r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( @@ -165,6 +178,40 @@ test.only("cross contract call success then call a panic method", async (t) => { t.is(state.length, 3); }); +test("handling error in promise then", async (t) => { + const { carl, highlevelPromise } = t.context.accounts; + let r = await carl.callRaw( + highlevelPromise, + "handle_error_in_promise_then", + "", + { + gas: "70 Tgas", + } + ); + t.assert( + r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( + "caught error in the callback: " + ) + ); +}); + +test("handling error in promise then after promise and", async (t) => { + const { carl, highlevelPromise } = t.context.accounts; + let r = await carl.callRaw( + highlevelPromise, + "handle_error_in_promise_then_after_promise_and", + "", + { + gas: "100 Tgas", + } + ); + t.assert( + r.result.status.Failure.ActionError.kind.FunctionCallError.ExecutionError.includes( + "caught error in the callback: " + ) + ); +}); + test("highlevel promise then", async (t) => { const { ali, highlevelPromise, calleeContract } = t.context.accounts; let r = await ali.callRaw(highlevelPromise, "test_promise_then", "", { diff --git a/tests/src/highlevel-promise.js b/tests/src/highlevel-promise.js index 719ebef33..c9bc3800c 100644 --- a/tests/src/highlevel-promise.js +++ b/tests/src/highlevel-promise.js @@ -165,7 +165,7 @@ export class HighlevelPromiseContract { .then( NearPromise.new("highlevel-promise.test.near").functionCall( "cross_contract_callback_write_state", - bytes(JSON.stringify({ callbackArg1: "def" })), + bytes(""), 0, 2 * Math.pow(10, 13) ) @@ -177,13 +177,13 @@ export class HighlevelPromiseContract { callee_panic_and() { let promise = NearPromise.new("callee-contract.test.near").functionCall( "just_panic", - bytes("abc"), + bytes(""), 0, 2 * Math.pow(10, 13) ); let promise2 = NearPromise.new("callee-contract.test.near").functionCall( "write_some_state", - bytes("def"), + bytes(""), 0, 2 * Math.pow(10, 13) ); @@ -192,7 +192,7 @@ export class HighlevelPromiseContract { .then( NearPromise.new("highlevel-promise.test.near").functionCall( "cross_contract_callback_write_state", - bytes(JSON.stringify({ callbackArg1: "ghi" })), + bytes(""), 0, 3 * Math.pow(10, 13) ) @@ -208,7 +208,7 @@ export class HighlevelPromiseContract { .then( NearPromise.new("callee-contract.test.near").functionCall( "just_panic", - bytes(JSON.stringify({ callbackArg1: "def" })), + bytes(""), 0, 2 * Math.pow(10, 13) ) @@ -216,4 +216,61 @@ export class HighlevelPromiseContract { near.storageWrite("aaa", "bbb"); return promise; } + + @call({}) + handler({ promiseId }) { + // example to catch and handle one given promiseId. This is to simulate when you know some + // promiseId can be possibly fail and some promiseId can never fail. If more than one promiseId + // can be failed. a similar approach can be applied to all promiseIds. + let res; + try { + res = near.promiseResult(promiseId); + } catch (e) { + throw new Error("caught error in the callback: " + e.toString()); + } + return "callback got " + res; + } + + @call({}) + handle_error_in_promise_then() { + let promise = NearPromise.new("callee-contract.test.near") + .functionCall("just_panic", bytes(""), 0, 2 * Math.pow(10, 13)) + .then( + NearPromise.new("highlevel-promise.test.near").functionCall( + "handler", + bytes(JSON.stringify({ promiseId: 0 })), + 0, + 2 * Math.pow(10, 13) + ) + ); + return promise; + } + + @call({}) + handle_error_in_promise_then_after_promise_and() { + let promise = NearPromise.new("callee-contract.test.near") + .functionCall( + "cross_contract_callee", + bytes("abc"), + 0, + 2 * Math.pow(10, 13) + ) + .and( + NearPromise.new("callee-contract.test.near").functionCall( + "just_panic", + bytes(""), + 0, + 2 * Math.pow(10, 13) + ) + ) + .then( + NearPromise.new("highlevel-promise.test.near").functionCall( + "handler", + bytes(JSON.stringify({ promiseId: 1 })), + 0, + 2 * Math.pow(10, 13) + ) + ); + return promise; + } }