From 2d26e210aa24193aa472b31795d5e4c1e73e611a Mon Sep 17 00:00:00 2001 From: Benjamin Date: Mon, 29 Jul 2024 08:46:16 +0200 Subject: [PATCH] Improve stake & lock UX (#16) * Improve stake & lock UX * Skip calling .permit() if the data might be bad. * forge fmt * update the if statement wrapping permit call * flip the if statement * >= for permit.deadline check --------- Co-authored-by: bxmmm1 --- .../script/DeployPufferL2Depositor.s.sol | 4 +- mainnet-contracts/src/PufLocker.sol | 15 ++- mainnet-contracts/src/PufferL2Depositor.sol | 62 ++++++++++--- .../src/interface/IPufLocker.sol | 3 +- .../src/interface/IPufferL2Depositor.sol | 10 +- mainnet-contracts/test/unit/PufLocker.t.sol | 25 +++-- .../test/unit/PufferL2Staking.t.sol | 91 ++++++++++++++++--- 7 files changed, 161 insertions(+), 49 deletions(-) diff --git a/mainnet-contracts/script/DeployPufferL2Depositor.s.sol b/mainnet-contracts/script/DeployPufferL2Depositor.s.sol index 586a922..d0397f5 100644 --- a/mainnet-contracts/script/DeployPufferL2Depositor.s.sol +++ b/mainnet-contracts/script/DeployPufferL2Depositor.s.sol @@ -39,13 +39,13 @@ contract DeployPufferL2Depositor is Script { vm.startBroadcast(); - depositor = new PufferL2Depositor(address(accessManager), weth); - address pufLockerImpl = address(new PufLocker()); pufLocker = PufLocker( address(new ERC1967Proxy(pufLockerImpl, abi.encodeCall(PufLocker.initialize, (address(accessManager))))) ); + depositor = new PufferL2Depositor(address(accessManager), weth, pufLocker); + bytes[] memory calldatas = new bytes[](4); bytes4[] memory publicSelectors = new bytes4[](3); diff --git a/mainnet-contracts/src/PufLocker.sol b/mainnet-contracts/src/PufLocker.sol index 19caa98..f49d4cd 100644 --- a/mainnet-contracts/src/PufLocker.sol +++ b/mainnet-contracts/src/PufLocker.sol @@ -42,7 +42,7 @@ contract PufLocker is IPufLocker, AccessManagedUpgradeable, UUPSUpgradeable, Puf * @inheritdoc IPufLocker * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function deposit(address token, uint128 lockPeriod, Permit calldata permitData) + function deposit(address token, address recipient, uint128 lockPeriod, Permit calldata permitData) external isAllowedToken(token) restricted @@ -55,8 +55,13 @@ contract PufLocker is IPufLocker, AccessManagedUpgradeable, UUPSUpgradeable, Puf if (lockPeriod < $.minLockPeriod || lockPeriod > $.maxLockPeriod) { revert InvalidLockPeriod(); } - // if the first 32 bytes of the signature is non-zero - if (permitData.r != 0) { + + // The users that use a smart wallet and do not use the Permit and they do the .approve and then .deposit. + // They might get confused when they open Etherscan, and see: + // "Although one or more Error Occurred [execution reverted] Contract Execution Completed" + + // To avoid that, we don't want to call the permit function if it is not necessary. + if (permitData.deadline >= block.timestamp) { // https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#security_considerations try ERC20Permit(token).permit({ owner: msg.sender, @@ -73,9 +78,9 @@ contract PufLocker is IPufLocker, AccessManagedUpgradeable, UUPSUpgradeable, Puf uint128 releaseTime = uint128(block.timestamp) + lockPeriod; - $.deposits[msg.sender][token].push(Deposit(uint128(permitData.amount), releaseTime)); + $.deposits[recipient][token].push(Deposit(uint128(permitData.amount), releaseTime)); - emit Deposited(msg.sender, token, uint128(permitData.amount), releaseTime); + emit Deposited(recipient, token, uint128(permitData.amount), releaseTime); } /** diff --git a/mainnet-contracts/src/PufferL2Depositor.sol b/mainnet-contracts/src/PufferL2Depositor.sol index b17fe77..73b51f0 100644 --- a/mainnet-contracts/src/PufferL2Depositor.sol +++ b/mainnet-contracts/src/PufferL2Depositor.sol @@ -10,6 +10,7 @@ import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC2 import { Permit } from "./structs/Permit.sol"; import { IWETH } from "./interface/IWETH.sol"; import { IPufferL2Depositor } from "./interface/IPufferL2Depositor.sol"; +import { IPufLocker } from "./interface/IPufLocker.sol"; /** * @title Puffer L2 Depositor contract @@ -25,11 +26,14 @@ contract PufferL2Depositor is IPufferL2Depositor, AccessManaged { address public immutable WETH; + IPufLocker public immutable PUFFER_LOCKER; + mapping(address token => address pufToken) public tokens; mapping(address migrator => bool isAllowed) public isAllowedMigrator; - constructor(address accessManager, address weth) AccessManaged(accessManager) { + constructor(address accessManager, address weth, IPufLocker locker) AccessManaged(accessManager) { WETH = weth; + PUFFER_LOCKER = locker; _addNewToken(weth); } @@ -44,13 +48,19 @@ contract PufferL2Depositor is IPufferL2Depositor, AccessManaged { * @inheritdoc IPufferL2Depositor * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function deposit(address token, address account, Permit calldata permitData, uint256 referralCode) - external - onlySupportedTokens(token) - restricted - { - // if the first 32 bytes of the signature is non-zero - if (permitData.r != 0) { + function deposit( + address token, + address account, + Permit calldata permitData, + uint256 referralCode, + uint128 lockPeriod + ) external onlySupportedTokens(token) restricted { + // The users that use a smart wallet and do not use the Permit and they do the .approve and then .deposit. + // They might get confused when they open Etherscan, and see: + // "Although one or more Error Occurred [execution reverted] Contract Execution Completed" + + // To avoid that, we don't want to call the permit function if it is not necessary. + if (permitData.deadline >= block.timestamp) { // https://docs.openzeppelin.com/contracts/5.x/api/token/erc20#security_considerations try ERC20Permit(token).permit({ owner: msg.sender, @@ -66,6 +76,7 @@ contract PufferL2Depositor is IPufferL2Depositor, AccessManaged { IERC20(token).safeTransferFrom(msg.sender, address(this), permitData.amount); _deposit({ + lockPeriod: lockPeriod, token: token, depositor: msg.sender, account: account, @@ -78,10 +89,17 @@ contract PufferL2Depositor is IPufferL2Depositor, AccessManaged { * @inheritdoc IPufferL2Depositor * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function depositETH(address account, uint256 referralCode) external payable restricted { + function depositETH(address account, uint256 referralCode, uint128 lockPeriod) external payable restricted { IWETH(WETH).deposit{ value: msg.value }(); - _deposit({ token: WETH, depositor: msg.sender, account: account, amount: msg.value, referralCode: referralCode }); + _deposit({ + token: WETH, + depositor: msg.sender, + account: account, + amount: msg.value, + referralCode: referralCode, + lockPeriod: lockPeriod + }); } /** @@ -120,14 +138,30 @@ contract PufferL2Depositor is IPufferL2Depositor, AccessManaged { */ function revertIfPaused() external restricted { } - function _deposit(address token, address depositor, address account, uint256 amount, uint256 referralCode) - internal - { + function _deposit( + address token, + address depositor, + address account, + uint256 amount, + uint256 referralCode, + uint128 lockPeriod + ) internal { PufToken pufToken = PufToken(tokens[token]); IERC20(token).safeIncreaseAllowance(address(pufToken), amount); - pufToken.deposit(depositor, account, amount); + // If the lockPeriod is greater than 0 we wrap and then deposit the wrapped tokens to the locker contract + if (lockPeriod > 0) { + pufToken.deposit(depositor, address(this), amount); + IERC20(address(pufToken)).safeIncreaseAllowance(address(PUFFER_LOCKER), amount); + Permit memory permitData; + permitData.amount = amount; + // Tokens are being deposited to the locker contract for the account + PUFFER_LOCKER.deposit(address(pufToken), account, lockPeriod, permitData); + } else { + // The account will receive the ERC20 tokens + pufToken.deposit(depositor, account, amount); + } emit DepositedToken(token, msg.sender, account, amount, referralCode); } diff --git a/mainnet-contracts/src/interface/IPufLocker.sol b/mainnet-contracts/src/interface/IPufLocker.sol index 3095de3..5cd2bcc 100644 --- a/mainnet-contracts/src/interface/IPufLocker.sol +++ b/mainnet-contracts/src/interface/IPufLocker.sol @@ -87,10 +87,11 @@ interface IPufLocker { /** * @notice Deposit tokens into the locker * @param token The address of the token to deposit + * @param token The address of the recipient * @param lockPeriod The lock period for the deposit * @param permitData The permit data for the deposit */ - function deposit(address token, uint128 lockPeriod, Permit calldata permitData) external; + function deposit(address token, address recipient, uint128 lockPeriod, Permit calldata permitData) external; /** * @notice Withdraws specified deposits for a given token and transfers the funds to the recipient diff --git a/mainnet-contracts/src/interface/IPufferL2Depositor.sol b/mainnet-contracts/src/interface/IPufferL2Depositor.sol index 7c1cd3f..ee622e0 100644 --- a/mainnet-contracts/src/interface/IPufferL2Depositor.sol +++ b/mainnet-contracts/src/interface/IPufferL2Depositor.sol @@ -54,14 +54,20 @@ interface IPufferL2Depositor { * * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function deposit(address token, address account, Permit calldata permitData, uint256 referralCode) external; + function deposit( + address token, + address account, + Permit calldata permitData, + uint256 referralCode, + uint128 lockPeriod + ) external; /** * @notice Deposits naative ETH by wrapping it into WETH and then depositing to corresponding token contract * * @dev Restricted in this context is like `whenNotPaused` modifier from Pausable.sol */ - function depositETH(address account, uint256 referralCode) external payable; + function depositETH(address account, uint256 referralCode, uint128 lockPeriod) external payable; /** * @notice Called by the Token contracts to check if the system is paused diff --git a/mainnet-contracts/test/unit/PufLocker.t.sol b/mainnet-contracts/test/unit/PufLocker.t.sol index fadfdab..b4b82b6 100644 --- a/mainnet-contracts/test/unit/PufLocker.t.sol +++ b/mainnet-contracts/test/unit/PufLocker.t.sol @@ -7,9 +7,6 @@ import { IPufLocker } from "../../src/interface/IPufLocker.sol"; import { AccessManager } from "@openzeppelin/contracts/access/manager/AccessManager.sol"; import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; import { ROLE_ID_OPERATIONS_MULTISIG, PUBLIC_ROLE } from "../../script/Roles.sol"; -import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IAccessManaged } from "@openzeppelin/contracts/access/manager/IAccessManaged.sol"; import { ERC20Mock } from "../mocks/ERC20Mock.sol"; @@ -75,7 +72,7 @@ contract PufLockerTest is UnitTestHelper { Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); - pufLocker.deposit(address(mockToken), 61, permit); // Lock for 1 minute + pufLocker.deposit(address(mockToken), address(this), 61, permit); // Lock for 1 minute } function testRevert_SetAllowedToken_Unauthorized() public { @@ -105,7 +102,7 @@ contract PufLockerTest is UnitTestHelper { Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); - pufLocker.deposit(address(mockToken), 3600, permit); // Lock for 1 hour + pufLocker.deposit(address(mockToken), bob, 3600, permit); // Lock for 1 hour (PufLocker.Deposit[] memory deposits) = pufLocker.getDeposits(bob, address(mockToken), 0, 1); assertEq(deposits.length, 1, "Should have 1 deposit"); assertEq(deposits[0].amount, 10e18, "Deposit amount should be 100 tokens"); @@ -118,7 +115,7 @@ contract PufLockerTest is UnitTestHelper { Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); vm.expectRevert(abi.encodeWithSelector(InvalidAmount.selector)); - pufLocker.deposit(address(mockToken), 3600, permit); // Lock for 1 hour with 0 amount + pufLocker.deposit(address(mockToken), bob, 3600, permit); // Lock for 1 hour with 0 amount vm.stopPrank(); } @@ -128,7 +125,7 @@ contract PufLockerTest is UnitTestHelper { Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); vm.expectRevert(abi.encodeWithSelector(IPufLocker.InvalidLockPeriod.selector)); - pufLocker.deposit(address(mockToken), 30, permit); // Lock for 30 seconds, which is less than minLockPeriod + pufLocker.deposit(address(mockToken), bob, 30, permit); // Lock for 30 seconds, which is less than minLockPeriod vm.stopPrank(); } @@ -137,7 +134,7 @@ contract PufLockerTest is UnitTestHelper { uint256 amount = 10e18; Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); - pufLocker.deposit(address(mockToken), 60, permit); // Lock for 1 minute + pufLocker.deposit(address(mockToken), bob, 60, permit); // Lock for 1 minute vm.warp(block.timestamp + 61); // Fast forward time by 61 seconds uint256[] memory indexes = new uint256[](1); @@ -155,7 +152,7 @@ contract PufLockerTest is UnitTestHelper { uint256 amount = 10e18; Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); - pufLocker.deposit(address(mockToken), 3600, permit); // Lock for 1 hour + pufLocker.deposit(address(mockToken), bob, 3600, permit); // Lock for 1 hour uint256[] memory indexes = new uint256[](1); indexes[0] = 0; @@ -169,7 +166,7 @@ contract PufLockerTest is UnitTestHelper { uint256 amount = 10e18; Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); - pufLocker.deposit(address(mockToken), 60, permit); // Lock for 1 minute + pufLocker.deposit(address(mockToken), bob, 60, permit); // Lock for 1 minute vm.warp(block.timestamp + 61); // Fast forward time by 61 seconds uint256[] memory indexes = new uint256[](2); @@ -186,7 +183,7 @@ contract PufLockerTest is UnitTestHelper { uint256 amount = 10e18; Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); - pufLocker.deposit(address(mockToken), 60, permit); // Lock for 1 minute + pufLocker.deposit(address(mockToken), bob, 60, permit); // Lock for 1 minute vm.warp(block.timestamp + 61); // Fast forward time by 61 seconds uint256[] memory indexes = new uint256[](1); @@ -206,20 +203,20 @@ contract PufLockerTest is UnitTestHelper { uint256 amount = 2e18; Permit memory permit = _signPermit(_testTemps("bob", address(pufLocker), amount, block.timestamp), mockToken.DOMAIN_SEPARATOR()); - pufLocker.deposit(address(mockToken), 3601, permit); + pufLocker.deposit(address(mockToken), bob, 3601, permit); uint256 amount2 = 4e18; Permit memory permit2 = _signPermit(_testTemps("bob", address(pufLocker), amount2, block.timestamp), mockToken.DOMAIN_SEPARATOR()); mockToken.approve(address(pufLocker), amount2); - pufLocker.deposit(address(mockToken), 3620, permit2); + pufLocker.deposit(address(mockToken), bob, 3620, permit2); uint256 amount3 = 5e18; Permit memory permit3 = _signPermit( _testTemps("bob", address(pufLocker), amount3, block.timestamp + 2), mockToken.DOMAIN_SEPARATOR() ); mockToken.approve(address(pufLocker), amount3); - pufLocker.deposit(address(mockToken), 3630, permit3); + pufLocker.deposit(address(mockToken), bob, 3630, permit3); // Get the first 2 deposits PufLocker.Deposit[] memory depositsPage1 = pufLocker.getDeposits(bob, address(mockToken), 0, 2); diff --git a/mainnet-contracts/test/unit/PufferL2Staking.t.sol b/mainnet-contracts/test/unit/PufferL2Staking.t.sol index 2cff963..c8af507 100644 --- a/mainnet-contracts/test/unit/PufferL2Staking.t.sol +++ b/mainnet-contracts/test/unit/PufferL2Staking.t.sol @@ -14,8 +14,12 @@ import { Permit } from "../../src/structs/Permit.sol"; import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol"; +import { ERC1967Proxy } from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; import { IAccessManaged } from "@openzeppelin/contracts/access/manager/IAccessManaged.sol"; import { InvalidAmount } from "../../src/Errors.sol"; +import { IPufLocker } from "../../src/interface/IPufLocker.sol"; +import { PufLocker } from "../../src/PufLocker.sol"; +import { IPufLocker } from "../../src/interface/IPufLocker.sol"; contract MockToken is ERC20, ERC20Permit { uint8 _dec; // decimals @@ -58,6 +62,7 @@ contract PufferL2Staking is UnitTestHelper { MockToken sixDecimal; MockToken twentyTwoDecimal; MockToken notSupportedToken; + PufLocker pufLocker; address mockMigrator; @@ -74,7 +79,12 @@ contract PufferL2Staking is UnitTestHelper { twentyTwoDecimal = new MockToken("TwentyTwoDecimal", "TKN22", 22); notSupportedToken = new MockToken("NotSupported", "NOT", 18); - depositor = new PufferL2Depositor(address(accessManager), address(weth)); + address pufLockerImpl = address(new PufLocker()); + pufLocker = PufLocker( + address(new ERC1967Proxy(pufLockerImpl, abi.encodeCall(PufLocker.initialize, (address(accessManager))))) + ); + + depositor = new PufferL2Depositor(address(accessManager), address(weth), pufLocker); // Access setup @@ -105,11 +115,44 @@ contract PufferL2Staking is UnitTestHelper { vm.prank(address(timelock)); accessManager.multicall(calldatas); + // Access setup Locker + + calldatas = new bytes[](2); + + publicSelectors = new bytes4[](2); + publicSelectors[0] = PufLocker.deposit.selector; + publicSelectors[1] = PufLocker.withdraw.selector; + + calldatas[0] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, address(pufLocker), publicSelectors, PUBLIC_ROLE + ); + + multisigSelectors = new bytes4[](2); + multisigSelectors[0] = PufLocker.setIsAllowedToken.selector; + multisigSelectors[1] = PufLocker.setLockPeriods.selector; + + calldatas[1] = abi.encodeWithSelector( + AccessManager.setTargetFunctionRole.selector, + address(pufLocker), + multisigSelectors, + ROLE_ID_OPERATIONS_MULTISIG + ); + + // bytes memory multicallData = abi.encodeCall(Multicall.multicall, (calldatas)); + vm.prank(address(timelock)); + accessManager.multicall(calldatas); + vm.startPrank(OPERATIONS_MULTISIG); depositor.addNewToken(address(dai)); depositor.addNewToken(address(sixDecimal)); depositor.addNewToken(address(twentyTwoDecimal)); + + pufLocker.setLockPeriods(0, 365 days); + + pufLocker.setIsAllowedToken(depositor.tokens(address(dai)), true); + pufLocker.setIsAllowedToken(depositor.tokens(address(sixDecimal)), true); + pufLocker.setIsAllowedToken(depositor.tokens(address(twentyTwoDecimal)), true); } function test_setup() public view { @@ -147,7 +190,7 @@ contract PufferL2Staking is UnitTestHelper { vm.expectEmit(true, true, true, true); emit IPufferL2Depositor.DepositedToken(address(dai), bob, bob, amount, refCode); - depositor.deposit(address(dai), bob, permit, refCode); + depositor.deposit(address(dai), bob, permit, refCode, 0); PufToken pufToken = PufToken(depositor.tokens(address(dai))); assertEq(pufToken.balanceOf(bob), amount, "bob got pufToken"); @@ -168,7 +211,7 @@ contract PufferL2Staking is UnitTestHelper { vm.expectEmit(true, true, true, true); emit IPufferL2Depositor.DepositedToken(address(sixDecimal), bob, bob, amount, referralCode); - depositor.deposit(address(sixDecimal), bob, permit, referralCode); + depositor.deposit(address(sixDecimal), bob, permit, referralCode, 0); PufToken pufToken = PufToken(depositor.tokens(address(sixDecimal))); @@ -198,7 +241,7 @@ contract PufferL2Staking is UnitTestHelper { vm.expectEmit(true, true, true, true); emit IPufferL2Depositor.DepositedToken(address(twentyTwoDecimal), bob, bob, amount, referralCode); - depositor.deposit(address(twentyTwoDecimal), bob, permit, referralCode); + depositor.deposit(address(twentyTwoDecimal), bob, permit, referralCode, 0); PufToken pufToken = PufToken(depositor.tokens(address(twentyTwoDecimal))); @@ -228,7 +271,7 @@ contract PufferL2Staking is UnitTestHelper { vm.expectEmit(true, true, true, true); emit IPufferL2Depositor.DepositedToken(address(dai), bob, bob, amount, refCode); - depositor.deposit(address(dai), bob, permit, refCode); + depositor.deposit(address(dai), bob, permit, refCode, 0); PufToken pufToken = PufToken(depositor.tokens(address(dai))); assertEq(pufToken.balanceOf(bob), amount, "bob got pufToken"); @@ -252,7 +295,7 @@ contract PufferL2Staking is UnitTestHelper { // weth.permit triggers weth.fallback() and it doesn't revert vm.expectEmit(true, true, true, true); emit IPufferL2Depositor.DepositedToken(address(weth), bob, bob, amount, refCode); - depositor.deposit(address(weth), bob, permit, refCode); + depositor.deposit(address(weth), bob, permit, refCode, 0); PufToken pufToken = PufToken(depositor.tokens(address(weth))); assertEq(pufToken.balanceOf(bob), amount, "bob got pufToken"); @@ -268,7 +311,7 @@ contract PufferL2Staking is UnitTestHelper { vm.expectEmit(true, true, true, true); emit IPufferL2Depositor.DepositedToken(address(weth), bob, bob, amount, refCode); - depositor.depositETH{ value: amount }(bob, refCode); + depositor.depositETH{ value: amount }(bob, refCode, 0); PufToken pufToken = PufToken(depositor.tokens(address(weth))); assertEq(pufToken.balanceOf(bob), amount, "bob got pufToken"); @@ -392,7 +435,7 @@ contract PufferL2Staking is UnitTestHelper { vm.startPrank(bob); vm.expectRevert(); - depositor.deposit(address(notSupportedToken), bob, permit, referralCode); + depositor.deposit(address(notSupportedToken), bob, permit, referralCode, 0); } // zero address token reverts @@ -424,7 +467,7 @@ contract PufferL2Staking is UnitTestHelper { vm.startPrank(bob); vm.expectRevert(InvalidAmount.selector); - depositor.depositETH{ value: 0 }(bob, referralCode); + depositor.depositETH{ value: 0 }(bob, referralCode, 0); } // Mock address 123 is not allowed to be migrator @@ -440,14 +483,14 @@ contract PufferL2Staking is UnitTestHelper { vm.deal(bob, 1 ether); vm.startPrank(bob); vm.expectRevert(); - depositor.depositETH{ value: 1 ether }(address(0), referralCode); + depositor.depositETH{ value: 1 ether }(address(0), referralCode, 0); } // 0 deposit eth reverts function testRevert_zero_deposit_ETH() public { vm.startPrank(bob); vm.expectRevert(); - depositor.depositETH{ value: 0 }(bob, referralCode); + depositor.depositETH{ value: 0 }(bob, referralCode, 0); } // No deposit reverts @@ -504,6 +547,32 @@ contract PufferL2Staking is UnitTestHelper { pufToken.deposit(bob, bob, 7 ether); } + // Deposit and lock tokens in one tx + function test_deposit_and_lock(uint32 amount, uint256 refCode) public { + vm.assume(amount > 0); + + // This is a bad permit signature + Permit memory permit = + _signPermit(_testTemps("bob", address(depositor), amount, block.timestamp), "dummy domain separator"); + + dai.mint(bob, amount); + + vm.startPrank(bob); + + dai.approve(address(depositor), amount); + + vm.expectEmit(true, true, true, true); + emit IPufferL2Depositor.DepositedToken(address(dai), bob, bob, amount, refCode); + depositor.deposit(address(dai), bob, permit, refCode, 15); // lock for 15 seconds + + PufToken pufToken = PufToken(depositor.tokens(address(dai))); + assertEq(pufToken.balanceOf(bob), 0, "bob did not get pufToken"); + + (PufLocker.Deposit[] memory deposits) = pufLocker.getDeposits(bob, address(pufToken), 0, 1); + assertEq(deposits.length, 1, "Should have 1 deposit"); + assertEq(deposits[0].amount, amount, "Deposit amount locked for Bob"); + } + function testRevert_SetDepositCap_Unauthorized() public { // Try setting the supply cap from an unauthorized address vm.startPrank(bob);