Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: return WrappedAToken #50

Merged
merged 2 commits into from
Sep 4, 2023
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
18 changes: 9 additions & 9 deletions contracts/adapters/aave/AaveV2_WrappedATokenAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {AbstractAdapter} from "../AbstractAdapter.sol";
import {AdapterType} from "@gearbox-protocol/sdk-gov/contracts/AdapterType.sol";

import {WrappedAToken} from "../../helpers/aave/AaveV2_WrappedAToken.sol";
import {IAaveV2_WrappedATokenAdapter} from "../../interfaces/aave/IAaveV2_WrappedATokenAdapter.sol";
import {IWrappedATokenV2} from "@gearbox-protocol/oracles-v3/contracts/interfaces/aave/IWrappedATokenV2.sol";

/// @title Aave V2 Wrapped aToken adapter
/// @notice Implements logic allowing CAs to convert between waTokens, aTokens and underlying tokens
Expand Down Expand Up @@ -38,10 +38,10 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd
constructor(address _creditManager, address _waToken) AbstractAdapter(_creditManager, _waToken) {
waTokenMask = _getMaskOrRevert(targetContract); // F: [AAV2W-1, AAV2W-2]

aToken = IWrappedATokenV2(targetContract).aToken(); // F: [AAV2W-2]
aToken = WrappedAToken(targetContract).aToken(); // F: [AAV2W-2]
aTokenMask = _getMaskOrRevert(aToken); // F: [AAV2W-2]

underlying = IWrappedATokenV2(targetContract).underlying(); // F: [AAV2W-2]
underlying = WrappedAToken(targetContract).underlying(); // F: [AAV2W-2]
tokenMask = _getMaskOrRevert(underlying); // F: [AAV2W-2]
}

Expand Down Expand Up @@ -127,11 +127,11 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd
(tokensToEnable, tokensToDisable) = (waTokenMask, fromUnderlying ? tokenMask : aTokenMask);
}

/// @dev Returns data for `IWrappedATokenV2`'s `deposit` or `depositUnderlying` call
/// @dev Returns data for `WrappedAToken`'s `deposit` or `depositUnderlying` call
function _encodeDeposit(uint256 assets, bool fromUnderlying) internal pure returns (bytes memory callData) {
callData = fromUnderlying
? abi.encodeCall(IWrappedATokenV2.depositUnderlying, (assets))
: abi.encodeCall(IWrappedATokenV2.deposit, (assets));
? abi.encodeCall(WrappedAToken.depositUnderlying, (assets))
: abi.encodeCall(WrappedAToken.deposit, (assets));
}

// ----------- //
Expand Down Expand Up @@ -209,10 +209,10 @@ contract AaveV2_WrappedATokenAdapter is AbstractAdapter, IAaveV2_WrappedATokenAd
(tokensToEnable, tokensToDisable) = (toUnderlying ? tokenMask : aTokenMask, waTokenMask);
}

/// @dev Returns data for `IWrappedATokenV2`'s `withdraw` or `withdrawUnderlying` call
/// @dev Returns data for `WrappedAToken`'s `withdraw` or `withdrawUnderlying` call
function _encodeWithdraw(uint256 shares, bool toUnderlying) internal pure returns (bytes memory callData) {
callData = toUnderlying
? abi.encodeCall(IWrappedATokenV2.withdrawUnderlying, (shares))
: abi.encodeCall(IWrappedATokenV2.withdraw, (shares));
? abi.encodeCall(WrappedAToken.withdrawUnderlying, (shares))
: abi.encodeCall(WrappedAToken.withdraw, (shares));
}
}
137 changes: 137 additions & 0 deletions contracts/helpers/aave/AaveV2_WrappedAToken.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
// SPDX-License-Identifier: GPL-2.0-or-later
// Gearbox Protocol. Generalized leverage for DeFi protocols
// (c) Gearbox Foundation, 2023.
pragma solidity ^0.8.17;

import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";

import {WAD} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";
import {SanityCheckTrait} from "@gearbox-protocol/core-v3/contracts/traits/SanityCheckTrait.sol";

import {IAToken} from "../../integrations/aave/IAToken.sol";
import {ILendingPool} from "../../integrations/aave/ILendingPool.sol";

