Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
43 commits
Select commit Hold shift + click to select a range
394908b
feat: Update Solidity version to ^0.8.0 and refactor contracts for im…
gfournierPro Oct 29, 2025
68c1e89
feat: Enhance IexecEscrowTokenFacet with approval event and order mat…
gfournierPro Oct 29, 2025
23a067f
feat: Add error and event definitions for approval handling in IexecE…
gfournierPro Oct 29, 2025
fc6926b
feat: Add ApprovalReceivedAndMatched event and refactor error handlin…
gfournierPro Oct 30, 2025
0ae822a
Merge branch 'main' into feat/deposit-matchOrder-via-receiveApproval
gfournierPro Nov 4, 2025
fb844af
feat: update ABI definitions and remove deprecated events
gfournierPro Nov 4, 2025
7f08cf3
feat: implement encodeOrders function for ABI encoding in receiveAppr…
gfournierPro Nov 6, 2025
3517bf0
refactor: reorganize order handling functions in receiveApproval tests
gfournierPro Nov 6, 2025
47ecf74
fix: clarify return value description in _matchOrdersAfterDeposit fun…
gfournierPro Nov 6, 2025
0b8ed3e
Merge branch 'main' into feat/deposit-matchOrder-via-receiveApproval
gfournierPro Nov 6, 2025
bd2adf1
Merge branch 'chore/solidity-v8' into feat/deposit-matchOrder-via-rec…
gfournierPro Nov 6, 2025
0e57654
chore: remove deprecated ABI files from the project
gfournierPro Nov 6, 2025
fda1c38
style: standardize code formatting and improve readability across mul…
gfournierPro Nov 6, 2025
6ca7c61
fix: solidity docs
gfournierPro Nov 6, 2025
921df75
Merge branch 'fix/format-sc' into feat/deposit-matchOrder-via-receive…
gfournierPro Nov 6, 2025
0c661b3
refactor: remove duplicate import statements in IexecEscrowTokenFacet…
gfournierPro Nov 6, 2025
d9b56ba
docs: enhance receiveApproval function documentation with usage patte…
gfournierPro Nov 6, 2025
b51807f
test: remove describe.only to enable all tests in IexecEscrowToken.re…
gfournierPro Nov 6, 2025
ad0130f
feat: add human-readable ABIs for ERC20, ERC721, and utility contracts
gfournierPro Nov 6, 2025
689e1c3
Merge branch 'fix/format-sc' into feat/deposit-matchOrder-via-receive…
gfournierPro Nov 6, 2025
ec05830
fix: correct typo in error message for transferFrom in _deposit function
gfournierPro Nov 6, 2025
22e3b3e
docs: update receiveApproval function documentation for clarity and a…
gfournierPro Nov 6, 2025
be65435
refactor: remove ApprovalReceivedAndMatched event and related references
gfournierPro Nov 6, 2025
648078e
refactor: move IexecInterfaceToken documentation to the correct section
gfournierPro Nov 6, 2025
f8681d3
refactor: rename _matchOrdersAfterDeposit to _decodeDataAndMatchOrder…
gfournierPro Nov 6, 2025
b229dc5
refactor: remove human-readable ABIs for unused contracts
gfournierPro Nov 6, 2025
0e656ee
Merge branch 'fix/format-sc' into feat/deposit-matchOrder-via-receive…
gfournierPro Nov 6, 2025
137dd04
refactor: update delegatecall usage for safety and clarify function d…
gfournierPro Nov 6, 2025
c49a93e
Merge branch 'chore/solidity-v8' into fix/format-sc
gfournierPro Nov 7, 2025
69ebbcd
fix: docs
gfournierPro Nov 7, 2025
f21473b
Merge branch 'fix/format-sc' into feat/deposit-matchOrder-via-receive…
gfournierPro Nov 7, 2025
024675e
feat: enhance receiveApproval function with order matching capabiliti…
gfournierPro Nov 7, 2025
b06e552
docs: enhance receiveApproval function documentation with important n…
gfournierPro Nov 7, 2025
64d8d80
fix: update deposit function to override and adjust balance verificat…
gfournierPro Nov 7, 2025
fc99890
Merge branch 'chore/solidity-v8' into feat/deposit-matchOrder-via-rec…
gfournierPro Nov 7, 2025
4917773
docs: add important notes on order matching and deal cost calculation…
gfournierPro Nov 7, 2025
6e36267
refactor: remove gas comparison test for order matching
gfournierPro Nov 7, 2025
2e32246
fix: update approval process to use approveAndCall for existing balan…
gfournierPro Nov 7, 2025
14e1614
feat: add TODO to consider making sponsor origin a variable in match …
gfournierPro Nov 7, 2025
c2397f9
feat: add ReceiveApprovalTestHelper contract to simulate silent failu…
gfournierPro Nov 7, 2025
5fee6ac
fix: update copyright year in ReceiveApprovalTestHelper contract
gfournierPro Nov 7, 2025
b6a04d5
refactor: simplify _decodeDataAndMatchOrders by removing unused dealI…
gfournierPro Nov 10, 2025
da0c7bb
refactor: update comments in IexecEscrowTokenFacet for clarity on dep…
gfournierPro Nov 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion abis/contracts/facets/IexecEscrowTokenFacet.json
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@
},
{
"internalType": "bytes",
"name": "",
"name": "data",
"type": "bytes"
}
],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[
"function matchOrders(tuple(address,uint256,uint256,bytes32,address,address,address,bytes32,bytes),tuple(address,uint256,uint256,bytes32,address,address,address,bytes32,bytes),tuple(address,uint256,uint256,bytes32,uint256,uint256,address,address,address,bytes32,bytes),tuple(address,uint256,address,uint256,address,uint256,address,uint256,bytes32,uint256,uint256,address,address,string,bytes32,bytes)) pure returns (bytes32)"
]
106 changes: 103 additions & 3 deletions contracts/facets/IexecEscrowTokenFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import {IexecERC20Core} from "./IexecERC20Core.sol";
import {FacetBase} from "./FacetBase.sol";
import {IexecEscrowToken} from "../interfaces/IexecEscrowToken.sol";
import {IexecTokenSpender} from "../interfaces/IexecTokenSpender.sol";
import {IexecPoco1} from "../interfaces/IexecPoco1.sol";
import {IexecLibOrders_v5} from "../libs/IexecLibOrders_v5.sol";
import {PocoStorageLib} from "../libs/PocoStorageLib.sol";

