I have just released Motoko v0.14.2 extending the parentheticals syntax to message sends. This now lets one to set cycles to be sent and also to activate bounded-wait messaging (formerly known as “best effort”) using (with timeout = <seconds>). Please note, though, that this latter feature is not active on mainnet just yet, with subnets being enabled successively. For local development with dfx you are all set.
To give a timeline, we’ll deprecate Cycles.add<system> in one of the upcoming releases of the 0.14.x series, nudging users to use (with cycles) instead. At some later point (0.15.0) we’ll remove the legacy interface. The new base library probably won’t even contain Cycles.add…
You are invited to test the new features and bugfixes either in the Playground or in an upcoming dfx version 0.25.1 -beta.1 final (not out yet now released). Of course you can also download it directly from the release page and use it with env DFX_MOC_PATH ... on the CLI. Also, don’t forget to bump your VSCode Motoko extension to get the new type checker in the IDE.
We are happy to receive feedback and bug reports (well, hopefully not) from early adopters.
Thanks for your support and keep having Motoko fun,
Maybe I should motivate why we chose the (with cycles) way of attaching cycles and why Cycles.add was an inferior idea.
First Cycles.add is/was a base library and compiler primitive that accumulates cycles in a private variable and attaches the current total to the call that comes next. Especially the occurrences of these two may be very distant in your (or someone else’s) source code, which opens up the possibility of supply-chain attacks and may drain your cycles balance by having a tactically placed message send to an unrelated canister snuck in by a library update.
By strictly joining the cycle attachment to the call in the Motoko syntax, such shenanigans are not possible any more (assuming proper audit), making security reviews more predicable and thus securing the Motoko canisters programmed in this new fashion.
Please refer to the programmer’s manual about the full feature description of parentheticals for syntax and usage.
## 0.14.3
* Added `isRetryPossible : Error -> Bool` to `Error` (#692).
* Made `ExperimentalInternetComputer.replyDeadline` to return
an optional return type (#693).
...
## 0.13.5
* Added `Text.fromList` and `Text.toList` functions (#676).
* Added `Text.fromArray` and `Text.fromVarArray` functions (#674).
* Added `replyDeadline` to `ExperimentalInternetComputer` (#677).
(replyDeadline is also available as a primitive if you have an older dfx/moc.) Note that before 0.14.3 (i.e. an upcoming dfx) it returned a Nat and you had to check if it is zero (meaning no deadline). Sorry for the inconvenience!
just wanna ask if this is a safe pattern?
my function has a mandatory deduplication and it will require the icrc1 token ledger to deduplicate the payment args too.
if the token transfer has been deduplicated, but my function’s deduplication is not yet, it’s safe to treat the Duplicated error from the token transfer as the success case too right?
i’m asking because i’m using the best effort call, where the call will return Unknown error and i’m allowing user to manually retry… or should i not, and go with a different way?
...
switch (RBTree.get(topup_dedupes, L.dedupeTopup, (caller, arg))) {
case (?#Locked until) if (time.now < until) return #Err(#Locked { until });
case (?#Finished block) return #Err(#Duplicate { of = block });
case null ();
};
topup_dedupes := RBTree.insert(topup_dedupes, L.dedupeTopup, (caller, arg), #Locked { until = time.now + env.duration.lock + Time2Nat64.SECONDS(env.update_timeout) });
func undedupe<T>(ret : T) : T {
topup_dedupes := RBTree.delete(topup_dedupes, L.dedupeTopup, (caller, arg));
ret;
};
let payment_arg = {
spender_subaccount = null;
from = payer_a;
to = canister_a;
fee = ?token_fee;
amount = arg.amount;
memo = arg.memo;
created_at_time = ?arg.created_at;
};
let payment_result = try await (with timeout = env.update_timeout) token_can.icrc2_transfer_from(payment_arg) catch (e) return undedupe(#Err(Error.convert(e)));
let payment_id = switch undedupe(payment_result) {
case (#Ok ok) ok;
case (#Err(#Duplicate { duplicate_of })) duplicate_of;
case (#Err other) return #Err err; // fail it
};
// success!
topup_dedupes := RBTree.insert(topup_dedupes, L.dedupeTopup, (caller, arg), #Finished block_id);
user := { game_credits += 10000 };
...
Honestly I am not the right person to judge real-world design patterns. A person with security background would be a better choice. You can also ask AI, to look for context switches and try to verify the code for absence of concurrent thread interference. Just select your code and “Ask AI” from the context menu. Or point Claude at your post’s URL.