Skip to content

Commit

Permalink
Merge pull request #147 from base-org/add-simulate-deposit-transaction
Browse files Browse the repository at this point in the history
Add simulateDepositTransaction
  • Loading branch information
zencephalon authored Oct 24, 2023
2 parents 884a40f + d163b7c commit 50ffdb2
Show file tree
Hide file tree
Showing 18 changed files with 334 additions and 38 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-years-kick.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"op-viem": minor
---

Add simulateDepositTransaction action
4 changes: 4 additions & 0 deletions site/.vitepress/config.mts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ export default defineConfig({
text: 'simulateDepositETH',
link: '/docs/actions/public/L1/simulateDepositETH',
},
{
text: 'simulateDepositTransaction',
link: '/docs/actions/public/L1/simulateDepositTransaction',
},
{
text: 'simulateFinalizeWithdrawalTransaction',
link: '/docs/actions/public/L1/simulateFinalizeWithdrawalTransaction',
Expand Down
135 changes: 135 additions & 0 deletions site/docs/actions/public/L1/simulateDepositTransaction.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
# simulateDepositTransaction

Simulates a [depositTransaction](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal.sol#L374) call to the [`OptimismPortal`](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal.sol) contract.

::: code-group

```ts [example.ts]
import { DepositTransactionParameters } from 'op-viem'
import { baseAddresses } from 'op-viem/chains'
import { account, l2PublicClient, opStackL1PublicClient } from './config'

const args: DepositTransactionParameters = {
to: account,
value: 1n,
data: '0x',
gasLimit: 0n,
isCreation: false,
}

const gas = await l2PublicClient.estimateGas({
account,
to: args.to,
value: args.value,
data: args.data,
})

args.gasLimit = gas

const { request, result } = await opStackL1PublicClient
.simulateDepositTransaction({
args,
...baseAddresses,
value: 1n,
})
```

```ts [config.ts]
import { createPublicClient, custom } from 'viem'
import { privateKeyToAccount } from 'viem/accounts'
import { base } from 'op-viem/chains'
import { mainnet } from 'viem/chains'
import { publicL1OpStackActions } from 'op-viem'

export const l2PublicClient = createPublicClient({
chain: base,
transport: http()
})

export const opStackL1PublicClient = createPublicClient({
chain: mainnet,
transport: custom(window.ethereum)
}).extend(publicL1OpStackActions)

// JSON-RPC Account
export const [account] = await walletClient.getAddresses()
// Local Account
export const account = privateKeyToAccount(...)
```

:::

## Return Value

The simulation result and write request. Type is inferred.

## Parameters

### args

- #### to
- **Type:** [`Address`](https://viem.sh/docs/glossary/types#address)
- The address the L2 transaction will be sent.

- #### gasLimit
- **Type:** `bigint`
- The gas limit of the L2 transaction

- #### value (optional)
- **Type:** `bigint`
- **Default:** `0`
- Value in wei of the L2 transaction.

- #### isCreation (optional)
- **Type:** `boolean`
- **Default:** `false`
- Whether the L2 tx is creating a new contract

- #### data (optional)
- **Type:** `Hex`
- **Default:** `0x`
- The calldata of the L2 transaction

```ts
await publicClient.simulateDepositTransaction({
args: { // [!code focus:7]
to: account.address,
value: 1n,
data: '0x',
gasLimit: 21000n,
isCreation: false,
},
...baseAddresses,
})
```

### portal

- **Type:** [`RawOrContractAddress`](https://viem.sh/docs/glossary/types#raworcontractaddress)

The `OptimismPortal` contract where the depositTransaction call should be made.

```ts
await publicClient.writeDepositTransaction({
args,
portalAddress: portal, // [!code focus:1]
})
```

### value (optional)

- **Type:** `number`

Value in wei sent with this transaction. This value will be credited to the balance of the caller address on L2 _before_ the L2 transaction created by this transaction is made.

```ts
await publicClient.simulateDepositTransaction({
args,
portalAddress: portal,
value: parseEther(1), // [!code focus:1]
})
```

::: tip
`account`, `accessList`, `chain`, `dataSuffix`, `gasPrice`, `maxFeePerGas`, `maxPriorityFeePerGas`, and `nonce` can all also be passed and behave as with any viem writeContract call. See [their documentation](https://viem.sh/docs/contract/writeContract.html#writecontract) for more details.
:::
2 changes: 1 addition & 1 deletion site/docs/actions/wallet/L1/writeDepositTransaction.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ From Viem [writeContract]((https://viem.sh/docs/contract/writeContract.html#writ

> The `writeContract` internally sends a transaction – it **does not** validate if the contract write will succeed (the contract may throw an error). It is highly recommended to [simulate the contract write with `simulateContract`](#usage) before you execute it.
In this case, you can use [simulateSendMessage](/docs/actions/wallet/L1/simulateUnsafeDepositTransaction).
In this case, you can use [simulateDepositTransaction](/docs/actions/public/L1/simulateDepositTransaction).

:::

Expand Down
6 changes: 6 additions & 0 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ export {
type SimulateDepositETHParameters,
type SimulateDepositETHReturnType,
} from './public/L1/simulateDepositETH.js'
export {
simulateDepositTransaction,
type SimulateDepositTransactionParameters,
type SimulateDepositTransactionReturnType,
} from './public/L1/simulateDepositTransaction.js'
export {
simulateProveWithdrawalTransaction,
type SimulateProveWithdrawalTransactionParameters,
Expand Down Expand Up @@ -59,6 +64,7 @@ export {
type SimulateWithdrawETHParameters,
type SimulateWithdrawETHReturnType,
} from './public/L2/simulateWithdrawETH.js'

export { writeContractDeposit, type WriteContractDepositParameters } from './wallet/L1/writeContractDeposit.js'
export { writeDepositERC20, type WriteDepositERC20Parameters } from './wallet/L1/writeDepositERC20.js'
export { writeDepositETH, type WriteDepositETHParameters } from './wallet/L1/writeDepositETH.js'
Expand Down
3 changes: 0 additions & 3 deletions src/actions/public/L1/readFinalizedWithdrawals.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ import type { Chain, PublicClient, ReadContractParameters, Transport } from 'vie
import { readContract } from 'viem/actions'
import type { MessagePassedEvent } from '../../../index.js'
import { type RawOrContractAddress, resolveAddress } from '../../../types/addresses.js'
import { OpStackL1Contract } from '../../../types/opStackContracts.js'

const ABI = optimismPortalABI
const CONTRACT = OpStackL1Contract.OptimismPortal
const FUNCTION_NAME = 'finalizedWithdrawals'

export type ReadFinalizedWithdrawalsParameters<
Expand All @@ -22,7 +20,6 @@ export async function readFinalizedWithdrawals<TChain extends Chain | undefined>
}: ReadFinalizedWithdrawalsParameters<TChain>,
): Promise<boolean> {
const finalizedWithdrawal = await readContract(client, {
contract: CONTRACT,
abi: ABI,
functionName: FUNCTION_NAME,
address: resolveAddress(portal),
Expand Down
106 changes: 106 additions & 0 deletions src/actions/public/L1/simulateDepositTransaction.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { type Address } from 'viem'
import { estimateGas, mine } from 'viem/actions'
import { expect, test } from 'vitest'
import { accounts } from '../../../_test/constants.js'
import { publicClient, rollupPublicClient, testClient } from '../../../_test/utils.js'
import { baseAddresses } from '../../../chains/index.js'
import { type DepositTransactionParameters } from '../../index.js'
import { simulateDepositTransaction } from './simulateDepositTransaction.js'

test('default', async () => {
expect(
await simulateDepositTransaction(publicClient, {
args: {
to: '0x0c54fccd2e384b4bb6f2e405bf5cbc15a017aafb',
value: 1n,
gasLimit: 25000n,
data: '0x',
isCreation: false,
},
value: 0n,
...baseAddresses,
account: accounts[0].address,
}),
).toBeDefined()
})

test('sends transaction to correct infered address', async () => {
const args: DepositTransactionParameters = {
to: '0x0c54fccd2e384b4bb6f2e405bf5cbc15a017aafb',
value: 1n,
gasLimit: 0n,
data: '0x',
isCreation: false,
}

const gas = await estimateGas(rollupPublicClient, {
account: accounts[0].address,
to: args.to,
value: args.value,
data: args.data,
})

args.gasLimit = gas

const { request } = await simulateDepositTransaction(publicClient, {
args,
value: 1n,
...baseAddresses,
account: accounts[0].address,
})

expect(request.address).toEqual(
baseAddresses.portal.address,
)
})

test('sends transaction to correct explicit address', async () => {
const portal: Address = '0xbEb5Fc579115071764c7423A4f12eDde41f106Ed'
const { request } = await simulateDepositTransaction(publicClient, {
args: {
to: '0x0c54fccd2e384b4bb6f2e405bf5cbc15a017aafb',
value: 1n,
gasLimit: 25000n,
},
value: 1n,
portal: portal,
account: accounts[0].address,
})

expect(request.address).toEqual(portal)
})

test('correctly passes arugments', async () => {
const args: DepositTransactionParameters = {
to: '0x0c54fccd2e384b4bb6f2e405bf5cbc15a017aafb',
value: 1n,
gasLimit: 25000n,
data: '0x',
isCreation: false,
}

const { request } = await simulateDepositTransaction(publicClient, {
args,
...baseAddresses,
account: accounts[0].address,
value: 2n,
})

await mine(testClient, { blocks: 1 })

expect(request.value).toEqual(2n)
})

test('errors if portal not passed', async () => {
expect(() =>
// @ts-expect-error
simulateDepositTransaction(publicClient, {
args: {
to: '0x0c54fccd2e384b4bb6f2e405bf5cbc15a017aafb',
gasLimit: 25000n,
},
value: 0n,
account: accounts[0].address,
})
).rejects.toThrowError('Invalid address')
})
62 changes: 62 additions & 0 deletions src/actions/public/L1/simulateDepositTransaction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { Chain, PublicClient, SimulateContractParameters, SimulateContractReturnType, Transport } from 'viem'
import { simulateContract } from 'viem/actions'
import { type RawOrContractAddress, resolveAddress } from '../../../types/addresses.js'
import { type L1SimulateActionBaseType } from '../../../types/l1Actions.js'
import { ABI, type DepositTransactionParameters, FUNCTION } from '../../wallet/L1/writeDepositTransaction.js'

export type SimulateDepositTransactionParameters<
TChain extends Chain | undefined = Chain,
TChainOverride extends Chain | undefined = Chain | undefined,
_chainId = TChain extends Chain ? TChain['id'] : number,
> =
& { args: DepositTransactionParameters; portal: RawOrContractAddress<_chainId> }
& L1SimulateActionBaseType<
TChain,
TChainOverride,
typeof ABI,
typeof FUNCTION
>

export type SimulateDepositTransactionReturnType<
TChain extends Chain | undefined,
TChainOverride extends Chain | undefined = undefined,
> = SimulateContractReturnType<
typeof ABI,
typeof FUNCTION,
TChain,
TChainOverride
>

/**
* Simulates a call to DepositTranasction on the OptimismPortal contract.
*
* @param parameters - {@link SimulateDepositTransactionParameters}
* @returns A [Transaction Hash](https://viem.sh/docs/glossary/terms.html#hash). {@link WriteContractReturnType}
*/
export async function simulateDepositTransaction<
TChain extends Chain | undefined,
TChainOverride extends Chain | undefined = undefined,
>(
client: PublicClient<Transport, TChain>,
{
args: { to, value = 0n, gasLimit, isCreation = false, data = '0x' },
portal,
...rest
}: SimulateDepositTransactionParameters<
TChain,
TChainOverride
>,
): Promise<SimulateDepositTransactionReturnType<TChain, TChainOverride>> {
return simulateContract(client, {
address: resolveAddress(portal),
abi: ABI,
functionName: FUNCTION,
args: [to, value, gasLimit, isCreation, data],
...rest,
} as unknown as SimulateContractParameters<
typeof ABI,
typeof FUNCTION,
TChain,
TChainOverride
>)
}
3 changes: 1 addition & 2 deletions src/actions/wallet/L1/writeDepositERC20.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Account, Chain, Transport, WalletClient, WriteContractParameters, WriteContractReturnType } from 'viem'
import { writeContract } from 'viem/actions'
import { type RawOrContractAddress, resolveAddress } from '../../../types/addresses.js'
import { ABI, CONTRACT, type DepositERC20Parameters, FUNCTION } from '../../../types/depositERC20.js'
import { ABI, type DepositERC20Parameters, FUNCTION } from '../../../types/depositERC20.js'
import type { L1WriteActionBaseType } from '../../../types/l1Actions.js'

export type WriteDepositERC20Parameters<
Expand Down Expand Up @@ -41,7 +41,6 @@ export async function writeDepositERC20<
return writeContract(client, {
address: resolveAddress(l1StandardBridge),
abi: ABI,
contract: CONTRACT,
functionName: FUNCTION,
args: [l1Token, l2Token, to, amount, minGasLimit, extraData],
...rest,
Expand Down
Loading

0 comments on commit 50ffdb2

Please sign in to comment.