contract IexecEscrowTokenFacet is IexecEscrowToken, IexecTokenSpender, FacetBase, IexecERC20Core {
Expand Down Expand Up @@ -64,23 +66,121 @@ contract IexecEscrowTokenFacet is IexecEscrowToken, IexecTokenSpender, FacetBase
return delta;
}

// Token Spender (endpoint for approveAndCallback calls to the proxy)
/***************************************************************************
* Token Spender: Atomic Deposit+Match *
***************************************************************************/

/**
* @notice Receives approval, deposit and optionally matches orders in one transaction
*
* Usage patterns:
* 1. Simple deposit: RLC.approveAndCall(escrow, amount, "")
* 2. Deposit + match: RLC.approveAndCall(escrow, amount, encodedOrders)
*
* The `data` parameter should be ABI-encoded orders if matching is desired:
* abi.encode(appOrder, datasetOrder, workerpoolOrder, requestOrder)
*
* @dev Important notes:
* - Match orders sponsoring is NOT supported. The requester (sender) always pays for the deal.
* - Clients must compute the exact deal cost and deposit the right amount for the deal to be matched.
* The deal cost = (appPrice + datasetPrice + workerpoolPrice) * volume.
* - If insufficient funds are deposited, the match will fail.
*
* @param sender The address that approved tokens (must be requester if matching)
* @param amount Amount of tokens approved and to be deposited
* @param token Address of the token (must be RLC)
* @param data Optional: ABI-encoded orders for matching
* @return success True if operation succeeded
*
*
* @custom:example
* ```solidity
* // Compute deal cost
* uint256 dealCost = (appPrice + datasetPrice + workerpoolPrice) * volume;
*
* // Encode orders
* bytes memory data = abi.encode(appOrder, datasetOrder, workerpoolOrder, requestOrder);
*
* // One transaction does it all
* RLC(token).approveAndCall(iexecProxy, dealCost, data);
* ```
*/
function receiveApproval(
address sender,
uint256 amount,
address token,
bytes calldata
bytes calldata data
) external override returns (bool) {
PocoStorageLib.PocoStorage storage $ = PocoStorageLib.getPocoStorage();
require(token == address($.m_baseToken), "wrong-token");
_deposit(sender, amount);
_mint(sender, amount);
if (data.length > 0) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We have two choices here:

  1. data = encoded orders: the simpler option but limited for future updates
  2. data = matchOrders selector + encoded orders: this version seems a bit more complicated but it allows for easier future evolutions. We can encode the hole call and check the selector, if it is matchOrders than do the call, if not revert. This way when we want to add a new operation the SDK needs just to encode the new call and we'll need to allow it. The behavior does not change from only encoding call arguments to encoding the full call.
if (data.length == 0) {
    return true;
}
if (getSelector(data) == matchOrders.selector) {
    matchOrders
    return true;
}
revert;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will check that on a later PR

_decodeDataAndMatchOrders(sender, data);
}
return true;
}

