diff --git a/package.json b/package.json index 3565c6e0f..679476e75 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@aztec/bridge-clients", - "version": "0.1.59", + "version": "0.1.60", "description": "This repo contains the solidity files and typescript helper class for all of the Aztec Connect Bridge Contracts", "repository": "git@github.com:AztecProtocol/aztec-connect-bridges.git", "license": "Apache-2.0", diff --git a/src/client/erc4626/erc4626-bridge-data.test.ts b/src/client/erc4626/erc4626-bridge-data.test.ts index 83ca58222..63eb9cc43 100644 --- a/src/client/erc4626/erc4626-bridge-data.test.ts +++ b/src/client/erc4626/erc4626-bridge-data.test.ts @@ -106,4 +106,19 @@ describe("ERC4626 bridge data", () => { )[0]; expect(expectedOutput).toBe(111111n); }); + + it("should correctly get asset", async () => { + // Setup mocks + erc4626Contract = { + ...erc4626Contract, + asset: jest.fn().mockResolvedValue(mplAsset.erc20Address.toString()), + }; + IERC4626__factory.connect = () => erc4626Contract as any; + + const erc4626BridgeData = ERC4626BridgeData.create({} as any); + + // Test the code using mocked controller + const asset = await erc4626BridgeData.getAsset(xmplAsset.erc20Address); + expect(asset.toString()).toBe(mplAsset.erc20Address.toString()); + }); }); diff --git a/src/client/erc4626/erc4626-bridge-data.ts b/src/client/erc4626/erc4626-bridge-data.ts index ff4445d92..7f029dd1d 100644 --- a/src/client/erc4626/erc4626-bridge-data.ts +++ b/src/client/erc4626/erc4626-bridge-data.ts @@ -7,8 +7,9 @@ import { AuxDataConfig, AztecAsset, BridgeDataFieldGetters, SolidityType } from export class ERC4626BridgeData implements BridgeDataFieldGetters { readonly WETH = EthAddress.fromString("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"); + shareToAssetMap = new Map(); - private constructor(private ethersProvider: Web3Provider) {} + protected constructor(protected ethersProvider: Web3Provider) {} static create(provider: EthereumProvider) { const ethersProvider = createWeb3Provider(provider); @@ -81,4 +82,19 @@ export class ERC4626BridgeData implements BridgeDataFieldGetters { throw "Invalid auxData"; } } + + /** + * @notice Gets asset for a given share + * @param share Address of the share/vault + * @return Address of the underlying asset + */ + async getAsset(share: EthAddress): Promise { + let asset = this.shareToAssetMap.get(share); + if (asset === undefined) { + const vault = IERC4626__factory.connect(share.toString(), this.ethersProvider); + asset = EthAddress.fromString(await vault.asset()); + this.shareToAssetMap.set(share, asset); + } + return asset; + } } diff --git a/src/client/euler/euler-bridge-data.test.ts b/src/client/euler/euler-bridge-data.test.ts new file mode 100644 index 000000000..87d975845 --- /dev/null +++ b/src/client/euler/euler-bridge-data.test.ts @@ -0,0 +1,63 @@ +import { EthAddress } from "@aztec/barretenberg/address"; +import { IERC4626, IERC4626__factory } from "../../../typechain-types"; +import { AztecAsset, AztecAssetType } from "../bridge-data"; +import { EulerBridgeData } from "./euler-bridge-data"; + +jest.mock("../aztec/provider", () => ({ + createWeb3Provider: jest.fn(), +})); + +type Mockify = { + [P in keyof T]: jest.Mock | any; +}; + +describe("Euler bridge data", () => { + let erc4626Contract: Mockify; + + let ethAsset: AztecAsset; + let weDaiAsset: AztecAsset; + let daiAsset: AztecAsset; + let emptyAsset: AztecAsset; + + beforeAll(() => { + ethAsset = { + id: 0, + assetType: AztecAssetType.ETH, + erc20Address: EthAddress.ZERO, + }; + weDaiAsset = { + id: 7, + assetType: AztecAssetType.ERC20, + erc20Address: EthAddress.fromString("0x4169Df1B7820702f566cc10938DA51F6F597d264"), + }; + daiAsset = { + id: 1, + assetType: AztecAssetType.ERC20, + erc20Address: EthAddress.fromString("0x6b175474e89094c44da98b954eedeac495271d0f"), + }; + emptyAsset = { + id: 0, + assetType: AztecAssetType.NOT_USED, + erc20Address: EthAddress.ZERO, + }; + }); + + it("should correctly fetch APR", async () => { + erc4626Contract = { + ...erc4626Contract, + asset: jest.fn().mockResolvedValue(daiAsset.erc20Address.toString()), + }; + IERC4626__factory.connect = () => erc4626Contract as any; + + const eulerBridgeData = EulerBridgeData.create({} as any); + const apr = await eulerBridgeData.getAPR(weDaiAsset); + expect(apr).toBeGreaterThan(0); + }); + + it("should correctly fetch market size", async () => { + const eulerBridgeData = EulerBridgeData.create({} as any); + const assetValue = (await eulerBridgeData.getMarketSize(daiAsset, emptyAsset, emptyAsset, emptyAsset, 0))[0]; + expect(assetValue.assetId).toBe(daiAsset.id); + expect(assetValue.value).toBeGreaterThan(0); + }); +}); diff --git a/src/client/euler/euler-bridge-data.ts b/src/client/euler/euler-bridge-data.ts new file mode 100644 index 000000000..31804bfa2 --- /dev/null +++ b/src/client/euler/euler-bridge-data.ts @@ -0,0 +1,85 @@ +import { AssetValue } from "@aztec/barretenberg/asset"; +import { EthereumProvider } from "@aztec/barretenberg/blockchain"; +import { Web3Provider } from "@ethersproject/providers"; +import "isomorphic-fetch"; +import { createWeb3Provider } from "../aztec/provider"; +import { AztecAsset } from "../bridge-data"; + +import { ERC4626BridgeData } from "../erc4626/erc4626-bridge-data"; + +export class EulerBridgeData extends ERC4626BridgeData { + protected constructor(ethersProvider: Web3Provider) { + super(ethersProvider); + } + + static create(provider: EthereumProvider) { + const ethersProvider = createWeb3Provider(provider); + return new EulerBridgeData(ethersProvider); + } + + async getAPR(yieldAsset: AztecAsset): Promise { + const underlyingAddress = await this.getAsset(yieldAsset.erc20Address); + const result = await ( + await fetch("https://api.thegraph.com/subgraphs/name/euler-xyz/euler-mainnet", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: ` + query($id: String!) { + asset(id: $id) { + supplyAPY + } + } + `, + variables: { + id: underlyingAddress.toString().toLowerCase(), + }, + }), + }) + ).json(); + + return result.data.asset.supplyAPY / 10 ** 25; + } + + /** + * @notice Gets market size which in this case means the amount of underlying asset deposited to Euler + * @param inputAssetA - The underlying asset + * @param inputAssetB - ignored + * @param outputAssetA - ignored + * @param outputAssetB - ignored + * @param auxData - ignored + * @return The amount of the underlying asset deposited to Euler + * @dev the returned value is displayed as totalSupply in Euler's UI + */ + async getMarketSize( + inputAssetA: AztecAsset, + inputAssetB: AztecAsset, + outputAssetA: AztecAsset, + outputAssetB: AztecAsset, + auxData: number, + ): Promise { + const result = await ( + await fetch("https://api.thegraph.com/subgraphs/name/euler-xyz/euler-mainnet", { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + query: ` + query($id: String!) { + asset(id: $id) { + totalBalances + } + } + `, + variables: { + id: inputAssetA.erc20Address.toString().toLowerCase(), + }, + }), + }) + ).json(); + return [{ assetId: inputAssetA.id, value: BigInt(result.data.asset.totalBalances) }]; + } +}