diff --git a/abis/contracts/IexecInterfaceNative.json b/abis/contracts/IexecInterfaceNative.json index f17de571..9d5ef746 100644 --- a/abis/contracts/IexecInterfaceNative.json +++ b/abis/contracts/IexecInterfaceNative.json @@ -1,4 +1,9 @@ [ + { + "inputs": [], + "name": "CallerMustBeRequester", + "type": "error" + }, { "inputs": [ { @@ -10,6 +15,11 @@ "name": "IncompatibleDatasetOrder", "type": "error" }, + { + "inputs": [], + "name": "TokenTransferFailed", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -1884,6 +1894,273 @@ "stateMutability": "payable", "type": "function" }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "app", + "type": "address" + }, + { + "internalType": "uint256", + "name": "appprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "datasetrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "workerpoolrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.AppOrder", + "name": "_apporder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "dataset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "datasetprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "apprestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "workerpoolrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.DatasetOrder", + "name": "_datasetorder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "workerpool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "workerpoolprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "category", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "trust", + "type": "uint256" + }, + { + "internalType": "address", + "name": "apprestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "datasetrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.WorkerpoolOrder", + "name": "_workerpoolorder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "app", + "type": "address" + }, + { + "internalType": "uint256", + "name": "appmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "dataset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "datasetmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "workerpool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "workerpoolmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "category", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "trust", + "type": "uint256" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "internalType": "address", + "name": "callback", + "type": "address" + }, + { + "internalType": "string", + "name": "params", + "type": "string" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.RequestOrder", + "name": "_requestorder", + "type": "tuple" + } + ], + "name": "depositAndMatchOrders", + "outputs": [ + { + "internalType": "bytes32", + "name": "dealId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -4396,4 +4673,4 @@ "stateMutability": "payable", "type": "receive" } -] \ No newline at end of file +] diff --git a/abis/contracts/IexecInterfaceToken.json b/abis/contracts/IexecInterfaceToken.json index 74edd3ea..2ab65168 100644 --- a/abis/contracts/IexecInterfaceToken.json +++ b/abis/contracts/IexecInterfaceToken.json @@ -1,4 +1,9 @@ [ + { + "inputs": [], + "name": "CallerMustBeRequester", + "type": "error" + }, { "inputs": [ { @@ -10,6 +15,11 @@ "name": "IncompatibleDatasetOrder", "type": "error" }, + { + "inputs": [], + "name": "TokenTransferFailed", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -1890,6 +1900,273 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "app", + "type": "address" + }, + { + "internalType": "uint256", + "name": "appprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "datasetrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "workerpoolrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.AppOrder", + "name": "_apporder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "dataset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "datasetprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "apprestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "workerpoolrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.DatasetOrder", + "name": "_datasetorder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "workerpool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "workerpoolprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "category", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "trust", + "type": "uint256" + }, + { + "internalType": "address", + "name": "apprestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "datasetrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.WorkerpoolOrder", + "name": "_workerpoolorder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "app", + "type": "address" + }, + { + "internalType": "uint256", + "name": "appmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "dataset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "datasetmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "workerpool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "workerpoolmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "category", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "trust", + "type": "uint256" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "internalType": "address", + "name": "callback", + "type": "address" + }, + { + "internalType": "string", + "name": "params", + "type": "string" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.RequestOrder", + "name": "_requestorder", + "type": "tuple" + } + ], + "name": "depositAndMatchOrders", + "outputs": [ + { + "internalType": "bytes32", + "name": "dealId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -4407,4 +4684,4 @@ "stateMutability": "payable", "type": "receive" } -] \ No newline at end of file +] diff --git a/abis/contracts/facets/IexecPoco1Facet.json b/abis/contracts/facets/IexecPoco1Facet.json index 8664e0e7..8d8c3f68 100644 --- a/abis/contracts/facets/IexecPoco1Facet.json +++ b/abis/contracts/facets/IexecPoco1Facet.json @@ -1,4 +1,9 @@ [ + { + "inputs": [], + "name": "CallerMustBeRequester", + "type": "error" + }, { "inputs": [ { @@ -10,6 +15,11 @@ "name": "IncompatibleDatasetOrder", "type": "error" }, + { + "inputs": [], + "name": "TokenTransferFailed", + "type": "error" + }, { "anonymous": false, "inputs": [ @@ -525,6 +535,273 @@ "type": "tuple" } ], + "name": "depositAndMatchOrders", + "outputs": [ + { + "internalType": "bytes32", + "name": "dealId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "app", + "type": "address" + }, + { + "internalType": "uint256", + "name": "appprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "datasetrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "workerpoolrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.AppOrder", + "name": "_apporder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "dataset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "datasetprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "apprestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "workerpoolrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.DatasetOrder", + "name": "_datasetorder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "workerpool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "workerpoolprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "category", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "trust", + "type": "uint256" + }, + { + "internalType": "address", + "name": "apprestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "datasetrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.WorkerpoolOrder", + "name": "_workerpoolorder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "app", + "type": "address" + }, + { + "internalType": "uint256", + "name": "appmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "dataset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "datasetmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "workerpool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "workerpoolmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "category", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "trust", + "type": "uint256" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "internalType": "address", + "name": "callback", + "type": "address" + }, + { + "internalType": "string", + "name": "params", + "type": "string" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.RequestOrder", + "name": "_requestorder", + "type": "tuple" + } + ], "name": "matchOrders", "outputs": [ { @@ -885,4 +1162,4 @@ "stateMutability": "view", "type": "function" } -] \ No newline at end of file +] diff --git a/abis/contracts/interfaces/IexecPoco1.json b/abis/contracts/interfaces/IexecPoco1.json index 2b7d7f02..17e5c9f5 100644 --- a/abis/contracts/interfaces/IexecPoco1.json +++ b/abis/contracts/interfaces/IexecPoco1.json @@ -145,6 +145,273 @@ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "components": [ + { + "internalType": "address", + "name": "app", + "type": "address" + }, + { + "internalType": "uint256", + "name": "appprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "datasetrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "workerpoolrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.AppOrder", + "name": "_apporder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "dataset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "datasetprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "address", + "name": "apprestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "workerpoolrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.DatasetOrder", + "name": "_datasetorder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "workerpool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "workerpoolprice", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "category", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "trust", + "type": "uint256" + }, + { + "internalType": "address", + "name": "apprestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "datasetrestrict", + "type": "address" + }, + { + "internalType": "address", + "name": "requesterrestrict", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.WorkerpoolOrder", + "name": "_workerpoolorder", + "type": "tuple" + }, + { + "components": [ + { + "internalType": "address", + "name": "app", + "type": "address" + }, + { + "internalType": "uint256", + "name": "appmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "dataset", + "type": "address" + }, + { + "internalType": "uint256", + "name": "datasetmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "workerpool", + "type": "address" + }, + { + "internalType": "uint256", + "name": "workerpoolmaxprice", + "type": "uint256" + }, + { + "internalType": "address", + "name": "requester", + "type": "address" + }, + { + "internalType": "uint256", + "name": "volume", + "type": "uint256" + }, + { + "internalType": "bytes32", + "name": "tag", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "category", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "trust", + "type": "uint256" + }, + { + "internalType": "address", + "name": "beneficiary", + "type": "address" + }, + { + "internalType": "address", + "name": "callback", + "type": "address" + }, + { + "internalType": "string", + "name": "params", + "type": "string" + }, + { + "internalType": "bytes32", + "name": "salt", + "type": "bytes32" + }, + { + "internalType": "bytes", + "name": "sign", + "type": "bytes" + } + ], + "internalType": "struct IexecLibOrders_v5.RequestOrder", + "name": "_requestorder", + "type": "tuple" + } + ], + "name": "depositAndMatchOrders", + "outputs": [ + { + "internalType": "bytes32", + "name": "dealId", + "type": "bytes32" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { @@ -761,4 +1028,4 @@ "stateMutability": "view", "type": "function" } -] \ No newline at end of file +] diff --git a/abis/contracts/interfaces/IexecPoco1Errors.json b/abis/contracts/interfaces/IexecPoco1Errors.json index 923c52f9..94d13996 100644 --- a/abis/contracts/interfaces/IexecPoco1Errors.json +++ b/abis/contracts/interfaces/IexecPoco1Errors.json @@ -1,4 +1,9 @@ [ + { + "inputs": [], + "name": "CallerMustBeRequester", + "type": "error" + }, { "inputs": [ { @@ -9,5 +14,10 @@ ], "name": "IncompatibleDatasetOrder", "type": "error" + }, + { + "inputs": [], + "name": "TokenTransferFailed", + "type": "error" } -] \ No newline at end of file +] diff --git a/contracts/IexecInterfaceToken.sol b/contracts/IexecInterfaceToken.sol index 2865622d..fa11f44c 100644 --- a/contracts/IexecInterfaceToken.sol +++ b/contracts/IexecInterfaceToken.sol @@ -28,9 +28,9 @@ import {IOwnable} from "./interfaces/IOwnable.sol"; * @dev Referenced in the SDK with the current path `contracts/IexecInterfaceToken.sol`. * Changing the name or the path would cause a breaking change in the SDK. */ +// TODO Remove interface `IexecAccessorsABILegacy` when it's not used in the middleware anymore. +// https://github.com/iExecBlockchainComputing/iexec-commons-poco/blob/819cd008/generateContractWrappers#L7 interface IexecInterfaceToken is - // TODO Remove this interface when it's not used in the middelware anymore. - // https://github.com/iExecBlockchainComputing/iexec-commons-poco/blob/819cd008/generateContractWrappers#L7 IexecAccessorsABILegacy, IexecCategoryManager, IexecConfiguration, diff --git a/contracts/facets/IexecPoco1Facet.sol b/contracts/facets/IexecPoco1Facet.sol index c1b4d00b..09871cf2 100644 --- a/contracts/facets/IexecPoco1Facet.sol +++ b/contracts/facets/IexecPoco1Facet.sol @@ -27,7 +27,14 @@ struct Matching { bool hasDataset; } -contract IexecPoco1Facet is IexecPoco1, IexecPoco1Errors, FacetBase, IexecEscrow, SignatureVerifier, IexecPocoCommon { +contract IexecPoco1Facet is + IexecPoco1, + IexecPoco1Errors, + FacetBase, + IexecEscrow, + SignatureVerifier, + IexecPocoCommon +{ using Math for uint256; using IexecLibOrders_v5 for IexecLibOrders_v5.AppOrder; using IexecLibOrders_v5 for IexecLibOrders_v5.DatasetOrder; @@ -143,7 +150,7 @@ contract IexecPoco1Facet is IexecPoco1, IexecPoco1Errors, FacetBase, IexecEscrow IexecLibOrders_v5.DatasetOrder calldata _datasetorder, IexecLibOrders_v5.WorkerpoolOrder calldata _workerpoolorder, IexecLibOrders_v5.RequestOrder calldata _requestorder - ) external override returns (bytes32) { + ) public override returns (bytes32) { return _matchOrders( _apporder, @@ -191,6 +198,59 @@ contract IexecPoco1Facet is IexecPoco1, IexecPoco1Errors, FacetBase, IexecEscrow return dealId; } + /** + * @notice Deposit RLC tokens and match orders in a single transaction + * @dev The requester (msg.sender) will be both the depositor and the sponsor of the deal + * @param _apporder The app order + * @param _datasetorder The dataset order + * @param _workerpoolorder The workerpool order + * @param _requestorder The request order + * @return dealId The ID of the created deal + */ + function depositAndMatchOrders( + IexecLibOrders_v5.AppOrder calldata _apporder, + IexecLibOrders_v5.DatasetOrder calldata _datasetorder, + IexecLibOrders_v5.WorkerpoolOrder calldata _workerpoolorder, + IexecLibOrders_v5.RequestOrder calldata _requestorder + ) external override returns (bytes32 dealId) { + if (_requestorder.requester != msg.sender) { + revert CallerMustBeRequester(); + } + + // Calculate required deal cost + bool hasDataset = _datasetorder.dataset != address(0); + uint256 volume = _computeDealVolume( + _apporder.volume, + _toTypedDataHash(_apporder.hash()), + hasDataset, + _datasetorder.volume, + _toTypedDataHash(_datasetorder.hash()), + _workerpoolorder.volume, + _toTypedDataHash(_workerpoolorder.hash()), + _requestorder.volume, + _toTypedDataHash(_requestorder.hash()) + ); + uint256 dealCost = volume * + (_apporder.appprice + + (hasDataset ? _datasetorder.datasetprice : 0) + + _workerpoolorder.workerpoolprice); + + PocoStorageLib.PocoStorage storage $ = PocoStorageLib.getPocoStorage(); + uint256 currentBalance = $.m_balances[msg.sender]; + + uint256 depositedAmount = 0; + if (currentBalance < dealCost) { + uint256 requiredDeposit = dealCost - currentBalance; + _depositTokens(msg.sender, requiredDeposit); + depositedAmount = requiredDeposit; + } + + // Match the orders with the requester as sponsor + dealId = matchOrders(_apporder, _datasetorder, _workerpoolorder, _requestorder); + + return dealId; + } + /** * Match orders and specify a sponsor in charge of paying for the deal. * @@ -450,4 +510,20 @@ contract IexecPoco1Facet is IexecPoco1, IexecPoco1Errors, FacetBase, IexecEscrow return dealid; } + + /** + * @notice Internal function to handle token deposits + * @dev This function handles the RLC token transfer and minting + * @param depositor The account making the deposit + * @param amount The amount to deposit + */ + function _depositTokens(address depositor, uint256 amount) private { + PocoStorageLib.PocoStorage storage $ = PocoStorageLib.getPocoStorage(); + if (!$.m_baseToken.transferFrom(depositor, address(this), amount)) { + revert TokenTransferFailed(); + } + $.m_balances[depositor] += amount; + $.m_totalSupply += amount; + emit Transfer(address(0), depositor, amount); + } } diff --git a/contracts/interfaces/IexecPoco1.sol b/contracts/interfaces/IexecPoco1.sol index 9840fb35..d1703b6e 100644 --- a/contracts/interfaces/IexecPoco1.sol +++ b/contracts/interfaces/IexecPoco1.sol @@ -46,4 +46,21 @@ interface IexecPoco1 { IexecLibOrders_v5.WorkerpoolOrder calldata, IexecLibOrders_v5.RequestOrder calldata ) external returns (bytes32); + + /** + * @notice Deposit RLC token in your iexec account and match orders in a single transaction + * @dev This function allows builders to deposit the required amount and match orders atomically, + * improving UX by eliminating the need for separate deposit transactions + * @param _apporder The app order + * @param _datasetorder The dataset order + * @param _workerpoolorder The workerpool order + * @param _requestorder The request order + * @return dealId The ID of the created deal + */ + function depositAndMatchOrders( + IexecLibOrders_v5.AppOrder calldata _apporder, + IexecLibOrders_v5.DatasetOrder calldata _datasetorder, + IexecLibOrders_v5.WorkerpoolOrder calldata _workerpoolorder, + IexecLibOrders_v5.RequestOrder calldata _requestorder + ) external returns (bytes32 dealId); } diff --git a/contracts/interfaces/IexecPoco1Errors.sol b/contracts/interfaces/IexecPoco1Errors.sol index 76cd70ab..0d960c7c 100644 --- a/contracts/interfaces/IexecPoco1Errors.sol +++ b/contracts/interfaces/IexecPoco1Errors.sol @@ -6,4 +6,14 @@ pragma solidity ^0.8.0; interface IexecPoco1Errors { // TODO put this in `IexecPoco1` when it is migrated to Solidity v8. error IncompatibleDatasetOrder(string reason); + + /** + * @notice Thrown when the caller is not the requester in the request order + */ + error CallerMustBeRequester(); + + /** + * @notice Thrown when the token transfer fails during deposit + */ + error TokenTransferFailed(); } diff --git a/docs/solidity/index.md b/docs/solidity/index.md index fb587fc8..fc8f9ca9 100644 --- a/docs/solidity/index.md +++ b/docs/solidity/index.md @@ -1,21 +1,5 @@ # Solidity API -## IexecInterfaceNative - -A global interface that aggregates all the interfaces needed to interact with -the PoCo contracts in native mode. - -_Referenced in the SDK with the current path `contracts/IexecInterfaceNative.sol`. -Changing the name or the path would cause a breaking change in the SDK._ - -## IexecInterfaceToken - -A global interface that aggregates all the interfaces needed to interact with -the PoCo contracts in token mode. - -_Referenced in the SDK with the current path `contracts/IexecInterfaceToken.sol`. -Changing the name or the path would cause a breaking change in the SDK._ - ## FacetBase _Every facet must inherit from this contract._ @@ -52,32 +36,6 @@ event Reward(address owner, uint256 amount, bytes32 ref) event Seize(address owner, uint256 amount, bytes32 ref) ``` -## IexecOrderManagementFacet - -### manageAppOrder - -```solidity -function manageAppOrder(struct IexecLibOrders_v5.AppOrderOperation _apporderoperation) external -``` - -### manageDatasetOrder - -```solidity -function manageDatasetOrder(struct IexecLibOrders_v5.DatasetOrderOperation _datasetorderoperation) external -``` - -### manageWorkerpoolOrder - -```solidity -function manageWorkerpoolOrder(struct IexecLibOrders_v5.WorkerpoolOrderOperation _workerpoolorderoperation) external -``` - -### manageRequestOrder - -```solidity -function manageRequestOrder(struct IexecLibOrders_v5.RequestOrderOperation _requestorderoperation) external -``` - ## Matching ```solidity @@ -182,6 +140,31 @@ in a loss of some of the requester funds deposited in the iExec account | _workerpoolorder | struct IexecLibOrders_v5.WorkerpoolOrder | The workerpool order. | | _requestorder | struct IexecLibOrders_v5.RequestOrder | The requester order. | +### depositAndMatchOrders + +```solidity +function depositAndMatchOrders(struct IexecLibOrders_v5.AppOrder _apporder, struct IexecLibOrders_v5.DatasetOrder _datasetorder, struct IexecLibOrders_v5.WorkerpoolOrder _workerpoolorder, struct IexecLibOrders_v5.RequestOrder _requestorder) external returns (bytes32 dealId) +``` + +Deposit RLC tokens and match orders in a single transaction + +_The requester (msg.sender) will be both the depositor and the sponsor 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 request order | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| dealId | bytes32 | The ID of the created deal | + ## IexecPoco2Facet ### initialize @@ -497,115 +480,6 @@ 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 `IexecPocoAccessors`. - -### 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 @@ -1169,6 +1043,22 @@ function m_schedulerRewardRatioPolicy() external view returns (uint256) function m_workerStakeRatioPolicy() external view returns (uint256) ``` +## IexecInterfaceNative + +A global interface that aggregates all the interfaces needed to interact with +the PoCo contracts in native mode. + +_Referenced in the SDK with the current path `contracts/IexecInterfaceNative.sol`. +Changing the name or the path would cause a breaking change in the SDK._ + +## IexecInterfaceToken + +A global interface that aggregates all the interfaces needed to interact with +the PoCo contracts in token mode. + +_Referenced in the SDK with the current path `contracts/IexecInterfaceToken.sol`. +Changing the name or the path would cause a breaking change in the SDK._ + ## IexecCategoryManagerFacet ### createCategory @@ -1747,3 +1637,170 @@ function createWorkerpool(address _workerpoolOwner, string _workerpoolDescriptio function predictWorkerpool(address _workerpoolOwner, string _workerpoolDescription) external view returns (contract Workerpool) ``` +## IexecPocoBoostAccessorsFacet + +Access to PoCo Boost tasks must be done with PoCo Classic `IexecPocoAccessors`. + +### 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. | + +## IexecOrderManagementFacet + +### manageAppOrder + +```solidity +function manageAppOrder(struct IexecLibOrders_v5.AppOrderOperation _apporderoperation) external +``` + +### manageDatasetOrder + +```solidity +function manageDatasetOrder(struct IexecLibOrders_v5.DatasetOrderOperation _datasetorderoperation) external +``` + +### manageWorkerpoolOrder + +```solidity +function manageWorkerpoolOrder(struct IexecLibOrders_v5.WorkerpoolOrderOperation _workerpoolorderoperation) external +``` + +### manageRequestOrder + +```solidity +function manageRequestOrder(struct IexecLibOrders_v5.RequestOrderOperation _requestorderoperation) external +``` + +## IexecDepositAndMatchOrdersFacet + +Facet that combines deposit and order matching for token-based PoCo deployments + +_This facet allows builders to deposit RLC tokens and match orders in a single transaction, + significantly improving the user experience by eliminating the need for separate approve+deposit+match transactions_ + +### depositAndMatchOrders + +```solidity +function depositAndMatchOrders(struct IexecLibOrders_v5.AppOrder _apporder, struct IexecLibOrders_v5.DatasetOrder _datasetorder, struct IexecLibOrders_v5.WorkerpoolOrder _workerpoolorder, struct IexecLibOrders_v5.RequestOrder _requestorder) external returns (bytes32 dealId) +``` + +Deposit RLC tokens and match orders in a single transaction + +_The requester (msg.sender) will be both the depositor and the sponsor 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 request order | + +#### Return Values + +| Name | Type | Description | +| ---- | ---- | ----------- | +| dealId | bytes32 | The ID of the created deal | + diff --git a/scripts/upgrades/v6.2.0-deposit-and-match.md b/scripts/upgrades/v6.2.0-deposit-and-match.md new file mode 100644 index 00000000..3bd6b42b --- /dev/null +++ b/scripts/upgrades/v6.2.0-deposit-and-match.md @@ -0,0 +1,20 @@ +# Upgrade v6.2.0 - Deposit and Match Orders + +- **Date**: 2025-10-20 +- **Git tag**: [v6.2.0](https://github.com/iExecBlockchainComputing/PoCo/releases/tag/v6.2.0) +- **Upgrade script**: `./v6.2.0-deposit-and-match.ts` +- **Upgrade GitHub Action run**: + - **Testnet**: [run id](https://github.com/iExecBlockchainComputing/PoCo/actions/runs/) + - **Mainnet**: [run id](https://github.com/iExecBlockchainComputing/PoCo/actions/runs/) + +## Summary of changes + +- Add `IexecDepositAndMatchOrdersFacet` to optimize deposit and order matching operations +- Introduce functions to handle deposit and match orders in a single transaction +- Improve gas efficiency for token-based operations + +## Deployment logs + +``` +TODO +``` diff --git a/scripts/upgrades/v6.2.0-deposit-and-match.ts b/scripts/upgrades/v6.2.0-deposit-and-match.ts new file mode 100644 index 00000000..5bed1024 --- /dev/null +++ b/scripts/upgrades/v6.2.0-deposit-and-match.ts @@ -0,0 +1,62 @@ +import { IexecPoco1Facet__factory } from '../../typechain'; +import { + deployFacets, + FacetDetails, + getUpgradeContext, + linkFacetsToDiamond, + printOnchainDiamondDescription, + removeFacetsFromDiamond, + saveOnchainDiamondDescription, +} from '../../utils/proxy-tools'; +import { tryVerify } from '../verify'; + +async function main() { + console.log('Deploying and adding DepositAndMatchOrders facet to diamond proxy...'); + + const { chainId, networkName, deployer, proxyOwner, proxyAddress, iexecLibOrders } = + await getUpgradeContext(); + + const facetAddressesPerChain: { [key: string]: { [key: string]: string } } = { + // Arbitrum sepolia + '421614': { + IexecPoco1Facet: '0xC8dE3913fcdBC576970dCe24eE416CA25681f65f', + }, + // Arbitrum mainnet + '42161': { + IexecPoco1Facet: '0x46b555fE117DFd8D4eAC2470FA2d739c6c3a0152', + }, + }; + const facetsToRemove: FacetDetails[] = [ + { + name: 'IexecPoco1Facet', + address: facetAddressesPerChain[chainId.toString()]['IexecPoco1Facet'], + factory: null, + }, + ]; + + const facetsToAdd: FacetDetails[] = [ + { + name: 'IexecPoco1Facet', + address: null, + factory: new IexecPoco1Facet__factory(iexecLibOrders), + }, + ]; + + // Print diamond description before upgrade + await printOnchainDiamondDescription(proxyAddress); + await deployFacets(deployer, chainId, facetsToAdd); // Adds deployed addresses to `facetsToAdd`. + await removeFacetsFromDiamond(proxyAddress, proxyOwner, facetsToRemove); + await printOnchainDiamondDescription(proxyAddress); + await linkFacetsToDiamond(proxyAddress, proxyOwner, facetsToAdd); + await printOnchainDiamondDescription(proxyAddress); + console.log('Upgrade performed successfully!'); + await saveOnchainDiamondDescription(proxyAddress, networkName); + await tryVerify(facetsToAdd.map((facet) => facet.name)); +} + +if (require.main === module) { + main().catch((error) => { + console.error(error); + process.exitCode = 1; + }); +} diff --git a/test/byContract/IexecPoco/IexecPoco1-depositAndMatchOrders.test.ts b/test/byContract/IexecPoco/IexecPoco1-depositAndMatchOrders.test.ts new file mode 100644 index 00000000..9847d99e --- /dev/null +++ b/test/byContract/IexecPoco/IexecPoco1-depositAndMatchOrders.test.ts @@ -0,0 +1,494 @@ +// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH +// SPDX-License-Identifier: Apache-2.0 + +import { AddressZero } from '@ethersproject/constants'; +import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; +import { loadFixture } from '@nomicfoundation/hardhat-network-helpers'; +import { expect } from 'chai'; +import { ethers } from 'hardhat'; +import { + IexecInterfaceToken, + IexecInterfaceToken__factory, + RLC, + RLC__factory, +} from '../../../typechain'; +import { TAG_TEE } from '../../../utils/constants'; +import { + IexecOrders, + OrdersActors, + OrdersAssets, + OrdersPrices, + buildOrders, + signOrders, +} from '../../../utils/createOrders'; +import { getDealId, getIexecAccounts } from '../../../utils/poco-tools'; +import { IexecWrapper } from '../../utils/IexecWrapper'; +import { loadHardhatFixtureDeployment } from '../../utils/hardhat-fixture-deployer'; + +const appPrice = 1000n; +const datasetPrice = 1_000_000n; +const workerpoolPrice = 1_000_000_000n; +const volume = 1n; + +describe('IexecPoco1-depositAndMatchOrders', () => { + let proxyAddress: string; + let iexecPoco: IexecInterfaceToken; + let iexecPocoAsRequester: IexecInterfaceToken; + let rlcInstance: RLC; + let rlcInstanceAsRequester: RLC; + let [ + iexecAdmin, + requester, + scheduler, + appProvider, + datasetProvider, + anyone, + ]: SignerWithAddress[] = []; + let iexecWrapper: IexecWrapper; + let [appAddress, datasetAddress, workerpoolAddress]: string[] = []; + let ordersActors: OrdersActors; + let ordersAssets: OrdersAssets; + let ordersPrices: OrdersPrices; + + beforeEach('Deploy', async () => { + proxyAddress = await loadHardhatFixtureDeployment(); + await loadFixture(initFixture); + }); + + async function initFixture() { + const accounts = await getIexecAccounts(); + ({ iexecAdmin, requester, scheduler, appProvider, datasetProvider, anyone } = accounts); + + iexecPoco = IexecInterfaceToken__factory.connect(proxyAddress, anyone); + iexecPocoAsRequester = iexecPoco.connect(requester); + + rlcInstance = RLC__factory.connect(await iexecPoco.token(), anyone); + rlcInstanceAsRequester = rlcInstance.connect(requester); + + iexecWrapper = new IexecWrapper(proxyAddress, accounts); + ({ appAddress, datasetAddress, workerpoolAddress } = await iexecWrapper.createAssets()); + + ordersActors = { + appOwner: appProvider, + datasetOwner: datasetProvider, + workerpoolOwner: scheduler, + requester: requester, + }; + ordersAssets = { + app: appAddress, + dataset: datasetAddress, + workerpool: workerpoolAddress, + }; + ordersPrices = { + app: appPrice, + dataset: datasetPrice, + workerpool: workerpoolPrice, + }; + + // Transfer RLC to accounts for testing + const totalAmount = (appPrice + datasetPrice + workerpoolPrice) * volume * 100n; + await rlcInstance + .connect(iexecAdmin) + .transfer(requester.address, totalAmount) + .then((tx) => tx.wait()); + await rlcInstance + .connect(iexecAdmin) + .transfer(scheduler.address, totalAmount) + .then((tx) => tx.wait()); + } + + async function signAndPrepareOrders(orders: IexecOrders): Promise { + await signOrders(iexecWrapper.getDomain(), orders, ordersActors); + } + + it('Should deposit and match orders with all assets', async () => { + const orders = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + }); + + await signAndPrepareOrders(orders); + + const dealCost = (appPrice + datasetPrice + workerpoolPrice) * volume; + const schedulerStake = await iexecWrapper.computeSchedulerDealStake( + workerpoolPrice, + volume, + ); + + await rlcInstanceAsRequester.approve(proxyAddress, dealCost).then((tx) => tx.wait()); + await iexecWrapper.depositInIexecAccount(scheduler, schedulerStake); + + const initialTotalSupply = await iexecPoco.totalSupply(); + + // Get order hashes for event verification + const { appOrderHash, datasetOrderHash, workerpoolOrderHash, requestOrderHash } = + iexecWrapper.hashOrders(orders); + + const dealId = getDealId(iexecWrapper.getDomain(), orders.requester); + expect( + await iexecPocoAsRequester.depositAndMatchOrders.staticCall( + orders.app, + orders.dataset, + orders.workerpool, + orders.requester, + ), + ).to.equal(dealId); + const tx = iexecPocoAsRequester.depositAndMatchOrders( + orders.app, + orders.dataset, + orders.workerpool, + orders.requester, + ); + + await expect(tx).to.changeTokenBalances( + rlcInstance, + [requester, iexecPoco], + [-dealCost, dealCost], + ); + await expect(tx).to.changeTokenBalances(iexecPoco, [requester], [0]); + await expect(tx) + .to.emit(iexecPoco, 'Transfer') + .withArgs(AddressZero, requester.address, dealCost); + + // Verify matchOrders was called correctly by checking events + await tx; + await expect(tx) + .to.emit(iexecPoco, 'SchedulerNotice') + .withArgs(workerpoolAddress, dealId) + .to.emit(iexecPoco, 'OrdersMatched') + .withArgs( + dealId, + appOrderHash, + datasetOrderHash, + workerpoolOrderHash, + requestOrderHash, + volume, + ); + + expect(await iexecPoco.frozenOf(requester.address)).to.equal(dealCost); + expect(await iexecPoco.totalSupply()).to.equal(initialTotalSupply + dealCost); + }); + + it('Should deposit and match orders without dataset', async () => { + const ordersWithoutDataset = buildOrders({ + assets: { ...ordersAssets, dataset: AddressZero }, + prices: { app: appPrice, workerpool: workerpoolPrice }, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + }); + + await signAndPrepareOrders(ordersWithoutDataset); + + const dealCost = (appPrice + workerpoolPrice) * volume; + const schedulerStake = await iexecWrapper.computeSchedulerDealStake( + workerpoolPrice, + volume, + ); + + await rlcInstanceAsRequester.approve(proxyAddress, dealCost).then((tx) => tx.wait()); + await iexecWrapper.depositInIexecAccount(scheduler, schedulerStake); + + const { appOrderHash, workerpoolOrderHash, requestOrderHash } = + iexecWrapper.hashOrders(ordersWithoutDataset); + + const tx = iexecPocoAsRequester.depositAndMatchOrders( + ordersWithoutDataset.app, + ordersWithoutDataset.dataset, + ordersWithoutDataset.workerpool, + ordersWithoutDataset.requester, + ); + + await expect(tx).to.changeTokenBalances( + rlcInstance, + [requester, iexecPoco], + [-dealCost, dealCost], + ); + await expect(tx).to.changeTokenBalances(iexecPoco, [requester], [0]); + await expect(tx) + .to.emit(iexecPoco, 'Transfer') + .withArgs(AddressZero, requester.address, dealCost); + + const dealId = getDealId(iexecWrapper.getDomain(), ordersWithoutDataset.requester); + await expect(tx) + .to.emit(iexecPoco, 'SchedulerNotice') + .withArgs(workerpoolAddress, dealId) + .to.emit(iexecPoco, 'OrdersMatched') + .withArgs( + dealId, + appOrderHash, + ethers.ZeroHash, // datasetOrderHash is zero when no dataset + workerpoolOrderHash, + requestOrderHash, + volume, + ); + }); + + it('Should use existing balance when sufficient', async () => { + const orders = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + }); + + const dealCost = (appPrice + datasetPrice + workerpoolPrice) * volume; + const schedulerStake = await iexecWrapper.computeSchedulerDealStake( + workerpoolPrice, + volume, + ); + + await iexecWrapper.depositInIexecAccount(requester, dealCost * 2n); + await iexecWrapper.depositInIexecAccount(scheduler, schedulerStake); + + await signAndPrepareOrders(orders); + + const tx = iexecPocoAsRequester.depositAndMatchOrders( + orders.app, + orders.dataset, + orders.workerpool, + orders.requester, + ); + await expect(tx).to.changeTokenBalances(rlcInstance, [requester, iexecPoco], [0, 0]); + await expect(tx).to.changeTokenBalances(iexecPoco, [requester], [-dealCost]); + }); + + it('Should deposit only the difference when partial balance exists', async () => { + const orders = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + }); + + const dealCost = (appPrice + datasetPrice + workerpoolPrice) * volume; + const partialBalance = dealCost / 2n; + const requiredDeposit = dealCost - partialBalance; + const schedulerStake = await iexecWrapper.computeSchedulerDealStake( + workerpoolPrice, + volume, + ); + + await iexecWrapper.depositInIexecAccount(requester, partialBalance); + await iexecWrapper.depositInIexecAccount(scheduler, schedulerStake); + + await signAndPrepareOrders(orders); + + await rlcInstanceAsRequester.approve(proxyAddress, requiredDeposit).then((tx) => tx.wait()); + + const initialTotalSupply = await iexecPoco.totalSupply(); + + const tx = iexecPocoAsRequester.depositAndMatchOrders( + orders.app, + orders.dataset, + orders.workerpool, + orders.requester, + ); + await expect(tx).to.changeTokenBalances( + rlcInstance, + [requester, iexecPoco], + [-requiredDeposit, requiredDeposit], + ); + await expect(tx).to.changeTokenBalances(iexecPoco, [requester], [-partialBalance]); + await expect(tx) + .to.emit(iexecPoco, 'Transfer') + .withArgs(AddressZero, requester.address, requiredDeposit); + + expect(await iexecPoco.totalSupply()).to.equal(initialTotalSupply + requiredDeposit); + }); + + it('Should create the same deal structure as traditional flow', async () => { + const orders1 = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + salt: ethers.hexlify(ethers.randomBytes(32)), + }); + + const orders2 = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + salt: ethers.hexlify(ethers.randomBytes(32)), + }); + + const dealCost = (appPrice + datasetPrice + workerpoolPrice) * volume; + const schedulerStake = await iexecWrapper.computeSchedulerDealStake( + workerpoolPrice, + volume, + ); + + await iexecWrapper.depositInIexecAccount(scheduler, schedulerStake * 2n); + + await signAndPrepareOrders(orders1); + await signAndPrepareOrders(orders2); + + const orderHashes1 = iexecWrapper.hashOrders(orders1); + const orderHashes2 = iexecWrapper.hashOrders(orders2); + + await rlcInstanceAsRequester.approve(proxyAddress, dealCost).then((tx) => tx.wait()); + const tx1 = iexecPocoAsRequester.depositAndMatchOrders( + orders1.app, + orders1.dataset, + orders1.workerpool, + orders1.requester, + ); + + await iexecWrapper.depositInIexecAccount(requester, dealCost); + const tx2 = iexecPocoAsRequester.matchOrders( + orders2.app, + orders2.dataset, + orders2.workerpool, + orders2.requester, + ); + const dealId1 = getDealId(iexecWrapper.getDomain(), orders1.requester); + const dealId2 = getDealId(iexecWrapper.getDomain(), orders2.requester); + + // Verify both transactions emit the same events + await expect(tx1) + .to.emit(iexecPoco, 'OrdersMatched') + .withArgs( + dealId1, + orderHashes1.appOrderHash, + orderHashes1.datasetOrderHash, + orderHashes1.workerpoolOrderHash, + orderHashes1.requestOrderHash, + volume, + ); + + await expect(tx2) + .to.emit(iexecPoco, 'OrdersMatched') + .withArgs( + dealId2, + orderHashes2.appOrderHash, + orderHashes2.datasetOrderHash, + orderHashes2.workerpoolOrderHash, + orderHashes2.requestOrderHash, + volume, + ); + + const deal1 = await iexecPoco.viewDeal(dealId1); + const deal2 = await iexecPoco.viewDeal(dealId2); + + expect(deal1.app.pointer).to.equal(deal2.app.pointer); + expect(deal1.dataset.pointer).to.equal(deal2.dataset.pointer); + expect(deal1.workerpool.pointer).to.equal(deal2.workerpool.pointer); + expect(deal1.requester).to.equal(deal2.requester); + }); + + it('Should fail when caller is not the requester', async () => { + const orders = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + }); + + const iexecPocoAsAnyone = iexecPoco.connect(anyone); + + await expect( + (iexecPocoAsAnyone as any).depositAndMatchOrders( + orders.app, + orders.dataset, + orders.workerpool, + orders.requester, + ), + ).to.be.revertedWithCustomError(iexecPoco, 'CallerMustBeRequester'); + }); + + it('Should fail when RLC approval is insufficient', async () => { + const orders = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + }); + + const dealCost = (appPrice + datasetPrice + workerpoolPrice) * volume; + const schedulerStake = await iexecWrapper.computeSchedulerDealStake( + workerpoolPrice, + volume, + ); + + await signAndPrepareOrders(orders); + await iexecWrapper.depositInIexecAccount(scheduler, schedulerStake); + + await rlcInstanceAsRequester.approve(proxyAddress, dealCost - 1n).then((tx) => tx.wait()); + + // RLC token will revert without a specific reason string + await expect( + iexecPocoAsRequester.depositAndMatchOrders( + orders.app, + orders.dataset, + orders.workerpool, + orders.requester, + ), + ).to.be.reverted; + }); + + it('Should fail when no RLC approval is given', async () => { + const orders = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + }); + + const schedulerStake = await iexecWrapper.computeSchedulerDealStake( + workerpoolPrice, + volume, + ); + + await signAndPrepareOrders(orders); + await iexecWrapper.depositInIexecAccount(scheduler, schedulerStake); + + // No approval given - RLC token will revert without a specific reason string + await expect( + iexecPocoAsRequester.depositAndMatchOrders( + orders.app, + orders.dataset, + orders.workerpool, + orders.requester, + ), + ).to.be.reverted; + }); + + it('Should fail when scheduler has insufficient stake', async () => { + const orders = buildOrders({ + assets: ordersAssets, + prices: ordersPrices, + requester: requester.address, + tag: TAG_TEE, + volume: volume, + }); + + const dealCost = (appPrice + datasetPrice + workerpoolPrice) * volume; + + await signAndPrepareOrders(orders); + await rlcInstanceAsRequester.approve(proxyAddress, dealCost).then((tx) => tx.wait()); + + // The function will deposit and lock the requester's funds first, + // then matchOrders will fail due to insufficient scheduler stake + // This results in "Transfer amount exceeds balance" when trying to unlock + await expect( + iexecPocoAsRequester.depositAndMatchOrders( + orders.app, + orders.dataset, + orders.workerpool, + orders.requester, + ), + ).to.be.reverted; + }); +}); diff --git a/utils/proxy-tools.ts b/utils/proxy-tools.ts index a51898ae..8cc80b1f 100644 --- a/utils/proxy-tools.ts +++ b/utils/proxy-tools.ts @@ -1,9 +1,9 @@ // SPDX-FileCopyrightText: 2024-2025 IEXEC BLOCKCHAIN TECH // SPDX-License-Identifier: Apache-2.0 -import fs from 'fs'; import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'; import { ContractFactory, FunctionFragment, Interface, ZeroAddress } from 'ethers'; +import fs from 'fs'; import { deployments, ethers } from 'hardhat'; import { FacetCut, FacetCutAction } from 'hardhat-deploy/dist/types'; import type { IDiamond } from '../typechain'; @@ -11,7 +11,6 @@ import { DiamondCutFacet, DiamondCutFacet__factory, DiamondLoupeFacet__factory, - OwnershipFacet__factory, IexecAccessorsABILegacyFacet__factory, IexecCategoryManagerFacet__factory, IexecConfigurationExtraFacet__factory, @@ -28,10 +27,11 @@ import { IexecPocoBoostFacet__factory, IexecRelayFacet__factory, Ownable__factory, + OwnershipFacet__factory, } from '../typechain'; import { getBaseNameFromContractFactory, getDeployerAndOwnerSigners } from '../utils/deploy-tools'; -import { getChainConfig, isFork } from './config'; import { FactoryDeployer } from './FactoryDeployer'; +import { getChainConfig, isFork } from './config'; const POCO_STORAGE_LOCATION = '0x5862653c6982c162832160cf30593645e8487b257e44d77cdd6b51eee2651b00';