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
20 changes: 20 additions & 0 deletions script/ghost/DeployGhostMintHelper.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import {GhostMintHelper} from "src/ghost/GhostMintHelper.sol";
import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";

/*
forge script script/ghost/DeployGhostMintHelper.s.sol:DeployGhostMintHelperScript -f $RPC_URL --broadcast --private-key $PRIVATE_KEY \
--json | jq "select(.traces[1][1].arena[3].trace.address!=null) | .traces.[1][1].arena[0].trace.decoded.label,.traces[1][1].arena[3].trace.address"
forge verify-contract -f $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY CONTRACT_ADDRESS
*/
contract DeployGhostMintHelperScript is Script {
function run() public {
vm.startBroadcast();
GhostMintHelper helper = new GhostMintHelper{salt: bytes32(0)}(0xE2A7f267124AC3E4131f27b9159c78C521A44F3c);
vm.stopBroadcast();
console2.log("Ghost mint helper deployed at", address(helper));
}
}
20 changes: 20 additions & 0 deletions script/ghost/DeployGhostRedeemHelper.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import {GhostRedeemHelper} from "src/ghost/GhostRedeemHelper.sol";
import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/console2.sol";

/*
forge script script/ghost/DeployGhostRedeemHelper.s.sol:DeployGhostRedeemHelperScript -f $RPC_URL --broadcast --private-key $PRIVATE_KEY \
--json | jq "select(.traces[1][1].arena[3].trace.address!=null) | .traces.[1][1].arena[0].trace.decoded.label,.traces[1][1].arena[3].trace.address"
forge verify-contract -f $RPC_URL --etherscan-api-key $ETHERSCAN_API_KEY CONTRACT_ADDRESS
*/
contract DeployGhostRedeemHelperScript is Script {
function run() public {
vm.startBroadcast();
GhostRedeemHelper helper = new GhostRedeemHelper{salt: bytes32(0)}(0xE2A7f267124AC3E4131f27b9159c78C521A44F3c);
vm.stopBroadcast();
console2.log("Ghost redeem helper deployed at", address(helper));
}
}
1 change: 0 additions & 1 deletion src/CommonFlashLoanHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {ILowLevelVault} from "src/interfaces/ILowLevelVault.sol";
import {IWETH} from "src/interfaces/tokens/IWETH.sol";
import {IstEth} from "src/interfaces/tokens/IstEth.sol";
import {IwstEth} from "src/interfaces/tokens/IwstEth.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IFlashLoanHelperErrors} from "src/interfaces/IFlashLoanHelperErrors.sol";
import {IFlashLoanHelperEvents} from "src/interfaces/IFlashLoanHelperEvents.sol";
import {IMoprho} from "src/interfaces/IMoprho.sol";
Expand Down
49 changes: 49 additions & 0 deletions src/ghost/GhostMintHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import {ILowLevelVault} from "src/interfaces/ILowLevelVault.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

// forge-lint: disable-start
contract GhostMintHelper is Ownable {
using SafeERC20 for IERC20;
using SafeERC20 for ILowLevelVault;

error InvalidRebalanceMode();

ILowLevelVault public immutable GHOST_VAULT;

constructor(address _ltvVault) Ownable(msg.sender) {
GHOST_VAULT = ILowLevelVault(_ltvVault);
}

function previewMintSharesWithFlashLoanCollateral(uint256 sharesToMint) public view returns (uint256) {
(int256 collateral, int256 borrow) = GHOST_VAULT.previewLowLevelRebalanceShares(int256(sharesToMint));

require(collateral > 0 && borrow > 0 && collateral >= borrow, InvalidRebalanceMode());

return uint256(collateral - borrow);
}

function mintSharesWithFlashLoanCollateral(uint256 sharesToMint) external returns (uint256) {
(int256 collateral, int256 borrow) = GHOST_VAULT.previewLowLevelRebalanceShares(int256(sharesToMint));
require(collateral > 0 && borrow > 0 && collateral >= borrow, InvalidRebalanceMode());

address collateralAsset = GHOST_VAULT.assetCollateral();

IERC20(collateralAsset).safeTransferFrom(msg.sender, address(this), uint256(collateral - borrow));

IERC20(collateralAsset).forceApprove(address(GHOST_VAULT), uint256(collateral));

GHOST_VAULT.executeLowLevelRebalanceShares(int256(sharesToMint));
GHOST_VAULT.safeTransfer(msg.sender, sharesToMint);

return uint256(collateral - borrow);
}

function sweep(address token) external onlyOwner {
IERC20(token).safeTransfer(msg.sender, IERC20(token).balanceOf(address(this)));
}
}
// forge-lint: disable-end
49 changes: 49 additions & 0 deletions src/ghost/GhostRedeemHelper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.28;

import {ILowLevelVault} from "src/interfaces/ILowLevelVault.sol";
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";

