vault: rename Fund -> FundId

This commit is contained in:
Mark Spanbroek 2025-03-03 10:49:56 +01:00
parent 62aea75295
commit 2900bed8fd
2 changed files with 107 additions and 97 deletions

View File

@ -62,64 +62,67 @@ contract Vault is VaultBase, Pausable, Ownable {
/// Extracts the address of the account holder and the discriminator from the /// Extracts the address of the account holder and the discriminator from the
/// account id. /// account id.
function decodeAccountId( function decodeAccountId(
AccountId account AccountId id
) public pure returns (address holder, bytes12 discriminator) { ) public pure returns (address holder, bytes12 discriminator) {
return Accounts.decodeId(account); return Accounts.decodeId(id);
} }
/// The amount of tokens that are currently in an account. /// The amount of tokens that are currently in an account.
/// This includes available and designated tokens. Available tokens can be /// This includes available and designated tokens. Available tokens can be
/// transfered to other accounts, but designated tokens cannot. /// transfered to other accounts, but designated tokens cannot.
function getBalance(Fund fund, AccountId id) public view returns (uint128) { function getBalance(
FundId fundId,
AccountId accountId
) public view returns (uint128) {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
Balance memory balance = _getBalance(controller, fund, id); Balance memory balance = _getBalance(controller, fundId, accountId);
return balance.available + balance.designated; return balance.available + balance.designated;
} }
/// The amount of tokens that are currently designated in an account /// The amount of tokens that are currently designated in an account
/// These tokens can no longer be transfered to other accounts. /// These tokens can no longer be transfered to other accounts.
function getDesignatedBalance( function getDesignatedBalance(
Fund fund, FundId fundId,
AccountId id AccountId accountId
) public view returns (uint128) { ) public view returns (uint128) {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
Balance memory balance = _getBalance(controller, fund, id); Balance memory balance = _getBalance(controller, fundId, accountId);
return balance.designated; return balance.designated;
} }
/// Returns the status of the lock on the fund. Most operations on the vault /// Returns the status of the lock on the fund. Most operations on the vault
/// can only be done by the controller when the funds are locked. Withdrawal /// can only be done by the controller when the funds are locked. Withdrawal
/// can only be done when the funds are unlocked. /// can only be done when the funds are unlocked.
function getLockStatus(Fund fund) public view returns (LockStatus) { function getLockStatus(FundId fundId) public view returns (LockStatus) {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
return _getLockStatus(controller, fund); return _getLockStatus(controller, fundId);
} }
/// Returns the expiry time of the lock on the fund. A locked fund unlocks /// Returns the expiry time of the lock on the fund. A locked fund unlocks
/// automatically at this timestamp. /// automatically at this timestamp.
function getLockExpiry(Fund fund) public view returns (Timestamp) { function getLockExpiry(FundId fundId) public view returns (Timestamp) {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
return _getLockExpiry(controller, fund); return _getLockExpiry(controller, fundId);
} }
/// Locks the fund until the expiry timestamp. The lock expiry can be extended /// Locks the fund until the expiry timestamp. The lock expiry can be extended
/// later, but no more than the maximum timestamp. /// later, but no more than the maximum timestamp.
function lock( function lock(
Fund fund, FundId fundId,
Timestamp expiry, Timestamp expiry,
Timestamp maximum Timestamp maximum
) public whenNotPaused { ) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_lock(controller, fund, expiry, maximum); _lock(controller, fundId, expiry, maximum);
} }
/// Delays unlocking of a locked fund. The new expiry should be later than /// Delays unlocking of a locked fund. The new expiry should be later than
/// the existing expiry, but no later than the maximum timestamp that was /// the existing expiry, but no later than the maximum timestamp that was
/// provided when locking the fund. /// provided when locking the fund.
/// Only allowed when the lock has not unlocked yet. /// Only allowed when the lock has not unlocked yet.
function extendLock(Fund fund, Timestamp expiry) public whenNotPaused { function extendLock(FundId fundId, Timestamp expiry) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_extendLock(controller, fund, expiry); _extendLock(controller, fundId, expiry);
} }
/// Deposits an amount of tokens into the vault, and adds them to the balance /// Deposits an amount of tokens into the vault, and adds them to the balance
@ -127,12 +130,12 @@ contract Vault is VaultBase, Pausable, Ownable {
/// contract. /// contract.
/// Only allowed when the fund is locked. /// Only allowed when the fund is locked.
function deposit( function deposit(
Fund fund, FundId fundId,
AccountId id, AccountId accountId,
uint128 amount uint128 amount
) public whenNotPaused { ) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_deposit(controller, fund, id, amount); _deposit(controller, fundId, accountId, amount);
} }
/// Takes an amount of tokens from the account balance and designates them /// Takes an amount of tokens from the account balance and designates them
@ -140,24 +143,24 @@ contract Vault is VaultBase, Pausable, Ownable {
/// transfered to other accounts. /// transfered to other accounts.
/// Only allowed when the fund is locked. /// Only allowed when the fund is locked.
function designate( function designate(
Fund fund, FundId fundId,
AccountId id, AccountId accountId,
uint128 amount uint128 amount
) public whenNotPaused { ) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_designate(controller, fund, id, amount); _designate(controller, fundId, accountId, amount);
} }
/// Transfers an amount of tokens from one account to the other. /// Transfers an amount of tokens from one account to the other.
/// Only allowed when the fund is locked. /// Only allowed when the fund is locked.
function transfer( function transfer(
Fund fund, FundId fundId,
AccountId from, AccountId from,
AccountId to, AccountId to,
uint128 amount uint128 amount
) public whenNotPaused { ) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_transfer(controller, fund, from, to, amount); _transfer(controller, fundId, from, to, amount);
} }
/// Transfers tokens from one account the other over time. /// Transfers tokens from one account the other over time.
@ -168,40 +171,43 @@ contract Vault is VaultBase, Pausable, Ownable {
/// Only allowed when the balance is sufficient to sustain the flow until the /// Only allowed when the balance is sufficient to sustain the flow until the
/// fund unlocks, even if the lock expiry time is extended to its maximum. /// fund unlocks, even if the lock expiry time is extended to its maximum.
function flow( function flow(
Fund fund, FundId fundId,
AccountId from, AccountId from,
AccountId to, AccountId to,
TokensPerSecond rate TokensPerSecond rate
) public whenNotPaused { ) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_flow(controller, fund, from, to, rate); _flow(controller, fundId, from, to, rate);
} }
/// Burns an amount of designated tokens from the account. /// Burns an amount of designated tokens from the account.
/// Only allowed when the fund is locked. /// Only allowed when the fund is locked.
function burnDesignated( function burnDesignated(
Fund fund, FundId fundId,
AccountId account, AccountId accountId,
uint128 amount uint128 amount
) public whenNotPaused { ) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_burnDesignated(controller, fund, account, amount); _burnDesignated(controller, fundId, accountId, amount);
} }
/// Burns all tokens from the account. /// Burns all tokens from the account.
/// Only allowed when the fund is locked. /// Only allowed when the fund is locked.
/// Only allowed when no funds are flowing into or out of the account. /// Only allowed when no funds are flowing into or out of the account.
function burnAccount(Fund fund, AccountId account) public whenNotPaused { function burnAccount(
FundId fundId,
AccountId accountId
) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_burnAccount(controller, fund, account); _burnAccount(controller, fundId, accountId);
} }
/// Freezes a fund. Stops all tokens flows and disallows any operations on the /// Freezes a fund. Stops all tokens flows and disallows any operations on the
/// fund until it unlocks. /// fund until it unlocks.
/// Only allowed when the fund is locked. /// Only allowed when the fund is locked.
function freezeFund(Fund fund) public whenNotPaused { function freezeFund(FundId fundId) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_freezeFund(controller, fund); _freezeFund(controller, fundId);
} }
/// Transfers all ERC20 tokens in the account out of the vault to the account /// Transfers all ERC20 tokens in the account out of the vault to the account
@ -210,9 +216,9 @@ contract Vault is VaultBase, Pausable, Ownable {
/// The account holder can also withdraw itself, so when designing a smart /// The account holder can also withdraw itself, so when designing a smart
/// contract that controls funds in the vault, don't assume that only this /// contract that controls funds in the vault, don't assume that only this
/// smart contract can initiate a withdrawal /// smart contract can initiate a withdrawal
function withdraw(Fund fund, AccountId account) public whenNotPaused { function withdraw(FundId fund, AccountId accountId) public whenNotPaused {
Controller controller = Controller.wrap(msg.sender); Controller controller = Controller.wrap(msg.sender);
_withdraw(controller, fund, account); _withdraw(controller, fund, accountId);
} }
/// Allows an account holder to withdraw its tokens from a fund directly, /// Allows an account holder to withdraw its tokens from a fund directly,
@ -221,12 +227,12 @@ contract Vault is VaultBase, Pausable, Ownable {
/// Only allowed when the fund is unlocked. /// Only allowed when the fund is unlocked.
function withdrawByRecipient( function withdrawByRecipient(
Controller controller, Controller controller,
Fund fund, FundId fund,
AccountId account AccountId accountId
) public { ) public {
(address holder, ) = Accounts.decodeId(account); (address holder, ) = Accounts.decodeId(accountId);
require(msg.sender == holder, VaultOnlyAccountHolder()); require(msg.sender == holder, VaultOnlyAccountHolder());
_withdraw(controller, fund, account); _withdraw(controller, fund, accountId);
} }
function pause() public onlyOwner { function pause() public onlyOwner {

View File

@ -17,14 +17,14 @@ import "./Locks.sol";
/// The lock invariant ensures that there is a maximum time that a fund can be /// The lock invariant ensures that there is a maximum time that a fund can be
/// locked: /// locked:
/// ///
/// ( controller Controller, fund Fund: /// ( controller Controller, fund FundId:
/// lock.expiry <= lock.maximum /// lock.expiry <= lock.maximum
/// where lock = _locks[controller][fund]) /// where lock = _locks[controller][fund])
/// ///
/// The account invariant ensures that the outgoing token flow can be sustained /// The account invariant ensures that the outgoing token flow can be sustained
/// for the maximum time that a fund can be locked: /// for the maximum time that a fund can be locked:
/// ///
/// ( controller Controller, fund Fund, account AccountId: /// ( controller Controller, fund FundId, account AccountId:
/// flow.outgoing * (lock.maximum - flow.updated) <= balance.available /// flow.outgoing * (lock.maximum - flow.updated) <= balance.available
/// where lock = _locks[controller][fund]) /// where lock = _locks[controller][fund])
/// and flow = _accounts[controller][fund][account].flow /// and flow = _accounts[controller][fund][account].flow
@ -32,7 +32,7 @@ import "./Locks.sol";
/// ///
/// The flow invariant ensures that incoming and outgoing flow rates match: /// The flow invariant ensures that incoming and outgoing flow rates match:
/// ///
/// ( controller Controller, fund Fund: /// ( controller Controller, fund FundId:
/// ( account AccountId: accounts[account].flow.incoming) = /// ( account AccountId: accounts[account].flow.incoming) =
/// ( account AccountId: accounts[account].flow.outgoing) /// ( account AccountId: accounts[account].flow.outgoing)
/// where accounts = _accounts[controller][fund]) /// where accounts = _accounts[controller][fund])
@ -47,12 +47,12 @@ abstract contract VaultBase {
/// Represents a smart contract that can redistribute and burn tokens in funds /// Represents a smart contract that can redistribute and burn tokens in funds
type Controller is address; type Controller is address;
/// Unique identifier for a fund, chosen by the controller /// Unique identifier for a fund, chosen by the controller
type Fund is bytes32; type FundId is bytes32;
/// Each fund has its own time lock /// Each fund has its own time lock
mapping(Controller => mapping(Fund => Lock)) private _locks; mapping(Controller => mapping(FundId => Lock)) private _locks;
/// Each account holder has its own set of accounts in a fund /// Each account holder has its own set of accounts in a fund
mapping(Controller => mapping(Fund => mapping(AccountId => Account))) mapping(Controller => mapping(FundId => mapping(AccountId => Account)))
private _accounts; private _accounts;
constructor(IERC20 token) { constructor(IERC20 token) {
@ -61,32 +61,32 @@ abstract contract VaultBase {
function _getLockStatus( function _getLockStatus(
Controller controller, Controller controller,
Fund fund FundId fundId
) internal view returns (LockStatus) { ) internal view returns (LockStatus) {
return _locks[controller][fund].status(); return _locks[controller][fundId].status();
} }
function _getLockExpiry( function _getLockExpiry(
Controller controller, Controller controller,
Fund fund FundId fundId
) internal view returns (Timestamp) { ) internal view returns (Timestamp) {
return _locks[controller][fund].expiry; return _locks[controller][fundId].expiry;
} }
function _getBalance( function _getBalance(
Controller controller, Controller controller,
Fund fund, FundId fundId,
AccountId id AccountId accountId
) internal view returns (Balance memory) { ) internal view returns (Balance memory) {
Lock memory lock = _locks[controller][fund]; Lock memory lock = _locks[controller][fundId];
LockStatus lockStatus = lock.status(); LockStatus lockStatus = lock.status();
if (lockStatus == LockStatus.Locked) { if (lockStatus == LockStatus.Locked) {
Account memory account = _accounts[controller][fund][id]; Account memory account = _accounts[controller][fundId][accountId];
account.update(Timestamps.currentTime()); account.update(Timestamps.currentTime());
return account.balance; return account.balance;
} }
if (lockStatus == LockStatus.Unlocked || lockStatus == LockStatus.Frozen) { if (lockStatus == LockStatus.Unlocked || lockStatus == LockStatus.Frozen) {
Account memory account = _accounts[controller][fund][id]; Account memory account = _accounts[controller][fundId][accountId];
account.update(lock.flowEnd()); account.update(lock.flowEnd());
return account.balance; return account.balance;
} }
@ -95,41 +95,41 @@ abstract contract VaultBase {
function _lock( function _lock(
Controller controller, Controller controller,
Fund fund, FundId fundId,
Timestamp expiry, Timestamp expiry,
Timestamp maximum Timestamp maximum
) internal { ) internal {
Lock memory lock = _locks[controller][fund]; Lock memory lock = _locks[controller][fundId];
require(lock.status() == LockStatus.NoLock, VaultFundAlreadyLocked()); require(lock.status() == LockStatus.NoLock, VaultFundAlreadyLocked());
lock.expiry = expiry; lock.expiry = expiry;
lock.maximum = maximum; lock.maximum = maximum;
_checkLockInvariant(lock); _checkLockInvariant(lock);
_locks[controller][fund] = lock; _locks[controller][fundId] = lock;
} }
function _extendLock( function _extendLock(
Controller controller, Controller controller,
Fund fund, FundId fundId,
Timestamp expiry Timestamp expiry
) internal { ) internal {
Lock memory lock = _locks[controller][fund]; Lock memory lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Locked, VaultFundNotLocked()); require(lock.status() == LockStatus.Locked, VaultFundNotLocked());
require(lock.expiry <= expiry, VaultInvalidExpiry()); require(lock.expiry <= expiry, VaultInvalidExpiry());
lock.expiry = expiry; lock.expiry = expiry;
_checkLockInvariant(lock); _checkLockInvariant(lock);
_locks[controller][fund] = lock; _locks[controller][fundId] = lock;
} }
function _deposit( function _deposit(
Controller controller, Controller controller,
Fund fund, FundId fundId,
AccountId id, AccountId accountId,
uint128 amount uint128 amount
) internal { ) internal {
Lock storage lock = _locks[controller][fund]; Lock storage lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Locked, VaultFundNotLocked()); require(lock.status() == LockStatus.Locked, VaultFundNotLocked());
Account storage account = _accounts[controller][fund][id]; Account storage account = _accounts[controller][fundId][accountId];
account.balance.available += amount; account.balance.available += amount;
lock.value += amount; lock.value += amount;
@ -143,74 +143,74 @@ abstract contract VaultBase {
function _designate( function _designate(
Controller controller, Controller controller,
Fund fund, FundId fundId,
AccountId id, AccountId accountId,
uint128 amount uint128 amount
) internal { ) internal {
Lock memory lock = _locks[controller][fund]; Lock memory lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Locked, VaultFundNotLocked()); require(lock.status() == LockStatus.Locked, VaultFundNotLocked());
Account memory account = _accounts[controller][fund][id]; Account memory account = _accounts[controller][fundId][accountId];
require(amount <= account.balance.available, VaultInsufficientBalance()); require(amount <= account.balance.available, VaultInsufficientBalance());
account.balance.available -= amount; account.balance.available -= amount;
account.balance.designated += amount; account.balance.designated += amount;
_checkAccountInvariant(account, lock); _checkAccountInvariant(account, lock);
_accounts[controller][fund][id] = account; _accounts[controller][fundId][accountId] = account;
} }
function _transfer( function _transfer(
Controller controller, Controller controller,
Fund fund, FundId fundId,
AccountId from, AccountId from,
AccountId to, AccountId to,
uint128 amount uint128 amount
) internal { ) internal {
Lock memory lock = _locks[controller][fund]; Lock memory lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Locked, VaultFundNotLocked()); require(lock.status() == LockStatus.Locked, VaultFundNotLocked());
Account memory sender = _accounts[controller][fund][from]; Account memory sender = _accounts[controller][fundId][from];
require(amount <= sender.balance.available, VaultInsufficientBalance()); require(amount <= sender.balance.available, VaultInsufficientBalance());
sender.balance.available -= amount; sender.balance.available -= amount;
_checkAccountInvariant(sender, lock); _checkAccountInvariant(sender, lock);
_accounts[controller][fund][from] = sender; _accounts[controller][fundId][from] = sender;
_accounts[controller][fund][to].balance.available += amount; _accounts[controller][fundId][to].balance.available += amount;
} }
function _flow( function _flow(
Controller controller, Controller controller,
Fund fund, FundId fundId,
AccountId from, AccountId from,
AccountId to, AccountId to,
TokensPerSecond rate TokensPerSecond rate
) internal { ) internal {
Lock memory lock = _locks[controller][fund]; Lock memory lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Locked, VaultFundNotLocked()); require(lock.status() == LockStatus.Locked, VaultFundNotLocked());
Account memory sender = _accounts[controller][fund][from]; Account memory sender = _accounts[controller][fundId][from];
sender.flowOut(rate); sender.flowOut(rate);
_checkAccountInvariant(sender, lock); _checkAccountInvariant(sender, lock);
_accounts[controller][fund][from] = sender; _accounts[controller][fundId][from] = sender;
Account memory receiver = _accounts[controller][fund][to]; Account memory receiver = _accounts[controller][fundId][to];
receiver.flowIn(rate); receiver.flowIn(rate);
_accounts[controller][fund][to] = receiver; _accounts[controller][fundId][to] = receiver;
} }
function _burnDesignated( function _burnDesignated(
Controller controller, Controller controller,
Fund fund, FundId fundId,
AccountId id, AccountId accountId,
uint128 amount uint128 amount
) internal { ) internal {
Lock storage lock = _locks[controller][fund]; Lock storage lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Locked, VaultFundNotLocked()); require(lock.status() == LockStatus.Locked, VaultFundNotLocked());
Account storage account = _accounts[controller][fund][id]; Account storage account = _accounts[controller][fundId][accountId];
require(account.balance.designated >= amount, VaultInsufficientBalance()); require(account.balance.designated >= amount, VaultInsufficientBalance());
account.balance.designated -= amount; account.balance.designated -= amount;
@ -222,49 +222,53 @@ abstract contract VaultBase {
function _burnAccount( function _burnAccount(
Controller controller, Controller controller,
Fund fund, FundId fundId,
AccountId id AccountId accountId
) internal { ) internal {
Lock storage lock = _locks[controller][fund]; Lock storage lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Locked, VaultFundNotLocked()); require(lock.status() == LockStatus.Locked, VaultFundNotLocked());
Account memory account = _accounts[controller][fund][id]; Account memory account = _accounts[controller][fundId][accountId];
require(account.flow.incoming == account.flow.outgoing, VaultFlowNotZero()); require(account.flow.incoming == account.flow.outgoing, VaultFlowNotZero());
uint128 amount = account.balance.available + account.balance.designated; uint128 amount = account.balance.available + account.balance.designated;
lock.value -= amount; lock.value -= amount;
delete _accounts[controller][fund][id]; delete _accounts[controller][fundId][accountId];
_token.safeTransfer(address(0xdead), amount); _token.safeTransfer(address(0xdead), amount);
} }
function _freezeFund(Controller controller, Fund fund) internal { function _freezeFund(Controller controller, FundId fundId) internal {
Lock storage lock = _locks[controller][fund]; Lock storage lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Locked, VaultFundNotLocked()); require(lock.status() == LockStatus.Locked, VaultFundNotLocked());
lock.frozenAt = Timestamps.currentTime(); lock.frozenAt = Timestamps.currentTime();
} }
function _withdraw(Controller controller, Fund fund, AccountId id) internal { function _withdraw(
Lock memory lock = _locks[controller][fund]; Controller controller,
FundId fundId,
AccountId accountId
) internal {
Lock memory lock = _locks[controller][fundId];
require(lock.status() == LockStatus.Unlocked, VaultFundNotUnlocked()); require(lock.status() == LockStatus.Unlocked, VaultFundNotUnlocked());
Account memory account = _accounts[controller][fund][id]; Account memory account = _accounts[controller][fundId][accountId];
account.update(lock.flowEnd()); account.update(lock.flowEnd());
uint128 amount = account.balance.available + account.balance.designated; uint128 amount = account.balance.available + account.balance.designated;
lock.value -= amount; lock.value -= amount;
if (lock.value == 0) { if (lock.value == 0) {
delete _locks[controller][fund]; delete _locks[controller][fundId];
} else { } else {
_locks[controller][fund] = lock; _locks[controller][fundId] = lock;
} }
delete _accounts[controller][fund][id]; delete _accounts[controller][fundId][accountId];
(address owner, ) = Accounts.decodeId(id); (address owner, ) = Accounts.decodeId(accountId);
_token.safeTransfer(owner, amount); _token.safeTransfer(owner, amount);
} }