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

SDK integration Renzo protocol #116

Open
wants to merge 50 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
4c5b091
feat: Initial Setup and interfaces
domenicodev Apr 8, 2024
e69afc0
fix: Fixed ezETH conversion in constructor
domenicodev Apr 8, 2024
8e5c689
fix: Fixed ezETH conversion in constructor
domenicodev Apr 8, 2024
6ca1098
Updated manifest
domenicodev Apr 8, 2024
4140c89
feat: Implemented getTokens
domenicodev Apr 8, 2024
abfbd29
feat: Implemented getLimits
domenicodev Apr 11, 2024
d231bfd
feat: Implemented price
domenicodev Apr 11, 2024
883e9f0
feat: Implemented swap
domenicodev Apr 12, 2024
8e494f0
feat: Implemented getCapabilites and fixed swap function
domenicodev Apr 16, 2024
24e8f25
chore: removed warnings
domenicodev Apr 16, 2024
d411a9f
feat: Finalized Renzo Adapter tests
domenicodev Apr 16, 2024
d28c965
Formatted code
domenicodev Apr 16, 2024
aa5370b
chore: Fixed naming in not implemented function
domenicodev Apr 17, 2024
6f37c11
fix: Fixed post trade calculation in getPriceAt
domenicodev Apr 24, 2024
f6c8595
fix: Fixed buy function
domenicodev May 7, 2024
c1fb25e
fix: Removed unused test
domenicodev May 7, 2024
b54b334
fix: Final review fixes: buy function, price, tests, calculateAmountIn
domenicodev May 8, 2024
961316e
chore: Formatted code
domenicodev May 8, 2024
f127291
Merge branch 'main' into feature/renzo-adapter
domenicodev May 8, 2024
8fee442
Merge branch 'main' into feature/renzo-adapter
domenicodev Nov 8, 2024
213907c
feat: Aligned functions with new ISwapAdapter interface
domenicodev Nov 8, 2024
6f64b57
chore: Added new test and fixed adapter constructor
domenicodev Nov 8, 2024
174c6b5
chore: fmt
domenicodev Nov 8, 2024
c7a881e
chore: Updated manifest
domenicodev Nov 8, 2024
a9b1fde
chore: fmt test
domenicodev Nov 8, 2024
ca468ed
ethereum-renzo repo setup with restakeManager and Withdrawals contracts
mp-web3 Nov 16, 2024
8a7c766
added ethereum-renzo to cargo.toml members
mp-web3 Nov 16, 2024
9b99ae6
map_components and store_components
mp-web3 Nov 18, 2024
258295f
chore: all modules created
mp-web3 Nov 19, 2024
56e4d5b
fixing
mp-web3 Nov 19, 2024
2f7ce0f
fixes
mp-web3 Nov 20, 2024
f766d96
Merge branch 'feature/renzo-adapter' into sdk-substream/renzo-adapter…
mp-web3 Nov 21, 2024
ff52bb6
Added integration test restake_manager creation
mp-web3 Nov 21, 2024
7da0141
changes with dome
mp-web3 Nov 26, 2024
745f483
implemented 2 signatured for deposit event
mp-web3 Nov 26, 2024
51f42eb
WIP: debug map_relative_balances
mp-web3 Nov 26, 2024
a33b7c1
wip: Final Debugs containing fixes for map_relative_balances
domenicodev Nov 26, 2024
35b5f1c
WIP: mock version of modules.rs to tdebug
mp-web3 Nov 27, 2024
3e2712d
fix: use match_and_decode
ignazio-bovo Nov 27, 2024
17e8776
fix: addresses
ignazio-bovo Nov 27, 2024
d025ab5
fix: map relative balances for renzo
ignazio-bovo Nov 29, 2024
600a225
fix: map_components for renzo
ignazio-bovo Nov 29, 2024
7072bbb
fix: event generation for old deposit event
ignazio-bovo Nov 29, 2024
819e917
test: passing integration tests
ignazio-bovo Nov 29, 2024
293d91d
chore: format
ignazio-bovo Nov 29, 2024
7f6075c
build: ci errors
ignazio-bovo Nov 29, 2024
c046c77
build: fix ci errors
ignazio-bovo Nov 29, 2024
5d2fd65
fix: clippy warnings
ignazio-bovo Nov 29, 2024
9a135fd
Merge branch 'main' into fix/renzo-relative-balances
ignazio-bovo Nov 30, 2024
d046c4e
chore: format
ignazio-bovo Nov 30, 2024
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
369 changes: 369 additions & 0 deletions evm/src/renzo/RenzoAdapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,369 @@
// SPDX-License-Identifier: AGPL-3.0-or-later
pragma solidity ^0.8.13;

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

