Skip to content
Open
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
811 changes: 811 additions & 0 deletions packages/foundry/src/vault/MultiStrategyVault.sol

Large diffs are not rendered by default.

139 changes: 139 additions & 0 deletions packages/foundry/test/Tester.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.15

pragma solidity ^0.8.15;
import { Test } from "forge-std/Test.sol";
import { Math } from "openzeppelin-contracts/utils/math/Math.sol";
import { MultiMockERC4626, IERC4626, IERC20 } from "./utils/mocks/MultiMockERC4626.sol";
import { MockERC20 } from "./utils/mocks/MockERC20.sol";
import { MockERC4626 } from "./utils/mocks/MockERC4626.sol";
import { Clones } from "openzeppelin-contracts/proxy/Clones.sol";

contract Tester is Test {
using Math for uint256;

MultiMockERC4626 multiMockERC4626;
MockERC20 asset;
MockERC4626 adapter1;
MockERC4626 adapter2;
MockERC4626 adapter3;

IERC4626[10] adapters;
uint256[10] allocations;

address adapterImplementation;
address implementation;

function _createAdapter(IERC20 _asset) internal returns (MockERC4626) {
address adapterAddress = Clones.clone(adapterImplementation);
MockERC4626(adapterAddress).initialize(_asset, "Mock Token Vault", "vwTKN");
return MockERC4626(adapterAddress);
}

function setUp() public {
asset = new MockERC20("asset", "asset", 18);

adapterImplementation = address(new MockERC4626());
implementation = address(new MultiMockERC4626());

adapter1 = _createAdapter(IERC20(address(asset)));
adapter2 = _createAdapter(IERC20(address(asset)));
adapter3 = _createAdapter(IERC20(address(asset)));

adapters[0] = IERC4626(address(adapter1));
adapters[1] = IERC4626(address(adapter2));
adapters[2] = IERC4626(address(adapter3));

allocations[0] = 0.25e18;
allocations[1] = 0.25e18;
allocations[2] = 0.5e18;

multiMockERC4626 = MultiMockERC4626(Clones.clone(implementation));
multiMockERC4626.initialize(
IERC20(address(asset)),
adapters,
allocations,
2,
"MultiMockERC4626",
"MultiMockERC4626"
);
}

function test_deposit_withdraw() public {
uint256 amount = 4759;
asset.mint(address(this), amount);
asset.approve(address(multiMockERC4626), amount);

uint256 prevD = multiMockERC4626.previewDeposit(amount);
uint256 shares = multiMockERC4626.deposit(amount, address(this));
uint256 totalSupply = multiMockERC4626.totalSupply();
uint256 totalAssets = multiMockERC4626.totalAssets();

emit log_named_uint("prevD", prevD);
emit log_named_uint("shares", shares);
emit log_named_uint("totalSupply", totalSupply);
emit log_named_uint("totalAssets", totalAssets);

uint256 prevW = multiMockERC4626.previewWithdraw(amount);
uint256 burned = multiMockERC4626.withdraw(totalAssets, address(this), address(this));
totalSupply = multiMockERC4626.totalSupply();
totalAssets = multiMockERC4626.totalAssets();

emit log_named_uint("prevW", prevW);
emit log_named_uint("burned", burned);
emit log_named_uint("totalSupply", totalSupply);
emit log_named_uint("totalAssets", totalAssets);
}

function test_deposit_redeem() public {
uint256 amount = 4759;
asset.mint(address(this), amount);
asset.approve(address(multiMockERC4626), amount);

uint256 prevD = multiMockERC4626.previewDeposit(amount);
uint256 shares = multiMockERC4626.deposit(amount, address(this));
uint256 totalSupply = multiMockERC4626.totalSupply();
uint256 totalAssets = multiMockERC4626.totalAssets();

emit log_named_uint("prevD", prevD);
emit log_named_uint("shares", shares);
emit log_named_uint("totalSupply", totalSupply);
emit log_named_uint("totalAssets", totalAssets);

uint256 prevR = multiMockERC4626.previewRedeem(totalSupply);
uint256 burned = multiMockERC4626.redeem(totalSupply, address(this), address(this));
totalSupply = multiMockERC4626.totalSupply();
totalAssets = multiMockERC4626.totalAssets();

emit log_named_uint("prevR", prevR);
emit log_named_uint("burned", burned);
emit log_named_uint("totalSupply", totalSupply);
emit log_named_uint("totalAssets", totalAssets);
}

function test_mint_withdraw() public {
uint256 amount = 4759000000000;
asset.mint(address(this), amount);
asset.approve(address(multiMockERC4626), amount);

uint256 prevM = multiMockERC4626.previewMint(amount);
uint256 assets = multiMockERC4626.mint(amount, address(this));
uint256 totalSupply = multiMockERC4626.totalSupply();
uint256 totalAssets = multiMockERC4626.totalAssets();

emit log_named_uint("prevM", prevM);
emit log_named_uint("assets", assets);
emit log_named_uint("totalSupply", totalSupply);
emit log_named_uint("totalAssets", totalAssets);

uint256 prevW = multiMockERC4626.previewWithdraw(totalAssets);
uint256 returned = multiMockERC4626.withdraw(totalAssets, address(this), address(this));
totalSupply = multiMockERC4626.totalSupply();
totalAssets = multiMockERC4626.totalAssets();

emit log_named_uint("prevW", prevW);
emit log_named_uint("returned", returned);
emit log_named_uint("totalSupply", totalSupply);
emit log_named_uint("totalAssets", totalAssets);
}
}
140 changes: 140 additions & 0 deletions packages/foundry/test/utils/mocks/MultiMockERC4626.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: GPL-3.0
// Docgen-SOLC: 0.8.15
pragma solidity ^0.8.15;

