Skip to content
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

fix: deposit flow #103

Merged
merged 5 commits into from
Sep 18, 2024
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
49 changes: 24 additions & 25 deletions src/TokenizedStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -746,26 +746,25 @@ contract TokenizedStrategy {

/**
* @notice Total number of underlying assets that can
* be deposited by `_owner` into the strategy, where `owner`
* corresponds to the receiver of a {deposit} call.
* be deposited into the strategy, where `receiver`
* corresponds to the receiver of the shares of a {deposit} call.
*
* @param owner The address depositing.
* @return . The max that `owner` can deposit in `asset`.
* @param receiver The address receiving the shares.
* @return . The max that `receiver` can deposit in `asset`.
*/
function maxDeposit(address owner) external view returns (uint256) {
return _maxDeposit(_strategyStorage(), owner);
function maxDeposit(address receiver) external view returns (uint256) {
return _maxDeposit(_strategyStorage(), receiver);
}

/**
* @notice Total number of shares that can be minted by `owner`
* into the strategy, where `_owner` corresponds to the receiver
* @notice Total number of shares that can be minted to `receiver`
* of a {mint} call.
*
* @param owner The address minting.
* @return _maxMint The max that `owner` can mint in shares.
* @param receiver The address receiving the shares.
* @return _maxMint The max that `receiver` can mint in shares.
*/
function maxMint(address owner) external view returns (uint256) {
return _maxMint(_strategyStorage(), owner);
function maxMint(address receiver) external view returns (uint256) {
return _maxMint(_strategyStorage(), receiver);
}

/**
Expand Down Expand Up @@ -840,12 +839,14 @@ contract TokenizedStrategy {
uint256 assets,
Math.Rounding _rounding
) internal view returns (uint256) {
// Saves an extra SLOAD if totalAssets() is non-zero.
uint256 totalAssets_ = _totalAssets(S);
// Saves an extra SLOAD if values are non-zero.
uint256 totalSupply_ = _totalSupply(S);
// If supply is 0, PPS = 1.
if (totalSupply_ == 0) return assets;

uint256 totalAssets_ = _totalAssets(S);
// If assets are 0 but supply is not PPS = 0.
if (totalAssets_ == 0) return totalSupply_ == 0 ? assets : 0;
if (totalAssets_ == 0) return 0;

return assets.mulDiv(totalSupply_, totalAssets_, _rounding);
}
Expand All @@ -868,23 +869,23 @@ contract TokenizedStrategy {
/// @dev Internal implementation of {maxDeposit}.
function _maxDeposit(
StrategyData storage S,
address owner
address receiver
) internal view returns (uint256) {
// Cannot deposit when shutdown.
if (S.shutdown) return 0;
// Cannot deposit when shutdown or to the strategy.
if (S.shutdown || receiver == address(this)) return 0;

return IBaseStrategy(address(this)).availableDepositLimit(owner);
return IBaseStrategy(address(this)).availableDepositLimit(receiver);
}

/// @dev Internal implementation of {maxMint}.
function _maxMint(
StrategyData storage S,
address owner
address receiver
) internal view returns (uint256 maxMint_) {
// Cannot mint when shutdown.
if (S.shutdown) return 0;
// Cannot mint when shutdown or to the strategy.
if (S.shutdown || receiver == address(this)) return 0;

maxMint_ = IBaseStrategy(address(this)).availableDepositLimit(owner);
maxMint_ = IBaseStrategy(address(this)).availableDepositLimit(receiver);
if (maxMint_ != type(uint256).max) {
maxMint_ = _convertToShares(S, maxMint_, Math.Rounding.Down);
}
Expand Down Expand Up @@ -956,8 +957,6 @@ contract TokenizedStrategy {
uint256 assets,
uint256 shares
) internal {
require(receiver != address(this), "ERC4626: mint to self");

// Cache storage variables used more than once.
ERC20 _asset = S.asset;

Expand Down
110 changes: 97 additions & 13 deletions src/test/Accounting.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -439,10 +439,10 @@ contract AccountingTest is Setup {
setFees(0, 0);
mintAndDepositIntoStrategy(strategy, _address, _amount);

uint256 toLoose = (_amount * _lossFactor) / MAX_BPS;
uint256 toLose = (_amount * _lossFactor) / MAX_BPS;
// Simulate a loss.
vm.prank(address(yieldSource));
asset.transfer(address(69), toLoose);
asset.transfer(address(69), toLose);

vm.expectRevert("too much loss");
vm.prank(_address);
Expand All @@ -465,13 +465,13 @@ contract AccountingTest is Setup {
setFees(0, 0);
mintAndDepositIntoStrategy(strategy, _address, _amount);

uint256 toLoose = (_amount * _lossFactor) / MAX_BPS;
uint256 toLose = (_amount * _lossFactor) / MAX_BPS;
// Simulate a loss.
vm.prank(address(yieldSource));
asset.transfer(address(69), toLoose);
asset.transfer(address(69), toLose);

uint256 beforeBalance = asset.balanceOf(_address);
uint256 expectedOut = _amount - toLoose;
uint256 expectedOut = _amount - toLose;
// Withdraw the full amount before the loss is reported.
vm.prank(_address);
strategy.withdraw(_amount, _address, _address, _lossFactor);
Expand Down Expand Up @@ -499,13 +499,13 @@ contract AccountingTest is Setup {
setFees(0, 0);
mintAndDepositIntoStrategy(strategy, _address, _amount);

uint256 toLoose = (_amount * _lossFactor) / MAX_BPS;
uint256 toLose = (_amount * _lossFactor) / MAX_BPS;
// Simulate a loss.
vm.prank(address(yieldSource));
asset.transfer(address(69), toLoose);
asset.transfer(address(69), toLose);

uint256 beforeBalance = asset.balanceOf(_address);
uint256 expectedOut = _amount - toLoose;
uint256 expectedOut = _amount - toLose;
// Withdraw the full amount before the loss is reported.
vm.prank(_address);
strategy.redeem(_amount, _address, _address);
Expand Down Expand Up @@ -533,10 +533,10 @@ contract AccountingTest is Setup {
setFees(0, 0);
mintAndDepositIntoStrategy(strategy, _address, _amount);

uint256 toLoose = (_amount * _lossFactor) / MAX_BPS;
uint256 toLose = (_amount * _lossFactor) / MAX_BPS;
// Simulate a loss.
vm.prank(address(yieldSource));
asset.transfer(address(69), toLoose);
asset.transfer(address(69), toLose);

vm.expectRevert("too much loss");
vm.prank(_address);
Expand All @@ -559,13 +559,13 @@ contract AccountingTest is Setup {
setFees(0, 0);
mintAndDepositIntoStrategy(strategy, _address, _amount);

uint256 toLoose = (_amount * _lossFactor) / MAX_BPS;
uint256 toLose = (_amount * _lossFactor) / MAX_BPS;
// Simulate a loss.
vm.prank(address(yieldSource));
asset.transfer(address(69), toLoose);
asset.transfer(address(69), toLose);

uint256 beforeBalance = asset.balanceOf(_address);
uint256 expectedOut = _amount - toLoose;
uint256 expectedOut = _amount - toLose;

// First set it to just under the expected loss.
vm.expectRevert("too much loss");
Expand Down Expand Up @@ -613,4 +613,88 @@ contract AccountingTest is Setup {

assertEq(asset.balanceOf(address(yieldSource)), _amount);
}

function test_deposit_zeroAssetsPositiveSupply_reverts(
address _address,
uint256 _amount
) public {
_amount = bound(_amount, minFuzzAmount, maxFuzzAmount);
vm.assume(
_address != address(0) &&
_address != address(strategy) &&
_address != address(yieldSource)
);

setFees(0, 0);
mintAndDepositIntoStrategy(strategy, _address, _amount);

uint256 toLose = _amount;
// Simulate a loss.
vm.prank(address(yieldSource));
asset.transfer(address(69), toLose);

vm.prank(keeper);
strategy.report();

// Should still have shares but no assets
checkStrategyTotals(strategy, 0, 0, 0, _amount);

assertEq(strategy.balanceOf(_address), _amount);
assertEq(asset.balanceOf(address(strategy)), 0);
assertEq(asset.balanceOf(address(yieldSource)), 0);

asset.mint(_address, _amount);
vm.prank(_address);
asset.approve(address(strategy), _amount);

vm.expectRevert("ZERO_SHARES");
vm.prank(_address);
strategy.deposit(_amount, _address);

assertEq(strategy.convertToAssets(_amount), 0);
assertEq(strategy.convertToShares(_amount), 0);
assertEq(strategy.pricePerShare(), 0);
}

function test_mint_zeroAssetsPositiveSupply_reverts(
address _address,
uint256 _amount
) public {
_amount = bound(_amount, minFuzzAmount, maxFuzzAmount);
vm.assume(
_address != address(0) &&
_address != address(strategy) &&
_address != address(yieldSource)
);

setFees(0, 0);
mintAndDepositIntoStrategy(strategy, _address, _amount);

uint256 toLose = _amount;
// Simulate a loss.
vm.prank(address(yieldSource));
asset.transfer(address(69), toLose);

vm.prank(keeper);
strategy.report();

// Should still have shares but no assets
checkStrategyTotals(strategy, 0, 0, 0, _amount);

assertEq(strategy.balanceOf(_address), _amount);
assertEq(asset.balanceOf(address(strategy)), 0);
assertEq(asset.balanceOf(address(yieldSource)), 0);

asset.mint(_address, _amount);
vm.prank(_address);
asset.approve(address(strategy), _amount);

vm.expectRevert("ZERO_ASSETS");
vm.prank(_address);
strategy.mint(_amount, _address);

assertEq(strategy.convertToAssets(_amount), 0);
assertEq(strategy.convertToShares(_amount), 0);
assertEq(strategy.pricePerShare(), 0);
}
}
Loading