From a63c9717c9048d73303dc7cbeebc218a18a0a7a1 Mon Sep 17 00:00:00 2001 From: Andres Adjimann Date: Thu, 7 Dec 2023 16:14:04 -0300 Subject: [PATCH 1/2] feat: split deploy tests in integration and runtime --- packages/deploy/README.md | 19 +++++++++++++++---- packages/deploy/hardhat.config.ts | 1 + packages/deploy/package.json | 4 +++- .../multiGiveaway/multiGiveaway.test.ts | 0 packages/deploy/tasks/importedPackages.ts | 2 +- packages/deploy/tasks/specialTests.ts | 15 +++++++++++++++ packages/deploy/tsconfig.json | 2 ++ 7 files changed, 37 insertions(+), 6 deletions(-) rename packages/deploy/{test => runtime_test}/multiGiveaway/multiGiveaway.test.ts (100%) create mode 100644 packages/deploy/tasks/specialTests.ts diff --git a/packages/deploy/README.md b/packages/deploy/README.md index 6e8647e403..64ab6738d2 100644 --- a/packages/deploy/README.md +++ b/packages/deploy/README.md @@ -49,9 +49,20 @@ where: We assume that the imported contracts are well tested in their own package by having enough unit tests and more that 80% coverage. This repo contains integrations tests, tests for the deployment process and tests that verify the -integrity of the system. For example in the case of the `SignedMultiGiveaway` -contract we check the roles and the users assigned to them are correctly -configured. +runtime integrity of the system. For example in the case of the +`SignedMultiGiveaway` contract we check the roles and the users assigned to them +are correctly configured. + +There are three directories to distinguish the type of test: + +- runtime_test: tests that check the runtime consistency of the contracts. They + must be able to run on a live network, a forked network or after a local + deploy. +- integration_test: tests used to check some functionality of imported contract + when they interact together. They are meant to be run in a forked network or + on a local deploy, they don't necessarily work on a live network. +- test: tests in this folder are always executed they must run at least on a + local deploy. To test the deployment process: @@ -61,7 +72,7 @@ To test the deployment process: `fork:deploy NETWORK` where NETWORK is `mainnet`,`polygon` ,`goerli`,`mumbai`, etc. -The tests the integrity of the system: +To test the integrity of the system: - using hardhat local node and `hardhat-deploy` deployment scripts: `yarn test` - on a live network over contracts that are already deployed: diff --git a/packages/deploy/hardhat.config.ts b/packages/deploy/hardhat.config.ts index 5d9c342f43..ba67e11bb8 100644 --- a/packages/deploy/hardhat.config.ts +++ b/packages/deploy/hardhat.config.ts @@ -8,6 +8,7 @@ import { addNodeAndMnemonic, skipDeploymentsOnLiveNetworks, } from './utils/hardhatConfig'; +import './tasks/specialTests'; import './tasks/importedPackages'; // Package name : solidity source code path diff --git a/packages/deploy/package.json b/packages/deploy/package.json index 6834a3be71..f3778da413 100644 --- a/packages/deploy/package.json +++ b/packages/deploy/package.json @@ -28,7 +28,9 @@ ], "scripts": { "compile": "hardhat compile", - "test": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true hardhat test", + "test": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true yarn hardhat test && yarn test:integration && yarn test:runtime", + "test:integration": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true hardhat test --integration", + "test:runtime": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192 node --unhandled-rejections=strict\" HARDHAT_COMPILE=true hardhat test --runtime", "void:deploy": "cross-env NODE_OPTIONS=\"--max-old-space-size=8192 --unhandled-rejections=strict\" hardhat deploy", "deploy": "npm run void:deploy", "hardhat": "hardhat", diff --git a/packages/deploy/test/multiGiveaway/multiGiveaway.test.ts b/packages/deploy/runtime_test/multiGiveaway/multiGiveaway.test.ts similarity index 100% rename from packages/deploy/test/multiGiveaway/multiGiveaway.test.ts rename to packages/deploy/runtime_test/multiGiveaway/multiGiveaway.test.ts diff --git a/packages/deploy/tasks/importedPackages.ts b/packages/deploy/tasks/importedPackages.ts index c2fac0f3ca..2e7a7d865d 100644 --- a/packages/deploy/tasks/importedPackages.ts +++ b/packages/deploy/tasks/importedPackages.ts @@ -21,7 +21,7 @@ declare module 'hardhat/types/runtime' { } declare module 'hardhat/types/config' { interface HardhatUserConfig { - importedPackages: {[name: string]: string}; + importedPackages: {[name: string]: string | string[]}; } } diff --git a/packages/deploy/tasks/specialTests.ts b/packages/deploy/tasks/specialTests.ts new file mode 100644 index 0000000000..82b2addfdc --- /dev/null +++ b/packages/deploy/tasks/specialTests.ts @@ -0,0 +1,15 @@ +import {task} from 'hardhat/config'; +import {TASK_TEST} from 'hardhat/builtin-tasks/task-names'; +import path from 'path'; + +task(TASK_TEST) + .addFlag('runtime', 'run runtime tests') + .addFlag('integration', 'run integration tests') + .setAction(async (args, {config}, runSuper) => { + if (args.runtime) { + config.paths.tests = path.resolve(config.paths.root, 'runtime_test'); + } else if (args.integration) { + config.paths.tests = path.resolve(config.paths.root, 'integration_test'); + } + return await runSuper(args); + }); diff --git a/packages/deploy/tsconfig.json b/packages/deploy/tsconfig.json index ed9985a5ae..fedf5414a6 100644 --- a/packages/deploy/tsconfig.json +++ b/packages/deploy/tsconfig.json @@ -15,6 +15,8 @@ "./deploy_mocks", "./utils", "./tasks", + "./integration_test", + "./runtime_test", "./test" ] } From ff9e0ad241c0b5f9c40a17a7c5dd3d739af84685 Mon Sep 17 00:00:00 2001 From: Andres Adjimann Date: Mon, 11 Dec 2023 20:24:18 -0300 Subject: [PATCH 2/2] test: basic marketplace land integration test --- .../000_core/001_deploy_polygon_land.ts | 30 ++++ .../deploy/000_core/002_setup_polygon_land.ts | 28 +++ .../401_deploy_asset_auth_validator.ts | 3 +- .../500_marketplace/504_deploy_exchange.ts | 1 + packages/deploy/hardhat.config.ts | 1 + .../marketplaceLand/exchange.test.ts | 131 +++++++++++++++ .../marketplaceLand/orders.ts | 159 ++++++++++++++++++ .../{multiGiveaway => }/multiGiveaway.test.ts | 2 +- .../deploy/test/asset/AssetCreate.test.ts | 8 +- .../deploy/test/asset/AssetReveal.test.ts | 8 +- 10 files changed, 361 insertions(+), 10 deletions(-) create mode 100644 packages/deploy/deploy/000_core/001_deploy_polygon_land.ts create mode 100644 packages/deploy/deploy/000_core/002_setup_polygon_land.ts create mode 100644 packages/deploy/integration_test/marketplaceLand/exchange.test.ts create mode 100644 packages/deploy/integration_test/marketplaceLand/orders.ts rename packages/deploy/runtime_test/{multiGiveaway => }/multiGiveaway.test.ts (94%) diff --git a/packages/deploy/deploy/000_core/001_deploy_polygon_land.ts b/packages/deploy/deploy/000_core/001_deploy_polygon_land.ts new file mode 100644 index 0000000000..6b5f0799e1 --- /dev/null +++ b/packages/deploy/deploy/000_core/001_deploy_polygon_land.ts @@ -0,0 +1,30 @@ +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const {deployments, getNamedAccounts} = hre; + const {deploy} = deployments; + + const {deployer, upgradeAdmin} = await getNamedAccounts(); + + const TRUSTED_FORWARDER_V2 = await deployments.get('TRUSTED_FORWARDER_V2'); + + await deploy('PolygonLand', { + from: deployer, + contract: + '@sandbox-smart-contracts/core/src/solc_0.8/polygon/child/land/PolygonLandV2.sol:PolygonLandV2', + proxy: { + owner: upgradeAdmin, + proxyContract: 'OpenZeppelinTransparentProxy', + execute: { + methodName: 'initialize', + args: [TRUSTED_FORWARDER_V2.address], + }, + upgradeIndex: 0, + }, + log: true, + }); +}; +export default func; +func.tags = ['PolygonLand', 'PolygonLand_deploy', 'L2']; +func.dependencies = ['TRUSTED_FORWARDER_V2']; diff --git a/packages/deploy/deploy/000_core/002_setup_polygon_land.ts b/packages/deploy/deploy/000_core/002_setup_polygon_land.ts new file mode 100644 index 0000000000..41dcdda95c --- /dev/null +++ b/packages/deploy/deploy/000_core/002_setup_polygon_land.ts @@ -0,0 +1,28 @@ +import {DeployFunction} from 'hardhat-deploy/types'; +import {HardhatRuntimeEnvironment} from 'hardhat/types'; + +export const royaltyAmount = 500; + +const func: DeployFunction = async function ( + hre: HardhatRuntimeEnvironment +): Promise { + const {deployments, getNamedAccounts} = hre; + const {execute, read, catchUnknownSigner} = deployments; + + const {sandAdmin} = await getNamedAccounts(); + const currentAdmin = await read('PolygonLand', {}, 'getAdmin'); + if (currentAdmin !== sandAdmin) { + await catchUnknownSigner( + execute( + 'PolygonLand', + {from: currentAdmin, log: true}, + 'changeAdmin', + sandAdmin + ) + ); + } +}; + +export default func; +func.tags = ['PolygonLand', 'PolygonLand_setup', 'L2']; +func.dependencies = ['PolygonLand_deploy']; diff --git a/packages/deploy/deploy/400_asset/401_deploy_asset_auth_validator.ts b/packages/deploy/deploy/400_asset/401_deploy_asset_auth_validator.ts index 332a7586be..9aba6effcf 100644 --- a/packages/deploy/deploy/400_asset/401_deploy_asset_auth_validator.ts +++ b/packages/deploy/deploy/400_asset/401_deploy_asset_auth_validator.ts @@ -8,7 +8,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { const {deployer, assetAdmin} = await getNamedAccounts(); await deploy('AuthSuperValidator', { from: deployer, - contract: 'AuthSuperValidator', + contract: + '@sandbox-smart-contracts/asset/contracts/AuthSuperValidator.sol:AuthSuperValidator', args: [assetAdmin], log: true, }); diff --git a/packages/deploy/deploy/500_marketplace/504_deploy_exchange.ts b/packages/deploy/deploy/500_marketplace/504_deploy_exchange.ts index 1599ea3f04..9c55902b9d 100644 --- a/packages/deploy/deploy/500_marketplace/504_deploy_exchange.ts +++ b/packages/deploy/deploy/500_marketplace/504_deploy_exchange.ts @@ -45,5 +45,6 @@ func.tags = ['Exchange', 'Exchange_deploy']; func.dependencies = [ 'RoyaltiesRegistry_deploy', 'OrderValidator_deploy', + 'OrderValidator_setup', 'TRUSTED_FORWARDER_V2', ]; diff --git a/packages/deploy/hardhat.config.ts b/packages/deploy/hardhat.config.ts index ba67e11bb8..e880cd4110 100644 --- a/packages/deploy/hardhat.config.ts +++ b/packages/deploy/hardhat.config.ts @@ -24,6 +24,7 @@ const importedPackages = { '@sandbox-smart-contracts/dependency-operator-filter': 'contracts/', '@sandbox-smart-contracts/dependency-royalty-management': 'contracts/', '@sandbox-smart-contracts/core': [ + '/src/solc_0.8/polygon/child/land/PolygonLandV2.sol', '/src/solc_0.8/polygon/child/sand/PolygonSand.sol', '/src/solc_0.8/test/FakeChildChainManager.sol', ], diff --git a/packages/deploy/integration_test/marketplaceLand/exchange.test.ts b/packages/deploy/integration_test/marketplaceLand/exchange.test.ts new file mode 100644 index 0000000000..6cfd8e9d64 --- /dev/null +++ b/packages/deploy/integration_test/marketplaceLand/exchange.test.ts @@ -0,0 +1,131 @@ +import {getContract, withSnapshot} from '../../utils/testUtils'; +import {expect} from 'chai'; +import {ethers} from 'hardhat'; +import {BigNumber} from 'ethers'; +import {AssetERC20, AssetERC721, OrderDefault, signOrder} from './orders'; + +const setupTest = withSnapshot( + ['Exchange', 'PolygonSand', 'PolygonLand'], + async (hre) => { + const {sandAdmin} = await hre.getNamedAccounts(); + const [user1, user2, minter] = await hre.getUnnamedAccounts(); + + const sandContract = await getContract(hre, 'PolygonSand'); + + const sandAdminSigner = await ethers.getSigner(sandAdmin); + const landContract = await getContract(hre, 'PolygonLand', sandAdminSigner); + await landContract.setMinter(minter, true); + const landContractAsMinter = await landContract.connect( + await ethers.getSigner(minter) + ); + + const childChainManager = await getContract( + hre, + 'CHILD_CHAIN_MANAGER', + sandAdminSigner + ); + await childChainManager.setPolygonAsset(landContract.address); + + const exchangeAsUser2 = await getContract( + hre, + 'Exchange', + await ethers.getSigner(user2) + ); + const orderValidatorAsAdmin = await getContract( + hre, + 'OrderValidator', + await ethers.getSigner(sandAdmin) + ); + const TSB_ROLE = await orderValidatorAsAdmin.TSB_ROLE(); + // enable land in whitelist + await orderValidatorAsAdmin.grantRole( + TSB_ROLE, + landContractAsMinter.address + ); + return { + landContract, + landContractAsMinter, + sandContract, + exchangeAsUser2, + orderValidatorAsAdmin, + sandAdmin, + user1, + user2, + minter, + mintSand: async (user: string, amount: BigNumber) => + childChainManager.callSandDeposit( + sandContract.address, + user, + ethers.utils.defaultAbiCoder.encode(['uint256'], [amount]) + ), + }; + } +); + +describe('Marketplace Land <-> Sand exchange', function () { + it('simple exchange', async function () { + const { + sandContract, + landContractAsMinter, + exchangeAsUser2, + orderValidatorAsAdmin, + user1, + user2, + mintSand, + } = await setupTest(); + const oneEth = ethers.utils.parseEther('1'); + const landTokenId = 0; + const size = 1; + await landContractAsMinter.mintQuad(user1, size, 0, 0, '0x'); + expect(await landContractAsMinter.balanceOf(user1)).to.be.equal( + size * size + ); + + await mintSand(user2, oneEth); + expect(await sandContract.balanceOf(user2)).to.be.equal(oneEth); + + await sandContract + .connect(await ethers.getSigner(user2)) + .approve(exchangeAsUser2.address, oneEth); + await landContractAsMinter + .connect(await ethers.getSigner(user1)) + .approve(exchangeAsUser2.address, landTokenId); + + const makerAsset = await AssetERC721(landContractAsMinter, landTokenId); + const takerAsset = await AssetERC20(sandContract, oneEth); + const orderLeft = OrderDefault( + user1, + makerAsset, + ethers.constants.AddressZero, + takerAsset, + 1, + 0, + 0 + ); + const orderRight = await OrderDefault( + user2, + takerAsset, + ethers.constants.AddressZero, + makerAsset, + 1, + 0, + 0 + ); + const makerSig = await signOrder(orderLeft, user1, orderValidatorAsAdmin); + const takerSig = await signOrder(orderRight, user2, orderValidatorAsAdmin); + const tx = await exchangeAsUser2.matchOrders([ + { + orderLeft, + signatureLeft: makerSig, + orderRight, + signatureRight: takerSig, + }, + ]); + const receipt = await tx.wait(); + console.log(receipt.gasUsed.toString()); + expect(await landContractAsMinter.balanceOf(user2)).to.be.equal( + size * size + ); + expect(await sandContract.balanceOf(user1)).to.be.equal(oneEth); + }); +}); diff --git a/packages/deploy/integration_test/marketplaceLand/orders.ts b/packages/deploy/integration_test/marketplaceLand/orders.ts new file mode 100644 index 0000000000..d42604e0a6 --- /dev/null +++ b/packages/deploy/integration_test/marketplaceLand/orders.ts @@ -0,0 +1,159 @@ +import {ethers} from 'hardhat'; +import {BigNumber, BigNumberish, Contract} from 'ethers'; + +export const ASSET_TYPE_TYPEHASH = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes('AssetType(uint256 assetClass,bytes data)') +); + +export function hashAssetType(a: AssetType) { + if (a.assetClass === AssetClassType.INVALID_ASSET_CLASS) { + throw new Error('Invalid assetClass' + a.assetClass); + } + return ethers.utils.keccak256( + ethers.utils.defaultAbiCoder.encode( + ['bytes32', 'uint256', 'bytes32'], + [ASSET_TYPE_TYPEHASH, a.assetClass, ethers.utils.keccak256(a.data)] + ) + ); +} + +export function hashKey(order: Order): string { + const encoded = ethers.utils.defaultAbiCoder.encode( + ['address', 'bytes32', 'bytes32', 'uint256'], + [ + order.maker, + hashAssetType(order.makeAsset.assetType), + hashAssetType(order.takeAsset.assetType), + order.salt, + ] + ); + return ethers.utils.keccak256(encoded); +} + +export async function signOrder( + order: Order, + account: string, + verifyingContract: Contract +) { + const network = await verifyingContract.provider.getNetwork(); + return await ethers.provider.send('eth_signTypedData_v4', [ + account, + { + types: { + EIP712Domain: [ + { + name: 'name', + type: 'string', + }, + { + name: 'version', + type: 'string', + }, + { + name: 'chainId', + type: 'uint256', + }, + { + name: 'verifyingContract', + type: 'address', + }, + ], + AssetType: [ + {name: 'assetClass', type: 'uint256'}, + {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'}, + ], + }, + primaryType: 'Order', + domain: { + name: 'The Sandbox Marketplace', + version: '1.0.0', + chainId: network.chainId, + verifyingContract: verifyingContract.address, + }, + message: order, + }, + ]); +} + +export type Order = { + maker: string; + makeAsset: Asset; + taker: string; + takeAsset: Asset; + salt: string; + start: string; + end: string; +}; + +export const OrderDefault = ( + maker: string, + makeAsset: Asset, + taker: string, + takeAsset: Asset, + salt: BigNumberish, + start: BigNumberish, + end: BigNumberish +): Order => ({ + maker: maker, + makeAsset, + taker: taker, + takeAsset, + salt: BigNumber.from(salt).toString(), + start: BigNumber.from(start).toString(), + end: BigNumber.from(end).toString(), +}); + +export enum AssetClassType { + INVALID_ASSET_CLASS = '0x0', + ERC20_ASSET_CLASS = '0x1', + ERC721_ASSET_CLASS = '0x2', + ERC1155_ASSET_CLASS = '0x3', +} + +export type AssetType = { + assetClass: AssetClassType; + data: string; +}; +export type Asset = { + assetType: AssetType; + value: string; +}; +export const AssetERC20 = async ( + tokenContract: Contract, + value: BigNumberish +): Promise => ({ + assetType: { + assetClass: AssetClassType.ERC20_ASSET_CLASS, + data: ethers.utils.defaultAbiCoder.encode( + ['address'], + [tokenContract.address] + ), + }, + value: BigNumber.from(value).toString(), +}); +export const AssetERC721 = async ( + tokenContract: Contract, + tokenId: BigNumberish +): Promise => ({ + assetType: { + assetClass: AssetClassType.ERC721_ASSET_CLASS, + data: ethers.utils.defaultAbiCoder.encode( + ['address', 'uint256'], + [tokenContract.address, BigNumber.from(tokenId)] + ), + }, + value: BigNumber.from(1).toString(), +}); diff --git a/packages/deploy/runtime_test/multiGiveaway/multiGiveaway.test.ts b/packages/deploy/runtime_test/multiGiveaway.test.ts similarity index 94% rename from packages/deploy/runtime_test/multiGiveaway/multiGiveaway.test.ts rename to packages/deploy/runtime_test/multiGiveaway.test.ts index 852a4bd0aa..4dcf895274 100644 --- a/packages/deploy/runtime_test/multiGiveaway/multiGiveaway.test.ts +++ b/packages/deploy/runtime_test/multiGiveaway.test.ts @@ -1,4 +1,4 @@ -import {getContract, withSnapshot} from '../../utils/testUtils'; +import {getContract, withSnapshot} from '../utils/testUtils'; import {expect} from 'chai'; const setupTest = withSnapshot(['SignedMultiGiveaway'], async (hre) => { diff --git a/packages/deploy/test/asset/AssetCreate.test.ts b/packages/deploy/test/asset/AssetCreate.test.ts index 0fdd78c51a..58de6742c7 100644 --- a/packages/deploy/test/asset/AssetCreate.test.ts +++ b/packages/deploy/test/asset/AssetCreate.test.ts @@ -7,21 +7,21 @@ const setupTest = deployments.createFixture( await getNamedAccounts(); await deployments.fixture(); const Asset = await deployments.get('Asset'); - const AssetContract = await ethers.getContractAt('Asset', Asset.address); + const AssetContract = await ethers.getContractAt(Asset.abi, Asset.address); const AssetCreate = await deployments.get('AssetCreate'); const AssetCreateContract = await ethers.getContractAt( - 'AssetCreate', + AssetCreate.abi, AssetCreate.address ); const Catalyst = await deployments.get('Catalyst'); const CatalystContract = await ethers.getContractAt( - 'Catalyst', + Catalyst.abi, Catalyst.address ); const TRUSTED_FORWARDER = await deployments.get('TRUSTED_FORWARDER_V2'); const AuthSuperValidator = await deployments.get('AuthSuperValidator'); const AuthSuperValidatorContract = await ethers.getContractAt( - 'AuthSuperValidator', + AuthSuperValidator.abi, AuthSuperValidator.address ); diff --git a/packages/deploy/test/asset/AssetReveal.test.ts b/packages/deploy/test/asset/AssetReveal.test.ts index 4a16c2a573..babe18e2b7 100644 --- a/packages/deploy/test/asset/AssetReveal.test.ts +++ b/packages/deploy/test/asset/AssetReveal.test.ts @@ -7,21 +7,21 @@ const setupTest = deployments.createFixture( await getNamedAccounts(); await deployments.fixture('Asset'); const Asset = await deployments.get('Asset'); - const AssetContract = await ethers.getContractAt('Asset', Asset.address); + const AssetContract = await ethers.getContractAt(Asset.abi, Asset.address); const AssetReveal = await deployments.get('AssetReveal'); const AssetRevealContract = await ethers.getContractAt( - 'AssetReveal', + AssetReveal.abi, AssetReveal.address ); const Catalyst = await deployments.get('Catalyst'); const CatalystContract = await ethers.getContractAt( - 'Catalyst', + Catalyst.abi, Catalyst.address ); const TRUSTED_FORWARDER = await deployments.get('TRUSTED_FORWARDER_V2'); const AuthSuperValidator = await deployments.get('AuthSuperValidator'); const AuthSuperValidatorContract = await ethers.getContractAt( - 'AuthSuperValidator', + AuthSuperValidator.abi, AuthSuperValidator.address );