Merge branch 'main' of https://github.com/propeller-heads/propeller-protocol-lib into feat/curve-maverick

This commit is contained in:
0xMochan
2024-05-09 11:27:50 -05:00
19 changed files with 2072 additions and 195 deletions

View File

@@ -1,7 +1,7 @@
name: test evm name: test & check evm
on: on:
push: pull_request:
paths: paths:
- "evm/**" - "evm/**"
@@ -15,6 +15,9 @@ jobs:
name: Foundry project name: Foundry project
runs-on: ubuntu-latest runs-on: ubuntu-latest
defaults:
run:
working-directory: evm
steps: steps:
- uses: actions/checkout@v3 - uses: actions/checkout@v3
with: with:
@@ -27,7 +30,6 @@ jobs:
- name: Run Forge build - name: Run Forge build
run: | run: |
cd evm
forge --version forge --version
forge build --sizes forge build --sizes
id: build id: build
@@ -40,7 +42,6 @@ jobs:
- name: Run Forge tests - name: Run Forge tests
run: | run: |
cd evm
forge test -vvv forge test -vvv
id: test id: test
env: env:

View File

@@ -0,0 +1,710 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma experimental ABIEncoderV2;
pragma solidity ^0.8.13;
import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from
"openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {SafeERC20} from
"openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
/// @dev custom reserve limit factor to prevent revert errors in OrderSide.Buy
uint256 constant RESERVE_LIMIT_FACTOR = 10;
uint256 constant STANDARD_TOKEN_DECIMALS = 10 ** 18;
/// @title AngleAdapter
/// @dev Information about prices: When swapping collateral to agEUR, the trade
/// price will not decrease(amountOut). Instead, when swapping agEUR to
/// collateral, it will, because agEUR is minted, and this mechanism is used to
/// stabilize the agEUR price.
contract AngleAdapter is ISwapAdapter {
using SafeERC20 for IERC20;
ITransmuter immutable transmuter;
constructor(ITransmuter _transmuter) {
transmuter = _transmuter;
}
/// @inheritdoc ISwapAdapter
/**
* @dev It is not possible to reproduce the swap in a view mode (like
* Bancor, Uniswap v2, etc..) as the swap produce a change of storage in
* the Angle protocol, that impacts the price post trade. Due to the
* architecture of Angle, it's not possible to calculate the storage
* modifications of Angle inside the adapter.
*/
function price(bytes32, address, address, uint256[] memory)
external
pure
override
returns (Fraction[] memory)
{
revert NotImplemented("AngleAdapter.price");
}
/// @inheritdoc ISwapAdapter
/**
* @dev The price post trade is indipendent by the amount, since is the
* price with minimal fees with 0 slippage. In Angle there is no price with
* 0 slippage, so we use the PRECISE_UNIT (10^18, that is a small value) as
* input amount to have a slippage ---> 0.
*/
function swap(
bytes32,
address sellToken,
address buyToken,
OrderSide side,
uint256 specifiedAmount
) external returns (Trade memory trade) {
if (specifiedAmount == 0) {
return trade;
}
uint256 gasBefore = gasleft();
if (side == OrderSide.Sell) {
trade.calculatedAmount = sell(sellToken, buyToken, specifiedAmount);
} else {
trade.calculatedAmount = buy(sellToken, buyToken, specifiedAmount);
}
trade.gasUsed = gasBefore - gasleft();
uint8 decimals = side == OrderSide.Sell
? IERC20Metadata(sellToken).decimals()
: IERC20Metadata(buyToken).decimals();
trade.price = getPriceAt(sellToken, buyToken, side, decimals);
}
/// @inheritdoc ISwapAdapter
/// @dev Mint may have no limits, but we underestimate them to make sure,
/// with the same amount of sellToken. We use the quoteIn (incl. fee),
/// because calculating fee requires a part of the implementation of the
/// Angle Diamond Storage, and therefore redundant functions and excessive
/// contract size, with an high complexity. In addition, we underestimate to
/// RESERVE_LIMIT_FACTOR to ensure swaps with OrderSide.Buy won't fail
/// anyway.
function getLimits(bytes32, address sellToken, address buyToken)
external
view
override
returns (uint256[] memory limits)
{
limits = new uint256[](2);
address transmuterAddress = address(transmuter);
if (buyToken == transmuter.agToken()) {
// mint(buy agToken)
Collateral memory collatInfo =
transmuter.getCollateralInfo(sellToken);
if (collatInfo.isManaged > 0) {
limits[0] =
LibManager.maxAvailable(collatInfo.managerData.config);
} else {
limits[0] = IERC20(sellToken).balanceOf(transmuterAddress);
}
limits[1] = transmuter.quoteIn(limits[0], sellToken, buyToken);
limits[1] = limits[1] / RESERVE_LIMIT_FACTOR;
limits[0] = limits[0] / RESERVE_LIMIT_FACTOR;
} else {
// burn(sell agToken)
Collateral memory collatInfo =
transmuter.getCollateralInfo(buyToken);
if (collatInfo.isManaged > 0) {
limits[1] =
LibManager.maxAvailable(collatInfo.managerData.config);
} else {
limits[1] = IERC20(buyToken).balanceOf(transmuterAddress);
}
limits[0] = transmuter.quoteIn(limits[1], buyToken, sellToken);
limits[1] = limits[1] / RESERVE_LIMIT_FACTOR;
limits[0] = limits[0] / RESERVE_LIMIT_FACTOR;
}
}
/// @inheritdoc ISwapAdapter
function getCapabilities(bytes32, address, address)
external
pure
override
returns (Capability[] memory capabilities)
{
capabilities = new Capability[](2);
capabilities[0] = Capability.SellOrder;
capabilities[1] = Capability.BuyOrder;
}
/// @inheritdoc ISwapAdapter
/// @dev Since Angle has no pool IDs but supports 3 tokens(agToken and the
/// collaterals), we return all the available collaterals and the
/// agToken(agEUR)
function getTokens(bytes32)
external
view
override
returns (address[] memory tokens)
{
address[] memory collateralsAddresses = transmuter.getCollateralList();
tokens = new address[](collateralsAddresses.length + 1);
for (uint256 i = 0; i < collateralsAddresses.length; i++) {
tokens[i] = address(collateralsAddresses[i]);
}
tokens[collateralsAddresses.length] = transmuter.agToken();
}
function getPoolIds(uint256, uint256)
external
pure
override
returns (bytes32[] memory)
{
revert NotImplemented("AngleAdapter.getPoolIds");
}
/// @notice Calculates pool prices for specified amounts
/// @param tokenIn The token being sold
/// @param tokenOut The token being bought
/// @param side Order side
/// @param decimals Decimals of the sell token
/// @return The price as a fraction corresponding to the provided amount.
function getPriceAt(
address tokenIn,
address tokenOut,
OrderSide side,
uint8 decimals
) internal view returns (Fraction memory) {
uint256 amountOut;
uint256 amountIn;
if (side == OrderSide.Sell) {
amountIn = 10 ** decimals;
amountOut = transmuter.quoteIn(amountIn, tokenIn, tokenOut);
} else {
amountOut = 10 ** decimals;
amountIn = transmuter.quoteOut(amountOut, tokenIn, tokenOut);
}
return Fraction(amountOut, amountIn);
}
/// @notice Executes a sell order on the contract.
/// @param sellToken The token being sold.
/// @param buyToken The token being bought.
/// @param amount The amount to be traded.
/// @return calculatedAmount The amount of tokens received.
function sell(address sellToken, address buyToken, uint256 amount)
internal
returns (uint256 calculatedAmount)
{
IERC20(sellToken).safeTransferFrom(msg.sender, address(this), amount);
IERC20(sellToken).approve(address(transmuter), amount);
calculatedAmount = transmuter.swapExactInput(
amount, 0, sellToken, buyToken, msg.sender, 0
);
}
/// @notice Executes a buy order on the contract.
/// @param sellToken The token being sold.
/// @param buyToken The token being bought.
/// @param amountOut The amount of buyToken to receive.
/// @return calculatedAmount The amount of tokens received.
function buy(address sellToken, address buyToken, uint256 amountOut)
internal
returns (uint256 calculatedAmount)
{
calculatedAmount = transmuter.quoteOut(amountOut, sellToken, buyToken);
IERC20(sellToken).safeTransferFrom(
msg.sender, address(this), calculatedAmount
);
IERC20(sellToken).approve(address(transmuter), calculatedAmount);
transmuter.swapExactOutput(
amountOut, type(uint256).max, sellToken, buyToken, msg.sender, 0
);
}
}
interface IAgToken is IERC20 {
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
MINTER ROLE ONLY FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
/// @notice Lets a whitelisted contract mint agTokens
/// @param account Address to mint to
/// @param amount Amount to mint
function mint(address account, uint256 amount) external;
/// @notice Burns `amount` tokens from a `burner` address after being asked
/// to by `sender`
/// @param amount Amount of tokens to burn
/// @param burner Address to burn from
/// @param sender Address which requested the burn from `burner`
/// @dev This method is to be called by a contract with the minter right
/// after being requested
/// to do so by a `sender` address willing to burn tokens from another
/// `burner` address
/// @dev The method checks the allowance between the `sender` and the
/// `burner`
function burnFrom(uint256 amount, address burner, address sender)
external;
/// @notice Burns `amount` tokens from a `burner` address
/// @param amount Amount of tokens to burn
/// @param burner Address to burn from
/// @dev This method is to be called by a contract with a minter right on
/// the AgToken after being
/// requested to do so by an address willing to burn tokens from its address
function burnSelf(uint256 amount, address burner) external;
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
TREASURY ONLY FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
/// @notice Adds a minter in the contract
/// @param minter Minter address to add
/// @dev Zero address checks are performed directly in the `Treasury`
/// contract
function addMinter(address minter) external;
/// @notice Removes a minter from the contract
/// @param minter Minter address to remove
/// @dev This function can also be called by a minter wishing to revoke
/// itself
function removeMinter(address minter) external;
/// @notice Sets a new treasury contract
/// @param _treasury New treasury address
function setTreasury(address _treasury) external;
/*//////////////////////////////////////////////////////////////////////////////////////////////////////////////////
EXTERNAL FUNCTIONS
//////////////////////////////////////////////////////////////////////////////////////////////////////////////////*/
/// @notice Checks whether an address has the right to mint agTokens
/// @param minter Address for which the minting right should be checked
/// @return Whether the address has the right to mint agTokens or not
function isMinter(address minter) external view returns (bool);
/// @notice Amount of decimals of the stablecoin
function decimals() external view returns (uint8);
}
enum ManagerType {
EXTERNAL
}
enum WhitelistType {
BACKED
}
struct ManagerStorage {
IERC20[] subCollaterals; // Subtokens handled by the manager or strategies
bytes config; // Additional configuration data
}
struct Collateral {
uint8 isManaged; // If the collateral is managed through external strategies
uint8 isMintLive; // If minting from this asset is unpaused
uint8 isBurnLive; // If burning to this asset is unpaused
uint8 decimals; // IERC20Metadata(collateral).decimals()
uint8 onlyWhitelisted; // If only whitelisted addresses can burn or redeem
// for this token
uint216 normalizedStables; // Normalized amount of stablecoins issued from
// this collateral
uint64[] xFeeMint; // Increasing exposures in [0,BASE_9[
int64[] yFeeMint; // Mint fees at the exposures specified in `xFeeMint`
uint64[] xFeeBurn; // Decreasing exposures in ]0,BASE_9]
int64[] yFeeBurn; // Burn fees at the exposures specified in `xFeeBurn`
bytes oracleConfig; // Data about the oracle used for the collateral
bytes whitelistData; // For whitelisted collateral, data used to verify
// whitelists
ManagerStorage managerData; // For managed collateral, data used to handle
// the strategies
}
struct TransmuterStorage {
IAgToken agToken; // agToken handled by the system
uint8 isRedemptionLive; // If redemption is unpaused
uint8 statusReentrant; // If call is reentrant or not
uint128 normalizedStables; // Normalized amount of stablecoins issued by the
// system
uint128 normalizer; // To reconcile `normalizedStables` values with the
// actual amount
address[] collateralList; // List of collateral assets supported by the
// system
uint64[] xRedemptionCurve; // Increasing collateral ratios > 0
int64[] yRedemptionCurve; // Value of the redemption fees at
// `xRedemptionCurve`
mapping(address => Collateral) collaterals; // Maps a collateral asset to
// its parameters
mapping(address => uint256) isTrusted; // If an address is trusted to update
// the normalizer value
mapping(address => uint256) isSellerTrusted; // If an address is trusted to
// sell accruing reward tokens
mapping(WhitelistType => mapping(address => uint256)) isWhitelistedForType;
}
interface IManager {
/// @notice Returns the amount of collateral managed by the Manager
/// @return balances Balances of all the subCollaterals handled by the
/// manager
/// @dev MUST NOT revert
function totalAssets()
external
view
returns (uint256[] memory balances, uint256 totalValue);
/// @notice Hook to invest `amount` of `collateral`
/// @dev MUST revert if the manager cannot accept these funds
/// @dev MUST have received the funds beforehand
function invest(uint256 amount) external;
/// @notice Sends `amount` of `collateral` to the `to` address
/// @dev Called when `agToken` are burnt and during redemptions
// @dev MUST revert if there are not funds enough available
/// @dev MUST be callable only by the transmuter
function release(address asset, address to, uint256 amount) external;
/// @notice Gives the maximum amount of collateral immediately available for
/// a transfer
/// @dev Useful for integrators using `quoteIn` and `quoteOut`
function maxAvailable() external view returns (uint256);
}
/// @title LibManager
/// @author Angle Labs, Inc.
/// @dev Managed collateral assets may be handled through external smart
/// contracts or directly through this library
/// @dev There is no implementation at this point for a managed collateral
/// handled through this library, and
/// a new specific `ManagerType` would need to be added in this case
library LibManager {
/// @notice Checks to which address managed funds must be transferred
function transferRecipient(bytes memory config)
internal
view
returns (address recipient)
{
(ManagerType managerType, bytes memory data) =
parseManagerConfig(config);
recipient = address(this);
if (managerType == ManagerType.EXTERNAL) {
return abi.decode(data, (address));
}
}
/// @notice Performs a transfer of `token` for a collateral that is managed
/// to a `to` address
/// @dev `token` may not be the actual collateral itself, as some
/// collaterals have subcollaterals associated
/// with it
/// @dev Eventually pulls funds from strategies
function release(
address token,
address to,
uint256 amount,
bytes memory config
) internal {
(ManagerType managerType, bytes memory data) =
parseManagerConfig(config);
if (managerType == ManagerType.EXTERNAL) {
abi.decode(data, (IManager)).release(token, to, amount);
}
}
/// @notice Gets the balances of all the tokens controlled through
/// `managerData`
/// @return balances An array of size `subCollaterals` with current balances
/// of all subCollaterals
/// including the one corresponding to the `managerData` given
/// @return totalValue The value of all the `subCollaterals` in `collateral`
/// @dev `subCollaterals` must always have as first token (index 0) the
/// collateral itself
function totalAssets(bytes memory config)
internal
view
returns (uint256[] memory balances, uint256 totalValue)
{
(ManagerType managerType, bytes memory data) =
parseManagerConfig(config);
if (managerType == ManagerType.EXTERNAL) {
return abi.decode(data, (IManager)).totalAssets();
}
}
/// @notice Calls a hook if needed after new funds have been transfered to a
/// manager
function invest(uint256 amount, bytes memory config) internal {
(ManagerType managerType, bytes memory data) =
parseManagerConfig(config);
if (managerType == ManagerType.EXTERNAL) {
abi.decode(data, (IManager)).invest(amount);
}
}
/// @notice Returns available underlying tokens, for instance if liquidity
/// is fully used and
/// not withdrawable the function will return 0
function maxAvailable(bytes memory config)
internal
view
returns (uint256 available)
{
(ManagerType managerType, bytes memory data) =
parseManagerConfig(config);
if (managerType == ManagerType.EXTERNAL) {
return abi.decode(data, (IManager)).maxAvailable();
}
}
/// @notice Decodes the `managerData` associated to a collateral
function parseManagerConfig(bytes memory config)
internal
pure
returns (ManagerType managerType, bytes memory data)
{
(managerType, data) = abi.decode(config, (ManagerType, bytes));
}
}
interface ITransmuter {
function implementation() external view returns (address);
function setDummyImplementation(address _implementation) external;
function facetAddress(bytes4 _functionSelector)
external
view
returns (address facetAddress_);
function facetAddresses()
external
view
returns (address[] memory facetAddresses_);
function facetFunctionSelectors(address _facet)
external
view
returns (bytes4[] memory _facetFunctionSelectors);
function accessControlManager() external view returns (address);
function agToken() external view returns (address);
function getCollateralBurnFees(address collateral)
external
view
returns (uint64[] memory xFeeBurn, int64[] memory yFeeBurn);
function getCollateralDecimals(address collateral)
external
view
returns (uint8);
function getCollateralInfo(address collateral)
external
view
returns (Collateral memory);
function getCollateralList() external view returns (address[] memory);
function getCollateralMintFees(address collateral)
external
view
returns (uint64[] memory xFeeMint, int64[] memory yFeeMint);
function getCollateralRatio()
external
view
returns (uint64 collatRatio, uint256 stablecoinsIssued);
function getCollateralWhitelistData(address collateral)
external
view
returns (bytes memory);
function getIssuedByCollateral(address collateral)
external
view
returns (uint256 stablecoinsFromCollateral, uint256 stablecoinsIssued);
function getManagerData(address collateral)
external
view
returns (bool, address[] memory, bytes memory);
function getOracle(address collateral)
external
view
returns (
uint8 oracleType,
uint8 targetType,
bytes memory oracleData,
bytes memory targetData
);
function getOracleValues(address collateral)
external
view
returns (
uint256 mint,
uint256 burn,
uint256 ratio,
uint256 minRatio,
uint256 redemption
);
function getRedemptionFees()
external
view
returns (
uint64[] memory xRedemptionCurve,
int64[] memory yRedemptionCurve
);
function getTotalIssued() external view returns (uint256);
function isPaused(address collateral, uint8 action)
external
view
returns (bool);
function isTrusted(address sender) external view returns (bool);
function isTrustedSeller(address sender) external view returns (bool);
function isValidSelector(bytes4 selector) external view returns (bool);
function isWhitelistedCollateral(address collateral)
external
view
returns (bool);
function isWhitelistedForCollateral(address collateral, address sender)
external
returns (bool);
function isWhitelistedForType(uint8 whitelistType, address sender)
external
view
returns (bool);
function sellRewards(uint256 minAmountOut, bytes memory payload)
external
returns (uint256 amountOut);
function addCollateral(address collateral) external;
function adjustStablecoins(
address collateral,
uint128 amount,
bool increase
) external;
function changeAllowance(address token, address spender, uint256 amount)
external;
function recoverERC20(
address collateral,
address token,
address to,
uint256 amount
) external;
function revokeCollateral(address collateral) external;
function setAccessControlManager(address _newAccessControlManager)
external;
function setOracle(address collateral, bytes memory oracleConfig)
external;
function setWhitelistStatus(
address collateral,
uint8 whitelistStatus,
bytes memory whitelistData
) external;
function toggleTrusted(address sender, uint8 t) external;
function setFees(
address collateral,
uint64[] memory xFee,
int64[] memory yFee,
bool mint
) external;
function setRedemptionCurveParams(uint64[] memory xFee, int64[] memory yFee)
external;
function togglePause(address collateral, uint8 pausedType) external;
function toggleWhitelist(uint8 whitelistType, address who) external;
function quoteIn(uint256 amountIn, address tokenIn, address tokenOut)
external
view
returns (uint256 amountOut);
function quoteOut(uint256 amountOut, address tokenIn, address tokenOut)
external
view
returns (uint256 amountIn);
function swapExactInput(
uint256 amountIn,
uint256 amountOutMin,
address tokenIn,
address tokenOut,
address to,
uint256 deadline
) external returns (uint256 amountOut);
function swapExactInputWithPermit(
uint256 amountIn,
uint256 amountOutMin,
address tokenIn,
address to,
uint256 deadline,
bytes memory permitData
) external returns (uint256 amountOut);
function swapExactOutput(
uint256 amountOut,
uint256 amountInMax,
address tokenIn,
address tokenOut,
address to,
uint256 deadline
) external returns (uint256 amountIn);
function swapExactOutputWithPermit(
uint256 amountOut,
uint256 amountInMax,
address tokenIn,
address to,
uint256 deadline,
bytes memory permitData
) external returns (uint256 amountIn);
function quoteRedemptionCurve(uint256 amount)
external
view
returns (address[] memory tokens, uint256[] memory amounts);
function redeem(
uint256 amount,
address receiver,
uint256 deadline,
uint256[] memory minAmountOuts
) external returns (address[] memory tokens, uint256[] memory amounts);
function redeemWithForfeit(
uint256 amount,
address receiver,
uint256 deadline,
uint256[] memory minAmountOuts,
address[] memory forfeitTokens
) external returns (address[] memory tokens, uint256[] memory amounts);
function updateNormalizer(uint256 amount, bool increase)
external
returns (uint256);
}

