diff --git a/contracts/partners/tokenWrappers/MezoWrapper.sol b/contracts/partners/tokenWrappers/MezoWrapper.sol new file mode 100644 index 0000000..1485dde --- /dev/null +++ b/contracts/partners/tokenWrappers/MezoWrapper.sol @@ -0,0 +1,72 @@ +// SPDX-License-Identifier: GPL-3.0 + +pragma solidity ^0.8.17; + +import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; + +import { PullTokenWrapperImmutableBase } from "./PullTokenWrapperImmutableBase.sol"; +import { Errors } from "../../utils/Errors.sol"; + +interface IMezoStaking { + function createLockFor(uint256 _value, uint256 _lockDuration, address _to) external returns (uint256); +} + +/// @title MezoWrapper +/// @notice Non-upgradeable wrapper that creates Mezo locks for claimed tokens +/// @dev On claim, underlying tokens are approved and locked via `createLockFor` on the Mezo staking contract +contract MezoWrapper is PullTokenWrapperImmutableBase { + using SafeERC20 for IERC20; + + /// @notice Mezo staking contract + address public immutable mezoStaking; + /// @notice Duration used when creating locks + uint256 public lockDuration; + + event LockDurationUpdated(uint256 newLockDuration); + + constructor( + address _token, + address _distributionCreator, + address _holder, + address _mezoStaking, + uint256 _lockDuration, + string memory _name, + string memory _symbol + ) ERC20(_name, _symbol) PullTokenWrapperImmutableBase(_token, _distributionCreator, _holder) { + if (_mezoStaking == address(0)) revert Errors.ZeroAddress(); + mezoStaking = _mezoStaking; + lockDuration = _lockDuration; + IERC20(_token).safeApprove(_mezoStaking, type(uint256).max); + } + + /// @notice Hook called before every transfer: on claim, pulls tokens from holder and creates a Mezo lock; + /// on fee transfer, pulls tokens from holder and sends them directly to the fee recipient + function _beforeTokenTransfer(address from, address to, uint256 amount) internal override { + if (to == feeRecipient) { + IERC20(token).safeTransferFrom(holder, to, amount); + } else if (from == distributor) { + if (lockDuration == 0) { + IERC20(token).safeTransferFrom(holder, to, amount); + } else { + IERC20(token).safeTransferFrom(holder, address(this), amount); + IMezoStaking(mezoStaking).createLockFor(amount, lockDuration, to); + } + } + } + + // ================================= ADMIN ================================= + + /// @notice Updates the lock duration used for future claims + function setLockDuration(uint256 _newLockDuration) external onlyHolderOrGovernor { + lockDuration = _newLockDuration; + emit LockDurationUpdated(_newLockDuration); + } + + /// @notice Resets the token allowance granted to the Mezo staking contract + function setStakingAllowance(uint256 _allowance) external onlyHolderOrGovernor { + IERC20(token).safeApprove(mezoStaking, 0); + IERC20(token).safeApprove(mezoStaking, _allowance); + } +} diff --git a/contracts/partners/tokenWrappers/PublicWrapperBase.sol b/contracts/partners/tokenWrappers/PublicWrapperBase.sol index a9e9c4e..b20e8f9 100644 --- a/contracts/partners/tokenWrappers/PublicWrapperBase.sol +++ b/contracts/partners/tokenWrappers/PublicWrapperBase.sol @@ -56,6 +56,9 @@ abstract contract PublicWrapperBase is PullTokenWrapperImmutableBase { if (_minting == 0 && isAllowed[to] == 0) _burn(to, amount); } + /// @notice Disabled: minting happens automatically in `_beforeTokenTransfer` + function mint(address, uint256) external override {} + /// @notice Burns wrapper tokens from a given address function burn(address from, uint256 amount) external onlyHolderOrGovernor { _burn(from, amount); diff --git a/contracts/partners/tokenWrappers/PullTokenWrapperImmutableBase.sol b/contracts/partners/tokenWrappers/PullTokenWrapperImmutableBase.sol index 0bd661c..dba53c7 100644 --- a/contracts/partners/tokenWrappers/PullTokenWrapperImmutableBase.sol +++ b/contracts/partners/tokenWrappers/PullTokenWrapperImmutableBase.sol @@ -70,7 +70,7 @@ abstract contract PullTokenWrapperImmutableBase is ERC20 { /// @notice Mints wrapper tokens to a recipient and allows them to hold wrapper tokens /// @param recipient Address to receive the minted wrapper tokens /// @param amount Amount of wrapper tokens to mint - function mint(address recipient, uint256 amount) external onlyHolderOrGovernor { + function mint(address recipient, uint256 amount) external virtual onlyHolderOrGovernor { isAllowed[recipient] = 1; _mint(recipient, amount); } diff --git a/contracts/partners/tokenWrappers/PullTokenWrapperVaultImmutable.sol b/contracts/partners/tokenWrappers/PullTokenWrapperVaultImmutable.sol index 1664035..e27d033 100644 --- a/contracts/partners/tokenWrappers/PullTokenWrapperVaultImmutable.sol +++ b/contracts/partners/tokenWrappers/PullTokenWrapperVaultImmutable.sol @@ -32,7 +32,7 @@ contract PullTokenWrapperVaultImmutable is PullTokenWrapperImmutableBase { address _holder, address _vault ) - ERC20(string(abi.encodePacked(IERC20Metadata(_token).name(), " (wrapped)")), IERC20Metadata(_token).symbol()) + ERC20(string(abi.encodePacked(IERC20Metadata(_vault).name(), " (wrapped)")), IERC20Metadata(_token).symbol()) PullTokenWrapperImmutableBase(_token, _distributionCreator, _holder) { if (_vault == address(0)) revert Errors.ZeroAddress(); diff --git a/scripts/MockToken.s.sol b/scripts/MockToken.s.sol index 7861b3f..c6931bd 100644 --- a/scripts/MockToken.s.sol +++ b/scripts/MockToken.s.sol @@ -13,17 +13,14 @@ contract MockTokenScript is BaseScript {} // Deploy script contract Deploy is MockTokenScript { function run() external broadcast { + // forge script scripts/MockToken.s.sol:Deploy --rpc-url avalanche --sender 0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701 --broadcast // MODIFY THESE VALUES TO SET YOUR DESIRED TOKEN PARAMETERS - string memory name = "Mock Token"; - string memory symbol = "MOCK"; + string memory name = "USDe"; + string memory symbol = "USDe"; uint8 decimals = 18; _run(name, symbol, decimals); } - function run(string calldata name, string calldata symbol, uint8 decimals) external broadcast { - _run(name, symbol, decimals); - } - function _run(string memory name, string memory symbol, uint8 decimals) internal { console.log("DEPLOYER_ADDRESS:", broadcaster); diff --git a/scripts/deployMezoWrapper.s.sol b/scripts/deployMezoWrapper.s.sol new file mode 100644 index 0000000..5c88d79 --- /dev/null +++ b/scripts/deployMezoWrapper.s.sol @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.17; + +import { console } from "forge-std/console.sol"; + +import { BaseScript } from "./utils/Base.s.sol"; + +import { MezoWrapper } from "../contracts/partners/tokenWrappers/MezoWrapper.sol"; + +contract DeployMezoWrapper is BaseScript { + // forge script scripts/deployMezoWrapper.s.sol --rpc-url mezo --sender 0xA9DdD91249DFdd450E81E1c56Ab60E1A62651701 --broadcast --verify + function run() public { + uint256 deployerPrivateKey = vm.envUint("DEPLOYER_PRIVATE_KEY"); + vm.startBroadcast(deployerPrivateKey); + + address distributionCreator = 0x8BB4C975Ff3c250e0ceEA271728547f3802B36Fd; + + // ------------------------------------------------------------------------ + // TO EDIT + address underlying = 0x7B7c000000000000000000000000000000000001; + address holder = 0x6b57b0Ef5594a5820fD473353180442764d8601D; + address mezoStaking = 0xb90fdAd3DFD180458D62Cc6acedc983D78E20122; + uint256 lockDuration = 0; + string memory name = "veMEZO (wrapped)"; + string memory symbol = "veMEZO"; + // ------------------------------------------------------------------------ + + MezoWrapper wrapper = new MezoWrapper( + underlying, + distributionCreator, + holder, + mezoStaking, + lockDuration, + name, + symbol + ); + + console.log("MezoWrapper:", address(wrapper)); + + vm.stopBroadcast(); + } +} diff --git a/scripts/deployWETH9.s.sol b/scripts/deployWETH9.s.sol index 1c522d9..7127dd8 100644 --- a/scripts/deployWETH9.s.sol +++ b/scripts/deployWETH9.s.sol @@ -1,8 +1,8 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity ^0.8.17; -import {Script} from "forge-std/Script.sol"; -import {WETH9} from "../contracts/partners/tokenWrappers/weth9.sol"; +import { Script } from "forge-std/Script.sol"; +import { WETH9 } from "../contracts/partners/tokenWrappers/weth9.sol"; contract DeployWETH9 is Script { WETH9 public weth9; @@ -12,7 +12,7 @@ contract DeployWETH9 is Script { DEPLOYER_PRIVATE_KEY = vm.envUint("DEPLOYER_PRIVATE_KEY"); vm.startBroadcast(DEPLOYER_PRIVATE_KEY); - weth9 = new WETH9(); + weth9 = new WETH9(); vm.stopBroadcast(); } diff --git a/test/unit/partners/tokenWrappers/ClaimFeeTokenWrapperImmutable.t.sol b/test/unit/partners/tokenWrappers/ClaimFeeTokenWrapperImmutable.t.sol index 3bdaa38..3119a0e 100644 --- a/test/unit/partners/tokenWrappers/ClaimFeeTokenWrapperImmutable.t.sol +++ b/test/unit/partners/tokenWrappers/ClaimFeeTokenWrapperImmutable.t.sol @@ -166,8 +166,7 @@ contract Test_ClaimFeeTokenWrapperImmutable_ClaimPath is ClaimFeeTokenWrapperImm contract Test_ClaimFeeTokenWrapperImmutable_Burn is ClaimFeeTokenWrapperImmutableTest { function test_RevertWhen_NotHolderOrGovernor() public { - vm.prank(alice); - wrapper.mint(alice, 10 ether); + deal(address(wrapper), alice, 10 ether); vm.expectRevert(Errors.NotAllowed.selector); vm.prank(bob); @@ -175,8 +174,7 @@ contract Test_ClaimFeeTokenWrapperImmutable_Burn is ClaimFeeTokenWrapperImmutabl } function test_Success_Holder() public { - vm.prank(alice); - wrapper.mint(alice, 10 ether); + deal(address(wrapper), alice, 10 ether); vm.prank(alice); wrapper.burn(alice, 5 ether); @@ -185,8 +183,7 @@ contract Test_ClaimFeeTokenWrapperImmutable_Burn is ClaimFeeTokenWrapperImmutabl } function test_Success_Governor() public { - vm.prank(alice); - wrapper.mint(alice, 10 ether); + deal(address(wrapper), alice, 10 ether); vm.prank(governor); wrapper.burn(alice, 3 ether); diff --git a/test/unit/partners/tokenWrappers/MezoWrapper.t.sol b/test/unit/partners/tokenWrappers/MezoWrapper.t.sol new file mode 100644 index 0000000..454c9a7 --- /dev/null +++ b/test/unit/partners/tokenWrappers/MezoWrapper.t.sol @@ -0,0 +1,320 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity ^0.8.17; + +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; + +import { MezoWrapper, IMezoStaking } from "../../../../contracts/partners/tokenWrappers/MezoWrapper.sol"; +import { Fixture } from "../../../Fixture.t.sol"; +import { Errors } from "../../../../contracts/utils/Errors.sol"; +import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import { MockDistributor, MockFeeRecipient } from "./TokenWrapperMocks.sol"; + +/// @dev Mock Mezo staking contract that records lock calls and pulls tokens from the caller +contract MockMezoStaking { + struct LockCall { + uint256 value; + uint256 lockDuration; + address to; + } + + LockCall[] public lockCalls; + IERC20 public token; + uint256 public nextLockId; + + constructor(address _token) { + token = IERC20(_token); + } + + function createLockFor(uint256 _value, uint256 _lockDuration, address _to) external returns (uint256) { + token.transferFrom(msg.sender, address(this), _value); + lockCalls.push(LockCall(_value, _lockDuration, _to)); + return nextLockId++; + } + + function lockCallsLength() external view returns (uint256) { + return lockCalls.length; + } +} + +contract MezoWrapperTest is Fixture { + MezoWrapper public wrapper; + MockDistributor public mockDistributor; + MockFeeRecipient public mockFeeRecipient; + MockMezoStaking public mockMezoStaking; + + uint256 public constant LOCK_DURATION = 30 days; + + function setUp() public virtual override { + super.setUp(); + + mockDistributor = new MockDistributor(); + mockFeeRecipient = new MockFeeRecipient(); + mockMezoStaking = new MockMezoStaking(address(angle)); + + vm.mockCall(address(creator), abi.encodeWithSignature("distributor()"), abi.encode(address(mockDistributor))); + vm.mockCall(address(creator), abi.encodeWithSignature("feeRecipient()"), abi.encode(address(mockFeeRecipient))); + + wrapper = new MezoWrapper( + address(angle), + address(creator), + alice, + address(mockMezoStaking), + LOCK_DURATION, + "Mezo Locked ANGLE", + "mlANGLE" + ); + + mockDistributor.setWrapper(address(wrapper)); + + angle.mint(alice, 1000 ether); + + vm.prank(alice); + angle.approve(address(wrapper), type(uint256).max); + } +} + +contract Test_MezoWrapper_Constructor is MezoWrapperTest { + function test_RevertWhen_ZeroAddressMezoStaking() public { + vm.expectRevert(Errors.ZeroAddress.selector); + new MezoWrapper(address(angle), address(creator), alice, address(0), LOCK_DURATION, "Test", "TST"); + } + + function test_Success_StateSetCorrectly() public { + assertEq(wrapper.name(), "Mezo Locked ANGLE"); + assertEq(wrapper.symbol(), "mlANGLE"); + assertEq(wrapper.token(), address(angle)); + assertEq(wrapper.holder(), alice); + assertEq(wrapper.mezoStaking(), address(mockMezoStaking)); + assertEq(wrapper.lockDuration(), LOCK_DURATION); + assertEq(address(wrapper.accessControlManager()), address(accessControlManager)); + assertEq(wrapper.distributor(), address(mockDistributor)); + assertEq(wrapper.distributionCreator(), address(creator)); + assertEq(wrapper.feeRecipient(), address(mockFeeRecipient)); + assertEq(wrapper.decimals(), angle.decimals()); + assertEq(angle.allowance(address(wrapper), address(mockMezoStaking)), type(uint256).max); + } +} + +contract Test_MezoWrapper_BeforeTokenTransfer is MezoWrapperTest { + function setUp() public override { + super.setUp(); + + vm.prank(alice); + wrapper.mint(alice, 100 ether); + + vm.prank(alice); + wrapper.transfer(address(mockDistributor), 100 ether); + } + + function test_Success_ClaimCreatesLockForRecipient() public { + uint256 aliceAngleBefore = angle.balanceOf(alice); + + vm.prank(address(mockDistributor)); + mockDistributor.simulateClaim(bob, 20 ether); + + // Tokens pulled from holder and locked in Mezo staking + assertEq(angle.balanceOf(alice), aliceAngleBefore - 20 ether); + assertEq(angle.balanceOf(address(mockMezoStaking)), 20 ether); + + // Lock was created with correct params + assertEq(mockMezoStaking.lockCallsLength(), 1); + (uint256 value, uint256 duration, address to) = mockMezoStaking.lockCalls(0); + assertEq(value, 20 ether); + assertEq(duration, LOCK_DURATION); + assertEq(to, bob); + + // Wrapper tokens burned for non-allowed recipient + assertEq(wrapper.balanceOf(bob), 0); + } + + function test_Success_FeeTransferSendsDirectly() public { + uint256 aliceAngleBefore = angle.balanceOf(alice); + + vm.prank(address(mockDistributor)); + wrapper.transfer(address(mockFeeRecipient), 10 ether); + + // Tokens sent directly to fee recipient, not locked in Mezo + assertEq(angle.balanceOf(alice), aliceAngleBefore - 10 ether); + assertEq(angle.balanceOf(address(mockFeeRecipient)), 10 ether); + assertEq(mockMezoStaking.lockCallsLength(), 0); + } + + function test_Success_NormalTransferDoesNotCreateLock() public { + uint256 aliceAngleBefore = angle.balanceOf(alice); + + vm.prank(alice); + wrapper.mint(alice, 50 ether); + vm.prank(alice); + wrapper.transfer(bob, 50 ether); + + assertEq(angle.balanceOf(alice), aliceAngleBefore); + assertEq(mockMezoStaking.lockCallsLength(), 0); + assertEq(wrapper.balanceOf(bob), 0); + } + + function test_Success_MultipleClaims() public { + vm.prank(address(mockDistributor)); + mockDistributor.simulateClaim(bob, 20 ether); + + vm.prank(address(mockDistributor)); + mockDistributor.simulateClaim(charlie, 30 ether); + + assertEq(mockMezoStaking.lockCallsLength(), 2); + + (uint256 value0, uint256 duration0, address to0) = mockMezoStaking.lockCalls(0); + assertEq(value0, 20 ether); + assertEq(duration0, LOCK_DURATION); + assertEq(to0, bob); + + (uint256 value1, uint256 duration1, address to1) = mockMezoStaking.lockCalls(1); + assertEq(value1, 30 ether); + assertEq(duration1, LOCK_DURATION); + assertEq(to1, charlie); + + assertEq(angle.balanceOf(address(mockMezoStaking)), 50 ether); + } + + function test_RevertWhen_HolderHasInsufficientTokens() public { + uint256 aliceBalance = angle.balanceOf(alice); + vm.prank(alice); + angle.transfer(address(1), aliceBalance); + + vm.expectRevert("ERC20: transfer amount exceeds balance"); + vm.prank(address(mockDistributor)); + wrapper.transfer(bob, 10 ether); + } + + function test_RevertWhen_HolderHasNotApproved() public { + vm.prank(alice); + angle.approve(address(wrapper), 0); + + vm.expectRevert("ERC20: insufficient allowance"); + vm.prank(address(mockDistributor)); + wrapper.transfer(bob, 10 ether); + } +} + +contract Test_MezoWrapper_SetLockDuration is MezoWrapperTest { + function test_RevertWhen_NotHolderOrGovernor() public { + vm.expectRevert(Errors.NotAllowed.selector); + vm.prank(bob); + wrapper.setLockDuration(60 days); + } + + function test_Success_Holder() public { + vm.prank(alice); + wrapper.setLockDuration(60 days); + + assertEq(wrapper.lockDuration(), 60 days); + } + + function test_Success_Governor() public { + vm.prank(governor); + wrapper.setLockDuration(90 days); + + assertEq(wrapper.lockDuration(), 90 days); + } + + function test_Success_EmitsEvent() public { + vm.expectEmit(true, true, true, true); + emit MezoWrapper.LockDurationUpdated(60 days); + + vm.prank(alice); + wrapper.setLockDuration(60 days); + } + + function test_Success_NewDurationUsedOnClaim() public { + vm.prank(alice); + wrapper.setLockDuration(90 days); + + vm.prank(alice); + wrapper.mint(alice, 50 ether); + vm.prank(alice); + wrapper.transfer(address(mockDistributor), 50 ether); + + vm.prank(address(mockDistributor)); + mockDistributor.simulateClaim(bob, 10 ether); + + (, uint256 duration, ) = mockMezoStaking.lockCalls(0); + assertEq(duration, 90 days); + } +} + +contract Test_MezoWrapper_SetStakingAllowance is MezoWrapperTest { + function test_RevertWhen_NotHolderOrGovernor() public { + vm.expectRevert(Errors.NotAllowed.selector); + vm.prank(bob); + wrapper.setStakingAllowance(100 ether); + } + + function test_Success_Holder() public { + vm.prank(alice); + wrapper.setStakingAllowance(500 ether); + + assertEq(angle.allowance(address(wrapper), address(mockMezoStaking)), 500 ether); + } + + function test_Success_Governor() public { + vm.prank(governor); + wrapper.setStakingAllowance(1000 ether); + + assertEq(angle.allowance(address(wrapper), address(mockMezoStaking)), 1000 ether); + } + + function test_Success_ResetToMax() public { + vm.prank(alice); + wrapper.setStakingAllowance(100 ether); + assertEq(angle.allowance(address(wrapper), address(mockMezoStaking)), 100 ether); + + vm.prank(alice); + wrapper.setStakingAllowance(type(uint256).max); + assertEq(angle.allowance(address(wrapper), address(mockMezoStaking)), type(uint256).max); + } +} + +contract Test_MezoWrapper_Integration is MezoWrapperTest { + function test_Integration_CompleteFlow() public { + // 1. Holder mints wrapper tokens + vm.prank(alice); + wrapper.mint(alice, 100 ether); + + assertEq(wrapper.balanceOf(alice), 100 ether); + + // 2. Holder transfers wrapper tokens to distributor + vm.prank(alice); + wrapper.transfer(address(mockDistributor), 80 ether); + + assertEq(wrapper.balanceOf(address(mockDistributor)), 80 ether); + assertEq(wrapper.balanceOf(alice), 20 ether); + + // 3. Distributor distributes rewards — tokens pulled from holder, locked in Mezo + vm.prank(address(mockDistributor)); + wrapper.transfer(bob, 30 ether); + + assertEq(angle.balanceOf(address(mockMezoStaking)), 30 ether); + assertEq(wrapper.balanceOf(bob), 0); + + // 4. Distributor sends fees — sent directly to fee recipient + vm.prank(address(mockDistributor)); + wrapper.transfer(address(mockFeeRecipient), 10 ether); + + assertEq(angle.balanceOf(address(mockMezoStaking)), 30 ether); + assertEq(angle.balanceOf(address(mockFeeRecipient)), 10 ether); + assertEq(wrapper.balanceOf(address(mockDistributor)), 40 ether); + + // 5. Verify locks (only claim creates a lock, not fee transfer) + assertEq(mockMezoStaking.lockCallsLength(), 1); + + // 6. Owner changes lock duration for future claims + vm.prank(alice); + wrapper.setLockDuration(90 days); + + vm.prank(address(mockDistributor)); + wrapper.transfer(charlie, 15 ether); + + (, uint256 duration, ) = mockMezoStaking.lockCalls(1); + assertEq(duration, 90 days); + assertEq(mockMezoStaking.lockCallsLength(), 2); + } +} diff --git a/test/unit/partners/tokenWrappers/NativeTokenUnwrapperImmutable.t.sol b/test/unit/partners/tokenWrappers/NativeTokenUnwrapperImmutable.t.sol index 8d3ff2e..45a371f 100644 --- a/test/unit/partners/tokenWrappers/NativeTokenUnwrapperImmutable.t.sol +++ b/test/unit/partners/tokenWrappers/NativeTokenUnwrapperImmutable.t.sol @@ -162,8 +162,7 @@ contract Test_NativeTokenUnwrapperImmutable_ClaimPath is NativeTokenUnwrapperImm contract Test_NativeTokenUnwrapperImmutable_Burn is NativeTokenUnwrapperImmutableTest { function test_RevertWhen_NotHolderOrGovernor() public { - vm.prank(alice); - wrapper.mint(alice, 10 ether); + deal(address(wrapper), alice, 10 ether); vm.expectRevert(Errors.NotAllowed.selector); vm.prank(bob); @@ -171,8 +170,7 @@ contract Test_NativeTokenUnwrapperImmutable_Burn is NativeTokenUnwrapperImmutabl } function test_Success_Holder() public { - vm.prank(alice); - wrapper.mint(alice, 10 ether); + deal(address(wrapper), alice, 10 ether); vm.prank(alice); wrapper.burn(alice, 5 ether); @@ -181,8 +179,7 @@ contract Test_NativeTokenUnwrapperImmutable_Burn is NativeTokenUnwrapperImmutabl } function test_Success_Governor() public { - vm.prank(alice); - wrapper.mint(alice, 10 ether); + deal(address(wrapper), alice, 10 ether); vm.prank(governor); wrapper.burn(alice, 3 ether); diff --git a/test/unit/partners/tokenWrappers/PullTokenWrapperVaultImmutable.t.sol b/test/unit/partners/tokenWrappers/PullTokenWrapperVaultImmutable.t.sol index 76e3c88..67602bc 100644 --- a/test/unit/partners/tokenWrappers/PullTokenWrapperVaultImmutable.t.sol +++ b/test/unit/partners/tokenWrappers/PullTokenWrapperVaultImmutable.t.sol @@ -49,7 +49,7 @@ contract PullTokenWrapperVaultImmutableTest is Fixture { contract Test_PullTokenWrapperVaultImmutable_Constructor is PullTokenWrapperVaultImmutableTest { function test_RevertWhen_ZeroAddressVault() public { - vm.expectRevert(Errors.ZeroAddress.selector); + vm.expectRevert(); new PullTokenWrapperVaultImmutable(address(angle), address(creator), alice, address(0)); }