Skip to content

Commit

Permalink
Merge pull request #116 from MathisGD/feat/4626-vault-d3m
Browse files Browse the repository at this point in the history
D3M in 4626 vaults
  • Loading branch information
hexonaut authored Mar 15, 2024
2 parents a888da1 + 062b526 commit b1cb6a2
Show file tree
Hide file tree
Showing 5 changed files with 317 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/dss-test"]
path = lib/dss-test
url = https://github.com/makerdao/dss-test
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
1 change: 1 addition & 0 deletions lib/solmate
Submodule solmate added at c89230
150 changes: 150 additions & 0 deletions src/pools/D3M4626TypePool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
// SPDX-FileCopyrightText: © 2021 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity ^0.8.14;

import "./ID3MPool.sol";
import "forge-std/interfaces/IERC4626.sol";

interface VatLike {
function live() external view returns (uint256);
function hope(address) external;
function nope(address) external;
}

contract D3M4626TypePool is ID3MPool {
/* EVENTS */

event Rely(address indexed usr);
event Deny(address indexed usr);
event File(bytes32 indexed what, address data);

/* CONSTANTS */

IERC20 public immutable dai;
IERC4626 public immutable vault;
VatLike public immutable vat;

/* STORAGE */

address public hub;
mapping(address => uint256) public wards;

/* CONSTRUCTOR */

constructor(address newDai, address newVault, address newVat) {
require(newDai != address(0), "D3M4626TypePool/zero-address");
require(newVat != address(0), "D3M4626TypePool/zero-address");
require(newVault != address(0), "D3M4626TypePool/zero-address");
require(IERC4626(newVault).asset() == address(newDai), "D3M4626TypePool/vault-asset-is-not-dai");

dai = IERC20(newDai);
vault = IERC4626(newVault);
vat = VatLike(newVat);

wards[msg.sender] = 1;
emit Rely(msg.sender);

dai.approve(address(vault), type(uint256).max);
}

/* MODIFIERS */

modifier onlyHub() {
require(msg.sender == hub, "D3M4626TypePool/only-hub");
_;
}

modifier auth() {
require(wards[msg.sender] == 1, "D3M4626TypePool/not-authorized");
_;
}

/* ONLY HUB */

/// @inheritdoc ID3MPool
function deposit(uint256 assets) external override onlyHub {
vault.deposit(assets, address(this));
}

/// @inheritdoc ID3MPool
function withdraw(uint256 assets) external override onlyHub {
vault.withdraw(assets, msg.sender, address(this));
}

/// @inheritdoc ID3MPool
/// @dev prop = 100 ether exists 100%.
function exit(address dst, uint256 prop) external onlyHub {
uint256 shares = prop * vault.balanceOf(address(this)) / 100 ether;
vault.transfer(dst, shares);
}

/* ONLY AUTHORIZED */

/// @inheritdoc ID3MPool
function quit(address dst) external auth {
require(vat.live() == 1, "D3M4626TypePool/no-quit-during-shutdown");
vault.transfer(dst, vault.balanceOf(address(this)));
}

function rely(address usr) public auth {
wards[usr] = 1;
emit Rely(usr);
}

function deny(address usr) external auth {
wards[usr] = 0;
emit Deny(usr);
}

function file(bytes32 what, address data) external auth {
require(vat.live() == 1, "D3M4626TypePool/no-file-during-shutdown");
if (what == "hub") {
vat.nope(hub);
hub = data;
vat.hope(data);
} else revert("D3M4626TypePool/file-unrecognized-param");
emit File(what, data);
}

/* EXTERNAL */

/// @inheritdoc ID3MPool
function preDebtChange() external override {}

/// @inheritdoc ID3MPool
function postDebtChange() external override {}

/// @inheritdoc ID3MPool
function assetBalance() external view returns (uint256) {
return vault.convertToAssets(vault.balanceOf(address(this)));
}

/// @inheritdoc ID3MPool
function maxDeposit() external view returns (uint256) {
return vault.maxDeposit(address(this));
}

/// @inheritdoc ID3MPool
function maxWithdraw() external view returns (uint256) {
return vault.maxWithdraw(address(this));
}

/// @inheritdoc ID3MPool
function redeemable() external view returns (address) {
return address(vault);
}
}
163 changes: 163 additions & 0 deletions src/tests/pools/D3M4626TypePool.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
// SPDX-FileCopyrightText: © 2022 Dai Foundation <www.daifoundation.org>
// SPDX-License-Identifier: AGPL-3.0-or-later
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

pragma solidity ^0.8.14;

import "./D3MPoolBase.t.sol";
import "../../pools/D3M4626TypePool.sol";
import {ERC20, ERC4626 as ERC4626Abstract} from "solmate/tokens/ERC4626.sol";

contract ERC4626 is ERC4626Abstract {
constructor(
ERC20 _asset,
string memory _name,
string memory _symbol
) ERC4626Abstract(_asset, _name, _symbol) {}

function totalAssets() public view virtual override returns (uint256) {
return asset.balanceOf(address(this));
}
}

