Skip to content

Commit

Permalink
Test cross contract call failures and error handling (#294)
Browse files Browse the repository at this point in the history
* add a couple of test cases when cross contract call fails, promise then and promise and fails

* add cases of handling errors
  • Loading branch information
ailisp authored Nov 10, 2022
1 parent 2e0f9d3 commit 7a26824
Show file tree
Hide file tree
Showing 3 changed files with 279 additions and 1 deletion.
120 changes: 119 additions & 1 deletion tests/__tests__/test_highlevel_promise.ava.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -94,6 +102,116 @@ 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("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",
"",
{
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("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
t.is(r.result.status.SuccessValue, "");
let state = await highlevelPromise.viewStateRaw();
t.is(state.length, 4);
});

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
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("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(
"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("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", "", {
Expand Down
149 changes: 149 additions & 0 deletions tests/src/highlevel-promise.js
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export class HighlevelPromiseContract {

@call({})
cross_contract_callback({ callbackArg1 }) {
near.log("in callback");
return {
...callingData(),
promiseResults: arrayN(near.promiseResultsCount()).map((i) =>
Expand All @@ -124,4 +125,152 @@ 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(""),
0,
2 * Math.pow(10, 13)
)
);
return promise;
}

@call({})
callee_panic_and() {
let promise = NearPromise.new("callee-contract.test.near").functionCall(
"just_panic",
bytes(""),
0,
2 * Math.pow(10, 13)
);
let promise2 = NearPromise.new("callee-contract.test.near").functionCall(
"write_some_state",
bytes(""),
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(""),
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(""),
0,
2 * Math.pow(10, 13)
)
);
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;
}
}
11 changes: 11 additions & 0 deletions tests/src/promise_api.js
Original file line number Diff line number Diff line change
Expand Up @@ -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(),
Expand Down

0 comments on commit 7a26824

Please sign in to comment.