From 6979a23991b3b0e7982894feccdc8450128fc853 Mon Sep 17 00:00:00 2001 From: Andres Adjimann Date: Fri, 22 Sep 2023 18:55:40 -0300 Subject: [PATCH 1/3] test: matching orders --- .../contracts/lib-asset/LibAsset.sol | 3 + .../test/exchange/Exchange.test.ts | 311 ++++++++++-------- packages/marketplace/test/fixtures.ts | 10 +- packages/marketplace/test/utils/assets.ts | 170 ++++++++-- packages/marketplace/test/utils/order.ts | 220 +++++++++---- .../test/utils/raribleTestHelper.ts | 52 +++ packages/marketplace/test/utils/signature.ts | 46 +++ 7 files changed, 590 insertions(+), 222 deletions(-) create mode 100644 packages/marketplace/test/utils/raribleTestHelper.ts create mode 100644 packages/marketplace/test/utils/signature.ts diff --git a/packages/marketplace/contracts/lib-asset/LibAsset.sol b/packages/marketplace/contracts/lib-asset/LibAsset.sol index 92247684a5..5bab461947 100644 --- a/packages/marketplace/contracts/lib-asset/LibAsset.sol +++ b/packages/marketplace/contracts/lib-asset/LibAsset.sol @@ -11,8 +11,11 @@ library LibAsset { bytes4 public constant ERC20_ASSET_CLASS = bytes4(keccak256("ERC20")); bytes4 public constant ERC721_ASSET_CLASS = bytes4(keccak256("ERC721")); bytes4 public constant ERC1155_ASSET_CLASS = bytes4(keccak256("ERC1155")); + // TODO: Unused ? bytes4 public constant ERC721_TSB_CLASS = bytes4(keccak256("ERC721_TSB")); + // TODO: Unused ? bytes4 public constant ERC1155_TSB_CLASS = bytes4(keccak256("ERC1155_TSB")); + // TODO: rename to BUNDLE_ASSET_CLASS ? bytes4 public constant BUNDLE = bytes4(keccak256("BUNDLE")); bytes32 internal constant ASSET_TYPE_TYPEHASH = keccak256("AssetType(bytes4 assetClass,bytes data)"); diff --git a/packages/marketplace/test/exchange/Exchange.test.ts b/packages/marketplace/test/exchange/Exchange.test.ts index ab5582408c..0f0a968bc4 100644 --- a/packages/marketplace/test/exchange/Exchange.test.ts +++ b/packages/marketplace/test/exchange/Exchange.test.ts @@ -1,22 +1,52 @@ import {expect} from 'chai'; import {deployFixtures} from '../fixtures'; import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; +import {AssetERC20, AssetERC721, AssetETH} from '../utils/assets.ts'; import { - ETH_ASSET_CLASS, - ERC20_ASSET_CLASS, - ERC721_ASSET_CLASS, - enc, -} from '../utils/assets.ts'; - -import { - createOrder, - createAsset, - DEFAULT_ORDER_TYPE, + hashKey, + hashOrder, + OrderDefault, UINT256_MAX_VALUE, } from '../utils/order.ts'; +import {getBytes, ZeroAddress} from 'ethers'; +import {signOrder} from '../utils/signature'; describe('Exchange.sol', function () { + // TODO: Erase + it('check javascript hashing', async function () { + const {ExchangeContractAsDeployer, user, ERC20Contract} = await loadFixture( + deployFixtures + ); + const makerAsset = await AssetERC20(ERC20Contract, 100); + const takerAsset = await AssetETH(100); + + const defaultOrder = await OrderDefault( + user, + makerAsset, + ZeroAddress, + takerAsset, + 1, // setting salt value to 0 + 0, + 0 + ); + expect( + await ExchangeContractAsDeployer.getHashKey(defaultOrder) + ).to.be.equal(hashKey(defaultOrder)); + const sellOrder = await OrderDefault( + user, + makerAsset, + ZeroAddress, + takerAsset, + 1, // setting salt value to 0 + 0, + 0 + ); + expect(await ExchangeContractAsDeployer.getHashKey(sellOrder)).to.be.equal( + hashKey(sellOrder) + ); + }); + it('should not set trusted forwarder if caller is not owner', async function () { const {ExchangeContractAsUser, user} = await loadFixture(deployFixtures); await expect( @@ -37,11 +67,9 @@ describe('Exchange.sol', function () { }); it('should not be able to set trusted forwarder as zero address', async function () { - const {ExchangeContractAsDeployer, ZERO_ADDRESS} = await loadFixture( - deployFixtures - ); + const {ExchangeContractAsDeployer} = await loadFixture(deployFixtures); await expect( - ExchangeContractAsDeployer.setTrustedForwarder(ZERO_ADDRESS) + ExchangeContractAsDeployer.setTrustedForwarder(ZeroAddress) ).to.be.revertedWith('address must be different from 0'); }); @@ -81,107 +109,67 @@ describe('Exchange.sol', function () { }); it('should not cancel the order if caller is not maker', async function () { - const { - ExchangeContractAsDeployer, - user1, - user2, - ZERO_ADDRESS, - ERC20Contract, - } = await loadFixture(deployFixtures); + const {ExchangeContractAsDeployer, user1, user2, ERC20Contract} = + await loadFixture(deployFixtures); - const makeAsset = createAsset( - ERC20_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 100 - ); - const takeAsset = createAsset( - ETH_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 100 - ); + const makerAsset = await AssetERC20(ERC20Contract, 100); + const takerAsset = await AssetETH(100); - const leftOrder = createOrder( - user1.address, - makeAsset, - ZERO_ADDRESS, - takeAsset, + const leftOrder = await OrderDefault( + user1, + makerAsset, + ZeroAddress, + takerAsset, 1, 0, - 0, - DEFAULT_ORDER_TYPE, - '0x' + 0 ); - await expect( ExchangeContractAsDeployer.connect(user2).cancel( leftOrder, - await ExchangeContractAsDeployer.getHashKey(leftOrder) + hashOrder(leftOrder) ) ).to.be.revertedWith('ExchangeCore: not maker'); }); it('should not cancel order with zero salt', async function () { - const {ExchangeContractAsUser, user, ERC20Contract, ZERO_ADDRESS} = - await loadFixture(deployFixtures); - - const makeAsset = createAsset( - ERC20_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 100 + const {ExchangeContractAsUser, user, ERC20Contract} = await loadFixture( + deployFixtures ); - const takeAsset = createAsset( - ETH_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 100 - ); + const makerAsset = await AssetERC20(ERC20Contract, 100); + const takerAsset = await AssetETH(100); - const leftOrder = createOrder( - user.address, - makeAsset, - ZERO_ADDRESS, - takeAsset, + const leftOrder = await OrderDefault( + user, + makerAsset, + ZeroAddress, + takerAsset, 0, // setting salt value to 0 0, - 0, - DEFAULT_ORDER_TYPE, - '0x' + 0 ); - await expect( - ExchangeContractAsUser.cancel( - leftOrder, - await ExchangeContractAsUser.getHashKey(leftOrder) - ) + ExchangeContractAsUser.cancel(leftOrder, hashKey(leftOrder)) ).to.be.revertedWith("ExchangeCore: 0 salt can't be used"); }); it('should not cancel the order with invalid order hash', async function () { - const {ExchangeContractAsUser, user1, ERC20Contract, ZERO_ADDRESS} = - await loadFixture(deployFixtures); - - const makeAsset = createAsset( - ERC20_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 100 + const {ExchangeContractAsUser, user1, ERC20Contract} = await loadFixture( + deployFixtures ); - const takeAsset = createAsset( - ETH_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 100 - ); + const makerAsset = await AssetERC20(ERC20Contract, 100); + const takerAsset = await AssetETH(100); - const leftOrder = createOrder( - user1.address, - makeAsset, - ZERO_ADDRESS, - takeAsset, + const leftOrder = await OrderDefault( + user1, + makerAsset, + ZeroAddress, + takerAsset, 1, 0, - 0, - DEFAULT_ORDER_TYPE, - '0x' + 0 ); const invalidOrderHash = @@ -192,42 +180,85 @@ describe('Exchange.sol', function () { }); it('should cancel an order and update fills mapping', async function () { - const {ExchangeContractAsUser, user, ERC20Contract, ZERO_ADDRESS} = - await loadFixture(deployFixtures); + const {ExchangeContractAsUser, user, ERC20Contract} = await loadFixture( + deployFixtures + ); - const makeAsset = createAsset( - ERC20_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 100 + const makerAsset = await AssetERC20(ERC20Contract, 100); + const takerAsset = await AssetETH(100); + + const leftOrder = await OrderDefault( + user, + makerAsset, + ZeroAddress, + takerAsset, + 1, + 0, + 0 ); - const takeAsset = createAsset( - ETH_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), + await ExchangeContractAsUser.cancel(leftOrder, hashKey(leftOrder)); + + expect( + await ExchangeContractAsUser.fills( + await ExchangeContractAsUser.getHashKey(leftOrder) + ) + ).to.be.equal(UINT256_MAX_VALUE); + }); + + it('should be able to match two orders', async function () { + const { + ExchangeContractAsUser, + OrderValidator, + ERC20Contract, + ERC721Contract, + user, + user2, + } = await loadFixture(deployFixtures); + await ERC721Contract.mint(user.address, 1); + await ERC721Contract.connect(user).approve( + await ExchangeContractAsUser.getAddress(), + 1 + ); + await ERC20Contract.mint(user2.address, 100); + await ERC20Contract.connect(user2).approve( + await ExchangeContractAsUser.getAddress(), 100 ); - const leftOrder = createOrder( - user.address, - makeAsset, - ZERO_ADDRESS, - takeAsset, + const makerAsset = await AssetERC721(ERC721Contract, 1); + const takerAsset = await AssetERC20(ERC20Contract, 100); + + const leftOrder = await OrderDefault( + user, + makerAsset, + ZeroAddress, + takerAsset, 1, 0, + 0 + ); + const rightOrder = await OrderDefault( + user2, + takerAsset, + ZeroAddress, + makerAsset, + 1, 0, - DEFAULT_ORDER_TYPE, - '0x' + 0 ); - await ExchangeContractAsUser.cancel( + + const makerSig = await signOrder(leftOrder, user, OrderValidator); + const takerSig = await signOrder(rightOrder, user2, OrderValidator); + + await ExchangeContractAsUser.matchOrders( leftOrder, - await ExchangeContractAsUser.getHashKey(leftOrder) + makerSig, + rightOrder, + takerSig ); - expect( - await ExchangeContractAsUser.fills( - await ExchangeContractAsUser.getHashKey(leftOrder) - ) - ).to.be.equal(UINT256_MAX_VALUE); + // TODO: Check balances before and after, validate everything!!! }); it('should be able to execute direct purchase', async function () { @@ -235,7 +266,6 @@ describe('Exchange.sol', function () { ExchangeContractAsDeployer, ERC20Contract, ERC721Contract, - ZERO_ADDRESS, user1, user2, } = await loadFixture(deployFixtures); @@ -246,49 +276,48 @@ describe('Exchange.sol', function () { 1 ); - const makeAsset = createAsset( - ERC721_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 1 - ); - - const takeAsset = createAsset( - ERC20_ASSET_CLASS, - await enc(await ERC20Contract.getAddress(), 1), - 100 + const makerAsset = await AssetERC721(ERC721Contract, 1); + const takerAsset = await AssetERC20(ERC20Contract, 100); + const left = await OrderDefault( + user1, + makerAsset, + ZeroAddress, + takerAsset, + 1, + 0, + 0 ); + const signature = await signOrder(left, user1, ExchangeContractAsDeployer); + // const timestamp = await time.latest(); + // const sellOrderStart = timestamp - 100000; + // const sellOrderEnd = timestamp + 100000; const purchase = { sellOrderMaker: user1.address, sellOrderNftAmount: 1, - nftAssetClass: ERC721_ASSET_CLASS, // bytes4(keccak256("ERC721")) - nftData: '0x0', // TODO provide nft data + nftAssetClass: makerAsset.assetType.assetClass, + nftData: makerAsset.assetType.data, sellOrderPaymentAmount: 100, - paymentToken: ERC20Contract.getAddress(), + paymentToken: await ERC20Contract.getAddress(), sellOrderSalt: 1, sellOrderStart: 0, sellOrderEnd: 0, - sellOrderDataType: ERC20_ASSET_CLASS, // bytes4(keccak256("ERC20")); - sellOrderData: '0x0', // TODO pass sell order data - sellOrderSignature: '0x0', // TODO pass signature + sellOrderDataType: left.dataType, + sellOrderData: left.data, + sellOrderSignature: signature, buyOrderPaymentAmount: 100, buyOrderNftAmount: 1, - buyOrderData: '0x0', // TODO pass buy data + buyOrderData: getBytes('0x'), // TODO pass buy data }; - const leftOrder = { - maker: user1.address, - makeAsset: makeAsset, - taker: ZERO_ADDRESS, - takeAsset: takeAsset, - salt: 1, - start: 0, - end: 0, - dataType: DEFAULT_ORDER_TYPE, - data: '0x', - }; - - // WIP - // await ExchangeContractAsDeployer.directPurchase(user2.address, purchase); + // TODO: WIP + // TODO: We need to test orderValidator first, a call to setSigningWallet is needed ? + // TODO: What is this backend signature stuff ? + const backendSignature = getBytes('0x'); + await ExchangeContractAsDeployer.directPurchase( + user2.address, + [purchase], + [backendSignature] + ); }); }); diff --git a/packages/marketplace/test/fixtures.ts b/packages/marketplace/test/fixtures.ts index ff8f36db12..4914886a64 100644 --- a/packages/marketplace/test/fixtures.ts +++ b/packages/marketplace/test/fixtures.ts @@ -1,8 +1,7 @@ import {ethers, upgrades} from 'hardhat'; +import {ZeroAddress} from 'ethers'; export async function deployFixtures() { - const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'; - const [deployer, user, defaultFeeReceiver, user1, user2] = await ethers.getSigners(); @@ -55,6 +54,10 @@ export async function deployFixtures() { const ERC721Contract = await ERC721ContractFactory.deploy(); await ERC721Contract.waitForDeployment(); + // TODO: Do we always want this? + await ExchangeContractAsDeployer.setAssetMatcherContract( + await assetMatcherAsDeployer.getAddress() + ); return { assetMatcherAsDeployer, assetMatcherAsUser, @@ -63,10 +66,11 @@ export async function deployFixtures() { TrustedForwarder, ERC20Contract, ERC721Contract, + OrderValidator, deployer, user, user1, user2, - ZERO_ADDRESS, + ZERO_ADDRESS: ZeroAddress, }; } diff --git a/packages/marketplace/test/utils/assets.ts b/packages/marketplace/test/utils/assets.ts index 9aef11418c..78fa484ad6 100644 --- a/packages/marketplace/test/utils/assets.ts +++ b/packages/marketplace/test/utils/assets.ts @@ -1,25 +1,153 @@ -// TODO: This is the same as the root folder scripts... fix it -import {ethers} from 'hardhat'; -import {AbiCoder} from 'ethers'; - -export const ETH_ASSET_CLASS = '0xaaaebeba'; -export const ERC20_ASSET_CLASS = '0x8ae85d84'; -export const ERC721_ASSET_CLASS = '0x73ad2146'; - -export async function id(str: string) { - return `0x${ethers - .keccak256(Buffer.from(str)) - .toString('hex') - .substring(0, 8)}`; -} +// AssetXXX represents something you want to trade, for example: +// "20 eth" == AssetETH(20), "11 Sand" == AssetERC20(SandTokenAddress, 11) +// some NFT" == AssetERC721(nftContractAddress, tokenId), etc +// SEE: LibAsset.sol +import { + AbiCoder, + BytesLike, + Contract, + keccak256, + solidityPackedKeccak256, +} from 'ethers'; +import {bytes4Keccak, HashSignature} from './signature'; + +export const ETH_ASSET_CLASS = bytes4Keccak('ETH'); +export const ERC20_ASSET_CLASS = bytes4Keccak('ERC20'); +export const ERC721_ASSET_CLASS = bytes4Keccak('ERC721'); +export const ERC1155_ASSET_CLASS = bytes4Keccak('ERC1155'); +// export const ERC721_TSB_CLASS = bytes4Keccak('ERC721_TSB'); +// export const ERC1155_TSB_CLASS = bytes4Keccak('ERC1155_TSB'); +export const BUNDLE_ASSET_CLASS = bytes4Keccak('BUNDLE'); + +//TODO: export const ERC721_LAZY_ASSET_CLASS = bytes4Keccak('ERC721_LAZY'); +export const ASSET_TYPE_TYPEHASH = keccak256( + Buffer.from('AssetType(bytes4 assetClass,bytes data)') +); +export const ASSET_TYPEHASH = keccak256( + Buffer.from( + 'Asset(AssetType assetType,uint256 value)AssetType(bytes4 assetClass,bytes data)' + ) +); + +export type AssetType = { + assetClass: HashSignature; + data: BytesLike; +}; + +export type Asset = { + assetType: AssetType; + value: string; +}; + +export const AssetETH = (amount: number): Asset => ({ + assetType: { + assetClass: ETH_ASSET_CLASS, + data: '0x', + }, + value: amount.toString(), +}); + +export const AssetERC20 = async ( + tokenContract: Contract, + amount: number +): Promise => ({ + assetType: { + assetClass: ERC20_ASSET_CLASS, + data: AbiCoder.defaultAbiCoder().encode( + ['address'], + [await tokenContract.getAddress()] + ), + }, + value: amount.toString(), +}); + +export const AssetERC721 = async ( + tokenContract: Contract, + tokenId: number +): Promise => ({ + assetType: { + assetClass: ERC721_ASSET_CLASS, + data: AbiCoder.defaultAbiCoder().encode( + ['address', 'uint256'], + [await tokenContract.getAddress(), tokenId] + ), + }, + value: '1', +}); -export async function enc(token: string, tokenId: number) { - if (tokenId) { - return AbiCoder.defaultAbiCoder().encode( +export const AssetERC1155 = async ( + tokenContract: Contract, + tokenId: number, + amount: number +): Promise => ({ + assetType: { + assetClass: ERC1155_ASSET_CLASS, + data: AbiCoder.defaultAbiCoder().encode( ['address', 'uint256'], - [token, tokenId] - ); - } else { - return AbiCoder.defaultAbiCoder().encode(['address'], [token]); + [await tokenContract.getAddress(), tokenId] + ), + }, + value: amount.toString(), +}); + +export const AssetBundle = async ( + erc20: {tokenContract: Contract; amount: number}[], + erc721: {tokenContract: Contract; tokenId: number}[], + erc1155: {tokenContract: Contract; tokenId: number; amount: number}[] +): Promise => { + const erc20Details = []; + for (const x of erc20) { + erc20Details.push({ + token: await x.tokenContract.getAddress(), + value: x.amount, + }); + } + const erc721Details = []; + for (const x of erc721) { + erc20Details.push({ + token: await x.tokenContract.getAddress(), + id: x.tokenId, + value: '1', + }); + } + const erc1155Details = []; + for (const x of erc1155) { + erc20Details.push({ + token: await x.tokenContract.getAddress(), + id: x.tokenId, + value: x.amount, + }); } + return { + assetType: { + assetClass: BUNDLE_ASSET_CLASS, + data: AbiCoder.defaultAbiCoder().encode( + [ + 'tuple(address, uint256)[]', + 'tuple(address, uint256,uint256)[]', + 'tuple(address, uint256uint256)[]', + ], + [erc20Details, erc721Details, erc1155Details] + ), + }, + value: '1', + }; +}; + +export function hashAssetType(a: AssetType) { + if (a.assetClass.length !== 10) { + throw new Error('Invalid assetClass' + a.assetClass); + } + // There is aproblem with solidityPackedKeccak256 and byte4 => a.assetClass + '0'.repeat(56) + return solidityPackedKeccak256( + ['bytes32', 'bytes32', 'bytes32'], + [ASSET_TYPE_TYPEHASH, a.assetClass + '0'.repeat(56), keccak256(a.data)] + ); +} + +export function hashAsset(a: Asset) { + return solidityPackedKeccak256( + ['bytes32', 'bytes32', 'uint256'], + [ASSET_TYPEHASH, hashAssetType(a.assetType), a.value] + ); } diff --git a/packages/marketplace/test/utils/order.ts b/packages/marketplace/test/utils/order.ts index 5c039b6177..c677164e4a 100644 --- a/packages/marketplace/test/utils/order.ts +++ b/packages/marketplace/test/utils/order.ts @@ -1,69 +1,175 @@ -/* eslint-disable @typescript-eslint/no-explicit-any */ +// An order represents something offered (asset + who offers) plus what we want in exchange (asset + optionally for whom or everybody) +// SEE: LibOrder.sol and LibOrderData.sol +import {Asset, hashAsset, hashAssetType} from './assets'; +import {bytes4Keccak} from './signature'; +import {AbiCoder, keccak256, Signer, ZeroAddress} from 'ethers'; +import {BytesLike} from 'ethers/src.ts/utils/index'; + +export const ORDER_TYPEHASH = keccak256( + Buffer.from( + 'Order(address maker,Asset makeAsset,address taker,Asset takeAsset,uint256 salt,uint256 start,uint256 end,bytes4 dataType,bytes data)Asset(AssetType assetType,uint256 value)AssetType(bytes4 assetClass,bytes data)' + ) +); + +export const ORDER_BACK_TYPEHASH = keccak256( + Buffer.from( + 'OrderBack(address buyer,address maker,Asset makeAsset,address taker,Asset takeAsset,uint256 salt,uint256 start,uint256 end,bytes4 dataType,bytes data)Asset(AssetType assetType,uint256 value)AssetType(bytes4 assetClass,bytes data)' + ) +); + +const ORDER_DATA_BUY = bytes4Keccak('BUY'); +const ORDER_DATA_SELL = bytes4Keccak('SELL'); export const DEFAULT_ORDER_TYPE = '0xffffffff'; export const UINT256_MAX_VALUE = 115792089237316195423570985008687907853269984665640564039457584007913129639935n; -export function AssetType(assetClass: string, data: string) { - return {assetClass, data}; -} +export type Order = { + maker: string; + makeAsset: Asset; + taker: string; + takeAsset: Asset; + salt: number; + start: number; + end: number; + dataType: string; + data: BytesLike; +}; -export function createAsset( - assetClass: string, - assetData: string, - value: number -) { - return {assetType: AssetType(assetClass, assetData), value}; -} +export const OrderDefault = async ( + maker: Signer, + makeAsset: Asset, + taker: Signer | ZeroAddress, + takeAsset: Asset, + salt: number, + start: number, + end: number +): Promise => ({ + maker: await maker.getAddress(), + makeAsset, + taker: taker === ZeroAddress ? ZeroAddress : await taker.getAddress(), + takeAsset, + salt, + start, + end, + dataType: DEFAULT_ORDER_TYPE, + data: '0x', +}); -export function createOrder( - maker: string, - makeAsset: any, - taker: string, - takeAsset: any, +export const OrderSell = async ( + maker: Signer, + makeAsset: Asset, + taker: Signer | ZeroAddress, + takeAsset: Asset, salt: number, start: number, end: number, - dataType: string, - data: string -) { - return {maker, makeAsset, taker, takeAsset, salt, start, end, dataType, data}; -} + payouts: string, // TODO: better type + originFeeFirst: string, // TODO: better type + originFeeSecond: string, // TODO: better type + maxFeesBasePoint: string, // TODO: better type + marketplaceMarker: string // TODO: better type +): Promise => ({ + maker: await maker.getAddress(), + makeAsset, + taker: taker === ZeroAddress ? ZeroAddress : await taker.getAddress(), + takeAsset, + salt, + start, + end, + dataType: ORDER_DATA_SELL, + data: AbiCoder.defaultAbiCoder().encode( + ['uint256', 'uint256', 'uint256', 'uint256', 'bytes32'], + [ + payouts, + originFeeFirst, + originFeeSecond, + maxFeesBasePoint, + marketplaceMarker, + ] + ), +}); +export const OrderBuy = async ( + maker: Signer, + makeAsset: Asset, + taker: Signer | ZeroAddress, + takeAsset: Asset, + salt: number, + start: number, + end: number, + payouts: string, // TODO: better type + originFeeFirst: string, // TODO: better type + originFeeSecond: string, // TODO: better type + marketplaceMarker: string // TODO: better type +): Promise => ({ + maker: await maker.getAddress(), + makeAsset, + taker: taker === ZeroAddress ? ZeroAddress : await taker.getAddress(), + takeAsset, + salt, + start, + end, + dataType: ORDER_DATA_BUY, + data: AbiCoder.defaultAbiCoder().encode( + ['uint256', 'uint256', 'uint256', 'bytes32'], + [payouts, originFeeFirst, originFeeSecond, marketplaceMarker] + ), +}); -// const Types = { -// AssetType: [ -// {name: 'assetClass', type: 'bytes4'}, -// {name: 'data', type: 'bytes'}, -// ], -// Asset: [ -// {name: 'assetType', type: 'AssetType'}, -// {name: 'value', type: 'uint256'}, -// ], -// Order: [ -// {name: 'maker', type: 'address'}, -// {name: 'makeAsset', type: 'Asset'}, -// {name: 'taker', type: 'address'}, -// {name: 'takeAsset', type: 'Asset'}, -// {name: 'salt', type: 'uint256'}, -// {name: 'start', type: 'uint256'}, -// {name: 'end', type: 'uint256'}, -// {name: 'dataType', type: 'bytes4'}, -// {name: 'data', type: 'bytes'}, -// ], -// }; +export function hashKey(order: Order): string { + if (order.dataType === DEFAULT_ORDER_TYPE) { + const encoded = AbiCoder.defaultAbiCoder().encode( + ['address', 'bytes32', 'bytes32', 'uint256'], + [ + order.maker, + hashAssetType(order.makeAsset.assetType), + hashAssetType(order.takeAsset.assetType), + order.salt, + ] + ); + return keccak256(encoded); + } + // TODO: Review this on solidity side, instead of passing order.data maybe is better keccak(data)s + const encoded = AbiCoder.defaultAbiCoder().encode( + ['address', 'bytes32', 'bytes32', 'uint256', 'bytes'], + [ + order.maker, + hashAssetType(order.makeAsset.assetType), + hashAssetType(order.takeAsset.assetType), + order.salt, + order.data, + ] + ); + return keccak256(encoded); +} -// async function sign(order, account, verifyingContract) { -// const chainId = config.network_id; -// const data = EIP712.createTypeData( -// { -// name: 'Exchange', -// version: '1', -// chainId, -// verifyingContract, -// }, -// 'Order', -// order, -// Types -// ); -// return (await EIP712.signTypedData(web3, account, data)).sig; -// } +// TODO: Test it. +export function hashOrder(order: Order): string { + const encoded = AbiCoder.defaultAbiCoder().encode( + [ + 'bytes32', + 'address', + 'bytes32', + 'address', + 'bytes32', + 'uint256', + 'uint256', + 'uint256', + 'bytes4', + 'bytes32', + ], + [ + ORDER_TYPEHASH, + order.maker, + hashAsset(order.makeAsset), + order.taker, + hashAsset(order.takeAsset), + order.salt, + order.start, + order.end, + order.dataType, + keccak256(order.data), + ] + ); + return keccak256(encoded); +} diff --git a/packages/marketplace/test/utils/raribleTestHelper.ts b/packages/marketplace/test/utils/raribleTestHelper.ts new file mode 100644 index 0000000000..8429007fff --- /dev/null +++ b/packages/marketplace/test/utils/raribleTestHelper.ts @@ -0,0 +1,52 @@ +// This replaces the calls to the RaribleTestHelper.sol +import {AbiCoder} from 'ethers'; + +export type SellData = { + payouts: string; // TODO: better type + originFeeFirst: string; // TODO: better type + originFeeSecond: string; // TODO: better type + maxFeesBasePoint: string; // TODO: better type + marketplaceMarker: string; // TODO: better type +}; + +export function encode_SELL(data: SellData): string { + return AbiCoder.defaultAbiCoder().encode( + ['uint256', 'uint256', 'uint256', 'uint256', 'bytes32'], + [ + data.payouts, + data.originFeeFirst, + data.originFeeSecond, + data.maxFeesBasePoint, + data.marketplaceMarker, + ] + ); +} + +export type BuyData = { + payouts: string; // TODO: better type + originFeeFirst: string; // TODO: better type + originFeeSecond: string; // TODO: better type + marketplaceMarker: string; // TODO: better type +}; + +export function encode_BUY(data: BuyData): string { + return AbiCoder.defaultAbiCoder().encode( + ['uint256', 'uint256', 'uint256', 'bytes32'], + [ + data.payouts, + data.originFeeFirst, + data.originFeeSecond, + data.marketplaceMarker, + ] + ); +} + +export function encodeOriginFeeIntoUint( + account: string, + value: string +): bigint { + return BigInt(account) << (160 + BigInt(value)); +} + +// TODO: Implement +// function hashV2( diff --git a/packages/marketplace/test/utils/signature.ts b/packages/marketplace/test/utils/signature.ts new file mode 100644 index 0000000000..9706ba206d --- /dev/null +++ b/packages/marketplace/test/utils/signature.ts @@ -0,0 +1,46 @@ +import {Contract, keccak256, Signer} from 'ethers'; +import {Order} from './order'; + +export type HashSignature = string; + +export function bytes4Keccak(str: string): HashSignature { + return keccak256(Buffer.from(str)).substring(0, 10); +} + +export async function signOrder( + order: Order, + account: Signer, + verifyingContract: Contract +) { + const network = await verifyingContract.runner?.provider?.getNetwork(); + return account.signTypedData( + { + name: 'Exchange', + version: '1', + chainId: network.chainId, + verifyingContract: await verifyingContract.getAddress(), + }, + { + AssetType: [ + {name: 'assetClass', type: 'bytes4'}, + {name: 'data', type: 'bytes'}, + ], + Asset: [ + {name: 'assetType', type: 'AssetType'}, + {name: 'value', type: 'uint256'}, + ], + Order: [ + {name: 'maker', type: 'address'}, + {name: 'makeAsset', type: 'Asset'}, + {name: 'taker', type: 'address'}, + {name: 'takeAsset', type: 'Asset'}, + {name: 'salt', type: 'uint256'}, + {name: 'start', type: 'uint256'}, + {name: 'end', type: 'uint256'}, + {name: 'dataType', type: 'bytes4'}, + {name: 'data', type: 'bytes'}, + ], + }, + order + ); +} From 16cfd0390f28e1b7ddfdcf527f432154fcdae1bf Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Mon, 25 Sep 2023 11:29:34 +0530 Subject: [PATCH 2/3] test: wip signature create for direct purchase --- .../test/exchange/Exchange.test.ts | 29 +++++++++---- packages/marketplace/test/utils/order.ts | 38 +++++++++++++++++ packages/marketplace/test/utils/signature.ts | 41 ++++++++++++++++++- 3 files changed, 98 insertions(+), 10 deletions(-) diff --git a/packages/marketplace/test/exchange/Exchange.test.ts b/packages/marketplace/test/exchange/Exchange.test.ts index 0f0a968bc4..a56be7b91c 100644 --- a/packages/marketplace/test/exchange/Exchange.test.ts +++ b/packages/marketplace/test/exchange/Exchange.test.ts @@ -7,10 +7,12 @@ import { hashKey, hashOrder, OrderDefault, + OrderBack, UINT256_MAX_VALUE, + DEFAULT_ORDER_TYPE, } from '../utils/order.ts'; import {getBytes, ZeroAddress} from 'ethers'; -import {signOrder} from '../utils/signature'; +import {signOrder, signOrderBack} from '../utils/signature'; describe('Exchange.sol', function () { // TODO: Erase @@ -206,6 +208,7 @@ describe('Exchange.sol', function () { ).to.be.equal(UINT256_MAX_VALUE); }); + // TODO: remove it('should be able to match two orders', async function () { const { ExchangeContractAsUser, @@ -261,14 +264,17 @@ describe('Exchange.sol', function () { // TODO: Check balances before and after, validate everything!!! }); - it('should be able to execute direct purchase', async function () { + it.only('should be able to execute direct purchase', async function () { const { ExchangeContractAsDeployer, + OrderValidator, ERC20Contract, ERC721Contract, + deployer, user1, user2, } = await loadFixture(deployFixtures); + await OrderValidator.connect(deployer).setSigningWallet(deployer.address); await ERC721Contract.mint(user1.address, 1); await ERC20Contract.mint(user2.address, 100); await ERC721Contract.connect(user1).approve( @@ -278,16 +284,21 @@ describe('Exchange.sol', function () { const makerAsset = await AssetERC721(ERC721Contract, 1); const takerAsset = await AssetERC20(ERC20Contract, 100); - const left = await OrderDefault( + const order = await OrderBack( + user2, user1, makerAsset, ZeroAddress, takerAsset, 1, 0, - 0 + 0, + DEFAULT_ORDER_TYPE, + "0x" ); - const signature = await signOrder(left, user1, ExchangeContractAsDeployer); + // const hash = await ExchangeContractAsDeployer.getBEHashKey(order); + // const signature = await deployer.signMessage(hash); + const signature = await signOrderBack(order, deployer, ExchangeContractAsDeployer); // const timestamp = await time.latest(); // const sellOrderStart = timestamp - 100000; // const sellOrderEnd = timestamp + 100000; @@ -302,8 +313,8 @@ describe('Exchange.sol', function () { sellOrderSalt: 1, sellOrderStart: 0, sellOrderEnd: 0, - sellOrderDataType: left.dataType, - sellOrderData: left.data, + sellOrderDataType: order.dataType, + sellOrderData: order.data, sellOrderSignature: signature, buyOrderPaymentAmount: 100, buyOrderNftAmount: 1, @@ -313,11 +324,11 @@ describe('Exchange.sol', function () { // TODO: WIP // TODO: We need to test orderValidator first, a call to setSigningWallet is needed ? // TODO: What is this backend signature stuff ? - const backendSignature = getBytes('0x'); + // const backendSignature = getBytes('0x'); await ExchangeContractAsDeployer.directPurchase( user2.address, [purchase], - [backendSignature] + [signature] ); }); }); diff --git a/packages/marketplace/test/utils/order.ts b/packages/marketplace/test/utils/order.ts index c677164e4a..afe2460f06 100644 --- a/packages/marketplace/test/utils/order.ts +++ b/packages/marketplace/test/utils/order.ts @@ -36,6 +36,43 @@ export type Order = { data: BytesLike; }; +export type OrderBack = { + buyer: string; + maker: string; + makeAsset: Asset; + taker: string; + takeAsset: Asset; + salt: number; + start: number; + end: number; + dataType: string; + data: BytesLike; +}; + +export const OrderBack = async ( + buyer: Signer, + maker: Signer, + makeAsset: Asset, + taker: Signer | ZeroAddress, + takeAsset: Asset, + salt: number, + start: number, + end: number, + dataType: string, + data: string +): Promise => ({ + buyer: await buyer.getAddress(), + maker: await maker.getAddress(), + makeAsset, + taker: taker === ZeroAddress ? ZeroAddress : await taker.getAddress(), + takeAsset, + salt, + start, + end, + dataType: DEFAULT_ORDER_TYPE, + data: '0x', +}) + export const OrderDefault = async ( maker: Signer, makeAsset: Asset, @@ -89,6 +126,7 @@ export const OrderSell = async ( ] ), }); + export const OrderBuy = async ( maker: Signer, makeAsset: Asset, diff --git a/packages/marketplace/test/utils/signature.ts b/packages/marketplace/test/utils/signature.ts index 9706ba206d..9892858d60 100644 --- a/packages/marketplace/test/utils/signature.ts +++ b/packages/marketplace/test/utils/signature.ts @@ -1,5 +1,5 @@ import {Contract, keccak256, Signer} from 'ethers'; -import {Order} from './order'; +import {Order, OrderBack} from './order'; export type HashSignature = string; @@ -44,3 +44,42 @@ export async function signOrder( order ); } + +export async function signOrderBack( + order: OrderBack, + account: Signer, + verifyingContract: Contract +) { + const network = await verifyingContract.runner?.provider?.getNetwork(); + return account.signTypedData( + { + name: 'Exchange', + version: '1', + chainId: network.chainId, + verifyingContract: await verifyingContract.getAddress(), + }, + { + AssetType: [ + {name: 'assetClass', type: 'bytes4'}, + {name: 'data', type: 'bytes'}, + ], + Asset: [ + {name: 'assetType', type: 'AssetType'}, + {name: 'value', type: 'uint256'}, + ], + Order: [ + {name: 'buyer', type: 'address'}, + {name: 'maker', type: 'address'}, + {name: 'makeAsset', type: 'Asset'}, + {name: 'taker', type: 'address'}, + {name: 'takeAsset', type: 'Asset'}, + {name: 'salt', type: 'uint256'}, + {name: 'start', type: 'uint256'}, + {name: 'end', type: 'uint256'}, + {name: 'dataType', type: 'bytes4'}, + {name: 'data', type: 'bytes'}, + ], + }, + order + ); +} From 57c8e60703f269418900bf0dc253c690ee7657be Mon Sep 17 00:00:00 2001 From: Ayush Tiwari Date: Tue, 26 Sep 2023 11:41:31 +0530 Subject: [PATCH 3/3] test: OrderValidator --- .../test/exchange/Exchange.test.ts | 157 +++--- .../test/exchange/OrderValidator.test.ts | 464 ++++++++++++++++++ packages/marketplace/test/fixtures.ts | 10 +- packages/marketplace/test/utils/order.ts | 2 +- packages/marketplace/test/utils/signature.ts | 2 +- 5 files changed, 559 insertions(+), 76 deletions(-) create mode 100644 packages/marketplace/test/exchange/OrderValidator.test.ts diff --git a/packages/marketplace/test/exchange/Exchange.test.ts b/packages/marketplace/test/exchange/Exchange.test.ts index a56be7b91c..5c60bafdcc 100644 --- a/packages/marketplace/test/exchange/Exchange.test.ts +++ b/packages/marketplace/test/exchange/Exchange.test.ts @@ -208,11 +208,10 @@ describe('Exchange.sol', function () { ).to.be.equal(UINT256_MAX_VALUE); }); - // TODO: remove it('should be able to match two orders', async function () { const { ExchangeContractAsUser, - OrderValidator, + OrderValidatorAsDeployer, ERC20Contract, ERC721Contract, user, @@ -251,8 +250,12 @@ describe('Exchange.sol', function () { 0 ); - const makerSig = await signOrder(leftOrder, user, OrderValidator); - const takerSig = await signOrder(rightOrder, user2, OrderValidator); + const makerSig = await signOrder(leftOrder, user, OrderValidatorAsDeployer); + const takerSig = await signOrder( + rightOrder, + user2, + OrderValidatorAsDeployer + ); await ExchangeContractAsUser.matchOrders( leftOrder, @@ -264,71 +267,83 @@ describe('Exchange.sol', function () { // TODO: Check balances before and after, validate everything!!! }); - it.only('should be able to execute direct purchase', async function () { - const { - ExchangeContractAsDeployer, - OrderValidator, - ERC20Contract, - ERC721Contract, - deployer, - user1, - user2, - } = await loadFixture(deployFixtures); - await OrderValidator.connect(deployer).setSigningWallet(deployer.address); - await ERC721Contract.mint(user1.address, 1); - await ERC20Contract.mint(user2.address, 100); - await ERC721Contract.connect(user1).approve( - await ExchangeContractAsDeployer.getAddress(), - 1 - ); - - const makerAsset = await AssetERC721(ERC721Contract, 1); - const takerAsset = await AssetERC20(ERC20Contract, 100); - const order = await OrderBack( - user2, - user1, - makerAsset, - ZeroAddress, - takerAsset, - 1, - 0, - 0, - DEFAULT_ORDER_TYPE, - "0x" - ); - // const hash = await ExchangeContractAsDeployer.getBEHashKey(order); - // const signature = await deployer.signMessage(hash); - const signature = await signOrderBack(order, deployer, ExchangeContractAsDeployer); - // const timestamp = await time.latest(); - // const sellOrderStart = timestamp - 100000; - // const sellOrderEnd = timestamp + 100000; - - const purchase = { - sellOrderMaker: user1.address, - sellOrderNftAmount: 1, - nftAssetClass: makerAsset.assetType.assetClass, - nftData: makerAsset.assetType.data, - sellOrderPaymentAmount: 100, - paymentToken: await ERC20Contract.getAddress(), - sellOrderSalt: 1, - sellOrderStart: 0, - sellOrderEnd: 0, - sellOrderDataType: order.dataType, - sellOrderData: order.data, - sellOrderSignature: signature, - buyOrderPaymentAmount: 100, - buyOrderNftAmount: 1, - buyOrderData: getBytes('0x'), // TODO pass buy data - }; - - // TODO: WIP - // TODO: We need to test orderValidator first, a call to setSigningWallet is needed ? - // TODO: What is this backend signature stuff ? - // const backendSignature = getBytes('0x'); - await ExchangeContractAsDeployer.directPurchase( - user2.address, - [purchase], - [signature] - ); - }); + // TODO remove + // it('should be able to execute direct purchase', async function () { + // const { + // ExchangeContractAsDeployer, + // OrderValidatorAsDeployer, + // ERC20Contract, + // ERC721Contract, + // deployer, + // user1, + // user2, + // } = await loadFixture(deployFixtures); + // await OrderValidatorAsDeployer.connect(deployer).setSigningWallet( + // deployer.address + // ); + // await ERC721Contract.mint(user1.address, 1); + // await ERC20Contract.mint(user2.address, 100); + // await ERC721Contract.connect(user1).approve( + // await ExchangeContractAsDeployer.getAddress(), + // 1 + // ); + + // const makerAsset = await AssetERC721(ERC721Contract, 1); + // const takerAsset = await AssetERC20(ERC20Contract, 100); + // const order = await OrderBack( + // user2, + // user1, + // makerAsset, + // ZeroAddress, + // takerAsset, + // 1, + // 0, + // 0, + // DEFAULT_ORDER_TYPE, + // '0x' + // ); + // // const hash = await ExchangeContractAsDeployer.getBEHashKey(order); + // // const signature = await deployer.signMessage(hash); + // // const signature = await signOrderBack( + // // order, + // // deployer, + // // ExchangeContractAsDeployer + // // ); + // const signature = await signOrderBack( + // order, + // deployer, + // OrderValidatorAsDeployer + // ); + // // const timestamp = await time.latest(); + // // const sellOrderStart = timestamp - 100000; + // // const sellOrderEnd = timestamp + 100000; + + // const purchase = { + // sellOrderMaker: user1.address, + // sellOrderNftAmount: 1, + // nftAssetClass: makerAsset.assetType.assetClass, + // nftData: makerAsset.assetType.data, + // sellOrderPaymentAmount: 100, + // paymentToken: await ERC20Contract.getAddress(), + // sellOrderSalt: 1, + // sellOrderStart: 0, + // sellOrderEnd: 0, + // sellOrderDataType: order.dataType, + // sellOrderData: order.data, + // sellOrderSignature: signature, + // buyOrderPaymentAmount: 100, + // buyOrderNftAmount: 1, + // buyOrderData: getBytes('0x'), // TODO pass buy data + // }; + + // // TODO: WIP + // // TODO: We need to test orderValidator first, a call to setSigningWallet is needed ? + // // TODO: What is this backend signature stuff ? + // // const backendSignature = getBytes('0x'); + // await ExchangeContractAsDeployer.directPurchase( + // user2.address, + // [purchase], + // [signature] + // ); + // }); }); diff --git a/packages/marketplace/test/exchange/OrderValidator.test.ts b/packages/marketplace/test/exchange/OrderValidator.test.ts new file mode 100644 index 0000000000..898a3446fc --- /dev/null +++ b/packages/marketplace/test/exchange/OrderValidator.test.ts @@ -0,0 +1,464 @@ +import {deployFixtures} from '../fixtures'; +import {loadFixture} from '@nomicfoundation/hardhat-network-helpers'; +import {expect} from 'chai'; +import {AssetERC20, AssetERC721, AssetETH} from '../utils/assets.ts'; + +import {OrderDefault, OrderBack, DEFAULT_ORDER_TYPE} from '../utils/order.ts'; +import {ZeroAddress} from 'ethers'; +import {signOrder, signOrderBack} from '../utils/signature'; + +// keccak256("TSB_ROLE") +const TSBRole = + '0x6278160ef7ca8a5eb8e5b274bcc0427c2cc7e12eee2a53c5989a1afb360f6404'; +// keccak256("PARTNER_ROLE") +const PartnerRole = + '0x2f049b28665abd79bc83d9aa564dba6b787ac439dba27b48e163a83befa9b260'; +// keccak256("ERC20_ROLE") +const ERC20Role = + '0x839f6f26c78a3e8185d8004defa846bd7b66fef8def9b9f16459a6ebf2502162'; + +describe('OrderValidator.sol', function () { + it('should not set signing wallet if caller is not owner', async function () { + const {OrderValidatorAsUser, user1} = await loadFixture(deployFixtures); + await expect( + OrderValidatorAsUser.setSigningWallet(user1.address) + ).to.revertedWith('Ownable: caller is not the owner'); + }); + + it('should not be able to set signing wallet as zero address', async function () { + const {OrderValidatorAsDeployer} = await loadFixture(deployFixtures); + await expect( + OrderValidatorAsDeployer.setSigningWallet(ZeroAddress) + ).to.revertedWith('WALLET_ZERO_ADDRESS'); + }); + + it('should be able to set signing wallet', async function () { + const {OrderValidatorAsDeployer, deployer} = await loadFixture( + deployFixtures + ); + await expect(OrderValidatorAsDeployer.setSigningWallet(deployer.address)) + .to.emit(OrderValidatorAsDeployer, 'SigningWallet') + .withArgs(deployer.address); + }); + + it('should not be able to set same signing wallet again', async function () { + const {OrderValidatorAsDeployer, deployer} = await loadFixture( + deployFixtures + ); + await OrderValidatorAsDeployer.setSigningWallet(deployer.address); + await expect( + OrderValidatorAsDeployer.setSigningWallet(deployer.address) + ).to.revertedWith('WALLET_ALREADY_SET'); + }); + + it('should return signing wallet address', async function () { + const {OrderValidatorAsDeployer, deployer} = await loadFixture( + deployFixtures + ); + await OrderValidatorAsDeployer.setSigningWallet(deployer.address); + expect(await OrderValidatorAsDeployer.getSigningWallet()).to.be.equal( + deployer.address + ); + }); + + it('should not validate a purchase when signature is invalid', async function () { + const { + OrderValidatorAsDeployer, + ERC20Contract, + ERC721Contract, + deployer, + user2, + user1, + } = await loadFixture(deployFixtures); + + const makerAsset = await AssetERC721(ERC721Contract, 1); + const takerAsset = await AssetERC20(ERC20Contract, 100); + const order = await OrderBack( + user2, + user1, + makerAsset, + ZeroAddress, + takerAsset, + 1, + 0, + 0, + DEFAULT_ORDER_TYPE, + '0x' + ); + const signature = await signOrderBack( + order, + user1, + OrderValidatorAsDeployer + ); + await OrderValidatorAsDeployer.setSigningWallet(deployer.address); + expect( + await OrderValidatorAsDeployer.isPurchaseValid(order, signature) + ).to.be.equal(false); + }); + + it('should validate a purchase when the order and signature are valid', async function () { + const { + OrderValidatorAsDeployer, + ERC20Contract, + ERC721Contract, + deployer, + user2, + user1, + } = await loadFixture(deployFixtures); + + const makerAsset = await AssetERC721(ERC721Contract, 1); + const takerAsset = await AssetERC20(ERC20Contract, 100); + const order = await OrderBack( + user2, + user1, + makerAsset, + ZeroAddress, + takerAsset, + 1, + 0, + 0, + DEFAULT_ORDER_TYPE, + '0x' + ); + const signature = await signOrderBack( + order, + deployer, + OrderValidatorAsDeployer + ); + await OrderValidatorAsDeployer.setSigningWallet(deployer.address); + expect( + await OrderValidatorAsDeployer.isPurchaseValid(order, signature) + ).to.be.equal(true); + }); + + it('should validate when assetClass is not ETH_ASSET_CLASS', async function () { + const {OrderValidatorAsUser, ERC20Contract, user1, user2} = + await loadFixture(deployFixtures); + const makerAsset = await AssetERC20(ERC20Contract, 100); + const takerAsset = await AssetETH(100); + const order = await OrderDefault( + user1, + makerAsset, + ZeroAddress, + takerAsset, + 1, + 0, + 0 + ); + const signature = await signOrder(order, user1, OrderValidatorAsUser); + + await expect(OrderValidatorAsUser.validate(order, signature, user1.address)) + .to.not.be.reverted; + }); + + it('should revert validate when assetClass is ETH_ASSET_CLASS, salt is zero and Order maker is not sender', async function () { + const {OrderValidatorAsUser, ERC20Contract, user1, user2} = + await loadFixture(deployFixtures); + const makerAsset = await AssetETH(100); + const takerAsset = await AssetERC20(ERC20Contract, 100); + const order = await OrderDefault( + user1, + makerAsset, + ZeroAddress, + takerAsset, + 0, + 0, + 0 + ); + const signature = await signOrder(order, user1, OrderValidatorAsUser); + + await expect( + OrderValidatorAsUser.validate(order, signature, user2.address) + ).to.be.revertedWith('maker is not tx sender'); + }); + + it('should validate when assetClass is ETH_ASSET_CLASS, salt is zero and Order maker is sender', async function () { + const {OrderValidatorAsUser, ERC20Contract, user1, user2} = + await loadFixture(deployFixtures); + const makerAsset = await AssetETH(100); + const takerAsset = await AssetERC20(ERC20Contract, 100); + const order = await OrderDefault( + user1, + makerAsset, + ZeroAddress, + takerAsset, + 0, + 0, + 0 + ); + const signature = await signOrder(order, user1, OrderValidatorAsUser); + + await expect(OrderValidatorAsUser.validate(order, signature, user1.address)) + .to.not.be.reverted; + }); + + it('should not validate when sender and signature signer is not Order maker', async function () { + const {OrderValidatorAsUser, ERC20Contract, user1, user2} = + await loadFixture(deployFixtures); + const makerAsset = await AssetETH(100); + const takerAsset = await AssetERC20(ERC20Contract, 100); + const order = await OrderDefault( + user1, + makerAsset, + ZeroAddress, + takerAsset, + 1, + 0, + 0 + ); + const signature = await signOrder(order, user2, OrderValidatorAsUser); + + await expect( + OrderValidatorAsUser.validate(order, signature, user2.address) + ).to.be.revertedWith('order signature verification error'); + }); + + it('should validate when sender is not Order maker but signature signer is Order maker', async function () { + const {OrderValidatorAsUser, ERC20Contract, user1, user2} = + await loadFixture(deployFixtures); + const makerAsset = await AssetETH(100); + const takerAsset = await AssetERC20(ERC20Contract, 100); + const order = await OrderDefault( + user1, + makerAsset, + ZeroAddress, + takerAsset, + 1, + 0, + 0 + ); + const signature = await signOrder(order, user1, OrderValidatorAsUser); + + await expect(OrderValidatorAsUser.validate(order, signature, user2.address)) + .to.not.be.reverted; + }); + + it('should verify ERC20 Whitelist', async function () { + const {OrderValidatorAsUser, ERC20Contract, ERC721Contract} = + await loadFixture(deployFixtures); + await expect( + OrderValidatorAsUser.verifyERC20Whitelist( + await ERC20Contract.getAddress() + ) + ).to.not.be.reverted; + }); + + it('should not set permission for token if caller is not owner', async function () { + const {OrderValidatorAsUser} = await loadFixture(deployFixtures); + await expect( + OrderValidatorAsUser.setPermissions(true, true, true, true) + ).to.revertedWith('Ownable: caller is not the owner'); + }); + + it('should be able to set permission for token', async function () { + const {OrderValidatorAsDeployer} = await loadFixture(deployFixtures); + expect(await OrderValidatorAsDeployer.tsbOnly()).to.be.equal(false); + expect(await OrderValidatorAsDeployer.partners()).to.be.equal(false); + expect(await OrderValidatorAsDeployer.open()).to.be.equal(true); + expect(await OrderValidatorAsDeployer.erc20List()).to.be.equal(false); + + await OrderValidatorAsDeployer.setPermissions(true, true, false, true); + + expect(await OrderValidatorAsDeployer.tsbOnly()).to.be.equal(true); + expect(await OrderValidatorAsDeployer.partners()).to.be.equal(true); + expect(await OrderValidatorAsDeployer.open()).to.be.equal(false); + expect(await OrderValidatorAsDeployer.erc20List()).to.be.equal(true); + }); + + it('should not be able to add token to tsb list if caller is not owner', async function () { + const {OrderValidatorAsUser, ERC20Contract} = await loadFixture( + deployFixtures + ); + await expect( + OrderValidatorAsUser.addTSB(await ERC20Contract.getAddress()) + ).to.be.revertedWith('Ownable: caller is not the owner'); + }); + + it('should be able to add token to tsb list', async function () { + const {OrderValidatorAsDeployer, ERC20Contract} = await loadFixture( + deployFixtures + ); + expect( + await OrderValidatorAsDeployer.hasRole( + TSBRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + await OrderValidatorAsDeployer.addTSB(await ERC20Contract.getAddress()); + expect( + await OrderValidatorAsDeployer.hasRole( + TSBRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(true); + }); + + it('should not be able to remove token from tsb list if caller is not owner', async function () { + const {OrderValidatorAsUser, ERC20Contract} = await loadFixture( + deployFixtures + ); + await expect( + OrderValidatorAsUser.removeTSB(await ERC20Contract.getAddress()) + ).to.be.revertedWith('Ownable: caller is not the owner'); + }); + + it('should be able to remove token from tsb list', async function () { + const {OrderValidatorAsDeployer, ERC20Contract} = await loadFixture( + deployFixtures + ); + expect( + await OrderValidatorAsDeployer.hasRole( + TSBRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + await OrderValidatorAsDeployer.addTSB(await ERC20Contract.getAddress()); + expect( + await OrderValidatorAsDeployer.hasRole( + TSBRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(true); + + await OrderValidatorAsDeployer.removeTSB(await ERC20Contract.getAddress()); + expect( + await OrderValidatorAsDeployer.hasRole( + TSBRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + }); + + it('should not be able to add token to partners list if caller is not owner', async function () { + const {OrderValidatorAsUser, ERC20Contract} = await loadFixture( + deployFixtures + ); + await expect( + OrderValidatorAsUser.addPartner(await ERC20Contract.getAddress()) + ).to.be.revertedWith('Ownable: caller is not the owner'); + }); + + it('should be able to add token to partners list', async function () { + const {OrderValidatorAsDeployer, ERC20Contract} = await loadFixture( + deployFixtures + ); + expect( + await OrderValidatorAsDeployer.hasRole( + PartnerRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + await OrderValidatorAsDeployer.addPartner(await ERC20Contract.getAddress()); + expect( + await OrderValidatorAsDeployer.hasRole( + PartnerRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(true); + }); + + it('should not be able to remove token from partners list if caller is not owner', async function () { + const {OrderValidatorAsUser, ERC20Contract} = await loadFixture( + deployFixtures + ); + await expect( + OrderValidatorAsUser.removePartner(await ERC20Contract.getAddress()) + ).to.be.revertedWith('Ownable: caller is not the owner'); + }); + + it('should be able to remove token from partners list', async function () { + const {OrderValidatorAsDeployer, ERC20Contract} = await loadFixture( + deployFixtures + ); + expect( + await OrderValidatorAsDeployer.hasRole( + PartnerRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + await OrderValidatorAsDeployer.addPartner(await ERC20Contract.getAddress()); + expect( + await OrderValidatorAsDeployer.hasRole( + PartnerRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(true); + + await OrderValidatorAsDeployer.removePartner( + await ERC20Contract.getAddress() + ); + expect( + await OrderValidatorAsDeployer.hasRole( + PartnerRole, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + }); + + it('should not be able to add token to ERC20 list if caller is not owner', async function () { + const {OrderValidatorAsUser, ERC20Contract} = await loadFixture( + deployFixtures + ); + + await expect( + OrderValidatorAsUser.addERC20(await ERC20Contract.getAddress()) + ).to.be.revertedWith('Ownable: caller is not the owner'); + }); + + it('should be able to add token to ERC20 list', async function () { + const {OrderValidatorAsDeployer, ERC20Contract} = await loadFixture( + deployFixtures + ); + expect( + await OrderValidatorAsDeployer.hasRole( + ERC20Role, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + await OrderValidatorAsDeployer.addERC20(await ERC20Contract.getAddress()); + expect( + await OrderValidatorAsDeployer.hasRole( + ERC20Role, + await ERC20Contract.getAddress() + ) + ).to.be.equal(true); + }); + + it('should not be able to remove token from ERC20 list if caller is not owner', async function () { + const {OrderValidatorAsUser, ERC20Contract} = await loadFixture( + deployFixtures + ); + await expect( + OrderValidatorAsUser.removeERC20(await ERC20Contract.getAddress()) + ).to.be.revertedWith('Ownable: caller is not the owner'); + }); + + it('should be able to remove token from ERC20 list', async function () { + const {OrderValidatorAsDeployer, ERC20Contract} = await loadFixture( + deployFixtures + ); + expect( + await OrderValidatorAsDeployer.hasRole( + ERC20Role, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + await OrderValidatorAsDeployer.addERC20(await ERC20Contract.getAddress()); + expect( + await OrderValidatorAsDeployer.hasRole( + ERC20Role, + await ERC20Contract.getAddress() + ) + ).to.be.equal(true); + + await OrderValidatorAsDeployer.removeERC20( + await ERC20Contract.getAddress() + ); + expect( + await OrderValidatorAsDeployer.hasRole( + ERC20Role, + await ERC20Contract.getAddress() + ) + ).to.be.equal(false); + }); +}); diff --git a/packages/marketplace/test/fixtures.ts b/packages/marketplace/test/fixtures.ts index 4914886a64..0cf938a073 100644 --- a/packages/marketplace/test/fixtures.ts +++ b/packages/marketplace/test/fixtures.ts @@ -19,13 +19,16 @@ export async function deployFixtures() { const OrderValidatorFactory = await ethers.getContractFactory( 'OrderValidatorTest' ); - const OrderValidator = await upgrades.deployProxy( + const OrderValidatorAsDeployer = await upgrades.deployProxy( OrderValidatorFactory, [false, false, true, false], { initializer: '__OrderValidator_init_unchained', } ); + + const OrderValidatorAsUser = await OrderValidatorAsDeployer.connect(user); + const ExchangeFactory = await ethers.getContractFactory('Exchange'); const ExchangeContractAsDeployer = await upgrades.deployProxy( ExchangeFactory, @@ -35,7 +38,7 @@ export async function deployFixtures() { 250, defaultFeeReceiver.address, await RoyaltyRegistry.getAddress(), - await OrderValidator.getAddress(), + await OrderValidatorAsDeployer.getAddress(), true, true, ], @@ -66,7 +69,8 @@ export async function deployFixtures() { TrustedForwarder, ERC20Contract, ERC721Contract, - OrderValidator, + OrderValidatorAsDeployer, + OrderValidatorAsUser, deployer, user, user1, diff --git a/packages/marketplace/test/utils/order.ts b/packages/marketplace/test/utils/order.ts index afe2460f06..e1e59864ad 100644 --- a/packages/marketplace/test/utils/order.ts +++ b/packages/marketplace/test/utils/order.ts @@ -71,7 +71,7 @@ export const OrderBack = async ( end, dataType: DEFAULT_ORDER_TYPE, data: '0x', -}) +}); export const OrderDefault = async ( maker: Signer, diff --git a/packages/marketplace/test/utils/signature.ts b/packages/marketplace/test/utils/signature.ts index 9892858d60..934af54c0f 100644 --- a/packages/marketplace/test/utils/signature.ts +++ b/packages/marketplace/test/utils/signature.ts @@ -67,7 +67,7 @@ export async function signOrderBack( {name: 'assetType', type: 'AssetType'}, {name: 'value', type: 'uint256'}, ], - Order: [ + OrderBack: [ {name: 'buyer', type: 'address'}, {name: 'maker', type: 'address'}, {name: 'makeAsset', type: 'Asset'},