/******************************************************************************
* Token Spender: Atomic Deposit+Match if used with RLC.approveAndCall *
*****************************************************************************/

/**
* @dev Internal function to match orders after deposit
* @param sender The user who deposited (must be the requester)
* @param data ABI-encoded orders
*/
function _decodeDataAndMatchOrders(address sender, bytes calldata data) internal {
// Decode the orders from calldata
(
IexecLibOrders_v5.AppOrder memory apporder,
IexecLibOrders_v5.DatasetOrder memory datasetorder,
IexecLibOrders_v5.WorkerpoolOrder memory workerpoolorder,
IexecLibOrders_v5.RequestOrder memory requestorder
) = abi.decode(
data,
(
IexecLibOrders_v5.AppOrder,
IexecLibOrders_v5.DatasetOrder,
IexecLibOrders_v5.WorkerpoolOrder,
IexecLibOrders_v5.RequestOrder
)
);

// Validate that sender is the requester
if (requestorder.requester != sender) revert("caller-must-be-requester");

// Call matchOrders on the IexecPoco1 facet through the diamond
// Using delegatecall for safety: preserves msg.sender context (RLC address in this case)
// Note: matchOrders doesn't use msg.sender, but delegatecall is safer
// in case the implementation changes in the future
(bool success, bytes memory result) = address(this).delegatecall(
abi.encodeWithSelector(
IexecPoco1.matchOrders.selector,
apporder,
datasetorder,
workerpoolorder,
requestorder
)
);

// Handle failure and bubble up revert reason
if (!success) {
if (result.length > 0) {
// Decode and revert with the original error
assembly {
let returndata_size := mload(result)
revert(add(result, 32), returndata_size)
}
} else {
revert("receive-approval-failed");
}
}
}

function _deposit(address from, uint256 amount) internal {
PocoStorageLib.PocoStorage storage $ = PocoStorageLib.getPocoStorage();
require($.m_baseToken.transferFrom(from, address(this), amount), "failled-transferFrom");
require($.m_baseToken.transferFrom(from, address(this), amount), "failed-transferFrom");
}