interface IWrappedATokenEvents {
/// @notice Emitted on deposit
/// @param account Account that performed deposit
/// @param assets Amount of deposited aTokens
/// @param shares Amount of waTokens minted to account
event Deposit(address indexed account, uint256 assets, uint256 shares);

/// @notice Emitted on withdrawal
/// @param account Account that performed withdrawal
/// @param assets Amount of withdrawn aTokens
/// @param shares Amount of waTokens burnt from account
event Withdraw(address indexed account, uint256 assets, uint256 shares);
}

/// @title Wrapped aToken
/// @notice Non-rebasing wrapper of Aave V2 aToken
/// @dev Ignores any Aave incentives
contract WrappedAToken is ERC20, SanityCheckTrait, IWrappedATokenEvents {
using SafeERC20 for ERC20;

/// @notice Underlying aToken
address public immutable aToken;

/// @notice Underlying token
address public immutable underlying;

/// @notice Aave lending pool
address public immutable lendingPool;

/// @dev aToken's normalized income (aka interest accumulator) at the moment of waToken creation
uint256 private immutable _normalizedIncome;

/// @dev waToken decimals
uint8 private immutable _decimals;

/// @notice Constructor
/// @param _aToken Underlying aToken address
constructor(address _aToken)
ERC20(
address(_aToken) != address(0) ? string(abi.encodePacked("Wrapped ", ERC20(_aToken).name())) : "",
address(_aToken) != address(0) ? string(abi.encodePacked("w", ERC20(_aToken).symbol())) : ""
)
nonZeroAddress(_aToken) // U:[WAT-1]
{
aToken = _aToken; // U:[WAT-2]
underlying = IAToken(aToken).UNDERLYING_ASSET_ADDRESS(); // U:[WAT-2]
lendingPool = address(IAToken(aToken).POOL()); // U:[WAT-2]
_normalizedIncome = ILendingPool(lendingPool).getReserveNormalizedIncome(underlying);
_decimals = IAToken(aToken).decimals(); // U:[WAT-2]
ERC20(underlying).approve(lendingPool, type(uint256).max);
}

/// @notice waToken decimals, same as underlying and aToken
function decimals() public view override returns (uint8) {
return _decimals;
}

/// @notice Returns amount of aTokens belonging to given account (increases as interest is accrued)
function balanceOfUnderlying(address account) external view returns (uint256) {
return balanceOf(account) * exchangeRate() / WAD; // U:[WAT-3]
}

/// @notice Returns amount of aTokens per waToken, scaled by 1e18
function exchangeRate() public view returns (uint256) {
return WAD * ILendingPool(lendingPool).getReserveNormalizedIncome(underlying) / _normalizedIncome; // U:[WAT-4]
}

/// @notice Deposit given amount of aTokens (aToken must be approved before the call)
/// @param assets Amount of aTokens to deposit in exchange for waTokens
/// @return shares Amount of waTokens minted to the caller
function deposit(uint256 assets) external returns (uint256 shares) {
ERC20(aToken).transferFrom(msg.sender, address(this), assets);
shares = _deposit(assets); // U:[WAT-5]
}

/// @notice Deposit given amount underlying tokens (underlying must be approved before the call)
/// @param assets Amount of underlying tokens to deposit in exchange for waTokens
/// @return shares Amount of waTokens minted to the caller
function depositUnderlying(uint256 assets) external returns (uint256 shares) {
ERC20(underlying).safeTransferFrom(msg.sender, address(this), assets);
_ensureAllowance(assets);
ILendingPool(lendingPool).deposit(underlying, assets, address(this), 0); // U:[WAT-6]
shares = _deposit(assets); // U:[WAT-6]
}

/// @notice Withdraw given amount of waTokens for aTokens
/// @param shares Amount of waTokens to burn in exchange for aTokens
/// @return assets Amount of aTokens sent to the caller
function withdraw(uint256 shares) external returns (uint256 assets) {
assets = _withdraw(shares); // U:[WAT-7]
ERC20(aToken).transfer(msg.sender, assets);
}

/// @notice Withdraw given amount of waTokens for underlying tokens
/// @param shares Amount of waTokens to burn in exchange for underlying tokens
/// @return assets Amount of underlying tokens sent to the caller
function withdrawUnderlying(uint256 shares) external returns (uint256 assets) {
assets = _withdraw(shares); // U:[WAT-8]
ILendingPool(lendingPool).withdraw(underlying, assets, msg.sender); // U:[WAT-8]
}

/// @dev Internal implementation of deposit
function _deposit(uint256 assets) internal returns (uint256 shares) {
shares = assets * WAD / exchangeRate();
_mint(msg.sender, shares); // U:[WAT-5,6]
emit Deposit(msg.sender, assets, shares); // U:[WAT-5,6]
}

/// @dev Internal implementation of withdraw
function _withdraw(uint256 shares) internal returns (uint256 assets) {
assets = shares * exchangeRate() / WAD;
_burn(msg.sender, shares); // U:[WAT-7,8]
emit Withdraw(msg.sender, assets, shares); // U:[WAT-7,8]
}

/// @dev Gives lending pool max approval for underlying if it falls below `amount`
function _ensureAllowance(uint256 amount) internal {
if (ERC20(underlying).allowance(address(this), lendingPool) < amount) {
ERC20(underlying).approve(lendingPool, type(uint256).max); // [WAT-9]
}
}
}
10 changes: 5 additions & 5 deletions contracts/test/unit/adapters/aave/AaveTestHelper.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
WETH_EXCHANGE_AMOUNT
} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol";

