Skip to content

Commit

Permalink
feat: sky adapters v3.1 (#144)
Browse files Browse the repository at this point in the history
* feat: adapters for USDS and SKY rewards

* feat: integrations tests + zircuit fix

* fix: add `earned` to `IStakingRewards`
  • Loading branch information
Van0k authored Oct 24, 2024
1 parent 3d50468 commit 7dde79c
Show file tree
Hide file tree
Showing 23 changed files with 1,299 additions and 9 deletions.
9 changes: 9 additions & 0 deletions config_scripts/test_daiMainnetScript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import {
PoolV3CoreConfigurator,
testDaiConfigMainnet,

Check failure on line 3 in config_scripts/test_daiMainnetScript.ts

View workflow job for this annotation

GitHub Actions / release

'"@gearbox-protocol/sdk-gov"' has no exported member named 'testDaiConfigMainnet'. Did you mean 'daiConfigMainnet'?
} from "@gearbox-protocol/sdk-gov";

const poolCfg = PoolV3CoreConfigurator.new(testDaiConfigMainnet);
console.error(poolCfg.toString());

console.log(poolCfg.deployConfig());
99 changes: 99 additions & 0 deletions contracts/adapters/sky/DaiUsdsAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";

import {AbstractAdapter} from "../AbstractAdapter.sol";

import {IDaiUsds} from "../../integrations/sky/IDaiUsds.sol";
import {IDaiUsdsAdapter} from "../../interfaces/sky/IDaiUsdsAdapter.sol";

/// @title DaiUsds Adapter
/// @notice Implements logic for interacting with the DAI / USDS wrapping contract
contract DaiUsdsAdapter is AbstractAdapter, IDaiUsdsAdapter {
bytes32 public constant override contractType = "AD_DAI_USDS_EXCHANGE";
uint256 public constant override version = 3_10;

/// @notice DAI token
address public immutable override dai;

/// @notice USDS token
address public immutable override usds;

/// @notice Constructor
/// @param _creditManager Credit manager address
/// @param _targetContract DAI / USDS exchange contract
constructor(address _creditManager, address _targetContract) AbstractAdapter(_creditManager, _targetContract) {
dai = IDaiUsds(_targetContract).dai();
usds = IDaiUsds(_targetContract).usds();

// We check that DAI and USDS are both valid collaterals
_getMaskOrRevert(dai);
_getMaskOrRevert(usds);
}

/// @notice Swaps given amount of DAI to USDS
/// @param wad Amount of DAI to swap
/// @dev `usr` (recipient) is ignored as it is always the Credit Account
function daiToUsds(address, uint256 wad) external override creditFacadeOnly returns (bool) {
address creditAccount = _creditAccount();
_daiToUsds(creditAccount, wad);
return false;
}

/// @notice Swaps the entire balance of DAI to USDS, except the specified amount
/// @param leftoverAmount Amount of DAI to keep on the account
function daiToUsdsDiff(uint256 leftoverAmount) external override creditFacadeOnly returns (bool) {
address creditAccount = _creditAccount();

uint256 balance = IERC20(dai).balanceOf(creditAccount);
if (balance > leftoverAmount) {
unchecked {
_daiToUsds(creditAccount, balance - leftoverAmount);
}
}
return false;
}

/// @dev Internal implementation for `daiToUsds` and `daiToUsdsDiff`
function _daiToUsds(address creditAccount, uint256 amount) internal {
_executeSwapSafeApprove(dai, abi.encodeCall(IDaiUsds.daiToUsds, (creditAccount, amount)));
}

/// @notice Swaps given amount of USDS to DAI
/// @param wad Amount of USDS to swap
/// @dev `usr` (recipient) is ignored as it is always the Credit Account
function usdsToDai(address, uint256 wad) external override creditFacadeOnly returns (bool) {
address creditAccount = _creditAccount();
_usdsToDai(creditAccount, wad);
return false;
}

/// @notice Swaps the entire balance of USDS to DAI, except the specified amount
/// @param leftoverAmount Amount of USDS to keep on the account
function usdsToDaiDiff(uint256 leftoverAmount) external override creditFacadeOnly returns (bool) {
address creditAccount = _creditAccount();

uint256 balance = IERC20(usds).balanceOf(creditAccount);
if (balance > leftoverAmount) {
unchecked {
_usdsToDai(creditAccount, balance - leftoverAmount);
}
}
return false;
}

/// @dev Internal implementation for `usdsToDai` and `usdsToDaiDiff`
function _usdsToDai(address creditAccount, uint256 amount) internal {
_executeSwapSafeApprove(usds, abi.encodeCall(IDaiUsds.usdsToDai, (creditAccount, amount)));
}

/// @notice Serialized adapter parameters
function serialize() external view returns (bytes memory serializedData) {
serializedData = abi.encode(creditManager, targetContract, dai, usds);
}
}
123 changes: 123 additions & 0 deletions contracts/adapters/sky/StakingRewardsAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

import {ICreditManagerV3} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditManagerV3.sol";
import {AbstractAdapter} from "../AbstractAdapter.sol";
import {BitMask} from "@gearbox-protocol/core-v3/contracts/libraries/BitMask.sol";

import {IStakingRewards} from "../../integrations/sky/IStakingRewards.sol";
import {IStakingRewardsAdapter} from "../../interfaces/sky/IStakingRewardsAdapter.sol";

/// @title Staking Rewards adapter
/// @notice Implements logic for interacting with a generic StakingRewards contract
contract StakingRewardsAdapter is AbstractAdapter, IStakingRewardsAdapter {
using BitMask for uint256;

bytes32 public constant override contractType = "AD_STAKING_REWARDS";
uint256 public constant override version = 3_10;

/// @notice Address of the staking token
address public immutable override stakingToken;

/// @notice Address of the rewards token
address public immutable override rewardsToken;

/// @notice Address of a phantom token representing account's stake in the reward pool
address public immutable override stakedPhantomToken;

/// @notice Constructor
/// @param _creditManager Credit manager address
/// @param _stakingRewards StakingRewards contract address
/// @param _stakedPhantomToken Staked phantom token address
constructor(address _creditManager, address _stakingRewards, address _stakedPhantomToken)
AbstractAdapter(_creditManager, _stakingRewards)
{
stakingToken = IStakingRewards(_stakingRewards).stakingToken();
_getMaskOrRevert(stakingToken);

rewardsToken = IStakingRewards(_stakingRewards).rewardsToken();
_getMaskOrRevert(rewardsToken);

stakedPhantomToken = _stakedPhantomToken;
_getMaskOrRevert(stakedPhantomToken);
}

// ----- //
// STAKE //
// ----- //

/// @notice Stakes tokens in the StakingRewards contract
/// @param amount Amount of tokens to stake
function stake(uint256 amount) external override creditFacadeOnly returns (bool) {
_executeSwapSafeApprove(stakingToken, abi.encodeCall(IStakingRewards.stake, (amount)));
return false;
}

/// @notice Stakes the entire balance of staking token, except the specified amount
/// @param leftoverAmount Amount of staking token to keep on the account
function stakeDiff(uint256 leftoverAmount) external override creditFacadeOnly returns (bool) {
address creditAccount = _creditAccount();

uint256 balance = IERC20(stakingToken).balanceOf(creditAccount);

if (balance > leftoverAmount) {
unchecked {
_executeSwapSafeApprove(stakingToken, abi.encodeCall(IStakingRewards.stake, (balance - leftoverAmount)));
}
}
return false;
}

// ----- //
// CLAIM //
// ----- //

/// @notice Claims rewards on the current position
function getReward() external override creditFacadeOnly returns (bool) {
_execute(abi.encodeCall(IStakingRewards.getReward, ()));
return false;
}

// -------- //
// WITHDRAW //
// -------- //

/// @notice Withdraws staked tokens from the StakingRewards contract
/// @param amount Amount of tokens to withdraw
function withdraw(uint256 amount) external override creditFacadeOnly returns (bool) {
_execute(abi.encodeCall(IStakingRewards.withdraw, (amount)));
return false;
}

/// @notice Withdraws the entire balance of staked tokens, except the specified amount
/// @param leftoverAmount Amount of staked tokens to keep in the contract
function withdrawDiff(uint256 leftoverAmount) external override creditFacadeOnly returns (bool) {
address creditAccount = _creditAccount();

uint256 balance = IERC20(stakedPhantomToken).balanceOf(creditAccount);

if (balance > leftoverAmount) {
unchecked {
_execute(abi.encodeCall(IStakingRewards.withdraw, (balance - leftoverAmount)));
}
}

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 returns (bool) {
_execute(abi.encodeCall(IStakingRewards.withdraw, (amount)));
return false;
}

/// @notice Serialized adapter parameters
function serialize() external view returns (bytes memory serializedData) {
serializedData = abi.encode(creditManager, targetContract, stakingToken, rewardsToken, stakedPhantomToken);
}
}
11 changes: 9 additions & 2 deletions contracts/adapters/zircuit/ZircuitPoolAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ contract ZircuitPoolAdapter is AbstractAdapter, IZircuitPoolAdapter {
/// @notice Map from Zircuit underlying to their respective phantom tokens
mapping(address => address) public tokenToPhantomToken;

/// @notice Map from phantom token to Zircuit underlying
mapping(address => address) public phantomTokenToToken;

/// @notice Constructor
/// @param _creditManager Credit manager address
/// @param _pool Zircuit pool address
Expand Down Expand Up @@ -119,10 +122,13 @@ contract ZircuitPoolAdapter is AbstractAdapter, IZircuitPoolAdapter {
external
override
creditFacadeOnly // U: [ZIR-1]
supportedUnderlyingsOnly(token) // U: [ZIR-1A]
returns (bool)
{
_execute(abi.encodeCall(IZircuitPool.withdraw, (token, amount)));
address depositedToken = phantomTokenToToken[token];

if (!_supportedUnderlyings.contains(depositedToken)) revert UnsupportedUnderlyingException();

_execute(abi.encodeCall(IZircuitPool.withdraw, (depositedToken, amount))); // U: [ZIR-1A]
return false;
}

Expand Down Expand Up @@ -163,6 +169,7 @@ contract ZircuitPoolAdapter is AbstractAdapter, IZircuitPoolAdapter {
try IPhantomToken(token).getPhantomTokenInfo() returns (address target, address depositedToken) {
if (target == targetContract) {
tokenToPhantomToken[depositedToken] = token;
phantomTokenToToken[token] = depositedToken;
if (_supportedUnderlyings.add(depositedToken)) emit AddSupportedUnderlying(depositedToken, token);
}
} catch {}
Expand Down
48 changes: 48 additions & 0 deletions contracts/helpers/sky/StakingRewardsPhantomToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IStakingRewards} from "../../integrations/sky/IStakingRewards.sol";
import {PhantomERC20} from "../PhantomERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {MultiCall} from "@gearbox-protocol/core-v3/contracts/interfaces/ICreditFacadeV3.sol";
import {IPhantomToken} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IPhantomToken.sol";

/// @title StakingRewards position token
/// @notice Phantom ERC-20 token that represents the balance of the staked position in a StakingRewards pool
contract StakingRewardsPhantomToken is PhantomERC20, IPhantomToken {
uint256 public constant override version = 3_10;
bytes32 public constant override contractType = "PT_STAKING_REWARDS";

address public immutable pool;

/// @notice Constructor
/// @param _pool The rewards pool where the balance is tracked
constructor(address _pool)
PhantomERC20(
IStakingRewards(_pool).stakingToken(),
string(
abi.encodePacked(
"StakingRewards staked position ", IERC20Metadata(IStakingRewards(_pool).stakingToken()).name()
)
),
string(abi.encodePacked("stk", IERC20Metadata(IStakingRewards(_pool).stakingToken()).symbol())),
IERC20Metadata(IStakingRewards(_pool).stakingToken()).decimals()
)
{
pool = _pool;
}

/// @notice Returns the amount of underlying tokens staked in the pool
/// @param account The account for which the calculation is performed
function balanceOf(address account) public view returns (uint256) {
return IERC20(pool).balanceOf(account);
}

/// @notice Returns phantom token's target contract and underlying
function getPhantomTokenInfo() external view override returns (address, address) {
return (pool, underlying);
}
}
12 changes: 12 additions & 0 deletions contracts/integrations/sky/IDaiUsds.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

interface IDaiUsds {
function daiToUsds(address usr, uint256 wad) external;

function usdsToDai(address usr, uint256 wad) external;

function dai() external view returns (address);

function usds() external view returns (address);
}
14 changes: 14 additions & 0 deletions contracts/integrations/sky/IStakingRewards.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.23;

/// @title Sky Staking Rewards Interface
/// @notice Interface for the Sky StakingRewards contract
interface IStakingRewards {
function rewardsToken() external view returns (address);
function stakingToken() external view returns (address);

function stake(uint256 amount) external;
function withdraw(uint256 amount) external;
function getReward() external;
function earned(address account) external view returns (uint256);
}
21 changes: 21 additions & 0 deletions contracts/interfaces/sky/IDaiUsdsAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.23;

import {IAdapter} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IAdapter.sol";

/// @title DaiUsds adapter interface
interface IDaiUsdsAdapter is IAdapter {
function dai() external view returns (address);

function usds() external view returns (address);

function daiToUsds(address, uint256) external returns (bool);

function usdsToDai(address, uint256) external returns (bool);

function daiToUsdsDiff(uint256) external returns (bool);

function usdsToDaiDiff(uint256) external returns (bool);
}
26 changes: 26 additions & 0 deletions contracts/interfaces/sky/IStakingRewardsAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// SPDX-License-Identifier: MIT
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2024.
pragma solidity ^0.8.23;

import {IAdapter} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IAdapter.sol";
import {IPhantomTokenWithdrawer} from "@gearbox-protocol/core-v3/contracts/interfaces/base/IPhantomToken.sol";

/// @title Staking Rewards Adapter Interface
interface IStakingRewardsAdapter is IAdapter, IPhantomTokenWithdrawer {
function stakingToken() external view returns (address);

function rewardsToken() external view returns (address);

function stakedPhantomToken() external view returns (address);

function stake(uint256 amount) external returns (bool useSafePrices);

function stakeDiff(uint256 leftoverAmount) external returns (bool useSafePrices);

function getReward() external returns (bool useSafePrices);

function withdraw(uint256 amount) external returns (bool useSafePrices);

function withdrawDiff(uint256 leftoverAmount) external returns (bool useSafePrices);
}
Loading

0 comments on commit 7dde79c

Please sign in to comment.