Skip to content

Commit 9986127

Browse files
committed
feat: reserved funds
1 parent a5ad567 commit 9986127

14 files changed

Lines changed: 214 additions & 33 deletions

File tree

contracts/assets/AssetVault.sol

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ contract AssetVault is Initializable, UUPSUpgradeable, AccessControlledUpgradeab
6060
/// @param data The secure content to store, represented as bytes.
6161
function setContent(uint256 assetId, T.VaultType vault, bytes memory data) external onlyHolder(assetId) {
6262
_secured[assetId][vault] = data;
63+
// TODO emit event
6364
}
6465

6566
/// @notice Function that authorizes the contract upgrade. It ensures that only the admin

contracts/core/HookRegistry.sol

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
// IAccessHook with preset rules of access for holders account or assets
2+
// eg: holder can set if under some conditions the user can access
3+
// - time locked free access => access to my content for the first 7 days if not already accessed
4+
// - gated access conditions => hold X NFT and access my asset id 123
5+
// - locked access to N user
6+
7+
// ISplitHook with earning splits

contracts/core/interfaces/base/IBalanceOperator.sol

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,19 @@ pragma solidity 0.8.26;
77
import { IBalanceDepositor } from "@synaps3/core/interfaces/base/IBalanceDepositor.sol";
88
import { IBalanceWithdrawable } from "@synaps3/core/interfaces/base/IBalanceWithdrawable.sol";
99
import { IBalanceTransferable } from "@synaps3/core/interfaces/base/IBalanceTransferable.sol";
10+
import { IBalanceVerifiable } from "@synaps3/core/interfaces/base/IBalanceVerifiable.sol";
11+
import { IBalanceReservable } from "@synaps3/core/interfaces/base/IBalanceReservable.sol";
12+
import { ILedgerVerifiable } from "@synaps3/core/interfaces/base/ILedgerVerifiable.sol";
1013

