Skip to content

Commit 052a035

Browse files
feat(order): add allowDeposit option to matchOrders
Add allowDeposit option to automatically deposit nRLC from wallet to account when account balance is insufficient during order matching. When set to true, automatically deposits nRLC from wallet to account and executes matchOrders in a single transaction using approveAndCall. - Add allowDeposit parameter to matchOrders method - Implement automatic deposit of only missing amount via approveAndCall - Add tests for allowDeposit scenarios including insufficient wallet balance - Update documentation with concise description
1 parent 411be68 commit 052a035

File tree

8 files changed

+432
-1166
lines changed

8 files changed

+432
-1166
lines changed

CLI.md

Lines changed: 57 additions & 1060 deletions
Large diffs are not rendered by default.

docs/classes/IExecOrderModule.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -497,6 +497,7 @@ console.log(`created deal ${dealid} in tx ${txHash}`);
497497
| `orders.requestorder` | [`ConsumableRequestorder`](../interfaces/internal_.ConsumableRequestorder.md) | - |
498498
| `orders.workerpoolorder` | [`ConsumableWorkerpoolorder`](../interfaces/internal_.ConsumableWorkerpoolorder.md) | - |
499499
| `options?` | `Object` | - |
500+
| `options.allowDeposit?` | `boolean` | allow automatic deposit from wallet when account balance is insufficient when `true`, automatically deposits nRLC from wallet to account and executes matchOrders in a single transaction using `approveAndCall` _NB_: the requester's wallet must have sufficient nRLC balance before matching orders |
500501
| `options.preflightCheck?` | `boolean` | - |
501502
| `options.useVoucher?` | `boolean` | use a voucher contract to sponsor the deal |
502503
| `options.voucherAddress?` | `string` | override the voucher contract to use, must be combined with `useVoucher: true` the user must be authorized by the voucher's owner to use it |

src/common/market/order.js

Lines changed: 91 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
import Debug from 'debug';
21
import BN from 'bn.js';
3-
import { getAddress } from '../wallet/address.js';
2+
import Debug from 'debug';
3+
import { checkAllowance } from '../account/allowance.js';
44
import { checkBalance } from '../account/balance.js';
5+
import { createObjParams } from '../execution/order-helper.js';
56
import {
67
checkDeployedApp,
78
checkDeployedDataset,
@@ -10,63 +11,63 @@ import {
1011
getDatasetOwner,
1112
getWorkerpoolOwner,
1213
} from '../protocol/registries.js';
13-
import { createObjParams } from '../execution/order-helper.js';
1414
import {
15-
checkEventFromLogs,
16-
bigIntToBn,
17-
getSalt,
18-
sumTags,
19-
findMissingBitsInTag,
20-
checkActiveBitInTag,
21-
tagBitToHuman,
22-
checkSigner,
23-
TAG_MAP,
24-
parseTransactionLogs,
25-
} from '../utils/utils.js';
26-
import { hashEIP712 } from '../utils/sig-utils.js';
15+
CHAIN_SPECIFIC_FEATURES,
16+
checkImplementedOnChain,
17+
} from '../utils/config.js';
2718
import {
28-
NULL_BYTES,
29-
NULL_BYTES32,
30-
NULL_ADDRESS,
3119
APP_ORDER,
3220
DATASET_ORDER,
33-
WORKERPOOL_ORDER,
34-
REQUEST_ORDER,
21+
NULL_ADDRESS,
22+
NULL_BYTES,
23+
NULL_BYTES32,
3524
NULL_DATASETORDER,
25+
REQUEST_ORDER,
26+
WORKERPOOL_ORDER,
3627
} from '../utils/constant.js';
28+
import {
29+
wrapCall,
30+
wrapSend,
31+
wrapSignTypedData,
32+
wrapWait,
33+
} from '../utils/errorWrappers.js';
34+
import { hashEIP712 } from '../utils/sig-utils.js';
35+
import {
36+
bigIntToBn,
37+
checkActiveBitInTag,
38+
checkEventFromLogs,
39+
checkSigner,
40+
encodeMatchOrders,
41+
findMissingBitsInTag,
42+
getSalt,
43+
parseTransactionLogs,
44+
sumTags,
45+
TAG_MAP,
46+
tagBitToHuman,
47+
} from '../utils/utils.js';
3748
import {
3849
addressSchema,
3950
apporderSchema,
51+
booleanSchema,
4052
datasetorderSchema,
41-
workerpoolorderSchema,
53+
nRlcAmountSchema,
4254
requestorderSchema,
4355
saltedApporderSchema,
4456
saltedDatasetorderSchema,
45-
saltedWorkerpoolorderSchema,
4657
saltedRequestorderSchema,
58+
saltedWorkerpoolorderSchema,
4759
signedApporderSchema,
4860
signedDatasetorderSchema,
49-
signedWorkerpoolorderSchema,
5061
signedRequestorderSchema,
62+
signedWorkerpoolorderSchema,
5163
tagSchema,
52-
uint256Schema,
53-
nRlcAmountSchema,
5464
throwIfMissing,
55-
booleanSchema,
65+
uint256Schema,
66+
workerpoolorderSchema,
5667
} from '../utils/validator.js';
57-
import {
58-
wrapCall,
59-
wrapSend,
60-
wrapWait,
61-
wrapSignTypedData,
62-
} from '../utils/errorWrappers.js';
6368
import { getVoucherHubContract } from '../utils/voucher-utils.js';
64-
import { checkAllowance } from '../account/allowance.js';
6569
import { fetchVoucherContract } from '../voucher/voucher.js';
66-
import {
67-
CHAIN_SPECIFIC_FEATURES,
68-
checkImplementedOnChain,
69-
} from '../utils/config.js';
70+
import { getAddress } from '../wallet/address.js';
7071

