Sfraxeth adapter and substream with SDK integration (#87)

* first commit

* feat: implemented get tokens and improved modifier

* feat: added missing functions (to implement)

* feat: implemented getPoolIds

* feat: implemented getCapabilities

* feat: implemented getLimits function

* fix: fixed constructor

* feat and fix: implemented testGetLimitsFraxEthV3 and fixed getLimits

* feat: implementing getPriceAt function

* feat and fix: finished implementing getPriceAt, fixed modifier

* feat: price function and tests implemented

* fix: removed onlySupportedTokens modifier from getPriceAt and applied it in price and swap function

* feat: implemented sell, buy, and swap functions

* implementing final tests

* aligned with main branch

* fixes

* fix: Review Fixes

* feat: 🎨 sfraxeth substream initial scaffolding

* fix: 🎨 protocol component creation at deployment block

* build: 💚 cargo build

* feat: 🎨 map proper event to balance changes

* fix: 🚚 remove unnecessary files

* fix: 💚 ci checks

Due to the CI checks requiring the latest rust nightly, the rust-lang organisation
introduced new doc related rules. This commit fixes the CI errors by making
the necessary changes to the comments in substreams-balancer comments

* fix: 🐛 wasm output name

* fix: 🐛 update starting block = deployment block -1

* feat: 🎨 add store for reward cycles and update balances accounting after after deposit before Withdraw

* feat: 🎨 finish setting up block reward logic

* docs: 📝 add comments on extra module

* build: 📌 adapt dependencies to workspace dependencies

setting prost-types to workspace version causes build errors

* feat: 🎨 add support for several EVM compatible networks

* fix: 🐛 update balance delta accounting logic following the `NextRewardCycle` event only

* fix: 🐛 hex address string param encoding

* fix: 🐛 deployment transaction check

* ci: 💚 ci check passing

* fix: 🐛 issues with hex-binary encoding

* refactor: ♻️ address mappings for various networks

* fix: 💚 formatting

* feat: Implemented testPoolBehaviour

* chore: Removed unused comments

* feat: ⬆️ update to recent sdk

* feat: 🎨 testing setup

* test:  setup test environminte for sfraxeth

* fix: 🐛 unwrap error in map_protocol_changes

* build: ⬆️ update rust version

* build:  remove unnecessary deps

* build: 🚚 remove unnecessary pb/tycho

* fix: 🐛 remove balance owner attribute

* fix: 🐛 remove unnecessary static attributes

* fix: 🐛 remove manual updates

* fix: 🔥 remove unused data model from contract.proto

* fix: 🐛 filter by known components

* feat:  use store delta for reward change accounting

* refactor: ♻️ remove shallow create vault component

* feat:  replace is_deployment_tx logic with simpler txn match

* test:  manual testing with inspection against etherscan

https://etherscan.io/address/0xac3E018457B222d93114458476f3E3416Abbe38F#events

* ci: 💚 ci checks

* fix: 🐛 map_protocol_components output data

* fix: 🐛 output type on map_protocol_changes

* test: 🧪 skip balance checks

* fixed FraxV3FrxEthAdapter arguments for constructor in manifest.yaml

* fix: 🐛 adapter error with overflow/underflow and addresses

* restore: restored previous adater version

* fix: set minimum swap amount to prevent overflow/underflow

* fix: set minimum swap amount only for sfrxETH -> frxETH

* improve: added print block_number to runner.py when get_amout_out fails

* removed console.log

* alignment with propeller main

* Update forge-std submodule reference to include ds-test

* installed protosim_py 0.21.0

* commented out minimum swap amount for sfrxEth -> frxEth pair

* updated adapter limits

* working on fixes

* fix: Adjust getLimits according to protocol limitation.

Previously limits were estimated with token supplies, this commit simplifies limits and adjusts them so they correspond closely with what is supported by the sfrxETH contract.

* chore: fmt

* wip: Changed ubuntu to 20.04, fmt adapters

* wip: Updated python tests

* wip: Trying with ubuntu: latest

* chore: fmt adapters

* wip: Using ubuntu 20.04

* chore: Switched back to ubuntu-latest

---------

Co-authored-by: Ignazio Bovo <ignazio@jsgenesis.com>
Co-authored-by: domenicodev <domenico.romeo3919@gmail.com>
Co-authored-by: kayibal <alan@datarevenue.com>
Co-authored-by: domenicodev <domenico.rom3@gmail.com>
This commit is contained in:
Mattia Papa
2024-10-23 15:17:45 +02:00
committed by GitHub
parent 0dda680ab2
commit 3013cd9bfd
26 changed files with 7256 additions and 79 deletions

View File

@@ -0,0 +1,366 @@
// 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,
ERC20
} from "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
import {SafeERC20} from
"openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";
library FixedPointMathLib {
uint256 internal constant MAX_UINT256 = 2 ** 256 - 1;
function mulDivDown(uint256 x, uint256 y, uint256 denominator)
internal
pure
returns (uint256 z)
{
/// @solidity memory-safe-assembly
assembly {
// Equivalent to require(denominator != 0 && (y == 0 || x <=
// type(uint256).max / y))
if iszero(
mul(denominator, iszero(mul(y, gt(x, div(MAX_UINT256, y)))))
) { revert(0, 0) }
// Divide x * y by the denominator.
z := div(mul(x, y), denominator)
}
}
}
/// @title FraxV3FrxEthAdapter
/// Adapter for frxETH and sfrxETH tokens of FraxV3
/// @dev This contract only supports: ETH -> sfrxETH and frxETH <-> sfrxETH
contract FraxV3FrxEthAdapter is ISwapAdapter {
using SafeERC20 for IERC20;
using FixedPointMathLib for uint256;
uint256 constant PRECISE_UNIT = 1e18;
IFrxEth immutable frxEth;
IFrxEthMinter immutable frxEthMinter;
ISfrxEth immutable sfrxEth;
constructor(address _frxEthMinter, address _sfrxEth) {
sfrxEth = ISfrxEth(_sfrxEth);
frxEth = IFrxEth(address(0x5E8422345238F34275888049021821E8E08CAa1f));
frxEthMinter = IFrxEthMinter(_frxEthMinter);
}
/// @dev check input tokens for allowed trades
function isSwapNotSupported(address sellToken, address buyToken)
internal
view
returns (bool)
{
if (
(
sellToken != address(frxEth) && sellToken != address(sfrxEth)
&& sellToken != address(0)
) || (buyToken != address(frxEth) && buyToken != address(sfrxEth))
|| (sellToken == address(0) && buyToken != address(sfrxEth))
|| buyToken == sellToken
) {
return true;
}
return false;
}
/// @dev enable receive to fill the contract with ether for payable swaps
receive() external payable {}
/// @inheritdoc ISwapAdapter
function price(
bytes32,
address sellToken,
address buyToken,
uint256[] memory _specifiedAmounts
) external view override returns (Fraction[] memory _prices) {
_prices = new Fraction[](_specifiedAmounts.length);
if (isSwapNotSupported(sellToken, buyToken)) {
return _prices;
}
for (uint256 i = 0; i < _specifiedAmounts.length; i++) {
_prices[i] = getPriceAt(sellToken, _specifiedAmounts[i]);
}
}
/// @inheritdoc ISwapAdapter
/// @notice Executes a swap on the contract.
/// @param sellToken The token being sold.
/// @param buyToken The token being bought.
/// @param side Either buy or sell.
/// @param specifiedAmount The amount to be traded.
/// @return trade The amount of tokens being sold or bought.
function swap(
bytes32,
address sellToken,
address buyToken,
OrderSide side,
uint256 specifiedAmount
) external override returns (Trade memory trade) {
trade.price = Fraction(PRECISE_UNIT, PRECISE_UNIT);
trade.calculatedAmount = 0;
trade.gasUsed = 0;
if (isSwapNotSupported(sellToken, buyToken)) {
return trade;
}
uint256 gasBefore = gasleft();
if (side == OrderSide.Sell) {
trade.calculatedAmount = sell(sellToken, specifiedAmount);
trade.calculatedAmount != 0
? trade.price = Fraction(trade.calculatedAmount, specifiedAmount)
: trade.price;
} else {
trade.calculatedAmount = buy(sellToken, specifiedAmount);
trade.calculatedAmount != 0
? trade.price = Fraction(specifiedAmount, trade.calculatedAmount)
: trade.price;
}
trade.gasUsed = gasBefore - gasleft();
}
/// @inheritdoc ISwapAdapter
function getLimits(bytes32, address sellToken, address buyToken)
external
view
override
returns (uint256[] memory limits)
{
limits = new uint256[](2);
limits[0] = 0;
limits[1] = 0;
if (isSwapNotSupported(sellToken, buyToken)) {
return limits;
}
if (sellToken == address(frxEth) || sellToken == address(0)) {
// No limits when minting sfrxEth, the protocol has no limits when
// creating
// new sfrxETH as long as the user holds enough frxETH.
limits[0] = type(uint128).max;
limits[1] = type(uint128).max;
} else if (buyToken == address(frxEth)) {
uint256 totalAssets = sfrxEth.totalAssets();
// the amount of sfrxEth a user can exchange for frxETH is limited
// by the
// amount of frxEth owned by the protocol, we convert that into
// shares to
// get the corresponding sfrxEth amount.
limits[0] = sfrxEth.previewWithdraw(totalAssets);
// frxETH a user can buy is limited by the contracts balance
limits[1] = totalAssets;
}
return limits;
}
/// @inheritdoc ISwapAdapter
function getCapabilities(bytes32, address, address)
external
pure
override
returns (Capability[] memory capabilities)
{
capabilities = new Capability[](4);
capabilities[0] = Capability.SellOrder;
capabilities[1] = Capability.BuyOrder;
capabilities[2] = Capability.PriceFunction;
capabilities[3] = Capability.ConstantPrice;
}
/// @inheritdoc ISwapAdapter
function getTokens(bytes32)
external
view
override
returns (address[] memory tokens)
{
tokens = new address[](2);
tokens[0] = frxEthMinter.frxETHToken();
tokens[1] = frxEthMinter.sfrxETHToken();
}
/// @inheritdoc ISwapAdapter
/// @dev although FraxV3 frxETH has no pool ids, we return the sFrxETH and
/// frxETHMinter addresses as pools
function getPoolIds(uint256, uint256)
external
view
override
returns (bytes32[] memory ids)
{
ids = new bytes32[](2);
ids[0] = bytes20(address(sfrxEth));
ids[1] = bytes20(address(frxEthMinter));
}
/// @notice Executes a sell order on the contract.
/// @param sellToken The token being sold.
/// @param amount The amount to be traded.
/// @return calculatedAmount The amount of tokens received.
function sell(address sellToken, uint256 amount)
internal
returns (uint256 calculatedAmount)
{
if (sellToken == address(0)) {
return frxEthMinter.submitAndDeposit{value: amount}(msg.sender);
}
IERC20(sellToken).safeTransferFrom(msg.sender, address(this), amount);
if (sellToken == address(sfrxEth)) {
return sfrxEth.redeem(amount, msg.sender, address(this));
} else {
IERC20(sellToken).safeIncreaseAllowance(address(sfrxEth), amount);
return sfrxEth.deposit(amount, msg.sender);
}
}
/// @notice Executes a buy order on the contract.
/// @param sellToken The token being sold.
/// @param amount The amount of buyToken to receive.
/// @return calculatedAmount The amount of tokens received.
function buy(address sellToken, uint256 amount)
internal
returns (uint256 calculatedAmount)
{
if (sellToken == address(0)) {
uint256 amountIn = sfrxEth.previewMint(amount);
frxEthMinter.submit{value: amountIn}();
IERC20(address(frxEth)).safeIncreaseAllowance(
address(sfrxEth), amountIn
);
return sfrxEth.mint(amount, msg.sender);
}
if (sellToken == address(sfrxEth)) {
uint256 amountIn = sfrxEth.previewWithdraw(amount);
IERC20(sellToken).safeTransferFrom(
msg.sender, address(this), amountIn
);
return sfrxEth.withdraw(amount, msg.sender, address(this));
} else {
uint256 amountIn = sfrxEth.previewMint(amount);
IERC20(sellToken).safeTransferFrom(
msg.sender, address(this), amountIn
);
IERC20(sellToken).safeIncreaseAllowance(address(sfrxEth), amountIn);
return sfrxEth.mint(amount, msg.sender);
}
}
/// @notice Calculates prices for a specified amount
/// @dev frxEth is 1:1 eth
/// @param sellToken the token to sell
/// @param amountIn The amount of the token being sold.
/// @return (fraction) price as a fraction corresponding to the provided
/// amount.
function getPriceAt(address sellToken, uint256 amountIn)
internal
view
returns (Fraction memory)
{
if (sellToken == address(frxEth) || sellToken == address(0)) {
// calculate price sfrxEth/frxEth
uint256 totStoredAssets = sfrxEth.totalAssets() + amountIn;
uint256 newMintedShares = sfrxEth.previewDeposit(amountIn);
uint256 totMintedShares = sfrxEth.totalSupply() + newMintedShares;
uint256 numerator =
PRECISE_UNIT.mulDivDown(totMintedShares, totStoredAssets);
return Fraction(numerator, PRECISE_UNIT);
} else {
// calculate price frxEth/sfrxEth
uint256 fraxAmountRedeemed = sfrxEth.previewRedeem(amountIn);
uint256 totStoredAssets = sfrxEth.totalAssets() - fraxAmountRedeemed;
uint256 totMintedShares = sfrxEth.totalSupply() - amountIn;
uint256 numerator = totMintedShares == 0
? PRECISE_UNIT
: PRECISE_UNIT.mulDivDown(totStoredAssets, totMintedShares);
return Fraction(numerator, PRECISE_UNIT);
}
}
}
interface IFrxEth {
function balanceOf(address) external view returns (uint256);
function totalSupply() external view returns (uint256);
function minters(address) external view returns (bool);
}
interface ISfrxEth {
/// @dev even though the balance address of frxETH token is around 223,701
/// tokens, it returns 0 when the
/// address of frxEth is passed as an argument
function balanceOf(address) external view returns (uint256);
function previewDeposit(uint256 assets) external view returns (uint256);
function previewMint(uint256 shares) external view returns (uint256);
function previewRedeem(uint256 shares) external view returns (uint256);
function previewWithdraw(uint256 assets) external view returns (uint256);
/// @dev returns the totalSupply of frxETH
function totalSupply() external view returns (uint256);
/// @notice Compute the amount of tokens available to share holders
function totalAssets() external view returns (uint256);
function asset() external view returns (ERC20);
function deposit(uint256 assets, address receiver)
external
returns (uint256 shares);
function mint(uint256 shares, address receiver)
external
returns (uint256 assets);
function storedTotalAssets() external view returns (uint256);
function withdraw(uint256 assets, address receiver, address owner)
external
returns (uint256 shares);
function redeem(uint256 shares, address receiver, address owner)
external
returns (uint256 assets);
}
interface IFrxEthMinter {
//function sfrxETHTokenContract() external view returns (ISfrxEth);
function sfrxETHToken() external view returns (address);
function frxETHToken() external view returns (address);
function currentWithheldETH() external view returns (uint256);
function DEPOSIT_SIZE() external view returns (uint256);
/// @notice Mint frxETH to the sender depending on the ETH value sent
function submit() external payable;
/// @notice Mint frxETH and deposit it to receive sfrxETH in one transaction
function submitAndDeposit(address recipient)
external
payable
returns (uint256 shares);
}

