Skip to content

Commit

Permalink
Merge pull request #101 from DyadStablecoin/feat/apxeth-vault
Browse files Browse the repository at this point in the history
Add apxETH vault
  • Loading branch information
shafu0x authored Sep 19, 2024
2 parents 138febb + 6bda511 commit 9130df8
Show file tree
Hide file tree
Showing 6 changed files with 226 additions and 0 deletions.
12 changes: 12 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,18 @@ deployWeETH:
-i 1 \
-vvvv

deployApxETH:
forge clean
forge script script/deploy/Deploy.apxETH.Vault.s.sol \
--rpc-url $(MAINNET_RPC) \
--sender 0xaf17f6E53f6CC15AD685cF548A0d48d38462B23e \
--broadcast \
--via-ir \
--verify \
--optimize \
-i 1 \
-vvvv

deployUSDe:
forge clean
forge script script/deploy/Deploy.sUSDeVault.Mainnet.s.sol \
Expand Down
1 change: 1 addition & 0 deletions lib/v3-periphery
Submodule v3-periphery added at 80f26c
31 changes: 31 additions & 0 deletions script/deploy/Deploy.apxETH.Vault.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Script.sol";

import {Parameters} from "../../src/params/Parameters.sol";
import {VaultWeETH} from "../../src/core/Vault.weETH.sol";
import {VaultApxETH} from "../../src/core/Vault.apxETH.sol";
import {VaultManager} from "../../src/core/VaultManager.sol";
import {IAggregatorV3} from "../../src/interfaces/IAggregatorV3.sol";
import {IVault} from "../../src/interfaces/IVault.sol";
import {DNft} from "../../src/core/DNft.sol";

import {ERC20} from "@solmate/src/tokens/ERC20.sol";

contract DeployVault is Script, Parameters {
function run() public {
vm.startBroadcast(); // ----------------------

new VaultApxETH(
MAINNET_FEE_RECIPIENT,
VaultManager (MAINNET_V2_VAULT_MANAGER),
ERC20 (MAINNET_APXETH),
IAggregatorV3(MAINNET_APXETH_ORACLE),
IVault(MAINNET_V2_WETH_VAULT),
DNft(MAINNET_DNFT)
);

vm.stopBroadcast(); // ----------------------------
}
}
107 changes: 107 additions & 0 deletions src/core/Vault.apxETH.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IVaultManager} from "../interfaces/IVaultManager.sol";
import {IVault} from "../interfaces/IVault.sol";
import {IAggregatorV3} from "../interfaces/IAggregatorV3.sol";
import {DNft} from "./DNft.sol";

import {Owned} from "@solmate/src/auth/Owned.sol";
import {SafeCast} from "@openzeppelin/contracts/utils/math/SafeCast.sol";
import {SafeTransferLib} from "@solmate/src/utils/SafeTransferLib.sol";
import {FixedPointMathLib} from "@solmate/src/utils/FixedPointMathLib.sol";
import {ERC20} from "@solmate/src/tokens/ERC20.sol";

