-
Notifications
You must be signed in to change notification settings - Fork 2
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
ERC4626 contracts #40
Closed
Closed
Changes from 8 commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
89ffa04
Adding solmate lib 6.2.0
dimpar 675a44a
Copying ERC4626 related contracts from ERC4626-Alliance
dimpar 8103f81
Adding support for Solidity 0.8.10. Used by ERC4626 contracts
dimpar 509ddfa
Adding annotation that these contracts were copied from ERC4626-Allia…
dimpar 14b965a
Ignoring styling of added Solidity ERC4626 lib contracts
dimpar bc2ccbc
Moving erc4626 contracts under lib/erc4626 dir
dimpar 8d0a932
Removing components of ERC4626RouterBase
dimpar 93ee2bc
Adding IERC4626 interface
dimpar c2dbefe
Merge remote-tracking branch 'origin' into erc4626-contracts
dimpar File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,3 +4,4 @@ deployments/ | |
export.json | ||
export/ | ||
typechain/ | ||
contracts/lib/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,2 @@ | ||
node_modules/ | ||
contracts/lib/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,115 @@ | ||
// SPDX-License-Identifier: GPL-2.0-or-later | ||
pragma solidity 0.8.10; | ||
|
||
import {ERC20} from "solmate/src/tokens/ERC20.sol"; | ||
|
||
/// @title ERC4626 interface | ||
/// See: https://eips.ethereum.org/EIPS/eip-4626 | ||
abstract contract IERC4626 is ERC20 { | ||
/*//////////////////////////////////////////////////////// | ||
Events | ||
////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice `sender` has exchanged `assets` for `shares`, | ||
/// and transferred those `shares` to `receiver`. | ||
event Deposit(address indexed sender, address indexed receiver, uint256 assets, uint256 shares); | ||
|
||
/// @notice `sender` has exchanged `shares` for `assets`, | ||
/// and transferred those `assets` to `receiver`. | ||
event Withdraw(address indexed sender, address indexed receiver, uint256 assets, uint256 shares); | ||
|
||
/*//////////////////////////////////////////////////////// | ||
Vault properties | ||
////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice The address of the underlying ERC20 token used for | ||
/// the Vault for accounting, depositing, and withdrawing. | ||
function asset() external view virtual returns (address asset); | ||
|
||
/// @notice Total amount of the underlying asset that | ||
/// is "managed" by Vault. | ||
function totalAssets() external view virtual returns (uint256 totalAssets); | ||
|
||
/*//////////////////////////////////////////////////////// | ||
Deposit/Withdrawal Logic | ||
////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice Mints `shares` Vault shares to `receiver` by | ||
/// depositing exactly `assets` of underlying tokens. | ||
function deposit(uint256 assets, address receiver) external virtual returns (uint256 shares); | ||
|
||
/// @notice Mints exactly `shares` Vault shares to `receiver` | ||
/// by depositing `assets` of underlying tokens. | ||
function mint(uint256 shares, address receiver) external virtual returns (uint256 assets); | ||
|
||
/// @notice Redeems `shares` from `owner` and sends `assets` | ||
/// of underlying tokens to `receiver`. | ||
function withdraw( | ||
uint256 assets, | ||
address receiver, | ||
address owner | ||
) external virtual returns (uint256 shares); | ||
|
||
/// @notice Redeems `shares` from `owner` and sends `assets` | ||
/// of underlying tokens to `receiver`. | ||
function redeem( | ||
uint256 shares, | ||
address receiver, | ||
address owner | ||
) external virtual returns (uint256 assets); | ||
|
||
/*//////////////////////////////////////////////////////// | ||
Vault Accounting Logic | ||
////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice The amount of shares that the vault would | ||
/// exchange for the amount of assets provided, in an | ||
/// ideal scenario where all the conditions are met. | ||
function convertToShares(uint256 assets) external view virtual returns (uint256 shares); | ||
|
||
/// @notice The amount of assets that the vault would | ||
/// exchange for the amount of shares provided, in an | ||
/// ideal scenario where all the conditions are met. | ||
function convertToAssets(uint256 shares) external view virtual returns (uint256 assets); | ||
|
||
/// @notice Total number of underlying assets that can | ||
/// be deposited by `owner` into the Vault, where `owner` | ||
/// corresponds to the input parameter `receiver` of a | ||
/// `deposit` call. | ||
function maxDeposit(address owner) external view virtual returns (uint256 maxAssets); | ||
|
||
/// @notice Allows an on-chain or off-chain user to simulate | ||
/// the effects of their deposit at the current block, given | ||
/// current on-chain conditions. | ||
function previewDeposit(uint256 assets) external view virtual returns (uint256 shares); | ||
|
||
/// @notice Total number of underlying shares that can be minted | ||
/// for `owner`, where `owner` corresponds to the input | ||
/// parameter `receiver` of a `mint` call. | ||
function maxMint(address owner) external view virtual returns (uint256 maxShares); | ||
|
||
/// @notice Allows an on-chain or off-chain user to simulate | ||
/// the effects of their mint at the current block, given | ||
/// current on-chain conditions. | ||
function previewMint(uint256 shares) external view virtual returns (uint256 assets); | ||
|
||
/// @notice Total number of underlying assets that can be | ||
/// withdrawn from the Vault by `owner`, where `owner` | ||
/// corresponds to the input parameter of a `withdraw` call. | ||
function maxWithdraw(address owner) external view virtual returns (uint256 maxAssets); | ||
|
||
/// @notice Allows an on-chain or off-chain user to simulate | ||
/// the effects of their withdrawal at the current block, | ||
/// given current on-chain conditions. | ||
function previewWithdraw(uint256 assets) external view virtual returns (uint256 shares); | ||
|
||
/// @notice Total number of underlying shares that can be | ||
/// redeemed from the Vault by `owner`, where `owner` corresponds | ||
/// to the input parameter of a `redeem` call. | ||
function maxRedeem(address owner) external view virtual returns (uint256 maxShares); | ||
|
||
/// @notice Allows an on-chain or off-chain user to simulate | ||
/// the effects of their redeemption at the current block, | ||
/// given current on-chain conditions. | ||
function previewRedeem(uint256 shares) external view virtual returns (uint256 assets); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,60 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Rewards logic inspired by xERC20 (https://github.com/ZeframLou/playpen/blob/main/src/xERC20.sol) | ||
|
||
// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on | ||
// the project commit 643cd04 from Apr 20, 2022 | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "solmate/src/mixins/ERC4626.sol"; | ||
import "solmate/src/utils/SafeCastLib.sol"; | ||
|
||
/** | ||
@title An xERC4626 Single Staking Contract Interface | ||
@notice This contract allows users to autocompound rewards denominated in an underlying reward token. | ||
It is fully compatible with [ERC4626](https://eips.ethereum.org/EIPS/eip-4626) allowing for DeFi composability. | ||
It maintains balances using internal accounting to prevent instantaneous changes in the exchange rate. | ||
NOTE: an exception is at contract creation, when a reward cycle begins before the first deposit. After the first deposit, exchange rate updates smoothly. | ||
|
||
Operates on "cycles" which distribute the rewards surplus over the internal balance to users linearly over the remainder of the cycle window. | ||
*/ | ||
interface IxERC4626 { | ||
/*//////////////////////////////////////////////////////// | ||
Custom Errors | ||
////////////////////////////////////////////////////////*/ | ||
|
||
/// @dev thrown when syncing before cycle ends. | ||
error SyncError(); | ||
|
||
/*//////////////////////////////////////////////////////// | ||
Events | ||
////////////////////////////////////////////////////////*/ | ||
|
||
/// @dev emit every time a new rewards cycle starts | ||
event NewRewardsCycle(uint32 indexed cycleEnd, uint256 rewardAmount); | ||
|
||
/*//////////////////////////////////////////////////////// | ||
View Methods | ||
////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice the maximum length of a rewards cycle | ||
function rewardsCycleLength() external view returns (uint32); | ||
|
||
/// @notice the effective start of the current cycle | ||
/// NOTE: This will likely be after `rewardsCycleEnd - rewardsCycleLength` as this is set as block.timestamp of the last `syncRewards` call. | ||
function lastSync() external view returns (uint32); | ||
|
||
/// @notice the end of the current cycle. Will always be evenly divisible by `rewardsCycleLength`. | ||
function rewardsCycleEnd() external view returns (uint32); | ||
|
||
/// @notice the amount of rewards distributed in a the most recent cycle | ||
function lastRewardAmount() external view returns (uint192); | ||
|
||
/*//////////////////////////////////////////////////////// | ||
State Changing Methods | ||
////////////////////////////////////////////////////////*/ | ||
|
||
/// @notice Distributes rewards to xERC4626 holders. | ||
/// All surplus `asset` balance of the contract over the internal balance becomes queued for the next cycle. | ||
function syncRewards() external; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,101 @@ | ||
// SPDX-License-Identifier: MIT | ||
// Rewards logic inspired by xERC20 (https://github.com/ZeframLou/playpen/blob/main/src/xERC20.sol) | ||
|
||
// Copied from https://github.com/ERC4626-Alliance/ERC4626-Contracts based on | ||
// the project commit 643cd04 from Apr 20, 2022 | ||
|
||
pragma solidity ^0.8.0; | ||
|
||
import "solmate/src/mixins/ERC4626.sol"; | ||
import "solmate/src/utils/SafeCastLib.sol"; | ||
|
||
import "./interfaces/IxERC4626.sol"; | ||
|
||
/** | ||
@title An xERC4626 Single Staking Contract | ||
@notice This contract allows users to autocompound rewards denominated in an underlying reward token. | ||
It is fully compatible with [ERC4626](https://eips.ethereum.org/EIPS/eip-4626) allowing for DeFi composability. | ||
It maintains balances using internal accounting to prevent instantaneous changes in the exchange rate. | ||
NOTE: an exception is at contract creation, when a reward cycle begins before the first deposit. After the first deposit, exchange rate updates smoothly. | ||
|
||
Operates on "cycles" which distribute the rewards surplus over the internal balance to users linearly over the remainder of the cycle window. | ||
*/ | ||
abstract contract xERC4626 is IxERC4626, ERC4626 { | ||
using SafeCastLib for *; | ||
|
||
/// @notice the maximum length of a rewards cycle | ||
uint32 public immutable rewardsCycleLength; | ||
|
||
/// @notice the effective start of the current cycle | ||
uint32 public lastSync; | ||
|
||
/// @notice the end of the current cycle. Will always be evenly divisible by `rewardsCycleLength`. | ||
uint32 public rewardsCycleEnd; | ||
|
||
/// @notice the amount of rewards distributed in a the most recent cycle. | ||
uint192 public lastRewardAmount; | ||
|
||
uint256 internal storedTotalAssets; | ||
|
||
constructor(uint32 _rewardsCycleLength) { | ||
rewardsCycleLength = _rewardsCycleLength; | ||
// seed initial rewardsCycleEnd | ||
rewardsCycleEnd = (block.timestamp.safeCastTo32() / rewardsCycleLength) * rewardsCycleLength; | ||
} | ||
|
||
/// @notice Compute the amount of tokens available to share holders. | ||
/// Increases linearly during a reward distribution period from the sync call, not the cycle start. | ||
function totalAssets() public view override returns (uint256) { | ||
// cache global vars | ||
uint256 storedTotalAssets_ = storedTotalAssets; | ||
uint192 lastRewardAmount_ = lastRewardAmount; | ||
uint32 rewardsCycleEnd_ = rewardsCycleEnd; | ||
uint32 lastSync_ = lastSync; | ||
|
||
if (block.timestamp >= rewardsCycleEnd_) { | ||
// no rewards or rewards fully unlocked | ||
// entire reward amount is available | ||
return storedTotalAssets_ + lastRewardAmount_; | ||
} | ||
|
||
// rewards not fully unlocked | ||
// add unlocked rewards to stored total | ||
uint256 unlockedRewards = (lastRewardAmount_ * (block.timestamp - lastSync_)) / (rewardsCycleEnd_ - lastSync_); | ||
return storedTotalAssets_ + unlockedRewards; | ||
} | ||
|
||
// Update storedTotalAssets on withdraw/redeem | ||
function beforeWithdraw(uint256 amount, uint256 shares) internal virtual override { | ||
super.beforeWithdraw(amount, shares); | ||
storedTotalAssets -= amount; | ||
} | ||
|
||
// Update storedTotalAssets on deposit/mint | ||
function afterDeposit(uint256 amount, uint256 shares) internal virtual override { | ||
storedTotalAssets += amount; | ||
super.afterDeposit(amount, shares); | ||
} | ||
|
||
/// @notice Distributes rewards to xERC4626 holders. | ||
/// All surplus `asset` balance of the contract over the internal balance becomes queued for the next cycle. | ||
function syncRewards() public virtual { | ||
uint192 lastRewardAmount_ = lastRewardAmount; | ||
uint32 timestamp = block.timestamp.safeCastTo32(); | ||
|
||
if (timestamp < rewardsCycleEnd) revert SyncError(); | ||
|
||
uint256 storedTotalAssets_ = storedTotalAssets; | ||
uint256 nextRewards = asset.balanceOf(address(this)) - storedTotalAssets_ - lastRewardAmount_; | ||
|
||
storedTotalAssets = storedTotalAssets_ + lastRewardAmount_; // SSTORE | ||
|
||
uint32 end = ((timestamp + rewardsCycleLength) / rewardsCycleLength) * rewardsCycleLength; | ||
|
||
// Combined single SSTORE | ||
lastRewardAmount = nextRewards.safeCastTo192(); | ||
lastSync = timestamp; | ||
rewardsCycleEnd = end; | ||
|
||
emit NewRewardsCycle(end, nextRewards); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This interface is not following EIP-4626 perfectly.
E.g.
Withdraw
event according to the EIP-4626 should be:but here is:
Looks like OpenZeppelin's IERC4626 may be more accurate. But please compare it carefully with the EIP-4626 first.