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:
2
.gitignore
vendored
2
.gitignore
vendored
@@ -21,4 +21,4 @@ substreams/ethereum-template/Cargo.lock
|
||||
tycho-indexer
|
||||
substreams/my_substream
|
||||
testing/tycho-client/build/*
|
||||
testing/tycho-client/*.egg-info
|
||||
testing/tycho-client/*.egg-info
|
||||
|
||||
366
evm/src/sfraxeth/FraxV3FrxEthAdapter.sol
Normal file
366
evm/src/sfraxeth/FraxV3FrxEthAdapter.sol
Normal 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);
|
||||
}
|
||||
37
evm/src/sfraxeth/manifest.yaml
Normal file
37
evm/src/sfraxeth/manifest.yaml
Normal 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
|
||||
273
evm/test/FraxV3FrxEthAdapter.t.sol
Normal file
273
evm/test/FraxV3FrxEthAdapter.t.sol
Normal 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);
|
||||
// }
|
||||
}
|
||||
24
substreams/Cargo.lock
generated
24
substreams/Cargo.lock
generated
@@ -260,6 +260,26 @@ dependencies = [
|
||||
"tycho-substreams",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-sfraxeth"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"ethabi 17.2.0",
|
||||
"getrandom",
|
||||
"hex",
|
||||
"hex-literal 0.4.1",
|
||||
"itertools 0.12.1",
|
||||
"num-bigint",
|
||||
"num-traits",
|
||||
"prost 0.11.9",
|
||||
"prost-types 0.11.9",
|
||||
"regex",
|
||||
"substreams",
|
||||
"substreams-ethereum",
|
||||
"tycho-substreams",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ethereum-types"
|
||||
version = "0.13.1"
|
||||
@@ -824,9 +844,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "regex"
|
||||
version = "1.10.3"
|
||||
version = "1.10.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b62dbe01f0b06f9d8dc7d49e05a0785f153b00b2c227856282f671e0318c9b15"
|
||||
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"memchr",
|
||||
|
||||
@@ -7,6 +7,7 @@ members = [
|
||||
"ethereum-ambient",
|
||||
"ethereum-uniswap-v2",
|
||||
"ethereum-uniswap-v3",
|
||||
"ethereum-sfraxeth",
|
||||
]
|
||||
resolver = "2"
|
||||
|
||||
|
||||
@@ -6,16 +6,16 @@
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Block {
|
||||
/// The blocks hash.
|
||||
#[prost(bytes="vec", tag="1")]
|
||||
#[prost(bytes = "vec", tag = "1")]
|
||||
pub hash: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The parent blocks hash.
|
||||
#[prost(bytes="vec", tag="2")]
|
||||
#[prost(bytes = "vec", tag = "2")]
|
||||
pub parent_hash: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The block number.
|
||||
#[prost(uint64, tag="3")]
|
||||
#[prost(uint64, tag = "3")]
|
||||
pub number: u64,
|
||||
/// The block timestamp.
|
||||
#[prost(uint64, tag="4")]
|
||||
#[prost(uint64, tag = "4")]
|
||||
pub ts: u64,
|
||||
}
|
||||
/// A struct describing a transaction.
|
||||
@@ -24,96 +24,102 @@ pub struct Block {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Transaction {
|
||||
/// The transaction hash.
|
||||
#[prost(bytes="vec", tag="1")]
|
||||
#[prost(bytes = "vec", tag = "1")]
|
||||
pub hash: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The sender of the transaction.
|
||||
#[prost(bytes="vec", tag="2")]
|
||||
#[prost(bytes = "vec", tag = "2")]
|
||||
pub from: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The receiver of the transaction.
|
||||
#[prost(bytes="vec", tag="3")]
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub to: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The transactions index within the block.
|
||||
/// TODO: should this be uint32? to match the type from the native substream type?
|
||||
#[prost(uint64, tag="4")]
|
||||
#[prost(uint64, tag = "4")]
|
||||
pub index: u64,
|
||||
}
|
||||
/// A custom struct representing an arbitrary attribute of a protocol component.
|
||||
/// This is mainly used by the native integration to track the necessary information about the protocol.
|
||||
/// This is mainly used by the native integration to track the necessary information about the
|
||||
/// protocol.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Attribute {
|
||||
/// The name of the attribute.
|
||||
#[prost(string, tag="1")]
|
||||
#[prost(string, tag = "1")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
/// The value of the attribute.
|
||||
#[prost(bytes="vec", tag="2")]
|
||||
#[prost(bytes = "vec", tag = "2")]
|
||||
pub value: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The type of change the attribute underwent.
|
||||
#[prost(enumeration="ChangeType", tag="3")]
|
||||
#[prost(enumeration = "ChangeType", tag = "3")]
|
||||
pub change: i32,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtocolType {
|
||||
#[prost(string, tag="1")]
|
||||
#[prost(string, tag = "1")]
|
||||
pub name: ::prost::alloc::string::String,
|
||||
#[prost(enumeration="FinancialType", tag="2")]
|
||||
#[prost(enumeration = "FinancialType", tag = "2")]
|
||||
pub financial_type: i32,
|
||||
#[prost(message, repeated, tag="3")]
|
||||
#[prost(message, repeated, tag = "3")]
|
||||
pub attribute_schema: ::prost::alloc::vec::Vec<Attribute>,
|
||||
#[prost(enumeration="ImplementationType", tag="4")]
|
||||
#[prost(enumeration = "ImplementationType", tag = "4")]
|
||||
pub implementation_type: i32,
|
||||
}
|
||||
/// A struct describing a part of the protocol.
|
||||
/// Note: For example this can be a UniswapV2 pair, that tracks the two ERC20 tokens used by the pair,
|
||||
/// the component would represent a single contract. In case of VM integration, such component would
|
||||
/// not need any attributes, because all the relevant info would be tracked via storage slots and balance changes.
|
||||
/// It can also be a wrapping contract, like WETH, that has a constant price, but it allows swapping tokens.
|
||||
/// This is why the name ProtocolComponent is used instead of "Pool" or "Pair".
|
||||
///
|
||||
/// Note: For example this can be a UniswapV2 pair, that tracks the two ERC20 tokens used by the
|
||||
/// pair, the component would represent a single contract. In case of VM integration, such component
|
||||
/// would not need any attributes, because all the relevant info would be tracked via storage slots
|
||||
/// and balance changes. It can also be a wrapping contract, like WETH, that has a constant price,
|
||||
/// but it allows swapping tokens. This is why the name ProtocolComponent is used instead of "Pool"
|
||||
/// or "Pair".
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ProtocolComponent {
|
||||
/// A unique identifier for the component within the protocol.
|
||||
/// Can be e.g. a stringified address or a string describing the trading pair.
|
||||
#[prost(string, tag="1")]
|
||||
#[prost(string, tag = "1")]
|
||||
pub id: ::prost::alloc::string::String,
|
||||
/// Addresses of the ERC20 tokens used by the component.
|
||||
#[prost(bytes="vec", repeated, tag="2")]
|
||||
#[prost(bytes = "vec", repeated, tag = "2")]
|
||||
pub tokens: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
|
||||
/// Addresses of the contracts used by the component.
|
||||
/// Usually it is a single contract, but some protocols use multiple contracts.
|
||||
#[prost(bytes="vec", repeated, tag="3")]
|
||||
#[prost(bytes = "vec", repeated, tag = "3")]
|
||||
pub contracts: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
|
||||
/// Static attributes of the component.
|
||||
/// These attributes MUST be immutable. If it can ever change, it should be given as an EntityChanges for this component id.
|
||||
/// The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent.
|
||||
#[prost(message, repeated, tag="4")]
|
||||
#[prost(message, repeated, tag = "4")]
|
||||
pub static_att: ::prost::alloc::vec::Vec<Attribute>,
|
||||
/// Type of change the component underwent.
|
||||
#[prost(enumeration="ChangeType", tag="5")]
|
||||
#[prost(enumeration = "ChangeType", tag = "5")]
|
||||
pub change: i32,
|
||||
/// / Represents the functionality of the component.
|
||||
#[prost(message, optional, tag="6")]
|
||||
#[prost(message, optional, tag = "6")]
|
||||
pub protocol_type: ::core::option::Option<ProtocolType>,
|
||||
/// Transaction where this component was created
|
||||
#[prost(message, optional, tag="7")]
|
||||
#[prost(message, optional, tag = "7")]
|
||||
pub tx: ::core::option::Option<Transaction>,
|
||||
}
|
||||
/// A struct for following the changes of Total Value Locked (TVL) of a protocol component.
|
||||
/// Note that if a ProtocolComponent contains multiple contracts, the TVL is tracked for the component as a whole.
|
||||
/// E.g. for UniswapV2 pair WETH/USDC, this tracks the USDC and WETH balance of the pair contract.
|
||||
///
|
||||
/// Note that if a ProtocolComponent contains multiple contracts, the TVL is tracked for the
|
||||
/// component as a whole. E.g. for UniswapV2 pair WETH/USDC, this tracks the USDC and WETH balance
|
||||
/// of the pair contract.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct BalanceChange {
|
||||
/// The address of the ERC20 token whose balance changed.
|
||||
#[prost(bytes="vec", tag="1")]
|
||||
#[prost(bytes = "vec", tag = "1")]
|
||||
pub token: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The new balance of the token. Note: it must be a big endian encoded int.
|
||||
#[prost(bytes="vec", tag="2")]
|
||||
#[prost(bytes = "vec", tag = "2")]
|
||||
pub balance: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The id of the component whose TVL is tracked. Note: This MUST be utf8 encoded.
|
||||
/// If the protocol component includes multiple contracts, the balance change must be aggregated to reflect how much tokens can be traded.
|
||||
#[prost(bytes="vec", tag="3")]
|
||||
/// If the protocol component includes multiple contracts, the balance change must be
|
||||
/// aggregated to reflect how much tokens can be traded.
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub component_id: ::prost::alloc::vec::Vec<u8>,
|
||||
}
|
||||
// Native entities
|
||||
@@ -123,10 +129,10 @@ pub struct BalanceChange {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct EntityChanges {
|
||||
/// A unique identifier of the entity within the protocol.
|
||||
#[prost(string, tag="1")]
|
||||
#[prost(string, tag = "1")]
|
||||
pub component_id: ::prost::alloc::string::String,
|
||||
/// The set of attributes that are associated with the entity.
|
||||
#[prost(message, repeated, tag="2")]
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub attributes: ::prost::alloc::vec::Vec<Attribute>,
|
||||
}
|
||||
// VM entities
|
||||
@@ -136,10 +142,10 @@ pub struct EntityChanges {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ContractSlot {
|
||||
/// A contract's storage slot.
|
||||
#[prost(bytes="vec", tag="2")]
|
||||
#[prost(bytes = "vec", tag = "2")]
|
||||
pub slot: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The new value for this storage slot.
|
||||
#[prost(bytes="vec", tag="3")]
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub value: ::prost::alloc::vec::Vec<u8>,
|
||||
}
|
||||
/// Changes made to a single contract's state.
|
||||
@@ -147,19 +153,19 @@ pub struct ContractSlot {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct ContractChange {
|
||||
/// The contract's address
|
||||
#[prost(bytes="vec", tag="1")]
|
||||
#[prost(bytes = "vec", tag = "1")]
|
||||
pub address: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The new balance of the contract, empty bytes indicates no change.
|
||||
#[prost(bytes="vec", tag="2")]
|
||||
#[prost(bytes = "vec", tag = "2")]
|
||||
pub balance: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The new code of the contract, empty bytes indicates no change.
|
||||
#[prost(bytes="vec", tag="3")]
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub code: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The changes to this contract's slots, empty sequence indicates no change.
|
||||
#[prost(message, repeated, tag="4")]
|
||||
#[prost(message, repeated, tag = "4")]
|
||||
pub slots: ::prost::alloc::vec::Vec<ContractSlot>,
|
||||
/// Whether this is an update, a creation or a deletion.
|
||||
#[prost(enumeration="ChangeType", tag="5")]
|
||||
#[prost(enumeration = "ChangeType", tag = "5")]
|
||||
pub change: i32,
|
||||
}
|
||||
// Aggregate entities
|
||||
@@ -169,21 +175,22 @@ pub struct ContractChange {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TransactionChanges {
|
||||
/// The transaction instance that results in the changes.
|
||||
#[prost(message, optional, tag="1")]
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub tx: ::core::option::Option<Transaction>,
|
||||
/// Contains the changes induced by the above transaction, aggregated on a per-contract basis.
|
||||
/// Contains the contract changes induced by the above transaction, usually for tracking VM components.
|
||||
#[prost(message, repeated, tag="2")]
|
||||
/// Contains the contract changes induced by the above transaction, usually for tracking VM
|
||||
/// components.
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub contract_changes: ::prost::alloc::vec::Vec<ContractChange>,
|
||||
/// Contains the entity changes induced by the above transaction.
|
||||
/// Usually for tracking native components or used for VM extensions (plugins).
|
||||
#[prost(message, repeated, tag="3")]
|
||||
#[prost(message, repeated, tag = "3")]
|
||||
pub entity_changes: ::prost::alloc::vec::Vec<EntityChanges>,
|
||||
/// An array of newly added components.
|
||||
#[prost(message, repeated, tag="4")]
|
||||
#[prost(message, repeated, tag = "4")]
|
||||
pub component_changes: ::prost::alloc::vec::Vec<ProtocolComponent>,
|
||||
/// An array of balance changes to components.
|
||||
#[prost(message, repeated, tag="5")]
|
||||
#[prost(message, repeated, tag = "5")]
|
||||
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
|
||||
}
|
||||
/// A set of transaction changes within a single block.
|
||||
@@ -192,10 +199,10 @@ pub struct TransactionChanges {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct BlockChanges {
|
||||
/// The block for which these changes are collectively computed.
|
||||
#[prost(message, optional, tag="1")]
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub block: ::core::option::Option<Block>,
|
||||
/// The set of transaction changes observed in the specified block.
|
||||
#[prost(message, repeated, tag="2")]
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub changes: ::prost::alloc::vec::Vec<TransactionChanges>,
|
||||
}
|
||||
/// Enum to specify the type of a change.
|
||||
@@ -295,15 +302,15 @@ impl ImplementationType {
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TransactionEntityChanges {
|
||||
#[prost(message, optional, tag="1")]
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub tx: ::core::option::Option<Transaction>,
|
||||
#[prost(message, repeated, tag="2")]
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub entity_changes: ::prost::alloc::vec::Vec<EntityChanges>,
|
||||
/// An array of newly added components.
|
||||
#[prost(message, repeated, tag="3")]
|
||||
#[prost(message, repeated, tag = "3")]
|
||||
pub component_changes: ::prost::alloc::vec::Vec<ProtocolComponent>,
|
||||
/// An array of balance changes to components.
|
||||
#[prost(message, repeated, tag="4")]
|
||||
#[prost(message, repeated, tag = "4")]
|
||||
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
|
||||
}
|
||||
/// A set of transaction changes within a single block.
|
||||
@@ -311,10 +318,10 @@ pub struct TransactionEntityChanges {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct BlockEntityChanges {
|
||||
/// The block for which these changes are collectively computed.
|
||||
#[prost(message, optional, tag="1")]
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub block: ::core::option::Option<Block>,
|
||||
/// The set of transaction changes observed in the specified block.
|
||||
#[prost(message, repeated, tag="2")]
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub changes: ::prost::alloc::vec::Vec<TransactionEntityChanges>,
|
||||
}
|
||||
/// A message containing relative balance changes.
|
||||
@@ -326,44 +333,44 @@ pub struct BlockEntityChanges {
|
||||
pub struct BalanceDelta {
|
||||
/// The ordinal of the balance change. Must be unique & deterministic over all balances
|
||||
/// changes within a block.
|
||||
#[prost(uint64, tag="1")]
|
||||
#[prost(uint64, tag = "1")]
|
||||
pub ord: u64,
|
||||
/// The tx hash of the transaction that caused the balance change.
|
||||
#[prost(message, optional, tag="2")]
|
||||
#[prost(message, optional, tag = "2")]
|
||||
pub tx: ::core::option::Option<Transaction>,
|
||||
/// The address of the ERC20 token whose balance changed.
|
||||
#[prost(bytes="vec", tag="3")]
|
||||
#[prost(bytes = "vec", tag = "3")]
|
||||
pub token: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The delta balance of the token.
|
||||
#[prost(bytes="vec", tag="4")]
|
||||
#[prost(bytes = "vec", tag = "4")]
|
||||
pub delta: ::prost::alloc::vec::Vec<u8>,
|
||||
/// The id of the component whose TVL is tracked.
|
||||
/// If the protocol component includes multiple contracts, the balance change must be
|
||||
/// aggregated to reflect how much tokens can be traded.
|
||||
#[prost(bytes="vec", tag="5")]
|
||||
#[prost(bytes = "vec", tag = "5")]
|
||||
pub component_id: ::prost::alloc::vec::Vec<u8>,
|
||||
}
|
||||
/// A set of balances deltas, usually a group of changes within a single block.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct BlockBalanceDeltas {
|
||||
#[prost(message, repeated, tag="1")]
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub balance_deltas: ::prost::alloc::vec::Vec<BalanceDelta>,
|
||||
}
|
||||
/// A message containing protocol components that were created by a single tx.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TransactionProtocolComponents {
|
||||
#[prost(message, optional, tag="1")]
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub tx: ::core::option::Option<Transaction>,
|
||||
#[prost(message, repeated, tag="2")]
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub components: ::prost::alloc::vec::Vec<ProtocolComponent>,
|
||||
}
|
||||
/// All protocol components that were created within a block with their corresponding tx.
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct BlockTransactionProtocolComponents {
|
||||
#[prost(message, repeated, tag="1")]
|
||||
#[prost(message, repeated, tag = "1")]
|
||||
pub tx_components: ::prost::alloc::vec::Vec<TransactionProtocolComponents>,
|
||||
}
|
||||
// WARNING: DEPRECATED. Please use common.proto's TransactionChanges and BlockChanges instead.
|
||||
@@ -374,16 +381,16 @@ pub struct BlockTransactionProtocolComponents {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct TransactionContractChanges {
|
||||
/// The transaction instance that results in the changes.
|
||||
#[prost(message, optional, tag="1")]
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub tx: ::core::option::Option<Transaction>,
|
||||
/// Contains the changes induced by the above transaction, aggregated on a per-contract basis.
|
||||
#[prost(message, repeated, tag="2")]
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub contract_changes: ::prost::alloc::vec::Vec<ContractChange>,
|
||||
/// An array of newly added components.
|
||||
#[prost(message, repeated, tag="3")]
|
||||
#[prost(message, repeated, tag = "3")]
|
||||
pub component_changes: ::prost::alloc::vec::Vec<ProtocolComponent>,
|
||||
/// An array of balance changes to components.
|
||||
#[prost(message, repeated, tag="4")]
|
||||
#[prost(message, repeated, tag = "4")]
|
||||
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
|
||||
}
|
||||
/// A set of transaction changes within a single block.
|
||||
@@ -391,10 +398,10 @@ pub struct TransactionContractChanges {
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct BlockContractChanges {
|
||||
/// The block for which these changes are collectively computed.
|
||||
#[prost(message, optional, tag="1")]
|
||||
#[prost(message, optional, tag = "1")]
|
||||
pub block: ::core::option::Option<Block>,
|
||||
/// The set of transaction changes observed in the specified block.
|
||||
#[prost(message, repeated, tag="2")]
|
||||
#[prost(message, repeated, tag = "2")]
|
||||
pub changes: ::prost::alloc::vec::Vec<TransactionContractChanges>,
|
||||
}
|
||||
// @@protoc_insertion_point(module)
|
||||
|
||||
1108
substreams/ethereum-sfraxeth/Cargo.lock
generated
Normal file
1108
substreams/ethereum-sfraxeth/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
31
substreams/ethereum-sfraxeth/Cargo.toml
Normal file
31
substreams/ethereum-sfraxeth/Cargo.toml
Normal file
@@ -0,0 +1,31 @@
|
||||
[package]
|
||||
name = "ethereum-sfraxeth"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "ethereum_sfraxeth"
|
||||
crate-type = ["cdylib"]
|
||||
|
||||
[dependencies]
|
||||
ethabi = "17"
|
||||
hex-literal.workspace = true
|
||||
num-bigint = "0.4"
|
||||
num-traits = "0.2.15"
|
||||
prost.workspace = true
|
||||
prost-types = "0.11"
|
||||
substreams.workspace = true
|
||||
substreams-ethereum.workspace = true
|
||||
hex.workspace = true
|
||||
tycho-substreams.workspace = true
|
||||
itertools = "0.12.0"
|
||||
anyhow = "1.0.75"
|
||||
|
||||
[build-dependencies]
|
||||
anyhow = "1"
|
||||
substreams-ethereum = "0.9"
|
||||
regex = "1.10.4"
|
||||
|
||||
# Required so that ethabi > ethereum-types build correctly under wasm32-unknown-unknown
|
||||
[target.wasm32-unknown-unknown.dependencies]
|
||||
getrandom = { version = "0.2", features = ["custom"] }
|
||||
26
substreams/ethereum-sfraxeth/Makefile
Normal file
26
substreams/ethereum-sfraxeth/Makefile
Normal file
@@ -0,0 +1,26 @@
|
||||
CARGO_VERSION := $(shell cargo version 2>/dev/null)
|
||||
|
||||
.PHONY: build
|
||||
build:
|
||||
ifdef CARGO_VERSION
|
||||
cargo build --target wasm32-unknown-unknown --release
|
||||
else
|
||||
@echo "Building substreams target using Docker. To speed up this step, install a Rust development environment."
|
||||
docker run --rm -ti --init -v ${PWD}:/usr/src --workdir /usr/src/ rust:bullseye cargo build --target wasm32-unknown-unknown --release
|
||||
endif
|
||||
|
||||
.PHONY: run
|
||||
run: build
|
||||
substreams run substreams.yaml $(if $(MODULE),$(MODULE),map_events) $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK))
|
||||
|
||||
.PHONY: gui
|
||||
gui: build
|
||||
substreams gui substreams.yaml $(if $(MODULE),$(MODULE),map_events) $(if $(START_BLOCK),-s $(START_BLOCK)) $(if $(STOP_BLOCK),-t $(STOP_BLOCK))
|
||||
|
||||
.PHONY: protogen
|
||||
protogen:
|
||||
substreams protogen ./substreams.yaml --exclude-paths="sf/substreams,google"
|
||||
|
||||
.PHONY: pack
|
||||
pack: build
|
||||
substreams pack substreams.yaml
|
||||
472
substreams/ethereum-sfraxeth/abi/sfraxeth_contract.abi.json
Normal file
472
substreams/ethereum-sfraxeth/abi/sfraxeth_contract.abi.json
Normal file
@@ -0,0 +1,472 @@
|
||||
[
|
||||
{
|
||||
"inputs": [
|
||||
{
|
||||
"internalType": "contract ERC20",
|
||||
"name": "_underlying",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"internalType": "uint32",
|
||||
"name": "_rewardsCycleLength",
|
||||
"type": "uint32"
|
||||
}
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "constructor"
|
||||
},
|
||||
{ "inputs": [], "name": "SyncError", "type": "error" },
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "spender",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Approval",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "assets",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "shares",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Deposit",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "uint32",
|
||||
"name": "cycleEnd",
|
||||
"type": "uint32"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "rewardAmount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "NewRewardsCycle",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "from",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "to",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "amount",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Transfer",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"anonymous": false,
|
||||
"inputs": [
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "caller",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "receiver",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": true,
|
||||
"internalType": "address",
|
||||
"name": "owner",
|
||||
"type": "address"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "assets",
|
||||
"type": "uint256"
|
||||
},
|
||||
{
|
||||
"indexed": false,
|
||||
"internalType": "uint256",
|
||||
"name": "shares",
|
||||
"type": "uint256"
|
||||
}
|
||||
],
|
||||
"name": "Withdraw",
|
||||
"type": "event"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "DOMAIN_SEPARATOR",
|
||||
"outputs": [{ "internalType": "bytes32", "name": "", "type": "bytes32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "", "type": "address" },
|
||||
{ "internalType": "address", "name": "", "type": "address" }
|
||||
],
|
||||
"name": "allowance",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "spender", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
|
||||
],
|
||||
"name": "approve",
|
||||
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "asset",
|
||||
"outputs": [
|
||||
{ "internalType": "contract ERC20", "name": "", "type": "address" }
|
||||
],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
|
||||
"name": "balanceOf",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "shares", "type": "uint256" }
|
||||
],
|
||||
"name": "convertToAssets",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "assets", "type": "uint256" }
|
||||
],
|
||||
"name": "convertToShares",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "decimals",
|
||||
"outputs": [{ "internalType": "uint8", "name": "", "type": "uint8" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "assets", "type": "uint256" },
|
||||
{ "internalType": "address", "name": "receiver", "type": "address" }
|
||||
],
|
||||
"name": "deposit",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "shares", "type": "uint256" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "assets", "type": "uint256" },
|
||||
{ "internalType": "address", "name": "receiver", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "deadline", "type": "uint256" },
|
||||
{ "internalType": "bool", "name": "approveMax", "type": "bool" },
|
||||
{ "internalType": "uint8", "name": "v", "type": "uint8" },
|
||||
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
|
||||
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
|
||||
],
|
||||
"name": "depositWithSignature",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "shares", "type": "uint256" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "lastRewardAmount",
|
||||
"outputs": [{ "internalType": "uint192", "name": "", "type": "uint192" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "lastSync",
|
||||
"outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
|
||||
"name": "maxDeposit",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
|
||||
"name": "maxMint",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "owner", "type": "address" }
|
||||
],
|
||||
"name": "maxRedeem",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "owner", "type": "address" }
|
||||
],
|
||||
"name": "maxWithdraw",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "shares", "type": "uint256" },
|
||||
{ "internalType": "address", "name": "receiver", "type": "address" }
|
||||
],
|
||||
"name": "mint",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "assets", "type": "uint256" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "name",
|
||||
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [{ "internalType": "address", "name": "", "type": "address" }],
|
||||
"name": "nonces",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "owner", "type": "address" },
|
||||
{ "internalType": "address", "name": "spender", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "value", "type": "uint256" },
|
||||
{ "internalType": "uint256", "name": "deadline", "type": "uint256" },
|
||||
{ "internalType": "uint8", "name": "v", "type": "uint8" },
|
||||
{ "internalType": "bytes32", "name": "r", "type": "bytes32" },
|
||||
{ "internalType": "bytes32", "name": "s", "type": "bytes32" }
|
||||
],
|
||||
"name": "permit",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "assets", "type": "uint256" }
|
||||
],
|
||||
"name": "previewDeposit",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "shares", "type": "uint256" }
|
||||
],
|
||||
"name": "previewMint",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "shares", "type": "uint256" }
|
||||
],
|
||||
"name": "previewRedeem",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "assets", "type": "uint256" }
|
||||
],
|
||||
"name": "previewWithdraw",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "pricePerShare",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "shares", "type": "uint256" },
|
||||
{ "internalType": "address", "name": "receiver", "type": "address" },
|
||||
{ "internalType": "address", "name": "owner", "type": "address" }
|
||||
],
|
||||
"name": "redeem",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "assets", "type": "uint256" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "rewardsCycleEnd",
|
||||
"outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "rewardsCycleLength",
|
||||
"outputs": [{ "internalType": "uint32", "name": "", "type": "uint32" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "symbol",
|
||||
"outputs": [{ "internalType": "string", "name": "", "type": "string" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "syncRewards",
|
||||
"outputs": [],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalAssets",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [],
|
||||
"name": "totalSupply",
|
||||
"outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }],
|
||||
"stateMutability": "view",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "to", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
|
||||
],
|
||||
"name": "transfer",
|
||||
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "address", "name": "from", "type": "address" },
|
||||
{ "internalType": "address", "name": "to", "type": "address" },
|
||||
{ "internalType": "uint256", "name": "amount", "type": "uint256" }
|
||||
],
|
||||
"name": "transferFrom",
|
||||
"outputs": [{ "internalType": "bool", "name": "", "type": "bool" }],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
},
|
||||
{
|
||||
"inputs": [
|
||||
{ "internalType": "uint256", "name": "assets", "type": "uint256" },
|
||||
{ "internalType": "address", "name": "receiver", "type": "address" },
|
||||
{ "internalType": "address", "name": "owner", "type": "address" }
|
||||
],
|
||||
"name": "withdraw",
|
||||
"outputs": [
|
||||
{ "internalType": "uint256", "name": "shares", "type": "uint256" }
|
||||
],
|
||||
"stateMutability": "nonpayable",
|
||||
"type": "function"
|
||||
}
|
||||
]
|
||||
12
substreams/ethereum-sfraxeth/buf.gen.yaml
Normal file
12
substreams/ethereum-sfraxeth/buf.gen.yaml
Normal file
@@ -0,0 +1,12 @@
|
||||
|
||||
version: v1
|
||||
plugins:
|
||||
- plugin: buf.build/community/neoeinstein-prost:v0.2.2
|
||||
out: src/pb
|
||||
opt:
|
||||
- file_descriptor_set=false
|
||||
|
||||
- plugin: buf.build/community/neoeinstein-prost-crate:v0.3.1
|
||||
out: src/pb
|
||||
opt:
|
||||
- no_features
|
||||
27
substreams/ethereum-sfraxeth/build.rs
Normal file
27
substreams/ethereum-sfraxeth/build.rs
Normal file
@@ -0,0 +1,27 @@
|
||||
#![allow(clippy::all)]
|
||||
use anyhow::{Ok, Result};
|
||||
use regex::Regex;
|
||||
use std::fs;
|
||||
use substreams_ethereum::Abigen;
|
||||
|
||||
fn main() -> Result<(), anyhow::Error> {
|
||||
let file_names = ["abi/sfraxeth_contract.abi.json"];
|
||||
let file_output_names = ["src/abi/sfraxeth_contract.rs"];
|
||||
|
||||
let mut i = 0;
|
||||
for f in file_names {
|
||||
let contents = fs::read_to_string(f).expect("Should have been able to read the file");
|
||||
|
||||
// sanitize fields and attributes starting with an underscore
|
||||
let regex = Regex::new(r#"("\w+"\s?:\s?")_(\w+")"#).unwrap();
|
||||
let sanitized_abi_file = regex.replace_all(contents.as_str(), "${1}u_${2}");
|
||||
|
||||
Abigen::from_bytes("Contract", sanitized_abi_file.as_bytes())?
|
||||
.generate()?
|
||||
.write_to_file(file_output_names[i])?;
|
||||
|
||||
i = i + 1;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
42
substreams/ethereum-sfraxeth/integration_test.tycho.yaml
Normal file
42
substreams/ethereum-sfraxeth/integration_test.tycho.yaml
Normal file
@@ -0,0 +1,42 @@
|
||||
# Name of the substreams config file in your substreams module. Usually "./substreams.yaml"
|
||||
substreams_yaml_path: ./substreams.yaml
|
||||
# Name of the adapter contract, usually: ProtocolSwapAdapter"
|
||||
adapter_contract: "FraxV3FrxEthAdapter"
|
||||
# Constructor signature of the Adapter contract"
|
||||
adapter_build_signature: "constructor(address, address)"
|
||||
# A comma separated list of args to be passed to the contructor of the Adapter contract"
|
||||
adapter_build_args: "0xbAFA44EFE7901E04E39Dad13167D089C559c1138,0xac3E018457B222d93114458476f3E3416Abbe38F"
|
||||
# Whether or not the testing script should skip checking balances of the protocol components.
|
||||
# If set to `true` please always add a reason why it's skipped.
|
||||
skip_balance_check: true
|
||||
|
||||
# A list of protocol types names created by your Substreams module.
|
||||
protocol_type_names:
|
||||
- "sfraxeth_vault"
|
||||
|
||||
# need to test just if the vault is created properly
|
||||
# ref https://etherscan.io/txs?a=0xac3E018457B222d93114458476f3E3416Abbe38F&p=390
|
||||
tests:
|
||||
- name: test_sfraxeth_vault_created
|
||||
# Indexed block range
|
||||
start_block: 15686046
|
||||
stop_block: 15687288
|
||||
expected_components:
|
||||
- id: "0xac3E018457B222d93114458476f3E3416Abbe38F" # sfrax ETH vault
|
||||
tokens:
|
||||
- "0x5e8422345238f34275888049021821e8e08caa1f" # frax ETH (ERC20)
|
||||
- "0xac3E018457B222d93114458476f3E3416Abbe38F" # sfrax ETH (ERC20)
|
||||
creation_tx: "0xd78dbe6cba652eb844de5aa473636c202fb6366c1bfc5ff8d5a26c1a24b37b07"
|
||||
skip_simulation: false
|
||||
|
||||
# Test sFraxETH -> fraxETH swap
|
||||
- name: test_frxeth_to_sfraxeth_swap
|
||||
start_block: 15686046
|
||||
stop_block: 15687288
|
||||
expected_components:
|
||||
- id: "0xac3E018457B222d93114458476f3E3416Abbe38F" # sfrax ETH vault
|
||||
tokens:
|
||||
- "0xac3E018457B222d93114458476f3E3416Abbe38F" # sfrax ETH (ERC20)
|
||||
- "0x5e8422345238f34275888049021821e8e08caa1f" # frax ETH (ERC20)
|
||||
creation_tx: "0xd78dbe6cba652eb844de5aa473636c202fb6366c1bfc5ff8d5a26c1a24b37b07"
|
||||
skip_simulation: false
|
||||
13
substreams/ethereum-sfraxeth/proto/contract.proto
Normal file
13
substreams/ethereum-sfraxeth/proto/contract.proto
Normal file
@@ -0,0 +1,13 @@
|
||||
syntax = "proto3";
|
||||
|
||||
package contract.v1;
|
||||
|
||||
message RewardCycle {
|
||||
uint64 ord = 1;
|
||||
bytes next_reward_amount = 2;
|
||||
bytes vault_address = 3;
|
||||
}
|
||||
|
||||
message BlockRewardCycles {
|
||||
repeated RewardCycle reward_cycles = 1;
|
||||
}
|
||||
4
substreams/ethereum-sfraxeth/rust-toolchain.toml
Normal file
4
substreams/ethereum-sfraxeth/rust-toolchain.toml
Normal file
@@ -0,0 +1,4 @@
|
||||
[toolchain]
|
||||
channel = "1.75"
|
||||
components = ["rustfmt"]
|
||||
targets = ["wasm32-unknown-unknown"]
|
||||
2
substreams/ethereum-sfraxeth/src/abi/mod.rs
Normal file
2
substreams/ethereum-sfraxeth/src/abi/mod.rs
Normal file
@@ -0,0 +1,2 @@
|
||||
#[allow(clippy::all)]
|
||||
pub mod sfraxeth_contract;
|
||||
4096
substreams/ethereum-sfraxeth/src/abi/sfraxeth_contract.rs
Normal file
4096
substreams/ethereum-sfraxeth/src/abi/sfraxeth_contract.rs
Normal file
File diff suppressed because it is too large
Load Diff
3
substreams/ethereum-sfraxeth/src/lib.rs
Normal file
3
substreams/ethereum-sfraxeth/src/lib.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
mod abi;
|
||||
mod modules;
|
||||
mod pb;
|
||||
411
substreams/ethereum-sfraxeth/src/modules.rs
Normal file
411
substreams/ethereum-sfraxeth/src/modules.rs
Normal file
@@ -0,0 +1,411 @@
|
||||
use crate::{
|
||||
abi,
|
||||
pb::contract::v1::{BlockRewardCycles, RewardCycle},
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use std::collections::HashMap;
|
||||
use substreams::{
|
||||
hex,
|
||||
pb::substreams::StoreDeltas,
|
||||
scalar::BigInt,
|
||||
store::{
|
||||
StoreAdd, StoreAddBigInt, StoreAddInt64, StoreGet, StoreGetInt64, StoreGetString, StoreNew,
|
||||
StoreSet, StoreSetRaw,
|
||||
},
|
||||
};
|
||||
use substreams_ethereum::{pb::eth, Event};
|
||||
use tycho_substreams::{
|
||||
balances::aggregate_balances_changes, contract::extract_contract_changes_builder, prelude::*,
|
||||
};
|
||||
|
||||
#[substreams::handlers::map]
|
||||
pub fn map_components(
|
||||
params: String,
|
||||
block: eth::v2::Block,
|
||||
) -> Result<BlockTransactionProtocolComponents, anyhow::Error> {
|
||||
let vault_address = hex::decode(params).unwrap();
|
||||
let locked_asset = map_vault_to_locked_asset(&vault_address).unwrap();
|
||||
// We store these as a hashmap by tx hash since we need to agg by tx hash later
|
||||
Ok(BlockTransactionProtocolComponents {
|
||||
tx_components: block
|
||||
.transactions()
|
||||
.filter_map(|tx| {
|
||||
let components = tx
|
||||
.calls()
|
||||
.filter(|call| !call.call.state_reverted)
|
||||
.filter_map(|_| {
|
||||
// address doesn't exist before contract deployment, hence the first tx with
|
||||
// a log.address = vault_address is the deployment tx
|
||||
if is_deployment_tx(tx, &vault_address) {
|
||||
Some(
|
||||
ProtocolComponent::at_contract(&vault_address, &tx.into())
|
||||
.with_tokens(&[
|
||||
locked_asset.as_slice(),
|
||||
vault_address.as_slice(),
|
||||
])
|
||||
.as_swap_type("sfraxeth_vault", ImplementationType::Vm),
|
||||
)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
if !components.is_empty() {
|
||||
Some(TransactionProtocolComponents { tx: Some(tx.into()), components })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Simply stores the `ProtocolComponent`s with the pool id as the key
|
||||
#[substreams::handlers::store]
|
||||
pub fn store_components(map: BlockTransactionProtocolComponents, store: StoreAddInt64) {
|
||||
store.add_many(
|
||||
0,
|
||||
&map.tx_components
|
||||
.iter()
|
||||
.flat_map(|tx_components| &tx_components.components)
|
||||
.map(|component| format!("pool:{0}", component.id))
|
||||
.collect::<Vec<_>>(),
|
||||
1,
|
||||
);
|
||||
}
|
||||
|
||||
// updates the reward rate to be accounted for at each block for the totalAsset locked in the vault
|
||||
#[substreams::handlers::map]
|
||||
pub fn map_reward_cycles(
|
||||
block: eth::v2::Block,
|
||||
components_store: StoreGetString,
|
||||
) -> Result<BlockRewardCycles, anyhow::Error> {
|
||||
let reward_cycles = block
|
||||
.logs()
|
||||
.filter(|vault_log| {
|
||||
components_store
|
||||
.get_last(format!("pool:0x{}", hex::encode(vault_log.address())))
|
||||
.is_some()
|
||||
})
|
||||
.filter_map(|vault_log| {
|
||||
if let Some(ev) =
|
||||
abi::sfraxeth_contract::events::NewRewardsCycle::match_and_decode(vault_log.log)
|
||||
{
|
||||
substreams::log::info!(
|
||||
"New rewards cycle: end={}, next rewards={}",
|
||||
ev.cycle_end,
|
||||
ev.reward_amount,
|
||||
);
|
||||
Some(RewardCycle {
|
||||
ord: vault_log.ordinal(),
|
||||
next_reward_amount: ev.reward_amount.to_signed_bytes_be(),
|
||||
vault_address: vault_log.address().to_vec(), // be bytes
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(BlockRewardCycles { reward_cycles })
|
||||
}
|
||||
|
||||
#[substreams::handlers::store]
|
||||
pub fn store_reward_cycles(block_reward_cycles: BlockRewardCycles, store: StoreSetRaw) {
|
||||
block_reward_cycles
|
||||
.reward_cycles
|
||||
.into_iter()
|
||||
.for_each(|reward_cycle| {
|
||||
let address_hex = format!("0x{}", hex::encode(&reward_cycle.vault_address));
|
||||
store.set(
|
||||
reward_cycle.ord,
|
||||
format!("reward_cycle:{}", address_hex),
|
||||
&reward_cycle.next_reward_amount,
|
||||
);
|
||||
});
|
||||
}
|
||||
#[substreams::handlers::map]
|
||||
pub fn map_relative_balances(
|
||||
block: eth::v2::Block,
|
||||
store: StoreGetInt64,
|
||||
reward_store: StoreDeltas,
|
||||
) -> Result<BlockBalanceDeltas, anyhow::Error> {
|
||||
let balance_deltas = block
|
||||
.logs()
|
||||
.filter(|log| map_vault_to_locked_asset(log.address()).is_some())
|
||||
.flat_map(|vault_log| {
|
||||
let mut deltas = Vec::new();
|
||||
|
||||
if let Some(ev) =
|
||||
abi::sfraxeth_contract::events::Withdraw::match_and_decode(vault_log.log)
|
||||
{
|
||||
let address_bytes_be = vault_log.address();
|
||||
let address_hex = format!("0x{}", hex::encode(address_bytes_be));
|
||||
|
||||
if store
|
||||
.get_last(format!("pool:{}", address_hex))
|
||||
.is_some()
|
||||
{
|
||||
substreams::log::info!(
|
||||
"Withdraw: -fraxEth {} -sfraxEth {}",
|
||||
ev.assets,
|
||||
ev.shares
|
||||
);
|
||||
deltas.extend_from_slice(&[
|
||||
BalanceDelta {
|
||||
ord: vault_log.ordinal(),
|
||||
tx: Some(vault_log.receipt.transaction.into()),
|
||||
token: map_vault_to_locked_asset(address_bytes_be)
|
||||
.unwrap()
|
||||
.to_vec(),
|
||||
delta: ev.assets.neg().to_signed_bytes_be(),
|
||||
component_id: address_hex.as_bytes().to_vec(),
|
||||
},
|
||||
BalanceDelta {
|
||||
ord: vault_log.ordinal(),
|
||||
tx: Some(vault_log.receipt.transaction.into()),
|
||||
token: address_bytes_be.to_vec(),
|
||||
delta: ev.shares.neg().to_signed_bytes_be(),
|
||||
component_id: address_hex.as_bytes().to_vec(),
|
||||
},
|
||||
])
|
||||
}
|
||||
} else if let Some(ev) =
|
||||
abi::sfraxeth_contract::events::Deposit::match_and_decode(vault_log.log)
|
||||
{
|
||||
let address_bytes_be = vault_log.address();
|
||||
let address_hex = format!("0x{}", hex::encode(address_bytes_be));
|
||||
if store
|
||||
.get_last(format!("pool:{}", address_hex))
|
||||
.is_some()
|
||||
{
|
||||
deltas.extend_from_slice(&[
|
||||
BalanceDelta {
|
||||
ord: vault_log.ordinal(),
|
||||
tx: Some(vault_log.receipt.transaction.into()),
|
||||
token: map_vault_to_locked_asset(address_bytes_be)
|
||||
.unwrap()
|
||||
.to_vec(),
|
||||
delta: ev.assets.to_signed_bytes_be(),
|
||||
component_id: address_hex.as_bytes().to_vec(),
|
||||
},
|
||||
BalanceDelta {
|
||||
ord: vault_log.ordinal(),
|
||||
tx: Some(vault_log.receipt.transaction.into()),
|
||||
token: address_bytes_be.to_vec(),
|
||||
delta: ev.shares.to_signed_bytes_be(),
|
||||
component_id: address_hex.as_bytes().to_vec(),
|
||||
},
|
||||
]);
|
||||
substreams::log::info!("Deposit: {:?}", deltas);
|
||||
}
|
||||
} else if abi::sfraxeth_contract::events::NewRewardsCycle::match_and_decode(vault_log)
|
||||
.is_some()
|
||||
{
|
||||
let address_bytes_be = vault_log.address();
|
||||
let address_hex = format!("0x{}", hex::encode(address_bytes_be));
|
||||
if store
|
||||
.get_last(format!("pool:{}", address_hex))
|
||||
.is_some()
|
||||
{
|
||||
// When the NextRewardsCycle event is emitted:
|
||||
// 1. `lastRewardAmount` is read from storage
|
||||
// 2. `storedTotalAssets` is incremented by the `lastRewardAmount` in the event
|
||||
// 3. `lastRewardAmount` is update with the `nextReward` (2nd parameter) in the
|
||||
// event
|
||||
// Hence the reward_store at key `reward_cycle:{address_hex}` will is
|
||||
// updated in this block. We want to use the first value of
|
||||
// the record at the beginning of the block (before the store_reward_cycles
|
||||
// writes to that key) ref: https://github.com/FraxFinance/frax-solidity/blob/85039d4dff2fb24d8a1ba6efc1ebf7e464df9dcf/src/hardhat/contracts/FraxETH/sfrxETH.sol.old#L984
|
||||
if let Some(last_reward_amount) = reward_store
|
||||
.deltas
|
||||
.iter()
|
||||
.find(|el| el.key == format!("reward_cycle:{}", address_hex))
|
||||
.map(|el| el.old_value.clone())
|
||||
{
|
||||
substreams::log::info!(
|
||||
"Reward cycle balance change: address {}, sfraxEth amount {}",
|
||||
address_hex,
|
||||
BigInt::from_signed_bytes_be(&last_reward_amount)
|
||||
);
|
||||
deltas.push(BalanceDelta {
|
||||
ord: vault_log.ordinal(),
|
||||
tx: Some(vault_log.receipt.transaction.into()),
|
||||
token: map_vault_to_locked_asset(address_bytes_be)
|
||||
.unwrap()
|
||||
.to_vec(),
|
||||
delta: last_reward_amount,
|
||||
component_id: address_hex.as_bytes().to_vec(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
deltas
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
Ok(BlockBalanceDeltas { balance_deltas })
|
||||
}
|
||||
|
||||
/// It's significant to include both the `pool_id` and the `token_id` for each balance delta as the
|
||||
/// store key to ensure that there's a unique balance being tallied for each.
|
||||
#[substreams::handlers::store]
|
||||
pub fn store_balances(deltas: BlockBalanceDeltas, store: StoreAddBigInt) {
|
||||
tycho_substreams::balances::store_balance_changes(deltas, store);
|
||||
}
|
||||
|
||||
/// This is the main map that handles most of the indexing of this substream.
|
||||
/// Every contract change is grouped by transaction index via the `transaction_changes`
|
||||
/// map. Each block of code will extend the `TransactionChanges` struct with the
|
||||
/// cooresponding changes (balance, component, contract), inserting a new one if it doesn't exist.
|
||||
/// At the very end, the map can easily be sorted by index to ensure the final
|
||||
/// `BlockChanges` is ordered by transactions properly.
|
||||
#[substreams::handlers::map]
|
||||
pub fn map_protocol_changes(
|
||||
block: eth::v2::Block,
|
||||
grouped_components: BlockTransactionProtocolComponents,
|
||||
deltas: BlockBalanceDeltas,
|
||||
components_store: StoreGetString,
|
||||
balance_store: StoreDeltas, // Note, this map module is using the `deltas` mode for the store.
|
||||
) -> Result<BlockChanges, anyhow::Error> {
|
||||
// We merge contract changes by transaction (identified by transaction index) making it easy to
|
||||
// sort them at the very end.
|
||||
let mut transaction_changes: HashMap<_, TransactionChangesBuilder> = HashMap::new();
|
||||
|
||||
// `ProtocolComponents` are gathered from `map_pools_created` which just need a bit of work to
|
||||
// convert into `TransactionChanges`
|
||||
grouped_components
|
||||
.tx_components
|
||||
.iter()
|
||||
.for_each(|tx_component| {
|
||||
// initialise builder if not yet present for this tx
|
||||
let tx = tx_component.tx.as_ref().unwrap();
|
||||
let builder = transaction_changes
|
||||
.entry(tx.index)
|
||||
.or_insert_with(|| TransactionChangesBuilder::new(tx));
|
||||
|
||||
// iterate over individual components created within this tx
|
||||
tx_component
|
||||
.components
|
||||
.iter()
|
||||
.for_each(|component| {
|
||||
builder.add_protocol_component(component);
|
||||
});
|
||||
});
|
||||
|
||||
// Balance changes are gathered by the `StoreDelta` based on `PoolBalanceChanged` creating
|
||||
// `BlockBalanceDeltas`. We essentially just process the changes that occurred to the `store`
|
||||
// this block. Then, these balance changes are merged onto the existing map of tx contract
|
||||
// changes, inserting a new one if it doesn't exist.
|
||||
aggregate_balances_changes(balance_store, deltas)
|
||||
.into_iter()
|
||||
.for_each(|(_, (tx, balances))| {
|
||||
let builder = transaction_changes
|
||||
.entry(tx.index)
|
||||
.or_insert_with(|| TransactionChangesBuilder::new(&tx));
|
||||
balances.values().for_each(|bc| {
|
||||
builder.add_balance_change(bc);
|
||||
});
|
||||
});
|
||||
|
||||
// Extract and insert any storage changes that happened for any of the components.
|
||||
extract_contract_changes_builder(
|
||||
&block,
|
||||
|addr| {
|
||||
components_store
|
||||
.get_last(format!("pool:0x{0}", hex::encode(addr)))
|
||||
.is_some()
|
||||
},
|
||||
&mut transaction_changes,
|
||||
);
|
||||
|
||||
// Process all `transaction_changes` for final output in the `BlockChanges`,
|
||||
// sorted by transaction index (the key).
|
||||
|
||||
let block_changes = BlockChanges {
|
||||
block: Some((&block).into()),
|
||||
changes: transaction_changes
|
||||
.drain()
|
||||
.sorted_unstable_by_key(|(index, _)| *index)
|
||||
.filter_map(|(_, builder)| builder.build())
|
||||
.collect::<Vec<_>>(),
|
||||
};
|
||||
|
||||
for change in &block_changes.changes {
|
||||
substreams::log::info!("🚨 Balance changes {:?}", change.balance_changes);
|
||||
substreams::log::info!("🚨 Component changes {:?}", change.component_changes);
|
||||
}
|
||||
Ok(block_changes)
|
||||
}
|
||||
|
||||
fn is_deployment_tx(tx: ð::v2::TransactionTrace, vault_address: &[u8]) -> bool {
|
||||
match vault_address {
|
||||
hex!("95aB45875cFFdba1E5f451B950bC2E42c0053f39") => {
|
||||
// Arbitrum
|
||||
tx.hash == hex!("ad86e67a2d511576f802dca2f65b6dfbec1d050c63f55878f80272a5fcafcadf")
|
||||
}
|
||||
hex!("3Cd55356433C89E50DC51aB07EE0fa0A95623D53") => {
|
||||
// BSC
|
||||
tx.hash == hex!("c043ba8c30eeed718514b7d1d1d4654521eca2f7aa5e5a7ae1e2c212ca869997")
|
||||
}
|
||||
hex!("ac3E018457B222d93114458476f3E3416Abbe38F") => {
|
||||
// Ethereum
|
||||
tx.hash == hex!("d78dbe6cba652eb844de5aa473636c202fb6366c1bfc5ff8d5a26c1a24b37b07")
|
||||
}
|
||||
hex!("b90CCD563918fF900928dc529aA01046795ccb4A") => {
|
||||
// Fantom
|
||||
tx.hash == hex!("749c9ffb6022d5e6a8f3470499bfc2e9cf3bf122f75e2a5925930407d2a9e02c")
|
||||
}
|
||||
hex!("ecf91116348aF1cfFe335e9807f0051332BE128D") => {
|
||||
// Moonbeam
|
||||
tx.hash == hex!("3545822fb0695bec2d3e9860b22073cc79845a0bb3cfccf401241dc7fe0eb86b")
|
||||
}
|
||||
hex!("484c2D6e3cDd945a8B2DF735e079178C1036578c") => {
|
||||
// Optimism
|
||||
tx.hash == hex!("e2e4c7173ae6ac0d78cacb1d48004c2aea7e1ce4ae0110a128d40bdcdc4d51b0")
|
||||
}
|
||||
hex!("6d1FdBB266fCc09A16a22016369210A15bb95761") => {
|
||||
// Polygon
|
||||
tx.hash == hex!("ada03ce824bac4a811d0b1bb60f9f26dbdd921bcd5034b1b4b973026a04ad9ea")
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
|
||||
// ref: https://docs.frax.finance/smart-contracts/frxeth-and-sfrxeth-contract-addresses
|
||||
fn map_vault_to_locked_asset(address_bytes: &[u8]) -> Option<[u8; 20]> {
|
||||
// basedo on ADDRESS_MAP create a match condition to return the locked_asset
|
||||
match address_bytes {
|
||||
hex!("95aB45875cFFdba1E5f451B950bC2E42c0053f39") => {
|
||||
// Arbitrum
|
||||
Some(hex!("178412e79c25968a32e89b11f63B33F733770c2A"))
|
||||
}
|
||||
hex!("3Cd55356433C89E50DC51aB07EE0fa0A95623D53") => {
|
||||
// BSC
|
||||
Some(hex!("64048A7eEcF3a2F1BA9e144aAc3D7dB6e58F555e"))
|
||||
}
|
||||
hex!("ac3E018457B222d93114458476f3E3416Abbe38F") => {
|
||||
// Ethereum
|
||||
Some(hex!("5e8422345238f34275888049021821e8e08caa1f"))
|
||||
}
|
||||
hex!("b90CCD563918fF900928dc529aA01046795ccb4A") => {
|
||||
// Fantom
|
||||
Some(hex!("9E73F99EE061C8807F69f9c6CCc44ea3d8c373ee"))
|
||||
}
|
||||
hex!("ecf91116348aF1cfFe335e9807f0051332BE128D") => {
|
||||
// Moonbeam
|
||||
Some(hex!("82bbd1b6f6De2B7bb63D3e1546e6b1553508BE99"))
|
||||
}
|
||||
hex!("484c2D6e3cDd945a8B2DF735e079178C1036578c") => {
|
||||
// Optimism
|
||||
Some(hex!("6806411765Af15Bddd26f8f544A34cC40cb9838B"))
|
||||
}
|
||||
hex!("6d1FdBB266fCc09A16a22016369210A15bb95761") => {
|
||||
// Polygon
|
||||
Some(hex!("Ee327F889d5947c1dc1934Bb208a1E792F953E96"))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
126
substreams/ethereum-sfraxeth/src/pb/contract.v1.rs
Normal file
126
substreams/ethereum-sfraxeth/src/pb/contract.v1.rs
Normal file
@@ -0,0 +1,126 @@
|
||||
// @generated
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct Events {
|
||||
#[prost(message, repeated, tag="1")]
|
||||
pub sfraxeth_approvals: ::prost::alloc::vec::Vec<SfraxethApproval>,
|
||||
#[prost(message, repeated, tag="2")]
|
||||
pub sfraxeth_deposits: ::prost::alloc::vec::Vec<SfraxethDeposit>,
|
||||
#[prost(message, repeated, tag="3")]
|
||||
pub sfraxeth_new_rewards_cycles: ::prost::alloc::vec::Vec<SfraxethNewRewardsCycle>,
|
||||
#[prost(message, repeated, tag="4")]
|
||||
pub sfraxeth_transfers: ::prost::alloc::vec::Vec<SfraxethTransfer>,
|
||||
#[prost(message, repeated, tag="5")]
|
||||
pub sfraxeth_withdraws: ::prost::alloc::vec::Vec<SfraxethWithdraw>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct SfraxethApproval {
|
||||
#[prost(string, tag="1")]
|
||||
pub evt_tx_hash: ::prost::alloc::string::String,
|
||||
#[prost(uint32, tag="2")]
|
||||
pub evt_index: u32,
|
||||
#[prost(message, optional, tag="3")]
|
||||
pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>,
|
||||
#[prost(uint64, tag="4")]
|
||||
pub evt_block_number: u64,
|
||||
#[prost(bytes="vec", tag="5")]
|
||||
pub owner: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes="vec", tag="6")]
|
||||
pub spender: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(string, tag="7")]
|
||||
pub amount: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct SfraxethDeposit {
|
||||
#[prost(string, tag="1")]
|
||||
pub evt_tx_hash: ::prost::alloc::string::String,
|
||||
#[prost(uint32, tag="2")]
|
||||
pub evt_index: u32,
|
||||
#[prost(message, optional, tag="3")]
|
||||
pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>,
|
||||
#[prost(uint64, tag="4")]
|
||||
pub evt_block_number: u64,
|
||||
#[prost(bytes="vec", tag="5")]
|
||||
pub caller: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes="vec", tag="6")]
|
||||
pub owner: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(string, tag="7")]
|
||||
pub assets: ::prost::alloc::string::String,
|
||||
#[prost(string, tag="8")]
|
||||
pub shares: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct SfraxethNewRewardsCycle {
|
||||
#[prost(string, tag="1")]
|
||||
pub evt_tx_hash: ::prost::alloc::string::String,
|
||||
#[prost(uint32, tag="2")]
|
||||
pub evt_index: u32,
|
||||
#[prost(message, optional, tag="3")]
|
||||
pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>,
|
||||
#[prost(uint64, tag="4")]
|
||||
pub evt_block_number: u64,
|
||||
#[prost(uint64, tag="5")]
|
||||
pub cycle_end: u64,
|
||||
#[prost(string, tag="6")]
|
||||
pub reward_amount: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct SfraxethTransfer {
|
||||
#[prost(string, tag="1")]
|
||||
pub evt_tx_hash: ::prost::alloc::string::String,
|
||||
#[prost(uint32, tag="2")]
|
||||
pub evt_index: u32,
|
||||
#[prost(message, optional, tag="3")]
|
||||
pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>,
|
||||
#[prost(uint64, tag="4")]
|
||||
pub evt_block_number: u64,
|
||||
#[prost(bytes="vec", tag="5")]
|
||||
pub from: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes="vec", tag="6")]
|
||||
pub to: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(string, tag="7")]
|
||||
pub amount: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct SfraxethWithdraw {
|
||||
#[prost(string, tag="1")]
|
||||
pub evt_tx_hash: ::prost::alloc::string::String,
|
||||
#[prost(uint32, tag="2")]
|
||||
pub evt_index: u32,
|
||||
#[prost(message, optional, tag="3")]
|
||||
pub evt_block_time: ::core::option::Option<::prost_types::Timestamp>,
|
||||
#[prost(uint64, tag="4")]
|
||||
pub evt_block_number: u64,
|
||||
#[prost(bytes="vec", tag="5")]
|
||||
pub caller: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes="vec", tag="6")]
|
||||
pub receiver: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes="vec", tag="7")]
|
||||
pub owner: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(string, tag="8")]
|
||||
pub assets: ::prost::alloc::string::String,
|
||||
#[prost(string, tag="9")]
|
||||
pub shares: ::prost::alloc::string::String,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct RewardCycle {
|
||||
#[prost(uint64, tag="1")]
|
||||
pub ord: u64,
|
||||
#[prost(bytes="vec", tag="2")]
|
||||
pub next_reward_amount: ::prost::alloc::vec::Vec<u8>,
|
||||
#[prost(bytes="vec", tag="3")]
|
||||
pub vault_address: ::prost::alloc::vec::Vec<u8>,
|
||||
}
|
||||
#[allow(clippy::derive_partial_eq_without_eq)]
|
||||
#[derive(Clone, PartialEq, ::prost::Message)]
|
||||
pub struct BlockRewardCycles {
|
||||
#[prost(message, repeated, tag="1")]
|
||||
pub reward_cycles: ::prost::alloc::vec::Vec<RewardCycle>,
|
||||
}
|
||||
// @@protoc_insertion_point(module)
|
||||
8
substreams/ethereum-sfraxeth/src/pb/mod.rs
Normal file
8
substreams/ethereum-sfraxeth/src/pb/mod.rs
Normal file
@@ -0,0 +1,8 @@
|
||||
// @generated
|
||||
pub mod contract {
|
||||
// @@protoc_insertion_point(attribute:contract.v1)
|
||||
pub mod v1 {
|
||||
include!("contract.v1.rs");
|
||||
// @@protoc_insertion_point(contract.v1)
|
||||
}
|
||||
}
|
||||
91
substreams/ethereum-sfraxeth/substreams.yaml
Normal file
91
substreams/ethereum-sfraxeth/substreams.yaml
Normal file
@@ -0,0 +1,91 @@
|
||||
specVersion: v0.1.0
|
||||
package:
|
||||
name: "ethereum_sfraxeth"
|
||||
version: v0.1.0
|
||||
|
||||
protobuf:
|
||||
files:
|
||||
- tycho/evm/v1/vm.proto
|
||||
- tycho/evm/v1/common.proto
|
||||
- tycho/evm/v1/utils.proto
|
||||
- contract.proto
|
||||
importPaths:
|
||||
- ../../proto
|
||||
- ./proto
|
||||
|
||||
binaries:
|
||||
default:
|
||||
type: wasm/rust-v1
|
||||
file: ../target/wasm32-unknown-unknown/release/ethereum_sfraxeth.wasm
|
||||
|
||||
modules:
|
||||
- name: map_components
|
||||
kind: map
|
||||
initialBlock: 15686046
|
||||
inputs:
|
||||
- params: string
|
||||
- source: sf.ethereum.type.v2.Block
|
||||
output:
|
||||
type: proto:tycho.evm.v1.GroupedTransactionProtocolComponents
|
||||
doc: |
|
||||
param is the address without the 0x prefix of the sfrax vault you want to track
|
||||
|
||||
- name: store_components
|
||||
kind: store
|
||||
initialBlock: 15686046
|
||||
updatePolicy: add
|
||||
valueType: int64
|
||||
inputs:
|
||||
- map: map_components
|
||||
|
||||
- name: map_reward_cycles
|
||||
kind: map
|
||||
initialBlock: 15686046
|
||||
inputs:
|
||||
- source: sf.ethereum.type.v2.Block
|
||||
- store: store_components
|
||||
output:
|
||||
type: proto:contract.v1.BlockRewardCycles
|
||||
|
||||
- name: store_reward_cycles
|
||||
kind: store
|
||||
initialBlock: 15686046
|
||||
updatePolicy: set
|
||||
valueType: bytes
|
||||
inputs:
|
||||
- map: map_reward_cycles
|
||||
|
||||
- name: map_relative_balances
|
||||
kind: map
|
||||
initialBlock: 15686046 # An arbitrary block that should change based on your requirements
|
||||
inputs:
|
||||
- source: sf.ethereum.type.v2.Block
|
||||
- store: store_components
|
||||
- store: store_reward_cycles
|
||||
mode: deltas
|
||||
output:
|
||||
type: proto:tycho.evm.v1.BalanceDeltas
|
||||
|
||||
- name: store_balances
|
||||
kind: store
|
||||
initialBlock: 15686046
|
||||
updatePolicy: add
|
||||
valueType: bigint
|
||||
inputs:
|
||||
- map: map_relative_balances
|
||||
|
||||
- name: map_protocol_changes
|
||||
kind: map
|
||||
initialBlock: 15686046
|
||||
inputs:
|
||||
- source: sf.ethereum.type.v2.Block
|
||||
- map: map_components
|
||||
- map: map_relative_balances
|
||||
- store: store_components
|
||||
- store: store_balances
|
||||
mode: deltas # This is the key property that simplifies `BalanceChange` handling
|
||||
output:
|
||||
type: proto:tycho.evm.v1.BlockChanges
|
||||
|
||||
params:
|
||||
map_components: "ac3E018457B222d93114458476f3E3416Abbe38F"
|
||||
@@ -11,6 +11,7 @@ chain_width = 40
|
||||
ignore = [
|
||||
"crates/tycho-substreams/src/pb",
|
||||
"ethereum-balancer/src/abi",
|
||||
"ethereum-sfraxeth/src/abi",
|
||||
"ethereum-curve/src/abi",
|
||||
"ethereum-uniswap-v2/src/abi",
|
||||
"ethereum-uniswap-v3/src/abi",
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
export RPC_URL=https://mainnet.infura.io/v3/your-infura-key
|
||||
export SUBSTREAMS_API_TOKEN="changeme"
|
||||
export DOMAIN_OWNER="AWSAccountId"
|
||||
export DOMAIN_OWNER="AWSAccountId"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
version: '3.1'
|
||||
version: "3.1"
|
||||
services:
|
||||
db:
|
||||
build:
|
||||
@@ -12,7 +12,7 @@ services:
|
||||
POSTGRESQL_SHARED_PRELOAD_LIBRARIES: pg_cron
|
||||
ports:
|
||||
- "5431:5432"
|
||||
shm_size: '1gb'
|
||||
shm_size: "1gb"
|
||||
app:
|
||||
build:
|
||||
context: .
|
||||
@@ -43,4 +43,4 @@ services:
|
||||
- "--db-url"
|
||||
- "postgres://postgres:mypassword@db:5432/tycho_indexer_0"
|
||||
volumes:
|
||||
postgres_data:
|
||||
postgres_data:
|
||||
|
||||
Reference in New Issue
Block a user