mirror of
https://github.com/status-im/codex-contracts-eth.git
synced 2025-02-07 06:04:26 +00:00
241 lines
6.9 KiB
Solidity
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();
|
|
}
|