Skip to content

Commit

Permalink
feat: readContract
Browse files Browse the repository at this point in the history
  • Loading branch information
jxom committed Feb 3, 2023
1 parent 81327ef commit 7afdee8
Show file tree
Hide file tree
Showing 12 changed files with 237 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/empty-poets-destroy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"viem": patch
---

Added `readContract`
2 changes: 1 addition & 1 deletion site/.vitepress/sidebar.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,7 +413,7 @@ export const sidebar: DefaultTheme.Sidebar = {
link: '/docs/contract/multicall',
},
{
text: 'readContract 🚧',
text: 'readContract',
link: '/docs/contract/readContract',
},
{
Expand Down
1 change: 1 addition & 0 deletions src/actions/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ test('exports actions', () => {
"increaseTime": [Function],
"inspectTxpool": [Function],
"mine": [Function],
"readContract": [Function],
"removeBlockTimestampInterval": [Function],
"requestAccounts": [Function],
"requestPermissions": [Function],
Expand Down
3 changes: 3 additions & 0 deletions src/actions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
getTransactionConfirmations,
getTransactionCount,
getTransactionReceipt,
readContract,
simulateContract,
uninstallFilter,
waitForTransactionReceipt,
Expand Down Expand Up @@ -69,6 +70,8 @@ export type {
OnBlockResponse,
OnTransactions,
OnTransactionsResponse,
ReadContractArgs,
ReadContractResponse,
ReplacementReason,
ReplacementResponse,
SimulateContractArgs,
Expand Down
1 change: 1 addition & 0 deletions src/actions/public/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ test('exports actions', () => {
"getTransactionConfirmations": [Function],
"getTransactionCount": [Function],
"getTransactionReceipt": [Function],
"readContract": [Function],
"simulateContract": [Function],
"uninstallFilter": [Function],
"waitForTransactionReceipt": [Function],
Expand Down
7 changes: 6 additions & 1 deletion src/actions/public/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export { simulateContract } from './simulateContract'
export type {
SimulateContractArgs,
SimulateContractResponse,
FormattedSimulateContract,
} from './simulateContract'

export { createPendingTransactionFilter } from './createPendingTransactionFilter'
Expand Down Expand Up @@ -91,6 +90,12 @@ export type {
GetTransactionReceiptResponse,
} from './getTransactionReceipt'

export { readContract } from './readContract'
export type {
ReadContractArgs,
ReadContractResponse,
} from './readContract'

export { uninstallFilter } from './uninstallFilter'
export type {
UninstallFilterArgs,
Expand Down
128 changes: 128 additions & 0 deletions src/actions/public/readContract.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/**
* TODO: Heaps more test cases :D
* - Complex calldata types
* - Complex return types (tuple/structs)
* - Calls against blocks
*/

import { describe, expect, test } from 'vitest'
import {
accounts,
publicClient,
testClient,
wagmiContractConfig,
walletClient,
} from '../../_test'
import { baycContractConfig } from '../../_test/abis'
import { encodeFunctionData } from '../../utils'
import { mine } from '../test'
import { sendTransaction } from '../wallet'

import { deployContract } from './deployContract'
import { getTransactionReceipt } from './getTransactionReceipt'
import { readContract } from './readContract'

describe('wagmi', () => {
test('default', async () => {
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'balanceOf',
args: ['0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC'],
}),
).toEqual(3n)
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'getApproved',
args: [420n],
}),
).toEqual('0x0000000000000000000000000000000000000000')
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'isApprovedForAll',
args: [
'0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC',
'0x0000000000000000000000000000000000000000',
],
}),
).toEqual(false)
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'name',
}),
).toEqual('wagmi')
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'ownerOf',
args: [420n],
}),
).toEqual('0xa5cc3c03994DB5b0d9A5eEdD10CabaB0813678AC')
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'supportsInterface',
args: ['0x1a452251'],
}),
).toEqual(false)
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'symbol',
}),
).toEqual('WAGMI')
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'tokenURI',
args: [420n],
}),
).toMatchInlineSnapshot(
'"data:application/json;base64,eyJuYW1lIjogIndhZ21pICM0MjAiLCAiaW1hZ2UiOiAiZGF0YTppbWFnZS9zdmcreG1sO2Jhc2U2NCxQSE4yWnlCNGJXeHVjejBpYUhSMGNEb3ZMM2QzZHk1M015NXZjbWN2TWpBd01DOXpkbWNpSUhkcFpIUm9QU0l4TURJMElpQm9aV2xuYUhROUlqRXdNalFpSUdacGJHdzlJbTV2Ym1VaVBqeHdZWFJvSUdacGJHdzlJbWh6YkNneE1UY3NJREV3TUNVc0lERXdKU2tpSUdROUlrMHdJREJvTVRBeU5IWXhNREkwU0RCNklpQXZQanhuSUdacGJHdzlJbWh6YkNneU9EZ3NJREV3TUNVc0lEa3dKU2tpUGp4d1lYUm9JR1E5SWswNU1ETWdORE0zTGpWak1DQTVMakV4TXkwM0xqTTRPQ0F4Tmk0MUxURTJMalVnTVRZdU5YTXRNVFl1TlMwM0xqTTROeTB4Tmk0MUxURTJMalVnTnk0ek9EZ3RNVFl1TlNBeE5pNDFMVEUyTGpVZ01UWXVOU0EzTGpNNE55QXhOaTQxSURFMkxqVjZUVFk1T0M0MU1qa2dOVFkyWXpZdU9USXhJREFnTVRJdU5UTXROUzQxT1RZZ01USXVOVE10TVRJdU5YWXROVEJqTUMwMkxqa3dOQ0ExTGpZd09TMHhNaTQxSURFeUxqVXlPUzB4TWk0MWFESTFMakExT1dNMkxqa3lJREFnTVRJdU5USTVJRFV1TlRrMklERXlMalV5T1NBeE1pNDFkalV3WXpBZ05pNDVNRFFnTlM0Mk1Ea2dNVEl1TlNBeE1pNDFNeUF4TWk0MWN6RXlMalV5T1MwMUxqVTVOaUF4TWk0MU1qa3RNVEl1TlhZdE5UQmpNQzAyTGprd05DQTFMall3T1MweE1pNDFJREV5TGpVekxURXlMalZvTWpVdU1EVTVZell1T1RJZ01DQXhNaTQxTWprZ05TNDFPVFlnTVRJdU5USTVJREV5TGpWMk5UQmpNQ0EyTGprd05DQTFMall3T1NBeE1pNDFJREV5TGpVeU9TQXhNaTQxYURNM0xqVTRPV00yTGpreUlEQWdNVEl1TlRJNUxUVXVOVGsySURFeUxqVXlPUzB4TWk0MWRpMDNOV013TFRZdU9UQTBMVFV1TmpBNUxURXlMalV0TVRJdU5USTVMVEV5TGpWekxURXlMalV6SURVdU5UazJMVEV5TGpVeklERXlMalYyTlRZdU1qVmhOaTR5TmpRZ05pNHlOalFnTUNBeElERXRNVEl1TlRJNUlEQldORGM0TGpWak1DMDJMamt3TkMwMUxqWXdPUzB4TWk0MUxURXlMalV6TFRFeUxqVklOams0TGpVeU9XTXROaTQ1TWlBd0xURXlMalV5T1NBMUxqVTVOaTB4TWk0MU1qa2dNVEl1TlhZM05XTXdJRFl1T1RBMElEVXVOakE1SURFeUxqVWdNVEl1TlRJNUlERXlMalY2SWlBdlBqeHdZWFJvSUdROUlrMHhOVGN1TmpVMUlEVTBNV010Tmk0NU16SWdNQzB4TWk0MU5USXROUzQxT1RZdE1USXVOVFV5TFRFeUxqVjJMVFV3WXpBdE5pNDVNRFF0TlM0Mk1Ua3RNVEl1TlMweE1pNDFOVEV0TVRJdU5WTXhNakFnTkRjeExqVTVOaUF4TWpBZ05EYzRMalYyTnpWak1DQTJMamt3TkNBMUxqWXlJREV5TGpVZ01USXVOVFV5SURFeUxqVm9NVFV3TGpZeVl6WXVPVE16SURBZ01USXVOVFV5TFRVdU5UazJJREV5TGpVMU1pMHhNaTQxZGkwMU1HTXdMVFl1T1RBMElEVXVOakU1TFRFeUxqVWdNVEl1TlRVeUxURXlMalZvTVRRMExqTTBOV016TGpRMk5TQXdJRFl1TWpjMklESXVOems0SURZdU1qYzJJRFl1TWpWekxUSXVPREV4SURZdU1qVXROaTR5TnpZZ05pNHlOVWd6TWpBdU9ESTRZeTAyTGprek15QXdMVEV5TGpVMU1pQTFMalU1TmkweE1pNDFOVElnTVRJdU5YWXpOeTQxWXpBZ05pNDVNRFFnTlM0Mk1Ua2dNVEl1TlNBeE1pNDFOVElnTVRJdU5XZ3hOVEF1TmpKak5pNDVNek1nTUNBeE1pNDFOVEl0TlM0MU9UWWdNVEl1TlRVeUxURXlMalYyTFRjMVl6QXROaTQ1TURRdE5TNDJNVGt0TVRJdU5TMHhNaTQxTlRJdE1USXVOVWd5T0RNdU1UY3lZeTAyTGprek1pQXdMVEV5TGpVMU1TQTFMalU1TmkweE1pNDFOVEVnTVRJdU5YWTFNR013SURZdU9UQTBMVFV1TmpFNUlERXlMalV0TVRJdU5UVXlJREV5TGpWb0xUSTFMakV3TTJNdE5pNDVNek1nTUMweE1pNDFOVEl0TlM0MU9UWXRNVEl1TlRVeUxURXlMalYyTFRVd1l6QXROaTQ1TURRdE5TNDJNaTB4TWk0MUxURXlMalUxTWkweE1pNDFjeTB4TWk0MU5USWdOUzQxT1RZdE1USXVOVFV5SURFeUxqVjJOVEJqTUNBMkxqa3dOQzAxTGpZeE9TQXhNaTQxTFRFeUxqVTFNU0F4TWk0MWFDMHlOUzR4TURSNmJUTXdNUzR5TkRJdE5pNHlOV013SURNdU5EVXlMVEl1T0RFeElEWXVNalV0Tmk0eU56WWdOaTR5TlVnek16a3VOalUxWXkwekxqUTJOU0F3TFRZdU1qYzJMVEl1TnprNExUWXVNamMyTFRZdU1qVnpNaTQ0TVRFdE5pNHlOU0EyTGpJM05pMDJMakkxYURFeE1pNDVOalpqTXk0ME5qVWdNQ0EyTGpJM05pQXlMamM1T0NBMkxqSTNOaUEyTGpJMWVrMDBPVGNnTlRVekxqZ3hPR013SURZdU9USTVJRFV1TmpJNElERXlMalUwTmlBeE1pNDFOekVnTVRJdU5UUTJhREV6TW1FMkxqSTRJRFl1TWpnZ01DQXdJREVnTmk0eU9EWWdOaTR5TnpJZ05pNHlPQ0EyTGpJNElEQWdNQ0F4TFRZdU1qZzJJRFl1TWpjemFDMHhNekpqTFRZdU9UUXpJREF0TVRJdU5UY3hJRFV1TmpFMkxURXlMalUzTVNBeE1pNDFORFpCTVRJdU5UWWdNVEl1TlRZZ01DQXdJREFnTlRBNUxqVTNNU0EyTURSb01UVXdMamcxT0dNMkxqazBNeUF3SURFeUxqVTNNUzAxTGpZeE5pQXhNaTQxTnpFdE1USXVOVFExZGkweE1USXVPVEZqTUMwMkxqa3lPQzAxTGpZeU9DMHhNaTQxTkRVdE1USXVOVGN4TFRFeUxqVTBOVWcxTURrdU5UY3hZeTAyTGprME15QXdMVEV5TGpVM01TQTFMall4TnkweE1pNDFOekVnTVRJdU5UUTFkamMxTGpJM00zcHRNemN1TnpFMExUWXlMamN5TjJNdE5pNDVORE1nTUMweE1pNDFOekVnTlM0Mk1UY3RNVEl1TlRjeElERXlMalUwTlhZeU5TNHdPVEZqTUNBMkxqa3lPU0ExTGpZeU9DQXhNaTQxTkRZZ01USXVOVGN4SURFeUxqVTBObWd4TURBdU5UY3lZell1T1RReklEQWdNVEl1TlRjeExUVXVOakUzSURFeUxqVTNNUzB4TWk0MU5EWjJMVEkxTGpBNU1XTXdMVFl1T1RJNExUVXVOakk0TFRFeUxqVTBOUzB4TWk0MU56RXRNVEl1TlRRMVNEVXpOQzQzTVRSNklpQm1hV3hzTFhKMWJHVTlJbVYyWlc1dlpHUWlJQzgrUEM5blBqd3ZjM1puUGc9PSJ9"',
)
expect(
await readContract(publicClient, {
...wagmiContractConfig,
functionName: 'totalSupply',
}),
).toEqual(558n)
})
})