View File

@@ -0,0 +1,37 @@
# information about the author helps us reach out in case of issues.
author:
name: Propellerheads.xyz
email: alan@propellerheads.xyz
# 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: FraxV3Adapter.sol
# Deployment instances used to generate chain specific bytecode.
instances:
- chain:
name: mainnet
id: 0
arguments:
- "0xbAFA44EFE7901E04E39Dad13167D089C559c1138"
- "0xac3E018457B222d93114458476f3E3416Abbe38F"
# 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

@@ -0,0 +1,273 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;
import "./AdapterTest.sol";
import "forge-std/Test.sol";
import "forge-std/console.sol";
import "src/interfaces/ISwapAdapterTypes.sol";
import "src/libraries/FractionMath.sol";
import "src/sfraxeth/FraxV3FrxEthAdapter.sol";
contract FraxV3FrxEthAdapterTest is Test, ISwapAdapterTypes, AdapterTest {
using FractionMath for Fraction;
FraxV3FrxEthAdapter adapter;
address FRAXETH_ADDRESS;
address constant SFRAXETH_ADDRESS =
0xac3E018457B222d93114458476f3E3416Abbe38F;
address constant FRAXETHMINTER_ADDRESS =
0xbAFA44EFE7901E04E39Dad13167D089C559c1138;
address constant ETH_ADDRESS = address(0);
IERC20 FRAXETH;
IERC20 constant SFRAXETH =
IERC20(0xac3E018457B222d93114458476f3E3416Abbe38F);
IERC20 constant WBTC = IERC20(0x2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599);
uint256 constant TEST_ITERATIONS = 10;
uint256 constant AMOUNT0 = 1000000000000000000;
bytes32 constant PAIR = bytes32(0);
function setUp() public {
uint256 forkBlock = 19341682;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
adapter =
new FraxV3FrxEthAdapter(FRAXETHMINTER_ADDRESS, SFRAXETH_ADDRESS);
FRAXETH = IERC20(address(ISfrxEth(address(SFRAXETH)).asset()));
FRAXETH_ADDRESS = address(FRAXETH);
}
/////////////////////////////////////// PRICE
// ////////////////////////////////////
/// @dev set lower limit to greater than 1, because previewDeposit returns 0
/// with an amountIn == 1
function testPriceFuzzFraxEthV3FraxEth(uint256 amount0, uint256 amount1)
public
{
uint256[] memory limits =
adapter.getLimits(PAIR, FRAXETH_ADDRESS, SFRAXETH_ADDRESS);
vm.assume(amount0 < limits[0]);
vm.assume(amount0 > 1);
vm.assume(amount1 < limits[0]);
vm.assume(amount1 > 1);
uint256[] memory amounts = new uint256[](2);
amounts[0] = amount0;
amounts[1] = amount1;
Fraction[] memory prices =
adapter.price(PAIR, FRAXETH_ADDRESS, SFRAXETH_ADDRESS, amounts);
for (uint256 i = 0; i < prices.length; i++) {
assertGt(prices[i].numerator, 0);
assertGt(prices[i].denominator, 0);
}
}
/// @dev The price is kept among swaps if no FRAX rewards are distributed in
/// the contract during time
function testPriceKeepingSellFraxEthFraxEthV3() public {
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
amounts[i] = 1000 * (i + 1) * 10 ** 18;
}
Fraction[] memory prices =
adapter.price(PAIR, FRAXETH_ADDRESS, SFRAXETH_ADDRESS, amounts);
for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) {
assertEq(prices[i].compareFractions(prices[i + 1]), 0);
assertGt(prices[i].denominator, 0);
assertGt(prices[i + 1].denominator, 0);
}
}
function testPriceKeepingSellSFraxEthFraxEthV3() public {
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
amounts[i] = 1000 * (i + 1) * 10 ** 18;
}
Fraction[] memory prices =
adapter.price(PAIR, SFRAXETH_ADDRESS, FRAXETH_ADDRESS, amounts);
for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) {
assertEq(prices[i].compareFractions(prices[i + 1]), 0);
assertGt(prices[i].denominator, 0);
assertGt(prices[i + 1].denominator, 0);
}
}
function testPriceKeepingSellEthFraxEthV3() public {
uint256[] memory amounts = new uint256[](TEST_ITERATIONS);
for (uint256 i = 0; i < TEST_ITERATIONS; i++) {
amounts[i] = 1000 * (i + 1) * 10 ** 18;
}
Fraction[] memory prices =
adapter.price(PAIR, ETH_ADDRESS, SFRAXETH_ADDRESS, amounts);
for (uint256 i = 0; i < TEST_ITERATIONS - 1; i++) {
assertEq(prices[i].compareFractions(prices[i + 1]), 0);
assertGt(prices[i].denominator, 0);
assertGt(prices[i + 1].denominator, 0);
}
}
//////////////////////////////////////// SWAP
// ///////////////////////////////////
function testSwapFuzzsFraxEthV3WithFraxEth(
uint256 specifiedAmount,
bool isBuy
) public {
vm.assume(specifiedAmount > 1);
OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell;
uint256[] memory limits =
adapter.getLimits(PAIR, FRAXETH_ADDRESS, SFRAXETH_ADDRESS);
if (side == OrderSide.Buy) {
vm.assume(specifiedAmount < limits[1]);
deal(address(FRAXETH), address(this), type(uint256).max);
FRAXETH.approve(address(adapter), type(uint256).max);
} else {
vm.assume(specifiedAmount < limits[0]);
deal(address(FRAXETH), address(this), specifiedAmount);
FRAXETH.approve(address(adapter), specifiedAmount);
}
uint256 frxEth_balance_before = FRAXETH.balanceOf(address(this));
uint256 sfrxEth_balance_before = SFRAXETH.balanceOf(address(this));
Trade memory trade = adapter.swap(
PAIR, FRAXETH_ADDRESS, SFRAXETH_ADDRESS, side, specifiedAmount
);
uint256 frxEth_balance_after = FRAXETH.balanceOf(address(this));
uint256 sfrxEth_balance_after = SFRAXETH.balanceOf(address(this));
if (trade.calculatedAmount > 0) {
if (side == OrderSide.Buy) {
assertEq(
specifiedAmount,
sfrxEth_balance_after - sfrxEth_balance_before
);
assertEq(
trade.calculatedAmount,
frxEth_balance_before - frxEth_balance_after
);
} else {
assertEq(
specifiedAmount,
frxEth_balance_before - frxEth_balance_after
);
assertEq(
trade.calculatedAmount,
sfrxEth_balance_after - sfrxEth_balance_before
);
}
}
}
function testSwapFuzzFraxEthV3WithSFraxEth(
uint256 specifiedAmount,
bool isBuy
) public {
vm.assume(specifiedAmount > 1);
OrderSide side = isBuy ? OrderSide.Buy : OrderSide.Sell;
uint256[] memory limits =
adapter.getLimits(PAIR, SFRAXETH_ADDRESS, FRAXETH_ADDRESS);
if (side == OrderSide.Buy) {
vm.assume(specifiedAmount < limits[1]);
deal(address(SFRAXETH), address(this), type(uint256).max);
SFRAXETH.approve(address(adapter), type(uint256).max);
} else {
vm.assume(specifiedAmount < limits[0]);
deal(address(SFRAXETH), address(this), specifiedAmount);
SFRAXETH.approve(address(adapter), specifiedAmount);
}
uint256 frxEth_balance_before = FRAXETH.balanceOf(address(this));
uint256 sfrxEth_balance_before = SFRAXETH.balanceOf(address(this));
ISfrxEth(SFRAXETH_ADDRESS).totalAssets();
Trade memory trade = adapter.swap(
PAIR, SFRAXETH_ADDRESS, FRAXETH_ADDRESS, side, specifiedAmount
);
uint256 frxEth_balance_after = FRAXETH.balanceOf(address(this));
uint256 sfrxEth_balance_after = SFRAXETH.balanceOf(address(this));
if (trade.calculatedAmount > 0) {
if (side == OrderSide.Buy) {
assertEq(
specifiedAmount,
frxEth_balance_after - frxEth_balance_before
);
assertEq(
trade.calculatedAmount,
sfrxEth_balance_before - sfrxEth_balance_after
);
} else {
assertEq(
specifiedAmount,
sfrxEth_balance_before - sfrxEth_balance_after
);
assertEq(
trade.calculatedAmount,
frxEth_balance_after - frxEth_balance_before
);
}
}
}
function testGetTokensFraxEthV3() public {
address[] memory tokens = adapter.getTokens(bytes32(0));
assertEq(tokens[0], FRAXETH_ADDRESS);
assertEq(tokens[1], SFRAXETH_ADDRESS);
}
function testGetLimitsFraxEthV3() public {
uint256[] memory limits =
adapter.getLimits(bytes32(0), FRAXETH_ADDRESS, SFRAXETH_ADDRESS);
assertEq(limits.length, 2);
adapter.getLimits(bytes32(0), ETH_ADDRESS, SFRAXETH_ADDRESS);
assertEq(limits.length, 2);
adapter.getLimits(bytes32(0), SFRAXETH_ADDRESS, FRAXETH_ADDRESS);
assertEq(limits.length, 2);
}
function testGetCapabilitiesFraxEthV3() public {
Capability[] memory res =
adapter.getCapabilities(bytes32(0), ETH_ADDRESS, FRAXETH_ADDRESS);
assertEq(res.length, 4);
}
// This test is currently broken due to a bug in runPoolBehaviour
// with constant price pools.
//
// function testPoolBehaviourFraxV3Sfrax() public {
// bytes32[] memory poolIds = new bytes32[](1);
// poolIds[0] = bytes32(0);
// runPoolBehaviourTest(adapter, poolIds);
// }
}