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

add set multiple abis in setRecords + get abi by content type #165

Merged
merged 4 commits into from
Jan 31, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
77 changes: 55 additions & 22 deletions packages/ensjs/deploy/00_register_legacy.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -149,7 +149,10 @@ const dummyABI = [
* abi?: {
* contentType: 1 | 2 | 4 | 8 | 256
* data: object | string
* }
* } | {
* contentType: 1 | 2 | 4 | 8 | 256
* data: string
* }[]
* }
* duration?: number
* subnames?: Subname[]
Expand Down Expand Up @@ -308,6 +311,31 @@ const names = [
},
},
},
{
label: 'with-type-all-abi',
namedOwner: 'owner',
namedAddr: 'owner',
records: {
abi: [
{
contentType: 1,
data: dummyABI,
},
{
contentType: 2,
data: dummyABI,
},
{
contentType: 4,
data: dummyABI,
},
{
contentType: 8,
data: 'https://example.com',
},
],
},
},
{
label: 'expired',
namedOwner: 'owner',
Expand Down Expand Up @@ -428,28 +456,33 @@ const func = async function (hre) {
await setContenthashTx.wait()
}
if (records.abi) {
console.log('ABI')
/**
* @type {string | Buffer | Uint8Array}
*/
let data
if (records.abi.contentType === 1 || records.abi.contentType === 256) {
data = JSON.stringify(records.abi.data)
} else if (records.abi.contentType === 2) {
data = pako.deflate(JSON.stringify(records.abi.data))
} else if (records.abi.contentType === 4) {
data = cbor.encode(records.abi.data)
} else {
data = records.abi.data
const abis = Array.isArray(records.abi) ? records.abi : [records.abi]
for (const abi of abis) {
/**
* @type {string | Buffer | Uint8Array}
*/
let data
if (
abi.contentType === 1 ||
abi.contentType === 256
) {
data = JSON.stringify(abi.data)
} else if (abi.contentType === 2) {
data = pako.deflate(JSON.stringify(abi.data))
} else if (abi.contentType === 4) {
data = cbor.encode(abi.data)
} else {
data = abi.data
}
if (typeof data === 'string') data = toBytes(data)
const setABITx = await _publicResolver.setABI(
hash,
abi.contentType,
data,
)
console.log(` - ${abi.contentType} (tx: ${setABITx.hash})...`)
await setABITx.wait()
}
if (typeof data === 'string') data = toBytes(data)
const setABITx = await _publicResolver.setABI(
hash,
records.abi.contentType,
data,
)
console.log(` - ${records.abi.contentType} (tx: ${setABITx.hash})...`)
await setABITx.wait()
}
}

Expand Down
19 changes: 11 additions & 8 deletions packages/ensjs/src/functions/public/_getAbi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,22 +19,25 @@ import { namehash } from '../../utils/normalise.js'
export type InternalGetAbiParameters = {
/** Name to get ABI record for */
name: string
/** Supported content types as bitwise
* ID 1: JSON
* ID 2: zlib compressed JSON
* ID 4: CBOR
* ID 8: URI
*/
supportedContentTypes?: bigint
/** Whether or not to throw decoding errors */
strict?: boolean
}

export type InternalGetAbiReturnType = Prettify<DecodedAbi | null>

// Supported content types as bitwise OR
// ID 1: JSON
// ID 2: zlib compressed JSON
// ID 4: CBOR
// ID 8: URI
const supportedContentTypes = 0xfn

const encode = (
_client: ClientWithEns,
{ name }: Omit<InternalGetAbiParameters, 'strict'>,
{
name,
supportedContentTypes = 0xfn,
}: Omit<InternalGetAbiParameters, 'strict'>,
): SimpleTransactionRequest => {
return {
to: EMPTY_ADDRESS,
Expand Down
48 changes: 48 additions & 0 deletions packages/ensjs/src/functions/public/getAbiRecord.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { RawContractError } from 'viem'
import type { ClientWithEns } from '../../contracts/consts.js'
import { publicClient } from '../../test/addTestContracts.js'
import getAbiRecord from './getAbiRecord.js'
import { generateSupportedContentTypes } from '../../utils/generateSupportedContentTypes.js'

const dummyABI = [
{
Expand Down Expand Up @@ -173,6 +174,53 @@ describe('getAbiRecord()', () => {
})
expect(result).toBeNull()
})
it('should return the result of type 1 abi if the name has multiple abi records', async () => {
const result = await getAbiRecord(publicClient, {
name: 'with-type-all-abi.eth',
})
expect(result).toBeTruthy()
if (result) {
expect(result.contentType).toBe(1)
expect(result.decoded).toBe(true)
expect(result.abi).toMatchObject(dummyABI)
}
})
it('should return the result of type 2 abi if supportedContentTypes is zlib', async () => {
const result = await getAbiRecord(publicClient, {
name: 'with-type-all-abi.eth',
supportedContentTypes: generateSupportedContentTypes('zlib'),
})
expect(result).toBeTruthy()
if (result) {
expect(result.contentType).toBe(2)
expect(result.decoded).toBe(true)
expect(result.abi).toMatchObject(dummyABI)
}
})
it('should return the result of type 4 abi if supportedContentTypes is cbor', async () => {
const result = await getAbiRecord(publicClient, {
name: 'with-type-all-abi.eth',
supportedContentTypes: generateSupportedContentTypes('cbor'),
})
expect(result).toBeTruthy()
if (result) {
expect(result.contentType).toBe(4)
expect(result.decoded).toBe(true)
expect(result.abi).toMatchObject(dummyABI)
}
})
it('should return the result of type 8 abi if supportedContentTypes is uri', async () => {
const result = await getAbiRecord(publicClient, {
name: 'with-type-all-abi.eth',
supportedContentTypes: generateSupportedContentTypes('uri'),
})
expect(result).toBeTruthy()
if (result) {
expect(result.contentType).toBe(8)
expect(result.decoded).toBe(true)
expect(result.abi).toBe('https://example.com')
}
})
it('should return null on error when strict is false', async () => {
await expect(
getAbiRecord.decode(
Expand Down
8 changes: 6 additions & 2 deletions packages/ensjs/src/functions/public/getAbiRecord.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,13 @@ export type GetAbiRecordReturnType = Prettify<InternalGetAbiReturnType>

const encode = (
client: ClientWithEns,
{ name, gatewayUrls }: Omit<GetAbiRecordParameters, 'strict'>,
{
name,
supportedContentTypes,
gatewayUrls,
}: Omit<GetAbiRecordParameters, 'strict'>,
): SimpleTransactionRequest => {
const prData = _getAbi.encode(client, { name })
const prData = _getAbi.encode(client, { name, supportedContentTypes })
return universalWrapper.encode(client, {
name,
data: prData.data,
Expand Down
22 changes: 22 additions & 0 deletions packages/ensjs/src/functions/wallet/setRecords.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,28 @@ it('should return a transaction to the resolver and delete successfully', async
expect(records.coins).toHaveLength(0)
expect(records.texts).toHaveLength(0)
})
it('should return a transaction to the resolver and delete all abis successfully', async () => {
const tx = await setRecords(walletClient, {
name: 'with-type-all-abi.eth',
resolverAddress: (await getResolver(publicClient, {
name: 'with-type-all-abi.eth',
}))!,
abi: [
await encodeAbi({ encodeAs: 'json', data: null }),
await encodeAbi({ encodeAs: 'cbor', data: null }),
await encodeAbi({ encodeAs: 'zlib', data: null }),
await encodeAbi({ encodeAs: 'uri', data: null }),
],
account: accounts[1],
})
await waitForTransaction(tx)

const records = await getRecords(publicClient, {
name: 'test123.eth',
abi: true,
})
expect(records.abi).toBeNull()
})
it('should error if there are no records to set', async () => {
await expect(
setRecords(walletClient, {
Expand Down
17 changes: 17 additions & 0 deletions packages/ensjs/src/utils/generateRecordCallArray.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,3 +105,20 @@ it('adds abi call when data is not empty', async () => {
]
`)
})
it('adds multiple abi calls when multiple abis are added', async () => {
const result = [
await encodeAbi({ encodeAs: 'json', data: { foo: 'bar' } }),
await encodeAbi({ encodeAs: 'uri', data: null }),
]
expect(
generateRecordCallArray({
namehash: namehash('test.eth'),
abi: result,
}),
).toMatchInlineSnapshot(`
[
"0x623195b0eb4f647bea6caa36333c816d7b46fdcb05f9466ecacc140ea8c66faf15b3d9f100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000000d7b22666f6f223a22626172227d00000000000000000000000000000000000000",
"0x623195b0eb4f647bea6caa36333c816d7b46fdcb05f9466ecacc140ea8c66faf15b3d9f1000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000000",
]
`)
})
9 changes: 6 additions & 3 deletions packages/ensjs/src/utils/generateRecordCallArray.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ export type RecordOptions = Prettify<{
/** Array of coin records */
coins?: Omit<EncodeSetAddrParameters, 'namehash'>[]
/** ABI value */
abi?: EncodedAbi
abi?: EncodedAbi | EncodedAbi[]
}>

export const generateRecordCallArray = ({
Expand All @@ -49,8 +49,11 @@ export const generateRecordCallArray = ({
}

if (abi !== undefined) {
const data = encodeSetAbi({ namehash, ...abi } as EncodeSetAbiParameters)
if (data) calls.push(data)
const abis = Array.isArray(abi) ? abi : [abi]
for (const abi_ of abis) {
const data = encodeSetAbi({ namehash, ...abi_ } as EncodeSetAbiParameters)
if (data) calls.push(data)
}
}

if (texts && texts.length > 0) {
Expand Down
24 changes: 24 additions & 0 deletions packages/ensjs/src/utils/generateSupportedContentTypes.test.ts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { generateSupportedContentTypes } from './generateSupportedContentTypes.js'

type FunctionParameters = Parameters<typeof generateSupportedContentTypes>
type EncodeAsParameter = FunctionParameters[0]

describe('generateSupportedContentTypes', () => {
it.each([
['json', 1n],
['zlib', 2n],
['cbor', 4n],
['uri', 8n],
['unknown', 0n],
[['json', 'zlib'], 3n],
[['zlib', 'cbor'], 6n],
[['cbor', 'uri'], 12n],
[['json', 'zlib', 'cbor', 'uri'], 15n],
] as [EncodeAsParameter, bigint][])(
'should return the correct bitwise value supportedContentTypes %p (%p)',
(encodedAs, expected) => {
const result = generateSupportedContentTypes(encodedAs)
expect(result).toEqual(expected)
},
)
})
23 changes: 23 additions & 0 deletions packages/ensjs/src/utils/generateSupportedContentTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import type { EncodeAbiParameters } from './encoders/encodeAbi.js'

type AbiEncodeAs = EncodeAbiParameters['encodeAs']

const abiEncodeMap: { [key in AbiEncodeAs]: bigint } = {
json: 1n,
zlib: 2n,
cbor: 4n,
uri: 8n,
} as const

export const generateSupportedContentTypes = (
encodeAsItemOrList: AbiEncodeAs | AbiEncodeAs[],
): bigint => {
const encodeAsList = Array.isArray(encodeAsItemOrList)
? encodeAsItemOrList
: [encodeAsItemOrList]
return encodeAsList.reduce<bigint>((result, encodeAs) => {
const contentType = abiEncodeMap[encodeAs]
if (contentType) result |= contentType
return result
}, 0n)
}
Loading