test('fake contract address', async () => {
await expect(() =>
readContract(publicClient, {
abi: wagmiContractConfig.abi,
address: '0x0000000000000000000000000000000000000069',
functionName: 'totalSupply',
}),
).rejects.toThrowErrorMatchingInlineSnapshot(`
"The contract method \\"totalSupply\\" returned no data (\\"0x\\"). This could be due to any of the following:
- The contract does not have the function \\"totalSupply\\",
- The parameters passed to the contract function may be invalid, or
- The address is not a contract.
Contract: 0x0000000000000000000000000000000000000000
Function: totalSupply()
> \\"0x\\"
Version: viem@1.0.2"
`)
})

// Deploy BAYC Contract
async function deployBAYC() {
const hash = await deployContract(walletClient, {
...baycContractConfig,
args: ['Bored Ape Wagmi Club', 'BAYC', 69420n, 0n],
from: accounts[0].address,
})
await mine(testClient, { blocks: 1 })
const { contractAddress } = await getTransactionReceipt(publicClient, {
hash,
})
return { contractAddress }
}
87 changes: 87 additions & 0 deletions src/actions/public/readContract.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
import { Abi } from 'abitype'

import type { Chain, Formatter } from '../../chains'
import type { PublicClient } from '../../clients'
import type {
Address,
ExtractArgsFromAbi,
ExtractResultFromAbi,
ExtractFunctionNameFromAbi,
} from '../../types'
import {
EncodeFunctionDataArgs,
decodeFunctionResult,
encodeFunctionData,
getContractError,
} from '../../utils'
import { call, CallArgs, FormattedCall } from './call'