// forge-lint: disable-start
contract GhostRedeemHelper is Ownable {
using SafeERC20 for IERC20;
using SafeERC20 for ILowLevelVault;

error InvalidRebalanceMode();

ILowLevelVault public immutable GHOST_VAULT;

constructor(address _ltvVault) Ownable(msg.sender) {
GHOST_VAULT = ILowLevelVault(_ltvVault);
}

function previewRedeemSharesWithCurveAndFlashLoanBorrow(uint256 sharesToRedeem) public view returns (uint256) {
(int256 collateral, int256 borrow) = GHOST_VAULT.previewLowLevelRebalanceShares(-int256(sharesToRedeem));

require(collateral < 0 && borrow < 0 && collateral <= borrow, InvalidRebalanceMode());

return uint256(borrow - collateral);
}

function redeemSharesWithCurveAndFlashLoanBorrow(uint256 sharesToRedeem) external returns (uint256) {
(int256 collateral, int256 borrow) = GHOST_VAULT.previewLowLevelRebalanceShares(-int256(sharesToRedeem));

require(collateral < 0 && borrow < 0 && collateral <= borrow, InvalidRebalanceMode());

uint256 borrowToReturn = uint256(borrow - collateral);
address borrowAsset = GHOST_VAULT.asset();

GHOST_VAULT.safeTransferFrom(msg.sender, address(this), sharesToRedeem);
IERC20(borrowAsset).forceApprove(address(GHOST_VAULT), uint256(-borrow));
GHOST_VAULT.executeLowLevelRebalanceShares(-int256(sharesToRedeem));

IERC20(borrowAsset).safeTransfer(msg.sender, borrowToReturn);
return uint256(borrow - collateral);
}

function sweep(address token) external onlyOwner {
IERC20(token).safeTransfer(msg.sender, IERC20(token).balanceOf(address(this)));
}
}
// forge-lint: disable-end
3 changes: 3 additions & 0 deletions src/interfaces/ILowLevelVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ interface ILowLevelVault is IERC20 {
external
returns (int256 deltaCollateral, int256 deltaBorrow);

function asset() external view returns (address);
function assetCollateral() external view returns (address);

function isWhitelistActivated() external view returns (bool);
function whitelistRegistry() external view returns (address);
}
8 changes: 8 additions & 0 deletions test/mocks/MockLowLevelVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ contract MockLowLevelVault is ILowLevelVault {
_whitelistRegistry = new MockWhitelistRegistry();
}

function asset() external view returns (address) {
return address(borrowToken);
}

function assetCollateral() external view returns (address) {
return address(collateralToken);
}

function previewLowLevelRebalanceShares(int256 deltaShares)
public
view
Expand Down
2 changes: 0 additions & 2 deletions test/safe_4626/Mint.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ contract Safe4626HelperTest is Test {
// safeTransferFrom - no money (insufficient balance)
function test_safeMint_RevertWhen_InsufficientBalance() public {
uint256 shares = 100 ether;
uint256 expectedAssets = vault.previewMint(shares); // e.g., 100 ether
uint256 maxAssetsIn = type(uint256).max;

// User has less than expected assets
Expand All @@ -58,7 +57,6 @@ contract Safe4626HelperTest is Test {
// safeTransferFrom - insufficient allowance
function test_safeMint_RevertWhen_InsufficientAllowance() public {
uint256 shares = 100 ether;
uint256 expectedAssets = vault.previewMint(shares); // e.g., 100 ether
uint256 maxAssetsIn = type(uint256).max;

deal(address(asset), user, 1000 ether); // Has enough balance
Expand Down
1 change: 0 additions & 1 deletion test/safe_4626/Redeem.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ contract Safe4626HelperTest is Test {
// no approve for vault
function test_safeRedeem_RevertWhen_InsufficientAllowance() public {
uint256 shares = 100 ether;
uint256 expectedAssets = vault.previewRedeem(shares); // e.g., 100 ether
uint256 minAssetsOut = 0;

// User has shares but hasn't approved the helper
Expand Down
1 change: 0 additions & 1 deletion test/safe_4626/Withdraw.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ contract Safe4626HelperTest is Test {
// no approve for vault
function test_safeWithdraw_RevertWhen_InsufficientAllowance() public {
uint256 assets = 100 ether;
uint256 expectedShares = vault.previewWithdraw(assets); // e.g., 100 ether
uint256 maxSharesOut = type(uint256).max;

// User has shares but hasn't approved the helper
Expand Down
2 changes: 0 additions & 2 deletions test/safe_4626_collateral/MintCollateral.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,6 @@ contract Safe4626CollateralHelperTest is Test {
// safeTransferFrom - no money (insufficient balance)
function test_safeMintCollateral_RevertWhen_InsufficientBalance() public {
uint256 shares = 100 ether;
uint256 expectedAssets = vault.previewMintCollateral(shares); // e.g., 100 ether
uint256 maxAssetsIn = type(uint256).max;

// User has less than expected assets
Expand All @@ -58,7 +57,6 @@ contract Safe4626CollateralHelperTest is Test {
// safeTransferFrom - insufficient allowance
function test_safeMintCollateral_RevertWhen_InsufficientAllowance() public {
uint256 shares = 100 ether;
uint256 expectedAssets = vault.previewMintCollateral(shares); // e.g., 100 ether
uint256 maxAssetsIn = type(uint256).max;

deal(address(asset), user, 1000 ether); // Has enough balance
Expand Down
1 change: 0 additions & 1 deletion test/safe_4626_collateral/RedeemCollateral.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ contract Safe4626CollateralHelperTest is Test {
// no approve for vault
function test_safeRedeemCollateral_RevertWhen_InsufficientAllowance() public {
uint256 shares = 100 ether;
uint256 expectedAssets = vault.previewRedeemCollateral(shares); // e.g., 100 ether
uint256 minAssetsOut = 0;

// User has shares but hasn't approved the helper
Expand Down
1 change: 0 additions & 1 deletion test/safe_4626_collateral/WithdrawCollateral.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,6 @@ contract Safe4626CollateralHelperTest is Test {
// no approve for vault
function test_safeWithdrawCollateral_RevertWhen_InsufficientAllowance() public {
uint256 assets = 100 ether;
uint256 expectedShares = vault.previewWithdrawCollateral(assets); // e.g., 100 ether
uint256 maxSharesIn = type(uint256).max;

// User has shares but hasn't approved the helper
Expand Down