Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

SolidlyAdapter #186

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion chains/RealLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ library RealLib {
address public constant TOKEN_USDC = 0xc518A88c67CECA8B3f24c4562CB71deeB2AF86B7;
address public constant TOKEN_SACRA = 0x6B2e0fACD2F2A8f407aC591067Ac06b5d29247E4;

// AMMs
// CL AMMs
// 21.10.2024: TVL $3.22M, APR 84.6%
address public constant POOL_PEARL_arcUSD_USDC_100 = 0x22aC4821bBb8d1AC42eA7F0f32ed415F52577Ca1;
// 21.10.2024: TVL $3.08M, APR 84.77%
Expand Down Expand Up @@ -73,6 +73,14 @@ library RealLib {
// 21.10.2024: TVL $78.34K, APR 0%
address public constant POOL_PEARL_SACRA_reETH_10000 = 0x2EC05Ab55867719f433d8ab0a446C48003B3BE8F;

// StableSwap AMMs
address public constant POOL_PEARL_MORE_USDC = 0x1733720f30EF013539Fa2EcEE00671A60B66243D;
address public constant POOL_PEARL_stRWA_RWA = 0xb28d015563c81dd66Ab781853c03B7B66aa46C1b;

// VolatileV1 AMMs
address public constant POOL_PEARL_USDC_PEARL = 0xAFb84DFe257cBA43d2f44B4367aDAc5a82775275;
address public constant POOL_PEARL_arcUSD_PEARL = 0xd3Ae2AF8f1Fb00af2e363fe6592C19ABF9105FA9;

// ALMs
address public constant TRIDENT_LIQUID_BOX_FACTORY = 0xEfeFFa36b9FA787b500b2d18a3829271526023b1;

Expand Down
168 changes: 168 additions & 0 deletions src/adapters/SolidlyAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Strings} from "@openzeppelin/contracts/utils/Strings.sol";
import {IERC165} from "@openzeppelin/contracts/utils/introspection/IERC165.sol";
import {IControllable} from "../interfaces/IControllable.sol";
import {IAmmAdapter} from "../interfaces/IAmmAdapter.sol";
import {Controllable} from "../core/base/Controllable.sol";
import {AmmAdapterIdLib} from "./libs/AmmAdapterIdLib.sol";
import {ISolidlyPool} from "../integrations/solidly/ISolidlyPool.sol";
import {ConstantsLib} from "../core/libs/ConstantsLib.sol";

/// @title AMM adapter for Solidly forks
/// @author Alien Deployer (https://github.com/a17)
contract SolidlyAdapter is Controllable, IAmmAdapter {
using SafeERC20 for IERC20;

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @inheritdoc IControllable
string public constant VERSION = "1.0.0";

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INITIALIZATION */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @inheritdoc IAmmAdapter
function init(address platform_) external initializer {
__Controllable_init(platform_);
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* USER ACTIONS */

Check warning on line 39 in src/adapters/SolidlyAdapter.sol

View check run for this annotation

Codecov / codecov/patch

src/adapters/SolidlyAdapter.sol#L39

Added line #L39 was not covered by tests
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @inheritdoc IAmmAdapter
//slither-disable-next-line reentrancy-events
function swap(
address pool,
address tokenIn,
address tokenOut,
address recipient,
uint priceImpactTolerance
) external {
uint amountIn = IERC20(tokenIn).balanceOf(address(this));
uint amountOut = ISolidlyPool(pool).getAmountOut(amountIn, tokenIn);
uint balanceBefore = IERC20(tokenOut).balanceOf(recipient);

uint amount1 = 10 ** IERC20Metadata(tokenIn).decimals();
uint priceBefore = getPrice(pool, tokenIn, tokenOut, amount1);

uint amount0Out;
uint amount1Out;
{
(address token0,) = _sortTokens(tokenIn, tokenOut);
(amount0Out, amount1Out) = tokenIn == token0 ? (uint(0), amountOut) : (amountOut, uint(0));

IERC20(tokenIn).safeTransfer(pool, amountIn);
}
ISolidlyPool(pool).swap(amount0Out, amount1Out, recipient, new bytes(0));

uint priceAfter = getPrice(pool, tokenIn, tokenOut, amount1);
uint priceImpact = (priceBefore - priceAfter) * ConstantsLib.DENOMINATOR / priceBefore;

Check notice

Code scanning / Semgrep OSS

Semgrep Finding: rules.solidity.security.basic-arithmetic-underflow Note

Possible arithmetic underflow
if (priceImpact >= priceImpactTolerance) {
revert(string(abi.encodePacked("!PRICE ", Strings.toString(priceImpact))));
}

uint balanceAfter = IERC20(tokenOut).balanceOf(recipient);
emit SwapInPool(
pool,
tokenIn,
tokenOut,
recipient,
priceImpactTolerance,
amountIn,
balanceAfter > balanceBefore ? balanceAfter - balanceBefore : 0
);
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* VIEW FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

/// @inheritdoc IAmmAdapter
function ammAdapterId() external pure returns (string memory) {
return AmmAdapterIdLib.SOLIDLY;
}

/// @inheritdoc IAmmAdapter
function poolTokens(address pool) public view returns (address[] memory tokens) {
tokens = new address[](2);
tokens[0] = ISolidlyPool(pool).token0();
tokens[1] = ISolidlyPool(pool).token1();
}

/// @inheritdoc IAmmAdapter
function getLiquidityForAmounts(
address pool,
uint[] memory amounts
) external view returns (uint liquidity, uint[] memory amountsConsumed) {
amountsConsumed = new uint[](2);
(uint reserveA, uint reserveB) = _getReserves(pool);
uint amountBOptimal = _quoteAddLiquidity(amounts[0], reserveA, reserveB);
if (amountBOptimal <= amounts[1]) {
(amountsConsumed[0], amountsConsumed[1]) = (amounts[0], amountBOptimal);
} else {
uint amountAOptimal = _quoteAddLiquidity(amounts[1], reserveB, reserveA);
(amountsConsumed[0], amountsConsumed[1]) = (amountAOptimal, amounts[1]);
}

uint _totalSupply = ISolidlyPool(pool).totalSupply();
liquidity = Math.min(amountsConsumed[0] * _totalSupply / reserveA, amountsConsumed[1] * _totalSupply / reserveB);

Check warning on line 118 in src/adapters/SolidlyAdapter.sol

View check run for this annotation

Codecov / codecov/patch

src/adapters/SolidlyAdapter.sol#L118

Added line #L118 was not covered by tests
}

/// @inheritdoc IAmmAdapter

Check warning on line 121 in src/adapters/SolidlyAdapter.sol

View check run for this annotation

Codecov / codecov/patch

src/adapters/SolidlyAdapter.sol#L121

Added line #L121 was not covered by tests
function getProportions(address pool) external view returns (uint[] memory props) {
props = new uint[](2);
if (ISolidlyPool(pool).stable()) {
address token1 = ISolidlyPool(pool).token1();
uint token1Decimals = IERC20Metadata(token1).decimals();
uint token1Price = getPrice(pool, token1, address(0), 10 ** token1Decimals);
(uint reserve0, uint reserve1) = _getReserves(pool);
uint reserve1Priced = reserve1 * token1Price / 10 ** token1Decimals;
uint totalPriced = reserve0 + reserve1Priced;
props[0] = reserve0 * 1e18 / totalPriced;
props[1] = reserve1Priced * 1e18 / totalPriced;
} else {
props[0] = 5e17;
props[1] = 5e17;
}
}
Comment on lines +122 to +137

Check warning

Code scanning / Slither

Divide before multiply Medium


/// @inheritdoc IAmmAdapter
function getPrice(address pool, address tokenIn, address, /*tokenOut*/ uint amount) public view returns (uint) {
return ISolidlyPool(pool).getAmountOut(amount, tokenIn);
}

/// @inheritdoc IERC165
function supportsInterface(bytes4 interfaceId) public view override(Controllable, IERC165) returns (bool) {

Check warning on line 145 in src/adapters/SolidlyAdapter.sol

View check run for this annotation

Codecov / codecov/patch

src/adapters/SolidlyAdapter.sol#L144-L145

Added lines #L144 - L145 were not covered by tests
return interfaceId == type(IAmmAdapter).interfaceId || super.supportsInterface(interfaceId);
}

/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL LOGIC */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/

Check warning on line 151 in src/adapters/SolidlyAdapter.sol

View check run for this annotation

Codecov / codecov/patch

src/adapters/SolidlyAdapter.sol#L151

Added line #L151 was not covered by tests

/// @dev Returns sorted token addresses, used to handle return values from pairs sorted in this order

Check warning on line 153 in src/adapters/SolidlyAdapter.sol

View check run for this annotation

Codecov / codecov/patch

src/adapters/SolidlyAdapter.sol#L153

Added line #L153 was not covered by tests
function _sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
}

function _getReserves(address pool) internal view returns (uint reserveA, uint reserveB) {
address[] memory tokens = poolTokens(pool);
(address token0,) = _sortTokens(tokens[0], tokens[1]);
(uint reserve0, uint reserve1,) = ISolidlyPool(pool).getReserves();
(reserveA, reserveB) = tokens[0] == token0 ? (reserve0, reserve1) : (reserve1, reserve0);
}
Comment on lines +158 to +163

Check warning

Code scanning / Slither

Unused return Medium


function _quoteAddLiquidity(uint amountA, uint reserveA, uint reserveB) internal pure returns (uint) {
return amountA * reserveB / reserveA;
}
}
1 change: 1 addition & 0 deletions src/adapters/libs/AmmAdapterIdLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,5 @@ library AmmAdapterIdLib {
string public constant ALGEBRA = "Algebra";
string public constant KYBER = "KyberSwap";
string public constant CURVE = "Curve";
string public constant SOLIDLY = "Solidly";
}
134 changes: 134 additions & 0 deletions src/integrations/solidly/ISolidlyPool.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.23;

interface ISolidlyPool {
/// @notice True if pool is stable, false if volatile
function stable() external view returns (bool);

/// @notice Returns the decimal (dec), reserves (r), stable (st), and tokens (t) of token0 and token1
function metadata()
external
view
returns (uint dec0, uint dec1, uint r0, uint r1, bool st, address t0, address t1);

/// @notice Claim accumulated but unclaimed fees (claimable0 and claimable1)
function claimFees() external returns (uint, uint);

/// @notice Returns [token0, token1]
function tokens() external view returns (address, address);

/// @notice Address of token in the pool with the lower address value
function token0() external view returns (address);

/// @notice Address of token in the poool with the higher address value
function token1() external view returns (address);

/// @notice Amount of token0 in pool
function reserve0() external view returns (uint);

/// @notice Amount of token1 in pool
function reserve1() external view returns (uint);

/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);

/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);

/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);

