feat: introducing maverick

This commit is contained in:
0xMochan
2024-03-07 16:57:17 -05:00
parent 3f87d151df
commit 249fe6eb4c
25 changed files with 9636 additions and 0 deletions

1269
substreams/ethereum-maverick/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,29 @@
[package]
name = "substreams-maverick"
version = "0.1.0"
edition = "2021"
[lib]
name = "substreams_maverick"
crate-type = ["cdylib"]
[dependencies]
substreams = "0.5"
substreams-ethereum = "0.9.9"
prost = "0.11"
hex-literal = "0.4.1"
ethabi = "18.0.0"
hex = "0.4.2"
bytes = "1.5.0"
anyhow = "1.0.75"
prost-types = "0.12.3"
num-bigint = "0.4.4"
itertools = "0.12.0"
[build-dependencies]
anyhow = "1"
substreams-ethereum = "0.9"
# Required so that ethabi > ethereum-types build correctly under wasm32-unknown-unknown
[target.wasm32-unknown-unknown.dependencies]
getrandom = { version = "0.2", features = ["custom"] }

View File

@@ -0,0 +1,22 @@
# ABIs
`get_abis.py` is a simple python script using the etherscan API (free plan) to gather ABIs for all of the contracts we are tracking!
We then can define all of the abis via `substreams_ethereum::Abigen::new` in our `build.rs`.
## Recommendation
It would be apt to convert (maybe through copilot) the python code into the `build.rs` file and then automate the `Abigen` functionality.
## Usage
Requires `python 3.8+`,
```bash
cd abi
python get_abis.py
```
This will populate the files in the `abi` folder.
When the `build.rs` file runs (when `rust-analyzer` activates or `cargo build` is manually ran), Abigen will generate new rust src files from the abis in the `src/abi` folder.

View File