contract D3M4626TypePoolTest is D3MPoolBaseTest {

D3M4626TypePool pool;
ERC4626 vault;

function setUp() public {
baseInit("D3M4626TypePool");

vault = new ERC4626(ERC20(address(dai)), "dai vault", "DV");

setPoolContract(pool = new D3M4626TypePool(address(dai), address(vault), address(vat)));

pool.file("hub", address(hub));

dai.approve(address(vault), type(uint256).max);
}

function invariant_dai_value() public {
assertEq(address(pool.dai()), address(dai));
}

function invariant_vault_value() public {
assertEq(address(pool.vault()), address(vault));
}

function invariant_vat_value() public {
assertEq(address(pool.vat()), address(vat));
}

function test_cannot_file_hub_no_auth() public {
pool.deny(address(this));
vm.expectRevert("D3M4626TypePool/not-authorized");
pool.file("hub", address(123));
}

function test_deposit_calls_vault_deposit() public {
deal(address(dai), address(pool), 1);
vm.prank(address(hub)); pool.deposit(1);

assertEq(pool.assetBalance(), 1);
assertEq(dai.balanceOf(address(pool)), 0);
}

function test_withdraw_calls_vault_withdraw() public {
deal(address(dai), address(pool), 1);
vm.prank(address(hub)); pool.deposit(1);

vm.prank(address(hub)); pool.withdraw(1);

assertEq(pool.assetBalance(), 0);
assertEq(dai.balanceOf(address(hub)), 1);
}

function test_withdraw_calls_vault_withdraw_vat_caged() public {
deal(address(dai), address(pool), 1);
vm.prank(address(hub)); pool.deposit(1);

vat.cage();
vm.prank(address(hub)); pool.withdraw(1);

assertEq(pool.assetBalance(), 0);
assertEq(dai.balanceOf(address(hub)), 1);
}

function test_redeemable_returns_adai() public {
assertEq(pool.redeemable(), address(vault));
}

function test_exit_adai() public {
deal(address(dai), address(this), 1e18);
vault.deposit(1e18, address(this));
vault.transfer(address(pool), vault.balanceOf(address(this)));
assertEq(vault.balanceOf(address(this)), 0);
assertEq(vault.balanceOf(address(pool)), 1e18);

vm.prank(address(hub)); pool.exit(address(this), 1e18);

assertEq(vault.balanceOf(address(this)), 0.01e18);
assertEq(vault.balanceOf(address(pool)), 0.99e18);
}

function test_quit_moves_balance() public {
deal(address(dai), address(this), 1e18);
vault.deposit(1e18, address(this));
vault.transfer(address(pool), vault.balanceOf(address(this)));
assertEq(vault.balanceOf(address(this)), 0);
assertEq(vault.balanceOf(address(pool)), 1e18);

pool.quit(address(this));

assertEq(vault.balanceOf(address(this)), 1e18);
assertEq(vault.balanceOf(address(pool)), 0);
}

function test_assetBalance_gets_adai_balanceOf_pool() public {
deal(address(dai), address(this), 1e18);
vault.deposit(1e18, address(this));
assertEq(pool.assetBalance(), 0);
assertEq(vault.balanceOf(address(pool)), 0);

vault.transfer(address(pool), 1e18);

assertEq(pool.assetBalance(), 1e18);
assertEq(vault.balanceOf(address(pool)), 1e18);
}

function test_maxWithdraw_gets_available_assets_assetBal() public {
deal(address(dai), address(this), 1e18);
dai.transfer(address(vault), 1e18);
assertEq(dai.balanceOf(address(vault)), 1e18);
assertEq(vault.balanceOf(address(pool)), 0);

assertEq(pool.maxWithdraw(), 0);
}

function test_maxWithdraw_gets_available_assets_daiBal() public {
deal(address(dai), address(this), 1e18);
vault.deposit(1e18, address(this));
vault.transfer(address(pool), 1e18);
assertEq(dai.balanceOf(address(vault)), 1e18);
assertEq(vault.balanceOf(address(pool)), 1e18);

assertEq(pool.maxWithdraw(), 1e18);
}

function test_maxDeposit_returns_max_uint() public {
assertEq(pool.maxDeposit(), type(uint256).max);
}
}
3 changes: 0 additions & 3 deletions src/tests/pools/D3MPoolBase.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -110,19 +110,16 @@ abstract contract D3MPoolBaseTest is DssTest {
end.setArt(100 ether);

assertEq(redeemableToken.balanceOf(TEST_ADDRESS), 0);
assertEq(ExitLike(address(pool)).exited(), 0);

vm.prank(address(hub)); pool.exit(TEST_ADDRESS, 10 ether); // Exit 10%

assertApproxEqAbs(redeemableToken.balanceOf(TEST_ADDRESS), initialBalance * 10 / 100, 10);
assertApproxEqAbs(redeemableToken.balanceOf(address(pool)), initialBalance * 90 / 100, 10);
assertEq(ExitLike(address(pool)).exited(), 10 ether);

vm.prank(address(hub)); pool.exit(TEST_ADDRESS, 20 ether); // Exit another 20%

assertApproxEqAbs(redeemableToken.balanceOf(TEST_ADDRESS), initialBalance * 30 / 100, 10);
assertApproxEqAbs(redeemableToken.balanceOf(address(pool)), initialBalance * 70 / 100, 10);
assertEq(ExitLike(address(pool)).exited(), 30 ether);
}

}

0 comments on commit b1cb6a2

Please sign in to comment.