-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: calculate L1 TVL using batch request #37
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
export const tokenBalancesBytecode = | ||
"0x608060405234801561001057600080fd5b5060405161063538038061063583398181016040528101906100329190610332565b60008151905060006001826100479190610516565b67ffffffffffffffff811115610086577f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6040519080825280602002602001820160405280156100b45781602001602082028036833780820191505090505b50905060005b828110156101e75760008482815181106100fd577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b6020026020010151905060008173ffffffffffffffffffffffffffffffffffffffff166370a08231886040518263ffffffff1660e01b81526004016101429190610443565b60206040518083038186803b15801561015a57600080fd5b505afa15801561016e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906101929190610386565b9050808484815181106101ce577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101818152505082600101925050506100ba565b8473ffffffffffffffffffffffffffffffffffffffff1631828281518110610238577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001018181525050600082604051602001610257919061045e565b60405160208183030381529060405290506020810180590381f35b6000610285610280846104b1565b610480565b905080838252602082019050828560208602820111156102a457600080fd5b60005b858110156102d457816102ba88826102de565b8452602084019350602083019250506001810190506102a7565b5050509392505050565b6000815190506102ed81610606565b92915050565b600082601f83011261030457600080fd5b8151610314848260208601610272565b91505092915050565b60008151905061032c8161061d565b92915050565b6000806040838503121561034557600080fd5b6000610353858286016102de565b925050602083015167ffffffffffffffff81111561037057600080fd5b61037c858286016102f3565b9150509250929050565b60006020828403121561039857600080fd5b60006103a68482850161031d565b91505092915050565b60006103bb8383610434565b60208301905092915050565b6103d08161056c565b82525050565b60006103e1826104ed565b6103eb8185610505565b93506103f6836104dd565b8060005b8381101561042757815161040e88826103af565b9750610419836104f8565b9250506001810190506103fa565b5085935050505092915050565b61043d8161059e565b82525050565b600060208201905061045860008301846103c7565b92915050565b6000602082019050818103600083015261047881846103d6565b905092915050565b6000604051905081810181811067ffffffffffffffff821117156104a7576104a66105d7565b5b8060405250919050565b600067ffffffffffffffff8211156104cc576104cb6105d7565b5b602082029050602081019050919050565b6000819050602082019050919050565b600081519050919050565b6000602082019050919050565b600082825260208201905092915050565b60006105218261059e565b915061052c8361059e565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610561576105606105a8565b5b828201905092915050565b60006105778261057e565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b61060f8161056c565b811461061a57600080fd5b50565b6106268161059e565b811461063157600080fd5b5056fe"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,22 @@ | ||
import assert from "assert"; | ||
import { Inject, Injectable, LoggerService } from "@nestjs/common"; | ||
import { WINSTON_MODULE_NEST_PROVIDER } from "nest-winston"; | ||
import { | ||
Address, | ||
ContractConstructorArgs, | ||
formatUnits, | ||
parseAbiParameters, | ||
parseUnits, | ||
} from "viem"; | ||
|
||
import { bridgeHubAbi, sharedBridgeAbi } from "@zkchainhub/metrics/l1/abis"; | ||
import { tokenBalancesAbi } from "@zkchainhub/metrics/l1/abis/tokenBalances.abi"; | ||
import { tokenBalancesBytecode } from "@zkchainhub/metrics/l1/bytecode"; | ||
import { AssetTvl } from "@zkchainhub/metrics/types"; | ||
import { IPricingService, PRICING_PROVIDER } from "@zkchainhub/pricing"; | ||
import { EvmProviderService } from "@zkchainhub/providers"; | ||
import { AbiWithAddress, ChainId, L1_CONTRACTS } from "@zkchainhub/shared"; | ||
import { erc20Tokens, isNativeToken, tokens } from "@zkchainhub/shared/tokens/tokens"; | ||
|
||
/** | ||
* Acts as a wrapper around Viem library to provide methods to interact with an EVM-based blockchain. | ||
|
@@ -27,10 +39,98 @@ export class L1MetricsService { | |
@Inject(WINSTON_MODULE_NEST_PROVIDER) private readonly logger: LoggerService, | ||
) {} | ||
|
||
//TODO: Implement l1Tvl. | ||
async l1Tvl(): Promise<{ [asset: string]: { amount: number; amountUsd: number } }> { | ||
return { ETH: { amount: 1000000, amountUsd: 1000000 } }; | ||
/** | ||
* Retrieves the Total Value Locked by token on L1 Shared Bridge contract | ||
* @returns A Promise that resolves to an array of AssetTvl objects representing the TVL for each asset. | ||
*/ | ||
async l1Tvl(): Promise<AssetTvl[]> { | ||
const erc20Addresses = erc20Tokens.map((token) => token.contractAddress); | ||
|
||
const balances = await this.fetchTokenBalances(erc20Addresses); | ||
const pricesRecord = await this.pricingService.getTokenPrices( | ||
tokens.map((token) => token.coingeckoId), | ||
); | ||
|
||
assert(Object.keys(pricesRecord).length === tokens.length, "Invalid prices length"); | ||
|
||
return this.calculateTvl(balances, erc20Addresses, pricesRecord); | ||
} | ||
|
||
/** | ||
* Calculates the Total Value Locked (TVL) for each token based on the provided balances, addresses, and prices. | ||
* @param balances - The balances object containing the ETH balance and an array of erc20 token addresses balance. | ||
* @param addresses - The array of erc20 addresses. | ||
* @param prices - The object containing the prices of tokens. | ||
* @returns An array of AssetTvl objects representing the TVL for each token in descending order. | ||
*/ | ||
private calculateTvl( | ||
balances: { ethBalance: bigint; addressesBalance: bigint[] }, | ||
addresses: Address[], | ||
prices: Record<string, number>, | ||
): AssetTvl[] { | ||
const tvl: AssetTvl[] = []; | ||
|
||
for (const token of tokens) { | ||
const { coingeckoId, ...tokenInfo } = token; | ||
|
||
const balance = isNativeToken(token) | ||
? balances.ethBalance | ||
: balances.addressesBalance[ | ||
addresses.indexOf(tokenInfo.contractAddress as Address) | ||
]; | ||
Comment on lines
+76
to
+80
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💎 |
||
|
||
assert(balance !== undefined, `Balance for ${tokenInfo.symbol} not found`); | ||
|
||
const price = prices[coingeckoId] as number; | ||
// math is done with bigints for better precision | ||
const tvlValue = formatUnits( | ||
balance * parseUnits(price.toString(), tokenInfo.decimals), | ||
tokenInfo.decimals * 2, | ||
); | ||
|
||
const assetTvl: AssetTvl = { | ||
amount: formatUnits(balance, tokenInfo.decimals), | ||
amountUsd: tvlValue, | ||
price: price.toString(), | ||
...tokenInfo, | ||
}; | ||
|
||
tvl.push(assetTvl); | ||
} | ||
|
||
// we assume the rounding error is negligible for sorting purposes | ||
tvl.sort((a, b) => Number(b.amountUsd) - Number(a.amountUsd)); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good clarifying comment there 💯 , just one extremely nitpick question but figured is worth asking: this might cause Eg, this is potentially possible:
It seems that the JavaScript engines now generally tend to implement the To wrap up, is the order of There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, this is something we might not need to care about for now, but it's a good observation. Let's keep it as it is for now. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. i think that the chances of two tokens to have the same tvl is really low on the most relevant tokens, this is more likely the situation on token with low balances or 0ish TVL There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. hahahahaha , nigiri is right. However we should be aware of this if we need to change it in the future. :) |
||
|
||
return tvl; | ||
} | ||
|
||
/** | ||
* Fetches the token balances for the given addresses and ETH balance. | ||
* Note: The last balance in the returned array is the ETH balance, so the fetch length should be addresses.length + 1. | ||
* @param addresses - An array of addresses for which to fetch the token balances. | ||
* @returns A promise that resolves to an object containing the ETH balance and an array of address balances. | ||
*/ | ||
private async fetchTokenBalances( | ||
addresses: Address[], | ||
): Promise<{ ethBalance: bigint; addressesBalance: bigint[] }> { | ||
const returnAbiParams = parseAbiParameters("uint256[]"); | ||
const args: ContractConstructorArgs<typeof tokenBalancesAbi> = [ | ||
L1_CONTRACTS.SHARED_BRIDGE, | ||
addresses, | ||
]; | ||
|
||
const [balances] = await this.evmProviderService.batchRequest( | ||
tokenBalancesAbi, | ||
tokenBalancesBytecode, | ||
args, | ||
returnAbiParams, | ||
); | ||
|
||
assert(balances.length === addresses.length + 1, "Invalid balances length"); | ||
|
||
return { ethBalance: balances[addresses.length]!, addressesBalance: balances.slice(0, -1) }; | ||
} | ||
|
||
//TODO: Implement getBatchesInfo. | ||
async getBatchesInfo( | ||
_chainId: number, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
export * from "./tvl.type"; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
import { TokenUnion } from "@zkchainhub/shared/tokens/tokens"; | ||
|
||
export type AssetTvl = Omit<TokenUnion, "coingeckoId"> & { | ||
amount: string; | ||
amountUsd: string; | ||
price: string; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
do we have a script to generate this? is it necessary to have it as variable? maybe just plain text is good enough, given that we are not getting any typing from this value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for now, i manually copied the bytecode generated on contracts compilation (same as with the abi). i think adding a script that runs after compilation that copies the bytecode into the .ts file is too much at this stage but can be a future enhancement
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
regarding plain text, i didn't follow you here, what are you suggesting? the bytecode as string is needed to be passed as argument of
batchRequest
on EvmProvider methodThere was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i want to automate this for the cases in which the smart contract changes, so we use the compiled output each time we want to run or deploy or even running the pipeline.
this is kind of hardcoded, i don't like it if we have the contracts that can be compiled on the repo, maybe using the
typechain
version of viem could be an option.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
i would have to write a script or copy and move the json with compilation info but i think the json has too much extra info that is not needed. however i think this script should be written on a separate PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sounds good, i will add it to
tech debt