Skip to content

Commit

Permalink
Merge pull request #223 from decentraland/feat/check-blockchain-owner
Browse files Browse the repository at this point in the history
feat: check blockchain owner before letting create a trade
  • Loading branch information
juanmahidalgo authored Dec 13, 2024
2 parents 0209ae2 + 27b801d commit 1a796e7
Show file tree
Hide file tree
Showing 3 changed files with 58 additions and 5 deletions.
45 changes: 43 additions & 2 deletions src/logic/trades/utils.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
/* eslint-disable @typescript-eslint/naming-convention */
import { TypedDataField, hexlify, TypedDataDomain, verifyTypedData, toBeArray, toUtf8Bytes, zeroPadValue } from 'ethers'
import { TradeAsset, TradeAssetType, TradeCreation } from '@dcl/schemas'
import {
Contract,
TypedDataField,
hexlify,
TypedDataDomain,
verifyTypedData,
toBeArray,
toUtf8Bytes,
zeroPadValue,
JsonRpcProvider
} from 'ethers'
import { ChainId, ERC721TradeAsset, Network, TradeAsset, TradeAssetType, TradeCreation } from '@dcl/schemas'
import { ContractData, ContractName, getContract } from 'decentraland-transactions'
import { InvalidECDSASignatureError, MarketplaceContractNotFound } from '../../ports/trades/errors'
import { fromMillisecondsToSeconds } from '../date'
Expand Down Expand Up @@ -109,3 +119,34 @@ export function validateTradeSignature(trade: TradeCreation, signer: string): bo

return verifyTypedData(domain, MARKETPLACE_TRADE_TYPES, values, trade.signature).toLowerCase() === signer
}

export function isERC721TradeAsset(asset: TradeAsset): asset is ERC721TradeAsset {
return (asset as ERC721TradeAsset).tokenId !== undefined
}

async function getContractOwner(contractAddress: string, tokenId: string, network: Network, chainId: ChainId): Promise<string> {
const abi = ['function ownerOf(uint256 tokenId) view returns (address)']
const RPC_URL = `https://rpc.decentraland.org/${
network === Network.ETHEREUM
? chainId === ChainId.ETHEREUM_MAINNET
? 'mainnet'
: 'sepolia'
: chainId === ChainId.MATIC_MAINNET
? 'polygon'
: 'amoy'
}`
const provider = new JsonRpcProvider(RPC_URL)
const contract = new Contract(contractAddress, abi, provider)
return await contract.ownerOf(tokenId)
}

export async function validateAssetOwnership(
asset: ERC721TradeAsset,
signer: string,
network: Network,
chainId: ChainId
): Promise<boolean> {
const { contractAddress, tokenId } = asset
const blockchainOwner = await getContractOwner(contractAddress, tokenId, network, chainId)
return blockchainOwner.toLowerCase() === signer.toLowerCase()
}
12 changes: 9 additions & 3 deletions src/ports/trades/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import SQL from 'sql-template-strings'
import { Event, TradeAssetDirection, TradeCreation } from '@dcl/schemas'
import { fromDbTradeAndDBTradeAssetWithValueListToTrade } from '../../adapters/trades/trades'
import { isErrorWithMessage } from '../../logic/errors'
import { validateTradeSignature } from '../../logic/trades/utils'
import { validateAssetOwnership, validateTradeSignature } from '../../logic/trades/utils'
import { AppComponents } from '../../types'
import {
InvalidTradeSignatureError,
Expand All @@ -12,7 +12,8 @@ import {
InvalidTradeSignerError,
TradeNotFoundError,
EventNotGeneratedError,
TradeNotFoundBySignatureError
TradeNotFoundBySignatureError,
InvalidOwnerError
} from './errors'
import {
getInsertTradeAssetQuery,
Expand All @@ -22,7 +23,7 @@ import {
getTradeAssetsWithValuesByIdQuery
} from './queries'
import { DBTrade, DBTradeAsset, DBTradeAssetValue, DBTradeAssetWithValue, ITradesComponent, TradeEvent } from './types'
import { getNotificationEventForTrade, validateTradeByType } from './utils'
import { getNotificationEventForTrade, isERC721TradeAsset, validateTradeByType } from './utils'

export function createTradesComponent(components: Pick<AppComponents, 'dappsDatabase' | 'eventPublisher' | 'logs'>): ITradesComponent {
const { dappsDatabase: pg, eventPublisher, logs } = components
Expand Down Expand Up @@ -58,6 +59,11 @@ export function createTradesComponent(components: Pick<AppComponents, 'dappsData
throw new InvalidTradeSignatureError()
}

// validate right ownership
if (isERC721TradeAsset(trade.sent[0]) && !(await validateAssetOwnership(trade.sent[0], signer, trade.network, trade.chainId))) {
throw new InvalidOwnerError()
}

const insertedTrade = await pg.withTransaction(
async client => {
const insertedTrade = await client.query<DBTrade>(getInsertTradeQuery(trade, signer))
Expand Down
6 changes: 6 additions & 0 deletions src/ports/trades/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ export class InvalidTradeSignatureError extends Error {
}
}

export class InvalidOwnerError extends Error {
constructor() {
super('Invalid owner')
}
}

export class MarketplaceContractNotFound extends Error {
constructor(public chainId: ChainId, public network: Network) {
super(`Contract not found for ${chainId} and ${network}`)
Expand Down

0 comments on commit 1a796e7

Please sign in to comment.