From c27af96ec6becbedd163d0965971a7ee3225fbf9 Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Tue, 9 Jan 2024 20:56:19 -0600 Subject: [PATCH 1/2] Updated the sweep function and wrote some comprehensive tests --- contracts/src/instances/ERC4626Base.sol | 4 - contracts/src/instances/ERC4626Hyperdrive.sol | 23 +- .../ERC4626HyperdriveCoreDeployer.sol | 12 +- contracts/src/instances/ERC4626Target0.sol | 29 +- .../src/interfaces/IERC4626HyperdriveRead.sol | 2 - contracts/src/interfaces/IHyperdrive.sol | 15 +- contracts/test/MockERC4626Hyperdrive.sol | 6 +- .../erc4626}/AaveV3ERC4626.t.sol | 0 .../erc4626}/ERC4626Hyperdrive.t.sol | 130 +-------- .../erc4626}/ERC4626Validation.t.sol | 0 .../erc4626}/StethERC4626.t.sol | 0 test/instances/erc4626/Sweep.t.sol | 248 ++++++++++++++++++ .../erc4626}/UsdcERC4626.t.sol | 0 .../erc4626}/sDai.t.sol | 0 .../{ => factory}/HyperdriveFactory.t.sol | 0 15 files changed, 282 insertions(+), 187 deletions(-) rename test/{integrations => instances/erc4626}/AaveV3ERC4626.t.sol (100%) rename test/{integrations => instances/erc4626}/ERC4626Hyperdrive.t.sol (74%) rename test/{integrations => instances/erc4626}/ERC4626Validation.t.sol (100%) rename test/{integrations => instances/erc4626}/StethERC4626.t.sol (100%) create mode 100644 test/instances/erc4626/Sweep.t.sol rename test/{integrations => instances/erc4626}/UsdcERC4626.t.sol (100%) rename test/{integrations => instances/erc4626}/sDai.t.sol (100%) rename test/integrations/{ => factory}/HyperdriveFactory.t.sol (100%) diff --git a/contracts/src/instances/ERC4626Base.sol b/contracts/src/instances/ERC4626Base.sol index c5b4a9c3a..b8657f394 100644 --- a/contracts/src/instances/ERC4626Base.sol +++ b/contracts/src/instances/ERC4626Base.sol @@ -22,10 +22,6 @@ abstract contract ERC4626Base is HyperdriveBase { /// @dev The yield source contract for this hyperdrive. IERC4626 internal immutable _pool; - /// @dev A mapping from addresses to their status as a sweep target. This - /// mapping does not change after construction. - mapping(address target => bool canSweep) internal _isSweepable; - /// @notice Instantiates the ERC4626 Hyperdrive base contract. /// @param __pool The ERC4626 compatible yield source. constructor(IERC4626 __pool) { diff --git a/contracts/src/instances/ERC4626Hyperdrive.sol b/contracts/src/instances/ERC4626Hyperdrive.sol index 945513dda..346f35ab4 100644 --- a/contracts/src/instances/ERC4626Hyperdrive.sol +++ b/contracts/src/instances/ERC4626Hyperdrive.sol @@ -28,18 +28,13 @@ contract ERC4626Hyperdrive is Hyperdrive, ERC4626Base { /// @param _target2 The target2 address. /// @param _target3 The target3 address. /// @param __pool The ERC4626 compatible yield source. - /// @param _targets The addresses that can be swept by governance. This - /// allows governance to collect rewards derived from incentive - /// programs while also preventing edge cases where `sweep` is used - /// to access the pool or base tokens. constructor( IHyperdrive.PoolConfig memory _config, address _target0, address _target1, address _target2, address _target3, - IERC4626 __pool, - address[] memory _targets + IERC4626 __pool ) Hyperdrive(_config, _target0, _target1, _target2, _target3) ERC4626Base(__pool) @@ -61,27 +56,11 @@ contract ERC4626Hyperdrive is Hyperdrive, ERC4626Base { // Approve the base token with 1 wei. This ensures that all of the // subsequent approvals will be writing to a dirty storage slot. ERC20(address(_config.baseToken)).safeApprove(address(_pool), 1); - - // Set the sweep targets. The base and pool tokens can't be set as sweep - // targets to prevent governance from rugging the pool. - for (uint256 i = 0; i < _targets.length; i++) { - address target = _targets[i]; - if ( - address(target) == address(_pool) || - address(target) == address(_baseToken) - ) { - revert IHyperdrive.UnsupportedToken(); - } - _isSweepable[target] = true; - } } /// @notice Some yield sources [eg Morpho] pay rewards directly to this /// contract but we can't handle distributing them internally so we /// sweep to the fee collector address to then redistribute to users. - /// @dev WARN: The entire balance of any of the sweep targets can be swept - /// by governance. If these sweep targets provide access to the base or - /// pool token, then governance has the ability to rug the pool. /// @dev WARN: It is unlikely but possible that there is a selector overlap /// with 'transferFrom'. Any integrating contracts should be checked /// for that, as it may result in an unexpected call from this address. diff --git a/contracts/src/instances/ERC4626HyperdriveCoreDeployer.sol b/contracts/src/instances/ERC4626HyperdriveCoreDeployer.sol index d76641765..11fffc6de 100644 --- a/contracts/src/instances/ERC4626HyperdriveCoreDeployer.sol +++ b/contracts/src/instances/ERC4626HyperdriveCoreDeployer.sol @@ -17,12 +17,12 @@ import { ERC4626Hyperdrive } from "../instances/ERC4626Hyperdrive.sol"; contract ERC4626HyperdriveCoreDeployer is IERC4626HyperdriveDeployer { /// @notice Deploys a Hyperdrive instance with the given parameters. /// @param _config The configuration of the Hyperdrive pool. - /// @param _extraData The extra data that contains the sweep targets. + /// @param _extraData The extra data that contains the ERC4626 vault. /// @param target0 The target0 address. /// @param target1 The target1 address. /// @param target2 The target2 address. /// @param target3 The target3 address. - /// @return The address of the newly deployed ERC4626Hyperdrive Instance. + /// @return The address of the newly deployed ERC4626Hyperdrive instance. function deploy( IHyperdrive.PoolConfig memory _config, bytes memory _extraData, @@ -31,10 +31,7 @@ contract ERC4626HyperdriveCoreDeployer is IERC4626HyperdriveDeployer { address target2, address target3 ) external override returns (address) { - (address pool, address[] memory sweepTargets) = abi.decode( - _extraData, - (address, address[]) - ); + address pool = abi.decode(_extraData, (address)); // Deploy the ERC4626Hyperdrive instance. return ( @@ -45,8 +42,7 @@ contract ERC4626HyperdriveCoreDeployer is IERC4626HyperdriveDeployer { target1, target2, target3, - IERC4626(pool), - sweepTargets + IERC4626(pool) ) ) ); diff --git a/contracts/src/instances/ERC4626Target0.sol b/contracts/src/instances/ERC4626Target0.sol index eec1f33b5..ae09baf63 100644 --- a/contracts/src/instances/ERC4626Target0.sol +++ b/contracts/src/instances/ERC4626Target0.sol @@ -33,9 +33,6 @@ contract ERC4626Target0 is HyperdriveTarget0, ERC4626Base { /// @notice Some yield sources [eg Morpho] pay rewards directly to this /// contract but we can't handle distributing them internally so we /// sweep to the fee collector address to then redistribute to users. - /// @dev WARN: The entire balance of any of the sweep targets can be swept - /// by governance. If these sweep targets provide access to the base or - /// pool token, then governance has the ability to rug the pool. /// @dev WARN: It is unlikely but possible that there is a selector overlap /// with 'transferFrom'. Any integrating contracts should be checked /// for that, as it may result in an unexpected call from this address. @@ -46,14 +43,30 @@ contract ERC4626Target0 is HyperdriveTarget0, ERC4626Base { revert IHyperdrive.Unauthorized(); } - // Ensure that thet target can be swept by governance. - if (!_isSweepable[address(_target)]) { + // Ensure that thet target isn't the base or vault token + if ( + address(_target) == address(_baseToken) || + address(_target) == address(_pool) + ) { revert IHyperdrive.UnsupportedToken(); } + // Get Hyperdrive's balance of the base and pool tokens prior to + // sweeping. + uint256 baseBalance = _baseToken.balanceOf(address(this)); + uint256 poolBalance = _pool.balanceOf(address(this)); + // Transfer the entire balance of the sweep target to the fee collector. uint256 balance = _target.balanceOf(address(this)); ERC20(address(_target)).safeTransfer(_feeCollector, balance); + + // Ensure that the base and pool balances haven't changed. + if ( + _baseToken.balanceOf(address(this)) != baseBalance || + _pool.balanceOf(address(this)) != poolBalance + ) { + revert IHyperdrive.SweepFailed(); + } } /// Getters /// @@ -63,10 +76,4 @@ contract ERC4626Target0 is HyperdriveTarget0, ERC4626Base { function pool() external view returns (IERC4626) { _revert(abi.encode(_pool)); } - - /// @notice Gets the sweepable status of a target. - /// @param _target The target address. - function isSweepable(address _target) external view returns (bool) { - _revert(abi.encode(_isSweepable[_target])); - } } diff --git a/contracts/src/interfaces/IERC4626HyperdriveRead.sol b/contracts/src/interfaces/IERC4626HyperdriveRead.sol index 81727e7ea..083fddbf8 100644 --- a/contracts/src/interfaces/IERC4626HyperdriveRead.sol +++ b/contracts/src/interfaces/IERC4626HyperdriveRead.sol @@ -6,6 +6,4 @@ import { IHyperdriveRead } from "./IHyperdriveRead.sol"; interface IERC4626HyperdriveRead is IHyperdriveRead { function pool() external view returns (IERC4626); - - function isSweepable(address _target) external view returns (bool); } diff --git a/contracts/src/interfaces/IHyperdrive.sol b/contracts/src/interfaces/IHyperdrive.sol index baeeaa62e..98d1ea681 100644 --- a/contracts/src/interfaces/IHyperdrive.sol +++ b/contracts/src/interfaces/IHyperdrive.sol @@ -256,18 +256,23 @@ interface IHyperdrive is IHyperdriveRead, IHyperdriveCore, IMultiToken { error ApprovalFailed(); error BelowMinimumContribution(); error BelowMinimumShareReserves(); + error EndIndexTooLarge(); error InvalidApr(); error InvalidBaseToken(); error InvalidCheckpointTime(); error InvalidCheckpointDuration(); + error InvalidDeployer(); + error InvalidFeeAmounts(); + error InvalidFeeDestination(); + error InvalidIndexes(); error InvalidInitialSharePrice(); error InvalidMaturityTime(); error InvalidMinimumShareReserves(); error InvalidPositionDuration(); error InvalidShareReserves(); - error InvalidFeeAmounts(); - error InvalidFeeDestination(); error InsufficientLiquidity(); + error MinimumSharePrice(); + error MinimumTransactionAmount(); error NegativeInterest(); error NegativePresentValue(); error NoAssetsToWithdraw(); @@ -276,16 +281,12 @@ interface IHyperdrive is IHyperdriveRead, IHyperdriveCore, IMultiToken { error Paused(); error PoolAlreadyInitialized(); error ShareReservesDeltaExceedsBondReservesDelta(); + error SweepFailed(); error TransferFailed(); error UnexpectedAssetId(); error UnexpectedSender(); error UnsupportedToken(); - error MinimumSharePrice(); - error MinimumTransactionAmount(); error ZeroLpTotalSupply(); - error InvalidIndexes(); - error EndIndexTooLarge(); - error InvalidDeployer(); /// ############ /// ### TWAP ### diff --git a/contracts/test/MockERC4626Hyperdrive.sol b/contracts/test/MockERC4626Hyperdrive.sol index 5456d6d8f..e70144ff9 100644 --- a/contracts/test/MockERC4626Hyperdrive.sol +++ b/contracts/test/MockERC4626Hyperdrive.sol @@ -12,8 +12,7 @@ contract MockERC4626Hyperdrive is ERC4626Hyperdrive { address _target1, address _target2, address _target3, - IERC4626 _pool, - address[] memory _sweepTargets + IERC4626 _pool ) ERC4626Hyperdrive( _config, @@ -21,8 +20,7 @@ contract MockERC4626Hyperdrive is ERC4626Hyperdrive { _target1, _target2, _target3, - _pool, - _sweepTargets + _pool ) {} diff --git a/test/integrations/AaveV3ERC4626.t.sol b/test/instances/erc4626/AaveV3ERC4626.t.sol similarity index 100% rename from test/integrations/AaveV3ERC4626.t.sol rename to test/instances/erc4626/AaveV3ERC4626.t.sol diff --git a/test/integrations/ERC4626Hyperdrive.t.sol b/test/instances/erc4626/ERC4626Hyperdrive.t.sol similarity index 74% rename from test/integrations/ERC4626Hyperdrive.t.sol rename to test/instances/erc4626/ERC4626Hyperdrive.t.sol index 0f9e0407b..666fe4ca6 100644 --- a/test/integrations/ERC4626Hyperdrive.t.sol +++ b/test/instances/erc4626/ERC4626Hyperdrive.t.sol @@ -121,8 +121,7 @@ contract ERC4626HyperdriveTest is HyperdriveTest { target1, target2, target3, - pool, - new address[](0) + pool ); vm.stopPrank(); @@ -344,131 +343,4 @@ contract ERC4626HyperdriveTest is HyperdriveTest { (pool.totalAssets()).divDown(pool.totalSupply()) ); } - - function test_erc4626_sweep() public { - // Ensure that deployment will fail if the pool or base token is - // specified as a sweep target. - vm.startPrank(alice); - address[] memory sweepTargets = new address[](1); - sweepTargets[0] = address(dai); - bytes memory extraData = abi.encode(address(pool), sweepTargets); - IHyperdrive.PoolDeployConfig memory config = IHyperdrive - .PoolDeployConfig({ - baseToken: dai, - linkerFactory: address(0), - linkerCodeHash: bytes32(0), - minimumShareReserves: ONE, - minimumTransactionAmount: 0.001e18, - positionDuration: 365 days, - checkpointDuration: 1 days, - timeStretch: HyperdriveUtils.calculateTimeStretch( - 0.01e18, - 365 days - ), - governance: alice, - feeCollector: bob, - fees: IHyperdrive.Fees(0, 0, 0, 0) - }); - vm.expectRevert(IHyperdrive.UnsupportedToken.selector); - factory.deployAndInitialize( - hyperdriveDeployer, - config, - extraData, - 1_000e18, - 0.05e18, - new bytes(0) - ); - assert( - !IERC4626Hyperdrive(address(mockHyperdrive)).isSweepable( - address(dai) - ) - ); - sweepTargets[0] = address(pool); - extraData = abi.encode(address(pool), sweepTargets); - vm.expectRevert(IHyperdrive.UnsupportedToken.selector); - factory.deployAndInitialize( - hyperdriveDeployer, - config, - extraData, - 1_000e18, - 0.05e18, - new bytes(0) - ); - assert( - !IERC4626Hyperdrive(address(mockHyperdrive)).isSweepable( - address(pool) - ) - ); - vm.stopPrank(); - - // Ensure that the base token and the pool cannot be swept. - vm.startPrank(bob); - vm.expectRevert(IHyperdrive.UnsupportedToken.selector); - IERC4626Hyperdrive(address(mockHyperdrive)).sweep(dai); - vm.expectRevert(IHyperdrive.UnsupportedToken.selector); - IERC4626Hyperdrive(address(mockHyperdrive)).sweep( - IERC20(address(pool)) - ); - vm.stopPrank(); - - // Ensure that a sweep target that isn't the base token or the pool - // can be initialized and that the target can be swept successfully. - vm.startPrank(alice); - ERC20Mintable otherToken = new ERC20Mintable( - "Other", - "OTHER", - 18, - address(0), - false - ); - sweepTargets[0] = address(otherToken); - extraData = abi.encode(address(pool), sweepTargets); - mockHyperdrive = MockERC4626Hyperdrive( - address( - factory.deployAndInitialize( - hyperdriveDeployer, - config, - extraData, - 1_000e18, - 0.05e18, - new bytes(0) - ) - ) - ); - assert( - IERC4626Hyperdrive(address(mockHyperdrive)).isSweepable( - address(otherToken) - ) - ); - vm.stopPrank(); - vm.startPrank(bob); - otherToken.mint(address(mockHyperdrive), 1e18); - IERC4626Hyperdrive(address(mockHyperdrive)).sweep( - IERC20(address(otherToken)) - ); - assertEq(otherToken.balanceOf(bob), 1e18); - vm.stopPrank(); - - // Alice should not be able to sweep the target since she isn't a pauser. - vm.startPrank(alice); - vm.expectRevert(IHyperdrive.Unauthorized.selector); - IERC4626Hyperdrive(address(mockHyperdrive)).sweep( - IERC20(address(otherToken)) - ); - vm.stopPrank(); - - // Bob adds Alice as a pauser. - vm.startPrank(bob); - IERC4626Hyperdrive(address(mockHyperdrive)).setPauser(alice, true); - vm.stopPrank(); - - // Alice should be able to sweep the target successfully. - vm.startPrank(alice); - otherToken.mint(address(mockHyperdrive), 1e18); - IERC4626Hyperdrive(address(mockHyperdrive)).sweep( - IERC20(address(otherToken)) - ); - assertEq(otherToken.balanceOf(bob), 2e18); - vm.stopPrank(); - } } diff --git a/test/integrations/ERC4626Validation.t.sol b/test/instances/erc4626/ERC4626Validation.t.sol similarity index 100% rename from test/integrations/ERC4626Validation.t.sol rename to test/instances/erc4626/ERC4626Validation.t.sol diff --git a/test/integrations/StethERC4626.t.sol b/test/instances/erc4626/StethERC4626.t.sol similarity index 100% rename from test/integrations/StethERC4626.t.sol rename to test/instances/erc4626/StethERC4626.t.sol diff --git a/test/instances/erc4626/Sweep.t.sol b/test/instances/erc4626/Sweep.t.sol new file mode 100644 index 000000000..33d15ad07 --- /dev/null +++ b/test/instances/erc4626/Sweep.t.sol @@ -0,0 +1,248 @@ +// SPDX-License-Identifier: Apache-2.0 +pragma solidity 0.8.19; + +import { ERC4626Hyperdrive } from "contracts/src/instances/ERC4626Hyperdrive.sol"; +import { ERC4626Target0 } from "contracts/src/instances/ERC4626Target0.sol"; +import { ERC4626Target1 } from "contracts/src/instances/ERC4626Target1.sol"; +import { ERC4626Target2 } from "contracts/src/instances/ERC4626Target2.sol"; +import { ERC4626Target3 } from "contracts/src/instances/ERC4626Target3.sol"; +import { IERC20 } from "contracts/src/interfaces/IERC20.sol"; +import { IERC4626 } from "contracts/src/interfaces/IERC4626.sol"; +import { IERC4626Hyperdrive } from "contracts/src/interfaces/IERC4626Hyperdrive.sol"; +import { IHyperdrive } from "contracts/src/interfaces/IHyperdrive.sol"; +import { ONE } from "contracts/src/libraries/FixedPointMath.sol"; +import { ERC20Mintable } from "contracts/test/ERC20Mintable.sol"; +import { MockERC4626 } from "contracts/test/MockERC4626.sol"; +import { BaseTest } from "test/utils/BaseTest.sol"; +import { HyperdriveUtils } from "test/utils/HyperdriveUtils.sol"; + +contract SweepTest is BaseTest { + ForwardingToken baseForwarder; + ForwardingToken vaultForwarder; + ERC20Mintable sweepable; + + IERC4626Hyperdrive hyperdrive; + + function setUp() public override { + super.setUp(); + + // We'll use Alice to deploy the contracts. + vm.startPrank(alice); + + // Deploy the sweepable ERC20. + sweepable = new ERC20Mintable( + "Sweepable", + "SWEEP", + 18, + address(0), + false + ); + + // Deploy the leaky vault with the leaky ERC20 as the asset. Then deploy + // forwarding tokens for each of the targets. + LeakyERC20 leakyBase = new LeakyERC20(); + LeakyVault leakyVault = new LeakyVault(leakyBase); + baseForwarder = new ForwardingToken(address(leakyBase)); + vaultForwarder = new ForwardingToken(address(leakyVault)); + + // Deploy Hyperdrive with the leaky vault as the backing vault. + IHyperdrive.PoolConfig memory config = IHyperdrive.PoolConfig({ + baseToken: IERC20(address(leakyBase)), + linkerFactory: address(0), + linkerCodeHash: bytes32(0), + initialSharePrice: ONE, + minimumShareReserves: ONE, + minimumTransactionAmount: 0.001e18, + positionDuration: 365 days, + checkpointDuration: 1 days, + timeStretch: HyperdriveUtils.calculateTimeStretch( + 0.01e18, + 365 days + ), + governance: alice, + feeCollector: bob, + fees: IHyperdrive.Fees(0, 0, 0, 0) + }); + vm.warp(3 * config.positionDuration); + hyperdrive = IERC4626Hyperdrive( + address( + new ERC4626Hyperdrive( + config, + address( + new ERC4626Target0( + config, + IERC4626(address(leakyVault)) + ) + ), + address( + new ERC4626Target1( + config, + IERC4626(address(leakyVault)) + ) + ), + address( + new ERC4626Target2( + config, + IERC4626(address(leakyVault)) + ) + ), + address( + new ERC4626Target3( + config, + IERC4626(address(leakyVault)) + ) + ), + IERC4626(address(leakyVault)) + ) + ) + ); + + // Initialize Hyperdrive. This ensures that Hyperdrive has vault tokens + // to sweep. + leakyBase.mint(alice, 100e18); + leakyBase.approve(address(hyperdrive), 100e18); + hyperdrive.initialize( + 100e18, + 0.05e18, + IHyperdrive.Options({ + destination: alice, + asBase: true, + extraData: new bytes(0) + }) + ); + + // Mint some base tokens to Hyperdrive so that there is + // something to sweep. + leakyBase.mint(address(hyperdrive), 100e18); + + // Mint some of the sweepable tokens to Hyperdrive. + sweepable.mint(address(hyperdrive), 100e18); + } + + function test_sweep_failure_invalid_sweeper() external { + vm.stopPrank(); + vm.startPrank(celine); + + // Trying to call sweep with an invalid sweeper (an address that isn't + // the fee collector or a pauser) should fail. + vm.expectRevert(IHyperdrive.Unauthorized.selector); + hyperdrive.sweep(IERC20(address(sweepable))); + } + + function test_sweep_failure_direct_sweeps() external { + vm.stopPrank(); + vm.startPrank(bob); + + // Trying to sweep the base token should fail. + address baseToken = address(hyperdrive.baseToken()); + vm.expectRevert(IHyperdrive.UnsupportedToken.selector); + hyperdrive.sweep(IERC20(baseToken)); + + // Trying to sweep the vault token should fail. + address vaultToken = address(hyperdrive.pool()); + vm.expectRevert(IHyperdrive.UnsupportedToken.selector); + hyperdrive.sweep(IERC20(vaultToken)); + } + + function test_sweep_failure_indirect_sweeps() external { + vm.stopPrank(); + vm.startPrank(bob); + + // Trying to sweep the base token via the forwarding token should fail. + vm.expectRevert(IHyperdrive.SweepFailed.selector); + hyperdrive.sweep(IERC20(address(baseForwarder))); + + // Trying to sweep the vault token via the forwarding token should fail. + vm.expectRevert(IHyperdrive.SweepFailed.selector); + hyperdrive.sweep(IERC20(address(vaultForwarder))); + } + + function test_sweep_success_feeCollector() external { + // The fee collector can sweep a sweepable token. + vm.stopPrank(); + vm.startPrank(bob); + hyperdrive.sweep(IERC20(address(sweepable))); + } + + function test_sweep_success_pauser() external { + // Set up Celine as a pauser. + vm.stopPrank(); + vm.startPrank(alice); + hyperdrive.setPauser(celine, true); + + // A pauser can sweep a sweepable token. + vm.stopPrank(); + vm.startPrank(celine); + hyperdrive.sweep(IERC20(address(sweepable))); + } +} + +contract LeakyVault is MockERC4626 { + constructor( + ERC20Mintable _asset + ) MockERC4626(_asset, "Leaky Vault", "LEAK", 0, address(0), false) {} + + // This function allows other addresses to transfer tokens from a spender. + // This is obviously insecure, but it's an easy way to expose a forwarding + // token that can abuse this leaky transfer function. This gives us a way + // to test the `sweep` function. + function leakyTransferFrom( + address from, + address to, + uint256 amount + ) external returns (bool) { + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + return true; + } +} + +contract LeakyERC20 is ERC20Mintable { + constructor() ERC20Mintable("Leaky ERC20", "LEAK", 0, address(0), false) {} + + // This function allows other addresses to transfer tokens from a spender. + // This is obviously insecure, but it's an easy way to expose a forwarding + // token that can abuse this leaky transfer function. This gives us a way + // to test the `sweep` function. + function leakyTransferFrom( + address from, + address to, + uint256 amount + ) external returns (bool) { + balanceOf[from] -= amount; + + // Cannot overflow because the sum of all user + // balances can't exceed the max uint256 value. + unchecked { + balanceOf[to] += amount; + } + + emit Transfer(from, to, amount); + + return true; + } +} + +contract ForwardingToken { + address internal target; + + constructor(address _target) { + target = _target; + } + + function balanceOf(address account) external view returns (uint256) { + return LeakyERC20(target).balanceOf(account); + } + + function transfer(address to, uint256 amount) external returns (bool) { + return LeakyERC20(target).leakyTransferFrom(msg.sender, to, amount); + } +} diff --git a/test/integrations/UsdcERC4626.t.sol b/test/instances/erc4626/UsdcERC4626.t.sol similarity index 100% rename from test/integrations/UsdcERC4626.t.sol rename to test/instances/erc4626/UsdcERC4626.t.sol diff --git a/test/integrations/sDai.t.sol b/test/instances/erc4626/sDai.t.sol similarity index 100% rename from test/integrations/sDai.t.sol rename to test/instances/erc4626/sDai.t.sol diff --git a/test/integrations/HyperdriveFactory.t.sol b/test/integrations/factory/HyperdriveFactory.t.sol similarity index 100% rename from test/integrations/HyperdriveFactory.t.sol rename to test/integrations/factory/HyperdriveFactory.t.sol From 04cb6ca547fac568122625ee05c5189245314e37 Mon Sep 17 00:00:00 2001 From: Alex Towle Date: Wed, 10 Jan 2024 09:38:06 -0600 Subject: [PATCH 2/2] Fixed the rust tests --- crates/test-utils/src/chain/test_chain.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/test-utils/src/chain/test_chain.rs b/crates/test-utils/src/chain/test_chain.rs index 6cacde058..cacb04269 100644 --- a/crates/test-utils/src/chain/test_chain.rs +++ b/crates/test-utils/src/chain/test_chain.rs @@ -306,7 +306,6 @@ impl TestChain { target2.address(), target3.address(), pool.address(), - Vec::::new(), ), )? .gas_price(DEFAULT_GAS_PRICE)