/// @title Renzo Protocol Adapter
/// @dev This adapter only supports (supported token, ETH)->ezETH swaps
contract RenzoAdapter is ISwapAdapter {
using SafeERC20 for IERC20;

uint256 constant SCALE_FACTOR = 10 ** 18;

/// @dev custom value for limits(underestimate)
uint256 constant RESERVE_LIMIT_FACTOR = 10;

/// @dev custom scale factor for amounts used for prices, to avoid rounding
/// errors after and before trade due to large amounts
uint256 constant PRICE_SCALE_FACTOR = 10 ** 5;

IRestakeManager immutable restakeManager;
IRenzoOracle immutable renzoOracle;
IERC20 immutable ezETH;

constructor(address _restakeManager, address _renzoOracle, address _ezETH) {
restakeManager = IRestakeManager(_restakeManager);
renzoOracle = IRenzoOracle(_renzoOracle);
ezETH = IERC20(_ezETH);
}

/// @dev enable receive
receive() external payable {}

/// @inheritdoc ISwapAdapter
function price(
bytes32,
address _sellToken,
address,
uint256[] memory _specifiedAmounts
) external view override returns (Fraction[] memory _prices) {
_prices = new Fraction[](_specifiedAmounts.length);

for (uint256 i = 0; i < _specifiedAmounts.length; i++) {
_prices[i] = getPriceAt(_sellToken, _specifiedAmounts[i], true);
}
}

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

uint256 gasBefore = gasleft();
if (side == OrderSide.Sell) {
trade.calculatedAmount = sell(IERC20(sellToken), specifiedAmount);
} else {
trade.calculatedAmount = buy(IERC20(sellToken), specifiedAmount);
}
trade.gasUsed = gasBefore - gasleft();
trade.price = getPriceAt(
sellToken,
sellToken == address(0)
? 10 ** 18 / PRICE_SCALE_FACTOR
: 10 ** ERC20(sellToken).decimals() / PRICE_SCALE_FACTOR,
false
);
}

