Sdk implementation: Sfrax adapter and substream (#91)
* feat: initial setup * feat: implemented getCapabilities and getTokens * chore: adjusted getPoolIds * feat: Initial implementation of getLimits() * feat: implemented getLimits, getTokens and internal functions for amounts * feat: implemented price * feat: implemented swap function * fix and test: fixed minor issues on adapter and setup test * debugging price function * fix: debugged price function and fixed getPriceAt function * test: testOneIncreasingPriceFoundFraxV3SFrax * fix: debugging and fixing buy function * fix: fixed getPriceAt * test: testing post trade price * fix: Fixed getPriceAt * fix: Fixed getLimits * fix: Fixed prices and improved readability * fix: Fixed price and transfers in swap * feat: Finished tests * chore: Changed approve to safeIncreaseAllowance * feat: created substream for staked frax * feat: remove useless files * feat: fixed dependencies in cargo.toml * feat: rename folder * feat: updated cargo.lock * feat: changed lib.rs, added modules.rs * feat: update modules.rs with corrects addresses * feat: rename folder in ethereum-sfrax * feat: remove useless comments, change locked asset address, rename * feat: undo changes on mod.rs in ethereum-balancer * feat: rename variable * feat: update substreams/cargo.toml * feat: modify ristfmt.toml * feat: performed code formatting * feat: modify src/abi * feat: performed formatting with nightly * feat: fix addition opeation * feat: adjust code with for i, f * feat: performed fmt inside ethereum-sfrax folder * feat: performed clippy * feat: fix with clippy warning suggests * feat: undo any change in ethereum-balancer * feat: change stakedfrax_contract.rs * fix: stakedfrax_contract.rs * feat: add blank line * feat: add #[allow(clippy::all)] on ethereum-sfrax/src/abi/mod.rs * feat: update comments on pool_factories.rs * feat: update cargo.toml and substreams.yaml * feat: add tycho evm in pb folder * feat: add params to take contracts' addresses * feat: add logic to map rewards_cycle * feat: performed fmt and fix versioning * feat: remove useless functions * feat: add logic to track rewards_to_distribute * feat: passing CI * fix: substreams.yaml * feat: fixed params in manifest Co-authored-by: mrBovo <bovo.ignazio@proton.me> * feat: fixed error in map_relative_balances function * feat: passing CI checks * fix: 🐛 hex-binary address encoding + refactoring vault-> underlying map * style: 💄 fix formatting * feat: Implemented testPoolBehaviour * alignment with propeller main * Update forge-std submodule reference to include ds-test * files update to match propeller/main * creating integration_test fir sfrax * fixed FraxV3SFraxAdapter.sol import paths * updated with correct addresses FraxV3SFraxAdapter manifest.yaml * updated sfrax manifest.yaml * updated to support sdk * integration_test sfrax updated * fix: 🐛 add reward processing * chore: ♻️ minor cleanups * fix: Fixed adapter limits * fix: fix adapter and substream sdk tests * fix: Fixed CI errors * chore: fmt * chore: Removed unused line * fix: Fixed clippy warnings * chore: formatted with rustup * chore: Removed unused line * chore: post-build formatting * chore: Formatting using different toolchain vesion * chore: 💄 format * chore: 💄 format 2 * chore: Using static address for frax * feat: Added second constructor param for sfrax * fix: Fixed limits on frax sell * chore: Fixed merge conflict with sfraxeth * chore: Remove sfraxeth_contract changes * chore: Fixed EOFs * fix: Fixed fmt on stakedfrax contract --------- Co-authored-by: mp-web3 <mp.web3.t@gmail.com> Co-authored-by: gabrir99 <gabri.ruini@gmail.com> Co-authored-by: mrBovo <bovo.ignazio@proton.me> Co-authored-by: Ignazio Bovo <ignazio@jsgenesis.com> Co-authored-by: mrBovo <bovoignazio.dev@gmail.com> Co-authored-by: Mattia <mp.web3@gmail.com>
This commit is contained in:
312
evm/src/sfrax/FraxV3SFraxAdapter.sol
Normal file
312
evm/src/sfrax/FraxV3SFraxAdapter.sol
Normal file
@@ -0,0 +1,312 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-or-later
|
||||
pragma experimental ABIEncoderV2;
|
||||
pragma solidity ^0.8.13;
|
||||
|
||||
import {ISwapAdapter} from "../interfaces/ISwapAdapter.sol";
|
||||
import {
|
||||
IERC20,
|
||||
ERC20
|
||||
} from "../../lib/openzeppelin-contracts/contracts/token/ERC20/ERC20.sol";
|
||||
import {SafeERC20} from
|
||||
"../../lib/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 FraxV3SFraxAdapter
|
||||
/// @dev Adapter for FraxV3 protocol, supports Frax --> sFrax and sFrax --> Frax
|
||||
contract FraxV3SFraxAdapter is ISwapAdapter {
|
||||
using SafeERC20 for IERC20;
|
||||
using FixedPointMathLib for uint256;
|
||||
|
||||
uint256 constant PRECISE_UNIT = 1e18;
|
||||
|
||||
ISFrax immutable sFrax;
|
||||
IERC20 immutable frax;
|
||||
|
||||
constructor(address _sFrax, address _frax) {
|
||||
sFrax = ISFrax(_sFrax);
|
||||
frax = IERC20(_frax);
|
||||
}
|
||||
|
||||
/// @dev Check if tokens in input are supported
|
||||
modifier onlySupportedTokens(address sellToken, address buyToken) {
|
||||
if (
|
||||
(sellToken != address(frax) && sellToken != address(sFrax))
|
||||
|| (buyToken != address(frax) && buyToken != address(sFrax))
|
||||
|| buyToken == sellToken
|
||||
) {
|
||||
revert Unavailable("This adapter only supports FRAX<->SFRAX swaps");
|
||||
}
|
||||
_;
|
||||
}
|
||||
|
||||
/// @inheritdoc ISwapAdapter
|
||||
function price(
|
||||
bytes32,
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
uint256[] memory _specifiedAmounts
|
||||
)
|
||||
external
|
||||
view
|
||||
override
|
||||
onlySupportedTokens(sellToken, buyToken)
|
||||
returns (Fraction[] memory _prices)
|
||||
{
|
||||
_prices = new Fraction[](_specifiedAmounts.length);
|
||||
uint256[] memory _limits = getLimits(bytes32(0), sellToken, buyToken);
|
||||
|
||||
for (uint256 i = 0; i < _specifiedAmounts.length; i++) {
|
||||
// prevent price above sell limits as pool will revert for
|
||||
// underflow/overflow on mint/redeem
|
||||
_checkLimits(_limits, OrderSide.Sell, _specifiedAmounts[i]);
|
||||
|
||||
_prices[i] =
|
||||
getPriceAt(sellToken == address(frax), _specifiedAmounts[i]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @inheritdoc ISwapAdapter
|
||||
function swap(
|
||||
bytes32,
|
||||
address sellToken,
|
||||
address buyToken,
|
||||
OrderSide side,
|
||||
uint256 specifiedAmount
|
||||
)
|
||||
external
|
||||
override
|
||||
onlySupportedTokens(sellToken, buyToken)
|
||||
returns (Trade memory trade)
|
||||
{
|
||||
if (
|
||||
specifiedAmount == 0
|
||||
|| (sellToken == address(frax) && specifiedAmount < 2)
|
||||
) {
|
||||
return trade;
|
||||
}
|
||||
|
||||
// prevent swap above sell limits as pool will revert for
|
||||
// underflow/overflow on mint/redeem
|
||||
uint256[] memory _limits = getLimits(bytes32(0), sellToken, buyToken);
|
||||
_checkLimits(_limits, side, specifiedAmount);
|
||||
|
||||
uint256 gasBefore = gasleft();
|
||||
if (side == OrderSide.Sell) {
|
||||
// sell
|
||||
trade.calculatedAmount = sell(sellToken, specifiedAmount);
|
||||
} else {
|
||||
// buy
|
||||
trade.calculatedAmount = buy(sellToken, specifiedAmount);
|
||||
}
|
||||
trade.gasUsed = gasBefore - gasleft();
|
||||
|
||||
uint256 numerator = sellToken == address(frax)
|
||||
? sFrax.previewDeposit(PRECISE_UNIT)
|
||||
: sFrax.previewRedeem(PRECISE_UNIT);
|
||||
|
||||
trade.price = Fraction(numerator, PRECISE_UNIT);
|
||||
}
|
||||
|
||||
/// @inheritdoc ISwapAdapter
|
||||
/// @dev there is no hard capped limit
|
||||
function getLimits(bytes32, address sellToken, address buyToken)
|
||||
public
|
||||
view
|
||||
override
|
||||
onlySupportedTokens(address(sellToken), address(buyToken))
|
||||
returns (uint256[] memory limits)
|
||||
{
|
||||
limits = new uint256[](2);
|
||||
|
||||
if (sellToken == address(frax)) {
|
||||
// Frax --> sFrax
|
||||
limits[0] = type(uint128).max;
|
||||
limits[1] = type(uint128).max;
|
||||
} else {
|
||||
limits[1] = sFrax.storedTotalAssets();
|
||||
limits[0] = sFrax.previewWithdraw(limits[1]);
|
||||
}
|
||||
}
|
||||
|
||||
/// @inheritdoc ISwapAdapter
|
||||
function getCapabilities(bytes32, address, address)
|
||||
external
|
||||
pure
|
||||
override
|
||||
returns (Capability[] memory capabilities)
|
||||
{
|
||||
capabilities = new Capability[](5);
|
||||
capabilities[0] = Capability.SellOrder;
|
||||
capabilities[1] = Capability.BuyOrder;
|
||||
capabilities[2] = Capability.PriceFunction;
|
||||
capabilities[3] = Capability.ConstantPrice;
|
||||
capabilities[4] = Capability.HardLimits;
|
||||
}
|
||||
|
||||
/// @inheritdoc ISwapAdapter
|
||||
function getTokens(bytes32)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (address[] memory tokens)
|
||||
{
|
||||
tokens = new address[](2);
|
||||
|
||||
tokens[0] = address(frax);
|
||||
tokens[1] = address(sFrax);
|
||||
}
|
||||
|
||||
/// @inheritdoc ISwapAdapter
|
||||
/// @dev Since FraxV3 is a single pool that supports FRAX and SFRAX, we
|
||||
/// return it directly
|
||||
function getPoolIds(uint256, uint256)
|
||||
external
|
||||
view
|
||||
override
|
||||
returns (bytes32[] memory ids)
|
||||
{
|
||||
ids = new bytes32[](1);
|
||||
ids[0] = bytes32(bytes20(address(sFrax)));
|
||||
}
|
||||
|
||||
/// @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)
|
||||
{
|
||||
IERC20(sellToken).safeTransferFrom(msg.sender, address(this), amount);
|
||||
if (sellToken == address(sFrax)) {
|
||||
return sFrax.redeem(amount, msg.sender, address(this));
|
||||
} else {
|
||||
IERC20(sellToken).safeIncreaseAllowance(address(sFrax), amount);
|
||||
return sFrax.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(sFrax)) {
|
||||
uint256 amountIn = sFrax.previewWithdraw(amount);
|
||||
IERC20(sellToken).safeTransferFrom(
|
||||
msg.sender, address(this), amountIn
|
||||
);
|
||||
return sFrax.withdraw(amount, msg.sender, address(this));
|
||||
} else {
|
||||
uint256 amountIn = sFrax.previewMint(amount);
|
||||
IERC20(sellToken).safeTransferFrom(
|
||||
msg.sender, address(this), amountIn
|
||||
);
|
||||
IERC20(sellToken).safeIncreaseAllowance(address(sFrax), amountIn);
|
||||
return sFrax.mint(amount, msg.sender);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Calculates prices for a specified amount
|
||||
/// @param isSellFrax True if selling frax, false if selling sFrax
|
||||
/// @param amountIn The amount of the token being sold.
|
||||
/// @return (fraction) price as a fraction corresponding to the provided
|
||||
/// amount.
|
||||
function getPriceAt(bool isSellFrax, uint256 amountIn)
|
||||
internal
|
||||
view
|
||||
returns (Fraction memory)
|
||||
{
|
||||
if (isSellFrax == true) {
|
||||
if (amountIn < 2) {
|
||||
revert("Amount In must be greater than 1");
|
||||
}
|
||||
|
||||
return Fraction(sFrax.previewDeposit(amountIn), amountIn);
|
||||
} else {
|
||||
return Fraction(sFrax.previewRedeem(amountIn), amountIn);
|
||||
}
|
||||
}
|
||||
|
||||
/// @notice Checks if the specified amount is within the hard limits
|
||||
/// @dev If not, reverts
|
||||
/// @param limits The limits of the tokens being traded.
|
||||
/// @param side The side of the trade.
|
||||
/// @param specifiedAmount The amount to be traded.
|
||||
function _checkLimits(
|
||||
uint256[] memory limits,
|
||||
OrderSide side,
|
||||
uint256 specifiedAmount
|
||||
) internal pure {
|
||||
if (side == OrderSide.Sell && specifiedAmount > limits[0]) {
|
||||
require(specifiedAmount < limits[0], "Limit exceeded");
|
||||
} else if (side == OrderSide.Buy && specifiedAmount > limits[1]) {
|
||||
require(specifiedAmount < limits[1], "Limit exceeded");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
interface ISFrax {
|
||||
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);
|
||||
|
||||
function previewDistributeRewards()
|
||||
external
|
||||
view
|
||||
returns (uint256 _rewardToDistribute);
|
||||
|
||||
function pricePerShare() external view returns (uint256);
|
||||
|
||||
function asset() external view returns (ERC20); // FRAX
|
||||
|
||||
function totalSupply() external view returns (uint256);
|
||||
|
||||
function totalAssets() external view returns (uint256);
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -13,7 +13,7 @@ constants:
|
||||
- PriceFunction
|
||||
|
||||
# The file containing the adapter contract
|
||||
contract: IntegralSwapAdapter.sol
|
||||
contract: FraxV3SFraxAdapter.sol
|
||||
|
||||
# Deployment instances used to generate chain specific bytecode.
|
||||
instances:
|
||||
@@ -21,16 +21,17 @@ instances:
|
||||
name: mainnet
|
||||
id: 0
|
||||
arguments:
|
||||
- "0xd17b3c9784510E33cD5B87b490E79253BcD81e2E"
|
||||
- "0xA663B02CF0a4b149d2aD41910CB81e23e1c41c32"
|
||||
- "0x853d955aCEf822Db058eb8505911ED77F175b99e"
|
||||
|
||||
# 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
|
||||
- pool_id: "0xA663B02CF0a4b149d2aD41910CB81e23e1c41c32" # sFrax Vault
|
||||
sell_token: "0x853d955aCEf822Db058eb8505911ED77F175b99e" # Frax
|
||||
buy_token: "0xA663B02CF0a4b149d2aD41910CB81e23e1c41c32" # sFrax
|
||||
block: 19270612
|
||||
chain:
|
||||
id: 0
|
||||
name: mainnet
|
||||
Reference in New Issue
Block a user