diff --git a/contracts/facets/IexecPoco1Facet.sol b/contracts/facets/IexecPoco1Facet.sol index d3281937..d23b2be9 100644 --- a/contracts/facets/IexecPoco1Facet.sol +++ b/contracts/facets/IexecPoco1Facet.sol @@ -68,35 +68,36 @@ contract IexecPoco1Facet is IexecPoco1, FacetBase, IexecEscrow, SignatureVerifie * * @param datasetOrder The dataset order to verify * @param dealid The deal ID to check against - * @return true if the dataset order is compatible with the deal, false otherwise + * @return result true if the dataset order is compatible with the deal, false otherwise + * @return reason the specific reason why the compatibility check failed, empty string if successful */ function isDatasetCompatibleWithDeal( IexecLibOrders_v5.DatasetOrder calldata datasetOrder, bytes32 dealid - ) external view override returns (bool) { + ) external view override returns (bool result, string memory reason) { PocoStorageLib.PocoStorage storage $ = PocoStorageLib.getPocoStorage(); // Check if deal exists IexecLibCore_v5.Deal storage deal = $.m_deals[dealid]; if (deal.requester == address(0)) { - return false; + return (false, "Deal does not exist"); } // The specified deal should not have a dataset. if (deal.dataset.pointer != address(0)) { - return false; + return (false, "Deal already has a dataset"); } // Check dataset order owner signature (including presign and EIP1271) bytes32 datasetOrderHash = _toTypedDataHash(datasetOrder.hash()); address datasetOwner = IERC5313(datasetOrder.dataset).owner(); if (!_verifySignatureOrPresignature(datasetOwner, datasetOrderHash, datasetOrder.sign)) { - return false; + return (false, "Invalid dataset order signature"); } // Check if dataset order is not fully consumed if ($.m_consumed[datasetOrderHash] >= datasetOrder.volume) { - return false; + return (false, "Dataset order is fully consumed"); } // Check if deal app is allowed by dataset order apprestrict (including whitelist) if (!_isAccountAuthorizedByRestriction(datasetOrder.apprestrict, deal.app.pointer)) { - return false; + return (false, "App restriction not satisfied"); } // Check if deal workerpool is allowed by dataset order workerpoolrestrict (including whitelist) if ( @@ -105,17 +106,17 @@ contract IexecPoco1Facet is IexecPoco1, FacetBase, IexecEscrow, SignatureVerifie deal.workerpool.pointer ) ) { - return false; + return (false, "Workerpool restriction not satisfied"); } // Check if deal requester is allowed by dataset order requesterrestrict (including whitelist) if (!_isAccountAuthorizedByRestriction(datasetOrder.requesterrestrict, deal.requester)) { - return false; + return (false, "Requester restriction not satisfied"); } // Check if deal tag fulfills all the tag bits of the dataset order if ((deal.tag & datasetOrder.tag) != datasetOrder.tag) { - return false; + return (false, "Tag compatibility not satisfied"); } - return true; + return (true, ""); } /*************************************************************************** diff --git a/contracts/interfaces/IexecPoco1.sol b/contracts/interfaces/IexecPoco1.sol index 0e2eaca7..9621438a 100644 --- a/contracts/interfaces/IexecPoco1.sol +++ b/contracts/interfaces/IexecPoco1.sol @@ -50,5 +50,5 @@ interface IexecPoco1 { function isDatasetCompatibleWithDeal( IexecLibOrders_v5.DatasetOrder calldata datasetOrder, bytes32 dealid - ) external view returns (bool); + ) external view returns (bool result, string memory reason); } diff --git a/contracts/interfaces/IexecPoco1.v8.sol b/contracts/interfaces/IexecPoco1.v8.sol index bde6885c..30466dd3 100644 --- a/contracts/interfaces/IexecPoco1.v8.sol +++ b/contracts/interfaces/IexecPoco1.v8.sol @@ -44,5 +44,5 @@ interface IexecPoco1 { function isDatasetCompatibleWithDeal( IexecLibOrders_v5.DatasetOrder calldata datasetOrder, bytes32 dealid - ) external view returns (bool); + ) external view returns (bool result, string memory reason); } diff --git a/docs/solidity/index.md b/docs/solidity/index.md index 6e23a123..52f59ab4 100644 --- a/docs/solidity/index.md +++ b/docs/solidity/index.md @@ -74,7 +74,7 @@ function verifyPresignatureOrSignature(address _identity, bytes32 _hash, bytes _ ### isDatasetCompatibleWithDeal ```solidity -function isDatasetCompatibleWithDeal(struct IexecLibOrders_v5.DatasetOrder datasetOrder, bytes32 dealid) external view returns (bool) +function isDatasetCompatibleWithDeal(struct IexecLibOrders_v5.DatasetOrder datasetOrder, bytes32 dealid) external view returns (bool result, string reason) ``` Public view function to check if a dataset order is compatible with a deal. @@ -94,7 +94,8 @@ This function should not be used in matchOrders as it does not check the same re | Name | Type | Description | | ---- | ---- | ----------- | -| [0] | bool | true if the dataset order is compatible with the deal, false otherwise | +| result | bool | true if the dataset order is compatible with the deal, false otherwise | +| reason | string | the specific reason why the compatibility check failed, empty string if successful | ### matchOrders @@ -456,6 +457,115 @@ function groupmember_purpose() external pure returns (uint256) function eip712domain_separator() external view returns (bytes32) ``` +## IexecPocoBoostAccessorsFacet + +Access to PoCo Boost tasks must be done with PoCo Classic `IexecAccessors`. + +### viewDealBoost + +```solidity +function viewDealBoost(bytes32 id) external view returns (struct IexecLibCore_v5.DealBoost deal) +``` + +Get a deal created by PoCo Boost facet. + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| id | bytes32 | The ID of the deal. | + +## IexecPocoBoostFacet + +Works for deals with requested trust = 0. + +### matchOrdersBoost + +```solidity +function matchOrdersBoost(struct IexecLibOrders_v5.AppOrder appOrder, struct IexecLibOrders_v5.DatasetOrder datasetOrder, struct IexecLibOrders_v5.WorkerpoolOrder workerpoolOrder, struct IexecLibOrders_v5.RequestOrder requestOrder) external returns (bytes32) +``` + +This boost match orders is only compatible with trust <= 1. +The requester gets debited. + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| appOrder | struct IexecLibOrders_v5.AppOrder | The order signed by the application developer. | +| datasetOrder | struct IexecLibOrders_v5.DatasetOrder | The order signed by the dataset provider. | +| workerpoolOrder | struct IexecLibOrders_v5.WorkerpoolOrder | The order signed by the workerpool manager. | +| requestOrder | struct IexecLibOrders_v5.RequestOrder | The order signed by the requester. | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| [0] | bytes32 | The ID of the deal. | + +### sponsorMatchOrdersBoost + +```solidity +function sponsorMatchOrdersBoost(struct IexecLibOrders_v5.AppOrder appOrder, struct IexecLibOrders_v5.DatasetOrder datasetOrder, struct IexecLibOrders_v5.WorkerpoolOrder workerpoolOrder, struct IexecLibOrders_v5.RequestOrder requestOrder) external returns (bytes32) +``` + +Sponsor match orders boost for a requester. +Unlike the standard `matchOrdersBoost(..)` hook where the requester pays for +the deal, this current hook makes it possible for any `msg.sender` to pay for +a third party requester. + +Be aware that anyone seeing a valid request order on the network +(via an off-chain public marketplace, via a `sponsorMatchOrdersBoost(..)` +pending transaction in the mempool or by any other means) might decide +to call the standard `matchOrdersBoost(..)` hook which will result in the +requester being debited instead. Therefore, such a front run would result +in a loss of some of the requester funds deposited in the iExec account +(a loss value equivalent to the price of the deal). + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| appOrder | struct IexecLibOrders_v5.AppOrder | The app order. | +| datasetOrder | struct IexecLibOrders_v5.DatasetOrder | The dataset order. | +| workerpoolOrder | struct IexecLibOrders_v5.WorkerpoolOrder | The workerpool order. | +| requestOrder | struct IexecLibOrders_v5.RequestOrder | The requester order. | + +### pushResultBoost + +```solidity +function pushResultBoost(bytes32 dealId, uint256 index, bytes results, bytes resultsCallback, bytes authorizationSign, address enclaveChallenge, bytes enclaveSign) external +``` + +Accept results of a task computed by a worker during Boost workflow. + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| dealId | bytes32 | The id of the target deal. | +| index | uint256 | The index of the target task of the deal. | +| results | bytes | The results of the task computed by the worker. | +| resultsCallback | bytes | The results of the task computed by the worker that will be forwarded as call data to the callback address set by the requester. | +| authorizationSign | bytes | The authorization signed by the scheduler. authorizing the worker to push a result. | +| enclaveChallenge | address | The enclave address which can produce enclave signature. | +| enclaveSign | bytes | The signature generated from the enclave. | + +### claimBoost + +```solidity +function claimBoost(bytes32 dealId, uint256 index) external +``` + +Claim task to get a refund if task is not completed after deadline. + +#### Parameters + +| Name | Type | Description | +| ---- | ---- | ----------- | +| dealId | bytes32 | The ID of the deal. | +| index | uint256 | The index of the task. | + ## IexecLibCore_v5 ### Account @@ -1651,112 +1761,3 @@ function manageWorkerpoolOrder(struct IexecLibOrders_v5.WorkerpoolOrderOperation function manageRequestOrder(struct IexecLibOrders_v5.RequestOrderOperation _requestorderoperation) external ``` -## IexecPocoBoostAccessorsFacet - -Access to PoCo Boost tasks must be done with PoCo Classic `IexecAccessors`. - -### viewDealBoost - -```solidity -function viewDealBoost(bytes32 id) external view returns (struct IexecLibCore_v5.DealBoost deal) -``` - -Get a deal created by PoCo Boost facet. - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| id | bytes32 | The ID of the deal. | - -## IexecPocoBoostFacet - -Works for deals with requested trust = 0. - -### matchOrdersBoost - -```solidity -function matchOrdersBoost(struct IexecLibOrders_v5.AppOrder appOrder, struct IexecLibOrders_v5.DatasetOrder datasetOrder, struct IexecLibOrders_v5.WorkerpoolOrder workerpoolOrder, struct IexecLibOrders_v5.RequestOrder requestOrder) external returns (bytes32) -``` - -This boost match orders is only compatible with trust <= 1. -The requester gets debited. - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| appOrder | struct IexecLibOrders_v5.AppOrder | The order signed by the application developer. | -| datasetOrder | struct IexecLibOrders_v5.DatasetOrder | The order signed by the dataset provider. | -| workerpoolOrder | struct IexecLibOrders_v5.WorkerpoolOrder | The order signed by the workerpool manager. | -| requestOrder | struct IexecLibOrders_v5.RequestOrder | The order signed by the requester. | - -#### Return Values - -| Name | Type | Description | -| ---- | ---- | ----------- | -| [0] | bytes32 | The ID of the deal. | - -### sponsorMatchOrdersBoost - -```solidity -function sponsorMatchOrdersBoost(struct IexecLibOrders_v5.AppOrder appOrder, struct IexecLibOrders_v5.DatasetOrder datasetOrder, struct IexecLibOrders_v5.WorkerpoolOrder workerpoolOrder, struct IexecLibOrders_v5.RequestOrder requestOrder) external returns (bytes32) -``` - -Sponsor match orders boost for a requester. -Unlike the standard `matchOrdersBoost(..)` hook where the requester pays for -the deal, this current hook makes it possible for any `msg.sender` to pay for -a third party requester. - -Be aware that anyone seeing a valid request order on the network -(via an off-chain public marketplace, via a `sponsorMatchOrdersBoost(..)` -pending transaction in the mempool or by any other means) might decide -to call the standard `matchOrdersBoost(..)` hook which will result in the -requester being debited instead. Therefore, such a front run would result -in a loss of some of the requester funds deposited in the iExec account -(a loss value equivalent to the price of the deal). - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| appOrder | struct IexecLibOrders_v5.AppOrder | The app order. | -| datasetOrder | struct IexecLibOrders_v5.DatasetOrder | The dataset order. | -| workerpoolOrder | struct IexecLibOrders_v5.WorkerpoolOrder | The workerpool order. | -| requestOrder | struct IexecLibOrders_v5.RequestOrder | The requester order. | - -### pushResultBoost - -```solidity -function pushResultBoost(bytes32 dealId, uint256 index, bytes results, bytes resultsCallback, bytes authorizationSign, address enclaveChallenge, bytes enclaveSign) external -``` - -Accept results of a task computed by a worker during Boost workflow. - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| dealId | bytes32 | The id of the target deal. | -| index | uint256 | The index of the target task of the deal. | -| results | bytes | The results of the task computed by the worker. | -| resultsCallback | bytes | The results of the task computed by the worker that will be forwarded as call data to the callback address set by the requester. | -| authorizationSign | bytes | The authorization signed by the scheduler. authorizing the worker to push a result. | -| enclaveChallenge | address | The enclave address which can produce enclave signature. | -| enclaveSign | bytes | The signature generated from the enclave. | - -### claimBoost - -```solidity -function claimBoost(bytes32 dealId, uint256 index) external -``` - -Claim task to get a refund if task is not completed after deadline. - -#### Parameters - -| Name | Type | Description | -| ---- | ---- | ----------- | -| dealId | bytes32 | The ID of the deal. | -| index | uint256 | The index of the task. | - diff --git a/test/byContract/IexecPoco/IexecPoco1.test.ts b/test/byContract/IexecPoco/IexecPoco1.test.ts index 8fe23554..f01ebb2b 100644 --- a/test/byContract/IexecPoco/IexecPoco1.test.ts +++ b/test/byContract/IexecPoco/IexecPoco1.test.ts @@ -330,7 +330,7 @@ describe('IexecPoco1', () => { it(`Should fail to ${verifyPresignatureFunction} for an unknown messageHash for ${asset}`, async () => { const { providerAddress } = orderManagement[asset]; - const unknownMessageHash = ethers.keccak256(ethers.toUtf8Bytes('unknown')); + const unknownMessageHash = ethers.id('unknown'); const args = [ providerAddress, @@ -1117,20 +1117,20 @@ describe('IexecPoco1', () => { compatibleDatasetOrder, dealIdWithoutDataset, ), - ).to.be.true; + ).to.deep.equal([true, '']); }); - it('Should return false for non-existent deal', async () => { + it('Should return false with reason for non-existent deal', async () => { const nonExistentDealId = ethers.id('non-existent-deal'); expect( await iexecPoco.isDatasetCompatibleWithDeal( compatibleDatasetOrder, nonExistentDealId, ), - ).to.be.false; + ).to.deep.equal([false, 'Deal does not exist']); }); - it('Should return false for deal with a dataset', async () => { + it('Should return false with reason for deal with a dataset', async () => { // Use the original orders that include a dataset to create a deal with dataset const ordersWithDataset = buildOrders({ assets: ordersAssets, // This includes the dataset @@ -1142,15 +1142,9 @@ describe('IexecPoco1', () => { // Use fresh salts to avoid order consumption conflicts ordersWithDataset.app.salt = ethers.id('fresh-app-salt'); - ordersWithDataset.dataset.salt = ethers.keccak256( - ethers.toUtf8Bytes('fresh-dataset-salt'), - ); - ordersWithDataset.workerpool.salt = ethers.keccak256( - ethers.toUtf8Bytes('fresh-workerpool-salt'), - ); - ordersWithDataset.requester.salt = ethers.keccak256( - ethers.toUtf8Bytes('fresh-requester-salt'), - ); + ordersWithDataset.dataset.salt = ethers.id('fresh-dataset-salt'); + ordersWithDataset.workerpool.salt = ethers.id('fresh-workerpool-salt'); + ordersWithDataset.requester.salt = ethers.id('fresh-requester-salt'); await depositForRequesterAndSchedulerWithDefaultPrices(volume); await signOrders(iexecWrapper.getDomain(), ordersWithDataset, ordersActors); @@ -1165,10 +1159,10 @@ describe('IexecPoco1', () => { compatibleDatasetOrder, dealIdWithDataset, ), - ).to.be.false; + ).to.deep.equal([false, 'Deal already has a dataset']); }); - it('Should return false for dataset order with invalid signature', async () => { + it('Should return false with reason for dataset order with invalid signature', async () => { // Create dataset order with invalid signature const invalidSignatureDatasetOrder = { ...compatibleDatasetOrder, @@ -1180,10 +1174,10 @@ describe('IexecPoco1', () => { invalidSignatureDatasetOrder, dealIdWithoutDataset, ), - ).to.be.false; + ).to.deep.equal([false, 'Invalid dataset order signature']); }); - it('Should return false for fully consumed dataset order', async () => { + it('Should return false with reason for fully consumed dataset order', async () => { // Create dataset order with volume 0 (fully consumed) const consumedDatasetOrder = { ...compatibleDatasetOrder, @@ -1196,10 +1190,10 @@ describe('IexecPoco1', () => { consumedDatasetOrder, dealIdWithoutDataset, ), - ).to.be.false; + ).to.deep.equal([false, 'Dataset order is fully consumed']); }); - it('Should return false for dataset order with incompatible app restriction', async () => { + it('Should return false with reason for dataset order with incompatible app restriction', async () => { // Create dataset order with incompatible app restriction const incompatibleAppDatasetOrder = { ...compatibleDatasetOrder, @@ -1212,10 +1206,10 @@ describe('IexecPoco1', () => { incompatibleAppDatasetOrder, dealIdWithoutDataset, ), - ).to.be.false; + ).to.deep.equal([false, 'App restriction not satisfied']); }); - it('Should return false for dataset order with incompatible workerpool restriction', async () => { + it('Should return false with reason for dataset order with incompatible workerpool restriction', async () => { // Create dataset order with incompatible workerpool restriction const incompatibleWorkerpoolDatasetOrder = { ...compatibleDatasetOrder, @@ -1232,10 +1226,10 @@ describe('IexecPoco1', () => { incompatibleWorkerpoolDatasetOrder, dealIdWithoutDataset, ), - ).to.be.false; + ).to.deep.equal([false, 'Workerpool restriction not satisfied']); }); - it('Should return false for dataset order with incompatible requester restriction', async () => { + it('Should return false with reason for dataset order with incompatible requester restriction', async () => { // Create dataset order with incompatible requester restriction const incompatibleRequesterDatasetOrder = { ...compatibleDatasetOrder, @@ -1252,10 +1246,10 @@ describe('IexecPoco1', () => { incompatibleRequesterDatasetOrder, dealIdWithoutDataset, ), - ).to.be.false; + ).to.deep.equal([false, 'Requester restriction not satisfied']); }); - it('Should return false for dataset order with incompatible tag', async () => { + it('Should return false with reason for dataset order with incompatible tag', async () => { // Create dataset order with incompatible tag const incompatibleTagDatasetOrder = { ...compatibleDatasetOrder, @@ -1268,7 +1262,7 @@ describe('IexecPoco1', () => { incompatibleTagDatasetOrder, dealIdWithoutDataset, ), - ).to.be.false; + ).to.deep.equal([false, 'Tag compatibility not satisfied']); }); });