View File

@@ -0,0 +1,35 @@
# information about the author helps us reach out in case of issues.
author:
name: shadowycoders.dev
email: hello@shadowycreators.com
# Protocol Constants
constants:
protocol_gas: 30000
# minimum capabilities we can expect, individual pools may extend these
capabilities:
- SellSide
- BuySide
# The file containing the adapter contract
contract: AngleAdapter.sol
# Deployment instances used to generate chain specific bytecode.
instances:
- chain:
name: mainnet
id: 0
arguments:
- "0x00253582b2a3FE112feEC532221d9708c64cEFAb"
# Specify some automatic test cases in case getPoolIds and
# getTokens are not implemented.
tests:
instances:
- pool_id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
sell_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
buy_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
block: 17000000
chain:
id: 0
name: mainnet

View File

@@ -1,7 +1,11 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13; pragma solidity ^0.8.13;
import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";
import {
IERC20,
SafeERC20
} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
// Maximum Swap In/Out Ratio - 0.3 // Maximum Swap In/Out Ratio - 0.3
// https://balancer.gitbook.io/balancer/core-concepts/protocol/limitations#v2-limits // https://balancer.gitbook.io/balancer/core-concepts/protocol/limitations#v2-limits
@@ -9,6 +13,8 @@ uint256 constant RESERVE_LIMIT_FACTOR = 4;
uint256 constant SWAP_DEADLINE_SEC = 1000; uint256 constant SWAP_DEADLINE_SEC = 1000;
contract BalancerV2SwapAdapter is ISwapAdapter { contract BalancerV2SwapAdapter is ISwapAdapter {
using SafeERC20 for IERC20;
IVault immutable vault; IVault immutable vault;
constructor(address payable vault_) { constructor(address payable vault_) {
@@ -27,8 +33,8 @@ contract BalancerV2SwapAdapter is ISwapAdapter {
/// as a Fraction struct. /// as a Fraction struct.
function priceSingle( function priceSingle(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
uint256 sellAmount uint256 sellAmount
) public returns (Fraction memory calculatedPrice) { ) public returns (Fraction memory calculatedPrice) {
IVault.BatchSwapStep[] memory swapSteps = new IVault.BatchSwapStep[](1); IVault.BatchSwapStep[] memory swapSteps = new IVault.BatchSwapStep[](1);
@@ -40,8 +46,8 @@ contract BalancerV2SwapAdapter is ISwapAdapter {
userData: "" userData: ""
}); });
address[] memory assets = new address[](2); address[] memory assets = new address[](2);
assets[0] = address(sellToken); assets[0] = sellToken;
assets[1] = address(buyToken); assets[1] = buyToken;
IVault.FundManagement memory funds = IVault.FundManagement({ IVault.FundManagement memory funds = IVault.FundManagement({
sender: msg.sender, sender: msg.sender,
fromInternalBalance: false, fromInternalBalance: false,
@@ -63,8 +69,8 @@ contract BalancerV2SwapAdapter is ISwapAdapter {
function getSellAmount( function getSellAmount(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
uint256 buyAmount uint256 buyAmount
) public returns (uint256 sellAmount) { ) public returns (uint256 sellAmount) {
IVault.BatchSwapStep[] memory swapSteps = new IVault.BatchSwapStep[](1); IVault.BatchSwapStep[] memory swapSteps = new IVault.BatchSwapStep[](1);
@@ -76,8 +82,8 @@ contract BalancerV2SwapAdapter is ISwapAdapter {
userData: "" userData: ""
}); });
address[] memory assets = new address[](2); address[] memory assets = new address[](2);
assets[0] = address(sellToken); assets[0] = sellToken;
assets[1] = address(buyToken); assets[1] = buyToken;
IVault.FundManagement memory funds = IVault.FundManagement({ IVault.FundManagement memory funds = IVault.FundManagement({
sender: msg.sender, sender: msg.sender,
fromInternalBalance: false, fromInternalBalance: false,
@@ -94,31 +100,23 @@ contract BalancerV2SwapAdapter is ISwapAdapter {
sellAmount = uint256(assetDeltas[0]); sellAmount = uint256(assetDeltas[0]);
} }
function priceBatch( function price(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
uint256[] memory specifiedAmounts uint256[] memory specifiedAmounts
) external returns (Fraction[] memory calculatedPrices) { ) external returns (Fraction[] memory calculatedPrices) {
calculatedPrices = new Fraction[](specifiedAmounts.length);
for (uint256 i = 0; i < specifiedAmounts.length; i++) { for (uint256 i = 0; i < specifiedAmounts.length; i++) {
calculatedPrices[i] = calculatedPrices[i] =
priceSingle(poolId, sellToken, buyToken, specifiedAmounts[i]); priceSingle(poolId, sellToken, buyToken, specifiedAmounts[i]);
} }
} }
function price(bytes32, IERC20, IERC20, uint256[] memory)
external
pure
override
returns (Fraction[] memory)
{
revert NotImplemented("BalancerV2SwapAdapter.price");
}
function swap( function swap(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
OrderSide side, OrderSide side,
uint256 specifiedAmount uint256 specifiedAmount
) external override returns (Trade memory trade) { ) external override returns (Trade memory trade) {
@@ -136,16 +134,18 @@ contract BalancerV2SwapAdapter is ISwapAdapter {
limit = type(uint256).max; limit = type(uint256).max;
} }
sellToken.transferFrom(msg.sender, address(this), sellAmount); IERC20(sellToken).safeTransferFrom(
sellToken.approve(address(vault), sellAmount); msg.sender, address(this), sellAmount
);
IERC20(sellToken).safeIncreaseAllowance(address(vault), sellAmount);
uint256 gasBefore = gasleft(); uint256 gasBefore = gasleft();
trade.calculatedAmount = vault.swap( trade.calculatedAmount = vault.swap(
IVault.SingleSwap({ IVault.SingleSwap({
poolId: poolId, poolId: poolId,
kind: kind, kind: kind,
assetIn: address(sellToken), assetIn: sellToken,
assetOut: address(buyToken), assetOut: buyToken,
amount: specifiedAmount, amount: specifiedAmount,
userData: "" userData: ""
}), }),
@@ -162,14 +162,14 @@ contract BalancerV2SwapAdapter is ISwapAdapter {
trade.price = priceSingle(poolId, sellToken, buyToken, specifiedAmount); trade.price = priceSingle(poolId, sellToken, buyToken, specifiedAmount);
} }
function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) function getLimits(bytes32 poolId, address sellToken, address buyToken)
external external
view view
override override
returns (uint256[] memory limits) returns (uint256[] memory limits)
{ {
limits = new uint256[](2); limits = new uint256[](2);
(IERC20[] memory tokens, uint256[] memory balances,) = (address[] memory tokens, uint256[] memory balances,) =
vault.getPoolTokens(poolId); vault.getPoolTokens(poolId);
for (uint256 i = 0; i < tokens.length; i++) { for (uint256 i = 0; i < tokens.length; i++) {
@@ -182,22 +182,23 @@ contract BalancerV2SwapAdapter is ISwapAdapter {
} }
} }
function getCapabilities(bytes32, IERC20, IERC20) function getCapabilities(bytes32, address, address)
external external
pure pure
override override
returns (Capability[] memory capabilities) returns (Capability[] memory capabilities)
{ {
capabilities = new Capability[](2); capabilities = new Capability[](3);
capabilities[0] = Capability.SellOrder; capabilities[0] = Capability.SellOrder;
capabilities[1] = Capability.BuyOrder; capabilities[1] = Capability.BuyOrder;
capabilities[2] = Capability.PriceFunction;
} }
function getTokens(bytes32 poolId) function getTokens(bytes32 poolId)
external external
view view
override override
returns (IERC20[] memory tokens) returns (address[] memory tokens)
{ {
(tokens,,) = vault.getPoolTokens(poolId); (tokens,,) = vault.getPoolTokens(poolId);
} }
@@ -472,7 +473,7 @@ interface IVault {
external external
view view
returns ( returns (
IERC20[] memory tokens, address[] memory tokens,
uint256[] memory balances, uint256[] memory balances,
uint256 lastChangeBlock uint256 lastChangeBlock
); );

View File

@@ -0,0 +1,480 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma experimental ABIEncoderV2;
pragma solidity ^0.8.13;
import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";
import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from
"openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
/// @title Etherfi Adapter
/// @dev This contract supports the following swaps: ETH->eETH, weETH<->eETH,
/// ETH->weETH
contract EtherfiAdapter is ISwapAdapter {
using SafeERC20 for IERC20;
uint256 constant PRECISE_UNIT = 10 ** 18;
IWeEth immutable weEth;
IeEth immutable eEth;
ILiquidityPool immutable liquidityPool;
constructor(address _weEth) {
weEth = IWeEth(_weEth);
eEth = weEth.eETH();
liquidityPool = eEth.liquidityPool();
}
/// @dev Check if swap between provided sellToken and buyToken are supported
/// by this adapter
modifier checkInputTokens(address sellToken, address buyToken) {
if (sellToken == buyToken) {
revert Unavailable(
"This pool only supports ETH->eETH, weETH<->eETH and ETH->weETH swaps"
);
}
if (
sellToken != address(weEth) && sellToken != address(eEth)
&& sellToken != address(0)
) {
revert Unavailable(
"This pool only supports ETH->eETH, weETH<->eETH and ETH->weETH swaps"
);
}
if (buyToken != address(weEth) && buyToken != address(eEth)) {
revert Unavailable(
"This pool only supports ETH->eETH, weETH<->eETH and ETH->weETH swaps"
);
}
_;
}
/// @dev enable receive as this contract supports ETH
receive() external payable {}
/// @inheritdoc ISwapAdapter
function price(
bytes32,
address sellToken,
address buyToken,
uint256[] memory specifiedAmounts
)
external
view
override
checkInputTokens(sellToken, buyToken)
returns (Fraction[] memory prices)
{
prices = new Fraction[](specifiedAmounts.length);
uint256 totalPooledEther = liquidityPool.getTotalPooledEther();
uint256 eEthTotalShares = eEth.totalShares();
for (uint256 i = 0; i < specifiedAmounts.length; i++) {
if (sellToken == address(0)) {
uint256 sharesForDepositAmount = _sharesForDepositAmount(
specifiedAmounts[i], totalPooledEther, eEthTotalShares
);
prices[i] = getPriceAt(
sellToken,
buyToken,
specifiedAmounts[i],
totalPooledEther + specifiedAmounts[i],
eEthTotalShares + sharesForDepositAmount
);
} else {
prices[i] = getPriceAt(
sellToken,
buyToken,
specifiedAmounts[i],
totalPooledEther,
eEthTotalShares
);
}
}
}
/// @inheritdoc ISwapAdapter
function swap(
bytes32,
address sellToken,
address buyToken,
OrderSide side,
uint256 specifiedAmount
)
external
override
checkInputTokens(sellToken, buyToken)
returns (Trade memory trade)
{
if (specifiedAmount == 0) {
return trade;
}
uint256 gasBefore = gasleft();
if (sellToken == address(0)) {
if (buyToken == address(eEth)) {
trade.calculatedAmount = swapEthForEeth(specifiedAmount, side);
} else {
trade.calculatedAmount = swapEthForWeEth(specifiedAmount, side);
}
} else {
if (sellToken == address(eEth)) {
trade.calculatedAmount = swapEethForWeEth(specifiedAmount, side);
} else {
trade.calculatedAmount = swapWeEthForEeth(specifiedAmount, side);
}
}
trade.gasUsed = gasBefore - gasleft();
/// @dev as the price is constant for all the traded amounts and depends
/// only on the totalPooledEther and totalShares, we can use a standard
/// amount(PRECISE_UNIT) to render a well-formatted price without
/// precisions loss
trade.price = getPriceAt(
sellToken,
buyToken,
PRECISE_UNIT,
liquidityPool.getTotalPooledEther(),
eEth.totalShares()
);
}
/// @inheritdoc ISwapAdapter
function getLimits(bytes32, address sellToken, address buyToken)
external
view
override
checkInputTokens(sellToken, buyToken)
returns (uint256[] memory limits)
{
limits = new uint256[](2);
/// @dev Limits are underestimated to 90% of totalSupply as both weEth
/// and eEth have no limits but revert in some cases
if (sellToken == address(weEth) || buyToken == address(weEth)) {
limits[0] = IERC20(address(weEth)).totalSupply() * 90 / 100;
} else {
limits[0] = IERC20(address(eEth)).totalSupply() * 90 / 100;
}
limits[1] = limits[0];
}
/// @inheritdoc ISwapAdapter
function getCapabilities(bytes32, address, address)
external
pure
override
returns (Capability[] memory capabilities)
{
capabilities = new Capability[](3);
capabilities[0] = Capability.SellOrder;
capabilities[1] = Capability.BuyOrder;
capabilities[2] = Capability.PriceFunction;
}
/// @inheritdoc ISwapAdapter
function getTokens(bytes32)
external
view
override
returns (address[] memory tokens)
{
tokens = new address[](3);
tokens[0] = address(0);
tokens[1] = address(eEth);
tokens[2] = address(weEth);
}
/// @inheritdoc ISwapAdapter
function getPoolIds(uint256, uint256)
external
view
override
returns (bytes32[] memory ids)
{
ids = new bytes32[](1);
ids[0] = bytes20(address(liquidityPool));
}
/// @notice Swap ETH for eETH
/// @param amount amountIn or amountOut depending on side
function swapEthForEeth(uint256 amount, OrderSide side)
internal
returns (uint256)
{
if (side == OrderSide.Buy) {
uint256 amountIn = getAmountIn(address(0), address(eEth), amount);
liquidityPool.deposit{value: amountIn}();
IERC20(address(eEth)).safeTransfer(address(msg.sender), amount);
return amountIn;
} else {
uint256 receivedAmount = liquidityPool.deposit{value: amount}();
uint256 balBeforeUser =
IERC20(address(eEth)).balanceOf(address(msg.sender));
IERC20(address(eEth)).safeTransfer(msg.sender, receivedAmount);
return IERC20(address(eEth)).balanceOf(address(msg.sender))
- balBeforeUser;
}
}
/// @notice Swap ETH for weEth
/// @param amount amountIn or amountOut depending on side
function swapEthForWeEth(uint256 amount, OrderSide side)
internal
returns (uint256)
{
IERC20 eEth_ = IERC20(address(eEth));
if (side == OrderSide.Buy) {
uint256 amountIn = getAmountIn(address(0), address(weEth), amount);
uint256 receivedAmountEeth =
liquidityPool.deposit{value: amountIn}();
eEth_.safeIncreaseAllowance(address(weEth), receivedAmountEeth);
uint256 receivedAmount = weEth.wrap(receivedAmountEeth);
IERC20(address(weEth)).safeTransfer(
address(msg.sender), receivedAmount
);
return amountIn;
} else {
uint256 receivedAmountEeth = liquidityPool.deposit{value: amount}();
eEth_.safeIncreaseAllowance(address(weEth), receivedAmountEeth);
uint256 receivedAmount = weEth.wrap(receivedAmountEeth);
IERC20(address(weEth)).safeTransfer(
address(msg.sender), receivedAmount
);
return receivedAmount;
}
}
/// @notice Swap eETH for weETH
/// @param amount amountIn or amountOut depending on side
function swapEethForWeEth(uint256 amount, OrderSide side)
internal
returns (uint256)
{
if (side == OrderSide.Buy) {
uint256 amountIn =
getAmountIn(address(eEth), address(weEth), amount);
IERC20(address(eEth)).safeTransferFrom(
msg.sender, address(this), amountIn
);
IERC20(address(eEth)).safeIncreaseAllowance(
address(weEth), amountIn
);
uint256 receivedAmount = weEth.wrap(amountIn);
IERC20(address(weEth)).safeTransfer(
address(msg.sender), receivedAmount
);
return amountIn;
} else {
IERC20(address(eEth)).safeTransferFrom(
msg.sender, address(this), amount
);
IERC20(address(eEth)).safeIncreaseAllowance(address(weEth), amount);
uint256 receivedAmount = weEth.wrap(amount);
IERC20(address(weEth)).safeTransfer(
address(msg.sender), receivedAmount
);
return receivedAmount;
}
}
/// @notice Swap weETH for eEth
/// @param amount amountIn or amountOut depending on side
function swapWeEthForEeth(uint256 amount, OrderSide side)
internal
returns (uint256)
{
if (side == OrderSide.Buy) {
uint256 amountIn =
getAmountIn(address(weEth), address(eEth), amount);
IERC20(address(weEth)).safeTransferFrom(
msg.sender, address(this), amountIn
);
uint256 receivedAmount = weEth.unwrap(amountIn);
IERC20(address(eEth)).safeTransfer(
address(msg.sender), receivedAmount
);
return amountIn;
} else {
IERC20(address(weEth)).safeTransferFrom(
msg.sender, address(this), amount
);
uint256 receivedAmount = weEth.unwrap(amount);
uint256 balBeforeUser =
IERC20(address(eEth)).balanceOf(address(msg.sender));
IERC20(address(eEth)).safeTransfer(msg.sender, receivedAmount);
return IERC20(address(eEth)).balanceOf(address(msg.sender))
- balBeforeUser;
}
}
/// @dev copy of '_sharesForDepositAmount' internal function in
/// LiquidityPool, without ether subtraction
function _sharesForDepositAmount(
uint256 _depositAmount,
uint256 _totalPooledEther,
uint256 _eEthTotalShares
) internal pure returns (uint256) {
if (_totalPooledEther == 0) {
return _depositAmount;
}
return (_depositAmount * _eEthTotalShares) / _totalPooledEther;
}
/// @dev copy of 'getWeETHByeEth' function in weETH, dynamic
function _getWeETHByeEth(
uint256 _depositAmount,
uint256 _totalPooledEther,
uint256 _eEthTotalShares
) internal pure returns (uint256) {
if (_totalPooledEther == 0) {
return 0;
}
return (_depositAmount * _eEthTotalShares) / _totalPooledEther;
}
/// @dev copy of 'getEethByWeEth' function in weETH, dynamic
function _getEethByWeEth(
uint256 _depositAmount,
uint256 _totalPooledEther,
uint256 _eEthTotalShares
) internal pure returns (uint256) {
if (_eEthTotalShares == 0) {
return 0;
}
return (_depositAmount * _totalPooledEther) / _eEthTotalShares;
}
/// @notice Get swap price
/// @param sellToken token to sell
/// @param buyToken token to buy
/// @param totalPooledEther total pooled ether after or before trade if
/// required
/// @param eEthTotalShares total shares of eETH after or before trade if
/// required
function getPriceAt(
address sellToken,
address buyToken,
uint256 amount,
uint256 totalPooledEther,
uint256 eEthTotalShares
) internal view returns (Fraction memory) {
if (sellToken == address(0)) {
if (buyToken == address(eEth)) {
return Fraction(
_sharesForDepositAmount(
amount, totalPooledEther, eEthTotalShares
),
amount
);
} else {
uint256 eEthOut = _sharesForDepositAmount(
amount, totalPooledEther, eEthTotalShares
);
return Fraction(
_getWeETHByeEth(
eEthOut,
totalPooledEther + amount,
eEthTotalShares + eEthOut
),
amount
);
}
} else if (sellToken == address(eEth)) {
return Fraction(
_getWeETHByeEth(amount, totalPooledEther, eEthTotalShares),
amount
);
} else {
return Fraction(
_getEethByWeEth(amount, totalPooledEther, eEthTotalShares),
amount
);
}
}
/// @notice Get amountIn for swap functions with OrderSide buy
function getAmountIn(address sellToken, address buyToken, uint256 amountOut)
internal
view
returns (uint256)
{
if (sellToken == address(0)) {
if (buyToken == address(eEth)) {
return liquidityPool.amountForShare(amountOut);
} else {
uint256 ethRequiredForEeth =
liquidityPool.amountForShare(amountOut);
return liquidityPool.amountForShare(ethRequiredForEeth);
}
} else if (sellToken == address(eEth)) {
// eEth-weEth
return weEth.getEETHByWeETH(amountOut);
} else {
// weEth-eEth
return weEth.getWeETHByeETH(amountOut);
}
}
}
interface ILiquidityPool {
function numPendingDeposits() external view returns (uint32);
function totalValueOutOfLp() external view returns (uint128);
function totalValueInLp() external view returns (uint128);
function getTotalEtherClaimOf(address _user)
external
view
returns (uint256);
function getTotalPooledEther() external view returns (uint256);
function sharesForAmount(uint256 _amount) external view returns (uint256);
function sharesForWithdrawalAmount(uint256 _amount)
external
view
returns (uint256);
function amountForShare(uint256 _share) external view returns (uint256);
function deposit() external payable returns (uint256);
function deposit(address _referral) external payable returns (uint256);
function deposit(address _user, address _referral)
external
payable
returns (uint256);
function requestWithdraw(address recipient, uint256 amount)
external
returns (uint256);
}
interface IeEth {
function liquidityPool() external view returns (ILiquidityPool);
function totalShares() external view returns (uint256);
function shares(address _user) external view returns (uint256);
}
interface IWeEth {
function eETH() external view returns (IeEth);
function getWeETHByeETH(uint256 _eETHAmount)
external
view
returns (uint256);
function getEETHByWeETH(uint256 _weETHAmount)
external
view
returns (uint256);
function wrap(uint256 _eETHAmount) external returns (uint256);
function unwrap(uint256 _weETHAmount) external returns (uint256);
}

View File

@@ -0,0 +1,36 @@
# information about the author helps us reach out in case of issues.
author:
name: shadowycoders.dev
email: hello@shadowycreators.com
# Protocol Constants
constants:
protocol_gas: 30000
# minimum capabilities we can expect, individual pools may extend these
capabilities:
- SellSide
- BuySide
- PriceFunction
# The file containing the adapter contract
contract: EtherfiAdapter.sol
# Deployment instances used to generate chain specific bytecode.
instances:
- chain:
name: mainnet
id: 0
arguments:
- "0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee"
# Specify some automatic test cases in case getPoolIds and
# getTokens are not implemented.
tests:
instances:
- pool_id: "0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc"
sell_token: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"
buy_token: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"
block: 17000000
chain:
id: 0
name: mainnet

View File

@@ -1,10 +1,13 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13; pragma solidity ^0.8.13;
import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";
import {ERC20} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; import {IERC20Metadata} from
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/extensions/IERC20Metadata.sol";
"openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol"; import {
IERC20,
SafeERC20
} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
/// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but /// @dev Integral submitted deadline of 3600 seconds (1 hour) to Paraswap, but
/// it is not strictly necessary to be this long /// it is not strictly necessary to be this long
@@ -38,24 +41,23 @@ contract IntegralSwapAdapter is ISwapAdapter {
/// values to make sure the return value is the expected from caller. /// values to make sure the return value is the expected from caller.
function price( function price(
bytes32, bytes32,
IERC20 _sellToken, address _sellToken,
IERC20 _buyToken, address _buyToken,
uint256[] memory _specifiedAmounts uint256[] memory _specifiedAmounts
) external view override returns (Fraction[] memory _prices) { ) external view override returns (Fraction[] memory _prices) {
_prices = new Fraction[](_specifiedAmounts.length); _prices = new Fraction[](_specifiedAmounts.length);
Fraction memory price = Fraction memory uniformPrice = getPriceAt(_sellToken, _buyToken);
getPriceAt(address(_sellToken), address(_buyToken));
for (uint256 i = 0; i < _specifiedAmounts.length; i++) { for (uint256 i = 0; i < _specifiedAmounts.length; i++) {
_prices[i] = price; _prices[i] = uniformPrice;
} }
} }
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter
function swap( function swap(
bytes32, bytes32,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
OrderSide side, OrderSide side,
uint256 specifiedAmount uint256 specifiedAmount
) external override returns (Trade memory trade) { ) external override returns (Trade memory trade) {
@@ -72,18 +74,18 @@ contract IntegralSwapAdapter is ISwapAdapter {
trade.calculatedAmount = buy(sellToken, buyToken, specifiedAmount); trade.calculatedAmount = buy(sellToken, buyToken, specifiedAmount);
} }
trade.gasUsed = gasBefore - gasleft(); trade.gasUsed = gasBefore - gasleft();
trade.price = getPriceAt(address(sellToken), address(buyToken)); trade.price = getPriceAt(sellToken, buyToken);
} }
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter
function getLimits(bytes32, IERC20 sellToken, IERC20 buyToken) function getLimits(bytes32, address sellToken, address buyToken)
external external
view view
override override
returns (uint256[] memory limits) returns (uint256[] memory limits)
{ {
(,,, uint256 limitMax0,, uint256 limitMax1) = (,,, uint256 limitMax0,, uint256 limitMax1) =
relayer.getPoolState(address(sellToken), address(buyToken)); relayer.getPoolState(sellToken, buyToken);
limits = new uint256[](2); limits = new uint256[](2);
limits[0] = limitMax0; limits[0] = limitMax0;
@@ -98,7 +100,7 @@ contract IntegralSwapAdapter is ISwapAdapter {
} }
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter
function getCapabilities(bytes32, IERC20, IERC20) function getCapabilities(bytes32, address, address)
external external
pure pure
override override
@@ -116,12 +118,12 @@ contract IntegralSwapAdapter is ISwapAdapter {
external external
view view
override override
returns (IERC20[] memory tokens) returns (address[] memory tokens)
{ {
tokens = new IERC20[](2); tokens = new address[](2);
ITwapPair pair = ITwapPair(address(bytes20(poolId))); ITwapPair pair = ITwapPair(address(bytes20(poolId)));
tokens[0] = IERC20(pair.token0()); tokens[0] = pair.token0();
tokens[1] = IERC20(pair.token1()); tokens[1] = pair.token1();
} }
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter
@@ -147,23 +149,22 @@ contract IntegralSwapAdapter is ISwapAdapter {
/// @param buyToken The address of the token being bought. /// @param buyToken The address of the token being bought.
/// @param amount The amount to be traded. /// @param amount The amount to be traded.
/// @return uint256 The amount of tokens received. /// @return uint256 The amount of tokens received.
function sell(IERC20 sellToken, IERC20 buyToken, uint256 amount) function sell(address sellToken, address buyToken, uint256 amount)
internal internal
returns (uint256) returns (uint256)
{ {
uint256 amountOut = uint256 amountOut = relayer.quoteSell(sellToken, buyToken, amount);
relayer.quoteSell(address(sellToken), address(buyToken), amount);
if (amountOut == 0) { if (amountOut == 0) {
revert Unavailable("AmountOut is zero!"); revert Unavailable("AmountOut is zero!");
} }
sellToken.safeTransferFrom(msg.sender, address(this), amount); IERC20(sellToken).safeTransferFrom(msg.sender, address(this), amount);
sellToken.safeIncreaseAllowance(address(relayer), amount); IERC20(sellToken).safeIncreaseAllowance(address(relayer), amount);
relayer.sell( relayer.sell(
ITwapRelayer.SellParams({ ITwapRelayer.SellParams({
tokenIn: address(sellToken), tokenIn: sellToken,
tokenOut: address(buyToken), tokenOut: buyToken,
wrapUnwrap: false, wrapUnwrap: false,
to: msg.sender, to: msg.sender,
submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC),
@@ -180,24 +181,22 @@ contract IntegralSwapAdapter is ISwapAdapter {
/// @param buyToken The address of the token being bought. /// @param buyToken The address of the token being bought.
/// @param amountBought The amount of buyToken tokens to buy. /// @param amountBought The amount of buyToken tokens to buy.
/// @return uint256 The amount of tokens received. /// @return uint256 The amount of tokens received.
function buy(IERC20 sellToken, IERC20 buyToken, uint256 amountBought) function buy(address sellToken, address buyToken, uint256 amountBought)
internal internal
returns (uint256) returns (uint256)
{ {
uint256 amountIn = relayer.quoteBuy( uint256 amountIn = relayer.quoteBuy(sellToken, buyToken, amountBought);
address(sellToken), address(buyToken), amountBought
);
if (amountIn == 0) { if (amountIn == 0) {
revert Unavailable("AmountIn is zero!"); revert Unavailable("AmountIn is zero!");
} }
sellToken.safeTransferFrom(msg.sender, address(this), amountIn); IERC20(sellToken).safeTransferFrom(msg.sender, address(this), amountIn);
sellToken.safeIncreaseAllowance(address(relayer), amountIn); IERC20(sellToken).safeIncreaseAllowance(address(relayer), amountIn);
relayer.buy( relayer.buy(
ITwapRelayer.BuyParams({ ITwapRelayer.BuyParams({
tokenIn: address(sellToken), tokenIn: sellToken,
tokenOut: address(buyToken), tokenOut: buyToken,
wrapUnwrap: false, wrapUnwrap: false,
to: msg.sender, to: msg.sender,
submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC), submitDeadline: uint32(block.timestamp + SWAP_DEADLINE_SEC),
@@ -217,20 +216,18 @@ contract IntegralSwapAdapter is ISwapAdapter {
view view
returns (Fraction memory) returns (Fraction memory)
{ {
uint256 priceWithoutFee = relayer.getPriceByTokenAddresses( uint256 priceWithoutFee =
address(sellToken), address(buyToken) relayer.getPriceByTokenAddresses(sellToken, buyToken);
);
ITwapFactory factory = ITwapFactory(relayer.factory()); ITwapFactory factory = ITwapFactory(relayer.factory());
address pairAddress = address pairAddress = factory.getPair(sellToken, buyToken);
factory.getPair(address(sellToken), address(buyToken));
// get swapFee formatted; swapFee is a constant // get swapFee formatted; swapFee is a constant
uint256 swapFeeFormatted = uint256 swapFeeFormatted =
(STANDARD_TOKEN_DECIMALS - relayer.swapFee(pairAddress)); (STANDARD_TOKEN_DECIMALS - relayer.swapFee(pairAddress));
// get token decimals // get token decimals
uint256 sellTokenDecimals = 10 ** ERC20(sellToken).decimals(); uint256 sellTokenDecimals = 10 ** IERC20Metadata(sellToken).decimals();
uint256 buyTokenDecimals = 10 ** ERC20(buyToken).decimals(); uint256 buyTokenDecimals = 10 ** IERC20Metadata(buyToken).decimals();
/** /**
* @dev * @dev

View File

@@ -1,7 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13; pragma solidity ^0.8.13;
import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import {ISwapAdapterTypes} from "src/interfaces/ISwapAdapterTypes.sol"; import {ISwapAdapterTypes} from "src/interfaces/ISwapAdapterTypes.sol";
/// @title ISwapAdapter /// @title ISwapAdapter
@@ -35,10 +34,10 @@ interface ISwapAdapter is ISwapAdapterTypes {
/// provided amounts. /// provided amounts.
function price( function price(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
uint256[] memory specifiedAmounts uint256[] memory specifiedAmounts
) external view returns (Fraction[] memory prices); ) external returns (Fraction[] memory prices);
/** /**
* @notice Simulates swapping tokens on a given pool. * @notice Simulates swapping tokens on a given pool.
@@ -59,8 +58,8 @@ interface ISwapAdapter is ISwapAdapterTypes {
*/ */
function swap( function swap(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
OrderSide side, OrderSide side,
uint256 specifiedAmount uint256 specifiedAmount
) external returns (Trade memory trade); ) external returns (Trade memory trade);
@@ -76,25 +75,27 @@ interface ISwapAdapter is ISwapAdapterTypes {
/// @param sellToken The token being sold. /// @param sellToken The token being sold.
/// @param buyToken The token being bought. /// @param buyToken The token being bought.
/// @return limits An array of limits. /// @return limits An array of limits.
function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) function getLimits(bytes32 poolId, address sellToken, address buyToken)
external external
returns (uint256[] memory limits); returns (uint256[] memory limits);
/// @notice Retrieves the capabilities of the selected pool. /// @notice Retrieves the capabilities of the selected pool.
/// @param poolId The ID of the trading pool. /// @param poolId The ID of the trading pool.
/// @return capabilities An array of Capability. /// @return capabilities An array of Capability.
function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) function getCapabilities(
external bytes32 poolId,
returns (Capability[] memory capabilities); address sellToken,
address buyToken
) external returns (Capability[] memory capabilities);
/// @notice Retrieves the tokens in the selected pool. /// @notice Retrieves the tokens in the selected pool.
/// @dev Mainly used for testing as this is redundant with the required /// @dev Mainly used for testing as this is redundant with the required
/// substreams implementation. /// substreams implementation.
/// @param poolId The ID of the trading pool. /// @param poolId The ID of the trading pool.
/// @return tokens An array of IERC20 contracts. /// @return tokens An array of address contracts.
function getTokens(bytes32 poolId) function getTokens(bytes32 poolId)
external external
returns (IERC20[] memory tokens); returns (address[] memory tokens);
/// @notice Retrieves a range of pool IDs. /// @notice Retrieves a range of pool IDs.
/// @dev Mainly used for testing. It is alright to not return all available /// @dev Mainly used for testing. It is alright to not return all available

View File

@@ -1,8 +1,6 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13; pragma solidity ^0.8.13;
import {IERC20} from "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
interface ISwapAdapterTypes { interface ISwapAdapterTypes {
/// @dev The OrderSide enum represents possible sides of a trade: Sell or /// @dev The OrderSide enum represents possible sides of a trade: Sell or
/// Buy. E.g. if OrderSide is Sell, the sell amount is interpreted to be /// Buy. E.g. if OrderSide is Sell, the sell amount is interpreted to be

View File

@@ -1,7 +1,7 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13; pragma solidity ^0.8.13;
import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";
/// @title TemplateSwapAdapter /// @title TemplateSwapAdapter
/// @dev This is a template for a swap adapter. /// @dev This is a template for a swap adapter.
@@ -10,8 +10,8 @@ import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";
contract TemplateSwapAdapter is ISwapAdapter { contract TemplateSwapAdapter is ISwapAdapter {
function price( function price(
bytes32 _poolId, bytes32 _poolId,
IERC20 _sellToken, address _sellToken,
IERC20 _buyToken, address _buyToken,
uint256[] memory _specifiedAmounts uint256[] memory _specifiedAmounts
) external view override returns (Fraction[] memory _prices) { ) external view override returns (Fraction[] memory _prices) {
revert NotImplemented("TemplateSwapAdapter.price"); revert NotImplemented("TemplateSwapAdapter.price");
@@ -19,31 +19,32 @@ contract TemplateSwapAdapter is ISwapAdapter {
function swap( function swap(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
OrderSide side, OrderSide side,
uint256 specifiedAmount uint256 specifiedAmount
) external returns (Trade memory trade) { ) external returns (Trade memory trade) {
revert NotImplemented("TemplateSwapAdapter.swap"); revert NotImplemented("TemplateSwapAdapter.swap");
} }
function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) function getLimits(bytes32 poolId, address sellToken, address buyToken)
external external
returns (uint256[] memory limits) returns (uint256[] memory limits)
{ {
revert NotImplemented("TemplateSwapAdapter.getLimits"); revert NotImplemented("TemplateSwapAdapter.getLimits");
} }
function getCapabilities(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) function getCapabilities(
external bytes32 poolId,
returns (Capability[] memory capabilities) address sellToken,
{ address buyToken
) external returns (Capability[] memory capabilities) {
revert NotImplemented("TemplateSwapAdapter.getCapabilities"); revert NotImplemented("TemplateSwapAdapter.getCapabilities");
} }
function getTokens(bytes32 poolId) function getTokens(bytes32 poolId)
external external
returns (IERC20[] memory tokens) returns (address[] memory tokens)
{ {
revert NotImplemented("TemplateSwapAdapter.getTokens"); revert NotImplemented("TemplateSwapAdapter.getTokens");
} }

View File

@@ -5,8 +5,9 @@ author:
# Protocol Constants # Protocol Constants
constants: constants:
# The expected average gas cost of a swap
protocol_gas: 30000 protocol_gas: 30000
# minimum capabilities we can expect, individual pools may extend these # Minimum capabilities we can expect, individual pools may extend these
capabilities: capabilities:
- SellSide - SellSide
- BuySide - BuySide

View File

@@ -1,12 +1,18 @@
// SPDX-License-Identifier: AGPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13; pragma solidity ^0.8.13;
import {IERC20, ISwapAdapter} from "src/interfaces/ISwapAdapter.sol"; import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";
import {
IERC20,
SafeERC20
} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
// Uniswap handles arbirary amounts, but we limit the amount to 10x just in case // Uniswap handles arbirary amounts, but we limit the amount to 10x just in case
uint256 constant RESERVE_LIMIT_FACTOR = 10; uint256 constant RESERVE_LIMIT_FACTOR = 10;
contract UniswapV2SwapAdapter is ISwapAdapter { contract UniswapV2SwapAdapter is ISwapAdapter {
using SafeERC20 for IERC20;
IUniswapV2Factory immutable factory; IUniswapV2Factory immutable factory;
constructor(address factory_) { constructor(address factory_) {
@@ -16,8 +22,8 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter
function price( function price(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
uint256[] memory specifiedAmounts uint256[] memory specifiedAmounts
) external view override returns (Fraction[] memory prices) { ) external view override returns (Fraction[] memory prices) {
prices = new Fraction[](specifiedAmounts.length); prices = new Fraction[](specifiedAmounts.length);
@@ -60,8 +66,8 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter
function swap( function swap(
bytes32 poolId, bytes32 poolId,
IERC20 sellToken, address sellToken,
IERC20 buyToken, address buyToken,
OrderSide side, OrderSide side,
uint256 specifiedAmount uint256 specifiedAmount
) external override returns (Trade memory trade) { ) external override returns (Trade memory trade) {
@@ -104,7 +110,7 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
/// @return calculatedAmount The amount of tokens received. /// @return calculatedAmount The amount of tokens received.
function sell( function sell(
IUniswapV2Pair pair, IUniswapV2Pair pair,
IERC20 sellToken, address sellToken,
bool zero2one, bool zero2one,
uint112 reserveIn, uint112 reserveIn,
uint112 reserveOut, uint112 reserveOut,
@@ -113,8 +119,7 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
address swapper = msg.sender; address swapper = msg.sender;
uint256 amountOut = getAmountOut(amount, reserveIn, reserveOut); uint256 amountOut = getAmountOut(amount, reserveIn, reserveOut);
// TODO: use safeTransferFrom IERC20(sellToken).safeTransferFrom(swapper, address(pair), amount);
sellToken.transferFrom(swapper, address(pair), amount);
if (zero2one) { if (zero2one) {
pair.swap(0, amountOut, swapper, ""); pair.swap(0, amountOut, swapper, "");
} else { } else {
@@ -156,7 +161,7 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
/// @return calculatedAmount The amount of tokens sold. /// @return calculatedAmount The amount of tokens sold.
function buy( function buy(
IUniswapV2Pair pair, IUniswapV2Pair pair,
IERC20 sellToken, address sellToken,
bool zero2one, bool zero2one,
uint112 reserveIn, uint112 reserveIn,
uint112 reserveOut, uint112 reserveOut,
@@ -168,8 +173,8 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
if (amount == 0) { if (amount == 0) {
return 0; return 0;
} }
// TODO: use safeTransferFrom
sellToken.transferFrom(swapper, address(pair), amount); IERC20(sellToken).safeTransferFrom(swapper, address(pair), amount);
if (zero2one) { if (zero2one) {
pair.swap(0, amountOut, swapper, ""); pair.swap(0, amountOut, swapper, "");
} else { } else {
@@ -203,7 +208,7 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
} }
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter
function getLimits(bytes32 poolId, IERC20 sellToken, IERC20 buyToken) function getLimits(bytes32 poolId, address sellToken, address buyToken)
external external
view view
override override
@@ -222,7 +227,7 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
} }
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter
function getCapabilities(bytes32, IERC20, IERC20) function getCapabilities(bytes32, address, address)
external external
pure pure
override override
@@ -239,12 +244,12 @@ contract UniswapV2SwapAdapter is ISwapAdapter {
external external
view view
override override
returns (IERC20[] memory tokens) returns (address[] memory tokens)
{ {
tokens = new IERC20[](2); tokens = new address[](2);
IUniswapV2Pair pair = IUniswapV2Pair(address(bytes20(poolId))); IUniswapV2Pair pair = IUniswapV2Pair(address(bytes20(poolId)));
tokens[0] = IERC20(pair.token0()); tokens[0] = address(pair.token0());
tokens[1] = IERC20(pair.token1()); tokens[1] = address(pair.token1());
} }
/// @inheritdoc ISwapAdapter /// @inheritdoc ISwapAdapter

200
evm/test/AngleAdapter.t.sol Normal file
View File

@@ -0,0 +1,200 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import "src/angle/AngleAdapter.sol";
import "src/interfaces/ISwapAdapterTypes.sol";
import "src/libraries/FractionMath.sol";
import "forge-std/console.sol";
contract AngleAdapterTest is Test, ISwapAdapterTypes {
using FractionMath for Fraction;
AngleAdapter adapter;
IERC20 agEUR;
IERC20 constant EURC = IERC20(0x1aBaEA1f7C830bD89Acc67eC4af516284b1bC33c);
ITransmuter constant transmuter =
ITransmuter(0x00253582b2a3FE112feEC532221d9708c64cEFAb);
uint256 constant TEST_ITERATIONS = 100;
function setUp() public {
uint256 forkBlock = 18921770;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
adapter = new AngleAdapter(transmuter);
agEUR = IERC20(transmuter.agToken());
vm.label(address(adapter), "AngleAdapter");
vm.label(address(agEUR), "agEUR");
vm.label(address(EURC), "EURC");
}
function testSwapFuzzAngleMint(uint256 specifiedAmount, bool isBuy)
public
{
OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell;
bytes32 pair = bytes32(0);
uint256[] memory limits =
adapter.getLimits(pair, address(EURC), address(agEUR));
if (side == OrderSide.Buy) {
vm.assume(specifiedAmount < limits[1] && specifiedAmount > 0);
deal(address(EURC), address(this), type(uint256).max);
EURC.approve(address(adapter), type(uint256).max);
} else {
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 0);
deal(address(EURC), address(this), specifiedAmount);
EURC.approve(address(adapter), specifiedAmount);
}
uint256 eurc_balance = EURC.balanceOf(address(this));
uint256 agEUR_balance = agEUR.balanceOf(address(this));
Trade memory trade = adapter.swap(
pair, address(EURC), address(agEUR), side, specifiedAmount
);
if (trade.calculatedAmount > 0) {
if (side == OrderSide.Buy) {
assertEq(
specifiedAmount,
agEUR.balanceOf(address(this)) - agEUR_balance
);
assertEq(
trade.calculatedAmount,
eurc_balance - EURC.balanceOf(address(this))
);
} else {
assertEq(
specifiedAmount,
eurc_balance - EURC.balanceOf(address(this))
);
assertEq(
trade.calculatedAmount,
agEUR.balanceOf(address(this)) - agEUR_balance
);
}
}
}
function testSwapFuzzAngleRedeem(uint256 specifiedAmount, bool isBuy)
public
{
OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell;
bytes32 pair = bytes32(0);
uint256[] memory limits =
adapter.getLimits(pair, address(agEUR), address(EURC));
if (side == OrderSide.Buy) {
vm.assume(specifiedAmount < limits[1] && specifiedAmount > 0);
deal(address(agEUR), address(this), type(uint256).max);
agEUR.approve(address(adapter), type(uint256).max);
} else {
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 0);
deal(address(agEUR), address(this), specifiedAmount);
agEUR.approve(address(adapter), specifiedAmount);
}
uint256 eurc_balance = EURC.balanceOf(address(this));
uint256 agEUR_balance = agEUR.balanceOf(address(this));
Trade memory trade = adapter.swap(
pair, address(agEUR), address(EURC), side, specifiedAmount
);
if (trade.calculatedAmount > 0) {
if (side == OrderSide.Buy) {
assertEq(
specifiedAmount,
EURC.balanceOf(address(this)) - eurc_balance
);
assertEq(
trade.calculatedAmount,
agEUR_balance - agEUR.balanceOf(address(this))
);
} else {
assertEq(
specifiedAmount,
agEUR_balance - agEUR.balanceOf(address(this))
);
assertEq(
trade.calculatedAmount,
EURC.balanceOf(address(this)) - eurc_balance
);
}
}
}
function testSwapSellIncreasingAngle() public {
executeIncreasingSwapsAngle(OrderSide.Sell);
}
function executeIncreasingSwapsAngle(OrderSide side) internal {
bytes32 pair = bytes32(0);
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
amounts[i] = side == OrderSide.Sell
? (100 * i * 10 ** 18)
: (100 * i * 10 ** 6);
}
Trade[] memory trades = new Trade[](TEST_ITERATIONS);
uint256 beforeSwap;
for (uint256 i = 1; i < TEST_ITERATIONS; i++) {
beforeSwap = vm.snapshot();
if (side == OrderSide.Sell) {
deal(address(agEUR), address(this), amounts[i]);
agEUR.approve(address(adapter), amounts[i]);
} else {
deal(address(agEUR), address(this), type(uint256).max);
agEUR.approve(address(adapter), type(uint256).max);
}
trades[i] = adapter.swap(
pair, address(agEUR), address(EURC), side, amounts[i]
);
vm.revertTo(beforeSwap);
}
for (uint256 i = 1; i < TEST_ITERATIONS - 1; i++) {
assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount);
assertFalse(
trades[i].price.compareFractions(trades[i + 1].price) == -1
);
}
}
function testSwapBuyIncreasingAngle() public {
executeIncreasingSwapsAngle(OrderSide.Buy);
}
function testGetCapabilitiesAngle(bytes32 pair, address t0, address t1)
public
{
Capability[] memory res = adapter.getCapabilities(pair, t0, t1);
assertEq(res.length, 2);
}
function testGetTokensAngle() public {
address[] memory tokens = adapter.getTokens(bytes32(0));
assertGe(tokens.length, 2);
}
function testGetLimitsAngle() public {
bytes32 pair = bytes32(0);
uint256[] memory limits =
adapter.getLimits(pair, address(agEUR), address(EURC));
assertEq(limits.length, 2);
}
}

View File

@@ -17,8 +17,8 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes {
IVault(payable(0xBA12222222228d8Ba445958a75a0704d566BF2C8)); IVault(payable(0xBA12222222228d8Ba445958a75a0704d566BF2C8));
BalancerV2SwapAdapter adapter; BalancerV2SwapAdapter adapter;
IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
IERC20 constant BAL = IERC20(0xba100000625a3754423978a60c9317c58a424e3D); address constant BAL = 0xba100000625a3754423978a60c9317c58a424e3D;
address constant B_80BAL_20WETH = 0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56; address constant B_80BAL_20WETH = 0x5c6Ee304399DBdB9C8Ef030aB642B10820DB8F56;
bytes32 constant B_80BAL_20WETH_POOL_ID = bytes32 constant B_80BAL_20WETH_POOL_ID =
0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014; 0x5c6ee304399dbdb9c8ef030ab642b10820db8f56000200000000000000000014;
@@ -34,20 +34,22 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes {
vm.label(address(balancerV2Vault), "IVault"); vm.label(address(balancerV2Vault), "IVault");
vm.label(address(adapter), "BalancerV2SwapAdapter"); vm.label(address(adapter), "BalancerV2SwapAdapter");
vm.label(address(WETH), "WETH"); vm.label(address(WETH), "WETH");
vm.label(address(BAL), "BAL"); vm.label(BAL, "BAL");
vm.label(address(B_80BAL_20WETH), "B_80BAL_20WETH"); vm.label(address(B_80BAL_20WETH), "B_80BAL_20WETH");
} }
function testPrice() public { function testPrice() public {
uint256[] memory amounts = new uint256[](2); uint256[] memory amounts = new uint256[](2);
amounts[0] = 100; amounts[0] = 1e18;
amounts[1] = 200; amounts[1] = 2e18;
vm.expectRevert(
abi.encodeWithSelector( Fraction[] memory prices =
NotImplemented.selector, "BalancerV2SwapAdapter.price"
)
);
adapter.price(B_80BAL_20WETH_POOL_ID, BAL, WETH, amounts); adapter.price(B_80BAL_20WETH_POOL_ID, BAL, WETH, amounts);
for (uint256 i = 0; i < prices.length; i++) {
assertGt(prices[i].numerator, 0);
assertGt(prices[i].denominator, 0);
}
} }
function testPriceSingleFuzz() public { function testPriceSingleFuzz() public {
@@ -97,17 +99,17 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes {
// TODO calculate the amountIn by using price function as in // TODO calculate the amountIn by using price function as in
// testPriceDecreasing // testPriceDecreasing
deal(address(BAL), address(this), type(uint256).max); deal(BAL, address(this), type(uint256).max);
BAL.approve(address(adapter), type(uint256).max); IERC20(BAL).approve(address(adapter), type(uint256).max);
} else { } else {
vm.assume(specifiedAmount < limits[0]); vm.assume(specifiedAmount < limits[0]);
deal(address(BAL), address(this), specifiedAmount); deal(BAL, address(this), specifiedAmount);
BAL.approve(address(adapter), specifiedAmount); IERC20(BAL).approve(address(adapter), specifiedAmount);
} }
uint256 bal_balance = BAL.balanceOf(address(this)); uint256 bal_balance = IERC20(BAL).balanceOf(address(this));
uint256 weth_balance = WETH.balanceOf(address(this)); uint256 weth_balance = IERC20(WETH).balanceOf(address(this));
Trade memory trade = adapter.swap( Trade memory trade = adapter.swap(
B_80BAL_20WETH_POOL_ID, BAL, WETH, side, specifiedAmount B_80BAL_20WETH_POOL_ID, BAL, WETH, side, specifiedAmount
@@ -117,19 +119,20 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes {
if (side == OrderSide.Buy) { if (side == OrderSide.Buy) {
assertEq( assertEq(
specifiedAmount, specifiedAmount,
WETH.balanceOf(address(this)) - weth_balance IERC20(WETH).balanceOf(address(this)) - weth_balance
); );
assertEq( assertEq(
trade.calculatedAmount, trade.calculatedAmount,
bal_balance - BAL.balanceOf(address(this)) bal_balance - IERC20(BAL).balanceOf(address(this))
); );
} else { } else {
assertEq( assertEq(
specifiedAmount, bal_balance - BAL.balanceOf(address(this)) specifiedAmount,
bal_balance - IERC20(BAL).balanceOf(address(this))
); );
assertEq( assertEq(
trade.calculatedAmount, trade.calculatedAmount,
WETH.balanceOf(address(this)) - weth_balance IERC20(WETH).balanceOf(address(this)) - weth_balance
); );
} }
} }
@@ -144,8 +147,8 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes {
uint256 beforeSwap = vm.snapshot(); uint256 beforeSwap = vm.snapshot();
deal(address(BAL), address(this), amounts[i]); deal(BAL, address(this), amounts[i]);
BAL.approve(address(adapter), amounts[i]); IERC20(BAL).approve(address(adapter), amounts[i]);
trades[i] = adapter.swap( trades[i] = adapter.swap(
B_80BAL_20WETH_POOL_ID, BAL, WETH, OrderSide.Sell, amounts[i] B_80BAL_20WETH_POOL_ID, BAL, WETH, OrderSide.Sell, amounts[i]
); );
@@ -175,8 +178,8 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes {
uint256 amountIn = uint256 amountIn =
(amounts[i] * price.denominator / price.numerator) * 2; (amounts[i] * price.denominator / price.numerator) * 2;
deal(address(BAL), address(this), amountIn); deal(BAL, address(this), amountIn);
BAL.approve(address(adapter), amountIn); IERC20(BAL).approve(address(adapter), amountIn);
trades[i] = adapter.swap( trades[i] = adapter.swap(
B_80BAL_20WETH_POOL_ID, BAL, WETH, OrderSide.Buy, amounts[i] B_80BAL_20WETH_POOL_ID, BAL, WETH, OrderSide.Buy, amounts[i]
); );
@@ -203,19 +206,19 @@ contract BalancerV2SwapAdapterTest is Test, ISwapAdapterTypes {
function testGetCapabilitiesFuzz(bytes32 pool, address t0, address t1) function testGetCapabilitiesFuzz(bytes32 pool, address t0, address t1)
public public
{ {
Capability[] memory res = Capability[] memory res = adapter.getCapabilities(pool, t0, t1);
adapter.getCapabilities(pool, IERC20(t0), IERC20(t1));
assertEq(res.length, 2); assertEq(res.length, 3);
assertEq(uint256(res[0]), uint256(Capability.SellOrder)); assertEq(uint256(res[0]), uint256(Capability.SellOrder));
assertEq(uint256(res[1]), uint256(Capability.BuyOrder)); assertEq(uint256(res[1]), uint256(Capability.BuyOrder));
assertEq(uint256(res[2]), uint256(Capability.PriceFunction));
} }
function testGetTokens() public { function testGetTokens() public {
IERC20[] memory tokens = adapter.getTokens(B_80BAL_20WETH_POOL_ID); address[] memory tokens = adapter.getTokens(B_80BAL_20WETH_POOL_ID);
assertEq(address(tokens[0]), address(BAL)); assertEq(tokens[0], BAL);
assertEq(address(tokens[1]), address(WETH)); assertEq(tokens[1], address(WETH));
} }
function testGetPoolIds() public { function testGetPoolIds() public {

View File

@@ -0,0 +1,410 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import "openzeppelin-contracts/contracts/interfaces/IERC20.sol";
import "src/interfaces/ISwapAdapterTypes.sol";
import "src/libraries/FractionMath.sol";
import "src/etherfi/EtherfiAdapter.sol";
contract EtherfiAdapterTest is Test, ISwapAdapterTypes {
using FractionMath for Fraction;
EtherfiAdapter adapter;
IWeEth weEth = IWeEth(0xCd5fE23C85820F7B72D0926FC9b05b43E359b7ee);
IeEth eEth;
uint256 constant TEST_ITERATIONS = 100;
function setUp() public {
uint256 forkBlock = 19218495;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
adapter = new EtherfiAdapter(address(weEth));
eEth = weEth.eETH();
vm.label(address(weEth), "WeETH");
vm.label(address(eEth), "eETH");
}
receive() external payable {}
function testPriceFuzzEtherfi(uint256 amount0, uint256 amount1) public {
bytes32 pair = bytes32(0);
uint256[] memory limits = adapter.getLimits(
pair, address(address(weEth)), address(address(eEth))
);
vm.assume(amount0 < limits[0] && amount0 > 0);
vm.assume(amount1 < limits[1] && amount1 > 0);
uint256[] memory amounts = new uint256[](2);
amounts[0] = amount0;
amounts[1] = amount1;
Fraction[] memory prices = adapter.price(
pair, address(address(weEth)), address(address(eEth)), amounts
);
for (uint256 i = 0; i < prices.length; i++) {
assertGt(prices[i].numerator, 0);
assertGt(prices[i].denominator, 0);
}
}
function testSwapFuzzEtherfiEethWeEth(uint256 specifiedAmount, bool isBuy)
public
{
OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell;
IERC20 eEth_ = IERC20(address(eEth));
IERC20 weEth_ = IERC20(address(weEth));
bytes32 pair = bytes32(0);
uint256[] memory limits =
adapter.getLimits(pair, address(eEth_), address(weEth_));
if (side == OrderSide.Buy) {
vm.assume(specifiedAmount < limits[1] && specifiedAmount > 100);
/// @dev workaround for eETH "deal", as standard ERC20 does not
/// work(balance is shares)
deal(address(adapter), type(uint256).max);
adapter.swap(
pair,
address(address(0)),
address(eEth_),
OrderSide.Buy,
limits[0]
);
eEth_.approve(address(adapter), type(uint256).max);
} else {
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 100);
/// @dev workaround for eETH "deal", as standard ERC20 does not
/// work(balance is shares)
deal(address(adapter), type(uint128).max);
adapter.swap(
pair,
address(address(0)),
address(eEth_),
OrderSide.Buy,
specifiedAmount
);
eEth_.approve(address(adapter), specifiedAmount);
}
uint256 eEth_balance = eEth_.balanceOf(address(this));
uint256 weEth_balance = weEth_.balanceOf(address(this));
Trade memory trade = adapter.swap(
pair, address(eEth_), address(weEth_), side, specifiedAmount
);
if (trade.calculatedAmount > 0) {
if (side == OrderSide.Buy) {
assertGe(
weEth_.balanceOf(address(this)) - weEth_balance,
specifiedAmount - 2
);
/// @dev Transfer function contains rounding errors because of
/// rewards in weETH contract, therefore we assume a +/-2
/// tolerance
assertLe(
weEth_.balanceOf(address(this)) - weEth_balance,
specifiedAmount
);
assertLe(
eEth_balance - eEth_.balanceOf(address(this)),
trade.calculatedAmount + 2
);
assertGe(
eEth_balance - eEth_.balanceOf(address(this)),
trade.calculatedAmount - 1
);
} else {
assertGe(
specifiedAmount,
eEth_balance - eEth_.balanceOf(address(this))
);
/// @dev Transfer function contains rounding errors because of
/// rewards in eETH contract, therefore we assume a +/-2
/// tolerance
assertLe(
specifiedAmount - 2,
eEth_balance - eEth_.balanceOf(address(this))
);
assertEq(
trade.calculatedAmount,
weEth_.balanceOf(address(this)) - weEth_balance
);
}
}
}
function testSwapFuzzEtherfiWeEthEeth(uint256 specifiedAmount, bool isBuy)
public
{
OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell;
IERC20 eEth_ = IERC20(address(eEth));
IERC20 weEth_ = IERC20(address(weEth));
uint256 weEth_bal_before = weEth_.balanceOf(address(this));
bytes32 pair = bytes32(0);
uint256[] memory limits =
adapter.getLimits(pair, address(weEth_), address(eEth_));
if (side == OrderSide.Buy) {
vm.assume(specifiedAmount < limits[1] && specifiedAmount > 100);
/// @dev workaround for eETH "deal", as standard ERC20 does not
/// work(balance is shares)
deal(address(adapter), type(uint256).max);
adapter.swap(
pair,
address(address(0)),
address(weEth_),
OrderSide.Buy,
limits[0]
);
weEth_.approve(address(adapter), type(uint256).max);
} else {
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 100);
/// @dev workaround for eETH "deal", as standard ERC20 does not
/// work(balance is shares)
deal(address(adapter), type(uint128).max);
adapter.swap(
pair,
address(address(0)),
address(weEth_),
OrderSide.Buy,
specifiedAmount
);
weEth_.approve(address(adapter), specifiedAmount);
}
uint256 eEth_balance = eEth_.balanceOf(address(this));
uint256 weEth_balance = weEth_.balanceOf(address(this));
/// @dev as of rounding errors in Etherfi, specifiedAmount might lose
/// small digits for small numbers
/// therefore we use weEth_balance - weEth_bal_before as specifiedAmount
uint256 realAmountWeEth_ = weEth_balance - weEth_bal_before;
Trade memory trade = adapter.swap(
pair, address(weEth_), address(eEth_), side, realAmountWeEth_
);
if (trade.calculatedAmount > 0) {
if (side == OrderSide.Buy) {
assertGe(
realAmountWeEth_,
eEth_.balanceOf(address(this)) - eEth_balance
);
/// @dev Transfer function contains rounding errors because of
/// rewards in weETH contract, therefore we assume a +/-2
/// tolerance
assertLe(
realAmountWeEth_ - 2,
eEth_.balanceOf(address(this)) - eEth_balance
);
assertLe(
trade.calculatedAmount - 2,
weEth_balance - weEth_.balanceOf(address(this))
);
} else {
assertEq(
realAmountWeEth_,
weEth_balance - weEth_.balanceOf(address(this))
);
assertLe(
trade.calculatedAmount - 2,
eEth_.balanceOf(address(this)) - eEth_balance
);
assertGe(
trade.calculatedAmount,
eEth_.balanceOf(address(this)) - eEth_balance
);
}
}
}
function testSwapFuzzEtherfiEthEeth(uint256 specifiedAmount, bool isBuy)
public
{
OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell;
address eth_ = address(0);
IERC20 eEth_ = IERC20(address(eEth));
bytes32 pair = bytes32(0);
uint256[] memory limits = adapter.getLimits(pair, eth_, address(eEth_));
if (side == OrderSide.Buy) {
vm.assume(specifiedAmount < limits[1] && specifiedAmount > 10);
deal(address(adapter), eEth_.totalSupply());
} else {
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10);
deal(address(adapter), specifiedAmount);
}
uint256 eth_balance = address(adapter).balance;
uint256 eEth_balance = eEth_.balanceOf(address(this));
Trade memory trade =
adapter.swap(pair, eth_, address(eEth_), side, specifiedAmount);
if (trade.calculatedAmount > 0) {
if (side == OrderSide.Buy) {
assertGe(
specifiedAmount,
eEth_.balanceOf(address(this)) - eEth_balance
);
/// @dev Transfer function contains rounding errors because of
/// rewards in eETH contract, therefore we assume a +/-2
/// tolerance
assertLe(
specifiedAmount - 2,
eEth_.balanceOf(address(this)) - eEth_balance
);
assertEq(
trade.calculatedAmount,
eth_balance - address(adapter).balance
);
} else {
assertEq(
specifiedAmount, eth_balance - address(adapter).balance
);
assertEq(
trade.calculatedAmount,
eEth_.balanceOf(address(this)) - eEth_balance
);
}
}
}
function testSwapFuzzEtherfiEthWeEth(uint256 specifiedAmount, bool isBuy)
public
{
OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell;
address eth_ = address(0);
IERC20 weEth_ = IERC20(address(weEth));
bytes32 pair = bytes32(0);
uint256[] memory limits = adapter.getLimits(pair, eth_, address(weEth_));
if (side == OrderSide.Buy) {
vm.assume(specifiedAmount < limits[1] && specifiedAmount > 10);
deal(address(adapter), weEth_.totalSupply());
} else {
vm.assume(specifiedAmount < limits[0] && specifiedAmount > 10);
deal(address(adapter), specifiedAmount);
}
uint256 eth_balance = address(adapter).balance;
uint256 weEth_balance = weEth_.balanceOf(address(this));
Trade memory trade =
adapter.swap(pair, eth_, address(weEth_), side, specifiedAmount);
if (trade.calculatedAmount > 0) {
if (side == OrderSide.Buy) {
assertGe(
specifiedAmount,
weEth_.balanceOf(address(this)) - weEth_balance
);
/// @dev Transfer function contains rounding errors because of
/// rewards in eETH contract, therefore we assume a +/-2
/// tolerance
assertLe(
specifiedAmount - 2,
weEth_.balanceOf(address(this)) - weEth_balance
);
assertEq(
trade.calculatedAmount,
eth_balance - address(adapter).balance
);
} else {
assertEq(
specifiedAmount, eth_balance - address(adapter).balance
);
assertEq(
trade.calculatedAmount,
weEth_.balanceOf(address(this)) - weEth_balance
);
}
}
}
function testSwapSellIncreasingEtherfi() public {
executeIncreasingSwapsEtherfi(OrderSide.Sell);
}
function testSwapBuyIncreasingEtherfi() public {
executeIncreasingSwapsEtherfi(OrderSide.Buy);
}
function executeIncreasingSwapsEtherfi(OrderSide side) internal {
bytes32 pair = bytes32(0);
uint256 amountConstant_ = 10 ** 18;
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
amounts[0] = amountConstant_;
for (uint256 i = 1; i < TEST_ITERATIONS; i++) {
amounts[i] = amountConstant_ * i;
}
Trade[] memory trades = new Trade[](TEST_ITERATIONS);
uint256 beforeSwap;
for (uint256 i = 1; i < TEST_ITERATIONS; i++) {
beforeSwap = vm.snapshot();
deal(address(weEth), address(this), amounts[i]);
IERC20(address(weEth)).approve(address(adapter), amounts[i]);
trades[i] = adapter.swap(
pair,
address(address(weEth)),
address(address(eEth)),
side,
amounts[i]
);
vm.revertTo(beforeSwap);
}
for (uint256 i = 1; i < TEST_ITERATIONS - 1; i++) {
assertLe(trades[i].calculatedAmount, trades[i + 1].calculatedAmount);
assertLe(trades[i].gasUsed, trades[i + 1].gasUsed);
}
}
function testGetCapabilitiesEtherfi(bytes32 pair, address t0, address t1)
public
{
Capability[] memory res =
adapter.getCapabilities(pair, address(t0), address(t1));
assertEq(res.length, 3);
}
function testGetTokensEtherfi() public {
bytes32 pair = bytes32(0);
address[] memory tokens = adapter.getTokens(pair);
assertEq(tokens.length, 3);
}
function testGetLimitsEtherfi() public {
bytes32 pair = bytes32(0);
uint256[] memory limits =
adapter.getLimits(pair, address(eEth), address(weEth));
assertEq(limits.length, 2);
}
}

View File

@@ -12,8 +12,8 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes {
IntegralSwapAdapter adapter; IntegralSwapAdapter adapter;
ITwapRelayer relayer; ITwapRelayer relayer;
IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address constant USDC_WETH_PAIR = 0x2fe16Dd18bba26e457B7dD2080d5674312b026a2; address constant USDC_WETH_PAIR = 0x2fe16Dd18bba26e457B7dD2080d5674312b026a2;
address constant relayerAddress = 0xd17b3c9784510E33cD5B87b490E79253BcD81e2E; address constant relayerAddress = 0xd17b3c9784510E33cD5B87b490E79253BcD81e2E;
@@ -26,7 +26,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes {
relayer = ITwapRelayer(relayerAddress); relayer = ITwapRelayer(relayerAddress);
vm.label(address(WETH), "WETH"); vm.label(address(WETH), "WETH");
vm.label(address(USDC), "USDC"); vm.label(USDC, "USDC");
vm.label(address(USDC_WETH_PAIR), "USDC_WETH_PAIR"); vm.label(address(USDC_WETH_PAIR), "USDC_WETH_PAIR");
} }
@@ -66,8 +66,8 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes {
limitsMin = getMinLimits(USDC, WETH); limitsMin = getMinLimits(USDC, WETH);
vm.assume(specifiedAmount > limitsMin[1] * 115 / 100); vm.assume(specifiedAmount > limitsMin[1] * 115 / 100);
deal(address(USDC), address(this), type(uint256).max); deal(USDC, address(this), type(uint256).max);
USDC.approve(address(adapter), type(uint256).max); IERC20(USDC).approve(address(adapter), type(uint256).max);
} else { } else {
limits = adapter.getLimits(pair, USDC, WETH); limits = adapter.getLimits(pair, USDC, WETH);
vm.assume(specifiedAmount < limits[0]); vm.assume(specifiedAmount < limits[0]);
@@ -75,12 +75,12 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes {
limitsMin = getMinLimits(USDC, WETH); limitsMin = getMinLimits(USDC, WETH);
vm.assume(specifiedAmount > limitsMin[0] * 115 / 100); vm.assume(specifiedAmount > limitsMin[0] * 115 / 100);
deal(address(USDC), address(this), type(uint256).max); deal(USDC, address(this), type(uint256).max);
USDC.approve(address(adapter), specifiedAmount); IERC20(USDC).approve(address(adapter), specifiedAmount);
} }
uint256 usdc_balance_before = USDC.balanceOf(address(this)); uint256 usdc_balance_before = IERC20(USDC).balanceOf(address(this));
uint256 weth_balance_before = WETH.balanceOf(address(this)); uint256 weth_balance_before = IERC20(WETH).balanceOf(address(this));
Trade memory trade = Trade memory trade =
adapter.swap(pair, USDC, WETH, side, specifiedAmount); adapter.swap(pair, USDC, WETH, side, specifiedAmount);
@@ -89,22 +89,22 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes {
if (side == OrderSide.Buy) { if (side == OrderSide.Buy) {
assertEq( assertEq(
specifiedAmount, specifiedAmount,
WETH.balanceOf(address(this)) - weth_balance_before IERC20(WETH).balanceOf(address(this)) - weth_balance_before
); );
assertEq( assertEq(
trade.calculatedAmount, trade.calculatedAmount,
usdc_balance_before - USDC.balanceOf(address(this)) usdc_balance_before - IERC20(USDC).balanceOf(address(this))
); );
} else { } else {
assertEq( assertEq(
specifiedAmount, specifiedAmount,
usdc_balance_before - USDC.balanceOf(address(this)) usdc_balance_before - IERC20(USDC).balanceOf(address(this))
); );
assertEq( assertEq(
trade.calculatedAmount, trade.calculatedAmount,
WETH.balanceOf(address(this)) - weth_balance_before IERC20(WETH).balanceOf(address(this)) - weth_balance_before
); );
} }
} }
@@ -135,8 +135,8 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes {
for (uint256 i = 1; i < TEST_ITERATIONS; i++) { for (uint256 i = 1; i < TEST_ITERATIONS; i++) {
beforeSwap = vm.snapshot(); beforeSwap = vm.snapshot();
deal(address(USDC), address(this), amounts[i]); deal(USDC, address(this), amounts[i]);
USDC.approve(address(adapter), amounts[i]); IERC20(USDC).approve(address(adapter), amounts[i]);
trades[i] = adapter.swap(pair, USDC, WETH, side, amounts[i]); trades[i] = adapter.swap(pair, USDC, WETH, side, amounts[i]);
vm.revertTo(beforeSwap); vm.revertTo(beforeSwap);
@@ -152,15 +152,14 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes {
function testGetCapabilitiesIntegral(bytes32 pair, address t0, address t1) function testGetCapabilitiesIntegral(bytes32 pair, address t0, address t1)
public public
{ {
Capability[] memory res = Capability[] memory res = adapter.getCapabilities(pair, t0, t1);
adapter.getCapabilities(pair, IERC20(t0), IERC20(t1));
assertEq(res.length, 4); assertEq(res.length, 4);
} }
function testGetTokensIntegral() public { function testGetTokensIntegral() public {
bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR)); bytes32 pair = bytes32(bytes20(USDC_WETH_PAIR));
IERC20[] memory tokens = adapter.getTokens(pair); address[] memory tokens = adapter.getTokens(pair);
assertEq(tokens.length, 2); assertEq(tokens.length, 2);
} }
@@ -172,7 +171,7 @@ contract IntegralSwapAdapterTest is Test, ISwapAdapterTypes {
assertEq(limits.length, 2); assertEq(limits.length, 2);
} }
function getMinLimits(IERC20 sellToken, IERC20 buyToken) function getMinLimits(address sellToken, address buyToken)
public public
view view
returns (uint256[] memory limits) returns (uint256[] memory limits)

View File

@@ -11,8 +11,8 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes {
using FractionMath for Fraction; using FractionMath for Fraction;
UniswapV2SwapAdapter adapter; UniswapV2SwapAdapter adapter;
IERC20 constant WETH = IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); address constant WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
IERC20 constant USDC = IERC20(0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48); address constant USDC = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
address constant USDC_WETH_PAIR = 0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc; address constant USDC_WETH_PAIR = 0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc;
uint256 constant TEST_ITERATIONS = 100; uint256 constant TEST_ITERATIONS = 100;
@@ -24,9 +24,9 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes {
new UniswapV2SwapAdapter(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f); new UniswapV2SwapAdapter(0x5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f);
vm.label(address(adapter), "UniswapV2SwapAdapter"); vm.label(address(adapter), "UniswapV2SwapAdapter");
vm.label(address(WETH), "WETH"); vm.label(WETH, "WETH");
vm.label(address(USDC), "USDC"); vm.label(USDC, "USDC");
vm.label(address(USDC_WETH_PAIR), "USDC_WETH_PAIR"); vm.label(USDC_WETH_PAIR, "USDC_WETH_PAIR");
} }
function testPriceFuzz(uint256 amount0, uint256 amount1) public { function testPriceFuzz(uint256 amount0, uint256 amount1) public {
@@ -75,17 +75,17 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes {
// TODO calculate the amountIn by using price function as in // TODO calculate the amountIn by using price function as in
// BalancerV2 testPriceDecreasing // BalancerV2 testPriceDecreasing
deal(address(USDC), address(this), type(uint256).max); deal(USDC, address(this), type(uint256).max);
USDC.approve(address(adapter), type(uint256).max); IERC20(USDC).approve(address(adapter), type(uint256).max);
} else { } else {
vm.assume(specifiedAmount < limits[0]); vm.assume(specifiedAmount < limits[0]);
deal(address(USDC), address(this), specifiedAmount); deal(USDC, address(this), specifiedAmount);
USDC.approve(address(adapter), specifiedAmount); IERC20(USDC).approve(address(adapter), specifiedAmount);
} }
uint256 usdc_balance = USDC.balanceOf(address(this)); uint256 usdc_balance = IERC20(USDC).balanceOf(address(this));
uint256 weth_balance = WETH.balanceOf(address(this)); uint256 weth_balance = IERC20(WETH).balanceOf(address(this));
Trade memory trade = Trade memory trade =
adapter.swap(pair, USDC, WETH, side, specifiedAmount); adapter.swap(pair, USDC, WETH, side, specifiedAmount);
@@ -94,20 +94,20 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes {
if (side == OrderSide.Buy) { if (side == OrderSide.Buy) {
assertEq( assertEq(
specifiedAmount, specifiedAmount,
WETH.balanceOf(address(this)) - weth_balance IERC20(WETH).balanceOf(address(this)) - weth_balance
); );
assertEq( assertEq(
trade.calculatedAmount, trade.calculatedAmount,
usdc_balance - USDC.balanceOf(address(this)) usdc_balance - IERC20(USDC).balanceOf(address(this))
); );
} else { } else {
assertEq( assertEq(
specifiedAmount, specifiedAmount,
usdc_balance - USDC.balanceOf(address(this)) usdc_balance - IERC20(USDC).balanceOf(address(this))
); );
assertEq( assertEq(
trade.calculatedAmount, trade.calculatedAmount,
WETH.balanceOf(address(this)) - weth_balance IERC20(WETH).balanceOf(address(this)) - weth_balance
); );
} }
} }
@@ -130,8 +130,8 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes {
for (uint256 i = 0; i < TEST_ITERATIONS; i++) { for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
beforeSwap = vm.snapshot(); beforeSwap = vm.snapshot();
deal(address(USDC), address(this), amounts[i]); deal(USDC, address(this), amounts[i]);
USDC.approve(address(adapter), amounts[i]); IERC20(USDC).approve(address(adapter), amounts[i]);
trades[i] = adapter.swap(pair, USDC, WETH, side, amounts[i]); trades[i] = adapter.swap(pair, USDC, WETH, side, amounts[i]);
vm.revertTo(beforeSwap); vm.revertTo(beforeSwap);
@@ -149,8 +149,7 @@ contract UniswapV2PairFunctionTest is Test, ISwapAdapterTypes {
} }
function testGetCapabilities(bytes32 pair, address t0, address t1) public { function testGetCapabilities(bytes32 pair, address t0, address t1) public {
Capability[] memory res = Capability[] memory res = adapter.getCapabilities(pair, t0, t1);
adapter.getCapabilities(pair, IERC20(t0), IERC20(t1));
assertEq(res.length, 3); assertEq(res.length, 3);
} }