contract VaultApxETH is Owned, IVault {
using SafeTransferLib for ERC20;
using SafeCast for int;
using FixedPointMathLib for uint;

error ExceedsDepositCap();

uint public constant STALE_DATA_TIMEOUT = 36 hours;

IVaultManager public immutable vaultManager;
ERC20 public immutable asset;
IAggregatorV3 public immutable oracle;
IVault public immutable wethVault;
DNft public immutable dNft;

uint256 public depositCap;

mapping(uint => uint) public id2asset;

modifier onlyVaultManager() {
if (msg.sender != address(vaultManager)) revert NotVaultManager();
_;
}

constructor(
address owner,
IVaultManager _vaultManager,
ERC20 _asset,
IAggregatorV3 _oracle,
IVault _wethVault,
DNft _dNft
) Owned(owner) {
vaultManager = _vaultManager;
asset = _asset;
oracle = _oracle;
wethVault = _wethVault;
dNft = _dNft;

depositCap = type(uint256).max;
}

function deposit(uint id, uint amount) external onlyVaultManager {
if (asset.balanceOf(address(this)) > depositCap) {
revert ExceedsDepositCap();
}
id2asset[id] += amount;
emit Deposit(id, amount);
}

function withdraw(
uint id,
address to,
uint amount
) external onlyVaultManager {
id2asset[id] -= amount;
asset.safeTransfer(to, amount);
emit Withdraw(id, to, amount);
}

function move(uint from, uint to, uint amount) external onlyVaultManager {
id2asset[from] -= amount;
id2asset[to] += amount;
emit Move(from, to, amount);
}

function getUsdValue(uint id) external view returns (uint) {
return
(id2asset[id] * assetPrice() * 1e18) /
10 ** oracle.decimals() /
10 ** asset.decimals();
}

function balanceOf(
address account
) external view returns (uint256 assetBalance) {
uint256 dnftBalance = dNft.balanceOf(account);
for (uint256 i; i < dnftBalance; ++i) {
uint256 id = dNft.tokenOfOwnerByIndex(account, i);
assetBalance += id2asset[id];
}
}

function setDepositCap(uint _depositCap) external onlyOwner {
depositCap = _depositCap;
}

function assetPrice() public view returns (uint) {
(, int256 answer, , uint256 updatedAt, ) = oracle.latestRoundData();
if (block.timestamp > updatedAt + STALE_DATA_TIMEOUT)
revert StaleData();
return answer.toUint256().mulDivDown(wethVault.assetPrice(), 10 ** wethVault.oracle().decimals());
}
}
3 changes: 3 additions & 0 deletions src/params/Parameters.sol
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ contract Parameters {
address MAINNET_V2_WEETH_VAULT = 0x5B74DD13D4136443A7831fB7AD139BA123B5071B;
address MAINNET_V2_VAULT_LICENSER = 0xFe81952A0a2c6ab603ef1B3cC69E1B6Bffa92697;
address MAINNET_V2_XP = 0xeF443646E52d1C28bd757F570D18F4Db30dB70F4;
address MAINNET_APXETH = 0x9Ba021B0a9b958B5E75cE9f6dff97C7eE52cb3E6;
address MAINNET_APXETH_ORACLE = 0x19219BC90F48DeE4d5cF202E09c438FAacFd8Bea;
address MAINNET_APXETH_VAULT = 0xB58d87dD30a67823acC4b9Fa533F464CdEdA737E;

// ---------------- Sepolia ----------------
address SEPOLIA_OWNER = 0xEd6715D2172BFd50C2DBF608615c2AB497904803;
Expand Down
72 changes: 72 additions & 0 deletions test/Vault.apxETH.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import {DNft} from "../src/core/DNft.sol";
import {Parameters} from "../src/params/Parameters.sol";
import {VaultApxETH} from "../src/core/Vault.apxETH.sol";
import {VaultManager} from "../src/core/VaultManager.sol";
import {ERC20} from "@solmate/src/tokens/ERC20.sol";
import {IAggregatorV3} from "../src/interfaces/IAggregatorV3.sol";
import {IVault} from "../src/interfaces/IVault.sol";

contract VaultApxETHTest is Test, Parameters {
VaultApxETH vault;

uint256 depositCap = 100 ether;

function setUp() public {
vault = new VaultApxETH(
address(MAINNET_FEE_RECIPIENT), // owner
VaultManager(MAINNET_V2_VAULT_MANAGER),
ERC20(MAINNET_APXETH),
IAggregatorV3(MAINNET_APXETH_ORACLE),
IVault(MAINNET_V2_WETH_VAULT),
DNft(MAINNET_DNFT)
);

vm.prank(MAINNET_FEE_RECIPIENT);
vault.setDepositCap(depositCap);
}

function test_assetPrice() public view {
uint256 price = vault.assetPrice();
console.log("price: %s", price);
}

function testFuzz_setDepositCapReverts(address sender) public {
vm.assume(sender != address(MAINNET_FEE_RECIPIENT));
uint256 cap = 1000e18;
vm.prank(sender);
vm.expectRevert("UNAUTHORIZED");
vault.setDepositCap(cap);
}

function testFuzz_setDepositCapAsOwner(uint256 cap) public {
vm.prank(MAINNET_FEE_RECIPIENT);
vault.setDepositCap(cap);
uint256 newCap = vault.depositCap();
assertEq(newCap, cap);
}

function testFuzzDepositCap(
uint256 currentDeposit,
uint256 depositAmount
) public {
vm.assume(depositAmount < type(uint128).max);
vm.assume(currentDeposit < depositCap);

vm.mockCall(
MAINNET_APXETH,
abi.encodeWithSignature("balanceOf(address)", address(vault)),
abi.encode(currentDeposit + depositAmount)
);

vm.prank(MAINNET_V2_VAULT_MANAGER);
if (currentDeposit + depositAmount > depositCap) {
vm.expectRevert(VaultApxETH.ExceedsDepositCap.selector);
}
vault.deposit(1, depositAmount);
}
}

0 comments on commit 9130df8

Please sign in to comment.