/// @notice Returns the amount of tokens in existence.
function totalSupply() external view returns (uint);

/// @notice Amount of unclaimed, but claimable tokens from fees of token0 for an LP
function claimable0(address) external view returns (uint);

/// @notice Amount of unclaimed, but claimable tokens from fees of token1 for an LP
function claimable1(address) external view returns (uint);

/// @notice Get the amount of tokenOut given the amount of tokenIn
/// @param amountIn Amount of token in
/// @param tokenIn Address of token
/// @return Amount out
function getAmountOut(uint amountIn, address tokenIn) external view returns (uint);

/// @notice Update reserves and, on the first call per block, price accumulators
/// @return _reserve0 .
/// @return _reserve1 .
/// @return _blockTimestampLast .
function getReserves() external view returns (uint _reserve0, uint _reserve1, uint _blockTimestampLast);

/// @notice This low-level function should be called from a contract which performs important safety checks
/// @param amount0Out Amount of token0 to send to `to`
/// @param amount1Out Amount of token1 to send to `to`
/// @param to Address to recieve the swapped output
/// @param data Additional calldata for flashloans
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;

/// @notice This low-level function should be called from a contract which performs important safety checks
/// standard uniswap v2 implementation
/// @param to Address to receive token0 and token1 from burning the pool token
/// @return amount0 Amount of token0 returned
/// @return amount1 Amount of token1 returned
function burn(address to) external returns (uint amount0, uint amount1);

