Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

feat: Inception integration #44

Open
wants to merge 2 commits 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
130 changes: 130 additions & 0 deletions evm/src/inception-eth/InceptionEthSwapAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";

uint256 constant TOKEN_DECIMALS = 10 ** 18;

/// @title InceptionEthSwapAdapter
contract InceptionEthSwapAdapter is ISwapAdapter {
using SafeERC20 for IERC20;

IInceptionPool immutable pool;
IInceptionToken immutable token;

constructor(IInceptionPool _pool, IInceptionToken _token) {
pool = IInceptionPool(_pool);
token = IInceptionToken(_token);
}

/// @inheritdoc ISwapAdapter
function price(
bytes32,
address,
address,
uint256[] memory specifiedAmounts
) external override view returns (Fraction[] memory prices) {
prices = new Fraction[](specifiedAmounts.length);
uint256 ratio = token.ratio();

for (uint256 i = 0; i < specifiedAmounts.length; i++) {
prices[i] = Fraction((ratio / TOKEN_DECIMALS) * specifiedAmounts[i], 1);
}
}

/// @inheritdoc ISwapAdapter
function swap(
bytes32,
address,
address,
OrderSide,
uint256 specifiedAmount
) external returns (Trade memory trade) {
require(address(this).balance >= specifiedAmount, "Incorrect ETH amount sent");
if (specifiedAmount == 0) {
return trade;
}

uint256 gasBefore = gasleft();
trade.calculatedAmount = sellETH(specifiedAmount);
trade.gasUsed = gasBefore - gasleft();

trade.price = Fraction(specifiedAmount * token.ratio(), TOKEN_DECIMALS);
}

/// @notice Executes a sell order (pool stake).
/// @param amount The amount of ETH to be staked.
/// @return uint256 The amount of tokens received.
function sellETH(uint256 amount) internal returns (uint256) {
pool.stake{value: amount}();
uint256 shares = token.balanceOf(address(this));
if (shares == 0) {
revert Unavailable("Shares is zero!");
}

return shares;
}

/// @inheritdoc ISwapAdapter
function getLimits(bytes32, address, address)
external
pure
override
returns (uint256[] memory limits)
{
limits = new uint256[](2);
limits[0] = 100;
limits[1] = 1e25;
}

/// @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.PriceFunction;
}

/// @inheritdoc ISwapAdapter
function getTokens(bytes32)
external
pure
override
returns (address[] memory)
{
revert NotImplemented("InceptionStEthSwapAdapter.getTokens");
}

/// @inheritdoc ISwapAdapter
function getPoolIds(uint256, uint256)
external
pure
override
returns (bytes32[] memory)
{
revert NotImplemented("InceptionStEthSwapAdapter.getPoolIds");
}

function availableToStake() external view returns (uint256) {
return pool.availableToStake();
}

/// @notice Fallback function to receive ETH
receive() external payable {}
}

interface IInceptionPool {
function getMinStake() external view returns (uint256);
function stake() external payable;
function availableToStake() external view returns (uint256);
}

interface IInceptionToken is IERC20 {
function ratio() external view returns (uint256);
}
20 changes: 20 additions & 0 deletions evm/src/inception-eth/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
author:
name: neanvo
email: [email protected]

constants:
protocol_gas: 130000
capabilities:
- SellSide
- PriceFunction

contract: InceptionEthSwapAdapter.sol

instances:
- chain:
name: mainnet
id: 1
arguments:
- "0x46199cAa0e453971cedf97f926368d9E5415831a"
- "0xf073bAC22DAb7FaF4a3Dd6c6189a70D54110525C"

122 changes: 122 additions & 0 deletions evm/src/inception/InceptionSwapAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import {IERC20} from "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "openzeppelin-contracts/contracts/token/ERC20/utils/SafeERC20.sol";

import {ISwapAdapter} from "src/interfaces/ISwapAdapter.sol";

uint256 constant TOKEN_DECIMALS = 10 ** 18;

