Skip to content

Commit

Permalink
feat: deployless call via bytecode (#2408)
Browse files Browse the repository at this point in the history
* wip: checkpoint

* docs

* multicall

* wip: checkpoint

* tweaks

* chore: tweaks

* chore: bump size

* tweak

* tweaks

* tweak

* tweak
  • Loading branch information
jxom authored Jun 15, 2024
1 parent 07dcb05 commit 21cb684
Show file tree
Hide file tree
Showing 17 changed files with 314 additions and 50 deletions.
5 changes: 5 additions & 0 deletions .changeset/young-pants-draw.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `code` as a parameter to `call` + `readContract` – to enable Deployless Calls via Bytecode.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@
{
"name": "viem (esm)",
"path": "./src/_esm/index.js",
"limit": "59.6 kB",
"limit": "59.8 kB",
"import": "*"
},
{
Expand Down Expand Up @@ -173,13 +173,13 @@
{
"name": "viem/ens (tree-shaking)",
"path": "./src/_esm/ens/index.js",
"limit": "22.3 kB",
"limit": "22.5 kB",
"import": "{ getEnsAvatar }"
},
{
"name": "viem/siwe",
"path": "./src/_esm/siwe/index.js",
"limit": "30.3 kB",
"limit": "30.5 kB",
"import": "*"
},
{
Expand Down
73 changes: 62 additions & 11 deletions site/pages/docs/actions/public/call.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,57 @@ 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 Call** 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 ([`to`](#to-optional)).
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 { encodeFunctionData, parseAbi } from 'viem'
import { account, publicClient } from './config'
import { publicClient } from './config'

const data = await publicClient.call({
// Bytecode of the contract. Accessible here: https://etherscan.io/address/0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2#code
code: '0x...'
// Function to call on the contract.
data: encodeFunctionData({
abi: parseAbi(['function name() view returns (string)']),
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

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

const data = await publicClient.call({
// Address of the contract deployer (e.g. Smart Account Factory).
Expand All @@ -60,7 +100,7 @@ const data = await publicClient.call({
factoryData: encodeFunctionData({
abi: parseAbi(['function createAccount(address owner, uint256 salt)']),
functionName: 'createAccount',
args: [account, 0n],
args: [owner, 0n],
}),

// Function to call on the contract (e.g. Smart Account contract).
Expand All @@ -78,7 +118,7 @@ const data = await publicClient.call({
import { createPublicClient, http } from 'viem'
import { mainnet } from 'viem/chains'

export const account = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'
export const owner = '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266'

export const publicClient = createPublicClient({
chain: mainnet,
Expand All @@ -92,10 +132,6 @@ export const publicClient = createPublicClient({
This example utilizes the [SimpleAccountFactory](https://github.com/eth-infinitism/account-abstraction/blob/develop/contracts/samples/SimpleAccountFactory.sol).
:::

:::tip
The **Deployless Call** pattern (and `factory` + `factoryData` parameters) is also accessible via the [`readContract`](/docs/contract/readContract#deployless-reads) & [Contract Instance](http://localhost:5173/docs/contract/getContract) APIs.
:::

## Returns

`0x${string}`
Expand Down Expand Up @@ -211,6 +247,21 @@ const data = await publicClient.call({
})
```

### code (optional)

- **Type:**

Bytecode to perform the call against.

```ts twoslash
// [!include ~/snippets/publicClient.ts]
// ---cut---
const data = await publicClient.call({
code: '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)']),
code: '0x...', // Accessible here: https://etherscan.io/address/0xFBA3912Ca04dd458c843e2EE08967fC04f3579c2#code
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
83 changes: 80 additions & 3 deletions src/actions/public/call.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ import {
Mock4337AccountFactory,
OffchainLookupExample,
} from '~test/contracts/generated.js'
import { baycContractConfig, usdcContractConfig } from '~test/src/abis.js'
import {
baycContractConfig,
usdcContractConfig,
wagmiContractConfig,
} from '~test/src/abis.js'
import { createCcipServer } from '~test/src/ccip.js'
import { accounts } from '~test/src/constants.js'
import { blobData, kzg } from '~test/src/kzg.js'
Expand All @@ -30,6 +34,7 @@ import {
createClient,
decodeFunctionResult,
encodeAbiParameters,
multicall3Abi,
pad,
parseEther,
stringToHex,
Expand Down Expand Up @@ -1006,8 +1011,8 @@ describe('batch call', () => {
})
})

describe('deployless counterfactual call', () => {
test('call to account that has not been deployed', async () => {
describe('deployless call (factory)', () => {
test('default', async () => {
const { factoryAddress } = await deployMock4337Account()

const address = await readContract(client, {
Expand Down Expand Up @@ -1077,6 +1082,78 @@ describe('deployless counterfactual call', () => {
})
})

describe('deployless call (bytecode)', () => {
test('default', async () => {
const { data } = await call(client, {
code: wagmiContractConfig.bytecode,
data: encodeFunctionData({
abi: wagmiContractConfig.abi,
functionName: 'name',
}),
})

expect(
decodeFunctionResult({
abi: wagmiContractConfig.abi,
data: data!,
functionName: 'name',
}),
).toMatchInlineSnapshot(`"wagmi"`)
})

test('multicall', async () => {
const code =
'0x608060405234801561001057600080fd5b50610ee0806100206000396000f3fe6080604052600436106100f35760003560e01c80634d2301cc1161008a578063a8b0574e11610059578063a8b0574e1461025a578063bce38bd714610275578063c3077fa914610288578063ee82ac5e1461029b57600080fd5b80634d2301cc146101ec57806372425d9d1461022157806382ad56cb1461023457806386d516e81461024757600080fd5b80633408e470116100c65780633408e47014610191578063399542e9146101a45780633e64a696146101c657806342cbb15c146101d957600080fd5b80630f28c97d146100f8578063174dea711461011a578063252dba421461013a57806327e86d6e1461015b575b600080fd5b34801561010457600080fd5b50425b6040519081526020015b60405180910390f35b61012d610128366004610a85565b6102ba565b6040516101119190610bbe565b61014d610148366004610a85565b6104ef565b604051610111929190610bd8565b34801561016757600080fd5b50437fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0140610107565b34801561019d57600080fd5b5046610107565b6101b76101b2366004610c60565b610690565b60405161011193929190610cba565b3480156101d257600080fd5b5048610107565b3480156101e557600080fd5b5043610107565b3480156101f857600080fd5b50610107610207366004610ce2565b73ffffffffffffffffffffffffffffffffffffffff163190565b34801561022d57600080fd5b5044610107565b61012d610242366004610a85565b6106ab565b34801561025357600080fd5b5045610107565b34801561026657600080fd5b50604051418152602001610111565b61012d610283366004610c60565b61085a565b6101b7610296366004610a85565b610a1a565b3480156102a757600080fd5b506101076102b6366004610d18565b4090565b60606000828067ffffffffffffffff8111156102d8576102d8610d31565b60405190808252806020026020018201604052801561031e57816020015b6040805180820190915260008152606060208201528152602001906001900390816102f65790505b5092503660005b8281101561047757600085828151811061034157610341610d60565b6020026020010151905087878381811061035d5761035d610d60565b905060200281019061036f9190610d8f565b6040810135958601959093506103886020850185610ce2565b73ffffffffffffffffffffffffffffffffffffffff16816103ac6060870187610dcd565b6040516103ba929190610e32565b60006040518083038185875af1925050503d80600081146103f7576040519150601f19603f3d011682016040523d82523d6000602084013e6103fc565b606091505b50602080850191909152901515808452908501351761046d577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260846000fd5b5050600101610325565b508234146104e6576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601a60248201527f4d756c746963616c6c333a2076616c7565206d69736d6174636800000000000060448201526064015b60405180910390fd5b50505092915050565b436060828067ffffffffffffffff81111561050c5761050c610d31565b60405190808252806020026020018201604052801561053f57816020015b606081526020019060019003908161052a5790505b5091503660005b8281101561068657600087878381811061056257610562610d60565b90506020028101906105749190610e42565b92506105836020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166105a66020850185610dcd565b6040516105b4929190610e32565b6000604051808303816000865af19150503d80600081146105f1576040519150601f19603f3d011682016040523d82523d6000602084013e6105f6565b606091505b5086848151811061060957610609610d60565b602090810291909101015290508061067d576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b50600101610546565b5050509250929050565b43804060606106a086868661085a565b905093509350939050565b6060818067ffffffffffffffff8111156106c7576106c7610d31565b60405190808252806020026020018201604052801561070d57816020015b6040805180820190915260008152606060208201528152602001906001900390816106e55790505b5091503660005b828110156104e657600084828151811061073057610730610d60565b6020026020010151905086868381811061074c5761074c610d60565b905060200281019061075e9190610e76565b925061076d6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff166107906040850185610dcd565b60405161079e929190610e32565b6000604051808303816000865af19150503d80600081146107db576040519150601f19603f3d011682016040523d82523d6000602084013e6107e0565b606091505b506020808401919091529015158083529084013517610851577f08c379a000000000000000000000000000000000000000000000000000000000600052602060045260176024527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060445260646000fd5b50600101610714565b6060818067ffffffffffffffff81111561087657610876610d31565b6040519080825280602002602001820160405280156108bc57816020015b6040805180820190915260008152606060208201528152602001906001900390816108945790505b5091503660005b82811015610a105760008482815181106108df576108df610d60565b602002602001015190508686838181106108fb576108fb610d60565b905060200281019061090d9190610e42565b925061091c6020840184610ce2565b73ffffffffffffffffffffffffffffffffffffffff1661093f6020850185610dcd565b60405161094d929190610e32565b6000604051808303816000865af19150503d806000811461098a576040519150601f19603f3d011682016040523d82523d6000602084013e61098f565b606091505b506020830152151581528715610a07578051610a07576040517f08c379a000000000000000000000000000000000000000000000000000000000815260206004820152601760248201527f4d756c746963616c6c333a2063616c6c206661696c656400000000000000000060448201526064016104dd565b506001016108c3565b5050509392505050565b6000806060610a2b60018686610690565b919790965090945092505050565b60008083601f840112610a4b57600080fd5b50813567ffffffffffffffff811115610a6357600080fd5b6020830191508360208260051b8501011115610a7e57600080fd5b9250929050565b60008060208385031215610a9857600080fd5b823567ffffffffffffffff811115610aaf57600080fd5b610abb85828601610a39565b90969095509350505050565b6000815180845260005b81811015610aed57602081850181015186830182015201610ad1565b81811115610aff576000602083870101525b50601f017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0169290920160200192915050565b600082825180855260208086019550808260051b84010181860160005b84811015610bb1578583037fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe001895281518051151584528401516040858501819052610b9d81860183610ac7565b9a86019a9450505090830190600101610b4f565b5090979650505050505050565b602081526000610bd16020830184610b32565b9392505050565b600060408201848352602060408185015281855180845260608601915060608160051b870101935082870160005b82811015610c52577fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa0888703018452610c40868351610ac7565b95509284019290840190600101610c06565b509398975050505050505050565b600080600060408486031215610c7557600080fd5b83358015158114610c8557600080fd5b9250602084013567ffffffffffffffff811115610ca157600080fd5b610cad86828701610a39565b9497909650939450505050565b838152826020820152606060408201526000610cd96060830184610b32565b95945050505050565b600060208284031215610cf457600080fd5b813573ffffffffffffffffffffffffffffffffffffffff81168114610bd157600080fd5b600060208284031215610d2a57600080fd5b5035919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff81833603018112610dc357600080fd5b9190910192915050565b60008083357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe1843603018112610e0257600080fd5b83018035915067ffffffffffffffff821115610e1d57600080fd5b602001915036819003821315610a7e57600080fd5b8183823760009101908152919050565b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffc1833603018112610dc357600080fd5b600082357fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffa1833603018112610dc357600080fdfea2646970667358221220bb2b5c71a328032f97c676ae39a1ec2148d3e5d6f73d95e9b17910152d61f16264736f6c634300080c0033'

const { data } = await call(client, {
code,
data: encodeFunctionData({
abi: multicall3Abi,
functionName: 'aggregate3',
args: [
[
{
target: wagmiContractConfig.address,
allowFailure: true,
callData: encodeFunctionData({
abi: wagmiContractConfig.abi,
functionName: 'name',
}),
},
{
target: wagmiContractConfig.address,
allowFailure: true,
callData: encodeFunctionData({
abi: wagmiContractConfig.abi,
functionName: 'totalSupply',
}),
},
],
],
}),
})

expect(
decodeFunctionResult({
abi: multicall3Abi,
data: data!,
functionName: 'aggregate3',
}),
).toMatchInlineSnapshot(`
[
{
"returnData": "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000057761676d69000000000000000000000000000000000000000000000000000000",
"success": true,
},
{
"returnData": "0x0000000000000000000000000000000000000000000000000000000000000277",
"success": true,
},
]
`)
})
})

describe('getRevertErrorData', () => {
test('default', () => {
expect(getRevertErrorData(new Error('lol'))).toBe(undefined)
Expand Down
Loading

0 comments on commit 21cb684

Please sign in to comment.