export type FormattedReadContract<
TFormatter extends Formatter | undefined = Formatter,
> = FormattedCall<TFormatter>

export type ReadContractArgs<
TAbi extends Abi | readonly unknown[] = Abi,
TFunctionName extends string = any,
> = Omit<
CallArgs,
| 'accessList'
| 'chain'
| 'from'
| 'gas'
| 'gasPrice'
| 'maxFeePerGas'
| 'maxPriorityFeePerGas'
| 'nonce'
| 'to'
| 'data'
| 'value'
> & {
address: Address
abi: TAbi
functionName: ExtractFunctionNameFromAbi<TAbi, TFunctionName, 'pure' | 'view'>
} & ExtractArgsFromAbi<TAbi, TFunctionName>

export type ReadContractResponse<
TAbi extends Abi | readonly unknown[] = Abi,
TFunctionName extends string = string,
> = ExtractResultFromAbi<TAbi, TFunctionName>

export async function readContract<
TAbi extends Abi = Abi,
TFunctionName extends string = any,
>(
client: PublicClient,
{
abi,
address,
args,
functionName,
...callRequest
}: ReadContractArgs<TAbi, TFunctionName>,
): Promise<ReadContractResponse<TAbi, TFunctionName>> {
const calldata = encodeFunctionData({
abi,
args,
functionName,
} as unknown as EncodeFunctionDataArgs<TAbi, TFunctionName>)
try {
const { data } = await call(client, {
data: calldata,
to: address,
...callRequest,
} as unknown as CallArgs)
return decodeFunctionResult({
abi,
functionName,
data: data || '0x',
})
} catch (err) {
throw getContractError(err, {
abi,
address,
args,
functionName,
})
}
}
6 changes: 1 addition & 5 deletions src/actions/public/simulateContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,7 @@ import {
getContractError,
} from '../../utils'
import { WriteContractArgs } from '../wallet'
import { call, CallArgs, FormattedCall } from './call'

