Skip to content

Commit

Permalink
wip: checkpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Jun 14, 2024
1 parent 2406e89 commit 7550884
Show file tree
Hide file tree
Showing 7 changed files with 122 additions and 48 deletions.
17 changes: 16 additions & 1 deletion site/pages/docs/actions/public/call.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Viem offers two ways of performing a Deployless Call, via:
- a [Deploy Factory](#deploy-factory): "temporarily deploys" a contract with a provided [Deploy Factory](https://docs.alchemy.com/docs/create2-an-alternative-to-deriving-contract-addresses#create2-contract-factory), and calls the function on the deployed contract.

:::tip
The **Deployless Call** patterns are also accessible via the [`readContract`](/docs/contract/readContract#deployless-reads) & [Contract Instance](http://localhost:5173/docs/contract/getContract) APIs.
The **Deployless Call** patterns are also accessible via the [`readContract`](/docs/contract/readContract#deployless-reads) & [Contract Instance](/docs/contract/getContract) APIs.
:::

#### Bytecode
Expand Down Expand Up @@ -247,6 +247,21 @@ const data = await publicClient.call({
})
```

### bytecode (optional)

- **Type:**

Bytecode to perform the call against.

```ts twoslash
// [!include ~/snippets/publicClient.ts]
// ---cut---
const data = await publicClient.call({
bytecode: '0x...', // [!code focus]
data: '0xdeadbeef',
})
```

### factory (optional)

- **Type:**
Expand Down
44 changes: 40 additions & 4 deletions site/pages/docs/contract/readContract.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,11 +107,47 @@ export const publicClient = createPublicClient({
It is possible to call a function on a contract that has not been deployed yet. For instance, we may want
to call a function on an [ERC-4337 Smart Account](https://eips.ethereum.org/EIPS/eip-4337) contract which has not been deployed.

Viem utilizes a **Deployless Read** pattern via a [Deploy Factory](https://docs.alchemy.com/docs/create2-an-alternative-to-deriving-contract-addresses#create2-contract-factory) to:
1. "temporarily deploy" a contract (e.g. a Smart Account) with a provided [Deployment Factory Contract](https://docs.alchemy.com/docs/create2-an-alternative-to-deriving-contract-addresses#create2-contract-factory) address ([`factory`](#factory-optional)) with deployment arguments ([`factoryData`](#factorydata-optional)),
2. Call the function on the "temporarily deployed" contract ([`address`](#address)).
Viem offers two ways of performing a Deployless Call, via:

The example below demonstrates how we can utilize this pattern to call the `entryPoint` function on an [ERC-4337 Smart Account](https://eips.ethereum.org/EIPS/eip-4337) which has not been deployed:
- [Bytecode](#bytecode)
- a [Deploy Factory](#deploy-factory): "temporarily deploys" a contract with a provided [Deploy Factory](https://docs.alchemy.com/docs/create2-an-alternative-to-deriving-contract-addresses#create2-contract-factory), and calls the function on the deployed contract.

:::tip
The **Deployless Call** patterns are also accessible via the [`readContract`](/docs/contract/readContract#deployless-reads) & [Contract Instance](/docs/contract/getContract) APIs.
:::

#### Bytecode

The example below demonstrates how we can utilize a Deployless Call **via Bytecode** to call the `name` function on the [Wagmi Example ERC721 contract](https://etherscan.io/address/0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2#code) which has not been deployed:

:::code-group

```ts twoslash [example.ts]
import { parseAbi } from 'viem'
import { publicClient } from './config'

const data = await publicClient.readContract({
abi: parseAbi(['function name() view returns (string)']),
bytecode: '0x...',
functionName: 'name'
})
```

```ts twoslash [config.ts] filename="config.ts"
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

export const publicClient = createPublicClient({
chain: mainnet,
transport: http()
})
```

:::

#### Deploy Factory

The example below demonstrates how we can utilize a Deployless Call **via a [Deploy Factory](https://docs.alchemy.com/docs/create2-an-alternative-to-deriving-contract-addresses#create2-contract-factory)** to call the `entryPoint` function on an [ERC-4337 Smart Account](https://eips.ethereum.org/EIPS/eip-4337) which has not been deployed:

:::code-group

Expand Down
5 changes: 1 addition & 4 deletions src/actions/public/call.test.ts

Large diffs are not rendered by default.

76 changes: 39 additions & 37 deletions src/actions/public/call.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,8 @@ export type CallErrorType = GetCallErrorReturnType<
| FormatTransactionRequestErrorType
| ScheduleMulticallErrorType
| RequestErrorType
| ToCounterfactualDataErrorType
| ToDeploylessCallViaBytecodeDataErrorType
| ToDeploylessCallViaFactoryDataErrorType
>

/**
Expand Down Expand Up @@ -178,25 +179,25 @@ export async function call<TChain extends Chain | undefined>(
} = args
const account = account_ ? parseAccount(account_) : undefined

// Check if the call is going to be routed via a counterfactual contract deployment.
const counterfactual =
// Counterfactual deployment via bytecode.
(bytecode && data_) ||
// Counterfactual deployment via factory.
(factory && factoryData && to && data_)
// Check if the call is deployless via bytecode.
const deploylessCallViaBytecode = bytecode && data_
// Check if the call is deployless via a factory.
const deploylessCallViaFactory = factory && factoryData && to && data_
const deploylessCall = deploylessCallViaBytecode || deploylessCallViaFactory

const data = (() => {
// If the call is going to be routed via a counterfactual contract deployment,
// we need to get the data to deploy the counterfactual contract, and then perform
// the call.
if (counterfactual)
return toCounterfactualData({
if (deploylessCallViaBytecode)
return toDeploylessCallViaBytecodeData({
bytecode,
data: data_,
})

Check warning on line 193 in src/actions/public/call.ts

View check run for this annotation

Codecov / codecov/patch

src/actions/public/call.ts#L191-L193

Added lines #L191 - L193 were not covered by tests
if (deploylessCallViaFactory)
return toDeploylessCallViaFactoryData({
data: data_,
factory,
factoryData,
to,
} as ToCounterfactualDataParameters)
})
return data_
})()

Expand Down Expand Up @@ -224,7 +225,7 @@ export async function call<TChain extends Chain | undefined>(
maxFeePerGas,
maxPriorityFeePerGas,
nonce,
to: counterfactual ? undefined : to,
to: deploylessCall ? undefined : to,
value,
} as TransactionRequest) as TransactionRequest

Expand Down Expand Up @@ -271,7 +272,7 @@ export async function call<TChain extends Chain | undefined>(
return { data: await offchainLookup(client, { data, to }) }

// Check for counterfactual deployment error.
if (counterfactual && data?.slice(0, 10) === '0x101bb98d')
if (deploylessCall && data?.slice(0, 10) === '0x101bb98d')
throw new CounterfactualDeploymentFailedError({ factory })

throw getCallError(err as ErrorType, {
Expand Down Expand Up @@ -397,31 +398,32 @@ async function scheduleMulticall<TChain extends Chain | undefined>(
return { data: returnData }
}

type ToCounterfactualDataParameters = OneOf<
| {
data: Hex
factory: Address
factoryData: Hex
to: Address
}
| {
bytecode: Hex
data: Hex
}
>
type ToDeploylessCallViaBytecodeDataErrorType =
| EncodeDeployDataErrorType
| ErrorType

type ToCounterfactualDataErrorType = EncodeDeployDataErrorType | ErrorType
function toDeploylessCallViaBytecodeData(parameters: {
bytecode: Hex
data: Hex
}) {
const { bytecode, data } = parameters
return encodeDeployData({
abi: parseAbi(['constructor(bytes, bytes)']),
bytecode: deploylessCallViaBytecodeBytecode,
args: [bytecode, data],
})
}

Check warning on line 415 in src/actions/public/call.ts

View check run for this annotation

Codecov / codecov/patch

src/actions/public/call.ts#L405-L415

Added lines #L405 - L415 were not covered by tests

function toCounterfactualData(parameters: ToCounterfactualDataParameters) {
if (parameters.bytecode) {
const { bytecode, data } = parameters
return encodeDeployData({
abi: parseAbi(['constructor(bytes, bytes)']),
bytecode: deploylessCallViaBytecodeBytecode,
args: [bytecode, data],
})
}
type ToDeploylessCallViaFactoryDataErrorType =
| EncodeDeployDataErrorType
| ErrorType

function toDeploylessCallViaFactoryData(parameters: {
data: Hex
factory: Address
factoryData: Hex
to: Address
}) {
const { data, factory, factoryData, to } = parameters
return encodeDeployData({
abi: parseAbi(['constructor(address, bytes, address, bytes)']),
Expand Down
13 changes: 12 additions & 1 deletion src/actions/public/readContract.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,7 +195,7 @@ describe('bayc', () => {
})
})

describe('counterfactual read', () => {
describe('deployless read (factory)', () => {
test('default', async () => {
const { factoryAddress: factory } = await deployMock4337Account()

Expand Down Expand Up @@ -233,6 +233,17 @@ describe('counterfactual read', () => {
})
})

describe('deployless read (bytecode)', () => {
test('default', async () => {
const result = await readContract(client, {
abi: wagmiContractConfig.abi,
bytecode: wagmiContractConfig.bytecode,
functionName: 'name',
})
expect(result).toMatchInlineSnapshot(`"wagmi"`)
})
})

describe('contract errors', () => {
test('revert', async () => {
const { contractAddress } = await deployErrorExample()
Expand Down
13 changes: 12 additions & 1 deletion src/actions/public/readContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import {
} from '../../utils/errors/getContractError.js'
import { getAction } from '../../utils/getAction.js'

import type { Hex } from '../../types/misc.js'
import { type CallErrorType, type CallParameters, call } from './call.js'

export type ReadContractParameters<
Expand All @@ -50,7 +51,17 @@ export type ReadContractParameters<
| 'stateOverride'
>
> &
ContractFunctionParameters<abi, 'pure' | 'view', functionName, args>
(
| ({
bytecode?: Hex | undefined
} & ContractFunctionParameters<abi, 'pure' | 'view', functionName, args>)
| ({
bytecode: Hex
} & Omit<
ContractFunctionParameters<abi, 'pure' | 'view', functionName, args>,
'address'
>)
)

export type ReadContractReturnType<
abi extends Abi | readonly unknown[] = Abi,
Expand Down
2 changes: 2 additions & 0 deletions test/src/abis.ts

Large diffs are not rendered by default.

0 comments on commit 7550884

Please sign in to comment.