From ce9c070e0d23a82218e6db5253eb00541b7ab9cb Mon Sep 17 00:00:00 2001 From: byronbecker Date: Thu, 12 Sep 2024 11:08:03 -0700 Subject: [PATCH] Fix parallelization - inline async functions, refactor internals to mock the icrc2 actor instead of the function. --- .../{WrappedICRC2Actor.mo => ICRCCall.mo} | 30 ++-- mops.toml | 2 +- src/ICRC2Actor.mo | 17 +- src/lib.mo | 17 +- ...pedICRC2Actor.test.mo => ICRCCall.test.mo} | 160 ++++++++---------- 5 files changed, 103 insertions(+), 123 deletions(-) rename internal/{WrappedICRC2Actor.mo => ICRCCall.mo} (78%) rename test/{WrappedICRC2Actor.test.mo => ICRCCall.test.mo} (72%) diff --git a/internal/WrappedICRC2Actor.mo b/internal/ICRCCall.mo similarity index 78% rename from internal/WrappedICRC2Actor.mo rename to internal/ICRCCall.mo index fddb1f4..a1ee439 100644 --- a/internal/WrappedICRC2Actor.mo +++ b/internal/ICRCCall.mo @@ -26,21 +26,21 @@ module { icrc2Actor : ICRC2Interface.ICRC2Actor, passedBatchSize: Nat, allowances: [ICRC2Interface.AllowanceArgs], - allowanceFunction : (ICRC2Interface.ICRC2Actor, ICRC2Interface.AllowanceArgs) -> async* ICRC2Interface.Allowance + //allowanceFunction : (ICRC2Interface.ICRC2Actor, ICRC2Interface.AllowanceArgs) -> async* ICRC2Interface.Allowance ) : async* [ICRC2Interface.Allowance] { let batchSize = if (passedBatchSize > BATCH_SIZE_LIMIT) { print("You passed in a batch size of " # debug_show(passedBatchSize) # ", but the current max batch size is " # debug_show(BATCH_SIZE_LIMIT) # ". Defaulting to a batch size of " # debug_show(BATCH_SIZE_LIMIT) # "."); BATCH_SIZE_LIMIT; } else { passedBatchSize }; - let allowanceFutures = Buffer.Buffer(batchSize); + let allowanceFutures = Buffer.Buffer(batchSize); let allowanceResults = Buffer.Buffer(allowances.size()); for (allowanceArgs in allowances.vals()) { print("allowanceArgs: " # debug_show(allowanceArgs)); - allowanceFutures.add(allowanceFunction(icrc2Actor, allowanceArgs)); + allowanceFutures.add(icrc2Actor.icrc2_allowance(allowanceArgs)); if (allowanceFutures.size() >= batchSize) { for (allowanceFuture in allowanceFutures.vals()) { - allowanceResults.add(await* allowanceFuture); + allowanceResults.add(await allowanceFuture); }; allowanceFutures.clear(); }; @@ -48,7 +48,7 @@ module { // Add any remaining results for (allowanceFuture in allowanceFutures.vals()) { - allowanceResults.add(await* allowanceFuture); + allowanceResults.add(await allowanceFuture); }; Buffer.toArray(allowanceResults); @@ -59,20 +59,20 @@ module { icrc2Actor : ICRC2Interface.ICRC2Actor, passedBatchSize: Nat, approvals: [ICRC2Interface.ApproveArgs], - approveFunction : (ICRC2Interface.ICRC2Actor, ICRC2Interface.ApproveArgs) -> async* { #Ok : Nat; #Err : ICRC2Interface.ApproveError } + //approveFunction : (ICRC2Interface.ICRC2Actor, ICRC2Interface.ApproveArgs) -> async* { #Ok : Nat; #Err : ICRC2Interface.ApproveError } ) : async* [{ #Ok : Nat; #Err : ICRC2Interface.ApproveError }] { let batchSize = if (passedBatchSize > BATCH_SIZE_LIMIT) { print("You passed in a batch size of " # debug_show(passedBatchSize) # ", but the current max batch size is " # debug_show(BATCH_SIZE_LIMIT) # ". Defaulting to a batch size of " # debug_show(BATCH_SIZE_LIMIT) # "."); BATCH_SIZE_LIMIT; } else { passedBatchSize }; - let approvalFutures = Buffer.Buffer(batchSize); + let approvalFutures = Buffer.Buffer(batchSize); let approvalResults = Buffer.Buffer<{ #Ok : Nat; #Err : ICRC2Interface.ApproveError }>(approvals.size()); for (approvalArgs in approvals.vals()) { - approvalFutures.add(approveFunction(icrc2Actor, approvalArgs)); + approvalFutures.add(icrc2Actor.icrc2_approve(approvalArgs)); if (approvalFutures.size() >= batchSize) { for (approvalFuture in approvalFutures.vals()) { - approvalResults.add(await* approvalFuture); + approvalResults.add(await approvalFuture); }; approvalFutures.clear(); }; @@ -80,7 +80,7 @@ module { // Add any remaining results for (approvalFuture in approvalFutures.vals()) { - approvalResults.add(await* approvalFuture); + approvalResults.add(await approvalFuture); }; Buffer.toArray(approvalResults); @@ -92,20 +92,20 @@ module { icrc2Actor : ICRC2Interface.ICRC2Actor, passedBatchSize : Nat, transfers: [ICRC2Interface.TransferFromArgs], - transferFunction : (ICRC2Interface.ICRC2Actor, ICRC2Interface.TransferFromArgs) -> async* { #Ok : Nat; #Err : ICRC2Interface.TransferFromError } + //transferFunction : (ICRC2Interface.ICRC2Actor, ICRC2Interface.TransferFromArgs) -> async* { #Ok : Nat; #Err : ICRC2Interface.TransferFromError } ) : async* [{ #Ok : Nat; #Err : ICRC2Interface.TransferFromError }] { let batchSize = if (passedBatchSize > BATCH_SIZE_LIMIT) { print("You passed in a batch size of " # debug_show(passedBatchSize) # ", but the current max batch size is " # debug_show(BATCH_SIZE_LIMIT) # ". Defaulting to a batch size of " # debug_show(BATCH_SIZE_LIMIT) # "."); BATCH_SIZE_LIMIT; } else { passedBatchSize }; - let transferFutures = Buffer.Buffer(batchSize); + let transferFutures = Buffer.Buffer(batchSize); let transferResults = Buffer.Buffer<{ #Ok : Nat; #Err : ICRC2Interface.TransferFromError }>(transfers.size()); for (transferArgs in transfers.vals()) { - transferFutures.add(transferFunction(icrc2Actor, transferArgs)); + transferFutures.add(icrc2Actor.icrc2_transfer_from(transferArgs)); if (transferFutures.size() >= batchSize) { for (transferFuture in transferFutures.vals()) { - transferResults.add(await* transferFuture); + transferResults.add(await transferFuture); }; transferFutures.clear(); }; @@ -113,7 +113,7 @@ module { // Add any remaining results for (transferFuture in transferFutures.vals()) { - transferResults.add(await* transferFuture); + transferResults.add(await transferFuture); }; Buffer.toArray(transferResults); diff --git a/mops.toml b/mops.toml index a99ada4..bf22e09 100644 --- a/mops.toml +++ b/mops.toml @@ -1,6 +1,6 @@ [package] name = "icrc2-batch" -version = "1.0.0" +version = "1.0.1" description = "Batch functionality for ICRC-2 enabled token ledgers" repository = "https://github.com/MemeFighterCo/icrc2-batch" keywords = [ "ICRC", "ICRC-2", "transaction", "token", "ledger" ] diff --git a/src/ICRC2Actor.mo b/src/ICRC2Actor.mo index ea69d62..50c884c 100644 --- a/src/ICRC2Actor.mo +++ b/src/ICRC2Actor.mo @@ -2,7 +2,7 @@ import Principal "mo:base/Principal"; import ICRC2Interface "ICRC2Interface"; import Buffer "mo:base/Buffer"; import { print } "mo:base/Debug"; -import WrappedIcrc2Actor "../internal/WrappedICRC2Actor"; +import ICRCCall "../internal/ICRCCall"; import { BATCH_SIZE_LIMIT } "../internal/Constants"; module { @@ -38,49 +38,46 @@ module { /// Get the allowance for a spender on an account public func icrc2_allowance(args: ICRC2Interface.AllowanceArgs) : async* ICRC2Interface.Allowance { - await* WrappedIcrc2Actor.icrc2_allowance(icrc2Actor, args); + await* ICRCCall.icrc2_allowance(icrc2Actor, args); }; /// Approve a spender to spend a certain amount on behalf of the owner public func icrc2_approve(args: ICRC2Interface.ApproveArgs) : async* { #Ok : Nat; #Err : ICRC2Interface.ApproveError } { - await* WrappedIcrc2Actor.icrc2_approve(icrc2Actor, args); + await* ICRCCall.icrc2_approve(icrc2Actor, args); }; /// Transfer an approved amount from one account to another public func icrc2_transfer_from(args: ICRC2Interface.TransferFromArgs) : async* { #Ok : Nat; #Err : ICRC2Interface.TransferFromError } { - await* WrappedIcrc2Actor.icrc2_transfer_from(icrc2Actor, args); + await* ICRCCall.icrc2_transfer_from(icrc2Actor, args); }; /// Batch get allowances provided a list of allowance arguments /// Note: If one call fails, the entire batch will fail public func icrc2_allowance_batch(allowances: [ICRC2Interface.AllowanceArgs]) : async* [ICRC2Interface.Allowance] { - await* WrappedIcrc2Actor.wrapped_icrc2_allowance_batch( + await* ICRCCall.wrapped_icrc2_allowance_batch( icrc2Actor, limitedBatchSize, allowances, - WrappedIcrc2Actor.icrc2_allowance ); }; /// Batch approval provided a list of approval arguments /// Note: If one call traps, the entire batch will fail (expected errors handled gracefully by the variant type) public func icrc2_approve_batch(approvals: [ICRC2Interface.ApproveArgs]) : async* [{ #Ok : Nat; #Err : ICRC2Interface.ApproveError }] { - await* WrappedIcrc2Actor.wrapped_icrc2_approve_batch( + await* ICRCCall.wrapped_icrc2_approve_batch( icrc2Actor, limitedBatchSize, approvals, - WrappedIcrc2Actor.icrc2_approve ); }; /// Batch transfer_from provided a list of transfer_from arguments /// Note: If one call traps, the entire batch will fail (expected errors handled gracefully by the variant type) public func icrc2_transfer_from_batch(transfers: [ICRC2Interface.TransferFromArgs]) : async* [{ #Ok : Nat; #Err : ICRC2Interface.TransferFromError }] { - await* WrappedIcrc2Actor.wrapped_icrc2_transfer_from_batch( + await* ICRCCall.wrapped_icrc2_transfer_from_batch( icrc2Actor, limitedBatchSize, transfers, - WrappedIcrc2Actor.icrc2_transfer_from ); }; }; diff --git a/src/lib.mo b/src/lib.mo index 93e7b4b..9042c22 100755 --- a/src/lib.mo +++ b/src/lib.mo @@ -1,6 +1,6 @@ import ICRC2Actor "ICRC2Actor"; import ICRC2Interface "ICRC2Interface"; -import WrappedIcrc2Actor "../internal/WrappedICRC2Actor"; +import ICRCCall "../internal/ICRCCall"; module { /// Class capable of batching ICRC2 transactions to ICRC2 actor canisters @@ -8,17 +8,17 @@ module { /// Get the allowance for a spender on an account public func icrc2_allowance(icrc2Actor: ICRC2Interface.ICRC2Actor, args: ICRC2Interface.AllowanceArgs) : async* ICRC2Interface.Allowance { - return await* WrappedIcrc2Actor.icrc2_allowance(icrc2Actor, args); + return await* ICRCCall.icrc2_allowance(icrc2Actor, args); }; /// Approve a spender to spend a certain amount on behalf of the owner public func icrc2_approve(icrc2Actor: ICRC2Interface.ICRC2Actor, args: ICRC2Interface.ApproveArgs) : async* { #Ok : Nat; #Err : ICRC2Interface.ApproveError } { - return await* WrappedIcrc2Actor.icrc2_approve(icrc2Actor, args); + return await* ICRCCall.icrc2_approve(icrc2Actor, args); }; /// Transfer an approved amount from one account to another public func icrc2_transfer_from(icrc2Actor: ICRC2Interface.ICRC2Actor, args: ICRC2Interface.TransferFromArgs) : async* { #Ok : Nat; #Err : ICRC2Interface.TransferFromError } { - return await* WrappedIcrc2Actor.icrc2_transfer_from(icrc2Actor, args); + return await* ICRCCall.icrc2_transfer_from(icrc2Actor, args); }; /// Batch get allowances provided a list of allowance arguments @@ -28,11 +28,10 @@ module { batchSize: Nat, allowances: [ICRC2Interface.AllowanceArgs] ) : async* [ICRC2Interface.Allowance] { - return await* WrappedIcrc2Actor.wrapped_icrc2_allowance_batch( + return await* ICRCCall.wrapped_icrc2_allowance_batch( icrc2Actor, batchSize, allowances, - WrappedIcrc2Actor.icrc2_allowance ); }; @@ -43,11 +42,10 @@ module { batchSize: Nat, approvals: [ICRC2Interface.ApproveArgs] ) : async* [{ #Ok : Nat; #Err : ICRC2Interface.ApproveError }] { - return await* WrappedIcrc2Actor.wrapped_icrc2_approve_batch( + return await* ICRCCall.wrapped_icrc2_approve_batch( icrc2Actor, batchSize, approvals, - WrappedIcrc2Actor.icrc2_approve ); }; @@ -58,11 +56,10 @@ module { batchSize: Nat, transfers: [ICRC2Interface.TransferFromArgs] ) : async* [{ #Ok : Nat; #Err : ICRC2Interface.TransferFromError }] { - return await* WrappedIcrc2Actor.wrapped_icrc2_transfer_from_batch( + return await* ICRCCall.wrapped_icrc2_transfer_from_batch( icrc2Actor, batchSize, transfers, - WrappedIcrc2Actor.icrc2_transfer_from ); }; }; \ No newline at end of file diff --git a/test/WrappedICRC2Actor.test.mo b/test/ICRCCall.test.mo similarity index 72% rename from test/WrappedICRC2Actor.test.mo rename to test/ICRCCall.test.mo index f64eec5..cf8bc18 100644 --- a/test/WrappedICRC2Actor.test.mo +++ b/test/ICRCCall.test.mo @@ -2,7 +2,7 @@ import Option "mo:base/Option"; import { it; its; itsp; describe; Suite } "mo:testing/SuiteState"; import { principalFromText } = "TestUtils"; import ICRC2Interface "../src/ICRC2Interface"; -import WrappedIcrc2Actor "../internal/WrappedICRC2Actor"; +import ICRCCall "../internal/ICRCCall"; let customer1 = principalFromText("customer1"); let customer2 = principalFromText("customer2"); @@ -33,7 +33,7 @@ await* s.run([ }; }; - let result = await* WrappedIcrc2Actor.icrc2_allowance( + let result = await* ICRCCall.icrc2_allowance( icrc2Actor, { account = { owner = customer1; subaccount = null }; @@ -78,7 +78,7 @@ await* s.run([ }; }; - let result = await* WrappedIcrc2Actor.icrc2_approve( + let result = await* ICRCCall.icrc2_approve( icrc2Actor, { fee = ?10; @@ -129,7 +129,7 @@ await* s.run([ }; }; - let result = await* WrappedIcrc2Actor.icrc2_transfer_from( + let result = await* ICRCCall.icrc2_transfer_from( icrc2Actor, { to = { owner = customer1; subaccount = null }; @@ -161,9 +161,21 @@ await* s.run([ var areFirstCallArgsCorrect = false; var areSecondCallArgsCorrect = false; let icrc2Actor : ICRC2Interface.ICRC2Actor = actor { + var i = 0; public shared func icrc2_allowance({ account; spender }: ICRC2Interface.AllowanceArgs) : async ICRC2Interface.Allowance { // This should never get called - return { allowance = 50; expires_at = null }; + // each time this is called, increments i + i += 1; + if (i == 1) { + areFirstCallArgsCorrect := + account.owner == customer1 and Option.isNull(account.subaccount) and + spender.owner == customer2 and Option.isNull(spender.subaccount); + } else if (i == 2) { + areSecondCallArgsCorrect := + account.owner == customer2 and Option.isNull(account.subaccount) and + spender.owner == customer1 and Option.isNull(spender.subaccount); + }; + { allowance = 100 * i; expires_at = null }; }; public shared func icrc2_approve(args: ICRC2Interface.ApproveArgs) : async { #Ok : Nat; #Err : ICRC2Interface.ApproveError } { return #Ok(0); @@ -173,23 +185,7 @@ await* s.run([ }; }; - var i = 0; - func allowanceFunction(icrc2Actor: ICRC2Interface.ICRC2Actor, args: ICRC2Interface.AllowanceArgs) : async* ICRC2Interface.Allowance { - // each time this is called, increments i - i += 1; - if (i == 1) { - areFirstCallArgsCorrect := - args.account.owner == customer1 and Option.isNull(args.account.subaccount) and - args.spender.owner == customer2 and Option.isNull(args.spender.subaccount); - } else if (i == 2) { - areSecondCallArgsCorrect := - args.account.owner == customer2 and Option.isNull(args.account.subaccount) and - args.spender.owner == customer1 and Option.isNull(args.spender.subaccount); - }; - { allowance = 100 * i; expires_at = null }; - }; - - let allowances = await* WrappedIcrc2Actor.wrapped_icrc2_allowance_batch( + let allowances = await* ICRCCall.wrapped_icrc2_allowance_batch( icrc2Actor, 2, [ @@ -202,7 +198,7 @@ await* s.run([ spender = { owner = customer1; subaccount = null }; } ], - allowanceFunction + //allowanceFunction ); if (allowances != [{ allowance = 100; expires_at = null }, { allowance = 200; expires_at = null }]) { @@ -224,47 +220,42 @@ await* s.run([ var areFirstCallArgsCorrect = false; var areSecondCallArgsCorrect = false; let icrc2Actor : ICRC2Interface.ICRC2Actor = actor { + var i = 0; public shared func icrc2_allowance({ account; spender }: ICRC2Interface.AllowanceArgs) : async ICRC2Interface.Allowance { return { allowance = 100; expires_at = null }; }; public shared func icrc2_approve(args: ICRC2Interface.ApproveArgs) : async { #Ok : Nat; #Err : ICRC2Interface.ApproveError } { - // This should never get called - return #Ok(0); + // each time this is called, increments i + i += 1; + if (i == 1) { + areFirstCallArgsCorrect := + args.fee == ?10 and + args.memo == null and + args.from_subaccount == null and + args.amount == 100 and + args.expected_allowance == ?500 and + args.expires_at == ?1_000_000 and + args.spender.owner == customer2 and Option.isNull(args.spender.subaccount); + return #Ok(1); + } else if (i == 2) { + areSecondCallArgsCorrect := + args.fee == ?20 and + args.memo == null and + args.from_subaccount == null and + args.amount == 200 and + args.expected_allowance == ?600 and + args.expires_at == ?2_000_000 and + args.spender.owner == customer1 and Option.isNull(args.spender.subaccount); + return #Err(#TemporarilyUnavailable); + }; + #Ok(0); }; public shared func icrc2_transfer_from(args: ICRC2Interface.TransferFromArgs) : async { #Ok : Nat; #Err : ICRC2Interface.TransferFromError } { return #Ok(0); }; }; - var i = 0; - func approveFunction(icrc2Actor: ICRC2Interface.ICRC2Actor, args: ICRC2Interface.ApproveArgs) : async* { #Ok : Nat; #Err : ICRC2Interface.ApproveError } { - // each time this is called, increments i - i += 1; - if (i == 1) { - areFirstCallArgsCorrect := - args.fee == ?10 and - args.memo == null and - args.from_subaccount == null and - args.amount == 100 and - args.expected_allowance == ?500 and - args.expires_at == ?1_000_000 and - args.spender.owner == customer2 and Option.isNull(args.spender.subaccount); - return #Ok(1); - } else if (i == 2) { - areSecondCallArgsCorrect := - args.fee == ?20 and - args.memo == null and - args.from_subaccount == null and - args.amount == 200 and - args.expected_allowance == ?600 and - args.expires_at == ?2_000_000 and - args.spender.owner == customer1 and Option.isNull(args.spender.subaccount); - return #Err(#TemporarilyUnavailable); - }; - #Ok(0); - }; - - let approvals = await* WrappedIcrc2Actor.wrapped_icrc2_approve_batch( + let approvals = await* ICRCCall.wrapped_icrc2_approve_batch( icrc2Actor, 2, [ @@ -289,7 +280,7 @@ await* s.run([ spender = { owner = customer1; subaccount = null }; } ], - approveFunction + //approveFunction ); if (approvals != [#Ok(1), #Err(#TemporarilyUnavailable)]) { @@ -311,6 +302,7 @@ await* s.run([ var areFirstCallArgsCorrect = false; var areSecondCallArgsCorrect = false; let icrc2Actor : ICRC2Interface.ICRC2Actor = actor { + var i = 0; public shared func icrc2_allowance({ account; spender }: ICRC2Interface.AllowanceArgs) : async ICRC2Interface.Allowance { return { allowance = 100; expires_at = null }; }; @@ -318,40 +310,34 @@ await* s.run([ return #Ok(0); }; public shared func icrc2_transfer_from(args: ICRC2Interface.TransferFromArgs) : async { #Ok : Nat; #Err : ICRC2Interface.TransferFromError } { - // This should never get called - return #Ok(0); - }; - }; - - var i = 0; - func transferFunction(icrc2Actor: ICRC2Interface.ICRC2Actor, args: ICRC2Interface.TransferFromArgs) : async* { #Ok : Nat; #Err : ICRC2Interface.TransferFromError } { - // each time this is called, increments i - i += 1; - if (i == 1) { - areFirstCallArgsCorrect := - args.to.owner == customer1 and Option.isNull(args.to.subaccount) and - args.fee == ?10 and - args.spender_subaccount == null and - args.from.owner == customer2 and Option.isNull(args.from.subaccount) and - args.memo == null and - args.created_at_time == ?1_000_000 and - args.amount == 100; - return #Ok(1); - } else if (i == 2) { - areSecondCallArgsCorrect := - args.to.owner == customer2 and Option.isNull(args.to.subaccount) and - args.fee == ?20 and - args.spender_subaccount == null and - args.from.owner == customer1 and Option.isNull(args.from.subaccount) and - args.memo == null and - args.created_at_time == ?2_000_000 and - args.amount == 200; - return #Err(#InsufficientAllowance({ allowance = 100 })) + // each time this is called, increments i + i += 1; + if (i == 1) { + areFirstCallArgsCorrect := + args.to.owner == customer1 and Option.isNull(args.to.subaccount) and + args.fee == ?10 and + args.spender_subaccount == null and + args.from.owner == customer2 and Option.isNull(args.from.subaccount) and + args.memo == null and + args.created_at_time == ?1_000_000 and + args.amount == 100; + return #Ok(1); + } else if (i == 2) { + areSecondCallArgsCorrect := + args.to.owner == customer2 and Option.isNull(args.to.subaccount) and + args.fee == ?20 and + args.spender_subaccount == null and + args.from.owner == customer1 and Option.isNull(args.from.subaccount) and + args.memo == null and + args.created_at_time == ?2_000_000 and + args.amount == 200; + return #Err(#InsufficientAllowance({ allowance = 100 })) + }; + #Ok(0); }; - #Ok(0); }; - let transfers = await* WrappedIcrc2Actor.wrapped_icrc2_transfer_from_batch( + let transfers = await* ICRCCall.wrapped_icrc2_transfer_from_batch( icrc2Actor, 2, [ @@ -374,7 +360,7 @@ await* s.run([ amount = 200; } ], - transferFunction + //transferFunction ); if (transfers != [#Ok(1), #Err(#InsufficientAllowance({ allowance = 100 }))]) {