Skip to content
Merged
Show file tree
Hide file tree
Changes from 35 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
116 changes: 113 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,131 @@ contract IexecEscrowTokenFacet is IexecEscrowToken, IexecTokenSpender, FacetBase
return delta;
}

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

Choose a reason for hiding this comment

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

Suggested change
* Token Spender: Atomic Approve+Deposit+Match *
* Token Spender: Atomic Deposit+Match if used with RLC.approveAndCall *

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Copy link
Member

Choose a reason for hiding this comment

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

Up here. The comment is not correct we only have 2 of them, deposit and match.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

***************************************************************************/

/**
* @notice 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)
*
* @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);
bytes32 dealId = bytes32(0);
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

dealId = _decodeDataAndMatchOrders(sender, data);
Copy link

Copilot AI Nov 6, 2025

Choose a reason for hiding this comment

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

The dealId variable is declared but never used after being assigned. If the dealId is not needed for return or event emission in the receiveApproval function, consider removing this variable assignment and simplifying the code to just call _decodeDataAndMatchOrders(sender, data); without capturing the return value.

Suggested change
bytes32 dealId = bytes32(0);
if (data.length > 0) {
dealId = _decodeDataAndMatchOrders(sender, data);
if (data.length > 0) {
_decodeDataAndMatchOrders(sender, data);

Copilot uses AI. Check for mistakes.
Copy link

Copilot AI Nov 7, 2025

Choose a reason for hiding this comment

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

The variable dealId is initialized to bytes32(0) but is never used after line 120. Consider removing this variable declaration entirely since the return value from _decodeDataAndMatchOrders is not used anywhere.

Suggested change
bytes32 dealId = bytes32(0);
if (data.length > 0) {
dealId = _decodeDataAndMatchOrders(sender, data);
if (data.length > 0) {
_decodeDataAndMatchOrders(sender, data);

Copilot uses AI. Check for mistakes.
}
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
* @return dealId The deal ID of the matched deal
*/
function _decodeDataAndMatchOrders(
address sender,
bytes calldata data
) internal returns (bytes32 dealId) {
// 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
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Using delegatecall for safety: preserves msg.sender context
// Using delegatecall for safety: preserves msg.sender context (RLC address in this case)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

// 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");
}
}

// Decode the dealId from successful call
dealId = abi.decode(result, (bytes32));

return dealId;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// Decode the dealId from successful call
dealId = abi.decode(result, (bytes32));
return dealId;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

}

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
5 changes: 5 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 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: 30 additions & 1 deletion docs/solidity/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -247,9 +247,33 @@ 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)

#### 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 // 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 +367,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
Loading