Skip to content

Commit

Permalink
Merge pull request #502 from rabbitholegg/matthew/boost-4411-update-z…
Browse files Browse the repository at this point in the history
…ora-plugin

feat(zora): update plugin to be compatible with zora protocol upgrade [BOOST-4411]
  • Loading branch information
mmackz authored Aug 8, 2024
2 parents 72f923d + 4903e4e commit f07a3a7
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 58 deletions.
5 changes: 5 additions & 0 deletions .changeset/silver-ties-buy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@rabbitholegg/questdk-plugin-zora": minor
---

update for zora protocol upgrade
19 changes: 18 additions & 1 deletion packages/zora/src/Zora.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import {
EXPECTED_ENCODED_DATA_721,
EXPECTED_ENCODED_DATA_1155,
MINT_V2_ZORA,
} from './test-transactions'
import {
ActionType,
Expand All @@ -31,6 +32,7 @@ import { describe, expect, test, vi, beforeEach, MockedFunction } from 'vitest'
import { PremintResponse } from './types'
import axios from 'axios'
import { validatePremint } from './validate'
import { ZORA_TIMED_SALE_STRATEGY } from './contract-addresses'

const MockedPremintResponse: PremintResponse = [
{
Expand Down Expand Up @@ -93,10 +95,12 @@ describe('Given the zora plugin', () => {
describe('When handling the mint action', () => {
describe('should return a valid action filter', () => {
test('when making a valid mint action', async () => {
const filter = await mint({
const mintFilter = await mint({
chainId: 1,
contractAddress: '0xDeaDbeefdEAdbeefdEadbEEFdeadbeEFdEaDbeeF',
})
/// @ts-expect-error ///
const filter = mintFilter.$or.at(0)
expect(filter).toBeTypeOf('object')
expect(Number(filter.chainId)).toBe(1)
if (typeof filter.to === 'string') {
Expand Down Expand Up @@ -579,3 +583,16 @@ describe('getExternalUrl function', () => {
expect(result).toBe('https://zora.co/create')
})
})

describe('simulateMint function', () => {
test('should simulate a 1155 mint when tokenId is not 0', async () => {
const mint = MINT_V2_ZORA.params as MintIntentParams
const value = parseEther('0.000111')
const account = '0xf70da97812CB96acDF810712Aa562db8dfA3dbEF'

const result = await simulateMint(mint, value, account)
const request = result.request
expect(request.address).toBe(ZORA_TIMED_SALE_STRATEGY)
expect(request.value).toBe(value)
})
})
161 changes: 104 additions & 57 deletions packages/zora/src/Zora.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import {
CREATE_CONTRACT_ABI,
FUNCTION_SELECTORS,
UNIVERSAL_MINTER_ABI,
ZORA_MINTER_ABI_721,
ZORA_MINTER_ABI_1155,
ZORA_MINTER_ABI_1155_LEGACY,
V2_MINT_ABI,
} from './abi'
import { CHAIN_ID_ARRAY, CHAIN_ID_TO_ZORA_SLUG } from './chain-ids'
import {
FIXED_PRICE_SALE_STRATS,
ZORA_1155_FACTORY,
ZORA_TIMED_SALE_STRATEGY,
} from './contract-addresses'
import { AndArrayItem } from './types'
import { checkBytecode, getNextTokenId } from './utils'
import { validatePremint } from './validate'
import {
type MintActionParams,
Expand Down Expand Up @@ -44,14 +46,10 @@ import {
type TransactionRequest,
createPublicClient,
encodeFunctionData,
fromHex,
getAddress,
http,
keccak256,
pad,
parseEther,
stringToBytes,
toHex,
} from 'viem'

export const validate = async (
Expand Down Expand Up @@ -94,12 +92,9 @@ export const create = async (
})
}

export const mint = async (
mint: MintActionParams,
): Promise<TransactionFilter> => {
const v1MintFilter = (mint: MintActionParams) => {
const { chainId, contractAddress, tokenId, amount, recipient, referral } =
mint

const universalMinter =
zoraUniversalMinterAddress[
chainId as keyof typeof zoraUniversalMinterAddress
Expand Down Expand Up @@ -182,10 +177,39 @@ export const mint = async (
$or: [ERC721_FILTER_ABSTRACT, ERC1155_FILTER_ABSTRACT],
},
}
return compressJson({
return {
chainId,
to: getExitAddresses(chainId, mintContracts), // We need this top level so the backend knows what contracts this applies to
$or: [DIRECT_MINT_FILTER, UNIVERSAL_MINT_FILTER],
}
}

const v2MintFilter = (mint: MintActionParams) => {
const { chainId, contractAddress, tokenId, amount, recipient, referral } =
mint

return {
chainId,
to: getExitAddresses(chainId, ZORA_TIMED_SALE_STRATEGY),
input: {
$abi: V2_MINT_ABI,
mintTo: recipient,
quantity: formatAmountToFilterOperator(amount),
collection: contractAddress,
tokenId,
mintReferral: referral,
},
}
}

export const mint = async (
mint: MintActionParams,
): Promise<TransactionFilter> => {
const v1Filter = v1MintFilter(mint)
const v2Filter = v2MintFilter(mint)

return compressJson({
$or: [v1Filter, v2Filter],
})
}

Expand Down Expand Up @@ -238,56 +262,17 @@ export const getMintIntent = async (
}
}

export const simulateMint = async (
export const simulateV1Mint = async (
mint: MintIntentParams,
value: bigint,
client: PublicClient,
account?: Address,
client?: PublicClient,
): Promise<SimulateContractReturnType> => {
const { chainId, contractAddress, tokenId, amount, recipient, referral } =
mint
const _client =
client ??
createPublicClient({
chain: chainIdToViemChain(chainId),
transport: http(),
})
const from = account ?? DEFAULT_ACCOUNT
let _tokenId = tokenId
if (tokenId === null || tokenId === undefined) {
const nextTokenId = (await _client.readContract({
address: contractAddress,
abi: ZORA_MINTER_ABI_1155,
functionName: 'nextTokenId',
})) as bigint

_tokenId = Number(nextTokenId) - 1
}

// find implementation address for EIP-1967 proxy
const slot = keccak256(stringToBytes('eip1967.proxy.implementation'))
const slotValue = toHex(fromHex(slot, 'bigint') - 1n)
const slotForImplementation = pad(slotValue, { size: 32 })
const implementationAddressRaw = await _client.getStorageAt({
address: contractAddress,
slot: slotForImplementation,
})
const implementationAddress: Address = `0x${implementationAddressRaw?.slice(
-40,
)}`

// Check if the implementation contracts bytecode contains valid function selectors
const bytecode = await _client.getBytecode({ address: implementationAddress })

const containsSelector = FUNCTION_SELECTORS.some((selector) =>
bytecode?.includes(selector),
)

if (!containsSelector) {
throw new Error(
'None of the specified function selectors are present in the contract bytecode.',
)
}
const _tokenId = await getNextTokenId(client, contractAddress, tokenId)
await checkBytecode(client, contractAddress)

let fixedPriceSaleStratAddress = FIXED_PRICE_SALE_STRATS[chainId]

Expand All @@ -307,7 +292,7 @@ export const simulateMint = async (
[referral ?? ZORA_DEPLOYER_ADDRESS],
pad(recipient),
]
const result = await _client.simulateContract({
const result = await client.simulateContract({
address: contractAddress,
value,
abi: ZORA_MINTER_ABI_1155,
Expand All @@ -330,7 +315,7 @@ export const simulateMint = async (
pad(recipient),
]
try {
const result = await _client.simulateContract({
const result = await client.simulateContract({
address: contractAddress,
value,
abi: ZORA_MINTER_ABI_1155_LEGACY,
Expand All @@ -347,7 +332,7 @@ export const simulateMint = async (
return result
} catch {
// Assume it's a 721 mint
const result = await _client.simulateContract({
const result = await client.simulateContract({
address: contractAddress,
value,
abi: ZORA_MINTER_ABI_721,
Expand All @@ -366,6 +351,67 @@ export const simulateMint = async (
}
}

export const simulateV2Mint = async (
mint: MintIntentParams,
value: bigint,
client: PublicClient,
account?: Address,
): Promise<SimulateContractReturnType> => {
const { contractAddress, tokenId, amount, recipient, referral } = mint

const from = account ?? DEFAULT_ACCOUNT

const _tokenId = await getNextTokenId(client, contractAddress, tokenId)

await checkBytecode(client, contractAddress)

const mintArgs = [
recipient ?? from,
amount,
contractAddress,
_tokenId,
referral ?? ZORA_DEPLOYER_ADDRESS,
'',
]

const result = await client.simulateContract({
address: ZORA_TIMED_SALE_STRATEGY,
value,
abi: V2_MINT_ABI,
functionName: 'mint',
args: mintArgs,
account: from,
stateOverride: [
{
address: from,
balance: parseEther('100'),
},
],
})
return result
}

export const simulateMint = async (
mint: MintIntentParams,
value: bigint,
account?: Address,
client?: PublicClient,
): Promise<SimulateContractReturnType> => {
const _client =
client ??
(createPublicClient({
chain: chainIdToViemChain(mint.chainId),
transport: http(),
}) as PublicClient)

try {
return await simulateV2Mint(mint, value, _client, account)
} catch (error) {
console.error('simulateV2Mint failed:', error)
return await simulateV1Mint(mint, value, _client, account)
}
}

const getSalesConfigAndTokenInfo = async (
chainId: number,
tokenAddress: Address,
Expand Down Expand Up @@ -409,7 +455,8 @@ export const getFees = async (
return { actionFee: fee.tokenPurchaseCost, projectFee: fee.mintFee }
} catch (err) {
console.error(err)
return { actionFee: parseEther('0'), projectFee: parseEther('0.000777') } // https://github.com/ourzora/zora-protocol/blob/e9fb5072112b4434cc649c95729f4bd8c6d5e0d0/packages/protocol-sdk/src/apis/chain-constants.ts#L27
// ! temp change to base fee of 0.000111 until we update for fee calulation
return { actionFee: parseEther('0'), projectFee: parseEther('0.000111') } // https://github.com/ourzora/zora-protocol/blob/e9fb5072112b4434cc649c95729f4bd8c6d5e0d0/packages/protocol-sdk/src/apis/chain-constants.ts#L27
}
}

Expand Down
41 changes: 41 additions & 0 deletions packages/zora/src/abi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,47 @@ export const UNIVERSAL_MINTER_ABI = [
},
] // universal batch mint

export const V2_MINT_ABI = [
{
inputs: [
{
internalType: 'address',
name: 'mintTo',
type: 'address',
},
{
internalType: 'uint256',
name: 'quantity',
type: 'uint256',
},
{
internalType: 'address',
name: 'collection',
type: 'address',
},
{
internalType: 'uint256',
name: 'tokenId',
type: 'uint256',
},
{
internalType: 'address',
name: 'mintReferral',
type: 'address',
},
{
internalType: 'string',
name: 'comment',
type: 'string',
},
],
name: 'mint',
outputs: [],
stateMutability: 'payable',
type: 'function',
},
]

export const CREATE_CONTRACT_ABI = [
{
inputs: [
Expand Down
2 changes: 2 additions & 0 deletions packages/zora/src/contract-addresses.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@ export const FIXED_PRICE_SALE_STRATS: { [chainId: number]: Address } = {
}

export const ZORA_1155_FACTORY = '0x777777c338d93e2c7adf08d102d45ca7cc4ed021'
export const ZORA_TIMED_SALE_STRATEGY =
'0x777777722d078c97c6ad07d9f36801e653e356ae'
4 changes: 4 additions & 0 deletions packages/zora/src/test-setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import {
LAYER_ZERO_MINT,
MINT,
MINT_BATCH_WITHOUT_FEES,
MINT_V2_BASE,
MINT_V2_ZORA,
MINT_WITH_REWARDS,
MINT_WITH_REWARDS_1155,
ZERO_QUANTITY,
Expand All @@ -18,6 +20,8 @@ export const passingTestCasesMint = [
createTestCase(MINT_BATCH_WITHOUT_FEES, 'When using the batch mint function'),
createTestCase(BATCH_MINT_ARB, 'when using batch mint function on arbitrum'),
createTestCase(MINT, 'when using mint function'),
createTestCase(MINT_V2_BASE, 'when using mint function on base'),
createTestCase(MINT_V2_ZORA, 'when using mint function on zora'),
createTestCase(MINT_WITH_REWARDS, 'when contractAddress is checksummed', {
contractAddress: getAddress(MINT_WITH_REWARDS.params.contractAddress),
}),
Expand Down
Loading

0 comments on commit f07a3a7

Please sign in to comment.