Skip to content

Commit

Permalink
comment all the things
Browse files Browse the repository at this point in the history
  • Loading branch information
mcclurejt committed Sep 7, 2024
1 parent fa62b5d commit 062d89b
Show file tree
Hide file tree
Showing 6 changed files with 261 additions and 302 deletions.
176 changes: 78 additions & 98 deletions contracts/Everlong.sol
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity ^0.8.20;

import { console2 as console } from "forge-std/console2.sol";
import { IHyperdrive } from "hyperdrive/contracts/src/interfaces/IHyperdrive.sol";
import { FixedPointMath } from "hyperdrive/contracts/src/libraries/FixedPointMath.sol";
import { SafeCast } from "hyperdrive/contracts/src/libraries/SafeCast.sol";
import { HyperdriveUtils } from "hyperdrive/test/utils/HyperdriveUtils.sol";
import { IERC20 } from "openzeppelin/interfaces/IERC20.sol";
import { IEverlong } from "./interfaces/IEverlong.sol";
import { EVERLONG_KIND, EVERLONG_VERSION } from "./libraries/Constants.sol";
Expand Down Expand Up @@ -81,8 +79,6 @@ contract Everlong is IEverlong {

// ───────────────────────── Immutables ──────────────────────

// @FIXME: Comments

// NOTE: Immutables accessed during transactions are left as internal
// to avoid the gas overhead of auto-generated getter functions.
// https://zokyo-auditing-tutorials.gitbook.io/zokyo-gas-savings/tutorials/gas-saving-technique-23-public-to-private-constants
Expand All @@ -100,12 +96,16 @@ contract Everlong is IEverlong {
/// If false, use the Hyperdrive's `vaultSharesToken`.
bool internal immutable _asBase;

/// @dev Address of the underlying asset to use with hyperdrive.
IERC20 internal immutable _asset;

/// @dev Decimals to use with _asset.
uint8 internal immutable _decimals;

/// @dev Kind of everlong.
string public constant override kind = EVERLONG_KIND;

/// @dev Version of everlong.
string public constant override version = EVERLONG_VERSION;

/// @notice Virtual shares are used to mitigate inflation attacks.
Expand All @@ -118,8 +118,7 @@ contract Everlong is IEverlong {

// ────────────────────────── Internal ───────────────────────

// @FIXME: Comments

/// @dev Structure to store and account for everlong-controlled positions.
Portfolio.State internal _portfolio;

/// @dev Address of the contract admin.
Expand Down Expand Up @@ -165,6 +164,7 @@ contract Everlong is IEverlong {
? IHyperdrive(__hyperdrive).baseToken()
: IHyperdrive(__hyperdrive).vaultSharesToken()
);

// Set the admin to the contract deployer.
_admin = msg.sender;
}
Expand All @@ -173,107 +173,86 @@ contract Everlong is IEverlong {
// │ Admin │
// ╰─────────────────────────────────────────────────────────╯

function setAdmin(address admin_) external onlyAdmin {
_admin = admin_;
emit AdminUpdated(admin_);
/// @notice Allows admin to transfer the admin role.
/// @param __admin The new admin address.
function setAdmin(address __admin) external onlyAdmin {
_admin = __admin;
emit AdminUpdated(__admin);
}

// ╭─────────────────────────────────────────────────────────╮
// │ ERC4626 │
// ╰─────────────────────────────────────────────────────────╯

/// @notice Calculate the total amount of assets controlled by everlong.
/// @notice To do this efficiently, the weighted average maturity is used.
/// @dev Underestimates the actual value by overestimating the average
/// maturity of the portfolio.
/// @return Total amount of assets controlled by Everlong.
function totalAssets() public view override returns (uint256) {
// If everlong holds no bonds, return the balance.
uint256 balance = _asset.balanceOf(address(this));
if (_portfolio.totalBonds == 0) {
return balance;
}

// Estimate the value of everlong-controlled positions by calculating
// the proceeds one would receive from closing a position with the portfolio's
// total amount of bonds and weighted average maturity.
// The weighted average maturity is rounded to the next checkpoint
// timestamp to underestimate the value.
return
balance +
_hyperdrive.previewCloseLong(
_asBase,
IEverlong.Position({
maturityTime: _hyperdrive
.getNearestCheckpointIdUp(_portfolio.avgMaturityTime)
.getCheckpointIdUp(_portfolio.avgMaturityTime)
.toUint128(),
bondAmount: _portfolio.totalBonds
})
);
}

function previewRedeem(
uint256 shares
) public view override returns (uint256 assets) {
assets = convertToAssets(shares);
uint256 balance = _asset.balanceOf(address(this));
if (assets <= balance) {
return assets;
}
// Close positions until balance >= assets.
IEverlong.Position memory position;
uint256 i;
uint256 output;
uint256 count = _portfolio.positionCount();
while (balance < assets && i < count) {
position = _portfolio.at(i);
output = _hyperdrive.previewCloseLong(_asBase, position);
balance += output;
i++;
}
}

function maxDeposit(
address _depositor
) public view override returns (uint256) {
// HACK: Silence the voices.
_depositor = _depositor;
return HyperdriveUtils.calculateMaxLong(_hyperdrive);
}

function maxMint(address _minter) public view override returns (uint256) {
// Silence the voices.
_minter = _minter;
return convertToShares(maxDeposit(_minter));
}

/// @dev Frees sufficient assets for a withdrawal by closing positions.
/// @param _assets Amount of assets owed to the withdrawer.
function _beforeWithdraw(
uint256 _assets,
uint256
) internal virtual override {
// Close matured positions.
_closeMaturedLongs();

// Close more positions until sufficient idle to process withdrawal.
_closeLongs(_assets - _asset.balanceOf(address(this)));
if (_assets > _asset.balanceOf(address(this))) {
console.log("Want: %s", _assets);
console.log("Have: %s", _asset.balanceOf(address(this)));
revert("Couldnt pay out withdrawal");
}
_closePositions(_assets - _asset.balanceOf(address(this)));
}

// ╭─────────────────────────────────────────────────────────╮
// │ Rebalancing │
// ╰─────────────────────────────────────────────────────────╯

/// @notice Rebalance the everlong portfolio by closing mature positions
/// and using the proceeds to open new positions.
function rebalance() public override {
// Close matured positions.
_closeMaturedLongs();
_closeMaturedPositions();

// Spend idle.
// Spend idle on opening a new position.
uint256 toSpend = _asset.balanceOf(address(this));
IERC20(_asset).approve(address(_hyperdrive), toSpend);
(uint256 maturityTime, uint256 bondAmount) = _hyperdrive.openLong(
_asBase,
toSpend
);

// Account for the new position in the portfolio.
_portfolio.handleOpenPosition(maturityTime, bondAmount);
}

// ╭─────────────────────────────────────────────────────────╮
// │ Hyperdrive │
// ╰─────────────────────────────────────────────────────────╯

function _closeMaturedLongs() internal returns (uint256 output) {
/// @dev Close only matured positions in the portfolio.
/// @return output Proceeds of closing the matured positions.
function _closeMaturedPositions() internal returns (uint256 output) {
IEverlong.Position memory position;
while (!_portfolio.isEmpty()) {
position = _portfolio.head();
Expand All @@ -286,25 +265,10 @@ contract Everlong is IEverlong {
return output;
}

function _estimateCloseMaturedLongs()
internal
view
returns (uint256 output)
{
IEverlong.Position memory position;
uint256 i;
uint256 count = _portfolio.positionCount();
while (i < count) {
position = _portfolio.at(i);
if (!_hyperdrive.isMature(position)) {
return output;
}
output += _hyperdrive.previewCloseLong(_asBase, position);
i++;
}
}

function _closeLongs(
/// @dev Close positions until the targeted amount of output is received.
/// @param _targetOutput Minimum amount of proceeds to receive.
/// @return output Total output received from closed positions.
function _closePositions(
uint256 _targetOutput
) internal returns (uint256 output) {
while (!_portfolio.isEmpty() && output < _targetOutput) {
Expand All @@ -314,85 +278,101 @@ contract Everlong is IEverlong {
return output;
}

function _estimateCloseLongs(
uint256 _targetOutput
) internal view returns (uint256 output) {
uint256 i;
uint256 count = _portfolio.positionCount();
while (i < count && output < _targetOutput) {
output += _hyperdrive.previewCloseLong(_asBase, _portfolio.at(i));
i++;
}
}

// ╭─────────────────────────────────────────────────────────╮
// │ Getters │
// ╰─────────────────────────────────────────────────────────╯

/// @notice Name of the Everlong token.
/// @return Name of the Everlong token.
function name() public view override returns (string memory) {
return _name;
}

/// @notice Symbol of the Everlong token.
/// @return Symbol of the Everlong token.
function symbol() public view override returns (string memory) {
return _symbol;
}

/// @notice Gets the address of the underlying Hyperdrive Instance.
/// @dev The underlying asset decimals.
/// @return The underlying asset decimals.
function _underlyingDecimals()
internal
view
virtual
override
returns (uint8)
{
return _decimals;
}

/// @notice The address of the underlying Hyperdrive Instance.
/// @return The address of the underlying Hyperdrive Instance.
function hyperdrive() external view override returns (address) {
return address(_hyperdrive);
}

/// @notice Gets whether Everlong uses Hyperdrive's base token to
/// transact.
/// @notice Whether Everlong uses Hyperdrive's base token to transact.
/// @return Whether Everlong uses Hyperdrive's base token to transact.
function asBase() external view returns (bool) {
return _asBase;
}

/// @notice Gets the address of the token used to interact with the
/// Hyperdrive instance.
/// @notice Address of the token used to interact with the Hyperdrive instance.
/// @return Address of the token used to interact with the Hyperdrive instance.
function asset() public view override returns (address) {
return address(_asset);
}

/// @notice Gets the amount of decimals for `asset`.
function decimals() public view override returns (uint8) {
return _decimals;
}

/// @notice Gets the admin address for Everlong.
function admin() external view returns (address) {
return _admin;
}

// FIXME: Consider idle liquidity
// FIXME: Consider idle liquidity + maybe maxLong?
//
/// @notice Returns whether the portfolio needs rebalancing.
/// @return True if the portfolio needs rebalancing, false otherwise.
function canRebalance() external view returns (bool) {
return _hyperdrive.isMature(_portfolio.head());
}

/// @notice Returns whether the portfolio has matured positions.
/// @return True if the portfolio has matured positions, false otherwise.
function hasMaturedPositions() external view returns (bool) {
return _hyperdrive.isMature(_portfolio.head());
}

/// @notice Retrieve the position at the specified location in the queue..
/// @param _index Index in the queue to retrieve the position.
/// @return The position at the specified location.
function positionAt(
uint256 _index
) external view returns (IEverlong.Position memory) {
return _portfolio.at(_index);
}

/// @notice Returns how many positions are currently in the queue.
/// @return The queue's position count.
function positionCount() external view returns (uint256) {
return _portfolio.positionCount();
}

/// @notice Calculates the estimated value of the position at _index.
/// @param _index Location of the position to value.
/// @return Estimated proceeds of closing the position.
function positionValue(uint256 _index) external view returns (uint256) {
return _hyperdrive.previewCloseLong(_asBase, _portfolio.at(_index));
}

/// @notice Weighted average maturity timestamp of the portfolio.
/// @return Weighted average maturity timestamp of the portfolio.
function avgMaturityTime() external view returns (uint128) {
return _portfolio.avgMaturityTime;
}

/// @notice Total quantity of bonds held in the portfolio.
/// @return Total quantity of bonds held in the portfolio.
function totalBonds() external view returns (uint128) {
return _portfolio.totalBonds;
}
Expand Down
14 changes: 0 additions & 14 deletions contracts/interfaces/IEverlong.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,18 +49,4 @@ abstract contract IEverlong is

/// @notice Thrown when caller is not the admin.
error Unauthorized();

// ── Positions ──────────────────────────────────────────────

/// @notice Thrown when attempting to insert a position with
/// a `maturityTime` sooner than the most recent position's.
error InconsistentPositionMaturity();

/// @notice Thrown when attempting to close a position with
/// a `bondAmount` greater than that contained by the position.
error InconsistentPositionBondAmount();

/// @notice Thrown when a target idle amount is too high to be reached
/// even after closing all positions.
error TargetIdleTooHigh();
}
17 changes: 2 additions & 15 deletions contracts/interfaces/IEverlongEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,12 @@ interface IEverlongEvents {
// ── Positions ──────────────────────────────────────────────

/// @notice Emitted when a new position is added to the bond portfolio.
/// @dev This event will only be emitted with new `maturityTime`s in the portfolio.
/// TODO: Reconsider naming https://github.com/delvtech/hyperdrive/pull/1096#discussion_r1681337414
event PositionOpened(
uint128 indexed maturityTime,
uint128 bondAmount,
uint256 index
);

/// @notice Emitted when an existing position's `bondAmount` is modified.
/// TODO: Reconsider naming https://github.com/delvtech/hyperdrive/pull/1096#discussion_r1681337414
event PositionUpdated(
uint128 indexed maturityTime,
uint128 newBondAmount,
uint256 index
);
event PositionOpened(uint128 indexed maturityTime, uint128 bondAmount);

/// @notice Emitted when an existing position is closed.
/// TODO: Reconsider naming https://github.com/delvtech/hyperdrive/pull/1096#discussion_r1681337414
event PositionClosed(uint128 indexed maturityTime);
event PositionClosed(uint128 indexed maturityTime, uint128 bondAmount);

/// @notice Emitted when Everlong's underlying portfolio is rebalanced.
event Rebalanced();
Expand Down
Loading

0 comments on commit 062d89b

Please sign in to comment.