diff --git a/src/Position.sol b/src/Position.sol index cb93cfad..ad4e43f8 100644 --- a/src/Position.sol +++ b/src/Position.sol @@ -15,19 +15,18 @@ import { IFeeCollector } from "src/interfaces/IFeeCollector.sol"; contract Position is DebtService, SwapService { // Constants: no SLOAD to save gas uint256 public constant PROTOCOL_FEE = 3; + address public constant FEE_COLLECTOR = 0x7A7AbDb9E12F3a9845E2433958Eef8FB9C8489Ee; // Immutables: no SLOAD to save gas - address public immutable FEE_COLLECTOR; address public immutable B_TOKEN; // Events event Short(uint256 cAmt, uint256 dAmt, uint256 bAmt); event Close(uint256 gains); - constructor(address _owner, address _feeCollector, address _cToken, address _dToken, address _bToken) + constructor(address _owner, address _cToken, address _dToken, address _bToken) DebtService(_owner, _cToken, _dToken) { - FEE_COLLECTOR = _feeCollector; B_TOKEN = _bToken; } diff --git a/src/PositionFactory.sol b/src/PositionFactory.sol index 652f0ed8..b02e0bf3 100644 --- a/src/PositionFactory.sol +++ b/src/PositionFactory.sol @@ -14,9 +14,6 @@ contract PositionFactory is Ownable { // Constants: no SLOAD to save gas address private constant CONTRACT_DEPLOYER = 0x0a5B347509621337cDDf44CBCf6B6E7C9C908CD2; - // Immutables: no SLOAD to save gas - address public immutable FEE_COLLECTOR; - // Factory Storage /// @dev Mapping from owner to cToken to dToken to bToken to position mapping(address => mapping(address => mapping(address => mapping(address => address)))) public positions; @@ -26,9 +23,8 @@ contract PositionFactory is Ownable { error Unauthorized(); error PositionExists(); - constructor(address _owner, address _feeCollector) Ownable(_owner) { + constructor(address _owner) Ownable(_owner) { if (msg.sender != CONTRACT_DEPLOYER) revert Unauthorized(); - FEE_COLLECTOR = _feeCollector; } /** @@ -44,7 +40,7 @@ contract PositionFactory is Ownable { { if (positions[msg.sender][_cToken][_dToken][_bToken] != address(0)) revert PositionExists(); - position = address(new Position(msg.sender, FEE_COLLECTOR, _cToken, _dToken, _bToken)); + position = address(new Position(msg.sender, _cToken, _dToken, _bToken)); positionsLookup[msg.sender].push(position); positions[msg.sender][_cToken][_dToken][_bToken] = position; diff --git a/test/FeeCollector.t.sol b/test/FeeCollector.t.sol index 7b893d0c..8af61613 100644 --- a/test/FeeCollector.t.sol +++ b/test/FeeCollector.t.sol @@ -11,6 +11,7 @@ import { Assets, AAVE_ORACLE, CONTRACT_DEPLOYER, + FEE_COLLECTOR, TEST_CLIENT, PROTOCOL_FEE, CLIENT_RATE, @@ -19,6 +20,7 @@ import { WBTC } from "test/common/Constants.t.sol"; import { TokenUtils } from "test/common/utils/TokenUtils.t.sol"; +import { IFeeCollector } from "src/interfaces/IFeeCollector.sol"; import { IAaveOracle } from "src/interfaces/aave/IAaveOracle.sol"; import { IPosition } from "src/interfaces/IPosition.sol"; import { IERC20 } from "src/interfaces/token/IERC20.sol"; @@ -44,8 +46,8 @@ contract FeeCollectorTest is Test, TokenUtils { // Test Storage uint256 public mainnetFork; - address public feeCollectorAddr; address public positionOwner = address(this); + address public feeCollectorAddr; // Events event Short(uint256 cAmt, uint256 dAmt, uint256 bAmt); @@ -66,7 +68,7 @@ contract FeeCollectorTest is Test, TokenUtils { // Deploy PositionFactory vm.prank(CONTRACT_DEPLOYER); - positionFactory = new PositionFactory(CONTRACT_DEPLOYER, feeCollectorAddr); + positionFactory = new PositionFactory(CONTRACT_DEPLOYER); // Set client rate vm.prank(CONTRACT_DEPLOYER); @@ -179,88 +181,6 @@ contract FeeCollectorTest is Test, TokenUtils { } } - /// @dev - // - The FeeCollector's cToken balance should increase by protocolFee - // - The cToken totalClientBalances should increase by clientFee - // - The client's cToken balance on the FeeCollector contract should increase by clientFee - function testFuzz_CollectFeesWithClientIntegrated(uint256 _cAmt) external payable { - for (uint256 i; i < positions.length; i++) { - // Test Variables - address positionAddr = positions[i].addr; - address cToken = positions[i].cToken; - - // Bound fuzzed variables - _cAmt = bound(_cAmt, assets.minCAmts(cToken), assets.maxCAmts(cToken)); - - // Expectations - uint256 protocolFee = (_cAmt * PROTOCOL_FEE) / 1000; - uint256 clientFee = (protocolFee * CLIENT_RATE) / 100; - - // Fund positionOwner with _cAmt of cToken - _fund(positionOwner, cToken, _cAmt); - - // Approve Position contract to spend collateral - IERC20(cToken).approve(positionAddr, _cAmt); - - // Pre-act balances - uint256 preContractBalance = IERC20(cToken).balanceOf(feeCollectorAddr); - uint256 preTotalClientBalances = feeCollector.totalClientBalances(cToken); - uint256 preClientFeeBalance = feeCollector.balances(TEST_CLIENT, cToken); - - // Act: increase short position - IPosition(positionAddr).short(_cAmt, 50, 0, 3000, TEST_CLIENT); - - // Post-act balances - uint256 postContractBalance = IERC20(cToken).balanceOf(feeCollectorAddr); - uint256 postTotalClientBalances = feeCollector.totalClientBalances(cToken); - uint256 postClientFeeBalance = feeCollector.balances(TEST_CLIENT, cToken); - - // Assertions - assertEq(postContractBalance, preContractBalance + protocolFee); - assertEq(postTotalClientBalances, preTotalClientBalances + clientFee); - assertEq(postClientFeeBalance, preClientFeeBalance + clientFee); - } - } - - /// @dev - // - The FeeCollector's cToken balance should increase by protocolFee - // - The cToken totalClientBalances should not change - // - The above should be true when _client is sent as address(0) - function testFuzz_CollectFeesNoClientIntegrated(uint256 _cAmt) external payable { - for (uint256 i; i < positions.length; i++) { - // Test Variables - address positionAddr = positions[i].addr; - address cToken = positions[i].cToken; - - // Bound fuzzed variables - _cAmt = bound(_cAmt, assets.minCAmts(cToken), assets.maxCAmts(cToken)); - - // Expectations - uint256 protocolFee = (_cAmt * PROTOCOL_FEE) / 1000; - - // Fund positionOwner with _cAmt of cToken - _fund(positionOwner, cToken, _cAmt); - - // Approve Position contract to spend collateral - IERC20(cToken).approve(positionAddr, _cAmt); - - // Pre-act balances - uint256 preContractBalance = IERC20(cToken).balanceOf(feeCollectorAddr); - uint256 preTotalClientBalances = feeCollector.totalClientBalances(cToken); - - // Act: increase short position - IPosition(positionAddr).short(_cAmt, 50, 0, 3000, address(0)); - - // Post-act balances - uint256 postContractBalance = IERC20(cToken).balanceOf(feeCollectorAddr); - uint256 postTotalClientBalances = feeCollector.totalClientBalances(cToken); - - // Assertions - assertEq(postContractBalance, preContractBalance + protocolFee); - assertEq(postTotalClientBalances, preTotalClientBalances); - } - } - /// @dev // - The FeeCollector's feeToken balance should decrease by amount withdrawn // - The feeToken totalClientBalances should decrease by amount withdrawn @@ -514,3 +434,163 @@ contract FeeCollectorTest is Test, TokenUtils { assertEq(postContractBalance, preContractBalance + _amount); } } + +contract FeeCollectorIntegrationTest is Test, TokenUtils { + /* solhint-disable func-name-mixedcase */ + + struct TestPosition { + address addr; + address cToken; + address dToken; + address bToken; + } + + // Errors + error OwnableUnauthorizedAccount(address account); + + // Test contracts + PositionFactory public positionFactory; + Assets public assets; + TestPosition[] public positions; + + // Test Storage + uint256 public mainnetFork; + address public positionOwner = address(this); + + // Events + event Short(uint256 cAmt, uint256 dAmt, uint256 bAmt); + + function setUp() public { + // Setup: use mainnet fork + mainnetFork = vm.createFork(vm.envString("RPC_URL")); + vm.selectFork(mainnetFork); + + // Deploy assets + assets = new Assets(); + address[4] memory supportedAssets = assets.getSupported(); + + // Deploy FeeCollector + vm.prank(CONTRACT_DEPLOYER); + deployCodeTo("FeeCollector.sol", abi.encode(CONTRACT_DEPLOYER), FEE_COLLECTOR); + + // Deploy PositionFactory + vm.prank(CONTRACT_DEPLOYER); + positionFactory = new PositionFactory(CONTRACT_DEPLOYER); + + // Set client rate + vm.prank(CONTRACT_DEPLOYER); + IFeeCollector(FEE_COLLECTOR).setClientRate(CLIENT_RATE); + + // Deploy and store four position contracts - one for each supported asset as collateral + address positionAddr; + TestPosition memory newPosition; + for (uint256 i; i < supportedAssets.length; i++) { + if (supportedAssets[i] != WETH) { + positionAddr = positionFactory.createPosition(supportedAssets[i], WETH, WBTC); + newPosition = + TestPosition({ addr: positionAddr, cToken: supportedAssets[i], dToken: WETH, bToken: WBTC }); + positions.push(newPosition); + } + } + positionAddr = positionFactory.createPosition(WETH, USDC, WETH); + newPosition = TestPosition({ addr: positionAddr, cToken: WETH, dToken: USDC, bToken: WETH }); + positions.push(newPosition); + + // Mock AaveOracle + for (uint256 i; i < supportedAssets.length; i++) { + vm.mockCall( + AAVE_ORACLE, + abi.encodeWithSelector(IAaveOracle(AAVE_ORACLE).getAssetPrice.selector, supportedAssets[i]), + abi.encode(assets.prices(supportedAssets[i])) + ); + } + } + + /// @dev + // - The active fork should be the forked network created in the setup + function test_ActiveFork() public { + assertEq(vm.activeFork(), mainnetFork, "vm.activeFork() != mainnetFork"); + } + + /// @dev + // - The FeeCollector's cToken balance should increase by protocolFee + // - The cToken totalClientBalances should increase by clientFee + // - The client's cToken balance on the FeeCollector contract should increase by clientFee + function testFuzz_CollectFeesWithClientIntegrated(uint256 _cAmt) external payable { + for (uint256 i; i < positions.length; i++) { + // Test Variables + address positionAddr = positions[i].addr; + address cToken = positions[i].cToken; + + // Bound fuzzed variables + _cAmt = bound(_cAmt, assets.minCAmts(cToken), assets.maxCAmts(cToken)); + + // Expectations + uint256 protocolFee = (_cAmt * PROTOCOL_FEE) / 1000; + uint256 clientFee = (protocolFee * CLIENT_RATE) / 100; + + // Fund positionOwner with _cAmt of cToken + _fund(positionOwner, cToken, _cAmt); + + // Approve Position contract to spend collateral + IERC20(cToken).approve(positionAddr, _cAmt); + + // Pre-act balances + uint256 preContractBalance = IERC20(cToken).balanceOf(FEE_COLLECTOR); + uint256 preTotalClientBalances = IFeeCollector(FEE_COLLECTOR).totalClientBalances(cToken); + uint256 preClientFeeBalance = IFeeCollector(FEE_COLLECTOR).balances(TEST_CLIENT, cToken); + + // Act: increase short position + IPosition(positionAddr).short(_cAmt, 50, 0, 3000, TEST_CLIENT); + + // Post-act balances + uint256 postContractBalance = IERC20(cToken).balanceOf(FEE_COLLECTOR); + uint256 postTotalClientBalances = IFeeCollector(FEE_COLLECTOR).totalClientBalances(cToken); + uint256 postClientFeeBalance = IFeeCollector(FEE_COLLECTOR).balances(TEST_CLIENT, cToken); + + // Assertions + assertEq(postContractBalance, preContractBalance + protocolFee); + assertEq(postTotalClientBalances, preTotalClientBalances + clientFee); + assertEq(postClientFeeBalance, preClientFeeBalance + clientFee); + } + } + + /// @dev + // - The FeeCollector's cToken balance should increase by protocolFee + // - The cToken totalClientBalances should not change + // - The above should be true when _client is sent as address(0) + function testFuzz_CollectFeesNoClientIntegrated(uint256 _cAmt) external payable { + for (uint256 i; i < positions.length; i++) { + // Test Variables + address positionAddr = positions[i].addr; + address cToken = positions[i].cToken; + + // Bound fuzzed variables + _cAmt = bound(_cAmt, assets.minCAmts(cToken), assets.maxCAmts(cToken)); + + // Expectations + uint256 protocolFee = (_cAmt * PROTOCOL_FEE) / 1000; + + // Fund positionOwner with _cAmt of cToken + _fund(positionOwner, cToken, _cAmt); + + // Approve Position contract to spend collateral + IERC20(cToken).approve(positionAddr, _cAmt); + + // Pre-act balances + uint256 preContractBalance = IERC20(cToken).balanceOf(FEE_COLLECTOR); + uint256 preTotalClientBalances = IFeeCollector(FEE_COLLECTOR).totalClientBalances(cToken); + + // Act: increase short position + IPosition(positionAddr).short(_cAmt, 50, 0, 3000, address(0)); + + // Post-act balances + uint256 postContractBalance = IERC20(cToken).balanceOf(FEE_COLLECTOR); + uint256 postTotalClientBalances = IFeeCollector(FEE_COLLECTOR).totalClientBalances(cToken); + + // Assertions + assertEq(postContractBalance, preContractBalance + protocolFee); + assertEq(postTotalClientBalances, preTotalClientBalances); + } + } +} diff --git a/test/Position.t.sol b/test/Position.t.sol index f0dd2f2c..69779bf9 100644 --- a/test/Position.t.sol +++ b/test/Position.t.sol @@ -6,7 +6,6 @@ import { Test } from "forge-std/Test.sol"; import { VmSafe } from "forge-std/Vm.sol"; // Local Imports -import { FeeCollector } from "src/FeeCollector.sol"; import { PositionFactory } from "src/PositionFactory.sol"; import { PositionAdmin } from "src/PositionAdmin.sol"; import { @@ -14,6 +13,7 @@ import { AAVE_ORACLE, CONTRACT_DEPLOYER, DAI, + FEE_COLLECTOR, PROFIT_PERCENT, REPAY_PERCENT, SWAP_ROUTER, @@ -58,7 +58,6 @@ contract PositionTest is Test, TokenUtils, DebtUtils { // Test contracts PositionFactory public positionFactory; - FeeCollector public feeCollector; Assets public assets; TestPosition[] public positions; @@ -81,11 +80,11 @@ contract PositionTest is Test, TokenUtils, DebtUtils { // Deploy FeeCollector vm.prank(CONTRACT_DEPLOYER); - feeCollector = new FeeCollector(CONTRACT_DEPLOYER); + deployCodeTo("FeeCollector.sol", abi.encode(CONTRACT_DEPLOYER), FEE_COLLECTOR); // Deploy PositionFactory vm.prank(CONTRACT_DEPLOYER); - positionFactory = new PositionFactory(CONTRACT_DEPLOYER, address(feeCollector)); + positionFactory = new PositionFactory(CONTRACT_DEPLOYER); // Deploy and store all possible positions for (uint256 i; i < supportedAssets.length; i++) { @@ -560,7 +559,6 @@ contract PositionPermitTest is Test, TokenUtils, DebtUtils { // Test contracts PositionFactory public positionFactory; - FeeCollector public feeCollector; Assets public assets; TestPosition[] public positions; @@ -584,11 +582,11 @@ contract PositionPermitTest is Test, TokenUtils, DebtUtils { // Deploy FeeCollector vm.prank(CONTRACT_DEPLOYER); - feeCollector = new FeeCollector(CONTRACT_DEPLOYER); + deployCodeTo("FeeCollector.sol", abi.encode(CONTRACT_DEPLOYER), FEE_COLLECTOR); // Deploy PositionFactory vm.prank(CONTRACT_DEPLOYER); - positionFactory = new PositionFactory(CONTRACT_DEPLOYER, address(feeCollector)); + positionFactory = new PositionFactory(CONTRACT_DEPLOYER); // Set contract owner wallet = vm.createWallet(uint256(keccak256(abi.encodePacked(uint256(1))))); diff --git a/test/PositionAdmin.t.sol b/test/PositionAdmin.t.sol index 8d64d342..5c5dfa1a 100644 --- a/test/PositionAdmin.t.sol +++ b/test/PositionAdmin.t.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.21; import { Test } from "forge-std/Test.sol"; // Local Imports -import { FeeCollector } from "src/FeeCollector.sol"; import { PositionFactory } from "src/PositionFactory.sol"; import { PositionAdmin } from "src/PositionAdmin.sol"; import { Assets, CONTRACT_DEPLOYER } from "test/common/Constants.t.sol"; @@ -17,7 +16,6 @@ contract PositionAdminTest is Test, TokenUtils { /* solhint-disable func-name-mixedcase */ // Test contracts - FeeCollector public feeCollector; PositionFactory public positionFactory; Assets public assets; @@ -35,13 +33,9 @@ contract PositionAdminTest is Test, TokenUtils { assets = new Assets(); address[4] memory supportedAssets = assets.getSupported(); - // Deploy FeeCollector - vm.prank(CONTRACT_DEPLOYER); - feeCollector = new FeeCollector(CONTRACT_DEPLOYER); - // Deploy PositionFactory vm.prank(CONTRACT_DEPLOYER); - positionFactory = new PositionFactory(CONTRACT_DEPLOYER, address(feeCollector)); + positionFactory = new PositionFactory(CONTRACT_DEPLOYER); // Deploy a Position positionAddr = positionFactory.createPosition(supportedAssets[0], supportedAssets[3], supportedAssets[2]); diff --git a/test/PositionFactory.t.sol b/test/PositionFactory.t.sol index e5fc99ea..561c6ea9 100644 --- a/test/PositionFactory.t.sol +++ b/test/PositionFactory.t.sol @@ -5,7 +5,6 @@ pragma solidity ^0.8.21; import { Test } from "forge-std/Test.sol"; // Local Imports -import { FeeCollector } from "src/FeeCollector.sol"; import { PositionFactory } from "src/PositionFactory.sol"; import { Assets, CONTRACT_DEPLOYER } from "test/common/Constants.t.sol"; import { TokenUtils } from "test/common/utils/TokenUtils.t.sol"; @@ -16,7 +15,6 @@ contract PositionFactoryTest is Test, TokenUtils { /* solhint-disable func-name-mixedcase */ // Test Contracts - FeeCollector public feeCollector; PositionFactory public positionFactory; Assets public assets; @@ -32,12 +30,8 @@ contract PositionFactoryTest is Test, TokenUtils { mainnetFork = vm.createFork(vm.envString("RPC_URL")); vm.selectFork(mainnetFork); - // Deploy FeeCollector vm.prank(CONTRACT_DEPLOYER); - feeCollector = new FeeCollector(CONTRACT_DEPLOYER); - - vm.prank(CONTRACT_DEPLOYER); - positionFactory = new PositionFactory(CONTRACT_DEPLOYER, address(feeCollector)); + positionFactory = new PositionFactory(CONTRACT_DEPLOYER); assets = new Assets(); } diff --git a/test/common/Constants.t.sol b/test/common/Constants.t.sol index 09bea260..b5bee860 100644 --- a/test/common/Constants.t.sol +++ b/test/common/Constants.t.sol @@ -7,6 +7,7 @@ address constant AAVE_ORACLE = 0xb56c2F0B653B2e0b10C9b928C8580Ac5Df02C7C7; address constant AAVE_POOL = 0x794a61358D6845594F94dc1DB02A252b5b4814aD; address constant SWAP_ROUTER = 0xE592427A0AEce92De3Edee1F18E0157C05861564; address constant TEST_CLIENT = 0xd05E2C879821C31E5a8e3B5Da490e834211Ca443; +address constant FEE_COLLECTOR = 0x7A7AbDb9E12F3a9845E2433958Eef8FB9C8489Ee; // Supported Assets address constant USDC = 0xaf88d065e77c8cC2239327C5EDb3A432268e5831;