import {WrappedATokenV2} from "@gearbox-protocol/oracles-v3/contracts/tokens/aave/WrappedATokenV2.sol";
import {WrappedAToken} from "../../../../helpers/aave/AaveV2_WrappedAToken.sol";
import {IAToken} from "../../../../integrations/aave/IAToken.sol";

import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol";
Expand Down Expand Up @@ -81,9 +81,9 @@ contract AaveTestHelper is AdapterTestHelper {
}

function _setupWrappers() internal {
waDai = address(new WrappedATokenV2(aDai));
waUsdc = address(new WrappedATokenV2(aUsdc));
waWeth = address(new WrappedATokenV2(aWeth));
waDai = address(new WrappedAToken(aDai));
waUsdc = address(new WrappedAToken(aUsdc));
waWeth = address(new WrappedAToken(aWeth));
vm.label(waDai, "waDAI");
vm.label(waUsdc, "waUSDC");
vm.label(waWeth, "waWETH");
Expand Down Expand Up @@ -149,7 +149,7 @@ contract AaveTestHelper is AdapterTestHelper {

tokenTestSuite.approve(underlying, USER, waToken, amount);
vm.prank(USER);
balance = WrappedATokenV2(waToken).depositUnderlying(amount);
balance = WrappedAToken(waToken).depositUnderlying(amount);

tokenTestSuite.approve(waToken, USER, address(creditManager), balance);
vm.prank(USER);
Expand Down
26 changes: 13 additions & 13 deletions contracts/test/unit/adapters/aave/AaveV2_WrappedATokenAdapter.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {WAD} from "@gearbox-protocol/core-v2/contracts/libraries/Constants.sol";
import {USER, CONFIGURATOR} from "@gearbox-protocol/core-v3/contracts/test/lib/constants.sol";

import {AaveV2_WrappedATokenAdapter} from "../../../../adapters/aave/AaveV2_WrappedATokenAdapter.sol";
import {IWrappedATokenV2} from "@gearbox-protocol/oracles-v3/contracts/interfaces/aave/IWrappedATokenV2.sol";
import {WrappedAToken} from "../../../../helpers/aave/AaveV2_WrappedAToken.sol";
import {Tokens} from "@gearbox-protocol/sdk-gov/contracts/Tokens.sol";
import {AaveTestHelper} from "./AaveTestHelper.sol";

Expand Down Expand Up @@ -91,13 +91,13 @@ contract AaveV2_WrappedATokenAdapter_Test is AaveTestHelper {
uint256 depositAmount = initialBalance / 2;

bytes memory callData = fromUnderlying == 1
? abi.encodeCall(IWrappedATokenV2.depositUnderlying, (depositAmount))
: abi.encodeCall(IWrappedATokenV2.deposit, (depositAmount));
? abi.encodeCall(WrappedAToken.depositUnderlying, (depositAmount))
: abi.encodeCall(WrappedAToken.deposit, (depositAmount));
expectMulticallStackCalls(address(adapter), waUsdc, USER, callData, tokenIn, waUsdc, true);
executeOneLineMulticall(creditAccount, address(adapter), callData);

expectBalance(tokenIn, creditAccount, initialBalance - depositAmount);
expectBalance(waUsdc, creditAccount, depositAmount * WAD / IWrappedATokenV2(waUsdc).exchangeRate());
expectBalance(waUsdc, creditAccount, depositAmount * WAD / WrappedAToken(waUsdc).exchangeRate());

expectAllowance(tokenIn, creditAccount, waUsdc, 1);

Expand Down Expand Up @@ -125,8 +125,8 @@ contract AaveV2_WrappedATokenAdapter_Test is AaveTestHelper {
if (fromUnderlying == 0) initialBalance = tokenTestSuite.balanceOf(aUsdc, creditAccount);

bytes memory expectedCallData = fromUnderlying == 1
? abi.encodeCall(IWrappedATokenV2.depositUnderlying, (initialBalance - 1))
: abi.encodeCall(IWrappedATokenV2.deposit, (initialBalance - 1));
? abi.encodeCall(WrappedAToken.depositUnderlying, (initialBalance - 1))
: abi.encodeCall(WrappedAToken.deposit, (initialBalance - 1));
expectMulticallStackCalls(address(adapter), waUsdc, USER, expectedCallData, tokenIn, waUsdc, true);

bytes memory callData = fromUnderlying == 1
Expand All @@ -135,7 +135,7 @@ contract AaveV2_WrappedATokenAdapter_Test is AaveTestHelper {
executeOneLineMulticall(creditAccount, address(adapter), callData);

expectBalance(tokenIn, creditAccount, 1);
expectBalance(waUsdc, creditAccount, (initialBalance - 1) * WAD / IWrappedATokenV2(waUsdc).exchangeRate());
expectBalance(waUsdc, creditAccount, (initialBalance - 1) * WAD / WrappedAToken(waUsdc).exchangeRate());

expectAllowance(tokenIn, creditAccount, waUsdc, 1);

Expand All @@ -160,13 +160,13 @@ contract AaveV2_WrappedATokenAdapter_Test is AaveTestHelper {
uint256 withdrawAmount = initialBalance / 2;

bytes memory callData = toUnderlying == 1
? abi.encodeCall(IWrappedATokenV2.withdrawUnderlying, (withdrawAmount))
: abi.encodeCall(IWrappedATokenV2.withdraw, (withdrawAmount));
? abi.encodeCall(WrappedAToken.withdrawUnderlying, (withdrawAmount))
: abi.encodeCall(WrappedAToken.withdraw, (withdrawAmount));
expectMulticallStackCalls(address(adapter), waUsdc, USER, callData, waUsdc, tokenOut, false);
executeOneLineMulticall(creditAccount, address(adapter), callData);

expectBalance(waUsdc, creditAccount, initialBalance - withdrawAmount);
expectBalance(tokenOut, creditAccount, withdrawAmount * IWrappedATokenV2(waUsdc).exchangeRate() / WAD);
expectBalance(tokenOut, creditAccount, withdrawAmount * WrappedAToken(waUsdc).exchangeRate() / WAD);

expectTokenIsEnabled(creditAccount, waUsdc, true);
expectTokenIsEnabled(creditAccount, tokenOut, true);
Expand All @@ -188,8 +188,8 @@ contract AaveV2_WrappedATokenAdapter_Test is AaveTestHelper {
vm.warp(block.timestamp + timedelta);

bytes memory expectedCallData = toUnderlying == 1
? abi.encodeCall(IWrappedATokenV2.withdrawUnderlying, (initialBalance - 1))
: abi.encodeCall(IWrappedATokenV2.withdraw, (initialBalance - 1));
? abi.encodeCall(WrappedAToken.withdrawUnderlying, (initialBalance - 1))
: abi.encodeCall(WrappedAToken.withdraw, (initialBalance - 1));
expectMulticallStackCalls(address(adapter), waUsdc, USER, expectedCallData, waUsdc, tokenOut, false);

bytes memory callData = toUnderlying == 1
Expand All @@ -198,7 +198,7 @@ contract AaveV2_WrappedATokenAdapter_Test is AaveTestHelper {
executeOneLineMulticall(creditAccount, address(adapter), callData);

expectBalance(waUsdc, creditAccount, 1);
expectBalance(tokenOut, creditAccount, (initialBalance - 1) * IWrappedATokenV2(waUsdc).exchangeRate() / WAD);
expectBalance(tokenOut, creditAccount, (initialBalance - 1) * WrappedAToken(waUsdc).exchangeRate() / WAD);

expectTokenIsEnabled(creditAccount, waUsdc, false);
expectTokenIsEnabled(creditAccount, tokenOut, true);
Expand Down
Loading
Loading