Skip to content
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: adds viem support #476

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"test": "mocha",
"test:coverage": "nyc mocha",
"test:fork": "SHOULD_FORK=1 hardhat test tests/fork/*.test.ts",
"test:integration": "mocha tests/integration/ --timeout 30000000 --bail",
"test:integration": "mocha tests/integration/viem.test.ts --timeout 30000000 --bail",
"test:unit": "mocha --parallel tests/unit/ --timeout 30000 --bail",
"test:ci": "nyc --reporter=lcovonly mocha --reporter xunit",
"lint": "eslint .",
Expand Down Expand Up @@ -96,4 +96,4 @@
"files": [
"dist/**/*"
]
}
}
41 changes: 33 additions & 8 deletions scripts/testSetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ import { deployErc20AndInit } from './deployBridge'
import * as path from 'path'
import * as fs from 'fs'
import { ArbSdkError } from '../src/lib/dataEntities/errors'
import { createWalletClient, http } from 'viem'
import { mainnet } from 'viem/chains'
import { WalletClient, createWalletClient, http } from 'viem'
import { mainnet, arbitrum } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
import { createViemSigner } from '../src/lib/utils/universal/signerTransforms'

Expand Down Expand Up @@ -237,6 +237,8 @@ export const testSetup = async (): Promise<{
inboxTools: InboxTools
l1Deployer: Signer
l2Deployer: Signer
l1WalletClient: WalletClient
l2WalletClient: WalletClient
}> => {
const ethProvider = new JsonRpcProvider(config.ethUrl)
const arbProvider = new JsonRpcProvider(config.arbUrl)
Expand All @@ -246,11 +248,12 @@ export const testSetup = async (): Promise<{

const seed = Wallet.createRandom()
const ethersL1Signer = seed.connect(ethProvider)
const l2Signer = seed.connect(arbProvider)
const ethersL2Signer = seed.connect(arbProvider)

const pk = ethersL1Signer._signingKey().privateKey as `0x${string}`
const ethWalletClient = createWalletClient({
account: privateKeyToAccount(pk),
const l1pk = ethersL1Signer._signingKey().privateKey as `0x${string}`
const l2pk = ethersL2Signer._signingKey().privateKey as `0x${string}`
const l1WalletClient = createWalletClient({
account: privateKeyToAccount(l1pk),
transport: http(config.ethUrl),
chain: {
...mainnet,
Expand All @@ -266,9 +269,29 @@ export const testSetup = async (): Promise<{
},
})
const l1Signer = config.shouldUseViemSigner
? createViemSigner(ethWalletClient)
? createViemSigner(l1WalletClient)
: ethersL1Signer

const l2WalletClient = createWalletClient({
account: privateKeyToAccount(l2pk),
transport: http(config.arbUrl),
chain: {
...arbitrum,
id: 412346,
rpcUrls: {
default: {
http: [config.arbUrl],
},
public: {
http: [config.arbUrl],
},
},
},
})
const l2Signer = config.shouldUseViemSigner
? createViemSigner(l2WalletClient)
: ethersL2Signer

let setL1Network: L1Network, setL2Network: L2Network
try {
const l1Network = await getL1Network(l1Deployer)
Expand Down Expand Up @@ -313,9 +336,11 @@ export const testSetup = async (): Promise<{

return {
seed,
pk,
pk: l1pk,
l1Signer,
ethersL1Signer,
l1WalletClient: l1WalletClient,
l2WalletClient: l2WalletClient,
l2Signer,
l1Network: setL1Network,
l2Network: setL2Network,
Expand Down
2 changes: 2 additions & 0 deletions src/lib/assetBridger/erc20Bridger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ import { OmitTyped, RequiredPick } from '../utils/types'
import { RetryableDataTools } from '../dataEntities/retryableData'
import { EventArgs } from '../dataEntities/event'
import { L1ToL2MessageGasParams } from '../message/L1ToL2MessageCreator'
import { applyUseViemSignerToAllMethods } from '../utils/universal/providerTransforms'

export interface TokenApproveParams {
/**
Expand Down Expand Up @@ -168,6 +169,7 @@ type DefaultedDepositRequest = RequiredPick<
/**
* Bridger for moving ERC20 tokens back and forth between L1 to L2
*/
@applyUseViemSignerToAllMethods
export class Erc20Bridger extends AssetBridger<
Erc20DepositParams | L1ToL2TxReqAndSignerProvider,
OmitTyped<Erc20WithdrawParams, 'from'> | L2ToL1TransactionRequest
Expand Down
5 changes: 4 additions & 1 deletion src/lib/assetBridger/ethBridger.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ import { MissingProviderArbSdkError } from '../dataEntities/errors'
import { getL2Network } from '../dataEntities/networks'
import {
Providerish,
applyUseViemSignerToAllMethods,
transformUniversalProviderToEthersV5Provider,
} from '../utils/universal/providerTransforms'
import { ViemSigner } from '../utils/universal/signerTransforms'

export interface EthWithdrawParams {
/**
Expand All @@ -74,7 +76,7 @@ export type EthDepositParams = {
/**
* The L1 provider or signer
*/
l1Signer: Signer
l1Signer: Signer | ViemSigner | any
/**
* The amount of ETH or tokens to be deposited
*/
Expand Down Expand Up @@ -132,6 +134,7 @@ type EthDepositToRequestParams = OmitTyped<
/**
* Bridger for moving ETH back and forth between L1 to L2
*/
@applyUseViemSignerToAllMethods
export class EthBridger extends AssetBridger<
EthDepositParams | EthDepositToParams | L1ToL2TxReqAndSigner,
EthWithdrawParams | L2ToL1TxReqAndSigner
Expand Down
62 changes: 61 additions & 1 deletion src/lib/utils/universal/providerTransforms.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ import EthersV5, {
StaticJsonRpcProvider,
WebSocketProvider,
} from '@ethersproject/providers'
import { BigNumber } from 'ethers'
import EthersV6 from 'ethers-v6'
import { PublicClient, createWalletClient, http } from 'viem'
import Web3, { Web3BaseProvider } from 'web3'
import { isWalletClient, createViemSigner } from './signerTransforms'

export type Providerish =
| PublicClient
Expand Down Expand Up @@ -115,7 +117,8 @@ export function isPublicClient(object: any): object is PublicClient {
object.transport !== null &&
typeof object.transport === 'object' &&
'url' in object.transport &&
typeof object.transport.url === 'string'
typeof object.transport.url === 'string' &&
object.type === 'publicClient'
)
}

Expand Down Expand Up @@ -166,3 +169,60 @@ export const transformEthersProviderToWalletClient = async (
}
throw new Error('Invalid provider')
}

export function useViemSigner(
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
const originalMethod = descriptor.value

descriptor.value = function (...args: any[]) {
args = args.map(arg => {
if (arg && typeof arg === 'object') {
Object.keys(arg).forEach(key => {
// Check if the property is a PublicClient instance
if (isPublicClient(arg[key])) {
arg[key] = publicClientToProvider(arg[key])
}

// Check if the property is a WalletClient instance
if (isWalletClient(arg[key])) {
arg[key] = createViemSigner(arg[key])
}
// Check and convert bigint to BigNumber
if (typeof arg[key] === 'bigint') {
arg[key] = BigNumber.from(arg[key].toString())
}
})
}
return arg
})

// Call the original method with the transformed arguments
return originalMethod.apply(this, args)
}

return descriptor
}

export function applyUseViemSignerToAllMethods(constructor: any) {
Object.getOwnPropertyNames(constructor.prototype).forEach(method => {
if (
typeof constructor.prototype[method] === 'function' &&
method !== 'constructor'
) {
const descriptor = Object.getOwnPropertyDescriptor(
constructor.prototype,
method
)
if (descriptor) {
Object.defineProperty(
constructor.prototype,
method,
useViemSigner(constructor.prototype, method, descriptor)
)
}
}
})
}
86 changes: 86 additions & 0 deletions tests/integration/viem.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { expect } from 'chai'
import dotenv from 'dotenv'

import { parseEther } from '@ethersproject/units'
import { Wallet } from '@ethersproject/wallet'

import { testSetup } from '../../scripts/testSetup'
import {
GatewayType,
depositToken,
fundL1,
fundL2,
skipIfMainnet,
} from './testHelpers'
import { L1ToL2MessageStatus } from '../../src'
import { TestERC20__factory } from '../../src/lib/abi/factories/TestERC20__factory'
import { BigNumber } from 'ethers'

dotenv.config()

// TODO: create better tests before merging

describe('useViemSigner', async () => {
let testToken: any

before('init', async () => {
const setup = await testSetup()
await fundL1(setup.l1Signer)
await fundL2(setup.l2Signer)

const deployErc20 = new TestERC20__factory().connect(setup.l1Signer)
testToken = await deployErc20.deploy()
await testToken.deployed()

await (await testToken.mint()).wait()
})

beforeEach('skipIfMainnet', async function () {
await skipIfMainnet(this)
})

it('deposits ether', async () => {
const { ethBridger, l1Signer, l1WalletClient } = await testSetup()

await fundL1(l1Signer)
const ethToDeposit = parseEther('0.0002')
const res = await ethBridger.deposit({
amount: ethToDeposit,
l1Signer: l1WalletClient, // uses WalletClient from viem to create a ViemSigner in the decorator
})
const rec = await res.wait()

expect(rec.status).to.equal(1, 'eth deposit L1 txn failed')
})

it('deposits ether to a specific L2 address', async () => {
const { ethBridger, l1Signer, l2Signer, l1WalletClient } = await testSetup()

await fundL1(l1Signer)
const destWallet = Wallet.createRandom()

const ethToDeposit = parseEther('0.0002')
const res = await ethBridger.depositTo({
amount: ethToDeposit,
l1Signer: l1WalletClient, // uses WalletClient from viem to create a ViemSigner in the decorator
destinationAddress: destWallet.address,
l2Provider: l2Signer.provider!,
})
const rec = await res.wait()

expect(rec.status).to.equal(1, 'eth deposit L1 txn failed')
})
it.skip('register custom token', async () => {
const depositAmount = BigNumber.from(100)
const { erc20Bridger, l1WalletClient, l2WalletClient } = await testSetup()
await depositToken(
depositAmount,
testToken.address,
erc20Bridger,
l1WalletClient as any,
l2WalletClient as any,
L1ToL2MessageStatus.REDEEMED,
GatewayType.STANDARD
)
})
})
Loading