Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Test cross contract call failures and error handling #294

Merged
merged 3 commits into from
Nov 10, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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