Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
17 changes: 17 additions & 0 deletions src/mappings/poolManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,23 @@ export function handleInitializeHelper(
token0.whitelistPools = newPools
}

// if pool is a zora content token, add it to the whitelist pools of the content token
// assumes content token pool is always the first pool created for a content token
if (event.params.hooks.toHexString() == '0x9ea932730a7787000042e34390b8e435dd839040') {
let contentToken: Token | null = null
if (token0.whitelistPools.length == 0 && token1.whitelistPools.length > 0) {
contentToken = token0
}
if (token1.whitelistPools.length == 0 && token0.whitelistPools.length > 0) {
contentToken = token1
}
if (contentToken) {
const newPools = contentToken.whitelistPools
newPools.push(pool.id)
contentToken.whitelistPools = newPools
}
}

pool.token0 = token0.id
pool.token1 = token1.id
pool.feeTier = BigInt.fromI32(event.params.fee)
Expand Down
34 changes: 28 additions & 6 deletions src/mappings/swap.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,24 +47,46 @@ export function handleSwapHelper(event: SwapEvent, subgraphConfig: SubgraphConfi
const token1 = Token.load(pool.token1)

if (token0 && token1) {
// Backfill emppty tokens' whitelist pools to account for grafting to a point later than the block where the pool was initialized
if (token0.whitelistPools === null) {
if (whitelistTokens.includes(token1.id)) {
// backfill zora pools which have non-null whitelisted pools
// Creator: 0xd61a675f8a0c67a73dc3b54fb7318b4d91409040
// Content: 0x9ea932730a7787000042e34390b8e435dd839040

// find the content token
// add the pool to the content token's whitelist pool
// but not add it to the creator token's whitelist pool
// because the creator token would get a huge amount of whitelisted pools and slow things down
if (pool.hooks == '0xd61a675f8a0c67a73dc3b54fb7318b4d91409040') {
// Backfill emppty tokens' whitelist pools to account for grafting to a point later than the block where the pool was initialized
if (whitelistTokens.includes(token1.id) && !token0.whitelistPools.includes(pool.id)) {
const newPools = token0.whitelistPools
newPools.push(pool.id)
token0.whitelistPools = newPools
}
}

if (token1.whitelistPools === null) {
// update white listed pools
if (whitelistTokens.includes(token0.id)) {
if (whitelistTokens.includes(token0.id) && !token1.whitelistPools.includes(pool.id)) {
const newPools = token1.whitelistPools
newPools.push(pool.id)
token1.whitelistPools = newPools
}
}

// backfill whitelisted pools for content tokens
if (pool.hooks == '0x9ea932730a7787000042e34390b8e435dd839040') {
let contentToken: Token | null = null
if (token0.whitelistPools.length === 0 && token1.whitelistPools.length > 0) {
contentToken = token0
}
if (token1.whitelistPools.length === 0 && token0.whitelistPools.length > 0) {
contentToken = token1
}
if (contentToken) {
const newPools = contentToken.whitelistPools
newPools.push(pool.id)
contentToken.whitelistPools = newPools
}
}

// amounts - 0/1 are token deltas: can be positive or negative
// Unlike V3, a negative amount represents that amount is being sent to the pool and vice versa, so invert the sign
const amount0 = convertTokenToDecimal(event.params.amount0, token0.decimals).times(BigDecimal.fromString('-1'))
Expand Down
227 changes: 226 additions & 1 deletion tests/handlers/handleInitialize.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { assert, beforeEach, clearStore, describe, test } from 'matchstick-as'

import { handleInitializeHelper } from '../../src/mappings/poolManager'
import { Initialize } from '../../src/types/PoolManager/PoolManager'
import { Bundle, Pool } from '../../src/types/schema'
import { Bundle, Pool, Token } from '../../src/types/schema'
import { ADDRESS_ZERO } from '../../src/utils/constants'
import { safeDiv } from '../../src/utils/index'
import { findNativePerToken, getNativePriceInUSD, sqrtPriceX96ToTokenPrices } from '../../src/utils/pricing'
Expand Down Expand Up @@ -258,3 +258,228 @@ describe('findNativePerToken', () => {
assert.assertTrue(ethPerToken == BigDecimal.fromString('0'))
})
})

describe('handleInitialize - Zora Content Token Logic', () => {
// Zora content token test constants
const ZORA_CREATOR_TOKEN_ADDRESS = '0x1234567890123456789012345678901234567890'
const ZORA_CONTENT_TOKEN_ADDRESS = '0x0987654321098765432109876543210987654321'
const ZORA_CONTENT_HOOK = '0x9ea932730a7787000042e34390b8e435dd839040'
const ZORA_POOL_ID = '0x5555555555555555555555555555555555555555555555555555555555555555'

beforeEach(() => {
clearStore()

const bundle = new Bundle('1')
bundle.ethPriceUSD = TEST_ETH_PRICE_USD
bundle.save()
})

test('content token gets whitelisted when Zora pool is initialized', () => {
// Create creator token (has existing whitelist pools)
const creatorToken = createAndStoreTestToken({
address: ZORA_CREATOR_TOKEN_ADDRESS,
symbol: 'CREATOR',
name: 'Creator Token',
totalSupply: '1000000',
decimals: '18',
balanceOf: '1000',
})
creatorToken.whitelistPools = [USDC_WETH_POOL_ID]
creatorToken.save()

// Create content token (no whitelist pools initially)
const contentToken = createAndStoreTestToken({
address: ZORA_CONTENT_TOKEN_ADDRESS,
symbol: 'CONTENT',
name: 'Content Token',
totalSupply: '1000000',
decimals: '18',
balanceOf: '1000',
})
contentToken.whitelistPools = []
contentToken.save()

// Create initialize event for Zora content token pool
const zoraInitializeEvent = new Initialize(
MOCK_EVENT.address,
MOCK_EVENT.logIndex,
MOCK_EVENT.transactionLogIndex,
MOCK_EVENT.logType,
MOCK_EVENT.block,
MOCK_EVENT.transaction,
[
new ethereum.EventParam('id', ethereum.Value.fromFixedBytes(Bytes.fromHexString(ZORA_POOL_ID))),
new ethereum.EventParam(
'currency0',
ethereum.Value.fromAddress(Address.fromString(ZORA_CONTENT_TOKEN_ADDRESS)),
),
new ethereum.EventParam(
'currency1',
ethereum.Value.fromAddress(Address.fromString(ZORA_CREATOR_TOKEN_ADDRESS)),
),
new ethereum.EventParam('fee', ethereum.Value.fromI32(500)),
new ethereum.EventParam('tickSpacing', ethereum.Value.fromI32(10)),
new ethereum.EventParam('hooks', ethereum.Value.fromAddress(Address.fromString(ZORA_CONTENT_HOOK))),
new ethereum.EventParam(
'sqrtPriceX96',
ethereum.Value.fromUnsignedBigInt(BigInt.fromString('79228162514264337593543950336')),
),
new ethereum.EventParam('tick', ethereum.Value.fromI32(0)),
],
MOCK_EVENT.receipt,
)

// Before initialization: content token should have no whitelist pools
const contentTokenBefore = Token.load(ZORA_CONTENT_TOKEN_ADDRESS)!
assert.assertTrue(contentTokenBefore.whitelistPools.length == 0)

// Execute initialization
handleInitializeHelper(zoraInitializeEvent, TEST_CONFIG)

// After initialization: content token should be whitelisted
const contentTokenAfter = Token.load(ZORA_CONTENT_TOKEN_ADDRESS)!
assert.assertTrue(contentTokenAfter.whitelistPools.length == 1)
// Use array includes helper to work around string comparison issues
assert.assertTrue(arrayIncludesString(contentTokenAfter.whitelistPools, ZORA_POOL_ID))

// Creator token whitelist should remain unchanged
const creatorTokenAfter = Token.load(ZORA_CREATOR_TOKEN_ADDRESS)!
assert.assertTrue(creatorTokenAfter.whitelistPools.length == 1)
assert.assertTrue(creatorTokenAfter.whitelistPools[0] == USDC_WETH_POOL_ID)
})

test('content token as token1 gets whitelisted when Zora pool is initialized', () => {
// Test reverse scenario where content token is token1, creator is token0
const CREATOR_TOKEN_1_ADDRESS = '0xbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb'
const CONTENT_TOKEN_1_ADDRESS = '0xcccccccccccccccccccccccccccccccccccccccc'
const ZORA_POOL_ID_1 = '0x6666666666666666666666666666666666666666666666666666666666666666'

// Create creator token
const creatorToken1 = createAndStoreTestToken({
address: CREATOR_TOKEN_1_ADDRESS,
symbol: 'CREATOR1',
name: 'Creator Token 1',
totalSupply: '1000000',
decimals: '18',
balanceOf: '1000',
})
creatorToken1.whitelistPools = [USDC_WETH_POOL_ID]
creatorToken1.save()

// Create content token
const contentToken1 = createAndStoreTestToken({
address: CONTENT_TOKEN_1_ADDRESS,
symbol: 'CONTENT1',
name: 'Content Token 1',
totalSupply: '1000000',
decimals: '18',
balanceOf: '1000',
})
contentToken1.whitelistPools = []
contentToken1.save()

// Create initialize event with creator as token0, content as token1
const zoraInitializeEvent1 = new Initialize(
MOCK_EVENT.address,
MOCK_EVENT.logIndex,
MOCK_EVENT.transactionLogIndex,
MOCK_EVENT.logType,
MOCK_EVENT.block,
MOCK_EVENT.transaction,
[
new ethereum.EventParam('id', ethereum.Value.fromFixedBytes(Bytes.fromHexString(ZORA_POOL_ID_1))),
new ethereum.EventParam('currency0', ethereum.Value.fromAddress(Address.fromString(CREATOR_TOKEN_1_ADDRESS))),
new ethereum.EventParam('currency1', ethereum.Value.fromAddress(Address.fromString(CONTENT_TOKEN_1_ADDRESS))),
new ethereum.EventParam('fee', ethereum.Value.fromI32(500)),
new ethereum.EventParam('tickSpacing', ethereum.Value.fromI32(10)),
new ethereum.EventParam('hooks', ethereum.Value.fromAddress(Address.fromString(ZORA_CONTENT_HOOK))),
new ethereum.EventParam(
'sqrtPriceX96',
ethereum.Value.fromUnsignedBigInt(BigInt.fromString('79228162514264337593543950336')),
),
new ethereum.EventParam('tick', ethereum.Value.fromI32(0)),
],
MOCK_EVENT.receipt,
)

// Execute initialization
handleInitializeHelper(zoraInitializeEvent1, TEST_CONFIG)

// Content token (token1) should be whitelisted
const contentToken1After = Token.load(CONTENT_TOKEN_1_ADDRESS)!
assert.assertTrue(contentToken1After.whitelistPools.length == 1)
assert.assertTrue(arrayIncludesString(contentToken1After.whitelistPools, ZORA_POOL_ID_1))
})

test('non-Zora pools do not affect content token whitelist during initialization', () => {
// Create tokens
const token0 = createAndStoreTestToken({
address: ZORA_CONTENT_TOKEN_ADDRESS,
symbol: 'CONTENT',
name: 'Content Token',
totalSupply: '1000000',
decimals: '18',
balanceOf: '1000',
})
token0.whitelistPools = []
token0.save()

const token1 = createAndStoreTestToken({
address: ZORA_CREATOR_TOKEN_ADDRESS,
symbol: 'CREATOR',
name: 'Creator Token',
totalSupply: '1000000',
decimals: '18',
balanceOf: '1000',
})
token1.whitelistPools = [USDC_WETH_POOL_ID]
token1.save()

// Create regular pool (no hook) with same tokens
const REGULAR_POOL_ID = '0x7777777777777777777777777777777777777777777777777777777777777777'
const regularInitializeEvent = new Initialize(
MOCK_EVENT.address,
MOCK_EVENT.logIndex,
MOCK_EVENT.transactionLogIndex,
MOCK_EVENT.logType,
MOCK_EVENT.block,
MOCK_EVENT.transaction,
[
new ethereum.EventParam('id', ethereum.Value.fromFixedBytes(Bytes.fromHexString(REGULAR_POOL_ID))),
new ethereum.EventParam(
'currency0',
ethereum.Value.fromAddress(Address.fromString(ZORA_CONTENT_TOKEN_ADDRESS)),
),
new ethereum.EventParam(
'currency1',
ethereum.Value.fromAddress(Address.fromString(ZORA_CREATOR_TOKEN_ADDRESS)),
),
new ethereum.EventParam('fee', ethereum.Value.fromI32(500)),
new ethereum.EventParam('tickSpacing', ethereum.Value.fromI32(10)),
new ethereum.EventParam('hooks', ethereum.Value.fromAddress(Address.fromString(ADDRESS_ZERO))), // No hook
new ethereum.EventParam(
'sqrtPriceX96',
ethereum.Value.fromUnsignedBigInt(BigInt.fromString('79228162514264337593543950336')),
),
new ethereum.EventParam('tick', ethereum.Value.fromI32(0)),
],
MOCK_EVENT.receipt,
)

// Execute initialization
handleInitializeHelper(regularInitializeEvent, TEST_CONFIG)

// Content token whitelist should remain empty (regular pool shouldn't be added)
const contentTokenAfter = Token.load(ZORA_CONTENT_TOKEN_ADDRESS)!
assert.assertTrue(contentTokenAfter.whitelistPools.length == 0)
})
})

function arrayIncludesString(array: string[], item: string): boolean {
for (let i = 0; i < array.length; i++) {
if (array[i] == item) {
return true
}
}
return false
}
Loading
Loading