/// @inheritdoc ISwapAdapter
function getLimits(bytes32, address sellToken, address)
external
view
override
returns (uint256[] memory limits)
{
limits = new uint256[](2);
(uint256[][] memory operatorDelegatorTokenTVLs,, uint256 totalTvl) =
restakeManager.calculateTVLs();
uint256 limitInValue;
uint256 secondLimitInValue;

if (restakeManager.maxDepositTVL() != 0) {
limitInValue = restakeManager.maxDepositTVL() - totalTvl;
}

if (sellToken != address(0)) {
uint256 tokenIndex =
restakeManager.getCollateralTokenIndex(sellToken);

uint256 collateralTvlLimitSellToken =
restakeManager.collateralTokenTvlLimits(IERC20(sellToken));

if (collateralTvlLimitSellToken != 0) {
uint256 currentTokenTVL = 0;
uint256 odLength = operatorDelegatorTokenTVLs.length;
for (uint256 i = 0; i < odLength; i++) {
currentTokenTVL += operatorDelegatorTokenTVLs[i][tokenIndex];
}

secondLimitInValue =
collateralTvlLimitSellToken - currentTokenTVL;
}
}

uint256 ezEthTotalSupply = ezETH.totalSupply();

if (sellToken == address(0)) {
if (limitInValue == 0) {
limits[0] = ezEthTotalSupply / RESERVE_LIMIT_FACTOR;
} else {
limits[0] = limitInValue;
}
limits[1] = renzoOracle.calculateMintAmount(
totalTvl, limits[0], ezEthTotalSupply
);
} else {
if (limitInValue == 0) {
if (secondLimitInValue == 0) {
limits[0] = ezEthTotalSupply / RESERVE_LIMIT_FACTOR;
limits[1] = renzoOracle.calculateMintAmount(
totalTvl,
renzoOracle.lookupTokenValue(
IERC20(sellToken), limits[0]
),
ezEthTotalSupply
);
} else {
limits[0] = renzoOracle.lookupTokenAmountFromValue(
IERC20(sellToken), secondLimitInValue
);
limits[1] = renzoOracle.calculateMintAmount(
totalTvl, limits[0], ezEthTotalSupply
);
}
} else {
if (secondLimitInValue < limitInValue) {
limits[0] = renzoOracle.lookupTokenAmountFromValue(
IERC20(sellToken), secondLimitInValue
);
limits[1] = renzoOracle.calculateMintAmount(
totalTvl, limits[0], ezEthTotalSupply
);
} else {
limits[0] = renzoOracle.lookupTokenAmountFromValue(
IERC20(sellToken), limitInValue
);
limits[1] = renzoOracle.calculateMintAmount(
totalTvl, limits[0], ezEthTotalSupply
);
}
}
}
}

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

/// @inheritdoc ISwapAdapter
function getTokens(bytes32)
external
view
override
returns (address[] memory tokens)
{
uint256 tokensLength = restakeManager.getCollateralTokensLength();
tokens = new address[](tokensLength + 2);
for (uint256 i = 0; i < tokensLength; i++) {
tokens[i] = restakeManager.collateralTokens(i);
}
tokens[tokensLength] = address(ezETH);
tokens[tokensLength + 1] = address(0);
}

/// @inheritdoc ISwapAdapter
function getPoolIds(uint256, uint256)
external
pure
returns (bytes32[] memory)
{
return new bytes32[](1);
}

/// @notice Get swap price, incl. fee
/// @param sellToken token to sell
/// @param amount the amount to get the price of
/// @param simulateTrade determine if the price is postTrade
function getPriceAt(address sellToken, uint256 amount, bool simulateTrade)
internal
view
returns (Fraction memory)
{
(,, uint256 totalTVL) = restakeManager.calculateTVLs();
uint256 collateralTokenValue = sellToken == address(0)
? amount
: renzoOracle.lookupTokenValue(IERC20(sellToken), amount);
uint256 ezETHToMint = renzoOracle.calculateMintAmount(
totalTVL, collateralTokenValue, ezETH.totalSupply()
);

if (simulateTrade) {
amount = sellToken == address(0)
? (10 ** 18) / PRICE_SCALE_FACTOR
: (10 ** ERC20(sellToken).decimals()) / PRICE_SCALE_FACTOR;
uint256 collateralTokenValueAfter = sellToken == address(0)
? amount
: renzoOracle.lookupTokenValue(IERC20(sellToken), amount);
uint256 ezETHPostTrade = renzoOracle.calculateMintAmount(
totalTVL + collateralTokenValue,
collateralTokenValueAfter,
ezETH.totalSupply() + ezETHToMint
);
return Fraction(ezETHPostTrade, amount);
}

return Fraction(ezETHToMint, amount);
}