/// @title InceptionSwapAdapter
/// @notice This is a Inception adapter implementation compatible with all vaults.
contract InceptionSwapAdapter is ISwapAdapter {
using SafeERC20 for IERC20;

IInceptionVault immutable vault;
uint256 internal minLimit;
uint256 internal maxLimit;

constructor(IInceptionVault _vault, uint256 _minLimit, uint256 _maxLimit) {
vault = IInceptionVault(_vault);
minLimit = _minLimit;
maxLimit = _maxLimit;
}

/// @inheritdoc ISwapAdapter
function price(
bytes32,
address,
address,
uint256[] memory specifiedAmounts
) external view override returns (Fraction[] memory prices) {
prices = new Fraction[](specifiedAmounts.length);
uint256 ratio = vault.ratio();

for (uint256 i = 0; i < specifiedAmounts.length; i++) {
prices[i] = Fraction((ratio / TOKEN_DECIMALS) * specifiedAmounts[i], 1);
}
}

/// @inheritdoc ISwapAdapter
function swap(
bytes32,
address sellToken,
address,
OrderSide,
uint256 specifiedAmount
) external returns (Trade memory trade) {
if (specifiedAmount == 0) {
return trade;
}

uint256 gasBefore = gasleft();
trade.calculatedAmount = sell(sellToken, specifiedAmount);
trade.gasUsed = gasBefore - gasleft();

trade.price = Fraction(specifiedAmount * vault.ratio(), TOKEN_DECIMALS);
}

/// @notice Executes a sell order (vault deposit).
/// @param amount The amount to be deposited.
/// @return uint256 The amount of tokens received.
function sell(address sellToken, uint256 amount)
internal
returns (uint256)
{
IERC20(sellToken).approve(address(vault), amount);
uint256 shares = vault.deposit(amount, address(this));
if (shares == 0) {
revert Unavailable("Shares is zero!");
}

return shares;
}

/// @inheritdoc ISwapAdapter
function getLimits(bytes32, address, address)
external
view
override
returns (uint256[] memory limits)
{
limits = new uint256[](2);
limits[0] = minLimit;
limits[1] = maxLimit;
}

/// @inheritdoc ISwapAdapter
function getCapabilities(
bytes32,
address,
address
) external pure returns (Capability[] memory capabilities) {
capabilities = new Capability[](2);
capabilities[0] = Capability.SellOrder;
capabilities[1] = Capability.PriceFunction;
}

/// @inheritdoc ISwapAdapter
function getTokens(bytes32)
external
pure
returns (address[] memory)
{
revert NotImplemented("InceptionStEthSwapAdapter.getTokens");
}

/// @inheritdoc ISwapAdapter
function getPoolIds(uint256, uint256)
external
pure
returns (bytes32[] memory)
{
revert NotImplemented("InceptionStEthSwapAdapter.getPoolIds");
}
}

interface IInceptionVault {
function ratio() external view returns (uint256);
function deposit(uint256 amount, address receiver) external returns (uint256);
}

29 changes: 29 additions & 0 deletions evm/src/inception/manifest.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
author:
name: neanvo
email: [email protected]

constants:
protocol_gas: 260000
capabilities:
- SellSide
- PriceFunction

contracts:
- InceptionStEthSwapAdapter.sol
- InceptionREthSwapAdapter.sol

instances:
- chain:
name: mainnet
id: 1
arguments:
- "0x814cc6b8fd2555845541fb843f37418b05977d8d"
- 100
- 1e23
- chain:
name: mainnet
id: 1
arguments:
- "0x1Aa53BC4Beb82aDf7f5EDEE9e3bBF3434aD59F12"
- 100
- 1e23
80 changes: 80 additions & 0 deletions evm/test/InceptionEthSwapAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "src/interfaces/ISwapAdapterTypes.sol";
import "src/inception-eth/InceptionEthSwapAdapter.sol";
import "src/libraries/FractionMath.sol";

/// @title InceptionEthSwapAdapterTest
contract InceptionEthSwapAdapterTest is Test, ISwapAdapterTypes {
using FractionMath for Fraction;

InceptionEthSwapAdapter adapter;

address constant INETH = 0xf073bAC22DAb7FaF4a3Dd6c6189a70D54110525C;

uint256 constant TEST_ITERATIONS = 100;

function setUp() public {
uint256 forkBlock = 20000000;
vm.createSelectFork(vm.rpcUrl("mainnet"), forkBlock);
adapter = new InceptionEthSwapAdapter(
IInceptionPool(0x46199cAa0e453971cedf97f926368d9E5415831a),
IInceptionToken(0xf073bAC22DAb7FaF4a3Dd6c6189a70D54110525C)
);

vm.label(address(adapter), "InceptionEthSwapAdapter");
vm.label(INETH, "INETH");
}

function testPriceFuzzInception(uint256 amount0, uint256 amount1) public view {
bytes32 pair = bytes32(bytes20(address(adapter)));

uint256[] memory amounts = new uint256[](2);
amounts[0] = amount0;
amounts[1] = amount1;

Fraction[] memory prices = adapter.price(pair, address(0), INETH, amounts);

for (uint256 i = 0; i < prices.length; i++) {
assertGe(prices[i].numerator, 0);
assertGe(prices[i].denominator, 0);
}
}

function testSwapFuzz(uint256 specifiedAmount, bool) public {
bytes32 pair = bytes32(bytes20(address(adapter)));

uint256[] memory limits = adapter.getLimits(pair, address(0), INETH);
vm.assume(specifiedAmount > limits[0]);
vm.assume(specifiedAmount < limits[1]);
vm.assume(specifiedAmount < adapter.availableToStake());

(bool success, ) = address(adapter).call{value: specifiedAmount}("");
require(success);
uint256 eth_balance = address(adapter).balance;
uint256 ineth_balance = IERC20(INETH).balanceOf(address(adapter));

Trade memory trade =
adapter.swap(pair, address(0), INETH, OrderSide.Sell, specifiedAmount);

if (trade.calculatedAmount > 0) {
assertApproxEqAbs(
specifiedAmount,
eth_balance - address(adapter).balance,
2
);
assertApproxEqAbs(
trade.calculatedAmount,
IERC20(INETH).balanceOf(address(adapter)) - ineth_balance,
2
);
assertLe(trade.gasUsed, 130000);
}
}
}

interface ILidoEth {
function submit(address) external payable;
}
Loading
Loading