Skip to content

Commit

Permalink
refactor(metamorpho): rename rewards to skim
Browse files Browse the repository at this point in the history
  • Loading branch information
Rubilmax committed Oct 30, 2023
1 parent 2f9a8e7 commit 1eba06c
Show file tree
Hide file tree
Showing 8 changed files with 73 additions and 68 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/foundry.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ jobs:
type: ["slow", "fast"]
include:
- type: "slow"
fuzz-runs: 32768
fuzz-runs: 8192
max-test-rejects: 1048576
invariant-runs: 64
invariant-depth: 1024
Expand Down
37 changes: 21 additions & 16 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ The vault owner can set a performance fee, cutting up to 50% of the generated in
The `feeRecipient` can then withdraw the accumulated fee at any time.

The vault may be entitled to some rewards emitted on Morpho Blue markets the vault has supplied to.
Those rewards can be transferred to the `rewardsRecipient`.
Those rewards can be transferred to the `skimRecipient`.
The vault's owner has the choice to distribute back these rewards to vault depositors however they want.
For more information about this use case, see the [Rewards](#rewards) section.

Expand All @@ -39,6 +39,7 @@ After the timelock, the action can be executed by anyone until 3 days have passe
Only one address can have this role.

It can:

- Do what the curator can do.
- Transfer or renounce the ownership.
- Set the curator.
Expand All @@ -54,28 +55,31 @@ It can:
Only one address can have this role.

It can:

- Do what the allocators can do.
- [Timelocked] Enable or disable a market by setting a cap to a specific market.
- The cap must be set to 0 to disable the market.
- Disabling a market can then only be done if the vault has no liquidity supplied on the market.
- The cap must be set to 0 to disable the market.
- Disabling a market can then only be done if the vault has no liquidity supplied on the market.

#### Allocator

Multiple addresses can have this role.

It can:

- Set the `supplyQueue` and `withdrawQueue`, i.e. decide on the order of the markets to supply/withdraw from.
- Upon a deposit, the vault will supply up to the cap of each Morpho Blue market in the supply queue in the order set. The remaining funds are left as idle supply on the vault (uncapped).
- Upon a withdrawal, the vault will first withdraw from the idle supply and then withdraw up to the liquidity of each Morpho Blue market in the withdrawal queue in the order set.
- The `supplyQueue` contains only enabled markets (enabled market are markets with non-zero cap or with non-zero vault's supply).
- The `withdrawQueue` contains all enabled markets.
- Upon a deposit, the vault will supply up to the cap of each Morpho Blue market in the supply queue in the order set. The remaining funds are left as idle supply on the vault (uncapped).
- Upon a withdrawal, the vault will first withdraw from the idle supply and then withdraw up to the liquidity of each Morpho Blue market in the withdrawal queue in the order set.
- The `supplyQueue` contains only enabled markets (enabled market are markets with non-zero cap or with non-zero vault's supply).
- The `withdrawQueue` contains all enabled markets.
- Instantaneously reallocate funds among the enabled market at any moment.

#### Guardian

Only one address can have this role.

It can:

- Revoke any timelocked action except it cannot revoke a pending fee.

### Rewards
Expand All @@ -85,20 +89,21 @@ To redistribute rewards to vault depositors, it is advised to use the [Universal
Below is a typical example of how this use case would take place:

- If not already done:
- Create a rewards distributor using the [UrdFactory](https://github.com/morpho-org/universal-rewards-distributor/blob/main/src/UrdFactory.sol) (can be done by anyone).
- Set the vault’s rewards recipient address to the created URD using `setRewardsRecipient`.

- Create a rewards distributor using the [UrdFactory](https://github.com/morpho-org/universal-rewards-distributor/blob/main/src/UrdFactory.sol) (can be done by anyone).
- Set the vault’s rewards recipient address to the created URD using `setSkimRecipient`.

- Claim tokens from the Morpho Blue distribution to the vault.

NB: Anyone can claim tokens on behalf of the vault and automatically transfer them to the vault.
Thus, this step might be already performed by some third-party.
NB: Anyone can claim tokens on behalf of the vault and automatically transfer them to the vault.
Thus, this step might be already performed by some third-party.

- Transfer rewards from the vault to the rewards distributor using the `transferRewards` function.
- Transfer rewards from the vault to the rewards distributor using the `skim` function.

NB: Anyone can transfer rewards from the vault to the rewards distributor unless it is unset.
Thus, this step might be already performed by some third-party.
Note: the amount of rewards transferred is calculated based on the balance in the reward asset of the vault.
In case the reward asset is the vault’s asset, the vault’s idle liquidity is automatically subtracted to prevent stealing idle liquidity.
NB: Anyone can transfer rewards from the vault to the rewards distributor unless it is unset.
Thus, this step might be already performed by some third-party.
Note: the amount of rewards transferred is calculated based on the balance in the reward asset of the vault.
In case the reward asset is the vault’s asset, the vault’s idle liquidity is automatically subtracted to prevent stealing idle liquidity.

- Compute the new root for the vault’s rewards distributor, submit it, wait for the timelock (if any), accept the root, and let vault depositors claim their rewards according to the vault manager’s rewards re-distribution strategy.

Expand Down
22 changes: 11 additions & 11 deletions src/MetaMorpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
address public feeRecipient;

/// @notice The rewards recipient.
address public rewardsRecipient;
address public skimRecipient;

/// @notice The pending guardian.
PendingAddress public pendingGuardian;
Expand Down Expand Up @@ -183,13 +183,13 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
emit EventsLib.SetIsAllocator(newAllocator, newIsAllocator);
}

/// @notice Sets `rewardsRecipient` to `newRewardsRecipient`.
function setRewardsRecipient(address newRewardsRecipient) external onlyOwner {
if (newRewardsRecipient == rewardsRecipient) revert ErrorsLib.AlreadySet();
/// @notice Sets `skimRecipient` to `newSkimRecipient`.
function setSkimRecipient(address newSkimRecipient) external onlyOwner {
if (newSkimRecipient == skimRecipient) revert ErrorsLib.AlreadySet();

rewardsRecipient = newRewardsRecipient;
skimRecipient = newSkimRecipient;

emit EventsLib.SetRewardsRecipient(newRewardsRecipient);
emit EventsLib.SetSkimRecipient(newSkimRecipient);
}

/// @notice Submits a `newTimelock`.
Expand Down Expand Up @@ -451,17 +451,17 @@ contract MetaMorpho is ERC4626, ERC20Permit, Ownable2Step, Multicall, IMetaMorph
_setCap(id, pendingCap[id].value);
}

/// @notice Transfers `token` rewards collected by the vault to the `rewardsRecipient`.
/// @notice Transfers `token` rewards collected by the vault to the `skimRecipient`.
/// @dev Can be used to extract any token that would be stuck on the contract as well.
function transferRewards(address token) external {
if (rewardsRecipient == address(0)) revert ErrorsLib.ZeroAddress();
function skim(address token) external {
if (skimRecipient == address(0)) revert ErrorsLib.ZeroAddress();

uint256 amount = IERC20(token).balanceOf(address(this));
if (token == asset()) amount -= idle;

SafeERC20.safeTransfer(IERC20(token), rewardsRecipient, amount);
SafeERC20.safeTransfer(IERC20(token), skimRecipient, amount);

emit EventsLib.TransferRewards(_msgSender(), rewardsRecipient, token, amount);
emit EventsLib.Skim(_msgSender(), skimRecipient, token, amount);
}

/* ERC4626 (PUBLIC) */
Expand Down
6 changes: 3 additions & 3 deletions src/interfaces/IMetaMorpho.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ interface IMetaMorpho is IERC4626 {

function fee() external view returns (uint96);
function feeRecipient() external view returns (address);
function rewardsRecipient() external view returns (address);
function skimRecipient() external view returns (address);
function timelock() external view returns (uint256);
function supplyQueue(uint256) external view returns (Id);
function supplyQueueSize() external view returns (uint256);
Expand Down Expand Up @@ -74,12 +74,12 @@ interface IMetaMorpho is IERC4626 {
function revokeGuardian() external;
function pendingGuardian() external view returns (address guardian, uint96 submittedAt);

function transferRewards(address) external;
function skim(address) external;

function setIsAllocator(address newAllocator, bool newIsAllocator) external;
function setCurator(address newCurator) external;
function setFeeRecipient(address newFeeRecipient) external;
function setRewardsRecipient(address) external;
function setSkimRecipient(address) external;

function setSupplyQueue(Id[] calldata newSupplyQueue) external;
function sortWithdrawQueue(uint256[] calldata indexes) external;
Expand Down
10 changes: 4 additions & 6 deletions src/libraries/EventsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ library EventsLib {
/// @notice Emitted `timelock` is set to `newTimelock`.
event SetTimelock(uint256 newTimelock);

/// @notice Emitted `rewardsDistibutor` is set to `newRewardsRecipient`.
event SetRewardsRecipient(address indexed newRewardsRecipient);
/// @notice Emitted `rewardsDistibutor` is set to `newSkimRecipient`.
event SetSkimRecipient(address indexed newSkimRecipient);

/// @notice Emitted when a pending `newFee` is submitted.
event SubmitFee(uint256 newFee);
Expand Down Expand Up @@ -66,10 +66,8 @@ library EventsLib {
/// @notice Emitted when fees are accrued.
event AccrueFee(uint256 feeShares);

/// @notice Emitted when an `amount` of `token` is transferred to the `rewardsRecipient` by `caller`.
event TransferRewards(
address indexed caller, address indexed rewardsRecipient, address indexed token, uint256 amount
);
/// @notice Emitted when an `amount` of `token` is transferred to the `skimRecipient` by `caller`.
event Skim(address indexed caller, address indexed skimRecipient, address indexed token, uint256 amount);

/// @notice Emitted when a new MetaMorpho vault is created.
/// @param metaMorpho The address of the MetaMorpho vault.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import "erc4626-tests/ERC4626.test.sol";

import {IntegrationTest} from "./helpers/IntegrationTest.sol";

contract ERC4626A16zTest is IntegrationTest, ERC4626Test {
contract ERC4626ComplianceTest is IntegrationTest, ERC4626Test {
function setUp() public override(IntegrationTest, ERC4626Test) {
super.setUp();

Expand All @@ -14,5 +14,7 @@ contract ERC4626A16zTest is IntegrationTest, ERC4626Test {
_delta_ = 0;
_vaultMayBeEmpty = true;
_unlimitedAmount = true;

_setCap(allMarkets[0], 1e28);
}
}
40 changes: 20 additions & 20 deletions test/forge/UrdTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,41 +21,41 @@ contract UrdTest is IntegrationTest {
rewardsDistributor = urdFactory.createUrd(OWNER, 0, bytes32(0), bytes32(0), bytes32(0));
}

function testSetRewardsRecipient(address newRewardsRecipient) public {
vm.assume(newRewardsRecipient != vault.rewardsRecipient());
function testSetSkimRecipient(address newSkimRecipient) public {
vm.assume(newSkimRecipient != vault.skimRecipient());

vm.expectEmit();
emit EventsLib.SetRewardsRecipient(newRewardsRecipient);
emit EventsLib.SetSkimRecipient(newSkimRecipient);
vm.prank(OWNER);
vault.setRewardsRecipient(newRewardsRecipient);
vault.setSkimRecipient(newSkimRecipient);

assertEq(vault.rewardsRecipient(), newRewardsRecipient);
assertEq(vault.skimRecipient(), newSkimRecipient);
}

function testAlreadySetRewardsRecipient() public {
address currentRewardsRecipient = vault.rewardsRecipient();
function testAlreadySetSkimRecipient() public {
address currentSkimRecipient = vault.skimRecipient();

vm.prank(OWNER);
vm.expectRevert(ErrorsLib.AlreadySet.selector);
vault.setRewardsRecipient(currentRewardsRecipient);
vault.setSkimRecipient(currentSkimRecipient);
}

function testSetRewardsRecipientNotOwner() public {
function testSetSkimRecipientNotOwner() public {
vm.expectRevert(abi.encodeWithSelector(Ownable.OwnableUnauthorizedAccount.selector, address(this)));
vault.setRewardsRecipient(address(0));
vault.setSkimRecipient(address(0));
}

function testTransferRewardsNotLoanToken(uint256 amount) public {
function testSkimNotLoanToken(uint256 amount) public {
vm.prank(OWNER);
vault.setRewardsRecipient(address(rewardsDistributor));
vault.setSkimRecipient(address(rewardsDistributor));

collateralToken.setBalance(address(vault), amount);
uint256 vaultBalanceBefore = collateralToken.balanceOf(address(vault));
assertEq(vaultBalanceBefore, amount, "vaultBalanceBefore");

vm.expectEmit();
emit EventsLib.TransferRewards(address(this), address(rewardsDistributor), address(collateralToken), amount);
vault.transferRewards(address(collateralToken));
emit EventsLib.Skim(address(this), address(rewardsDistributor), address(collateralToken), amount);
vault.skim(address(collateralToken));
uint256 vaultBalanceAfter = collateralToken.balanceOf(address(vault));

assertEq(vaultBalanceAfter, 0, "vaultBalanceAfter");
Expand All @@ -66,12 +66,12 @@ contract UrdTest is IntegrationTest {
);
}

function testTransferRewardsLoanToken(uint256 rewards, uint256 idle) public {
function testSkimLoanToken(uint256 rewards, uint256 idle) public {
idle = bound(idle, 0, MAX_TEST_ASSETS);
rewards = bound(rewards, 0, MAX_TEST_ASSETS);

vm.prank(OWNER);
vault.setRewardsRecipient(address(rewardsDistributor));
vault.setSkimRecipient(address(rewardsDistributor));

loanToken.setBalance(address(vault), rewards);

Expand All @@ -84,8 +84,8 @@ contract UrdTest is IntegrationTest {
assertEq(vaultBalanceBefore, idle + rewards, "vaultBalanceBefore");

vm.expectEmit();
emit EventsLib.TransferRewards(address(this), address(rewardsDistributor), address(loanToken), rewards);
vault.transferRewards(address(loanToken));
emit EventsLib.Skim(address(this), address(rewardsDistributor), address(loanToken), rewards);
vault.skim(address(loanToken));
uint256 vaultBalanceAfter = loanToken.balanceOf(address(vault));

assertEq(vaultBalanceAfter, idle, "vaultBalanceAfter");
Expand All @@ -96,8 +96,8 @@ contract UrdTest is IntegrationTest {
);
}

function testTransferRewardsZeroAddress() public {
function testSkimZeroAddress() public {
vm.expectRevert(ErrorsLib.ZeroAddress.selector);
vault.transferRewards(address(loanToken));
vault.skim(address(loanToken));
}
}
20 changes: 10 additions & 10 deletions test/metamorpho_tests.tree
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@
└── it should emit SetIsAllocator(newAllocator, newIsAllocator)

.
└── setRewardsRecipient(address newRewardsRecipient) external
└── setSkimRecipient(address newSkimRecipient) external
├── when msg.sender not owner
│ └── revert with NOT_OWNER
└── when msg.sender is owner
├── when newRewardsRecipient == rewardsRecipient
├── when newSkimRecipient == skimRecipient
│ └── revert with AlreadySet()
└── when newRewardsRecipient != rewardsRecipient
├── it should set rewardsRecipient to newRewardsRecipient
└── it shoud emit SetRewardsRecipient(newRewardsRecipient)
└── when newSkimRecipient != skimRecipient
├── it should set skimRecipient to newSkimRecipient
└── it shoud emit SetSkimRecipient(newSkimRecipient)

.
└── submitTimelock(uint256 newTimelock) external
Expand Down Expand Up @@ -274,15 +274,15 @@


.
└── transferRewards(address token) external
├── when rewardsRecipient == address(0)
└── skim(address token) external
├── when skimRecipient == address(0)
│ └── revert with ZERO_ADDRESS
└── when rewardsRecipient != address(0)
└── when skimRecipient != address(0)
├── it should compute amount = IERC20(token).balanceOf(address(this))
├── when token == asset()
│ └── it should remove idle from amount
├── it should transfer amount of token from the the vault to rewardsRecipient
└── it should emit TransferRewards(msg.sender, rewardsRecipient, token, amount)
├── it should transfer amount of token from the the vault to skimRecipient
└── it should emit Skim(msg.sender, skimRecipient, token, amount)


/* ONLY GUARDIAN FUNCTIONS */
Expand Down

0 comments on commit 1eba06c

Please sign in to comment.