Skip to content

Commit

Permalink
Add multicall
Browse files Browse the repository at this point in the history
  • Loading branch information
prevostc committed Apr 19, 2024
1 parent 84ee153 commit c78cd3d
Show file tree
Hide file tree
Showing 8 changed files with 510 additions and 49 deletions.
440 changes: 440 additions & 0 deletions abis/multicall/Multicall3.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions config/arbitrum.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"network": "arbitrum-one",

"multicall3Address": "0xcA11bde05977b3631167028862bE2a173976CA11",
"shareTokenMintAddress": "0x0000000000000000000000000000000000000000",

"vaultFactoryAddress": "0xB45B92C318277d57328fE09DD5cF6Bd53F4F269B",
Expand Down
1 change: 1 addition & 0 deletions config/base.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"network": "base",

"multicall3Address": "0xcA11bde05977b3631167028862bE2a173976CA11",
"shareTokenMintAddress": "0x0000000000000000000000000000000000000000",

"vaultFactoryAddress": "0x30b94De3d18C9ab9fdf0dDF41077cc057F644E79",
Expand Down
1 change: 1 addition & 0 deletions config/optimism.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"network": "optimism",

"multicall3Address": "0xcA11bde05977b3631167028862bE2a173976CA11",
"shareTokenMintAddress": "0x0000000000000000000000000000000000000000",