function _withdraw(address to, uint256 amount) internal {
Expand Down
6 changes: 6 additions & 0 deletions contracts/facets/IexecPoco1Facet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,11 @@ contract IexecPoco1Facet is
/**
* Match orders. The requester gets debited.
*
* @notice This function does not use `msg.sender` to determine who pays for the deal.
* The sponsor is always set to `_requestorder.requester`, regardless of who calls this function.
* This design allows the function to be safely called via delegatecall from other facets
* (e.g., IexecEscrowTokenFacet.receiveApproval) without security concerns.
*
* @param _apporder The app order.
* @param _datasetorder The dataset order.
* @param _workerpoolorder The workerpool order.
Expand All @@ -161,6 +166,7 @@ contract IexecPoco1Facet is
);
}

// TODO: check if we want to modify sponsor origin to be a variable instead of msg.sender
/**
* Sponsor match orders for a requester.
* Unlike the standard `matchOrders(..)` hook where the requester pays for
Expand Down
1 change: 0 additions & 1 deletion contracts/interfaces/IexecEscrowToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ pragma solidity ^0.8.0;
interface IexecEscrowToken {
receive() external payable;
fallback() external payable;

function deposit(uint256) external returns (bool);
function depositFor(uint256, address) external returns (bool);
function depositForArray(uint256[] calldata, address[] calldata) external returns (bool);
Expand Down
31 changes: 31 additions & 0 deletions contracts/tools/testing/ReceiveApprovalTestHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-FileCopyrightText: 2025 IEXEC BLOCKCHAIN TECH <[email protected]>
// SPDX-License-Identifier: Apache-2.0

pragma solidity ^0.8.0;

import {IexecLibOrders_v5} from "../../libs/IexecLibOrders_v5.sol";

/**
* @title ReceiveApprovalTestHelper
* @notice Helper contract to test edge cases in receiveApproval function
* @dev This contract simulates a facet that fails silently (no error data)
*/
contract ReceiveApprovalTestHelper {
/**
* @notice Mock matchOrders function that fails without returning error data
* @dev Uses assembly to revert without data, simulating the edge case where
* delegatecall fails and result.length == 0
*/
function matchOrders(
IexecLibOrders_v5.AppOrder calldata,
IexecLibOrders_v5.DatasetOrder calldata,
IexecLibOrders_v5.WorkerpoolOrder calldata,
IexecLibOrders_v5.RequestOrder calldata
) external pure returns (bytes32) {
// Revert without any error data
// This simulates: delegatecall fails with success=false and result.length=0
assembly {
revert(0, 0)
}
}
}
37 changes: 36 additions & 1 deletion docs/solidity/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,39 @@ function recover() external returns (uint256)
### receiveApproval

```solidity
function receiveApproval(address sender, uint256 amount, address token, bytes) external returns (bool)
function receiveApproval(address sender, uint256 amount, address token, bytes data) external returns (bool)
```

Receives approval and optionally matches orders in one transaction

Usage patterns:
1. Simple deposit: RLC.approveAndCall(escrow, amount, "")
2. Deposit + match: RLC.approveAndCall(escrow, amount, encodedOrders)

The `data` parameter should be ABI-encoded orders if matching is desired:
abi.encode(appOrder, datasetOrder, workerpoolOrder, requestOrder)

_Important notes:
- Match orders sponsoring is NOT supported. The requester (sender) always pays for the deal.
- Clients must compute the exact deal cost and deposit the right amount for the deal to be matched.
The deal cost = (appPrice + datasetPrice + workerpoolPrice) * volume.
- If insufficient funds are deposited, the match will fail._

#### Parameters

| Name | Type | Description |
| ---- | ---- | ----------- |
| sender | address | The address that approved tokens (must be requester if matching) |
| amount | uint256 | Amount of tokens approved and to be deposited |
| token | address | Address of the token (must be RLC) |
| data | bytes | Optional: ABI-encoded orders for matching |

#### Return Values

| Name | Type | Description |
| ---- | ---- | ----------- |
| [0] | bool | success True if operation succeeded @custom:example ```solidity // Compute deal cost uint256 dealCost = (appPrice + datasetPrice + workerpoolPrice) * volume; // Encode orders bytes memory data = abi.encode(appOrder, datasetOrder, workerpoolOrder, requestOrder); // One transaction does it all RLC(token).approveAndCall(iexecProxy, dealCost, data); ``` |

## IexecOrderManagementFacet

### manageAppOrder
Expand Down Expand Up @@ -343,6 +373,11 @@ function matchOrders(struct IexecLibOrders_v5.AppOrder _apporder, struct IexecLi

Match orders. The requester gets debited.

This function does not use `msg.sender` to determine who pays for the deal.
The sponsor is always set to `_requestorder.requester`, regardless of who calls this function.
This design allows the function to be safely called via delegatecall from other facets
(e.g., IexecEscrowTokenFacet.receiveApproval) without security concerns.

#### Parameters

| Name | Type | Description |
Expand Down
Loading