Skip to content

Commit

Permalink
Merge pull request #165 from ensdomains/set-multiple-abi-records-with…
Browse files Browse the repository at this point in the history
…-multi-call

add set multiple abis in setRecords + get abi by content type
  • Loading branch information
storywithoutend authored Jan 31, 2024
2 parents d223a5b + 38aff57 commit db5f0cb
Show file tree
Hide file tree
Showing 11 changed files with 212 additions and 36 deletions.
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
2 changes: 1 addition & 1 deletion packages/ensjs/src/utils/encoders/encodeAbi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { bytesToHex, stringToHex, type Hex } from 'viem'
import { UnknownContentTypeError } from '../../errors/utils.js'
import type { Prettify } from '../../types.js'

type AbiEncodeAs = 'json' | 'zlib' | 'cbor' | 'uri'
export type AbiEncodeAs = 'json' | 'zlib' | 'cbor' | 'uri'

type AbiContentType = 1 | 2 | 4 | 8

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)
},
)
})
21 changes: 21 additions & 0 deletions packages/ensjs/src/utils/generateSupportedContentTypes.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { AbiEncodeAs } from './encoders/encodeAbi.js'

const abiEncodeAsMap: { [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 = abiEncodeAsMap[encodeAs]
if (contentType) result |= contentType
return result
}, 0n)
}
1 change: 1 addition & 0 deletions packages/ensjs/src/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,3 +133,4 @@ export {
expiryToBigInt,
wrappedLabelLengthCheck,
} from './wrapper.js'
export { generateSupportedContentTypes } from './generateSupportedContentTypes.js'

0 comments on commit db5f0cb

Please sign in to comment.