Skip to content

Conversation

@marcello-pv01
Copy link
Contributor

@marcello-pv01 marcello-pv01 commented Sep 25, 2025

Description

We want to allow executing a settlement in the most efficient way as possible while keeping the same trace of the settlement on the smart contract.
This means that we want to allow the user to define a settlement as the explicit set of cashflows, but we want to let them settle it in the most capital/gas efficient way possible, i.e. netting off transfers where applicable.

This PR adds an additional functionality upon settlement creation, where the caller could send an additional array of netted flows which will be used for the settlement when it gets executed.

The idea is that those netted flows are computed off-chain, where the computation is cheaper and easier to run and implement, but on-chain we still verify that the netted flows provided are equivalent to the explicit flows.

In order to simplify the life for devs that want to achieve trivial optimisations, I added an helper function to the helper contract which greedily computes those optimisations.
This is not as efficient as using MILP approaches but for simple scenarios they are equivalent, and it's still better than nothing.

I implemented tests for both functionalities and they are passing.

Considerations

This solution will cost extra gas to store the netted off flows and the useNettingOff flag.
If no netted flows are provided useNettingOff will be set to false automatically, if they are, even if it's an empty array, then useNettingOff will be set to true. The reason for this is that no flows is an optimisation, think of A -> B: 10 USDC, B -> A: 10 USDC.

Changes

  • Add settlement execution optimisation function

Checklist

  • Tests pass
  • review whether we want the extra validation of the netted flows, that checks whether the flows are mono-directional, aka everything has been netted off, it's still possible that we haven't minimised the number of transfers that will be performed, but we'll have minimised the capital required for the settlement
  • review all the storage/memory/calldata type definitions

Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
}

// Additional Step: For fungible assets, enforce debtor→creditor-only transfers with gross == net per party
// TODO: evaluate whether this is too restrictive, this would possibly prevent partial netting arrangements, write tests for this in case we decide to keep it
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's review this together

uint256 go = grossOut[base];
uint256 gi = grossIn[base];
if (d < 0) {
require(gi == 0, "Fungible: debtor must not receive");
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I need to write tests and possibly convert to exceptions for these guys

Signed-off-by: marcello-pv01 <marcello@pv0.one>
@marcello-pv01 marcello-pv01 force-pushed the implement-settlement-net-off-hint branch from ddd2c42 to 0150525 Compare October 1, 2025 11:00
uint256 cutoffDate,
bool isAutoSettled
) external returns (uint256 id) {
return _createSettlement(flows, nettedFlows,settlementReference, cutoffDate, isAutoSettled, true);
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
return _createSettlement(flows, nettedFlows,settlementReference, cutoffDate, isAutoSettled, true);
return _createSettlement(flows, nettedFlows, settlementReference, cutoffDate, isAutoSettled, true);

* @param nettedFlows The flows that will be used to process the settlement, used to provide optimised settlement solutions.
* @param settlementReference A free text reference for the settlement.
* @param cutoffDate The deadline for approvals and execution.
* @param isAutoSettled If true, the settlement will be executed automatically after all approvals are in place.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

missing useNettingOff parameter

if (block.timestamp > cutoffDate) revert CutoffDatePassed();
uint256 lengthFlows = flows.length;
if (lengthFlows == 0) revert NoFlowsProvided();
// no need to check for nettedFlows being not empty if useNettingOff is false at runtime
Copy link
Contributor Author

Choose a reason for hiding this comment

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

also empty nettedFlows is a valid optimisation.

Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
@marcello-pv01 marcello-pv01 force-pushed the implement-settlement-net-off-hint branch from 33ae549 to 306c4c0 Compare October 2, 2025 16:03
Comment on lines 208 to 209
uint256 idxA = k * partyCount + pFrom;
uint256 idxB = k * partyCount + pTo;
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Suggested change
uint256 idxA = k * partyCount + pFrom;
uint256 idxB = k * partyCount + pTo;
uint256 idxFrom = k * partyCount + pFrom;
uint256 idxTo = k * partyCount + pTo;

Signed-off-by: marcello-pv01 <marcello@pv0.one>
* @param party Address of the party to compute requirements for.
* @return result NetRequirement data struct with ETH and ERC20 requirements.
*/
function _computeNetRequirementsForParty(IDeliveryVersusPaymentV1.Flow[] memory flows, address party) internal pure returns (
Copy link
Contributor Author

Choose a reason for hiding this comment

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

move this function where "private" ones live

if (f.token == address(0)) {
// ETH leg
result.ethRequiredNet += f.amountOrId;
} else if (!f.isNFT) {
Copy link
Contributor Author

Choose a reason for hiding this comment

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

we should probably add nft as well for completeness

Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
Signed-off-by: marcello-pv01 <marcello@pv0.one>
@marcello-pv01
Copy link
Contributor Author

Agreed with Kevin to release the version without netting off to the public as that's the one audited by ShadowyCreators.
We'll deploy this whenever we'll have another audit iteration.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants