22// NatSpec format convention - https://docs.soliditylang.org/en/v0.5.10/natspec-format.html
33pragma solidity 0.8.26 ;
44
5- import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol " ;
65import { Initializable } from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol " ;
76import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol " ;
87import { AccessControlledUpgradeable } from "@synaps3/core/primitives/upgradeable/AccessControlledUpgradeable.sol " ;
@@ -13,8 +12,7 @@ import { ITollgate } from "@synaps3/core/interfaces/economics/ITollgate.sol";
1312import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol " ;
1413import { FeesOps } from "@synaps3/core/libraries/FeesOps.sol " ;
1514import { T } from "@synaps3/core/primitives/Types.sol " ;
16-
17- // TODO Doc: Trustless escrow system - modular escrow framework - escrow mechanism (agreement <arbitrer> settlement)
15+ import { C } from "@synaps3/core/primitives/Constants.sol " ;
1816
1917/// @title AgreementManager
2018/// @notice Manages the lifecycle (trustless escrow system) of agreements, including creation and retrieval.
@@ -23,7 +21,6 @@ import { T } from "@synaps3/core/primitives/Types.sol";
2321contract AgreementManager is Initializable , UUPSUpgradeable , AccessControlledUpgradeable , IAgreementManager {
2422 using FeesOps for uint256 ;
2523 using FinancialOps for address ;
26- using EnumerableSet for EnumerableSet.UintSet;
2724
2825 /// KIM: any initialization here is ephemeral and not included in bytecode..
2926 /// so the code within a logic contract’s constructor or global declaration
@@ -38,6 +35,10 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
3835 ILedgerVault public immutable LEDGER_VAULT;
3936 //slither-disable-end naming-convention
4037
38+ uint256 constant MAX_EXCESS = 13 ; // 1..13=91%, 14=>105%
39+ /// @notice Maximum allowed number of parties per agreement.
40+ /// @dev Can be updated by admin to adapt system limits.
41+ uint256 private _maxParties;
4142 /// @dev Holds a bounded key expressing the agreement between the parts.
4243 mapping (uint256 => T.Agreement) private _agreementsByProof;
4344
@@ -54,10 +55,13 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
5455 /// @notice Error thrown when a currency is not supported by the specified target.
5556 /// @param target The address or context for which the currency is unsupported.
5657 /// @param currency The address of the unsupported currency.
57- error UnsupportedAgreementCurrency (address target , address currency );
58+ error UnsupportedAgreementTarget (address target , address currency );
59+
60+ /// @notice Error thrown when trying to set an invalid maximum number of parties.
61+ error InvalidMaxParties (uint256 value );
5862
59- /// @notice Error thrown when an agreement includes no parties.
60- error NoPartiesInAgreement ();
63+ /// @notice Error thrown when the number of parties exceeds the protocol limit .
64+ error ExceedsMaxParties ();
6165
6266 /// @notice Ensures that the specified currency is supported for the given target.
6367 /// @dev This modifier verifies if the `currency` is accepted under the context of `target`.
@@ -66,7 +70,7 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
6670 /// @param currency The address of the currency being checked.
6771 modifier onlySupportedCurrency (address target , address currency ) {
6872 bool isCurrencySupported = TOLLGATE.isSupportedCurrency (target, currency);
69- if (! isCurrencySupported) revert UnsupportedAgreementCurrency (target, currency);
73+ if (! isCurrencySupported) revert UnsupportedAgreementTarget (target, currency);
7074 _;
7175 }
7276
@@ -84,6 +88,19 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
8488 function initialize (address accessManager ) public initializer {
8589 __UUPSUpgradeable_init ();
8690 __AccessControlled_init (accessManager);
91+ _maxParties = 5 ;
92+ }
93+
94+ /// @notice Updates the maximum number of allowed parties.
95+ /// @param newMax The new maximum number of parties.
96+ function setMaxParties (uint256 newMax ) external onlyAdmin {
97+ if (newMax == 0 ) revert InvalidMaxParties (newMax);
98+ _maxParties = newMax;
99+ }
100+
101+ /// @notice Retrieves the current max number of parties.
102+ function maxParties () external view returns (uint256 ) {
103+ return _maxParties;
87104 }
88105
89106 /// @notice Creates and stores a new agreement.
@@ -100,12 +117,13 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
100117 bytes calldata payload
101118 ) external onlySupportedCurrency (arbiter, currency) returns (uint256 ) {
102119 // IMPORTANT: The process of distributing funds to accounts should be handled within the settlement logic.
103- uint256 confirmed = LEDGER_VAULT.lock (msg .sender , amount, currency);
104- T.Agreement memory agreement = previewAgreement (confirmed, currency, arbiter, parties, payload);
120+ T.Agreement memory agreement = previewAgreement (amount, currency, arbiter, parties, payload);
121+ uint256 confirmed = LEDGER_VAULT.lock (msg .sender , agreement.locked, currency);
122+
105123 // only the initiator can operate with this agreement proof, or transfer the proof to the other party..
106124 // each agreement is unique and immutable, ensuring that it cannot be modified or reconstructed.
107125 uint256 proof = _createAndStoreProof (agreement);
108- emit AgreementCreated (msg .sender , proof, amount , currency);
126+ emit AgreementCreated (msg .sender , proof, confirmed , currency);
109127 return proof;
110128 }
111129
@@ -128,14 +146,6 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
128146 address [] calldata parties ,
129147 bytes calldata payload
130148 ) public view onlySupportedCurrency (arbiter, currency) returns (T.Agreement memory ) {
131- if (parties.length == 0 ) {
132- revert NoPartiesInAgreement ();
133- }
134-
135- // TODO Even if we are covered by gas fees, during execution a good way to avoid abuse
136- // is penalize parties after N length eg. The max parties allowed is 5, any extra
137- // parties are charged with a extra * fee. Denial of Service risk
138-
139149 // IMPORTANT:
140150 // Agreements transport value and represent a defined commitment between parties.
141151 // Think of an agreement as similar to a bonus, gift card, prepaid card, or check:
@@ -148,7 +158,13 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
148158 // By locking in fees during agreement creation, the protocol avoids scenarios
149159 // where fee structures change (favorably or unfavorably) after creation,
150160 // which could lead to abuse or exploitation.
151- uint256 deductions = _calcFees (amount, arbiter, currency);
161+ uint256 baseFees = _calcFees (amount, arbiter, currency);
162+ // Even if we are covered by gas fees, during execution a good way to avoid abuse
163+ // is penalize parties after N length eg. The max parties allowed is 5, any extra
164+ // parties are charged with a extra * fee. Denial of Service risk mitigation..
165+ uint256 penalization = _calculatePenalization (parties.length , amount);
166+ uint256 totalToLock = amount + penalization;
167+
152168 // This design ensures fairness and transparency by preventing any future
153169 // adjustments to fees or protocol conditions from affecting the terms of this agreement.
154170 return
@@ -157,8 +173,9 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
157173 currency: currency, // the currency used in transaction
158174 initiator: msg .sender , // the tx initiator
159175 total: amount, // the transaction amount
160- fees: deductions, // the protocol fees of the agreement
161- parties: parties, // the accounts related to agreement
176+ fees: baseFees, // the protocol fees of the agreement
177+ locked: totalToLock, // the total to lock, may contain penalization
178+ parties: parties, // the additional accounts related to agreement 1:N agreement
162179 payload: payload // any additional data needed during agreement execution
163180 });
164181 }
@@ -177,6 +194,33 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
177194 return proof;
178195 }
179196
197+ /// @dev Calculates the penalization based on parties len and total amount
198+ function _calculatePenalization (uint256 partiesLen , uint256 amount ) private view returns (uint256 penalization ) {
199+ uint256 hardCap = _maxParties + MAX_EXCESS;
200+ if (partiesLen > hardCap) revert ExceedsMaxParties ();
201+
202+ // soft cap validation, economic penalization
203+ if (partiesLen > _maxParties) {
204+ uint256 excess = partiesLen - _maxParties;
205+ uint256 multiplierBps = _penaltyBps (excess);
206+ penalization = amount.perOf (multiplierBps);
207+ }
208+ }
209+
210+ /// @dev Computes the penalty BPS as a arithmetic succession.
211+ /// 1st extra = 1%, 2nd extra = +2%, 3rd extra = +3%, ...
212+ /// Formula: (N * (N + 1) / 2) * 100
213+ /// @param excess Number of parties beyond the allowed max.
214+ /// @return penaltyBps Total penalty in basis points.
215+ function _penaltyBps (uint256 excess ) private pure returns (uint256 penaltyBps ) {
216+ if (excess == 0 ) return 0 ;
217+ // Formula for the sum of an arithmetic succession: S = n(n + 1) / 2
218+ // Example: excess = 3 -> 1 + 2 + 3 = 6 %
219+ unchecked {
220+ penaltyBps = ((excess * (excess + 1 )) / 2 ) * 100 ;
221+ }
222+ }
223+
180224 /// @notice Calculates the fee based on the provided total amount, agent, and currency.
181225 /// @dev Reverts if the currency is not supported by the Tollgate or if no fee scheme is defined for the agent.
182226 /// @param total The total amount from which the fee will be calculated.
@@ -185,7 +229,6 @@ contract AgreementManager is Initializable, UUPSUpgradeable, AccessControlledUpg
185229 /// @return The calculated fee amount based on the applicable fee scheme.
186230 function _calcFees (uint256 total , address target , address currency ) private view returns (uint256 ) {
187231 // !IMPORTANT if fees manager does not support the currency or the target, will revert..
188- // TODO avoid revert, just check if the currency is supported and return 0
189232 (uint256 fees , T.Scheme scheme ) = TOLLGATE.getFees (target, currency);
190233 if (scheme == T.Scheme.BPS) return total.perOf (fees); // bps calc
191234 if (scheme == T.Scheme.NOMINAL) return total.perOf (fees.calcBps ()); // nominal to bps
0 commit comments