export type FormattedSimulateContract<
TFormatter extends Formatter | undefined = Formatter,
> = FormattedCall<TFormatter>
import { call, CallArgs } from './call'

export type SimulateContractArgs<
TChain extends Chain = Chain,
Expand Down
5 changes: 0 additions & 5 deletions src/actions/wallet/writeContract.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,11 @@ import type {
} from '../../types'
import { EncodeFunctionDataArgs, encodeFunctionData } from '../../utils'
import {
FormattedTransactionRequest,
sendTransaction,
SendTransactionArgs,
SendTransactionResponse,
} from './sendTransaction'

export type FormattedWriteContract<
TFormatter extends Formatter | undefined = Formatter,
> = FormattedTransactionRequest<TFormatter>

export type WriteContractArgs<
TChain extends Chain = Chain,
TAbi extends Abi | readonly unknown[] = Abi,
Expand Down
1 change: 1 addition & 0 deletions src/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ test('exports actions', () => {
"parseEther": [Function],
"parseGwei": [Function],
"parseUnit": [Function],
"readContract": [Function],
"removeBlockTimestampInterval": [Function],
"requestAccounts": [Function],
"requestPermissions": [Function],
Expand Down
3 changes: 3 additions & 0 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,8 @@ export type {
OnBlockResponse,
OnTransactions,
OnTransactionsResponse,
ReadContractArgs,
ReadContractResponse,
ResetArgs,
RequestPermissionsResponse,
RevertArgs,
Expand Down Expand Up @@ -113,6 +115,7 @@ export {
increaseTime,
inspectTxpool,
mine,
readContract,
removeBlockTimestampInterval,
reset,
requestAccounts,
Expand Down

3 comments on commit 7afdee8

@vercel
Copy link

@vercel vercel bot commented on 7afdee8 Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viem-playground – ./playgrounds/dev

viem-playground-git-main-wagmi-dev.vercel.app
viem-playground-wagmi-dev.vercel.app
viem-playground.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 7afdee8 Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viem-benchmark – ./playgrounds/benchmark

viem-benchmark.vercel.app
viem-benchmark-wagmi-dev.vercel.app
viem-benchmark-git-main-wagmi-dev.vercel.app

@vercel
Copy link

@vercel vercel bot commented on 7afdee8 Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Successfully deployed to the following URLs:

viem-site – ./site

viem-site-git-main-wagmi-dev.vercel.app
viem-site-wagmi-dev.vercel.app
viem-site.vercel.app

Please sign in to comment.