diff --git a/src/L1YearnEscrow.sol b/src/L1YearnEscrow.sol index 96985c8..c23b15f 100644 --- a/src/L1YearnEscrow.sol +++ b/src/L1YearnEscrow.sol @@ -30,6 +30,11 @@ contract L1YearnEscrow is L1Escrow { */ event UpdateMinimumBuffer(uint256 newMinimumBuffer); + /** + * @dev Emitted when the deposit limit is updated. + */ + event UpdateDepositLimit(uint256 newDepositLimit); + // **************************** // * ERC-7201 Storage * // ************************** @@ -38,7 +43,8 @@ contract L1YearnEscrow is L1Escrow { struct VaultStorage { IVault vaultAddress; uint256 deposited; - uint256 minimumBuffer; + uint128 minimumBuffer; + uint128 depositLimit; } // keccak256(abi.encode(uint256(keccak256("yearn.storage.vault")) - 1)) & ~bytes32(uint256(0xff)) @@ -66,6 +72,11 @@ contract L1YearnEscrow is L1Escrow { return $.minimumBuffer; } + function depositLimit() public view returns (uint256) { + VaultStorage storage $ = _getVaultStorage(); + return $.depositLimit; + } + // **************************** // * Initializer * // **************************** @@ -107,6 +118,9 @@ contract L1YearnEscrow is L1Escrow { // Set the vault variable VaultStorage storage $ = _getVaultStorage(); $.vaultAddress = IVault(_vaultAddress); + + // Default to no deposit limit. + $.depositLimit = type(uint128).max; } // **************************** @@ -120,10 +134,12 @@ contract L1YearnEscrow is L1Escrow { function _receiveTokens( uint256 amount ) internal virtual override whenNotPaused { + VaultStorage storage $ = _getVaultStorage(); + require($.deposited + amount <= $.depositLimit, "deposit limit"); + IERC20 originToken = originTokenAddress(); originToken.safeTransferFrom(msg.sender, address(this), amount); - VaultStorage storage $ = _getVaultStorage(); unchecked { $.deposited += amount; } @@ -281,10 +297,11 @@ contract L1YearnEscrow is L1Escrow { /** * @dev Update the minimum buffer to keep in the escrow. + * uint128 max would be the max buffer. * @param _minimumBuffer The new minimum buffer to enforce. */ function updateMinimumBuffer( - uint256 _minimumBuffer + uint128 _minimumBuffer ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { VaultStorage storage $ = _getVaultStorage(); $.minimumBuffer = _minimumBuffer; @@ -292,6 +309,20 @@ contract L1YearnEscrow is L1Escrow { emit UpdateMinimumBuffer(_minimumBuffer); } + /** + * @dev Update the deposit limit to use for the escrow. + * uint128 is the max and means no deposit limit. + * @param _depositLimit The new deposit limit to enforce. + */ + function updateDepositLimit( + uint128 _depositLimit + ) external virtual onlyRole(DEFAULT_ADMIN_ROLE) { + VaultStorage storage $ = _getVaultStorage(); + $.depositLimit = _depositLimit; + + emit UpdateDepositLimit(_depositLimit); + } + /** * @notice Rebalance the funds to support the minimum buffer. * @dev Will revert if the difference is over the maxDeposit. diff --git a/test/L1Escrow.t.sol b/test/L1Escrow.t.sol index 2aad7c4..56fea6a 100644 --- a/test/L1Escrow.t.sol +++ b/test/L1Escrow.t.sol @@ -154,7 +154,94 @@ contract EscrowTest is Setup { assertEq(vault.balanceOf(address(mockEscrow)), 0); } - function test_bridgeAsset_maxDepositLimit(uint256 _amount) public { + function test_bridgeAsset_escrowDepositLimit(uint256 _amount) public { + _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); + address counterPart = l1Deployer.getL2EscrowAddress( + l2RollupID, + address(asset) + ); + mockEscrow = deployMockL1Escrow(); + + // Only Admin can update deposit limit + vm.expectRevert(); + mockEscrow.updateDepositLimit(0); + + vm.prank(governator); + mockEscrow.updateDepositLimit(0); + + // Simulate a bridge txn + airdrop(asset, user, _amount); + + vm.prank(user); + asset.approve(address(mockEscrow), _amount); + + // OVer Deposit limit + vm.expectRevert("deposit limit"); + vm.prank(user); + mockEscrow.bridgeToken(user, _amount, true); + + vm.prank(governator); + mockEscrow.updateDepositLimit(uint128(_amount)); + + bytes memory data = abi.encode(user, _amount); + uint256 depositCount = polygonZkEVMBridge.depositCount(); + vm.expectEmit(true, true, true, true, address(polygonZkEVMBridge)); + emit BridgeEvent( + 1, + l1RollupID, + address(mockEscrow), + l2RollupID, + counterPart, + 0, + data, + uint32(depositCount) + ); + vm.prank(user); + mockEscrow.bridgeToken(user, _amount, true); + + assertEq(vault.totalAssets(), _amount); + assertEq(mockEscrow.deposited(), _amount); + assertEq(asset.balanceOf(user), 0); + assertEq(asset.balanceOf(address(mockEscrow)), 0); + assertEq(vault.balanceOf(address(mockEscrow)), _amount); + + airdrop(asset, user, _amount); + + vm.prank(user); + asset.approve(address(mockEscrow), _amount); + + vm.expectRevert("deposit limit"); + vm.prank(user); + mockEscrow.bridgeToken(user, _amount, true); + + vm.prank(governator); + mockEscrow.updateDepositLimit(uint128(_amount * 2)); + + vm.prank(user); + mockEscrow.bridgeToken(user, _amount, true); + + assertEq(vault.totalAssets(), _amount * 2); + assertEq(mockEscrow.deposited(), _amount * 2); + assertEq(asset.balanceOf(user), 0); + assertEq(asset.balanceOf(address(mockEscrow)), 0); + assertEq(vault.balanceOf(address(mockEscrow)), _amount * 2); + + // Withdraw half + uint256 toWithdraw = _amount + 10; + + data = abi.encode(user, toWithdraw); + + vm.prank(address(polygonZkEVMBridge)); + mockEscrow.onMessageReceived(counterPart, l2RollupID, data); + + assertEq(vault.totalAssets(), _amount - 10); + assertEq(mockEscrow.deposited(), _amount * 2 - toWithdraw); + assertEq(asset.balanceOf(user), toWithdraw); + assertEq(asset.balanceOf(address(mockEscrow)), 0); + assertEq(vault.balanceOf(address(mockEscrow)), _amount - 10); + } + + function test_bridgeAsset_vaultDepositLimit(uint256 _amount) public { _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); address counterPart = l1Deployer.getL2EscrowAddress( l2RollupID, @@ -227,10 +314,10 @@ contract EscrowTest is Setup { function test_bridgeAsset_minimumBuffer( uint256 _amount, - uint256 _minimumBuffer + uint128 _minimumBuffer ) public { _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); - _minimumBuffer = bound(_minimumBuffer, 10, maxFuzzAmount); + _minimumBuffer = uint128(bound(_minimumBuffer, 10, maxFuzzAmount)); address counterPart = l1Deployer.getL2EscrowAddress( l2RollupID, address(asset) @@ -238,6 +325,7 @@ contract EscrowTest is Setup { mockEscrow = deployMockL1Escrow(); + // Only Admin can update vm.expectRevert(); mockEscrow.updateMinimumBuffer(_minimumBuffer); @@ -270,9 +358,9 @@ contract EscrowTest is Setup { assertEq(vault.balanceOf(address(mockEscrow)), 0); } - function test_rebalance(uint256 _amount, uint256 _minimumBuffer) public { + function test_rebalance(uint256 _amount, uint128 _minimumBuffer) public { _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); - _minimumBuffer = bound(_minimumBuffer, 10, maxFuzzAmount); + _minimumBuffer = uint128(bound(_minimumBuffer, 10, maxFuzzAmount)); address counterPart = l1Deployer.getL2EscrowAddress( l2RollupID, address(asset) @@ -471,10 +559,10 @@ contract EscrowTest is Setup { function test_illiquidWithdraw_withBuffer( uint256 _amount, - uint256 _minimumBuffer + uint128 _minimumBuffer ) public { _amount = bound(_amount, minFuzzAmount, maxFuzzAmount); - _minimumBuffer = bound(_minimumBuffer, 10, _amount / 2); + _minimumBuffer = uint128(bound(_minimumBuffer, 10, _amount / 2)); address counterPart = l1Deployer.getL2EscrowAddress( l2RollupID, address(asset)