1114
/// @dev The `IBalanceOperator` interface extends multiple interfaces to provide a comprehensive suite of
12-
/// balance-related operations, including deposit, withdrawal, transfer, and balance verification.
13-
interface IBalanceOperator is IBalanceWithdrawable, IBalanceDepositor, IBalanceTransferable {}
15+
/// balance-related operations, including deposit, withdrawal, transfer, reserve, and balance verification.
16+
interface IBalanceOperator is
17+
IBalanceWithdrawable,
18+
IBalanceDepositor,
19+
IBalanceTransferable,
20+
IBalanceReservable,
21+
IBalanceVerifiable,
22+
ILedgerVerifiable
23+
{
24+
25+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.26;
3+
4+
/// @title IBalanceReservable
5+
/// @notice Interface for reserving and collecting funds in a ledger-based system.
6+
/// @dev This interface defines the standard methods for managing fund reservations and collections
7+
/// within a ledger. Implementing contracts are expected to handle the actual storage and logic
8+
/// of reservations while adhering to this interface.
9+
interface IBalanceReservable {
10+
/// @notice Emitted when funds are reserved between two accounts.
11+
/// @dev Indicates that a specified amount of currency has been reserved from one account to another.
12+
/// @param from The address of the account from which the funds are reserved.
13+
/// @param to The address of the account for which the funds are reserved.
14+
/// @param amount The amount of funds reserved.
15+
/// @param currency The address of the currency in which the funds are reserved.
16+
event FundsReserved(address indexed from, address indexed to, uint256 amount, address indexed currency);
17+
18+
/// @notice Emitted when reserved funds are successfully collected by a recipient.
19+
/// @dev Indicates that a specified amount of previously reserved funds has been collected by the recipient.
20+
/// @param from The address of the account from which the funds are collected.
21+
/// @param to The address of the account that collected the reserved funds.
22+
/// @param amount The amount of funds collected.
23+
/// @param currency The address of the currency in which the funds are collected.
24+
event FundsCollected(address indexed from, address indexed to, uint256 amount, address indexed currency);
25+
26+
/// @notice Thrown when there are no available funds to reserve.
27+
/// @dev This error occurs if an account attempts to reserve more funds than available.
28+
error NoFundsToReserve();
29+
30+
/// @notice Thrown when there are no reserved funds available to release.
31+
/// @dev This error occurs if an operator tries to collected funds that are not reserved or insufficient.
32+
error NoFundsToCollect();
33+
34+
/// @notice Reserves a specific amount of funds from the caller's balance for a recipient.
35+
/// @param to The address of the recipient for whom the funds are being reserved.
36+
/// @param amount The amount of funds to reserve.
37+
/// @param currency The address of the ERC20 token to reserve. Use `address(0)` for native tokens.
38+
function reserve(address to, uint256 amount, address currency) external returns (uint256);
39+
40+
/// @notice Collects a specific amount of previously reserved funds.
41+
/// @param from The address of the account from which the reserved funds are being collected.
42+
/// @param amount The amount of funds to collect.
43+
/// @param currency The address of the ERC20 token to collect. Use `address(0)` for native tokens.
44+
function collect(address from, uint256 amount, address currency) external returns (uint256);
45+
}

contracts/core/interfaces/base/IBalanceTransferable.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ pragma solidity 0.8.26;
44
/// @title IBalanceTransferable Interface
55
/// @notice This interface defines the functionality for transferring balances between accounts.
66
interface IBalanceTransferable {
7-
/// @notice Error indicating that there are no funds available to transfer.
8-
/// @dev This error is triggered when a transfer is attempted but the sender has insufficient funds or no balance.
9-
error NoFundsToTransfer();
10-
117
/// @notice Emitted when funds are successfully transferred between accounts.
128
/// @param recipient The address of the account receiving the funds.
139
/// @param origin The address of the account initiating the transfer.
1410
/// @param amount The amount of currency transferred.
1511
/// @param currency The address of the ERC20 token transferred. Use `address(0)` for native tokens.
1612
event FundsTransferred(address indexed recipient, address indexed origin, uint256 amount, address currency);
1713

14+
/// @notice Error indicating that there are no funds available to transfer.
15+
/// @dev This error is triggered when a transfer is attempted but the sender has insufficient funds or no balance.
16+
error NoFundsToTransfer();
17+
1818
/// @notice Transfers a specified amount of currency from the caller's balance to a given recipient.
1919
/// @dev Ensures the caller has sufficient balance before performing the transfer. Updates the ledger accordingly.
2020
/// @param recipient The address of the account to credit with the transfer.

contracts/core/interfaces/base/IBalanceWithdrawable.sol

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,17 @@ pragma solidity 0.8.26;
44
/// @title IBalanceWithdrawable Interface
55
/// @notice This interface defines the functionality for withdrawing funds from the contract to a specified address.
66
interface IBalanceWithdrawable {
7-
/// @notice Error indicating that there are no funds available to withdraw.
8-
/// @dev This error is triggered when a withdrawal is attempted but the contract has insufficient funds.
9-
error NoFundsToWithdraw();
10-
117
/// @notice Emitted when funds are withdrawn from the contract.
128
/// @param recipient The address receiving the withdrawn funds.
139
/// @param origin The address sending the withdrawn funds.
1410
/// @param amount The amount of funds being withdrawn.
1511
/// @param currency The currency used for the withdrawal.
1612
event FundsWithdrawn(address indexed recipient, address indexed origin, uint256 amount, address indexed currency);
1713

14+
/// @notice Error indicating that there are no funds available to withdraw.
15+
/// @dev This error is triggered when a withdrawal is attempted but the contract has insufficient funds.
16+
error NoFundsToWithdraw();
17+
1818
/// @notice Withdraws tokens from the contract to a specified recipient's address.
1919
/// @param recipient The address that will receive the withdrawn tokens.
2020
/// @param amount The amount of tokens to withdraw.

contracts/core/interfaces/financial/ILedgerVault.sol

Lines changed: 1 addition & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,11 @@
22
pragma solidity 0.8.26;
33

44
import { IBalanceOperator } from "@synaps3/core/interfaces/base/IBalanceOperator.sol";
5-
import { ILedgerVerifiable } from "@synaps3/core/interfaces/base/ILedgerVerifiable.sol";
65

76
/// @title ILedgerVault
87
/// @notice Interface for managing locked funds and their operations.
98
/// @dev Extends IBalanceOperator for managing user balances in a vault-like system.
10-
interface ILedgerVault is IBalanceOperator, ILedgerVerifiable {
9+
interface ILedgerVault is IBalanceOperator {
1110
/// @notice Locks a specific amount of funds for a given account.
1211
/// @dev The funds are immobilized and cannot be withdrawn or transferred until released or claimed.
1312
/// @param account The address of the account for which the funds will be locked.
@@ -27,19 +26,4 @@ interface ILedgerVault is IBalanceOperator, ILedgerVerifiable {
2726
/// @param amount The amount of funds to release.
2827
/// @param currency The currency to associate release with. Use address(0) for the native coin.
2928
function release(address account, uint256 amount, address currency) external returns (uint256);
30-
31-
// /// @notice Settles a financial operation by claiming and transferring funds.
32-
// /// @dev Claims locked funds and transfers available balance to the counterparty.
33-
// /// @param initiator The address of the account whose funds are being claimed.
34-
// /// @param counterparty The address that will receive the transferred funds.
35-
// /// @param total The total amount of funds to claim.
36-
// /// @param available The amount of funds to transfer to the counterparty.
37-
// /// @param currency The address of the currency for the operation. Use `address(0)` for native tokens.
38-
// function settle(
39-
// address initiator,
40-
// address counterparty,
41-
// uint256 total,
42-
// uint256 available,
43-
// address currency
44-
// ) external returns (uint256);
4529
}

contracts/core/libraries/FinancialOps.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ library FinancialOps {
6767
}
6868

6969
if (amount > allowance(from, token)) revert FailDuringDeposit("Amount exceeds allowance.");
70-
// disable slitter use 'arbitraty transfer form' since the use of `safeDeposit` is handled in a safe manner.
70+
// disable slitter use 'arbitrary transfer form' since the use of `safeDeposit` is handled in a safe manner.
7171
// eg. msg.sender.safeDeposit(total, currency); <- Use msg.sender as from in transferFrom.
7272
// slither-disable-next-line arbitrary-send-erc20
7373
IERC20(token).safeTransferFrom(from, address(this), amount);

contracts/core/primitives/upgradeable/AccessControlledUpgradeable.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ abstract contract AccessControlledUpgradeable is Initializable, AccessManagedUpg
3030
// !WARNING The restricted modifier should never be used on internal functions,
3131
// judiciously used in public functions, and ideally only used in external functions.
3232
// See restricted:
33-
// Since we can't use the `restricted` modifier and we still need to check the admin role..
33+
// -> Since we can't use the `restricted` modifier and we still need to check the admin role..
3434
if (!_hasRole(C.ADMIN_ROLE, msg.sender)) {
3535
revert InvalidUnauthorizedOperation("Only admin can perform this action.");
3636
}

contracts/core/primitives/upgradeable/BalanceOperatorUpgradeable.sol

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,16 @@ import { FinancialOps } from "@synaps3/core/libraries/FinancialOps.sol";
1515
abstract contract BalanceOperatorUpgradeable is Initializable, LedgerUpgradeable, IBalanceOperator {
1616
using FinancialOps for address;
1717

18+
/// @custom:storage-location erc7201:balanceoperatorupgradeable
19+
struct BalanceOperatorStorage {
20+
/// @dev Holds the relation between a reserved funds the currency and amount
21+
mapping(bytes32 => mapping(address => uint256)) _reserved;
22+
}
23+
24+
/// @dev Storage slot for BalanceOperatorUpgradeable, calculated using a unique namespace to avoid conflicts.
25+
/// The `BALANCE_OPERATOR_SLOT` constant is used to point to the location of the storage.
26+
bytes32 private constant BALANCE_OPERATOR_SLOT = 0xa8707513830ffbd3c47e0c83d1f5f0270db240ae37bb1f9a13f077f85b949c00;
27+
1828
/// @dev Initializes the contract and ensures it is upgradeable.
1929
/// Even if the initialization is harmless, this ensures the contract follows upgradeable contract patterns.
2030
/// This is the method to initialize this contract and any other extended contracts.
@@ -67,4 +77,79 @@ abstract contract BalanceOperatorUpgradeable is Initializable, LedgerUpgradeable
6777
_sumLedgerEntry(recipient, amount, currency);
6878
return amount;
6979
}
80+
81+
/// @notice Reserves a specific amount of funds from the caller's balance for a recipient.
82+
/// @param to The address of the recipient for whom the funds are being reserved.
83+
/// @param amount The amount of funds to reserve.
84+
/// @param currency The address of the ERC20 token to reserve. Use `address(0)` for native tokens.
85+
function _reserve(address to, uint256 amount, address currency) internal returns (uint256) {
86+
if (getLedgerBalance(msg.sender, currency) < amount) revert NoFundsToReserve();
87+
_subLedgerEntry(msg.sender, amount, currency);
88+
_sumReservedAmount(msg.sender, to, amount, currency);
89+
return amount;
90+
}
91+
92+
/// @notice Collects a specific amount of previously reserved funds.
93+
/// @param from The address of the account from which the reserved funds are being collected.
94+
/// @param amount The amount of funds to collect.
95+
/// @param currency The address of the ERC20 token to collect. Use `address(0)` for native tokens.
96+
function _collect(address from, uint256 amount, address currency) internal returns (uint256) {
97+
if (_getReservedAmount(from, msg.sender, currency) < amount) revert NoFundsToCollect();
98+
_subReservedAmount(from, msg.sender, amount, currency);
99+
_sumLedgerEntry(msg.sender, amount, currency);
100+
return amount;
101+
}
102+
103+
/// @notice Reduces the reserved funds for a specific relationship and currency.
104+
/// @dev Deducts the specified `amount` from the `_reserved` mapping for the given `from` and `to` relationship and `currency`.
105+
/// @param from The address of the account from which the funds were reserved.
106+
/// @param to The address of the account for which the funds were reserved.
107+
/// @param amount The amount to subtract from the reserved balance.
108+
/// @param currency The address of the currency being reduced.
109+
function _subReservedAmount(address from, address to, uint256 amount, address currency) private {
110+
BalanceOperatorStorage storage $ = _getBalanceOperatorStorage();
111+
bytes32 relation = _computeComposedKey(from, to);
112+
$._reserved[relation][currency] -= amount;
113+
}
114+
115+
/// @notice Increases the reserved funds for a specific relationship and currency.
116+
/// @dev Adds the specified `amount` to the `_reserved` mapping for the given `from` and `to` relationship and `currency`.
117+
/// @param from The address of the account from which the funds are reserved.
118+
/// @param to The address of the account for which the funds are reserved.
119+
/// @param amount The amount to add to the reserved balance.
120+
/// @param currency The address of the currency being increased.
121+
function _sumReservedAmount(address from, address to, uint256 amount, address currency) private {
122+
BalanceOperatorStorage storage $ = _getBalanceOperatorStorage();
123+
bytes32 relation = _computeComposedKey(from, to);
124+
$._reserved[relation][currency] += amount;
125+
}
126+
127+
/// @notice Retrieves the reserved balance for a specific relationship and currency.
128+
/// @dev Returns the value stored in the `_reserved` mapping for the given `from` and `to` relationship and `currency`.
129+
/// @param from The address of the account from which the funds are reserved.
130+
/// @param to The address of the account for which the funds are reserved.
131+
/// @param currency The address of the currency to check the reserved balance for.
132+
/// @return The reserved balance for the specified relationship and currency.
133+
function _getReservedAmount(address from, address to, address currency) private view returns (uint256) {
134+
BalanceOperatorStorage storage $ = _getBalanceOperatorStorage();
135+
bytes32 relation = _computeComposedKey(from, to);
136+
return $._reserved[relation][currency];
137+
}
138+
139+
/// @notice Computes a unique key by combining two addresses.
140+
/// @dev This key is used to map relationships between accounts.
141+
/// @param from The address of the user for whom the key is being generated.
142+
/// @param to Encoded data representing the context for the operation.
143+
/// @return A `bytes32` hash that uniquely identifies the context-account pair.
144+
function _computeComposedKey(address from, address to) private pure returns (bytes32) {
145+
return keccak256(abi.encodePacked(from, to));
146+
}
147+
148+
/// @notice Internal function to get the balance operator storage.
149+
/// @dev Uses assembly to retrieve the storage at the pre-calculated storage slot.
150+
function _getBalanceOperatorStorage() private pure returns (BalanceOperatorStorage storage $) {
151+
assembly {
152+
$.slot := BALANCE_OPERATOR_SLOT
153+
}
154+
}
70155
}

0 commit comments

Comments
 (0)