Commit 00428c2d authored by James Kim's avatar James Kim

add adapter and tests for eco

parent d79b2496
...@@ -17,7 +17,7 @@ ...@@ -17,7 +17,7 @@
"lint:fix": "yarn lint:check --fix", "lint:fix": "yarn lint:check --fix",
"pre-commit": "lint-staged", "pre-commit": "lint-staged",
"test": "hardhat test", "test": "hardhat test",
"test:next": "vitest test-next/proveMessage.spec.ts", "test:next": "vitest run",
"test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json", "test:coverage": "nyc hardhat test && nyc merge .nyc_output coverage.json",
"autogen:docs": "typedoc --out docs src/index.ts" "autogen:docs": "typedoc --out docs src/index.ts"
}, },
...@@ -48,7 +48,9 @@ ...@@ -48,7 +48,9 @@
"typedoc": "^0.22.13", "typedoc": "^0.22.13",
"mocha": "^10.0.0", "mocha": "^10.0.0",
"vitest": "^0.28.3", "vitest": "^0.28.3",
"zod": "^3.11.6" "zod": "^3.11.6",
"viem": "^0.3.30",
"isomorphic-fetch": "^3.0.0"
}, },
"dependencies": { "dependencies": {
"@eth-optimism/contracts": "0.5.40", "@eth-optimism/contracts": "0.5.40",
......
import fetch from 'isomorphic-fetch'
// viem needs this
global.fetch = fetch
/* eslint-disable @typescript-eslint/no-unused-vars */
import { Contract } from 'ethers'
import { hexStringEquals } from '@eth-optimism/core-utils'
import { AddressLike } from '../interfaces'
import { toAddress } from '../utils'
import { StandardBridgeAdapter } from './standard-bridge'
/**
* Bridge adapter for ECO.
* ECO bridge requires a separate adapter as exposes different functions than our standard bridge
*/
export class ECOBridgeAdapter extends StandardBridgeAdapter {
public async supportsTokenPair(
l1Token: AddressLike,
l2Token: AddressLike
): Promise<boolean> {
const l1Bridge = new Contract(
this.l1Bridge.address,
[
{
inputs: [],
name: 'ecoAddress',
outputs: [
{
internalType: 'address',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
],
this.messenger.l1Provider
)
const l2Bridge = new Contract(
this.l2Bridge.address,
[
{
inputs: [],
name: 'l2EcoToken',
outputs: [
{
internalType: 'contract L2ECO',
name: '',
type: 'address',
},
],
stateMutability: 'view',
type: 'function',
},
],
this.messenger.l2Provider
)
const [remoteL1Token, remoteL2Token] = await Promise.all([
l1Bridge.ecoAddress(),
l2Bridge.l2EcoToken(),
])
if (!hexStringEquals(remoteL1Token, toAddress(l1Token))) {
return false
}
if (!hexStringEquals(remoteL2Token, toAddress(l2Token))) {
return false
}
return true
}
}
export * from './standard-bridge' export * from './standard-bridge'
export * from './eth-bridge' export * from './eth-bridge'
export * from './dai-bridge' export * from './dai-bridge'
export * from './eco-bridge'
...@@ -12,7 +12,11 @@ import { ...@@ -12,7 +12,11 @@ import {
OEL2ContractsLike, OEL2ContractsLike,
BridgeAdapterData, BridgeAdapterData,
} from '../interfaces' } from '../interfaces'
import { StandardBridgeAdapter, DAIBridgeAdapter } from '../adapters' import {
StandardBridgeAdapter,
DAIBridgeAdapter,
ECOBridgeAdapter,
} from '../adapters'
export const DEPOSIT_CONFIRMATION_BLOCKS: { export const DEPOSIT_CONFIRMATION_BLOCKS: {
[ChainID in L2ChainID]: number [ChainID in L2ChainID]: number
...@@ -206,7 +210,7 @@ export const BRIDGE_ADAPTER_DATA: { ...@@ -206,7 +210,7 @@ export const BRIDGE_ADAPTER_DATA: {
l2Bridge: '0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65' as const, l2Bridge: '0x467194771dAe2967Aef3ECbEDD3Bf9a310C76C65' as const,
}, },
ECO: { ECO: {
Adapter: StandardBridgeAdapter, Adapter: ECOBridgeAdapter,
l1Bridge: '0x7a01E277B8fDb8CDB2A2258508514716359f44A0' as const, l1Bridge: '0x7a01E277B8fDb8CDB2A2258508514716359f44A0' as const,
l2Bridge: '0x7a01E277B8fDb8CDB2A2258508514716359f44A0' as const, l2Bridge: '0x7a01E277B8fDb8CDB2A2258508514716359f44A0' as const,
}, },
......
import ethers from 'ethers'
import { describe, expect, it } from 'vitest'
import {
l1TestClient,
l2TestClient,
l1PublicClient,
l2PublicClient,
} from './testUtils/viemClients'
import { BRIDGE_ADAPTER_DATA, CrossChainMessenger, DEPOSIT_CONFIRMATION_BLOCKS, L2ChainID } from '../src'
import { Address, PublicClient, parseEther } from 'viem'
import { l1Provider, l2Provider } from './testUtils/ethersProviders'
const ECO_WHALE: Address = '0xBd11c836279a1352ce737FbBFba36b20734B04e7'
// TODO: use tokenlist as source of truth
const ECO_L1_TOKEN_ADDRESS: Address = '0x3E87d4d9E69163E7590f9b39a70853cf25e5ABE3'
const ECO_L2_TOKEN_ADDRESS: Address = '0x54bBECeA38ff36D32323f8A754683C1F5433A89f'
const getERC20TokenBalance = async (publicClient: PublicClient, tokenAddress: Address, ownerAddress: Address) => {
const result = await publicClient.readContract({
address: tokenAddress,
abi: [
{
inputs: [{ name: 'owner', type: 'address' }],
name: 'balanceOf',
outputs: [{ name: '', type: 'uint256' }],
stateMutability: 'view',
type: 'function',
}
],
functionName: 'balanceOf',
args: [ownerAddress]
})
return result as bigint
}
const getL1ERC20TokenBalance = async (ownerAddress: Address) => {
return getERC20TokenBalance(l1PublicClient, ECO_L1_TOKEN_ADDRESS, ownerAddress)
}
const getL2ERC20TokenBalance = async (ownerAddress: Address) => {
return getERC20TokenBalance(l2PublicClient, ECO_L2_TOKEN_ADDRESS, ownerAddress)
}
describe('ECO token', () => {
it('sdk should be able to deposit to l1 bridge contract correctly', async () => {
// this code is a bit whack because of the mix of viem + ethers
// TODO: use anviljs, and use viem entirely once the sdk supports it
await l1TestClient.impersonateAccount({ address: ECO_WHALE })
const l1EcoWhaleSigner = await l1Provider.getSigner(ECO_WHALE);
const preBridgeL1EcoWhaleBalance = await getL1ERC20TokenBalance(ECO_WHALE)
const crossChainMessenger = new CrossChainMessenger({
l1SignerOrProvider: l1EcoWhaleSigner,
l2SignerOrProvider: l2Provider,
l1ChainId: 5,
l2ChainId: 420,
// bedrock: true,
bridges: BRIDGE_ADAPTER_DATA[L2ChainID.OPTIMISM_GOERLI]
})
await crossChainMessenger.approveERC20(
ECO_L1_TOKEN_ADDRESS,
ECO_L2_TOKEN_ADDRESS,
ethers.utils.parseEther('0.1'),
)
const txResponse = await crossChainMessenger.depositERC20(
ECO_L1_TOKEN_ADDRESS,
ECO_L2_TOKEN_ADDRESS,
ethers.utils.parseEther('0.1'),
)
await txResponse.wait();
const l1EcoWhaleBalance = await getL1ERC20TokenBalance(ECO_WHALE)
expect(l1EcoWhaleBalance).toEqual(preBridgeL1EcoWhaleBalance - parseEther('0.1'))
}, 20_000)
it('sdk should be able to withdraw into the l2 bridge contract correctly', async () => {
await l2TestClient.impersonateAccount({ address: ECO_WHALE })
const l2EcoWhaleSigner = await l2Provider.getSigner(ECO_WHALE);
const preBridgeL2EcoWhaleBalance = await getL2ERC20TokenBalance(ECO_WHALE)
const crossChainMessenger = new CrossChainMessenger({
l1SignerOrProvider: l1Provider,
l2SignerOrProvider: l2EcoWhaleSigner,
l1ChainId: 5,
l2ChainId: 420,
// bedrock: true,
bridges: BRIDGE_ADAPTER_DATA[L2ChainID.OPTIMISM_GOERLI]
})
const txResponse = await crossChainMessenger.withdrawERC20(
ECO_L1_TOKEN_ADDRESS,
ECO_L2_TOKEN_ADDRESS,
ethers.utils.parseEther('0.1'),
)
await txResponse.wait();
const l2EcoWhaleBalance = await getL2ERC20TokenBalance(ECO_WHALE)
expect(l2EcoWhaleBalance).toEqual(preBridgeL2EcoWhaleBalance - parseEther('0.1'))
}, 20_000)
})
...@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest' ...@@ -3,6 +3,7 @@ import { describe, expect, it } from 'vitest'
import { z } from 'zod' import { z } from 'zod'
import { CrossChainMessenger } from '../src' import { CrossChainMessenger } from '../src'
import { l1Provider, l2Provider } from './testUtils/ethersProviders'
/** /**
* This test repros the bug where legacy withdrawals are not provable * This test repros the bug where legacy withdrawals are not provable
...@@ -48,33 +49,12 @@ transactionHash 0xd66fda632b51a8b25a9d260d70da8be57b9930c461637086152633 ...@@ -48,33 +49,12 @@ transactionHash 0xd66fda632b51a8b25a9d260d70da8be57b9930c461637086152633
transactionIndex 0 transactionIndex 0
type type
*/ */
const E2E_RPC_URL_L1 = z
.string()
.url()
.describe('L1 ethereum rpc Url')
.parse(import.meta.env.VITE_E2E_RPC_URL_L1)
const E2E_RPC_URL_L2 = z
.string()
.url()
.describe('L1 ethereum rpc Url')
.parse(import.meta.env.VITE_E2E_RPC_URL_L2)
const E2E_PRIVATE_KEY = z const E2E_PRIVATE_KEY = z
.string() .string()
.describe('Private key') .describe('Private key')
.parse(import.meta.env.VITE_E2E_PRIVATE_KEY) .parse(import.meta.env.VITE_E2E_PRIVATE_KEY)
const jsonRpcHeaders = { 'User-Agent': 'eth-optimism/@gateway/backend' }
/**
* Initialize the signer, prover, and cross chain messenger
*/
const l1Provider = new ethers.providers.JsonRpcProvider({
url: E2E_RPC_URL_L1,
headers: jsonRpcHeaders,
})
const l2Provider = new ethers.providers.JsonRpcProvider({
url: E2E_RPC_URL_L2,
headers: jsonRpcHeaders,
})
const l1Wallet = new ethers.Wallet(E2E_PRIVATE_KEY, l1Provider) const l1Wallet = new ethers.Wallet(E2E_PRIVATE_KEY, l1Provider)
const crossChainMessenger = new CrossChainMessenger({ const crossChainMessenger = new CrossChainMessenger({
l1SignerOrProvider: l1Wallet, l1SignerOrProvider: l1Wallet,
......
import ethers from 'ethers'
import { z } from 'zod'
const E2E_RPC_URL_L1 = z
.string()
.url()
.describe('L1 ethereum rpc Url')
.parse(import.meta.env.VITE_E2E_RPC_URL_L1)
const E2E_RPC_URL_L2 = z
.string()
.url()
.describe('L1 ethereum rpc Url')
.parse(import.meta.env.VITE_E2E_RPC_URL_L2)
const jsonRpcHeaders = { 'User-Agent': 'eth-optimism/@gateway/backend' }
/**
* Initialize the signer, prover, and cross chain messenger
*/
const l1Provider = new ethers.providers.JsonRpcProvider({
url: E2E_RPC_URL_L1,
headers: jsonRpcHeaders,
})
const l2Provider = new ethers.providers.JsonRpcProvider({
url: E2E_RPC_URL_L2,
headers: jsonRpcHeaders,
})
export {
l1Provider,
l2Provider
}
import {
createTestClient,
createPublicClient,
createWalletClient,
http,
} from 'viem'
import { goerli, optimismGoerli } from 'viem/chains'
// TODO: use env vars to determine chain so we can support alternate l1/l2 pairs
const L1_CHAIN = goerli;
const L2_CHAIN = optimismGoerli;
const L1_RPC_URL = 'http://localhost:8545';
const L2_RPC_URL = 'http://localhost:9545'
const l1TestClient = createTestClient({
mode: 'anvil',
chain: L1_CHAIN,
transport: http(L1_RPC_URL),
})
const l2TestClient = createTestClient({
mode: 'anvil',
chain: L2_CHAIN,
transport: http(L2_RPC_URL),
})
const l1PublicClient = createPublicClient({
chain: L1_CHAIN,
transport: http(L1_RPC_URL),
})
const l2PublicClient = createPublicClient({
chain: L2_CHAIN,
transport: http(L2_RPC_URL),
})
const l1WalletClient = createWalletClient({
chain: L1_CHAIN,
transport: http(L1_RPC_URL),
})
const l2WalletClient = createWalletClient({
chain: L2_CHAIN,
transport: http(L2_RPC_URL),
})
export {
l1TestClient,
l2TestClient,
l1PublicClient,
l2PublicClient,
l1WalletClient,
l2WalletClient,
}
import { defineConfig } from 'vitest/config'
// https://vitest.dev/config/ - for docs
export default defineConfig({
test: {
setupFiles: './setupVitest.ts',
include: ['test-next/*.{test,spec}.{js,mjs,cjs,ts,mts,cts,jsx,tsx}'],
},
})
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment