mirror of
https://github.com/status-im/codex-contracts-eth.git
synced 2025-02-14 17:36:56 +00:00
- transfer ERC20 funds into the vault from the controller, not from the user - prevents an attacker from hijacking a user's ERC20 approval to move tokens into a part of the vault that is controlled by the attacker
224 lines
5.7 KiB
Solidity
224 lines
5.7 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 "./Accounts.sol";
|
|
import "./Timestamps.sol";
|
|
import "./TokenFlows.sol";
|
|
import "./Locks.sol";
|
|
|
|
using SafeERC20 for IERC20;
|
|
using Timestamps for Timestamp;
|
|
using Accounts for Account;
|
|
using Locks for Lock;
|
|
|
|
abstract contract VaultBase {
|
|
IERC20 internal immutable _token;
|
|
|
|
type Controller is address;
|
|
type Fund is bytes32;
|
|
type Recipient is address;
|
|
|
|
mapping(Controller => mapping(Fund => Lock)) private _locks;
|
|
mapping(Controller => mapping(Fund => mapping(Recipient => Account)))
|
|
private _accounts;
|
|
|
|
constructor(IERC20 token) {
|
|
_token = token;
|
|
}
|
|
|
|
function _getLock(
|
|
Controller controller,
|
|
Fund fund
|
|
) internal view returns (Lock memory) {
|
|
return _locks[controller][fund];
|
|
}
|
|
|
|
function _getBalance(
|
|
Controller controller,
|
|
Fund fund,
|
|
Recipient recipient
|
|
) internal view returns (Balance memory) {
|
|
Account memory account = _accounts[controller][fund][recipient];
|
|
Lock memory lock = _locks[controller][fund];
|
|
if (lock.isLocked()) {
|
|
account.update(Timestamps.currentTime());
|
|
} else {
|
|
account.update(lock.expiry);
|
|
}
|
|
return account.balance;
|
|
}
|
|
|
|
function _lock(
|
|
Controller controller,
|
|
Fund fund,
|
|
Timestamp expiry,
|
|
Timestamp maximum
|
|
) internal {
|
|
Lock memory lock = _locks[controller][fund];
|
|
require(lock.maximum == Timestamp.wrap(0), AlreadyLocked());
|
|
lock.expiry = expiry;
|
|
lock.maximum = maximum;
|
|
_checkLockInvariant(lock);
|
|
_locks[controller][fund] = lock;
|
|
}
|
|
|
|
function _extendLock(
|
|
Controller controller,
|
|
Fund fund,
|
|
Timestamp expiry
|
|
) internal {
|
|
Lock memory lock = _locks[controller][fund];
|
|
require(lock.isLocked(), LockRequired());
|
|
require(lock.expiry <= expiry, InvalidExpiry());
|
|
lock.expiry = expiry;
|
|
_checkLockInvariant(lock);
|
|
_locks[controller][fund] = lock;
|
|
}
|
|
|
|
function _deposit(
|
|
Controller controller,
|
|
Fund fund,
|
|
Recipient recipient,
|
|
uint128 amount
|
|
) internal {
|
|
Lock storage lock = _locks[controller][fund];
|
|
require(lock.isLocked(), LockRequired());
|
|
|
|
Account storage account = _accounts[controller][fund][recipient];
|
|
|
|
account.balance.available += amount;
|
|
lock.value += amount;
|
|
|
|
_token.safeTransferFrom(
|
|
Controller.unwrap(controller),
|
|
address(this),
|
|
amount
|
|
);
|
|
}
|
|
|
|
function _designate(
|
|
Controller controller,
|
|
Fund fund,
|
|
Recipient recipient,
|
|
uint128 amount
|
|
) internal {
|
|
Lock memory lock = _locks[controller][fund];
|
|
require(lock.isLocked(), LockRequired());
|
|
|
|
Account memory account = _accounts[controller][fund][recipient];
|
|
require(amount <= account.balance.available, InsufficientBalance());
|
|
|
|
account.balance.available -= amount;
|
|
account.balance.designated += amount;
|
|
_checkAccountInvariant(account, lock);
|
|
|
|
_accounts[controller][fund][recipient] = account;
|
|
}
|
|
|
|
function _transfer(
|
|
Controller controller,
|
|
Fund fund,
|
|
Recipient from,
|
|
Recipient to,
|
|
uint128 amount
|
|
) internal {
|
|
Lock memory lock = _locks[controller][fund];
|
|
require(lock.isLocked(), LockRequired());
|
|
|
|
Account memory sender = _accounts[controller][fund][from];
|
|
require(amount <= sender.balance.available, InsufficientBalance());
|
|
|
|
sender.balance.available -= amount;
|
|
_checkAccountInvariant(sender, lock);
|
|
|
|
_accounts[controller][fund][from] = sender;
|
|
|
|
_accounts[controller][fund][to].balance.available += amount;
|
|
}
|
|
|
|
function _flow(
|
|
Controller controller,
|
|
Fund fund,
|
|
Recipient from,
|
|
Recipient to,
|
|
TokensPerSecond rate
|
|
) internal {
|
|
Lock memory lock = _locks[controller][fund];
|
|
require(lock.isLocked(), LockRequired());
|
|
|
|
Account memory sender = _accounts[controller][fund][from];
|
|
sender.flowOut(rate);
|
|
_checkAccountInvariant(sender, lock);
|
|
_accounts[controller][fund][from] = sender;
|
|
|
|
Account memory receiver = _accounts[controller][fund][to];
|
|
receiver.flowIn(rate);
|
|
_accounts[controller][fund][to] = receiver;
|
|
}
|
|
|
|
function _burn(
|
|
Controller controller,
|
|
Fund fund,
|
|
Recipient recipient
|
|
) internal {
|
|
Lock storage lock = _locks[controller][fund];
|
|
require(lock.isLocked(), LockRequired());
|
|
|
|
Account memory account = _accounts[controller][fund][recipient];
|
|
require(account.flow.incoming == account.flow.outgoing, FlowMustBeZero());
|
|
uint128 amount = account.balance.available + account.balance.designated;
|
|
|
|
lock.value -= amount;
|
|
|
|
delete _accounts[controller][fund][recipient];
|
|
|
|
_token.safeTransfer(address(0xdead), amount);
|
|
}
|
|
|
|
function _withdraw(
|
|
Controller controller,
|
|
Fund fund,
|
|
Recipient recipient
|
|
) internal {
|
|
Lock memory lock = _locks[controller][fund];
|
|
require(!lock.isLocked(), Locked());
|
|
|
|
Account memory account = _accounts[controller][fund][recipient];
|
|
account.update(lock.expiry);
|
|
uint128 amount = account.balance.available + account.balance.designated;
|
|
|
|
lock.value -= amount;
|
|
|
|
if (lock.value == 0) {
|
|
delete _locks[controller][fund];
|
|
} else {
|
|
_locks[controller][fund] = lock;
|
|
}
|
|
|
|
delete _accounts[controller][fund][recipient];
|
|
|
|
_token.safeTransfer(Recipient.unwrap(recipient), amount);
|
|
}
|
|
|
|
function _checkLockInvariant(Lock memory lock) private pure {
|
|
require(lock.expiry <= lock.maximum, ExpiryPastMaximum());
|
|
}
|
|
|
|
function _checkAccountInvariant(
|
|
Account memory account,
|
|
Lock memory lock
|
|
) private pure {
|
|
require(account.isSolventAt(lock.maximum), InsufficientBalance());
|
|
}
|
|
|
|
error InsufficientBalance();
|
|
error Locked();
|
|
error AlreadyLocked();
|
|
error ExpiryPastMaximum();
|
|
error InvalidExpiry();
|
|
error LockRequired();
|
|
error FlowMustBeZero();
|
|
}
|