2025-01-22 11:59:51 +01:00

136 lines
4.2 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";
using SafeERC20 for IERC20;
using Timestamps for Timestamp;
contract Vault {
IERC20 private immutable _token;
type Controller is address;
type Context is bytes32;
type Recipient is address;
struct Lock {
Timestamp expiry;
Timestamp maximum;
}
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;
constructor(IERC20 token) {
_token = token;
}
function balance(
Context context,
Recipient recipient
) public view returns (uint256) {
Controller controller = Controller.wrap(msg.sender);
return
_available[controller][context][recipient] +
_designated[controller][context][recipient];
}
function designated(
Context context,
Recipient recipient
) public view returns (uint256) {
Controller controller = Controller.wrap(msg.sender);
return _designated[controller][context][recipient];
}
function lock(Context context) public view returns (Lock memory) {
Controller controller = Controller.wrap(msg.sender);
return _locks[controller][context];
}
function deposit(Context context, address from, uint256 amount) public {
Controller controller = Controller.wrap(msg.sender);
Recipient recipient = Recipient.wrap(from);
_available[controller][context][recipient] += amount;
_token.safeTransferFrom(from, address(this), amount);
}
function _delete(Context context, Recipient recipient) private {
Controller controller = Controller.wrap(msg.sender);
delete _available[controller][context][recipient];
delete _designated[controller][context][recipient];
}
function withdraw(Context context, Recipient recipient) public {
Controller controller = Controller.wrap(msg.sender);
require(!lock(context).expiry.isFuture(), Locked());
delete _locks[controller][context];
uint256 amount = balance(context, recipient);
_delete(context, recipient);
_token.safeTransfer(Recipient.unwrap(recipient), amount);
}
function burn(Context context, Recipient recipient) public {
uint256 amount = balance(context, recipient);
_delete(context, recipient);
_token.safeTransfer(address(0xdead), amount);
}
function transfer(
Context context,
Recipient from,
Recipient to,
uint256 amount
) public {
Controller controller = Controller.wrap(msg.sender);
require(
amount <= _available[controller][context][from],
InsufficientBalance()
);
_available[controller][context][from] -= amount;
_available[controller][context][to] += amount;
}
function designate(
Context context,
Recipient recipient,
uint256 amount
) public {
Controller controller = Controller.wrap(msg.sender);
require(
amount <= _available[controller][context][recipient],
InsufficientBalance()
);
_available[controller][context][recipient] -= amount;
_designated[controller][context][recipient] += amount;
}
function lockup(Context context, Timestamp expiry, Timestamp maximum) public {
require(Timestamp.unwrap(lock(context).maximum) == 0, AlreadyLocked());
require(!expiry.isAfter(maximum), ExpiryPastMaximum());
Controller controller = Controller.wrap(msg.sender);
_locks[controller][context] = Lock({expiry: expiry, maximum: maximum});
}
function extend(Context context, Timestamp expiry) public {
Lock memory previous = lock(context);
require(previous.expiry.isFuture(), LockExpired());
require(!previous.expiry.isAfter(expiry), InvalidExpiry());
require(!expiry.isAfter(previous.maximum), ExpiryPastMaximum());
Controller controller = Controller.wrap(msg.sender);
_locks[controller][context].expiry = expiry;
}
error InsufficientBalance();
error Locked();
error AlreadyLocked();
error ExpiryPastMaximum();
error InvalidExpiry();
error LockExpired();
}