/// @notice Executes a sell(mint) order on the contract.
/// @param sellToken The token being sold.
/// @param amount The amount to be traded.
/// @return calculatedAmount The amount of ezETH received.
function sell(IERC20 sellToken, uint256 amount)
internal
returns (uint256 calculatedAmount)
{
uint256 balBefore = ezETH.balanceOf(address(this));
if (address(sellToken) != address(0)) {
sellToken.safeTransferFrom(msg.sender, address(this), amount);
sellToken.safeIncreaseAllowance(address(restakeManager), amount);

restakeManager.deposit(sellToken, amount);
} else {
restakeManager.depositETH{value: amount}();
}
calculatedAmount = ezETH.balanceOf(address(this)) - balBefore;
ezETH.safeTransfer(msg.sender, calculatedAmount);
}

/// @notice Executes a buy(mint) order on the contract.
/// @param sellToken The token being sold.
/// @param amount The amount of ezETH being bought.
/// @return calculatedAmount The amount of sellToken spent.
function buy(IERC20 sellToken, uint256 amount) internal returns (uint256) {
(,, uint256 totalTvl) = restakeManager.calculateTVLs();
uint256 amountIn =
calculateAmountIn(sellToken, totalTvl, ezETH.totalSupply(), amount);
uint256 ezEthBalBefore = ezETH.balanceOf(address(this));

if (address(sellToken) != address(0)) {
sellToken.safeTransferFrom(msg.sender, address(this), amountIn);
sellToken.safeIncreaseAllowance(address(restakeManager), amountIn);
restakeManager.deposit(sellToken, amountIn);
} else {
restakeManager.depositETH{value: amountIn}();
}

ezETH.safeTransfer(
msg.sender, ezETH.balanceOf(address(this)) - ezEthBalBefore
);

return amountIn;
}

/// @notice Calculate amountIn required to buy 'mintAmount' ezETH
/// @param sellToken token to sell
/// @param currentValueInProtocol totalTvl in protocol
/// @param existingEzETHSupply current ezETH totalSupply
/// @param mintAmount amount of ezETH to buy
/// @return (uint256) amount of sellToken to spend
function calculateAmountIn(
IERC20 sellToken,
uint256 currentValueInProtocol,
uint256 existingEzETHSupply,
uint256 mintAmount
) internal view returns (uint256) {
uint256 newEzETHSupply = existingEzETHSupply + mintAmount;

uint256 inflationPercentaage = SCALE_FACTOR
* (newEzETHSupply - existingEzETHSupply) / newEzETHSupply;

uint256 newValueAdded = (inflationPercentaage * currentValueInProtocol)
/ (SCALE_FACTOR - inflationPercentaage);

return address(sellToken) == address(0)
? newValueAdded
: renzoOracle.lookupTokenAmountFromValue(sellToken, newValueAdded);
}
}

interface IEzEthToken {}

interface IRestakeManager {
function renzoOracle() external view returns (IRenzoOracle);

function deposit(IERC20 _collateralToken, uint256 _amount) external;

function ezETH() external view returns (IEzEthToken);

function getCollateralTokensLength() external view returns (uint256);

function getCollateralTokenIndex(address _collateralToken)
external
view
returns (uint256);

function collateralTokens(uint256 i) external view returns (address);

function maxDepositTVL() external view returns (uint256);

function calculateTVLs()
external
view
returns (uint256[][] memory, uint256[] memory, uint256);

function collateralTokenTvlLimits(IERC20 token)
external
view
returns (uint256);

function depositETH() external payable;
}

interface IRenzoOracle {
function lookupTokenValue(IERC20 _token, uint256 _balance)
external
view
returns (uint256);
function lookupTokenAmountFromValue(IERC20 _token, uint256 _value)
external
view
returns (uint256);
function lookupTokenValues(
IERC20[] memory _tokens,
uint256[] memory _balances
) external view returns (uint256);
function calculateMintAmount(
uint256 _currentValueInProtocol,
uint256 _newValueAdded,
uint256 _existingEzETHSupply
) external pure returns (uint256);
function calculateRedeemAmount(
uint256 _ezETHBeingBurned,
uint256 _existingEzETHSupply,
uint256 _currentValueInProtocol
) external pure returns (uint256);
}
Loading
Loading