import { IERC4626Upgradeable as IERC4626, IERC20Upgradeable as IERC20, IERC20MetadataUpgradeable as IERC20Metadata } from "openzeppelin-contracts-upgradeable/interfaces/IERC4626Upgradeable.sol";
import { ERC4626Upgradeable, ERC20Upgradeable as ERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/extensions/ERC4626Upgradeable.sol";
import { SafeERC20Upgradeable as SafeERC20 } from "openzeppelin-contracts-upgradeable/token/ERC20/utils/SafeERC20Upgradeable.sol";
import { MathUpgradeable as Math } from "openzeppelin-contracts-upgradeable/utils/math/MathUpgradeable.sol";

contract MultiMockERC4626 is ERC4626Upgradeable {
using SafeERC20 for IERC20;
using Math for uint256;

uint256 public beforeWithdrawHookCalledCounter = 0;
uint256 public afterDepositHookCalledCounter = 0;

uint8 internal _decimals;
uint8 public constant decimalOffset = 9;

IERC4626[10] adapters;
uint256[10] allocations;

uint256 len;

/*//////////////////////////////////////////////////////////////
IMMUTABLES
//////////////////////////////////////////////////////////////*/

function initialize(
IERC20 _asset,
IERC4626[10] calldata _adapters,
uint256[10] calldata _allocations,
uint256 _len,
string memory _name,
string memory _symbol
) external initializer {
__ERC4626_init(IERC20Metadata(address(_asset)));
_decimals = IERC20Metadata(address(_asset)).decimals() + decimalOffset;

len = _len;

for (uint8 i; i < _len; i++) {
adapters[i] = _adapters[i];
allocations[i] = _allocations[i];

_asset.safeApprove(address(_adapters[i]), type(uint256).max);
}
}

/*//////////////////////////////////////////////////////////////
GENERAL VIEWS
//////////////////////////////////////////////////////////////*/

function decimals() public view override(IERC20Metadata, ERC20) returns (uint8) {
return _decimals;
}

/*//////////////////////////////////////////////////////////////
ACCOUNTING LOGIC
//////////////////////////////////////////////////////////////*/

function totalAssets() public view override returns (uint256 assets) {
assets = IERC20(asset()).balanceOf(address(this));

for (uint8 i; i < len; i++) {
assets += adapters[i].convertToAssets(adapters[i].balanceOf(address(this)));
}
}

// function previewDeposit(uint256 assets) public view override returns (uint256 shares) {
// uint256 leftover = assets;
// uint256 allocation_ = assets.mulDiv(allocation, 1e18, Math.Rounding.Down);

// leftover -= allocation_ * 2;
// shares = convertToShares(assets - leftover);
// }

// function previewMint(uint256 shares) public view override returns (uint256 assets) {
// assets = convertToAssets(shares);

// uint256 leftover = assets;
// uint256 allocation_ = assets.mulDiv(allocation, 1e18, Math.Rounding.Down);

// leftover -= allocation_ * 2;
// assets += leftover;
// }

function _convertToShares(uint256 assets, Math.Rounding rounding) internal view override returns (uint256 shares) {
return assets.mulDiv(totalSupply() + 10**decimalOffset, totalAssets() + 1, rounding);
}

function _convertToAssets(uint256 shares, Math.Rounding rounding) internal view override returns (uint256) {
return shares.mulDiv(totalAssets() + 1, totalSupply() + 10**decimalOffset, rounding);
}

/*//////////////////////////////////////////////////////////////
INTERNAL HOOKS LOGIC
//////////////////////////////////////////////////////////////*/

function _deposit(
address caller,
address receiver,
uint256 assets,
uint256 shares
) internal override {
IERC20(asset()).safeTransferFrom(caller, address(this), assets);

for (uint8 i; i < len; i++) {
uint256 allocation = assets.mulDiv(allocations[i], 1e18, Math.Rounding.Down);
adapters[i].deposit(allocation, address(this));
}

_mint(receiver, shares);

emit Deposit(caller, receiver, assets, shares);
}

function _withdraw(
address caller,
address receiver,
address owner,
uint256 assets,
uint256 shares
) internal override {
if (caller != owner) {
_spendAllowance(owner, caller, shares);
}

_burn(owner, shares);

for (uint8 i; i < len; i++) {
uint256 allocation = assets.mulDiv(allocations[i], 1e18, Math.Rounding.Down);
adapters[i].withdraw(allocation, address(this), address(this));
}

IERC20(asset()).safeTransfer(receiver, assets);

emit Withdraw(caller, receiver, owner, assets, shares);
}
}
Loading