Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
186 changes: 86 additions & 100 deletions contracts/src/instances/erc4626/ERC4626Base.sol
Original file line number Diff line number Diff line change
Expand Up @@ -34,114 +34,82 @@ abstract contract ERC4626Base is HyperdriveBase {

/// Yield Source ///

/// @notice Accepts a trader's deposit in either base or vault shares. If
/// the deposit is settled in base, the base is deposited into the
/// yield source immediately.
/// @param _amount The amount of token to transfer. The units of this
/// quantity are either base or vault shares, depending on the value
/// of `_options.asBase`.
/// @param _options The options that configure the deposit. The only option
/// used in this implementation is `_options.asBase`, which
/// determines if the deposit is settled in base or vault shares.
/// @return sharesMinted The amount deposited measured in vault shares.
/// @return vaultSharePrice The vault share price at the time of deposit.
function _deposit(
uint256 _amount,
IHyperdrive.Options calldata _options
)
internal
override
returns (uint256 sharesMinted, uint256 vaultSharePrice)
{
if (_options.asBase) {
// Take custody of the deposit in base.
ERC20(address(_baseToken)).safeTransferFrom(
msg.sender,
address(this),
_amount
);
/// @dev Accepts a deposit from the user in base.
/// @param _baseAmount The base amount to deposit.
/// @return The shares that were minted in the deposit.
/// @return The amount of ETH to refund. Since this yield source isn't
/// payable, this is always zero.
function _depositWithBase(
uint256 _baseAmount,
bytes calldata // unused
) internal override returns (uint256, uint256) {
// Take custody of the deposit in base.
ERC20(address(_baseToken)).safeTransferFrom(
msg.sender,
address(this),
_baseAmount
);

// Deposit the base into the yield source.
//
// NOTE: We increase the required approval amount by 1 wei so that
// the vault ends with an approval of 1 wei. This makes future
// approvals cheaper by keeping the storage slot warm.
ERC20(address(_baseToken)).forceApprove(
address(_vault),
_amount + 1
);
sharesMinted = _vault.deposit(_amount, address(this));
} else {
// WARN: This logic doesn't account for slippage in the conversion
// from base to shares. If deposits to the yield source incur
// slippage, this logic will be incorrect.
sharesMinted = _amount;
// Deposit the base into the yield source.
//
// NOTE: We increase the required approval amount by 1 wei so that
// the vault ends with an approval of 1 wei. This makes future
// approvals cheaper by keeping the storage slot warm.
ERC20(address(_baseToken)).forceApprove(
address(_vault),
_baseAmount + 1
);
uint256 sharesMinted = _vault.deposit(_baseAmount, address(this));

// Take custody of the deposit in vault shares.
ERC20(address(_vault)).safeTransferFrom(
msg.sender,
address(this),
sharesMinted
);
}
vaultSharePrice = _pricePerVaultShare();
return (sharesMinted, 0);
}

/// @notice Processes a trader's withdrawal in either base or vault shares.
/// If the withdrawal is settled in base, the base is withdrawn from
/// the yield source.
/// @param _shares The amount of vault shares to withdraw from Hyperdrive.
/// @param _vaultSharePrice The vault share price.
/// @param _options The options that configure the withdrawal. The options
/// used in this implementation are `_options.destination`, which
/// specifies the recipient of the withdrawal, and `_options.asBase`,
/// which determines if the withdrawal is settled in base or vault
/// shares.
/// @return amountWithdrawn The proceeds of the withdrawal. The units of
/// this quantity are vault shares since this yield source doesn't
/// support withdrawals in base.
function _withdraw(
uint256 _shares,
uint256 _vaultSharePrice,
IHyperdrive.Options calldata _options
) internal override returns (uint256 amountWithdrawn) {
// NOTE: Round down to underestimate the base proceeds.
//
// Correct for any error that crept into the calculation of the share
// amount by converting the shares to base and then back to shares
// using the vault's share conversion logic.
uint256 baseAmount = _shares.mulDown(_vaultSharePrice);
_shares = _vault.convertToShares(baseAmount);
/// @dev Process a deposit in vault shares.
/// @param _shareAmount The vault shares amount to deposit.
function _depositWithShares(
uint256 _shareAmount,
bytes calldata // unused
) internal override {
// Take custody of the deposit in vault shares.
ERC20(address(_vault)).safeTransferFrom(
msg.sender,
address(this),
_shareAmount
);
}

// If we're withdrawing zero shares, short circuit and return 0.
if (_shares == 0) {
return 0;
}
/// @dev Process a withdrawal in base and send the proceeds to the
/// destination.
/// @param _shareAmount The amount of vault shares to withdraw.
/// @param _destination The destination of the withdrawal.
/// @return amountWithdrawn The amount of base withdrawn.
function _withdrawWithBase(
uint256 _shareAmount,
address _destination,
bytes calldata // unused
) internal override returns (uint256 amountWithdrawn) {
// Redeem from the yield source and transfer the
// resulting base to the destination address.
amountWithdrawn = _vault.redeem(
_shareAmount,
_destination,
address(this)
);

// If we're withdrawing in base, we redeem the shares from the yield
// source, and we transfer base to the destination.
if (_options.asBase) {
// Redeem from the yield source and transfer the
// resulting base to the destination address.
amountWithdrawn = _vault.redeem(
_shares,
_options.destination,
address(this)
);
}
// Otherwise, we're withdrawing in vault shares, and we transfer vault
// shares to the destination.
else {
// Transfer vault shares to the destination.
ERC20(address(_vault)).safeTransfer(_options.destination, _shares);
amountWithdrawn = _shares;
}
return amountWithdrawn;
}

/// @notice Loads the vault share price from the yield source.
/// @return The current vault share price.
function _pricePerVaultShare() internal view override returns (uint256) {
return _vault.convertToAssets(ONE);
/// @dev Process a withdrawal in vault shares and send the proceeds to the
/// destination.
/// @param _shareAmount The amount of vault shares to withdraw.
/// @param _destination The destination of the withdrawal.
function _withdrawWithShares(
uint256 _shareAmount,
address _destination,
bytes calldata // unused
) internal override {
// Transfer vault shares to the destination.
ERC20(address(_vault)).safeTransfer(_destination, _shareAmount);
}

/// @dev Ensure that ether wasn't sent because ERC4626 vaults don't support
Expand All @@ -151,4 +119,22 @@ abstract contract ERC4626Base is HyperdriveBase {
revert IHyperdrive.NotPayable();
}
}

/// @dev Convert an amount of vault shares to an amount of base.
/// @param _shareAmount The vault shares amount.
/// @return The base amount.
function _convertToBase(
uint256 _shareAmount
) internal view override returns (uint256) {
return _vault.convertToAssets(_shareAmount);
}

/// @dev Convert an amount of base to an amount of vault shares.
/// @param _baseAmount The base amount.
/// @return The vault shares amount.
function _convertToShares(
uint256 _baseAmount
) internal view override returns (uint256) {
return _vault.convertToShares(_baseAmount);
}
}
167 changes: 69 additions & 98 deletions contracts/src/instances/steth/StETHBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,118 +30,89 @@ abstract contract StETHBase is HyperdriveBase {

/// Yield Source ///

/// @dev Accepts a transfer from the user in base or the yield source token.
/// @param _amount The amount of capital to deposit. The units of this
/// quantity are either base or vault shares, depending on the value
/// of `_options.asBase`.
/// @param _options The options that configure how the trade is settled. The
/// only option used in this deposit implementation is
/// `_options.asBase` which determines if the deposit is settled in
/// ETH or stETH shares.
/// @return shares The amount of capital deposited measured in vault shares.
/// @return vaultSharePrice The current vault share price.
function _deposit(
uint256 _amount,
IHyperdrive.Options calldata _options
) internal override returns (uint256 shares, uint256 vaultSharePrice) {
uint256 refund;
if (_options.asBase) {
// Ensure that sufficient ether was provided.
if (msg.value < _amount) {
revert IHyperdrive.TransferFailed();
}

// If the user sent more ether than the amount specified, refund the
// excess ether.
unchecked {
refund = msg.value - _amount;
}

// Submit the provided ether to Lido to be deposited. The fee
// collector address is passed as the referral address; however,
// users can specify whatever referrer they'd like by depositing
// stETH instead of ETH.
shares = _lido.submit{ value: _amount }(_feeCollector);
} else {
// Refund any ether that was sent to the contract.
refund = msg.value;
/// @dev Accepts a deposit from the user in base.
/// @param _baseAmount The base amount to deposit.
/// @return sharesMinted The shares that were minted in the deposit.
/// @return refund The amount of ETH to refund. This should be zero for
/// yield sources that don't accept ETH.
function _depositWithBase(
uint256 _baseAmount,
bytes calldata // unused
) internal override returns (uint256 sharesMinted, uint256 refund) {
// Ensure that sufficient ether was provided.
if (msg.value < _baseAmount) {
revert IHyperdrive.TransferFailed();
}

// Transfer stETH shares into the contract.
shares = _amount;
_lido.transferSharesFrom(msg.sender, address(this), shares);
// If the user sent more ether than the amount specified, refund the
// excess ether.
unchecked {
refund = msg.value - _baseAmount;
}

// Calculate the vault share price.
vaultSharePrice = _pricePerVaultShare();
// Submit the provided ether to Lido to be deposited. The fee
// collector address is passed as the referral address; however,
// users can specify whatever referrer they'd like by depositing
// stETH instead of ETH.
sharesMinted = _lido.submit{ value: _baseAmount }(_feeCollector);

// Return excess ether that was sent to the contract.
if (refund > 0) {
(bool success, ) = payable(msg.sender).call{ value: refund }("");
if (!success) {
revert IHyperdrive.TransferFailed();
}
}
return (sharesMinted, refund);
}

return (shares, vaultSharePrice);
/// @dev Process a deposit in vault shares.
/// @param _shareAmount The vault shares amount to deposit.
function _depositWithShares(
uint256 _shareAmount,
bytes calldata // unused
) internal override {
// Transfer stETH shares into the contract.
_lido.transferSharesFrom(msg.sender, address(this), _shareAmount);
}

/// @notice Processes a trader's withdrawal. This yield source only supports
/// withdrawals in stETH shares.
/// @param _shares The amount of vault shares to withdraw from Hyperdrive.
/// @param _vaultSharePrice The vault share price.
/// @param _options The options that configure the withdrawal. The options
/// used in this withdrawal implementation are `_options.destination`,
/// which specifies the recipient of the withdrawal, and
/// `_options.asBase`, which determines if the withdrawal is settled
/// in ETH or stETH. The `_options.asBase` option must be false since
/// stETH withdrawals aren't processed instantaneously. Users that
/// want to withdraw can manage their withdrawal separately.
/// @return The proceeds of the withdrawal. The units of this quantity are
/// vault shares since this yield source doesn't support withdrawals
/// in base.
function _withdraw(
uint256 _shares,
uint256 _vaultSharePrice,
IHyperdrive.Options calldata _options
) internal override returns (uint256) {
/// @dev Process a withdrawal in base and send the proceeds to the
/// destination.
function _withdrawWithBase(
uint256, // unused
address, // unused
bytes calldata // unused
) internal pure override returns (uint256) {
// stETH withdrawals aren't necessarily instantaneous. Users that want
// to withdraw can manage their withdrawal separately.
if (_options.asBase) {
revert IHyperdrive.UnsupportedToken();
}

// NOTE: Round down to underestimate the base proceeds.
//
// Correct for any error that crept into the calculation of the share
// amount by converting the shares to base and then back to shares
// using the vault's share conversion logic.
uint256 baseAmount = _shares.mulDown(_vaultSharePrice);
_shares = _lido.getSharesByPooledEth(baseAmount);

// If we're withdrawing zero shares, short circuit and return 0.
if (_shares == 0) {
return 0;
}

// Transfer the stETH shares to the destination.
_lido.transferShares(_options.destination, _shares);

return _shares;
revert IHyperdrive.UnsupportedToken();
}

/// @dev Returns the current vault share price. We simply use Lido's
/// internal share price.
/// @return price The current vault share price.
function _pricePerVaultShare()
internal
view
override
returns (uint256 price)
{
return _lido.getPooledEthByShares(ONE);
/// @dev Process a withdrawal in vault shares and send the proceeds to the
/// destination.
/// @param _shareAmount The amount of vault shares to withdraw.
/// @param _destination The destination of the withdrawal.
function _withdrawWithShares(
uint256 _shareAmount,
address _destination,
bytes calldata // unused
) internal override {
// Transfer the stETH shares to the destination.
_lido.transferShares(_destination, _shareAmount);
}

/// @dev We override the message value check since this integration is
/// payable.
function _checkMessageValue() internal pure override {}

/// @dev Convert an amount of vault shares to an amount of base.
/// @param _shareAmount The vault shares amount.
/// @return baseAmount The base amount.
function _convertToBase(
uint256 _shareAmount
) internal view override returns (uint256) {
return _lido.getPooledEthByShares(_shareAmount);
}

/// @dev Convert an amount of base to an amount of vault shares.
/// @param _baseAmount The base amount.
/// @return shareAmount The vault shares amount.
function _convertToShares(
uint256 _baseAmount
) internal view override returns (uint256) {
return _lido.getSharesByPooledEth(_baseAmount);
}
}
Loading