7172
const debug = Debug('iexec:market:order');
7273

@@ -916,6 +917,7 @@ export const matchOrders = async ({
916917
requestorder,
917918
useVoucher = false,
918919
voucherAddress,
920+
allowDeposit = false,
919921
}) => {
920922
try {
921923
checkSigner(contracts);
@@ -1022,19 +1024,28 @@ export const matchOrders = async ({
10221024
}
10231025
} else {
10241026
if (stake.lt(costPerTask)) {
1027+
if (allowDeposit) {
1028+
// Will handle deposit via approveAndCall
1029+
return { insufficient: true, totalCost };
1030+
}
10251031
throw new Error(
10261032
`Cost per task (${costPerTask}) is greater than requester account stake (${stake}). Orders can't be matched. If you are the requester, you should deposit to top up your account`,
10271033
);
10281034
}
10291035
if (stake.lt(totalCost)) {
1036+
if (allowDeposit) {
1037+
// Will handle deposit via approveAndCall
1038+
return { insufficient: true, totalCost };
1039+
}
10301040
throw new Error(
10311041
`Total cost for ${matchableVolume} tasks (${totalCost}) is greater than requester account stake (${stake}). Orders can't be matched. If you are the requester, you should deposit to top up your account or reduce your requestorder volume`,
10321042
);
10331043
}
10341044
}
1045+
return { insufficient: false };
10351046
};
10361047

1037-
await checkRequesterSolvabilityAsync();
1048+
const solvabilityCheck = await checkRequesterSolvabilityAsync();
10381049

10391050
const appOrderStruct = signedOrderToStruct(APP_ORDER, vAppOrder);
10401051
const datasetOrderStruct = signedOrderToStruct(
@@ -1064,15 +1075,52 @@ export const matchOrders = async ({
10641075
),
10651076
);
10661077
} else {
1067-
tx = await wrapSend(
1068-
iexecContract.matchOrders(
1078+
if (solvabilityCheck.insufficient && allowDeposit) {
1079+
// Balance is insufficient, use approveAndCall with encoded orders
1080+
// This will automatically deposit RLC from wallet to account and execute matchOrders
1081+
if (contracts.isNative) {
1082+
throw new Error(
1083+
'allowDeposit is not supported on native chains. Please deposit manually before matching orders.',
1084+
);
1085+
}
1086+
const { stake } = await checkBalance(
1087+
contracts,
1088+
vRequestOrder.requester,
1089+
);
1090+
1091+
// pass the missing amount to the approveAndCall
1092+
const missingAmount = solvabilityCheck.totalCost.sub(stake);
1093+
const encodedMatchOrders = await encodeMatchOrders(
1094+
contracts,
10691095
appOrderStruct,
10701096
datasetOrderStruct,
10711097
workerpoolOrderStruct,
10721098
requestOrderStruct,
1073-
contracts.txOptions,
1074-
),
1075-
);
1099+
);
1100+
1101+
const rlcContract = await wrapCall(contracts.fetchTokenContract());
1102+
const rlcContractWithSigner = rlcContract.connect(contracts.signer);
1103+
1104+
tx = await wrapSend(
1105+
rlcContractWithSigner.approveAndCall(
1106+
contracts.hubAddress,
1107+
missingAmount.toString(),
1108+
encodedMatchOrders,
1109+
contracts.txOptions,
1110+
),
1111+
);
1112+
} else {
1113+
// Balance is sufficient or allowDeposit is false, proceed normally
1114+
tx = await wrapSend(
1115+
iexecContract.matchOrders(
1116+
appOrderStruct,
1117+
datasetOrderStruct,
1118+
workerpoolOrderStruct,
1119+
requestOrderStruct,
1120+
contracts.txOptions,
1121+
),
1122+
);
1123+
}
10761124
}
10771125
const txReceipt = await wrapWait(tx.wait(contracts.confirms));
10781126
const events = parseTransactionLogs(

src/common/utils/utils.js

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,20 @@
1-
import Debug from 'debug';
2-
import { Buffer } from 'buffer';
31
import { BN } from 'bn.js';
2+
import { Buffer } from 'buffer';
3+
import Debug from 'debug';
44
import {
5-
getAddress,
6-
randomBytes,
5+
AbiCoder,
76
formatUnits,
8-
parseUnits,
7+
getAddress,
98
hexlify,
9+
parseUnits,
10+
randomBytes,
1011
Result,
1112
} from 'ethers';
1213
// import-js/eslint-plugin-import/issues/2703
1314
// eslint-disable-next-line import/no-unresolved
1415
import { multiaddr } from '@multiformats/multiaddr';
15-
import { ValidationError, ConfigurationError } from './errors.js';
1616
import { NULL_BYTES32, TEE_FRAMEWORKS } from './constant.js';
17+
import { ConfigurationError, ValidationError } from './errors.js';
1718

1819
export { BN } from 'bn.js';
1920

@@ -329,4 +330,48 @@ export const checkSigner = (contracts) => {
329330
}
330331
};
331332

333+
export function encodeMatchOrders(
334+
contracts,
335+
appOrderStruct,
336+
datasetOrderStruct,
337+
workerpoolOrderStruct,
338+
requestOrderStruct,
339+
) {
340+
// These types match the typechain-generated structs in IexecLibOrders_v5
341+
// AppOrderStruct, DatasetOrderStruct, WorkerpoolOrderStruct, RequestOrderStruct
342+
// By using named tuple components, ethers can encode objects with named properties
343+
const appOrderType =
344+
'tuple(address app, uint256 appprice, uint256 volume, bytes32 tag, address datasetrestrict, address workerpoolrestrict, address requesterrestrict, bytes32 salt, bytes sign)';
345+
const datasetOrderType =
346+
'tuple(address dataset, uint256 datasetprice, uint256 volume, bytes32 tag, address apprestrict, address workerpoolrestrict, address requesterrestrict, bytes32 salt, bytes sign)';
347+
const workerpoolOrderType =
348+
'tuple(address workerpool, uint256 workerpoolprice, uint256 volume, bytes32 tag, uint256 category, uint256 trust, address apprestrict, address datasetrestrict, address requesterrestrict, bytes32 salt, bytes sign)';
349+
const requestOrderType =
350+
'tuple(address app, uint256 appmaxprice, address dataset, uint256 datasetmaxprice, address workerpool, uint256 workerpoolmaxprice, address requester, uint256 volume, bytes32 tag, uint256 category, uint256 trust, address beneficiary, address callback, string params, bytes32 salt, bytes sign)';
351+
352+
// Encode the function parameters (without selector)
353+
const encodedParams = AbiCoder.defaultAbiCoder().encode(
354+
[appOrderType, datasetOrderType, workerpoolOrderType, requestOrderType],
355+
[
356+
appOrderStruct,
357+
datasetOrderStruct,
358+
workerpoolOrderStruct,
359+
requestOrderStruct,
360+
],
361+
);
362+
363+
// Get the matchOrders function selector from the IExec contract interface
364+
const iexecContract = contracts.getIExecContract();
365+
const matchOrdersFunction =
366+
iexecContract.interface.getFunction('matchOrders');
367+
if (!matchOrdersFunction) {
368+
throw new Error(
369+
'matchOrders function not found in IExec contract interface',
370+
);
371+
}
372+
const matchOrdersSelector = matchOrdersFunction.selector;
373+
374+
// Return selector + encoded parameters (remove '0x' prefix from encodedParams)
375+
return matchOrdersSelector + encodedParams.slice(2);
376+
}
332377
export const FETCH_INTERVAL = 5000;

src/lib/IExecOrderModule.d.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1044,6 +1044,14 @@ export default class IExecOrderModule extends IExecModule {
10441044
* the user must be authorized by the voucher's owner to use it
10451045
*/
10461046
voucherAddress?: Addressish;
1047+
/**
1048+
* allow automatic deposit from wallet when account balance is insufficient
1049+
*
1050+
* when `true`, automatically deposits nRLC from wallet to account and executes matchOrders in a single transaction using `approveAndCall`
1051+
*
1052+
* _NB_: the requester's wallet must have sufficient nRLC balance before matching orders
1053+
*/
1054+
allowDeposit?: boolean;
10471055
},
10481056
): Promise<{ dealid: Dealid; volume: BN; txHash: TxHash }>;
10491057
/**

0 commit comments

Comments
 (0)