diff --git a/contracts/vaults/ethereum/EthErc20Vault.sol b/contracts/vaults/ethereum/EthErc20Vault.sol index c4efd9c3..52a1a02f 100644 --- a/contracts/vaults/ethereum/EthErc20Vault.sol +++ b/contracts/vaults/ethereum/EthErc20Vault.sol @@ -134,6 +134,16 @@ contract EthErc20Vault is return _version; } + /// @inheritdoc VaultState + function _updateExitQueue() + internal + virtual + override(VaultState, VaultToken) + returns (uint256 burnedShares) + { + return super._updateExitQueue(); + } + /// @inheritdoc VaultState function _mintShares( address owner, diff --git a/contracts/vaults/ethereum/EthPrivErc20Vault.sol b/contracts/vaults/ethereum/EthPrivErc20Vault.sol index 5867fef3..847409fd 100644 --- a/contracts/vaults/ethereum/EthPrivErc20Vault.sol +++ b/contracts/vaults/ethereum/EthPrivErc20Vault.sol @@ -73,6 +73,16 @@ contract EthPrivErc20Vault is Initializable, EthErc20Vault, VaultWhitelist, IEth __VaultWhitelist_init(_admin); } + /// @inheritdoc IVaultVersion + function vaultId() public pure virtual override(IVaultVersion, EthErc20Vault) returns (bytes32) { + return keccak256('EthPrivErc20Vault'); + } + + /// @inheritdoc IVaultVersion + function version() public pure virtual override(IVaultVersion, EthErc20Vault) returns (uint8) { + return _version; + } + /// @inheritdoc IVaultEthStaking function deposit( address receiver, @@ -101,16 +111,6 @@ contract EthPrivErc20Vault is Initializable, EthErc20Vault, VaultWhitelist, IEth return super.mintOsToken(receiver, osTokenShares, referrer); } - /// @inheritdoc IVaultVersion - function vaultId() public pure virtual override(IVaultVersion, EthErc20Vault) returns (bytes32) { - return keccak256('EthPrivErc20Vault'); - } - - /// @inheritdoc IVaultVersion - function version() public pure virtual override(IVaultVersion, EthErc20Vault) returns (uint8) { - return _version; - } - /// @inheritdoc ERC20Upgradeable function _transfer(address from, address to, uint256 amount) internal virtual override { _checkWhitelist(from); diff --git a/contracts/vaults/modules/VaultEnterExit.sol b/contracts/vaults/modules/VaultEnterExit.sol index 7dbdcdc7..4cdcd8cc 100644 --- a/contracts/vaults/modules/VaultEnterExit.sol +++ b/contracts/vaults/modules/VaultEnterExit.sol @@ -140,7 +140,10 @@ abstract contract VaultEnterExit is VaultImmutables, Initializable, VaultState, if (totalExitedTickets <= positionTicket) return (0, 0); // calculate exited tickets and assets - exitedTickets = Math.min(exitingTickets, totalExitedTickets - positionTicket); + unchecked { + // cannot underflow as totalExitedTickets > positionTicket + exitedTickets = Math.min(exitingTickets, totalExitedTickets - positionTicket); + } return (exitedTickets, _convertExitTicketsToAssets(exitedTickets)); } diff --git a/contracts/vaults/modules/VaultOsToken.sol b/contracts/vaults/modules/VaultOsToken.sol index 827fb073..55fea244 100644 --- a/contracts/vaults/modules/VaultOsToken.sol +++ b/contracts/vaults/modules/VaultOsToken.sol @@ -44,7 +44,7 @@ abstract contract VaultOsToken is VaultImmutables, VaultState, VaultEnterExit, I } /// @inheritdoc IVaultOsToken - function osTokenPositions(address user) public view override returns (uint128 shares) { + function osTokenPositions(address user) external view override returns (uint128 shares) { OsTokenPosition memory position = _positions[user]; if (position.shares != 0) _syncPositionFee(position); return position.shares; @@ -272,40 +272,27 @@ abstract contract VaultOsToken is VaultImmutables, VaultState, VaultEnterExit, I } /** - * @dev Internal function for retrieving the amount of locked shares for a given user. - * Reverts if the vault is not harvested. + * @notice Internal function for checking position validity. Reverts if it is invalid. * @param user The address of the user - * @return The amount of locked shares */ - function _getLockedShares(address user) internal view returns (uint256) { - uint256 osTokenShares = osTokenPositions(user); - if (osTokenShares == 0) return 0; - - // calculate locked assets - uint256 lockedAssets = Math.mulDiv( - _osTokenVaultController.convertToAssets(osTokenShares), - _maxPercent, - _osTokenConfig.ltvPercent() - ); - if (lockedAssets == 0) return 0; + function _checkOsTokenPosition(address user) internal view { + // fetch user position + OsTokenPosition memory position = _positions[user]; + if (position.shares == 0) return; // check whether vault assets are up to date _checkHarvested(); - // convert to vault shares - return convertToShares(lockedAssets); - } - - /** - * @notice Internal function for checking position validity - * @param user The address of the user - */ - function _checkOsTokenPosition(address user) internal view { - uint256 lockedShares = _getLockedShares(user); - if (lockedShares == 0) return; + // sync fee + _syncPositionFee(position); - // validate position LTV - if (lockedShares > _balances[user]) revert Errors.LowLtv(); + // calculate and validate position LTV + if ( + Math.mulDiv(convertToAssets(_balances[user]), _osTokenConfig.ltvPercent(), _maxPercent) < + _osTokenVaultController.convertToAssets(position.shares) + ) { + revert Errors.LowLtv(); + } } /** diff --git a/contracts/vaults/modules/VaultState.sol b/contracts/vaults/modules/VaultState.sol index f4c44db1..8fc26f0d 100644 --- a/contracts/vaults/modules/VaultState.sol +++ b/contracts/vaults/modules/VaultState.sol @@ -23,6 +23,7 @@ abstract contract VaultState is VaultImmutables, Initializable, VaultFee, IVault uint128 internal _totalShares; uint128 internal _totalAssets; + /// @inheritdoc IVaultState uint128 public override queuedShares; uint128 internal _unclaimedAssets; @@ -32,6 +33,7 @@ abstract contract VaultState is VaultImmutables, Initializable, VaultFee, IVault uint256 private _capacity; + /// @inheritdoc IVaultState uint128 public override totalExitingAssets; uint128 internal _totalExitingTickets; uint256 internal _totalExitedTickets; @@ -118,27 +120,27 @@ abstract contract VaultState is VaultImmutables, Initializable, VaultFee, IVault // SLOAD to memory uint256 _totalExitingAssets = totalExitingAssets; - // apply penalty to exiting assets - uint256 exitingAssetsPenalty = Math.mulDiv( - penalty, - _totalExitingAssets, - _totalExitingAssets + newTotalAssets - ); + if (_totalExitingAssets > 0) { + // apply penalty to exiting assets + uint256 exitingAssetsPenalty = Math.mulDiv( + penalty, + _totalExitingAssets, + _totalExitingAssets + newTotalAssets + ); - unchecked { - // cannot underflow as exitingAssetsPenalty <= penalty - penalty -= exitingAssetsPenalty; + // apply penalty to total exiting assets + unchecked { + // cannot underflow as exitingAssetsPenalty <= penalty + penalty -= exitingAssetsPenalty; + // cannot underflow as exitingAssetsPenalty <= _totalExitingAssets + totalExitingAssets = SafeCast.toUint128(_totalExitingAssets - exitingAssetsPenalty); + } } // subtract penalty from total assets (excludes exiting assets) if (penalty > 0) { _totalAssets = SafeCast.toUint128(newTotalAssets - penalty); } - - // subtract penalty from total exiting assets - if (exitingAssetsPenalty > 0) { - totalExitingAssets = SafeCast.toUint128(_totalExitingAssets - exitingAssetsPenalty); - } return; } diff --git a/contracts/vaults/modules/VaultToken.sol b/contracts/vaults/modules/VaultToken.sol index 7e95f666..03ae52fe 100644 --- a/contracts/vaults/modules/VaultToken.sol +++ b/contracts/vaults/modules/VaultToken.sol @@ -37,6 +37,12 @@ abstract contract VaultToken is Initializable, ERC20Upgradeable, VaultState, IVa emit Transfer(owner, address(0), shares); } + /// @inheritdoc VaultState + function _updateExitQueue() internal virtual override returns (uint256 burnedShares) { + burnedShares = super._updateExitQueue(); + if (burnedShares != 0) emit Transfer(address(this), address(0), burnedShares); + } + /// @inheritdoc ERC20Upgradeable function _transfer(address from, address to, uint256 amount) internal virtual override { if (from == address(0) || to == address(0)) revert Errors.ZeroAddress(); diff --git a/contracts/vaults/modules/VaultWhitelist.sol b/contracts/vaults/modules/VaultWhitelist.sol index 83aef0b8..54554680 100644 --- a/contracts/vaults/modules/VaultWhitelist.sol +++ b/contracts/vaults/modules/VaultWhitelist.sol @@ -20,7 +20,7 @@ abstract contract VaultWhitelist is Initializable, VaultAdmin, IVaultWhitelist { mapping(address => bool) public override whitelistedAccounts; /// @inheritdoc IVaultWhitelist - function updateWhitelist(address account, bool approved) public override { + function updateWhitelist(address account, bool approved) external override { if (msg.sender != whitelister) revert Errors.AccessDenied(); if (whitelistedAccounts[account] == approved) return; whitelistedAccounts[account] = approved; diff --git a/test/EthGenesisVault.spec.ts b/test/EthGenesisVault.spec.ts index 5ff0eb66..f5287b51 100644 --- a/test/EthGenesisVault.spec.ts +++ b/test/EthGenesisVault.spec.ts @@ -398,6 +398,7 @@ describe('EthGenesisVault', () => { }) it('skips updating legacy with zero total assets', async () => { + if (MAINNET_FORK.enabled) return await acceptPoolEscrowOwnership() await rewardEthToken.setTotalStaked(0n) await rewardEthToken.setTotalRewards(0n) diff --git a/test/__snapshots__/EthBlocklistErc20Vault.spec.ts.snap b/test/__snapshots__/EthBlocklistErc20Vault.spec.ts.snap index 8ef2fe63..1180d9c3 100644 --- a/test/__snapshots__/EthBlocklistErc20Vault.spec.ts.snap +++ b/test/__snapshots__/EthBlocklistErc20Vault.spec.ts.snap @@ -3,14 +3,14 @@ exports[`EthBlocklistErc20Vault deposit can be called by not blocked user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 71149, + "gasUsed": 71181, } `; exports[`EthBlocklistErc20Vault deposit deposit through receive fallback can be called by not blocked sender 1`] = ` Object { "calldataByteLength": 4, - "gasUsed": 78315, + "gasUsed": 78347, } `; @@ -24,6 +24,6 @@ Object { exports[`EthBlocklistErc20Vault transfer can transfer 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 59012, + "gasUsed": 58987, } `; diff --git a/test/__snapshots__/EthErc20Vault.spec.ts.snap b/test/__snapshots__/EthErc20Vault.spec.ts.snap index ad24c40c..96d0553e 100644 --- a/test/__snapshots__/EthErc20Vault.spec.ts.snap +++ b/test/__snapshots__/EthErc20Vault.spec.ts.snap @@ -3,21 +3,21 @@ exports[`EthErc20Vault deposit emits transfer event 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 68826, + "gasUsed": 68858, } `; exports[`EthErc20Vault deposit through receive fallback function emits transfer event 1`] = ` Object { "calldataByteLength": 4, - "gasUsed": 76084, + "gasUsed": 76116, } `; exports[`EthErc20Vault enter exit queue emits transfer event 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 96055, + "gasUsed": 96030, } `; diff --git a/test/__snapshots__/EthGenesisVault.spec.ts.snap b/test/__snapshots__/EthGenesisVault.spec.ts.snap index 8e1df947..a4c8c982 100644 --- a/test/__snapshots__/EthGenesisVault.spec.ts.snap +++ b/test/__snapshots__/EthGenesisVault.spec.ts.snap @@ -17,7 +17,7 @@ Object { exports[`EthGenesisVault pulls withdrawals on claim exited assets 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 77231, + "gasUsed": 77206, } `; @@ -52,7 +52,7 @@ Object { exports[`EthGenesisVault update state splits penalty between rewardEthToken and vault 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 119951, + "gasUsed": 119650, } `; diff --git a/test/__snapshots__/EthPrivErc20Vault.spec.ts.snap b/test/__snapshots__/EthPrivErc20Vault.spec.ts.snap index c0ec74f3..d005196a 100644 --- a/test/__snapshots__/EthPrivErc20Vault.spec.ts.snap +++ b/test/__snapshots__/EthPrivErc20Vault.spec.ts.snap @@ -3,14 +3,14 @@ exports[`EthPrivErc20Vault deposit can be called by whitelisted user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 71137, + "gasUsed": 71169, } `; exports[`EthPrivErc20Vault deposit deposit through receive fallback can be called by whitelisted sender 1`] = ` Object { "calldataByteLength": 4, - "gasUsed": 78318, + "gasUsed": 78350, } `; @@ -24,6 +24,6 @@ Object { exports[`EthPrivErc20Vault transfer can transfer to whitelisted user 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 59040, + "gasUsed": 59015, } `; diff --git a/test/__snapshots__/EthVault.multicall.spec.ts.snap b/test/__snapshots__/EthVault.multicall.spec.ts.snap index 85d4dd42..7780e669 100644 --- a/test/__snapshots__/EthVault.multicall.spec.ts.snap +++ b/test/__snapshots__/EthVault.multicall.spec.ts.snap @@ -3,13 +3,13 @@ exports[`EthVault - multicall can update state and queue for exit 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 168253, + "gasUsed": 168196, } `; exports[`EthVault - multicall can update state and queue for exit 2`] = ` Object { "calldataByteLength": 548, - "gasUsed": 109869, + "gasUsed": 109844, } `; diff --git a/test/__snapshots__/EthVault.state.spec.ts.snap b/test/__snapshots__/EthVault.state.spec.ts.snap index ee528fe1..6f1db2a9 100644 --- a/test/__snapshots__/EthVault.state.spec.ts.snap +++ b/test/__snapshots__/EthVault.state.spec.ts.snap @@ -10,14 +10,14 @@ Object { exports[`EthVault - state applies penalty when delta is below zero 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 117639, + "gasUsed": 117338, } `; exports[`EthVault - state splits penalty between exiting assets and staking assets 1`] = ` Object { "calldataByteLength": 196, - "gasUsed": 107192, + "gasUsed": 107117, } `; diff --git a/test/__snapshots__/EthVault.token.spec.ts.snap b/test/__snapshots__/EthVault.token.spec.ts.snap index a0a89440..29a129bd 100644 --- a/test/__snapshots__/EthVault.token.spec.ts.snap +++ b/test/__snapshots__/EthVault.token.spec.ts.snap @@ -24,27 +24,27 @@ Object { exports[`EthVault - token transfer from when the spender has enough allowance when the token owner has enough balance transfers the requested amount 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 54827, + "gasUsed": 54802, } `; exports[`EthVault - token transfer from when the spender has unlimited allowance does not decrease the spender allowance 1`] = ` Object { "calldataByteLength": 100, - "gasUsed": 61303, + "gasUsed": 61278, } `; exports[`EthVault - token transfer when the sender transfers all balance transfers the requested amount 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 54490, + "gasUsed": 54465, } `; exports[`EthVault - token transfer when the sender transfers zero tokens transfers the requested amount 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 36566, + "gasUsed": 36541, } `; diff --git a/test/__snapshots__/EthVault.withdraw.spec.ts.snap b/test/__snapshots__/EthVault.withdraw.spec.ts.snap index 2e446e0e..4201d357 100644 --- a/test/__snapshots__/EthVault.withdraw.spec.ts.snap +++ b/test/__snapshots__/EthVault.withdraw.spec.ts.snap @@ -31,7 +31,7 @@ Object { exports[`EthVault - withdraw enter exit queue locks assets for the time of exit 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 77184, + "gasUsed": 77127, } `; diff --git a/test/__snapshots__/EthVaultFactory.spec.ts.snap b/test/__snapshots__/EthVaultFactory.spec.ts.snap index f0a7f936..406947fa 100644 --- a/test/__snapshots__/EthVaultFactory.spec.ts.snap +++ b/test/__snapshots__/EthVaultFactory.spec.ts.snap @@ -3,28 +3,28 @@ exports[`EthVaultFactory EthErc20Vault private vault deployment with own escrow gas 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 577167, + "gasUsed": 577199, } `; exports[`EthVaultFactory EthErc20Vault private vault deployment with shared escrow gas 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 428348, + "gasUsed": 428380, } `; exports[`EthVaultFactory EthErc20Vault public vault deployment with own escrow gas 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 553235, + "gasUsed": 553267, } `; exports[`EthVaultFactory EthErc20Vault public vault deployment with shared escrow gas 1`] = ` Object { "calldataByteLength": 516, - "gasUsed": 404416, + "gasUsed": 404448, } `; diff --git a/test/__snapshots__/RewardSplitter.spec.ts.snap b/test/__snapshots__/RewardSplitter.spec.ts.snap index cd131463..8e606aaa 100644 --- a/test/__snapshots__/RewardSplitter.spec.ts.snap +++ b/test/__snapshots__/RewardSplitter.spec.ts.snap @@ -24,13 +24,13 @@ Object { exports[`RewardSplitter withdraw rewards can claim vault tokens for ERC-20 vault 1`] = ` Object { "calldataByteLength": 68, - "gasUsed": 78205, + "gasUsed": 78180, } `; exports[`RewardSplitter withdraw rewards can enter exit queue with multicall 1`] = ` Object { "calldataByteLength": 612, - "gasUsed": 170686, + "gasUsed": 170629, } `; diff --git a/test/__snapshots__/SharedMevEscrow.spec.ts.snap b/test/__snapshots__/SharedMevEscrow.spec.ts.snap index d26d366c..99c6db1e 100644 --- a/test/__snapshots__/SharedMevEscrow.spec.ts.snap +++ b/test/__snapshots__/SharedMevEscrow.spec.ts.snap @@ -3,6 +3,6 @@ exports[`SharedMevEscrow vault deployment gas 1`] = ` Object { "calldataByteLength": 683, - "gasUsed": 167457, + "gasUsed": 167433, } `;