Skip to content
This repository has been archived by the owner on Jul 2, 2024. It is now read-only.

Commit

Permalink
Distribute fees when calling rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
kphed committed Nov 14, 2023
1 parent a9a4348 commit 2890325
Show file tree
Hide file tree
Showing 5 changed files with 133 additions and 20 deletions.
2 changes: 1 addition & 1 deletion foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ optimizer = false
via_ir = false

[fuzz]
runs = 10
runs = 100

[profile.optimized]
optimizer = true
Expand Down
39 changes: 29 additions & 10 deletions src/BrrETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ pragma solidity ^0.8.0;
import {ERC4626} from "solady/tokens/ERC4626.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
import {IComet} from "src/interfaces/IComet.sol";
import {ICometRewards} from "src/interfaces/ICometRewards.sol";
import {IRouter} from "src/interfaces/IRouter.sol";

contract BrrETH is Ownable, ERC4626 {
using SafeTransferLib for address;
using FixedPointMathLib for uint256;

string private constant _NAME = "Brrito-Compound WETH";
string private constant _SYMBOL = "brr-cWETHv3";
Expand Down Expand Up @@ -106,18 +108,35 @@ contract BrrETH is Ownable, ERC4626 {
tokenBalance
);

IComet(_COMET).supply(
// `swap` returns the entire WETH amount received from the swap.
uint256 actualOutput = _ROUTER.swap(
rewardConfig.token,
_WETH,
// `swap` returns the entire WETH amount received from the swap.
_ROUTER.swap(
rewardConfig.token,
_WETH,
tokenBalance,
output,
index,
address(0)
)
tokenBalance,
output,
index,
address(0)
);

// Calculate and take out the fees from the output before supplying to Comet.
uint256 rewardFeeShare = actualOutput.mulDiv(rewardFee, _FEE_BASE);

unchecked {
// `rewardFeeShare` is a percentage of the output so we can safely subtract it.
actualOutput -= rewardFeeShare;

if (rewardFeeShare != 0) {
uint256 ownerFeeShare = rewardFeeShare / 2;

_WETH.safeTransfer(owner(), ownerFeeShare);
_WETH.safeTransfer(
feeDistributor,
rewardFeeShare - ownerFeeShare
);
}

IComet(_COMET).supply(_WETH, actualOutput);
}
}

/**
Expand Down
99 changes: 93 additions & 6 deletions test/BrrETH.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import "forge-std/Test.sol";
import {Ownable} from "solady/auth/Ownable.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
import {FixedPointMathLib} from "solady/utils/FixedPointMathLib.sol";
import {Helper} from "test/Helper.sol";
import {BrrETH} from "src/BrrETH.sol";
import {IComet} from "src/interfaces/IComet.sol";
Expand All @@ -16,18 +17,19 @@ interface IComet2 {
function withdraw(address asset, uint amount) external;
}

contract BrrETHTest is Helper, Test {
contract BrrETHTest is Helper {
using SafeTransferLib for address;
using FixedPointMathLib for uint256;

address public immutable owner = address(this);
BrrETH public immutable vault = new BrrETH();
BrrETH public immutable vault = new BrrETH(address(this));

constructor() {
_WETH.safeApproveWithRetry(_COMET, type(uint256).max);
_COMET.safeApproveWithRetry(address(vault), type(uint256).max);
}

function _getCWETH(uint256 amount) private returns (uint256 balance) {
function _getCWETH(uint256 amount) internal returns (uint256 balance) {
deal(_WETH, address(this), amount);

balance = _COMET.balanceOf(address(this));
Expand All @@ -37,6 +39,15 @@ contract BrrETHTest is Helper, Test {
balance = _COMET.balanceOf(address(this)) - balance;
}

function _calculateFees(
uint256 amount
) internal view returns (uint256 ownerShare, uint256 feeDistributorShare) {
uint256 rewardFee = vault.rewardFee();
uint256 rewardFeeShare = amount.mulDiv(rewardFee, _FEE_BASE);
ownerShare = rewardFeeShare / 2;
feeDistributorShare = rewardFeeShare - ownerShare;
}

/*//////////////////////////////////////////////////////////////
constructor
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -104,30 +115,106 @@ contract BrrETHTest is Helper, Test {
//////////////////////////////////////////////////////////////*/

function testRebase() external {
_getCWETH(10 ether);
uint256 assets = 10000000000000401;
uint256 accrualTime = 187;

_getCWETH(assets);

vault.deposit(10 ether, address(this));
vault.deposit(assets, address(this));

skip(10_000);
skip(accrualTime);

IComet(_COMET).accrueAccount(address(vault));

IComet.UserBasic memory userBasic = IComet(_COMET).userBasic(
address(vault)
);
uint256 rewardsBalance = userBasic.baseTrackingAccrued * 1e12;

if (rewardsBalance == 0) return;

(, uint256 output) = IRouter(_ROUTER).getSwapOutput(
keccak256(abi.encodePacked(_COMP, _WETH)),
rewardsBalance
);
(uint256 ownerShare, uint256 feeDistributorShare) = _calculateFees(
output
);
output -= ownerShare + feeDistributorShare;
uint256 newAssets = output - 1;
uint256 totalAssets = vault.totalAssets();
uint256 totalSupply = vault.totalSupply();
uint256 ownerBalance = _WETH.balanceOf(vault.owner());
uint256 feeDistributorBalance = _WETH.balanceOf(vault.feeDistributor());

vault.rebase();

assertEq(totalAssets + newAssets, vault.totalAssets());
assertEq(totalSupply, vault.totalSupply());

if (vault.owner() == vault.feeDistributor()) {
assertEq(
ownerBalance + ownerShare + feeDistributorShare,
_WETH.balanceOf(vault.owner())
);
} else {
assertEq(ownerBalance + ownerShare, _WETH.balanceOf(vault.owner()));
assertEq(
feeDistributorBalance + feeDistributorShare,
_WETH.balanceOf(vault.feeDistributor())
);
}
}

function testRebaseFuzz(uint80 assets, uint24 accrualTime) external {
vm.assume(assets > 0.01 ether && accrualTime > 100);

_getCWETH(assets);

vault.deposit(assets, address(this));

skip(accrualTime);

IComet(_COMET).accrueAccount(address(vault));

IComet.UserBasic memory userBasic = IComet(_COMET).userBasic(
address(vault)
);
uint256 rewardsBalance = uint256(userBasic.baseTrackingAccrued) * 1e12;

if (rewardsBalance == 0) return;

(, uint256 output) = IRouter(_ROUTER).getSwapOutput(
keccak256(abi.encodePacked(_COMP, _WETH)),
rewardsBalance
);
(uint256 ownerShare, uint256 feeDistributorShare) = _calculateFees(
output
);
output -= ownerShare + feeDistributorShare;
uint256 newAssets = output - 1;
uint256 totalAssets = vault.totalAssets();
uint256 totalSupply = vault.totalSupply();
uint256 ownerBalance = _WETH.balanceOf(vault.owner());
uint256 feeDistributorBalance = _WETH.balanceOf(vault.feeDistributor());

vault.rebase();

assertEq(totalAssets + newAssets, vault.totalAssets());
assertEq(totalSupply, vault.totalSupply());

if (vault.owner() == vault.feeDistributor()) {
assertEq(
ownerBalance + ownerShare + feeDistributorShare,
_WETH.balanceOf(vault.owner())
);
} else {
assertEq(ownerBalance + ownerShare, _WETH.balanceOf(vault.owner()));
assertEq(
feeDistributorBalance + feeDistributorShare,
_WETH.balanceOf(vault.feeDistributor())
);
}
}

/*//////////////////////////////////////////////////////////////
Expand Down
4 changes: 2 additions & 2 deletions test/BrrETHManager.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import {Helper} from "test/Helper.sol";
import {BrrETH} from "src/BrrETH.sol";
import {BrrETHManager} from "src/BrrETHManager.sol";

contract BrrETHManagerTest is Helper, Test {
contract BrrETHManagerTest is Helper {
using SafeTransferLib for address;

BrrETH public immutable vault = new BrrETH();
BrrETH public immutable vault = new BrrETH(address(this));
BrrETHManager public immutable depositor =
new BrrETHManager(address(vault));

Expand Down
9 changes: 8 additions & 1 deletion test/Helper.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,17 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "forge-std/Test.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
import {BrrETH} from "src/BrrETH.sol";
import {IComet} from "src/interfaces/IComet.sol";
import {ICometRewards} from "src/interfaces/ICometRewards.sol";
import {IWETH} from "src/interfaces/IWETH.sol";

contract Helper {
contract Helper is Test {
using SafeTransferLib for address;

string internal constant _NAME = "Brrito-Compound WETH";
string internal constant _SYMBOL = "brr-cWETHv3";
address internal constant _WETH =
Expand All @@ -20,4 +24,7 @@ contract Helper {
0x9e1028F5F1D5eDE59748FFceE5532509976840E0;
ICometRewards internal constant _COMET_REWARDS =
ICometRewards(0x123964802e6ABabBE1Bc9547D72Ef1B69B00A6b1);
uint256 internal constant _FEE_BASE = 10_000;
uint256 internal constant _MAX_REWARD_FEE = 2_000;
uint256 internal constant _MAX_WITHDRAW_FEE = 5;
}

0 comments on commit 2890325

Please sign in to comment.