The function either returns a success response or an error result.
My question is; if an error result is returned, does that automatically revert any state changes that happened? Lets say the error was detected after the doThing2() function. Are any state changes before that automatically reverted just by returning the error response?
Coming from a Solidity background so wondering if this is equivalent to the revert() keyword in Solidity, which explicitly tells the EVM to revert any state changes.
Unless there is a trap/error, nothing should be rolled back. Returning a Result with error data does not do anything unless you explicitly handle rolling back state changes yourself
They bring said, a trap will only rollback to the last canister call, if there are any in your method
That is not quite correct. It rolls back to the last await if any. Canister calls can occur without await (and vice versa), and do not delimit a rollback.
Ok gotcha. So in asynchronous functions the entire function is not treated like one atomic transaction (in the database sense), where its all or nothing.
Also read more about this in the docs so better understand how this works.
How to handle when making multiple external canister calls, for example swapping tokens which requires multiple token transfers, where it’s possible one succeeds, but a later call fails? In that case the canister could be left in an inconsistent state.
So rolling back would require transferring tokens back. Going back to my example, but to be more concrete:
public shared func doTwoThingsAndUpdateState(params1, params2): async Result.Result<Nat, CustomError> {
let result1 = await token1.transfer_from(params1);
if (not checkResult(result1)) {
throw Error.reject("transfer 1 failed");
};
let result2 = await token2.transfer_from(params2);
if (not checkResult(result2)) {
// reverse the first transfer
token1.transfer(params_to_reverse);
throw Error.reject("transfer 2 failed");
};
#ok(1);
};
So essentially is this the pattern used to handle scenarios like this?