diff --git a/contracts/adapters/convex/ConvexV1_BaseRewardPool.sol b/contracts/adapters/convex/ConvexV1_BaseRewardPool.sol index 3d393d8f..d6636be1 100644 --- a/contracts/adapters/convex/ConvexV1_BaseRewardPool.sol +++ b/contracts/adapters/convex/ConvexV1_BaseRewardPool.sol @@ -213,6 +213,18 @@ contract ConvexV1BaseRewardPoolAdapter is AbstractAdapter, IConvexV1BaseRewardPo return false; } + /// @notice Withdraws phantom token for its underlying + /// @dev `token` parameter is ignored as adapter only handles one token + function withdrawPhantomToken(address, uint256 amount) + external + override + creditFacadeOnly // U:[CVX1R-3] + returns (bool) + { + _execute(abi.encodeCall(IBaseRewardPool.withdraw, (amount, false))); + return false; + } + // ------ // // UNWRAP // // ------ // diff --git a/contracts/adapters/zircuit/ZircuitPoolAdapter.sol b/contracts/adapters/zircuit/ZircuitPoolAdapter.sol index 4261b639..b6cdc3e5 100644 --- a/contracts/adapters/zircuit/ZircuitPoolAdapter.sol +++ b/contracts/adapters/zircuit/ZircuitPoolAdapter.sol @@ -7,7 +7,6 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {EnumerableSet} from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; import {AbstractAdapter} from "../AbstractAdapter.sol"; -import {PhantomTokenType} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; import {ZircuitPhantomToken} from "../../helpers/zircuit/ZircuitPhantomToken.sol"; import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol"; @@ -114,6 +113,18 @@ contract ZircuitPoolAdapter is AbstractAdapter, IZircuitPoolAdapter { return false; } + /// @notice Withdraws phantom token for its underlying + function withdrawPhantomToken(address token, uint256 amount) + external + override + creditFacadeOnly // U: [ZIR-1] + supportedUnderlyingsOnly(token) // U: [ZIR-1A] + returns (bool) + { + _execute(abi.encodeCall(IZircuitPool.withdraw, (token, amount))); + return false; + } + // ---- // // DATA // // ---- // @@ -149,10 +160,8 @@ contract ZircuitPoolAdapter is AbstractAdapter, IZircuitPoolAdapter { uint256 len = cm.collateralTokensCount(); for (uint256 i = 0; i < len; ++i) { address token = cm.getTokenByMask(1 << i); - try IPhantomToken(token)._gearboxPhantomTokenType() returns (PhantomTokenType ptType) { - if (ptType == PhantomTokenType.ZIRCUIT_PHANTOM_TOKEN) { - address depositedToken = ZircuitPhantomToken(token).underlying(); - + try IPhantomToken(token).getPhantomTokenInfo() returns (address target, address depositedToken) { + if (target == targetContract) { _getMaskOrRevert(token); _getMaskOrRevert(depositedToken); diff --git a/contracts/helpers/convex/ConvexV1_StakedPositionToken.sol b/contracts/helpers/convex/ConvexV1_StakedPositionToken.sol index ddd17a69..39d62800 100644 --- a/contracts/helpers/convex/ConvexV1_StakedPositionToken.sol +++ b/contracts/helpers/convex/ConvexV1_StakedPositionToken.sol @@ -9,14 +9,11 @@ import {IBooster} from "../../integrations/convex/IBooster.sol"; import {PhantomERC20} from "../PhantomERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IPhantomToken} from "../../interfaces/IPhantomToken.sol"; -import {PhantomTokenType} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; /// @title Convex staked position token /// @notice Phantom ERC-20 token that represents the balance of the staking position in Convex pools contract ConvexStakedPositionToken is PhantomERC20, IPhantomToken { - PhantomTokenType public constant override _gearboxPhantomTokenType = PhantomTokenType.CONVEX_PHANTOM_TOKEN; - address public immutable pool; address public immutable booster; address public immutable curveToken; @@ -48,15 +45,8 @@ contract ConvexStakedPositionToken is PhantomERC20, IPhantomToken { return IERC20(pool).balanceOf(account); } - /// @notice Returns the calls required to unwrap a Convex position into Curve LP before withdrawing from Gearbox - function getWithdrawalMultiCall(address, uint256 amount) - external - view - returns (address tokenOut, uint256 amountOut, address targetContract, bytes memory callData) - { - tokenOut = curveToken; - amountOut = amount; - targetContract = pool; - callData = abi.encodeCall(IBaseRewardPool.withdrawAndUnwrap, (amount, false)); + /// @notice Returns phantom token's target contract and underlying + function getPhantomTokenInfo() external view override returns (address, address) { + return (pool, underlying); } } diff --git a/contracts/helpers/zircuit/ZircuitPhantomToken.sol b/contracts/helpers/zircuit/ZircuitPhantomToken.sol index 225ff588..ef20718a 100644 --- a/contracts/helpers/zircuit/ZircuitPhantomToken.sol +++ b/contracts/helpers/zircuit/ZircuitPhantomToken.sol @@ -8,14 +8,11 @@ import {IZircuitPool} from "../../integrations/zircuit/IZircuitPool.sol"; import {PhantomERC20} from "../PhantomERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {IPhantomToken} from "../../interfaces/IPhantomToken.sol"; -import {PhantomTokenType} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; /// @title Zircuit staked position token /// @notice Phantom ERC-20 token that represents the balance of the staking position in a Zircuit pool contract ZircuitPhantomToken is PhantomERC20, IPhantomToken { - PhantomTokenType public constant override _gearboxPhantomTokenType = PhantomTokenType.ZIRCUIT_PHANTOM_TOKEN; - address public immutable zircuitPool; /// @notice Constructor @@ -43,15 +40,8 @@ contract ZircuitPhantomToken is PhantomERC20, IPhantomToken { return IERC20(underlying).balanceOf(zircuitPool); } - /// @notice Returns the calls required to unwrap a Zircuit position into underlying before withdrawing from Gearbox - function getWithdrawalMultiCall(address, uint256 amount) - external - view - returns (address tokenOut, uint256 amountOut, address targetContract, bytes memory callData) - { - tokenOut = underlying; - amountOut = amount; - targetContract = zircuitPool; - callData = abi.encodeCall(IZircuitPool.withdraw, (underlying, amount)); + /// @notice Returns phantom token's target contract and underlying + function getPhantomTokenInfo() external view override returns (address, address) { + return (zircuitPool, underlying); } } diff --git a/contracts/interfaces/IPhantomToken.sol b/contracts/interfaces/IPhantomToken.sol index 69994204..d8c95ad8 100644 --- a/contracts/interfaces/IPhantomToken.sol +++ b/contracts/interfaces/IPhantomToken.sol @@ -1,14 +1,14 @@ -// SPDX-License-Identifier: UNLICENSED +// SPDX-License-Identifier: MIT // Gearbox Protocol. Generalized leverage for DeFi protocols -// (c) Gearbox Foundation, 2024. +// (c) Gearbox Foundation, 2023. pragma solidity ^0.8.23; -import {PhantomTokenType} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; - interface IPhantomToken { - function _gearboxPhantomTokenType() external view returns (PhantomTokenType); + /// @notice Returns phantom token's target contract and underlying + function getPhantomTokenInfo() external view returns (address targetContract, address underlying); +} - function getWithdrawalMultiCall(address creditAccount, uint256 amount) - external - returns (address tokenOut, uint256 amountOut, address targetContract, bytes memory callData); +interface IPhantomTokenWithdrawer { + /// @notice Withdraws phantom token for its underlying + function withdrawPhantomToken(address token, uint256 amount) external returns (bool useSafePrices); } diff --git a/contracts/interfaces/convex/IConvexV1BaseRewardPoolAdapter.sol b/contracts/interfaces/convex/IConvexV1BaseRewardPoolAdapter.sol index 4f72c114..7bd658bc 100644 --- a/contracts/interfaces/convex/IConvexV1BaseRewardPoolAdapter.sol +++ b/contracts/interfaces/convex/IConvexV1BaseRewardPoolAdapter.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.23; import {IAdapter} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IAdapter.sol"; +import {IPhantomTokenWithdrawer} from "../IPhantomToken.sol"; /// @title Convex V1 BaseRewardPool adapter interface -interface IConvexV1BaseRewardPoolAdapter is IAdapter { +interface IConvexV1BaseRewardPoolAdapter is IAdapter, IPhantomTokenWithdrawer { function curveLPtoken() external view returns (address); function stakingToken() external view returns (address); diff --git a/contracts/interfaces/zircuit/IZircuitPoolAdapter.sol b/contracts/interfaces/zircuit/IZircuitPoolAdapter.sol index 22cf7cba..33b95545 100644 --- a/contracts/interfaces/zircuit/IZircuitPoolAdapter.sol +++ b/contracts/interfaces/zircuit/IZircuitPoolAdapter.sol @@ -4,9 +4,10 @@ pragma solidity ^0.8.23; import {IAdapter} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IAdapter.sol"; +import {IPhantomTokenWithdrawer} from "../IPhantomToken.sol"; /// @title Zircuit pool adapter interface -interface IZircuitPoolAdapter is IAdapter { +interface IZircuitPoolAdapter is IAdapter, IPhantomTokenWithdrawer { /// @notice Emitted when a supported underlying / phantom token pair is added to adapter event AddSupportedUnderlying(address indexed token, address indexed phantomToken); diff --git a/contracts/test/live/adapters/balancer/Live_BalancerV2EquivalenceTest.sol b/contracts/test/live/adapters/balancer/Live_BalancerV2EquivalenceTest.sol index 51ffc66a..53e004cf 100644 --- a/contracts/test/live/adapters/balancer/Live_BalancerV2EquivalenceTest.sol +++ b/contracts/test/live/adapters/balancer/Live_BalancerV2EquivalenceTest.sol @@ -3,6 +3,7 @@ // (c) Gearbox Foundation, 2023. pragma solidity ^0.8.23; +import {Address} from "@openzeppelin/contracts/utils/Address.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import {ICreditFacadeV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol"; @@ -54,6 +55,7 @@ struct BalancerPoolParams { contract Live_BalancerV2EquivalenceTest is LiveTestHelper { using BalancerV2_Calls for BalancerV2_Multicaller; using AddressList for address[]; + using Address for address; BalanceComparator comparator; @@ -209,7 +211,7 @@ contract Live_BalancerV2EquivalenceTest is LiveTestHelper { creditAccount, MultiCallBuilder.build(MultiCall({target: balancerVaultAddress, callData: callData})) ); } else { - address(balancerVaultAddress).call(callData); + address(balancerVaultAddress).functionCall(callData); } comparator.takeSnapshot("after_batchSwap", creditAccount); diff --git a/contracts/test/live/adapters/convex/Live_ConvexEquivalenceTest.t.sol b/contracts/test/live/adapters/convex/Live_ConvexEquivalenceTest.t.sol index 3e434afd..b091a398 100644 --- a/contracts/test/live/adapters/convex/Live_ConvexEquivalenceTest.t.sol +++ b/contracts/test/live/adapters/convex/Live_ConvexEquivalenceTest.t.sol @@ -13,7 +13,6 @@ import {IBooster} from "../../../../integrations/convex/IBooster.sol"; import {IConvexV1BaseRewardPoolAdapter} from "../../../../interfaces/convex/IConvexV1BaseRewardPoolAdapter.sol"; import {ConvexStakedPositionToken} from "../../../../helpers/convex/ConvexV1_StakedPositionToken.sol"; import {IPhantomToken} from "../../../../interfaces/IPhantomToken.sol"; -import {PhantomTokenType} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; import {PriceFeedParams} from "@gearbox-protocol/core-v3/contracts/interfaces/IPriceOracleV3.sol"; import {IPriceFeed} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IPriceFeed.sol"; @@ -297,8 +296,9 @@ contract Live_ConvexEquivalenceTest is LiveTestHelper { address token = creditManager.getTokenByMask(1 << i); - try IPhantomToken(token)._gearboxPhantomTokenType() returns (PhantomTokenType ptType) { - if (ptType != PhantomTokenType.CONVEX_PHANTOM_TOKEN) continue; + try IPhantomToken(token).getPhantomTokenInfo() returns (address target, address) { + address adapter = creditManager.contractToAdapter(target); + if (IAdapter(adapter).contractType() != "AD_CONVEX_V1_BASE_REWARD_POOL") continue; } catch { continue; } diff --git a/contracts/test/live/adapters/zircuit/Live_ZircuitEquivalenceTest.t.sol b/contracts/test/live/adapters/zircuit/Live_ZircuitEquivalenceTest.t.sol index 6780397c..ef770a4b 100644 --- a/contracts/test/live/adapters/zircuit/Live_ZircuitEquivalenceTest.t.sol +++ b/contracts/test/live/adapters/zircuit/Live_ZircuitEquivalenceTest.t.sol @@ -11,7 +11,6 @@ import {IZircuitPool} from "../../../../integrations/zircuit/IZircuitPool.sol"; import {IZircuitPoolAdapter} from "../../../../interfaces/zircuit/IZircuitPoolAdapter.sol"; import {IPhantomToken} from "../../../../interfaces/IPhantomToken.sol"; -import {PhantomTokenType} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol"; import {ZircuitPhantomToken} from "../../../../helpers/zircuit/ZircuitPhantomToken.sol"; import {PriceFeedParams} from "@gearbox-protocol/core-v3/contracts/interfaces/IPriceOracleV3.sol"; import {IPriceFeed} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IPriceFeed.sol"; @@ -149,8 +148,9 @@ contract Live_ZircuitEquivalenceTest is LiveTestHelper { for (uint256 i = 0; i < collateralTokensCount; ++i) { address token = creditManager.getTokenByMask(1 << i); - try IPhantomToken(token)._gearboxPhantomTokenType() returns (PhantomTokenType ptType) { - if (ptType != PhantomTokenType.ZIRCUIT_PHANTOM_TOKEN) continue; + try IPhantomToken(token).getPhantomTokenInfo() returns (address target, address) { + address adapter = creditManager.contractToAdapter(target); + if (IAdapter(adapter).contractType() != "AD_ZIRCUIT_POOL") continue; } catch { continue; } @@ -196,8 +196,9 @@ contract Live_ZircuitEquivalenceTest is LiveTestHelper { address token = creditManager.getTokenByMask(1 << i); - try IPhantomToken(token)._gearboxPhantomTokenType() returns (PhantomTokenType ptType) { - if (ptType != PhantomTokenType.ZIRCUIT_PHANTOM_TOKEN) continue; + try IPhantomToken(token).getPhantomTokenInfo() returns (address target, address) { + address adapter = creditManager.contractToAdapter(target); + if (IAdapter(adapter).contractType() != "AD_ZIRCUIT_POOL") continue; } catch { continue; } diff --git a/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol b/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol index 234fb5e9..4fe77b8e 100644 --- a/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/convex/ConvexV1BaseRewardPoolAdapter.unit.t.sol @@ -128,6 +128,9 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.withdrawDiff(0, false); + _revertsOnNonFacadeCaller(); + adapter.withdrawPhantomToken(address(0), 0); + _revertsOnNonFacadeCaller(); adapter.withdrawAndUnwrap(0, false); diff --git a/contracts/test/unit/adapters/zircuit/ZircuitPoolAdapter.unit.t.sol b/contracts/test/unit/adapters/zircuit/ZircuitPoolAdapter.unit.t.sol index 97112e2b..234207e9 100644 --- a/contracts/test/unit/adapters/zircuit/ZircuitPoolAdapter.unit.t.sol +++ b/contracts/test/unit/adapters/zircuit/ZircuitPoolAdapter.unit.t.sol @@ -44,6 +44,9 @@ contract ZircuitPoolAdapterUnitTest is AdapterUnitTestHelper { _revertsOnNonFacadeCaller(); adapter.withdrawDiff(address(1), 1); + _revertsOnNonFacadeCaller(); + adapter.withdrawPhantomToken(address(1), 1); + _revertsOnNonConfiguratorCaller(); adapter.updateSupportedUnderlyings(); } @@ -65,6 +68,10 @@ contract ZircuitPoolAdapterUnitTest is AdapterUnitTestHelper { vm.expectRevert(IZircuitPoolAdapter.UnsupportedUnderlyingException.selector); vm.prank(creditFacade); adapter.withdrawDiff(address(1), 1); + + vm.expectRevert(IZircuitPoolAdapter.UnsupportedUnderlyingException.selector); + vm.prank(creditFacade); + adapter.withdrawPhantomToken(address(1), 1); } /// @notice U:[ZIR-2]: depositFor works correctly