/// @notice This low-level function should be called by addLiquidity functions in Router.sol, which performs important safety checks
/// standard uniswap v2 implementation
/// @param to Address to receive the minted LP token
/// @return liquidity Amount of LP token minted
function mint(address to) external returns (uint liquidity);

/// @notice Force balances to match reserves
/// @param to Address to receive any skimmed rewards
function skim(address to) external;

/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint amount) external returns (bool);

/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint value,
uint deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;

}
4 changes: 2 additions & 2 deletions src/interfaces/IAmmAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ interface IAmmAdapter is IERC165 {
/// pool price.
/// This function signature can be used only for non-concentrated AMMs.
/// @param pool Address of a pool supported by the adapter
/// @param amounts Ampunts of pool assets
/// @param amounts Amounts of pool assets
/// @return liquidity Liquidity out value
/// @return amountsConsumed Amounts of consumed assets when providing liquidity
function getLiquidityForAmounts(
Expand All @@ -62,7 +62,7 @@ interface IAmmAdapter is IERC165 {

/// @notice Priced proportions of pool assets
/// @param pool Address of a pool supported by the adapter
/// @return Proportions with 5 decimals precision. Max is 100_000, min is 0.
/// @return Proportions with 18 decimals precision. Max is 1e18, min is 0.
function getProportions(address pool) external view returns (uint[] memory);

/// @notice Current price in pool without amount impact
Expand Down
Loading
Loading