@@ -0,0 +1,224 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "poolAddress",
"type": "address"
},
{
"indexed": false,
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "tickSpacing",
"type": "uint256"
},
{
"indexed": false,
"internalType": "int32",
"name": "activeTick",
"type": "int32"
},
{
"indexed": false,
"internalType": "int256",
"name": "lookback",
"type": "int256"
},
{
"indexed": false,
"internalType": "uint64",
"name": "protocolFeeRatio",
"type": "uint64"
},
{
"indexed": false,
"internalType": "contract IERC20",
"name": "tokenA",
"type": "address"
},
{
"indexed": false,
"internalType": "contract IERC20",
"name": "tokenB",
"type": "address"
}
],
"name": "PoolCreated",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "owner",
"type": "address"
}
],
"name": "SetFactoryOwner",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint64",
"name": "protocolFeeRatio",
"type": "uint64"
}
],
"name": "SetFactoryProtocolFeeRatio",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "_fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "_tickSpacing",
"type": "uint256"
},
{
"internalType": "int256",
"name": "_lookback",
"type": "int256"
},
{
"internalType": "int32",
"name": "_activeTick",
"type": "int32"
},
{
"internalType": "contract IERC20",
"name": "_tokenA",
"type": "address"
},
{
"internalType": "contract IERC20",
"name": "_tokenB",
"type": "address"
}
],
"name": "create",
"outputs": [
{
"internalType": "contract IPool",
"name": "",
"type": "address"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "contract IPool",
"name": "pool",
"type": "address"
}
],
"name": "isFactoryPool",
"outputs": [
{
"internalType": "bool",
"name": "",
"type": "bool"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "fee",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tickSpacing",
"type": "uint256"
},
{
"internalType": "int256",
"name": "lookback",
"type": "int256"
},
{
"internalType": "contract IERC20",
"name": "tokenA",
"type": "address"
},
{
"internalType": "contract IERC20",
"name": "tokenB",
"type": "address"
}
],
"name": "lookup",
"outputs": [
{
"internalType": "contract IPool",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "owner",
"outputs": [
{
"internalType": "address",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "position",
"outputs": [
{
"internalType": "contract IPosition",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "protocolFeeRatio",
"outputs": [
{
"internalType": "uint64",
"name": "",
"type": "uint64"
}
],
"stateMutability": "view",
"type": "function"
}
]

View File

@@ -0,0 +1,48 @@
#!/usr/bin/python
import json
import os
import re
import time
import urllib.request
# Exports contract ABI in JSON
abis = {
# Factories
"CryptoSwapRegistry": "0x9a32aF1A11D9c937aEa61A3790C2983257eA8Bc0",
"MainRegistry": "0x90E00ACe148ca3b23Ac1bC8C240C2a7Dd9c2d7f5",
"MetaPoolFactory": "0xB9fC157394Af804a3578134A6585C0dc9cc990d4",
"CryptoPoolFactory": "0xF18056Bbd320E96A48e3Fbf8bC061322531aac99",
# pool
"Pool": "0xbEbc44782C7dB0a1A60Cb6fe97d0b483032FF1C7",
}
ABI_ENDPOINT = (
"https://api.etherscan.io/api?module=contract&action=getabi&address={address}"
)
if etherscan_key := os.environ.get("ETHERSCAN_API_TOKEN"):
print("API KEY Loaded!")
ABI_ENDPOINT += f"&apikey={etherscan_key}"
def __main__():
for name, addr in abis.items():
normalized_name = "_".join(re.findall(r"[A-Z]+[a-z]*", name)).lower()
print(f"Getting ABI for {name} at {addr} ({normalized_name})")
try:
with urllib.request.urlopen(ABI_ENDPOINT.format(address=addr)) as response:
response_json = json.loads(response.read().decode())
abi_json = json.loads(response_json["result"])
result = json.dumps(abi_json, indent=4, sort_keys=True)
with open(f"{normalized_name}.json", "w") as f:
f.write(result)
except Exception as err:
print(response.content)
raise err
time.sleep(0.25)
if __name__ == "__main__":
__main__()

View File

@@ -0,0 +1,955 @@
[
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint128",
"name": "deltaA",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "deltaB",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "deltaLpBalance",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"internalType": "uint8",
"name": "kind",
"type": "uint8"
},
{
"internalType": "int32",
"name": "lowerTick",
"type": "int32"
},
{
"internalType": "bool",
"name": "isActive",
"type": "bool"
}
],
"indexed": false,
"internalType": "struct IPool.BinDelta[]",
"name": "binDeltas",
"type": "tuple[]"
}
],
"name": "AddLiquidity",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "reserveA",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "reserveB",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint128",
"name": "mergeId",
"type": "uint128"
}
],
"name": "BinMerged",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"indexed": false,
"internalType": "int128",
"name": "previousTick",
"type": "int128"
},
{
"indexed": false,
"internalType": "int128",
"name": "newTick",
"type": "int128"
}
],
"name": "BinMoved",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"indexed": false,
"internalType": "uint32",
"name": "maxRecursion",
"type": "uint32"
}
],
"name": "MigrateBinsUpStack",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "protocolFee",
"type": "uint256"
},
{
"indexed": false,
"internalType": "bool",
"name": "isTokenA",
"type": "bool"
}
],
"name": "ProtocolFeeCollected",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": true,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": true,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": true,
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint128",
"name": "deltaA",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "deltaB",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "deltaLpBalance",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"internalType": "uint8",
"name": "kind",
"type": "uint8"
},
{
"internalType": "int32",
"name": "lowerTick",
"type": "int32"
},
{
"internalType": "bool",
"name": "isActive",
"type": "bool"
}
],
"indexed": false,
"internalType": "struct IPool.BinDelta[]",
"name": "binDeltas",
"type": "tuple[]"
}
],
"name": "RemoveLiquidity",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "protocolFee",
"type": "uint256"
}
],
"name": "SetProtocolFeeRatio",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "address",
"name": "sender",
"type": "address"
},
{
"indexed": false,
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"indexed": false,
"internalType": "bool",
"name": "tokenAIn",
"type": "bool"
},
{
"indexed": false,
"internalType": "bool",
"name": "exactOutput",
"type": "bool"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
},
{
"indexed": false,
"internalType": "int32",
"name": "activeTick",
"type": "int32"
}
],
"name": "Swap",
"type": "event"
},
{
"anonymous": false,
"inputs": [
{
"indexed": false,
"internalType": "uint256",
"name": "fromTokenId",
"type": "uint256"
},
{
"indexed": false,
"internalType": "uint256",
"name": "toTokenId",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
}
],
"indexed": false,
"internalType": "struct IPool.RemoveLiquidityParams[]",
"name": "params",
"type": "tuple[]"
}
],
"name": "TransferLiquidity",
"type": "event"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint8",
"name": "kind",
"type": "uint8"
},
{
"internalType": "int32",
"name": "pos",
"type": "int32"
},
{
"internalType": "bool",
"name": "isDelta",
"type": "bool"
},
{
"internalType": "uint128",
"name": "deltaA",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "deltaB",
"type": "uint128"
}
],
"internalType": "struct IPool.AddLiquidityParams[]",
"name": "params",
"type": "tuple[]"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "addLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "tokenAAmount",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenBAmount",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint128",
"name": "deltaA",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "deltaB",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "deltaLpBalance",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"internalType": "uint8",
"name": "kind",
"type": "uint8"
},
{
"internalType": "int32",
"name": "lowerTick",
"type": "int32"
},
{
"internalType": "bool",
"name": "isActive",
"type": "bool"
}
],
"internalType": "struct IPool.BinDelta[]",
"name": "binDeltas",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
}
],
"name": "balanceOf",
"outputs": [
{
"internalType": "uint256",
"name": "lpToken",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "binBalanceA",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "binBalanceB",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int32",
"name": "tick",
"type": "int32"
}
],
"name": "binMap",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "int32",
"name": "tick",
"type": "int32"
},
{
"internalType": "uint256",
"name": "kind",
"type": "uint256"
}
],
"name": "binPositions",
"outputs": [
{
"internalType": "uint128",
"name": "",
"type": "uint128"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "factory",
"outputs": [
{
"internalType": "contract IFactory",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "fee",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "lookback",
"outputs": [
{
"internalType": "int256",
"name": "",
"type": "int256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
}
],
"name": "getBin",
"outputs": [
{
"components": [
{
"internalType": "uint128",
"name": "reserveA",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "reserveB",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "mergeBinBalance",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "mergeId",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "totalSupply",
"type": "uint128"
},
{
"internalType": "uint8",
"name": "kind",
"type": "uint8"
},
{
"internalType": "int32",
"name": "lowerTick",
"type": "int32"
}
],
"internalType": "struct IPool.BinState",
"name": "bin",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getCurrentTwa",
"outputs": [
{
"internalType": "int256",
"name": "",
"type": "int256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getState",
"outputs": [
{
"components": [
{
"internalType": "int32",
"name": "activeTick",
"type": "int32"
},
{
"internalType": "uint8",
"name": "status",
"type": "uint8"
},
{
"internalType": "uint128",
"name": "binCounter",
"type": "uint128"
},
{
"internalType": "uint64",
"name": "protocolFeeRatio",
"type": "uint64"
}
],
"internalType": "struct IPool.State",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "getTwa",
"outputs": [
{
"components": [
{
"internalType": "int96",
"name": "twa",
"type": "int96"
},
{
"internalType": "int96",
"name": "value",
"type": "int96"
},
{
"internalType": "uint64",
"name": "lastTimestamp",
"type": "uint64"
}
],
"internalType": "struct IPool.TwaState",
"name": "",
"type": "tuple"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"internalType": "uint32",
"name": "maxRecursion",
"type": "uint32"
}
],
"name": "migrateBinUpStack",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "tokenId",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
}
],
"internalType": "struct IPool.RemoveLiquidityParams[]",
"name": "params",
"type": "tuple[]"
}
],
"name": "removeLiquidity",
"outputs": [
{
"internalType": "uint256",
"name": "tokenAOut",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "tokenBOut",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint128",
"name": "deltaA",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "deltaB",
"type": "uint128"
},
{
"internalType": "uint256",
"name": "deltaLpBalance",
"type": "uint256"
},
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"internalType": "uint8",
"name": "kind",
"type": "uint8"
},
{
"internalType": "int32",
"name": "lowerTick",
"type": "int32"
},
{
"internalType": "bool",
"name": "isActive",
"type": "bool"
}
],
"internalType": "struct IPool.BinDelta[]",
"name": "binDeltas",
"type": "tuple[]"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [
{
"internalType": "address",
"name": "recipient",
"type": "address"
},
{
"internalType": "uint256",
"name": "amount",
"type": "uint256"
},
{
"internalType": "bool",
"name": "tokenAIn",
"type": "bool"
},
{
"internalType": "bool",
"name": "exactOutput",
"type": "bool"
},
{
"internalType": "uint256",
"name": "sqrtPriceLimit",
"type": "uint256"
},
{
"internalType": "bytes",
"name": "data",
"type": "bytes"
}
],
"name": "swap",
"outputs": [
{
"internalType": "uint256",
"name": "amountIn",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "amountOut",
"type": "uint256"
}
],
"stateMutability": "nonpayable",
"type": "function"
},
{
"inputs": [],
"name": "tickSpacing",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenA",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenAScale",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenB",
"outputs": [
{
"internalType": "contract IERC20",
"name": "",
"type": "address"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [],
"name": "tokenBScale",
"outputs": [
{
"internalType": "uint256",
"name": "",
"type": "uint256"
}
],
"stateMutability": "view",
"type": "function"
},
{
"inputs": [
{
"internalType": "uint256",
"name": "fromTokenId",
"type": "uint256"
},
{
"internalType": "uint256",
"name": "toTokenId",
"type": "uint256"
},
{
"components": [
{
"internalType": "uint128",
"name": "binId",
"type": "uint128"
},
{
"internalType": "uint128",
"name": "amount",
"type": "uint128"
}
],
"internalType": "struct IPool.RemoveLiquidityParams[]",
"name": "params",
"type": "tuple[]"
}
],
"name": "transferLiquidity",
"outputs": [],
"stateMutability": "nonpayable",
"type": "function"
}
]

View 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

View File

@@ -0,0 +1,43 @@
use anyhow::Result;
use std::{fs, io::Write};
use substreams_ethereum::Abigen;
fn main() -> Result<()> {
let abi_folder = "abi";
let output_folder = "src/abi";
let files = fs::read_dir(abi_folder)?;
let mut mod_rs_content = String::new();
for file in files {
let file = file?;
let file_name = file.file_name();
let file_name = file_name.to_string_lossy();
if !file_name.ends_with(".json") {
continue;
}
let contract_name = file_name.split('.').next().unwrap();
let input_path = format!("{}/{}", abi_folder, file_name);
let output_path = format!("{}/{}.rs", output_folder, contract_name);
mod_rs_content.push_str(&format!("pub mod {};\n", contract_name));
if std::path::Path::new(&output_path).exists() {
continue;
}
Abigen::new(contract_name, &input_path)?
.generate()?
.write_to_file(&output_path)?;
}
let mod_rs_path = format!("{}/mod.rs", output_folder);
let mut mod_rs_file = fs::File::create(mod_rs_path)?;
mod_rs_file.write_all(mod_rs_content.as_bytes())?;
Ok(())
}

View File

@@ -0,0 +1,19 @@
syntax = "proto3";
package eth.factory.v1;
message Pools {
repeated Pool pools = 1;
}
message Pool {
bytes pool_id = 1;
fixed64 log_ordinal = 2;
}
message Transfer {
bytes from = 1;
bytes to = 2;
string token = 3;
string amount = 4;
}

View File

@@ -0,0 +1,15 @@
syntax = "proto3";
package eth.pool.v1;
message Transfers {
repeated Transfer transfers = 1;
}
message Transfer {
string from = 1;
string to = 2;
uint64 token_id = 3;
string trx_hash = 4;
uint64 ordinal = 5;
}

View File

@@ -0,0 +1,113 @@
syntax = "proto3";
package tycho.evm.v1;
// This file contains the proto definitions for Substreams common to all integrations.
// A struct describing a block.
message Block {
// The blocks hash.
bytes hash = 1;
// The parent blocks hash.
bytes parent_hash = 2;
// The block number.
uint64 number = 3;
// The block timestamp.
uint64 ts = 4;
}
// A struct describing a transaction.
message Transaction {
// The transaction hash.
bytes hash = 1;
// The sender of the transaction.
bytes from = 2;
// The receiver of the transaction.
bytes to = 3;
// The transactions index within the block.
// TODO: should this be uint32? to match the type from the native substream type?
uint64 index = 4;
}
// Enum to specify the type of a change.
enum ChangeType {
CHANGE_TYPE_UNSPECIFIED = 0;
CHANGE_TYPE_UPDATE = 1;
CHANGE_TYPE_CREATION = 2;
CHANGE_TYPE_DELETION = 3;
}
// 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.
message Attribute {
// The name of the attribute.
string name = 1;
// The value of the attribute.
bytes value = 2;
// The type of change the attribute underwent.
ChangeType change = 3;
}
// 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".
message ProtocolComponent {
// A unique identifier for the component within the protocol.
// Can be e.g. a stringified address or a string describing the trading pair.
string id = 1;
// Addresses of the ERC20 tokens used by the component.
repeated bytes tokens = 2;
// Addresses of the contracts used by the component.
// Usually it is a single contract, but some protocols use multiple contracts.
repeated bytes contracts = 3;
// Attributes of the component. Used mainly be the native integration.
// The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent.
repeated Attribute static_att = 4;
// Type of change the component underwent.
ChangeType change = 5;
}
message TransactionProtocolComponents {
Transaction tx = 1;
repeated ProtocolComponent components = 2;
}
message GroupedTransactionProtocolComponents {
repeated TransactionProtocolComponents tx_components = 1;
}
// 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.
message BalanceChange {
// The address of the ERC20 token whose balance changed.
bytes token = 1;
// The new balance of the token.
bytes balance = 2;
// 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.
bytes component_id = 3;
}
// 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.
message BalanceDelta {
uint64 ord = 1;
// The tx hash of the transaction that caused the balance change.
Transaction tx = 2;
// The address of the ERC20 token whose balance changed.
bytes token = 3;
// The delta balance of the token.
bytes delta = 4;
// 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.
bytes component_id = 5;
}
message BalanceDeltas {
repeated BalanceDelta balance_deltas = 1;
}

View File

@@ -0,0 +1,32 @@
syntax = "proto3";
package tycho.evm.v1;
import "tycho/evm/v1/common.proto";
// This file contains the definition for the native integration of Substreams.
// A component is a set of attributes that are associated with a custom entity.
message EntityChanges {
// A unique identifier of the entity within the protocol.
string component_id = 1;
// The set of attributes that are associated with the entity.
repeated Attribute attributes = 2;
}
message TransactionEntityChanges {
Transaction tx = 1;
repeated EntityChanges entity_changes = 2;
// An array of newly added components.
repeated ProtocolComponent component_changes = 3;
// An array of balance changes to components.
repeated BalanceChange balance_changes = 4;
}
// A set of transaction changes within a single block.
message BlockEntityChanges {
// The block for which these changes are collectively computed.
Block block = 1;
// The set of transaction changes observed in the specified block.
repeated TransactionEntityChanges changes = 2;
}

View File

@@ -0,0 +1,50 @@
syntax = "proto3";
package tycho.evm.v1;
import "tycho/evm/v1/common.proto";
// This file contains proto definitions specific to the VM integration.
// A key value entry into contract storage.
message ContractSlot {
// A contract's storage slot.
bytes slot = 2;
// The new value for this storage slot.
bytes value = 3;
}
// Changes made to a single contract's state.
message ContractChange {
// The contract's address
bytes address = 1;
// The new native balance of the contract, empty bytes indicates no change.
bytes balance = 2;
// The new code of the contract, empty bytes indicates no change.
bytes code = 3;
// The changes to this contract's slots, empty sequence indicates no change.
repeated ContractSlot slots = 4;
// Whether this is an update, a creation or a deletion.
ChangeType change = 5;
}
// A set of changes aggregated by transaction.
message TransactionContractChanges {
// The transaction instance that results in the changes.
Transaction tx = 1;
// Contains the changes induced by the above transaction, aggregated on a per-contract basis.
// Must include changes to every contract that is tracked by all ProtocolComponents.
repeated ContractChange contract_changes = 2;
// An array of any component changes.
repeated ProtocolComponent component_changes = 3;
// An array of balance changes to components.
repeated BalanceChange balance_changes = 4;
}
// A set of transaction changes within a single block.
message BlockContractChanges {
// The block for which these changes are collectively computed.
Block block = 1;
// The set of transaction changes observed in the specified block.
repeated TransactionContractChanges changes = 2;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,2 @@
pub mod factory;
pub mod pool;

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,190 @@
/// This file contains helpers to capture contract changes from the expanded block model. These
/// leverage the `code_changes`, `balance_changes`, and `storage_changes` fields available on the
/// `Call` type provided by block model in a substream (i.e. `logs_and_calls`, etc).
///
/// ⚠️ These helpers *only* work if the **expanded block model** is available, more info blow.
/// https://streamingfastio.medium.com/new-block-model-to-accelerate-chain-integration-9f65126e5425
use std::collections::HashMap;
use substreams_ethereum::pb::eth;
use pb::tycho::evm::v1::{self as tycho};
use substreams::store::{StoreGet, StoreGetInt64};
use crate::pb;
struct SlotValue {
new_value: Vec<u8>,
start_value: Vec<u8>,
}
impl SlotValue {
fn has_changed(&self) -> bool {
self.start_value != self.new_value
}
}
// Uses a map for slots, protobuf does not allow bytes in hashmap keys
pub struct InterimContractChange {
address: Vec<u8>,
balance: Vec<u8>,
code: Vec<u8>,
slots: HashMap<Vec<u8>, SlotValue>,
change: tycho::ChangeType,
}
impl From<InterimContractChange> for tycho::ContractChange {
fn from(value: InterimContractChange) -> Self {
tycho::ContractChange {
address: value.address,
balance: value.balance,
code: value.code,
slots: value
.slots
.into_iter()
.filter(|(_, value)| value.has_changed())
.map(|(slot, value)| tycho::ContractSlot {
slot,
value: value.new_value,
})
.collect(),
change: value.change.into(),
}
}
}
pub fn extract_contract_changes(
block: &eth::v2::Block,
contracts: StoreGetInt64,
transaction_contract_changes: &mut HashMap<u64, tycho::TransactionContractChanges>,
) {
let mut changed_contracts: HashMap<Vec<u8>, InterimContractChange> = HashMap::new();
// Collect all accounts created in this block
let created_accounts: HashMap<_, _> = block
.transactions()
.flat_map(|tx| {
tx.calls.iter().flat_map(|call| {
call.account_creations
.iter()
.map(|ac| (&ac.account, ac.ordinal))
})
})
.collect();
block.transactions().for_each(|block_tx| {
let mut storage_changes = Vec::new();
let mut balance_changes = Vec::new();
let mut code_changes = Vec::new();
block_tx
.calls
.iter()
.filter(|call| {
!call.state_reverted
&& contracts
.get_last(format!("pool:{0}", hex::encode(&call.address)))
.is_some()
})
.for_each(|call| {
storage_changes.extend(call.storage_changes.iter());
balance_changes.extend(call.balance_changes.iter());
code_changes.extend(call.code_changes.iter());
});
storage_changes.sort_unstable_by_key(|change| change.ordinal);
balance_changes.sort_unstable_by_key(|change| change.ordinal);
code_changes.sort_unstable_by_key(|change| change.ordinal);
storage_changes.iter().for_each(|storage_change| {
let contract_change = changed_contracts
.entry(storage_change.address.clone())
.or_insert_with(|| InterimContractChange {
address: storage_change.address.clone(),
balance: Vec::new(),
code: Vec::new(),
slots: HashMap::new(),
change: if created_accounts.contains_key(&storage_change.address) {
tycho::ChangeType::Creation
} else {
tycho::ChangeType::Update
},
});
let slot_value = contract_change
.slots
.entry(storage_change.key.clone())
.or_insert_with(|| SlotValue {
new_value: storage_change.new_value.clone(),
start_value: storage_change.old_value.clone(),
});
slot_value
.new_value
.copy_from_slice(&storage_change.new_value);
});
balance_changes.iter().for_each(|balance_change| {
let contract_change = changed_contracts
.entry(balance_change.address.clone())
.or_insert_with(|| InterimContractChange {
address: balance_change.address.clone(),
balance: Vec::new(),
code: Vec::new(),
slots: HashMap::new(),
change: if created_accounts.contains_key(&balance_change.address) {
tycho::ChangeType::Creation
} else {
tycho::ChangeType::Update
},
});
if let Some(new_balance) = &balance_change.new_value {
contract_change.balance.clear();
contract_change
.balance
.extend_from_slice(&new_balance.bytes);
}
});
code_changes.iter().for_each(|code_change| {
let contract_change = changed_contracts
.entry(code_change.address.clone())
.or_insert_with(|| InterimContractChange {
address: code_change.address.clone(),
balance: Vec::new(),
code: Vec::new(),
slots: HashMap::new(),
change: if created_accounts.contains_key(&code_change.address) {
tycho::ChangeType::Creation
} else {
tycho::ChangeType::Update
},
});
contract_change.code.clear();
contract_change
.code
.extend_from_slice(&code_change.new_code);
});
if !storage_changes.is_empty() || !balance_changes.is_empty() || !code_changes.is_empty() {
transaction_contract_changes
.entry(block_tx.index.into())
.or_insert_with(|| tycho::TransactionContractChanges {
tx: Some(tycho::Transaction {
hash: block_tx.hash.clone(),
from: block_tx.from.clone(),
to: block_tx.to.clone(),
index: block_tx.index as u64,
}),
contract_changes: vec![],
component_changes: vec![],
balance_changes: vec![],
})
.contract_changes
.extend(changed_contracts.drain().map(|(_, change)| change.into()));
}
});
}

View File

@@ -0,0 +1,4 @@
mod abi;
mod contract_changes;
mod modules;
mod pb;

View File

@@ -0,0 +1,418 @@
use std::collections::HashMap;
use anyhow::Result;
use substreams::pb::substreams::StoreDeltas;
use substreams::store::{
StoreAdd, StoreAddBigInt, StoreAddInt64, StoreGet, StoreGetInt64, StoreGetString, StoreNew,
StoreSet, StoreSetString,
};
use substreams::key;
use substreams::scalar::BigInt;
use substreams::{hex, log};
use substreams_ethereum::block_view::LogView;
use substreams_ethereum::pb::eth;
use itertools::Itertools;
use pb::tycho::evm::v1::{self as tycho};
use contract_changes::extract_contract_changes;
use substreams_ethereum::Event;
use crate::{abi, contract_changes, pb};
const FACTORY: [u8; 20] = hex!("5C69bEe701ef814a2B6a3EDD4B1652CB9cc5aA6f");
// struct BinDelta {
// delta_a: BigInt,
// delta_b: BigInt,
// delta_lp_balance: BigInt,
// bin_id: BigInt,
// kind: BigInt,
// lower_tick: BigInt,
// is_active: bool,
// }
/// This struct purely exists to spoof the `PartialEq` trait for `Transaction` so we can use it in
/// a later groupby operation.
#[derive(Debug)]
struct TransactionWrapper(tycho::Transaction);
impl PartialEq for TransactionWrapper {
fn eq(&self, other: &Self) -> bool {
self.0.hash == other.0.hash
}
}
fn tx_from_log(log: &LogView) -> tycho::Transaction {
tycho::Transaction {
hash: log.receipt.transaction.hash.clone(),
from: log.receipt.transaction.from.clone(),
to: log.receipt.transaction.to.clone(),
index: Into::<u64>::into(log.receipt.transaction.index),
}
}
#[substreams::handlers::map]
pub fn map_pools_created(
block: eth::v2::Block,
) -> Result<tycho::GroupedTransactionProtocolComponents> {
// Gather contract changes by indexing `PoolCreated` events and analysing the `Create` call
// We store these as a hashmap by tx hash since we need to agg by tx hash later
Ok(tycho::GroupedTransactionProtocolComponents {
tx_components: block
.transactions()
.filter_map(|tx| {
let components: Vec<tycho::ProtocolComponent> = tx
.logs_with_calls()
.filter(|(_, call)| !call.call.state_reverted)
.filter(|(log, _)| log.address == FACTORY)
.filter_map(|(log, call)| {
let pool_added = abi::factory::events::PoolCreated::match_and_decode(log)?;
log::info!("tacos");
Some(tycho::ProtocolComponent {
id: hex::encode(&pool_added.pool_address),
tokens: vec![pool_added.token_a, pool_added.token_b],
contracts: vec![FACTORY.into(), pool_added.pool_address],
static_att: vec![
tycho::Attribute {
name: "activeTick".into(),
value: pool_added.active_tick.to_signed_bytes_be(),
change: tycho::ChangeType::Creation.into(),
},
tycho::Attribute {
name: "lookback".into(),
value: pool_added.lookback.to_signed_bytes_be(),
change: tycho::ChangeType::Creation.into(),
},
],
change: tycho::ChangeType::Creation.into(),
})
})
.collect::<Vec<_>>();
if !components.is_empty() {
Some(tycho::TransactionProtocolComponents {
tx: Some(tycho::Transaction {
hash: tx.hash.clone(),
from: tx.from.clone(),
to: tx.to.clone(),
index: Into::<u64>::into(tx.index),
}),
components,
})
} else {
None
}
})
.collect::<Vec<_>>(),
})
}
/// Simply stores the `ProtocolComponent`s with the pool id as the key
#[substreams::handlers::store]
pub fn store_pools_created(map: tycho::GroupedTransactionProtocolComponents, 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,
);
}
/// Simply stores the `ProtocolComponent`s with the pool id as the key
#[substreams::handlers::store]
pub fn store_pools_tokens(map: tycho::GroupedTransactionProtocolComponents, store: StoreSetString) {
map.tx_components
.iter()
.flat_map(|tx_components| &tx_components.components)
.for_each(|component| {
store.set(
0,
format!("pool:{0}", component.id),
&component
.tokens
.iter()
.map(|token| hex::encode(token))
.join(":".into()),
);
});
}
/// Since the `Swap`, `AddLiquidity`, `RemoveLiuidity` events administer only deltas, we need to
/// leverage a map and a store to be able to tally up final balances for tokens in a pool.
#[substreams::handlers::map]
pub fn map_balance_deltas(
block: eth::v2::Block,
pools_store: StoreGetInt64,
tokens_store: StoreGetString,
) -> Result<tycho::BalanceDeltas, anyhow::Error> {
let deltas = block
.logs()
.filter(|log| {
pools_store
.get_last(format!("pool:{0}", hex::encode(&log.address())))
.is_some()
})
.flat_map(|log| {
if let Some(event) = abi::pool::events::Swap::match_and_decode(log) {
let tokens = tokens_store
.get_last(format!("pool:{}", hex::encode(log.address())))
.unwrap()
.split(":")
.map(|token| token.to_owned()) // Clone the tokens
.collect::<Vec<_>>();
let (token_a, token_b) = if event.token_a_in {
(
hex::decode(tokens[0].clone()).unwrap(),
hex::decode(tokens[1].clone()).unwrap(),
)
} else {
(
hex::decode(tokens[1].clone()).unwrap(),
hex::decode(tokens[0].clone()).unwrap(),
)
};
vec![
tycho::BalanceDelta {
ord: log.log.ordinal,
tx: Some(tx_from_log(&log)),
token: token_a,
delta: event.amount_in.to_signed_bytes_be(),
component_id: log.address().into(),
},
tycho::BalanceDelta {
ord: log.log.ordinal,
tx: Some(tx_from_log(&log)),
token: token_b,
delta: event.amount_out.to_signed_bytes_be(),
component_id: log.address().into(),
},
]
} else if let Some(event) = abi::pool::events::AddLiquidity::match_and_decode(log) {
event
.bin_deltas
.into_iter()
.flat_map(
|(
delta_a,
delta_b,
delta_lp_balance,
bin_id,
kind,
lower_tick,
is_active,
)| {
let tokens = tokens_store
.get_last(format!("pool:{}", hex::encode(log.address())))
.unwrap()
.split(":")
.map(|token| token.to_owned()) // Clone the tokens
.collect::<Vec<_>>();
vec![
tycho::BalanceDelta {
ord: log.log.ordinal,
tx: Some(tx_from_log(&log)),
token: hex::decode(tokens[0].clone()).unwrap(),
delta: delta_a.to_signed_bytes_be(),
component_id: log.address().into(),
},
tycho::BalanceDelta {
ord: log.log.ordinal,
tx: Some(tx_from_log(&log)),
token: hex::decode(tokens[1].clone()).unwrap(),
delta: delta_b.to_signed_bytes_be(),
component_id: log.address().into(),
},
]
},
)
.collect::<Vec<_>>()
} else if let Some(event) = abi::pool::events::RemoveLiquidity::match_and_decode(log) {
event
.bin_deltas
.into_iter()
.flat_map(
|(
delta_a,
delta_b,
delta_lp_balance,
bin_id,
kind,
lower_tick,
is_active,
)| {
let tokens = tokens_store
.get_last(format!("pool:{}", hex::encode(log.address())))
.unwrap()
.split(":")
.map(|token| token.to_owned()) // Clone the tokens
.collect::<Vec<_>>();
let neg_delta_a: BigInt = delta_a * -1;
let neg_delta_b: BigInt = delta_b * -1;
vec![
tycho::BalanceDelta {
ord: log.log.ordinal,
tx: Some(tx_from_log(&log)),
token: hex::decode(tokens[0].clone()).unwrap(),
delta: neg_delta_a.to_signed_bytes_be(),
component_id: log.address().into(),
},
tycho::BalanceDelta {
ord: log.log.ordinal,
tx: Some(tx_from_log(&log)),
token: hex::decode(tokens[1].clone()).unwrap(),
delta: neg_delta_b.to_signed_bytes_be(),
component_id: log.address().into(),
},
]
},
)
.collect::<Vec<_>>()
} else {
vec![]
}
})
.collect::<Vec<_>>();
Ok(tycho::BalanceDeltas {
balance_deltas: 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_balance_changes(deltas: tycho::BalanceDeltas, store: StoreAddBigInt) {
deltas.balance_deltas.iter().for_each(|delta| {
store.add(
delta.ord,
format!(
"pool:{0}:token:{1}",
hex::encode(&delta.component_id),
hex::encode(&delta.token)
),
BigInt::from_signed_bytes_be(&delta.delta),
);
});
}
/// 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_contract_changes`
/// map. Each block of code will extend the `TransactionContractChanges` 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 `BlockContractChanges`
/// is ordered by transactions properly.
#[substreams::handlers::map]
pub fn map_changes(
block: eth::v2::Block,
grouped_components: tycho::GroupedTransactionProtocolComponents,
deltas: tycho::BalanceDeltas,
components_store: StoreGetInt64,
balance_store: StoreDeltas, // Note, this map module is using the `deltas` mode for the store.
) -> Result<tycho::BlockContractChanges> {
// We merge contract changes by transaction (identified by transaction index) making it easy to
// sort them at the very end.
let mut transaction_contract_changes: HashMap<_, tycho::TransactionContractChanges> =
HashMap::new();
// `ProtocolComponents` are gathered from `map_pools_created` which just need a bit of work to
// convert into `TransactionContractChanges`
grouped_components
.tx_components
.iter()
.for_each(|tx_component| {
let tx = tx_component.tx.as_ref().unwrap();
transaction_contract_changes
.entry(tx.index)
.or_insert_with(|| tycho::TransactionContractChanges {
tx: Some(tx.clone()),
contract_changes: vec![],
component_changes: vec![],
balance_changes: vec![],
})
.component_changes
.extend_from_slice(&tx_component.components);
});
// Balance changes are gathered by the `StoreDelta` based on transfer and liquidity events
// creating `BalanceDeltas`. We essentially just process the changes that occured 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.
balance_store
.deltas
.into_iter()
.zip(deltas.balance_deltas)
.map(|(store_delta, balance_delta)| {
let pool_id = key::segment_at(&store_delta.key, 1);
let token_id = key::segment_at(&store_delta.key, 3);
(
balance_delta.tx.unwrap(),
tycho::BalanceChange {
token: hex::decode(token_id).expect("Token ID not valid hex"),
balance: store_delta.new_value,
component_id: hex::decode(pool_id).expect("Token ID not valid hex"),
},
)
})
// We need to group the balance changes by tx hash for the `TransactionContractChanges` agg
.group_by(|(tx, _)| TransactionWrapper(tx.clone()))
.into_iter()
.for_each(|(tx_wrapped, group)| {
let tx = tx_wrapped.0;
transaction_contract_changes
.entry(tx.index)
.or_insert_with(|| tycho::TransactionContractChanges {
tx: Some(tx.clone()),
contract_changes: vec![],
component_changes: vec![],
balance_changes: vec![],
})
.balance_changes
.extend(group.map(|(_, change)| change));
});
// General helper for extracting contract changes. Uses block, our component store which holds
// all of our tracked deployed pool addresses, and the map of tx contract changes which we
// output into for final processing later.
extract_contract_changes(&block, components_store, &mut transaction_contract_changes);
// Process all `transaction_contract_changes` for final output in the `BlockContractChanges`,
// sorted by transaction index (the key).
Ok(tycho::BlockContractChanges {
block: Some(tycho::Block {
number: block.number,
hash: block.hash.clone(),
parent_hash: block
.header
.as_ref()
.expect("Block header not present")
.parent_hash
.clone(),
ts: block.timestamp_seconds(),
}),
changes: transaction_contract_changes
.drain()
.sorted_unstable_by_key(|(index, _)| index.clone())
.filter_map(|(_, change)| {
if change.contract_changes.is_empty()
&& change.component_changes.is_empty()
&& change.balance_changes.is_empty()
{
None
} else {
Some(change)
}
})
.collect::<Vec<_>>(),
})
}

View File

@@ -0,0 +1,28 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pools {
#[prost(message, repeated, tag="1")]
pub pools: ::prost::alloc::vec::Vec<Pool>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pool {
#[prost(bytes="vec", tag="1")]
pub pool_id: ::prost::alloc::vec::Vec<u8>,
#[prost(fixed64, tag="2")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transfer {
#[prost(bytes="vec", tag="1")]
pub from: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="2")]
pub to: ::prost::alloc::vec::Vec<u8>,
#[prost(string, tag="3")]
pub token: ::prost::alloc::string::String,
#[prost(string, tag="4")]
pub amount: ::prost::alloc::string::String,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,28 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pools {
#[prost(message, repeated, tag="1")]
pub pools: ::prost::alloc::vec::Vec<Pool>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Pool {
#[prost(bytes="vec", tag="1")]
pub pool_id: ::prost::alloc::vec::Vec<u8>,
#[prost(fixed64, tag="2")]
pub log_ordinal: u64,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transfer {
#[prost(bytes="vec", tag="1")]
pub from: ::prost::alloc::vec::Vec<u8>,
#[prost(bytes="vec", tag="2")]
pub to: ::prost::alloc::vec::Vec<u8>,
#[prost(string, tag="3")]
pub token: ::prost::alloc::string::String,
#[prost(string, tag="4")]
pub amount: ::prost::alloc::string::String,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,22 @@
// @generated
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transfers {
#[prost(message, repeated, tag="1")]
pub transfers: ::prost::alloc::vec::Vec<Transfer>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transfer {
#[prost(string, tag="1")]
pub from: ::prost::alloc::string::String,
#[prost(string, tag="2")]
pub to: ::prost::alloc::string::String,
#[prost(uint64, tag="3")]
pub token_id: u64,
#[prost(string, tag="4")]
pub trx_hash: ::prost::alloc::string::String,
#[prost(uint64, tag="5")]
pub ordinal: u64,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,10 @@
// @generated
pub mod tycho {
pub mod evm {
// @@protoc_insertion_point(attribute:tycho.evm.v1)
pub mod v1 {
include!("tycho.evm.v1.rs");
// @@protoc_insertion_point(tycho.evm.v1)
}
}
}

View File

@@ -0,0 +1,236 @@
// @generated
// This file contains the proto definitions for Substreams common to all integrations.
/// A struct describing a block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Block {
/// The blocks hash.
#[prost(bytes="vec", tag="1")]
pub hash: ::prost::alloc::vec::Vec<u8>,
/// The parent blocks hash.
#[prost(bytes="vec", tag="2")]
pub parent_hash: ::prost::alloc::vec::Vec<u8>,
/// The block number.
#[prost(uint64, tag="3")]
pub number: u64,
/// The block timestamp.
#[prost(uint64, tag="4")]
pub ts: u64,
}
/// A struct describing a transaction.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Transaction {
/// The transaction hash.
#[prost(bytes="vec", tag="1")]
pub hash: ::prost::alloc::vec::Vec<u8>,
/// The sender of the transaction.
#[prost(bytes="vec", tag="2")]
pub from: ::prost::alloc::vec::Vec<u8>,
/// The receiver of the transaction.
#[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")]
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.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct Attribute {
/// The name of the attribute.
#[prost(string, tag="1")]
pub name: ::prost::alloc::string::String,
/// The value of the attribute.
#[prost(bytes="vec", tag="2")]
pub value: ::prost::alloc::vec::Vec<u8>,
/// The type of change the attribute underwent.
#[prost(enumeration="ChangeType", tag="3")]
pub change: 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".
#[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")]
pub id: ::prost::alloc::string::String,
/// Addresses of the ERC20 tokens used by the component.
#[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")]
pub contracts: ::prost::alloc::vec::Vec<::prost::alloc::vec::Vec<u8>>,
/// Attributes of the component. Used mainly be the native integration.
/// The inner ChangeType of the attribute has to match the ChangeType of the ProtocolComponent.
#[prost(message, repeated, tag="4")]
pub static_att: ::prost::alloc::vec::Vec<Attribute>,
/// Type of change the component underwent.
#[prost(enumeration="ChangeType", tag="5")]
pub change: i32,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TransactionProtocolComponents {
#[prost(message, optional, tag="1")]
pub tx: ::core::option::Option<Transaction>,
#[prost(message, repeated, tag="2")]
pub components: ::prost::alloc::vec::Vec<ProtocolComponent>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct GroupedTransactionProtocolComponents {
#[prost(message, repeated, tag="1")]
pub tx_components: ::prost::alloc::vec::Vec<TransactionProtocolComponents>,
}
/// 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.
#[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")]
pub token: ::prost::alloc::vec::Vec<u8>,
/// The new balance of the token.
#[prost(bytes="vec", tag="2")]
pub balance: ::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="3")]
pub component_id: ::prost::alloc::vec::Vec<u8>,
}
/// 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.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceDelta {
#[prost(uint64, tag="1")]
pub ord: u64,
/// The tx hash of the transaction that caused the balance change.
#[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")]
pub token: ::prost::alloc::vec::Vec<u8>,
/// The delta balance of the token.
#[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")]
pub component_id: ::prost::alloc::vec::Vec<u8>,
}
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BalanceDeltas {
#[prost(message, repeated, tag="1")]
pub balance_deltas: ::prost::alloc::vec::Vec<BalanceDelta>,
}
/// Enum to specify the type of a change.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord, ::prost::Enumeration)]
#[repr(i32)]
pub enum ChangeType {
Unspecified = 0,
Update = 1,
Creation = 2,
Deletion = 3,
}
impl ChangeType {
/// String value of the enum field names used in the ProtoBuf definition.
///
/// The values are not transformed in any way and thus are considered stable
/// (if the ProtoBuf definition does not change) and safe for programmatic use.
pub fn as_str_name(&self) -> &'static str {
match self {
ChangeType::Unspecified => "CHANGE_TYPE_UNSPECIFIED",
ChangeType::Update => "CHANGE_TYPE_UPDATE",
ChangeType::Creation => "CHANGE_TYPE_CREATION",
ChangeType::Deletion => "CHANGE_TYPE_DELETION",
}
}
/// Creates an enum from field names used in the ProtoBuf definition.
pub fn from_str_name(value: &str) -> ::core::option::Option<Self> {
match value {
"CHANGE_TYPE_UNSPECIFIED" => Some(Self::Unspecified),
"CHANGE_TYPE_UPDATE" => Some(Self::Update),
"CHANGE_TYPE_CREATION" => Some(Self::Creation),
"CHANGE_TYPE_DELETION" => Some(Self::Deletion),
_ => None,
}
}
}
// This file contains proto definitions specific to the VM integration.
/// A key value entry into contract storage.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContractSlot {
/// A contract's storage slot.
#[prost(bytes="vec", tag="2")]
pub slot: ::prost::alloc::vec::Vec<u8>,
/// The new value for this storage slot.
#[prost(bytes="vec", tag="3")]
pub value: ::prost::alloc::vec::Vec<u8>,
}
/// Changes made to a single contract's state.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct ContractChange {
/// The contract's address
#[prost(bytes="vec", tag="1")]
pub address: ::prost::alloc::vec::Vec<u8>,
/// The new native balance of the contract, empty bytes indicates no change.
#[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")]
pub code: ::prost::alloc::vec::Vec<u8>,
/// The changes to this contract's slots, empty sequence indicates no change.
#[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")]
pub change: i32,
}
/// A set of changes aggregated by transaction.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct TransactionContractChanges {
/// The transaction instance that results in the changes.
#[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.
/// Must include changes to every contract that is tracked by all ProtocolComponents.
#[prost(message, repeated, tag="2")]
pub contract_changes: ::prost::alloc::vec::Vec<ContractChange>,
/// An array of any component changes.
#[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")]
pub balance_changes: ::prost::alloc::vec::Vec<BalanceChange>,
}
/// A set of transaction changes within a single block.
#[allow(clippy::derive_partial_eq_without_eq)]
#[derive(Clone, PartialEq, ::prost::Message)]
pub struct BlockContractChanges {
/// The block for which these changes are collectively computed.
#[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")]
pub changes: ::prost::alloc::vec::Vec<TransactionContractChanges>,
}
// @@protoc_insertion_point(module)

View File

@@ -0,0 +1,73 @@
specVersion: v0.1.0
package:
name: "substreams_maverick"
version: v0.1.0
protobuf:
files:
- tycho/evm/v1/vm.proto
- tycho/evm/v1/common.proto
importPaths:
- ../../proto/tycho/evm/v1/
- ./proto
binaries:
default:
type: wasm/rust-v1
file: target/wasm32-unknown-unknown/release/substreams_maverick.wasm
modules:
- name: map_pools_created
kind: map
initialBlock: 17216720
inputs:
- source: sf.ethereum.type.v2.Block
output:
type: proto:tycho.evm.v1.GroupedTransactionProtocolComponents
- name: store_pools_created
kind: store
initialBlock: 17216720
updatePolicy: add
valueType: int64
inputs:
- map: map_pools_created
- name: store_pools_tokens
kind: store
initialBlock: 17216720
updatePolicy: set
valueType: string
inputs:
- map: map_pools_created
- name: map_balance_deltas
kind: map
initialBlock: 17216720 # An arbitrary block that should change based on your requirements
inputs:
- source: sf.ethereum.type.v2.Block
- store: store_pools_created
- store: store_pools_tokens
output:
type: proto:tycho.evm.v1.BalanceDeltas
- name: store_balance_changes
kind: store
initialBlock: 17216720
updatePolicy: add
valueType: bigint
inputs:
- map: map_balance_deltas
- name: map_changes
kind: map
initialBlock: 17216720
inputs:
- source: sf.ethereum.type.v2.Block
- map: map_pools_created
- map: map_balance_deltas
- store: store_pools_created
- store: store_balance_changes
mode: deltas # This is the key property that simplifies `BalanceChange` handling
output:
type: proto:tycho.evm.v1.BlockContractChanges