"vaultFactoryAddress": "0x45df9B06fB22eA5f89541F613ba0733c2FbFc625",
Expand Down
3 changes: 2 additions & 1 deletion src/config.template.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,5 @@ export const WNATIVE_TOKEN_ADDRESS = Address.fromString("{{wrappedNativeAddress}
export const WNATIVE_DECIMALS = BigInt.fromU32({{wrappedNativeDecimals}})
export const CHAINLINK_NATIVE_PRICE_FEED_ADDRESS = Address.fromString("{{chainlinkNativePriceFeedAddress}}")
export const PRICE_FEED_DECIMALS = BigInt.fromU32({{chainlinkNativePriceFeedDecimals}})
export const SHARE_TOKEN_MINT_ADDRESS = Address.fromString("{{shareTokenMintAddress}}")
export const SHARE_TOKEN_MINT_ADDRESS = Address.fromString("{{shareTokenMintAddress}}")
export const MULTICALL3_ADDRESS = Address.fromString("{{multicall3Address}}")
41 changes: 41 additions & 0 deletions src/utils/multicall.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { Bytes, ethereum, log, crypto, ByteArray, Address } from "@graphprotocol/graph-ts"
import { Multicall3 as Multicall3Contract } from "../../generated/templates/BeefyCLStrategy/Multicall3"
import { MULTICALL3_ADDRESS } from "../config"

export class MulticallParams {
constructor(public contractAddress: Bytes, public functionSignature: string, public resultType: string) {}
}

export function multicallRead(callParams: Array<MulticallParams>): Array<ethereum.Value> {
const multicallContract = Multicall3Contract.bind(MULTICALL3_ADDRESS)

let params: Array<ethereum.Tuple> = []
for (let i = 0; i < callParams.length; i++) {
const callParam = callParams[i]
const sig = Bytes.fromUint8Array(crypto.keccak256(ByteArray.fromUTF8(callParam.functionSignature)).slice(0, 4))
params.push(
changetype<ethereum.Tuple>([
ethereum.Value.fromAddress(Address.fromBytes(callParam.contractAddress)),
ethereum.Value.fromBytes(sig),
]),
)
}

// need a low level call, can't call aggregate due to typing issues
const callResult = multicallContract.tryCall("aggregate", "aggregate((address,bytes)[]):(uint256,bytes[])", [
ethereum.Value.fromTupleArray(params),
])
if (callResult.reverted) {
log.error("Multicall failed", [])
throw Error("Multicall failed")
}

const multiResults = callResult.value[1].toBytesArray()
let results: Array<ethereum.Value> = []
for (let i = 0; i < callParams.length; i++) {
const callParam = callParams[i]
results.push(ethereum.decode(callParam.resultType, multiResults[i])!)
}

return results
}
62 changes: 14 additions & 48 deletions src/utils/price.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,68 +4,34 @@ import { BeefyStrategy as BeefyCLStrategyContract } from "../../generated/templa
import { ZERO_BD, tokenAmountToDecimal } from "./decimal"
import { ChainLinkPriceFeed } from "../../generated/templates/BeefyCLStrategy/ChainLinkPriceFeed"
import { CHAINLINK_NATIVE_PRICE_FEED_ADDRESS, PRICE_FEED_DECIMALS, WNATIVE_DECIMALS } from "../config"
import { MulticallParams, multicallRead } from "./multicall"
import { isNullToken } from "../entity/token"

const nativePriceFeed = ChainLinkPriceFeed.bind(CHAINLINK_NATIVE_PRICE_FEED_ADDRESS)

export function fetchVaultPrices(
vault: BeefyCLVault,
strategy: BeefyCLStrategy,
token0: Token,
token0: Token,
token1: Token,
earnedToken: Token,
): VaultPrices {
log.debug("fetchVaultPrices: fetching data for vault {}", [vault.id.toHexString()])
const strategyContract = BeefyCLStrategyContract.bind(Address.fromBytes(strategy.id))

const token0PriceInNativeRes = strategyContract.try_lpToken0ToNativePrice()
if (token0PriceInNativeRes.reverted) {
log.error("fetchVaultPrices: lpToken0ToNativePrice() of vault {} and strat {} reverted for token {} (token0)", [
vault.id.toHexString(),
strategy.id.toHexString(),
token0.id.toHexString(),
])
throw Error("fetchVaultPrices: lpToken0ToNativePrice() reverted")
}
const token0PriceInNative = tokenAmountToDecimal(token0PriceInNativeRes.value, WNATIVE_DECIMALS)
log.debug("fetchVaultPrices: token0PriceInNative: {}", [token0PriceInNative.toString()])

const token1PriceInNativeRes = strategyContract.try_lpToken1ToNativePrice()
if (token1PriceInNativeRes.reverted) {
log.error("fetchVaultPrices: lpToken1ToNativePrice() of vault {} and strat {} reverted for token {} (token1)", [
vault.id.toHexString(),
strategy.id.toHexString(),
token1.id.toHexString(),
])
throw Error("fetchVaultPrices: lpToken1ToNativePrice() reverted")
const signatures = [
new MulticallParams(strategy.id, 'lpToken0ToNativePrice()', "uint256"),
new MulticallParams(strategy.id, 'lpToken1ToNativePrice()', "uint256"),
new MulticallParams(CHAINLINK_NATIVE_PRICE_FEED_ADDRESS,'latestRoundData()', "uint256"),
]
if (!isNullToken(earnedToken)) {
signatures.push(new MulticallParams(strategy.id, 'ouptutToNativePrice()', "uint256"),)
}
const token1PriceInNative = tokenAmountToDecimal(token1PriceInNativeRes.value, WNATIVE_DECIMALS)
log.debug("fetchVaultPrices: token1PriceInNative: {}", [token1PriceInNative.toString()])

// some vaults have an additional token that is not part of the LP
const results = multicallRead(signatures)
const token0PriceInNative = tokenAmountToDecimal(results[0].toBigInt(), WNATIVE_DECIMALS)
const token1PriceInNative = tokenAmountToDecimal(results[1].toBigInt(), WNATIVE_DECIMALS)
const nativePriceUSD = tokenAmountToDecimal(results[2].toBigInt(), PRICE_FEED_DECIMALS)
let earnedTokenPriceInNative = ZERO_BD
if (!isNullToken(earnedToken)) {
const earnedTokenPriceInNativeRes = strategyContract.try_ouptutToNativePrice()
if (earnedTokenPriceInNativeRes.reverted) {
log.error("fetchVaultPrices: ouptutToNativePrice() of vault {} and strat {} reverted for token {} (earned)", [
vault.id.toHexString(),
strategy.id.toHexString(),
earnedToken.id.toHexString(),
])
throw Error("fetchVaultPrices: ouptutToNativePrice() reverted")
}
earnedTokenPriceInNative = tokenAmountToDecimal(earnedTokenPriceInNativeRes.value, WNATIVE_DECIMALS)
log.debug("fetchVaultPrices: earnedTokenPriceInNative: {}", [earnedTokenPriceInNative.toString()])
}

// fetch the native price in USD
const nativePriceUSDRes = nativePriceFeed.try_latestRoundData()
if (nativePriceUSDRes.reverted) {
log.error("fetchVaultPrices: latestRoundData() reverted for native token", [])
throw Error("fetchVaultPrices: latestRoundData() reverted")
earnedTokenPriceInNative = tokenAmountToDecimal(results[3].toBigInt(), WNATIVE_DECIMALS)
}
const nativePriceUSD = tokenAmountToDecimal(nativePriceUSDRes.value.getAnswer(), PRICE_FEED_DECIMALS)
log.debug("fetchVaultPrices: nativePriceUSD: {}", [nativePriceUSD.toString()])

return new VaultPrices(token0PriceInNative, token1PriceInNative, earnedTokenPriceInNative, nativePriceUSD)
}
Expand Down
10 changes: 10 additions & 0 deletions subgraph.template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,8 @@ dataSources:
file: ./abis/chainlink/AggregatorV3Interface.json
- name: IERC20
file: ./abis/IERC20/IERC20.json
- name: Multicall3
file: ./abis/multicall/Multicall3.json
#eventHandlers:
# - event: NewRound(indexed uint256,indexed address,uint256)
# handler: handleClockTick
Expand Down Expand Up @@ -126,6 +128,8 @@ dataSources:
file: ./abis/chainlink/AggregatorV3Interface.json
- name: IERC20
file: ./abis/IERC20/IERC20.json
- name: Multicall3
file: ./abis/multicall/Multicall3.json
eventHandlers:
- event: BoostDeployed(indexed address)
handler: handleBoostCreated
Expand Down Expand Up @@ -160,6 +164,8 @@ templates:
file: ./abis/chainlink/AggregatorV3Interface.json
- name: IERC20
file: ./abis/IERC20/IERC20.json
- name: Multicall3
file: ./abis/multicall/Multicall3.json
eventHandlers:
- event: Initialized(uint8)
handler: handleInitialized
Expand Down Expand Up @@ -202,6 +208,8 @@ templates:
file: ./abis/chainlink/AggregatorV3Interface.json
- name: IERC20
file: ./abis/IERC20/IERC20.json
- name: Multicall3
file: ./abis/multicall/Multicall3.json
eventHandlers:
- event: Initialized(uint8)
handler: handleInitialized
Expand Down Expand Up @@ -252,6 +260,8 @@ templates:
file: ./abis/chainlink/AggregatorV3Interface.json
- name: IERC20
file: ./abis/IERC20/IERC20.json
- name: Multicall3
file: ./abis/multicall/Multicall3.json
eventHandlers:
- event: OwnershipTransferred(indexed address,indexed address)
handler: handleOwnershipTransferred
Expand Down

0 comments on commit c78cd3d

Please sign in to comment.