Skip to content

Commit

Permalink
feat: linked wearables v2 (#301)
Browse files Browse the repository at this point in the history
  • Loading branch information
marianogoldman authored Jul 12, 2024
1 parent 8bda488 commit c8267a3
Show file tree
Hide file tree
Showing 16 changed files with 406 additions and 88 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"build:watch": "tsc -p tsconfig.json --watch",
"lint:check": "eslint '**/*.{js,ts}'",
"lint:fix": "eslint '**/*.{js,ts}' --fix",
"test": "jest --runInBand --detectOpenHandles --colors --coverage",
"test": "jest --runInBand --detectOpenHandles --colors --coverage --verbose",
"test:watch": "jest --runInBand --detectOpenHandles --colors --watch"
},
"repository": {
Expand Down Expand Up @@ -42,8 +42,8 @@
"@dcl/block-indexer": "^1.1.1",
"@dcl/content-hash-tree": "^1.1.4",
"@dcl/hashing": "^3.0.1",
"@dcl/schemas": "^11.5.0",
"@dcl/urn-resolver": "^3.4.0",
"@dcl/schemas": "^11.12.0",
"@dcl/urn-resolver": "^3.5.0",
"@well-known-components/interfaces": "^1.3.0",
"@well-known-components/thegraph-component": "^1.5.0",
"ms": "^2.1.3",
Expand Down
9 changes: 9 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { BlockSearch } from '@dcl/block-indexer'
import { AuthChain, Entity, EthAddress } from '@dcl/schemas'
import {
BlockchainCollectionLinkedWearablesAsset,
BlockchainCollectionThirdParty,
BlockchainCollectionV1Asset,
BlockchainCollectionV2Asset
Expand Down Expand Up @@ -234,3 +235,11 @@ export type ThirdPartyAssetValidateFn = (
asset: BlockchainCollectionThirdParty,
deployment: DeploymentToValidate
) => Promise<ValidationResponse>

/**
* @internal
*/
export type LinkedWearableAssetValidateFn = (
asset: BlockchainCollectionLinkedWearablesAsset,
deployment: DeploymentToValidate
) => Promise<ValidationResponse>
12 changes: 9 additions & 3 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { BlockchainCollectionThirdParty, parseUrn } from '@dcl/urn-resolver'
import { BlockchainCollectionLinkedWearablesAsset, BlockchainCollectionThirdParty, parseUrn } from '@dcl/urn-resolver'
import { L1_NETWORKS, L2_NETWORKS } from './types'

type URNsByNetwork = {
Expand Down Expand Up @@ -41,8 +41,14 @@ export async function splitItemsURNsByNetwork(urnsToSplit: string[]): Promise<UR
}
}

export function getThirdPartyId(urn: BlockchainCollectionThirdParty): string {
return `urn:decentraland:${urn.network}:collections-thirdparty:${urn.thirdPartyName}`
export function getThirdPartyId(
asset: BlockchainCollectionThirdParty | BlockchainCollectionLinkedWearablesAsset
): string {
if (asset.type === 'blockchain-collection-third-party') {
return `urn:decentraland:${asset.network}:collections-thirdparty:${asset.thirdPartyName}`
} else {
return `urn:decentraland:${asset.network}:collections-linked-wearables:${asset.linkedWearableProvider}`
}
}

export function toHexBuffer(value: string): Buffer {
Expand Down
58 changes: 39 additions & 19 deletions src/validations/access/common/items.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import {
BlockchainCollectionLinkedWearablesAsset,
BlockchainCollectionThirdParty,
BlockchainCollectionV1Asset,
BlockchainCollectionV2Asset,
OffChainAsset,
parseUrn
} from '@dcl/urn-resolver'
import { ThirdPartyAssetValidateFn, V1andV2collectionAssetValidateFn } from '../../..'
import { LinkedWearableAssetValidateFn, ThirdPartyAssetValidateFn, V1andV2collectionAssetValidateFn } from '../../..'
import {
ContentValidatorComponents,
DeploymentToValidate,
Expand All @@ -17,17 +18,14 @@ import {

export type ItemValidateFnComponents = Pick<ContentValidatorComponents, 'externalCalls'>

export type UrnType =
| 'off-chain'
| 'blockchain-collection-v1-asset'
| 'blockchain-collection-v2-asset'
| 'blockchain-collection-third-party'
export type UrnType = SupportedAsset['type']

export type SupportedAsset =
| BlockchainCollectionV1Asset
| BlockchainCollectionV2Asset
| OffChainAsset
| BlockchainCollectionThirdParty
| BlockchainCollectionLinkedWearablesAsset

function alreadySeen(resolvedPointers: SupportedAsset[], parsed: SupportedAsset): boolean {
return resolvedPointers.some((alreadyResolved) => resolveSameUrn(alreadyResolved, parsed))
Expand Down Expand Up @@ -59,39 +57,59 @@ async function parseUrnNoFail(urn: string): Promise<SupportedAsset | null> {
if (parsed?.type === 'blockchain-collection-third-party') {
return parsed
}
if (parsed?.type === 'blockchain-collection-linked-wearables-asset') {
return parsed
}
} catch {}
return null
}

export function createWearableValidateFn(
components: ItemValidateFnComponents,
v1andV2collectionAssetValidateFn: V1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn: ThirdPartyAssetValidateFn
thirdPartyAssetValidateFn: ThirdPartyAssetValidateFn,
linkedWearableItemValidateFn: LinkedWearableAssetValidateFn
): ValidateFn {
return createItemValidateFn(components, v1andV2collectionAssetValidateFn, thirdPartyAssetValidateFn, [
'off-chain',
'blockchain-collection-v1-asset',
'blockchain-collection-v2-asset',
'blockchain-collection-third-party'
])
return createItemValidateFn(
components,
v1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn,
linkedWearableItemValidateFn,
[
'off-chain',
'blockchain-collection-v1-asset',
'blockchain-collection-v2-asset',
'blockchain-collection-third-party',
'blockchain-collection-linked-wearables-asset'
]
)
}

export function createEmoteValidateFn(
components: ItemValidateFnComponents,
v1andV2collectionAssetValidateFn: V1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn: ThirdPartyAssetValidateFn
thirdPartyAssetValidateFn: ThirdPartyAssetValidateFn,
linkedWearableItemValidateFn: LinkedWearableAssetValidateFn
): ValidateFn {
return createItemValidateFn(components, v1andV2collectionAssetValidateFn, thirdPartyAssetValidateFn, [
'off-chain',
'blockchain-collection-v2-asset',
'blockchain-collection-third-party'
])
return createItemValidateFn(
components,
v1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn,
linkedWearableItemValidateFn,
[
'off-chain',
'blockchain-collection-v2-asset',
'blockchain-collection-third-party',
'blockchain-collection-linked-wearables-asset'
]
)
}

export function createItemValidateFn(
{ externalCalls }: ItemValidateFnComponents,
v1andV2collectionAssetValidateFn: V1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn: ThirdPartyAssetValidateFn,
linkedWearableAssetValidateFn: LinkedWearableAssetValidateFn,
validUrnTypesForItem: UrnType[]
): ValidateFn {
return async function validateFn(deployment: DeploymentToValidate): Promise<ValidationResponse> {
Expand Down Expand Up @@ -138,6 +156,8 @@ export function createItemValidateFn(
return v1andV2collectionAssetValidateFn(parsedAsset, deployment)
} else if (parsedAsset.type === 'blockchain-collection-third-party') {
return thirdPartyAssetValidateFn(parsedAsset, deployment)
} else if (parsedAsset.type === 'blockchain-collection-linked-wearables-asset') {
return linkedWearableAssetValidateFn(parsedAsset, deployment)
} else {
throw new Error('This should never happen. There is no validations for the asset.')
}
Expand Down
13 changes: 10 additions & 3 deletions src/validations/access/on-chain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,30 @@ import { createV1andV2collectionAssetValidateFn } from './collection-asset'
import { createOutfitsValidateFn } from './outfits'
import { createProfileValidateFn } from './profiles'
import { createSceneValidateFn } from './scenes'
import { createThirdPartyAssetValidateFn } from './third-party-asset'
import { createLinkedWearableItemValidateFn, createThirdPartyAssetValidateFn } from './third-party-asset'

export function createOnChainAccessCheckValidateFns(
components: OnChainAccessCheckerComponents
): Record<EntityType, ValidateFn> {
const v1andV2collectionAssetValidateFn = createV1andV2collectionAssetValidateFn(components)
const thirdPartyAssetValidateFn = createThirdPartyAssetValidateFn(components)
const linkedWearableItemValidateFn = createLinkedWearableItemValidateFn(components)
return {
[EntityType.PROFILE]: createProfileValidateFn(components),
[EntityType.SCENE]: createSceneValidateFn(components),
[EntityType.WEARABLE]: createWearableValidateFn(
components,
v1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn
thirdPartyAssetValidateFn,
linkedWearableItemValidateFn
),
[EntityType.STORE]: createStoreValidateFn(components),
[EntityType.EMOTE]: createEmoteValidateFn(components, v1andV2collectionAssetValidateFn, thirdPartyAssetValidateFn),
[EntityType.EMOTE]: createEmoteValidateFn(
components,
v1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn,
linkedWearableItemValidateFn
),
[EntityType.OUTFITS]: createOutfitsValidateFn(components)
}
}
90 changes: 57 additions & 33 deletions src/validations/access/on-chain/third-party-asset.ts
Original file line number Diff line number Diff line change
@@ -1,58 +1,82 @@
import { generateRoot } from '@dcl/content-hash-tree'
import { isThirdParty, ThirdPartyProps } from '@dcl/schemas'
import { BlockchainCollectionThirdParty } from '@dcl/urn-resolver'
import { BlockchainCollectionLinkedWearablesAsset, BlockchainCollectionThirdParty } from '@dcl/urn-resolver'
import {
DeploymentToValidate,
LinkedWearableAssetValidateFn,
OK,
OnChainAccessCheckerComponents,
ThirdPartyAssetValidateFn,
validationFailed
} from '../../../types'
import { ILoggerComponent } from '@well-known-components/interfaces'
import { getThirdPartyId, toHexBuffer } from '../../../utils'

export function createThirdPartyAssetValidateFn(
components: Pick<OnChainAccessCheckerComponents, 'externalCalls' | 'logs' | 'client' | 'L2'>
): ThirdPartyAssetValidateFn {
return async function validateFn(asset: BlockchainCollectionThirdParty, deployment: DeploymentToValidate) {
const logger = components.logs.getLogger('third-party-asset-validation')
const { checker } = components.L2
async function verifyMerkleProofedEntity(
components: Pick<OnChainAccessCheckerComponents, 'externalCalls' | 'logs' | 'client' | 'L2'>,
asset: BlockchainCollectionThirdParty | BlockchainCollectionLinkedWearablesAsset,
deployment: DeploymentToValidate,
logger: ILoggerComponent.ILogger
): Promise<boolean> {
const { timestamp } = deployment.entity
let verified = false

const { timestamp } = deployment.entity
let verified = false
if (isThirdParty(deployment.entity.metadata)) {
const metadata = deployment.entity.metadata as ThirdPartyProps
const merkleProof = metadata.merkleProof

if (isThirdParty(deployment.entity.metadata)) {
// This should always happen as the metadata validation ran before
const metadata = deployment.entity.metadata as ThirdPartyProps
const merkleProof = metadata.merkleProof
const thirdPartyId = getThirdPartyId(asset)

const thirdPartyId = getThirdPartyId(asset)
const bufferedProofs = merkleProof.proof.map((value) => toHexBuffer(value))
const root = generateRoot(merkleProof.index, merkleProof.entityHash, bufferedProofs)

const bufferedProofs = merkleProof.proof.map((value) => toHexBuffer(value))
const root = generateRoot(merkleProof.index, merkleProof.entityHash, bufferedProofs)
const { blockNumberAtDeployment, blockNumberFiveMinBeforeDeployment } =
await components.client.findBlocksForTimestamp(timestamp, components.L2.blockSearch)

const { blockNumberAtDeployment, blockNumberFiveMinBeforeDeployment } =
await components.client.findBlocksForTimestamp(timestamp, components.L2.blockSearch)

const validateThirdParty = async (block: number) => {
try {
return await checker.validateThirdParty(thirdPartyId, root, block)
} catch (err: any) {
logger.warn(err)
return false
}
const validateThirdParty = async (block: number) => {
try {
return await components.L2.checker.validateThirdParty(thirdPartyId, root, block)
} catch (err: any) {
logger.warn(err)
return false
}
}

if (blockNumberAtDeployment) {
verified = await validateThirdParty(blockNumberAtDeployment)
}
if (blockNumberAtDeployment) {
verified = await validateThirdParty(blockNumberAtDeployment)
}

if (!verified && blockNumberFiveMinBeforeDeployment) {
verified = await validateThirdParty(blockNumberFiveMinBeforeDeployment)
}
if (!verified && blockNumberFiveMinBeforeDeployment) {
verified = await validateThirdParty(blockNumberFiveMinBeforeDeployment)
}
}

return verified
}

export function createThirdPartyAssetValidateFn(
components: Pick<OnChainAccessCheckerComponents, 'externalCalls' | 'logs' | 'client' | 'L2'>
): ThirdPartyAssetValidateFn {
return async function validateFn(asset: BlockchainCollectionThirdParty, deployment: DeploymentToValidate) {
const logger = components.logs.getLogger('(on-chain) third-party-asset-validation')

const verified = await verifyMerkleProofedEntity(components, asset, deployment, logger)
if (!verified) {
return validationFailed(`Couldn't verify merkle proofed entity for third-party wearable`)
}
return OK
}
}

export function createLinkedWearableItemValidateFn(
components: Pick<OnChainAccessCheckerComponents, 'externalCalls' | 'logs' | 'client' | 'L2'>
): LinkedWearableAssetValidateFn {
return async function validateFn(asset: BlockchainCollectionLinkedWearablesAsset, deployment: DeploymentToValidate) {
const logger = components.logs.getLogger('(on-chain) third-party-asset-validation')

const verified = await verifyMerkleProofedEntity(components, asset, deployment, logger)
if (!verified) {
return validationFailed(`Couldn't verify merkle proofed entity`)
return validationFailed(`Couldn't verify merkle proofed entity for linked wearable v2`)
}
return OK
}
Expand Down
13 changes: 10 additions & 3 deletions src/validations/access/subgraph/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,30 @@ import { createV1andV2collectionAssetValidateFn } from './collection-asset'
import { createOutfitsValidateFn } from './outfits'
import { createProfileValidateFn } from './profiles'
import { createSceneValidateFn } from './scenes'
import { createThirdPartyAssetValidateFn } from './third-party-asset'
import { createLinkedWearableItemValidateFn, createThirdPartyAssetValidateFn } from './third-party-asset'

export function createSubgraphAccessCheckValidateFns(
components: SubgraphAccessCheckerComponents
): Record<EntityType, ValidateFn> {
const v1andV2collectionAssetValidateFn = createV1andV2collectionAssetValidateFn(components)
const thirdPartyAssetValidateFn = createThirdPartyAssetValidateFn(components)
const linkedWearableAssetValidateFn = createLinkedWearableItemValidateFn(components)
return {
[EntityType.PROFILE]: createProfileValidateFn(components),
[EntityType.SCENE]: createSceneValidateFn(components),
[EntityType.WEARABLE]: createWearableValidateFn(
components,
v1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn
thirdPartyAssetValidateFn,
linkedWearableAssetValidateFn
),
[EntityType.STORE]: createStoreValidateFn(components),
[EntityType.EMOTE]: createEmoteValidateFn(components, v1andV2collectionAssetValidateFn, thirdPartyAssetValidateFn),
[EntityType.EMOTE]: createEmoteValidateFn(
components,
v1andV2collectionAssetValidateFn,
thirdPartyAssetValidateFn,
linkedWearableAssetValidateFn
),
[EntityType.OUTFITS]: createOutfitsValidateFn(components)
}
}
Loading

0 comments on commit c8267a3

Please sign in to comment.