2025-01-28 16:17:14 +01:00

241 lines
6.9 KiB
Solidity

// 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";
import "./TokensPerSecond.sol";
import "./Flows.sol";
import "./Locks.sol";
using SafeERC20 for IERC20;
using Timestamps for Timestamp;
using Flows for Flow;
using Locks for Lock;
abstract contract VaultBase {
IERC20 internal immutable _token;
type Controller is address;
type Context is bytes32;
type Recipient is address;
struct Balance {
uint128 available;
uint128 designated;
}
mapping(Controller => mapping(Context => Lock)) private _locks;
mapping(Controller => mapping(Context => mapping(Recipient => Balance)))
private _balances;
mapping(Controller => mapping(Context => mapping(Recipient => Flow)))
private _flows;
constructor(IERC20 token) {
_token = token;
}
function _getBalance(
Controller controller,
Context context,
Recipient recipient
) internal view returns (Balance memory) {
Balance storage balance = _balances[controller][context][recipient];
Flow storage flow = _flows[controller][context][recipient];
Lock storage lock = _locks[controller][context];
Timestamp timestamp = Timestamps.currentTime();
return _getBalanceAt(balance, flow, lock, timestamp);
}
function _getBalanceAt(
Balance memory balance,
Flow memory flow,
Lock storage lock,
Timestamp timestamp
) private view returns (Balance memory) {
Balance memory result = balance;
if (flow.rate != TokensPerSecond.wrap(0)) {
Timestamp end = Timestamps.earliest(timestamp, lock.expiry);
int128 accumulated = flow._totalAt(end);
if (accumulated >= 0) {
result.designated += uint128(accumulated);
} else {
result.available -= uint128(-accumulated);
}
}
return result;
}
function _getLock(
Controller controller,
Context context
) internal view returns (Lock memory) {
return _locks[controller][context];
}
function _deposit(
Controller controller,
Context context,
address from,
uint128 amount
) internal {
Recipient recipient = Recipient.wrap(from);
_balances[controller][context][recipient].available += amount;
_token.safeTransferFrom(from, address(this), amount);
}
function _delete(
Controller controller,
Context context,
Recipient recipient
) private {
delete _balances[controller][context][recipient];
delete _flows[controller][context][recipient];
}
function _withdraw(
Controller controller,
Context context,
Recipient recipient
) internal {
require(!_locks[controller][context].isLocked(), Locked());
delete _locks[controller][context];
Balance memory balance = _getBalance(controller, context, recipient);
uint128 amount = balance.available + balance.designated;
_delete(controller, context, recipient);
_token.safeTransfer(Recipient.unwrap(recipient), amount);
}
function _burn(
Controller controller,
Context context,
Recipient recipient
) internal {
Flow memory flow = _flows[controller][context][recipient];
require(flow.rate == TokensPerSecond.wrap(0), CannotBurnFlowingTokens());
Balance memory balance = _getBalance(controller, context, recipient);
uint128 amount = balance.available + balance.designated;
_delete(controller, context, recipient);
_token.safeTransfer(address(0xdead), amount);
}
function _transfer(
Controller controller,
Context context,
Recipient from,
Recipient to,
uint128 amount
) internal {
Balance memory senderBalance = _getBalance(controller, context, from);
Balance memory receiverBalance = _getBalance(controller, context, to);
require(amount <= senderBalance.available, InsufficientBalance());
senderBalance.available -= amount;
receiverBalance.available += amount;
Flow memory senderFlow = _flows[controller][context][from];
Lock memory lock = _locks[controller][context];
_checkFlowInvariant(senderBalance, lock, senderFlow);
_balances[controller][context][from] = senderBalance;
_balances[controller][context][to] = receiverBalance;
}
function _designate(
Controller controller,
Context context,
Recipient recipient,
uint128 amount
) internal {
Balance storage balance = _balances[controller][context][recipient];
require(amount <= balance.available, InsufficientBalance());
balance.available -= amount;
balance.designated += amount;
}
function _lockup(
Controller controller,
Context context,
Timestamp expiry,
Timestamp maximum
) internal {
Lock memory lock = _locks[controller][context];
require(lock.maximum == Timestamp.wrap(0), AlreadyLocked());
lock.expiry = expiry;
lock.maximum = maximum;
_checkLockInvariant(lock);
_locks[controller][context] = lock;
}
function _extendLock(
Controller controller,
Context context,
Timestamp expiry
) internal {
Lock memory lock = _locks[controller][context];
require(lock.isLocked(), LockRequired());
require(lock.expiry <= expiry, InvalidExpiry());
lock.expiry = expiry;
_checkLockInvariant(lock);
_locks[controller][context] = lock;
}
function _flow(
Controller controller,
Context context,
Recipient from,
Recipient to,
TokensPerSecond rate
) internal {
require(rate >= TokensPerSecond.wrap(0), NegativeFlow());
Lock memory lock = _locks[controller][context];
require(lock.isLocked(), LockRequired());
Balance memory senderBalance = _getBalance(controller, context, from);
Balance memory receiverBalance = _getBalance(controller, context, to);
Flow memory senderFlow = _flows[controller][context][from];
Flow memory receiverFlow = _flows[controller][context][to];
Timestamp start = Timestamps.currentTime();
senderFlow.start = start;
senderFlow.rate = senderFlow.rate - rate;
receiverFlow.start = start;
receiverFlow.rate = receiverFlow.rate + rate;
_checkFlowInvariant(senderBalance, lock, senderFlow);
_balances[controller][context][from] = senderBalance;
_balances[controller][context][to] = receiverBalance;
_flows[controller][context][from] = senderFlow;
_flows[controller][context][to] = receiverFlow;
}
function _checkLockInvariant(Lock memory lock) private pure {
require(lock.expiry <= lock.maximum, ExpiryPastMaximum());
}
function _checkFlowInvariant(
Balance memory balance,
Lock memory lock,
Flow memory flow
) private pure {
if (flow.rate < TokensPerSecond.wrap(0)) {
uint128 outgoing = uint128(-flow._totalAt(lock.maximum));
require(outgoing <= balance.available, InsufficientBalance());
}
}
error InsufficientBalance();
error Locked();
error AlreadyLocked();
error ExpiryPastMaximum();
error InvalidExpiry();
error LockRequired();
error NegativeFlow();
error CannotBurnFlowingTokens();
}