2025-01-22 11:32:22 +01:00
|
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
pragma solidity 0.8.28;
|
|
|
|
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
|
|
import "./Timestamps.sol";
|
2025-01-22 15:07:51 +01:00
|
|
|
import "./TokenFlows.sol";
|
2025-01-22 11:32:22 +01:00
|
|
|
|
|
|
|
using SafeERC20 for IERC20;
|
|
|
|
using Timestamps for Timestamp;
|
2025-01-22 15:07:51 +01:00
|
|
|
using TokenFlows for TokensPerSecond;
|
2025-01-22 11:32:22 +01:00
|
|
|
|
|
|
|
abstract contract VaultBase {
|
|
|
|
IERC20 internal immutable _token;
|
|
|
|
|
|
|
|
type Controller is address;
|
|
|
|
type Context is bytes32;
|
|
|
|
type Recipient is address;
|
|
|
|
|
|
|
|
struct Lock {
|
|
|
|
Timestamp expiry;
|
|
|
|
Timestamp maximum;
|
|
|
|
}
|
|
|
|
|
2025-01-22 15:07:51 +01:00
|
|
|
struct Flow {
|
|
|
|
Timestamp start;
|
|
|
|
TokensPerSecond rate;
|
|
|
|
}
|
|
|
|
|
2025-01-22 11:32:22 +01:00
|
|
|
mapping(Controller => mapping(Context => Lock)) private _locks;
|
|
|
|
mapping(Controller => mapping(Context => mapping(Recipient => uint256)))
|
|
|
|
private _available;
|
|
|
|
mapping(Controller => mapping(Context => mapping(Recipient => uint256)))
|
|
|
|
private _designated;
|
2025-01-22 15:07:51 +01:00
|
|
|
mapping(Controller => mapping(Context => mapping(Recipient => Flow)))
|
|
|
|
private _flows;
|
2025-01-22 11:32:22 +01:00
|
|
|
|
|
|
|
constructor(IERC20 token) {
|
|
|
|
_token = token;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _getBalance(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Recipient recipient
|
|
|
|
) internal view returns (uint256) {
|
2025-01-22 15:07:51 +01:00
|
|
|
Flow memory flow = _flows[controller][context][recipient];
|
|
|
|
int256 flowed = flow.rate.accumulated(flow.start, Timestamps.currentTime());
|
|
|
|
uint256 available = _available[controller][context][recipient];
|
|
|
|
uint256 designated = _designated[controller][context][recipient];
|
|
|
|
if (flowed >= 0) {
|
|
|
|
return available + designated + uint256(flowed);
|
|
|
|
} else {
|
|
|
|
return available + designated - uint256(-flowed);
|
|
|
|
}
|
2025-01-22 11:32:22 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
function _getDesignated(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Recipient recipient
|
|
|
|
) internal view returns (uint256) {
|
|
|
|
return _designated[controller][context][recipient];
|
|
|
|
}
|
|
|
|
|
|
|
|
function _getLock(
|
|
|
|
Controller controller,
|
|
|
|
Context context
|
|
|
|
) internal view returns (Lock memory) {
|
|
|
|
return _locks[controller][context];
|
|
|
|
}
|
|
|
|
|
|
|
|
function _deposit(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
address from,
|
|
|
|
uint256 amount
|
|
|
|
) internal {
|
|
|
|
Recipient recipient = Recipient.wrap(from);
|
|
|
|
_available[controller][context][recipient] += amount;
|
|
|
|
_token.safeTransferFrom(from, address(this), amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _delete(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Recipient recipient
|
|
|
|
) private {
|
|
|
|
delete _available[controller][context][recipient];
|
|
|
|
delete _designated[controller][context][recipient];
|
|
|
|
}
|
|
|
|
|
|
|
|
function _withdraw(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Recipient recipient
|
|
|
|
) internal {
|
|
|
|
require(!_getLock(controller, context).expiry.isFuture(), Locked());
|
|
|
|
delete _locks[controller][context];
|
|
|
|
uint256 amount = _getBalance(controller, context, recipient);
|
|
|
|
_delete(controller, context, recipient);
|
|
|
|
_token.safeTransfer(Recipient.unwrap(recipient), amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _burn(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Recipient recipient
|
|
|
|
) internal {
|
|
|
|
uint256 amount = _getBalance(controller, context, recipient);
|
|
|
|
_delete(controller, context, recipient);
|
|
|
|
_token.safeTransfer(address(0xdead), amount);
|
|
|
|
}
|
|
|
|
|
|
|
|
function _transfer(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Recipient from,
|
|
|
|
Recipient to,
|
|
|
|
uint256 amount
|
|
|
|
) internal {
|
|
|
|
require(
|
|
|
|
amount <= _available[controller][context][from],
|
|
|
|
InsufficientBalance()
|
|
|
|
);
|
|
|
|
_available[controller][context][from] -= amount;
|
|
|
|
_available[controller][context][to] += amount;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _designate(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Recipient recipient,
|
|
|
|
uint256 amount
|
|
|
|
) internal {
|
|
|
|
require(
|
|
|
|
amount <= _available[controller][context][recipient],
|
|
|
|
InsufficientBalance()
|
|
|
|
);
|
|
|
|
_available[controller][context][recipient] -= amount;
|
|
|
|
_designated[controller][context][recipient] += amount;
|
|
|
|
}
|
|
|
|
|
|
|
|
function _lockup(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Timestamp expiry,
|
|
|
|
Timestamp maximum
|
|
|
|
) internal {
|
|
|
|
require(
|
|
|
|
Timestamp.unwrap(_getLock(controller, context).maximum) == 0,
|
|
|
|
AlreadyLocked()
|
|
|
|
);
|
|
|
|
require(!expiry.isAfter(maximum), ExpiryPastMaximum());
|
|
|
|
_locks[controller][context] = Lock({expiry: expiry, maximum: maximum});
|
|
|
|
}
|
|
|
|
|
|
|
|
function _extendLock(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Timestamp expiry
|
|
|
|
) internal {
|
|
|
|
Lock memory previous = _getLock(controller, context);
|
|
|
|
require(previous.expiry.isFuture(), LockExpired());
|
|
|
|
require(!previous.expiry.isAfter(expiry), InvalidExpiry());
|
|
|
|
require(!expiry.isAfter(previous.maximum), ExpiryPastMaximum());
|
|
|
|
_locks[controller][context].expiry = expiry;
|
|
|
|
}
|
|
|
|
|
2025-01-22 15:07:51 +01:00
|
|
|
function _flow(
|
|
|
|
Controller controller,
|
|
|
|
Context context,
|
|
|
|
Recipient from,
|
|
|
|
Recipient to,
|
|
|
|
TokensPerSecond rate
|
|
|
|
) internal {
|
|
|
|
Timestamp start = Timestamps.currentTime();
|
|
|
|
_flows[controller][context][to] = Flow({start: start, rate: rate});
|
|
|
|
_flows[controller][context][from] = Flow({start: start, rate: -rate});
|
|
|
|
}
|
|
|
|
|
2025-01-22 11:32:22 +01:00
|
|
|
error InsufficientBalance();
|
|
|
|
error Locked();
|
|
|
|
error AlreadyLocked();
|
|
|
|
error ExpiryPastMaximum();
|
|
|
|
error InvalidExpiry();
|
|
|
|
error LockExpired();
|
|
|
|
}
|