Skip to content

Commit

Permalink
feat: slightly rework phantom token withdrawals
Browse files Browse the repository at this point in the history
Now phantom tokens only provide basic info like their target contract and underlying.
The withdrawals are fully handled by adapters which have a unified interface for that.
  • Loading branch information
lekhovitsky committed Jul 8, 2024
1 parent da748ea commit 876b4d9
Show file tree
Hide file tree
Showing 12 changed files with 66 additions and 50 deletions.
12 changes: 12 additions & 0 deletions contracts/adapters/convex/ConvexV1_BaseRewardPool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 //
// ------ //
Expand Down
19 changes: 14 additions & 5 deletions contracts/adapters/zircuit/ZircuitPoolAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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 //
// ---- //
Expand Down Expand Up @@ -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);

Expand Down
16 changes: 3 additions & 13 deletions contracts/helpers/convex/ConvexV1_StakedPositionToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}
}
16 changes: 3 additions & 13 deletions contracts/helpers/zircuit/ZircuitPhantomToken.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);
}
}
16 changes: 8 additions & 8 deletions contracts/interfaces/IPhantomToken.sol
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 2 additions & 1 deletion contracts/interfaces/zircuit/IZircuitPoolAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,9 @@ contract ConvexV1BaseRewardPoolAdapterUnitTest is AdapterUnitTestHelper {
_revertsOnNonFacadeCaller();
adapter.withdrawDiff(0, false);

_revertsOnNonFacadeCaller();
adapter.withdrawPhantomToken(address(0), 0);

_revertsOnNonFacadeCaller();
adapter.withdrawAndUnwrap(0, false);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ contract ZircuitPoolAdapterUnitTest is AdapterUnitTestHelper {
_revertsOnNonFacadeCaller();
adapter.withdrawDiff(address(1), 1);

_revertsOnNonFacadeCaller();
adapter.withdrawPhantomToken(address(1), 1);

_revertsOnNonConfiguratorCaller();
adapter.updateSupportedUnderlyings();
}
Expand All @@ -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
Expand Down

0 comments on commit 876b4d9

Please sign in to comment.