diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..86fa021 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,28 @@ +version: 2.1 +executors: + cypress-14-19-0: + docker: + - image: "cypress/base:14.19.0" +orbs: + cypress: cypress-io/cypress@1 +workflows: + build: + jobs: + # first get the source code and install npm dependencies + - cypress/install: + executor: cypress-14-19-0 + # run a custom app build step + install-command: 'yarn install --frozen-lockfile' + build: 'yarn build' + - cypress/run: + # make sure app has been installed and built + # before running tests across multiple machines + # this avoids installing same dependencies 10 times + executor: cypress-14-19-0 + requires: + - cypress/install + record: false # record results on Cypress Dashboard + parallel: true # split all specs across machines + parallelism: 4 # use 4 CircleCI machines to finish quickly + group: 'all tests' # name this group "all tests" on the dashboard + start: 'yarn start' # start server before running tests \ No newline at end of file diff --git a/.eslintrc.json b/.eslintrc.json index c38f66a..e9665de 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,5 +5,12 @@ "plugin:@typescript-eslint/recommended", "plugin:react-hooks/recommended", "prettier" - ] + ], + "rules": { + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-empty-function": "off", + "@typescript-eslint/no-namespace": "off", + "@typescript-eslint/ban-types": "off", + "jsx-a11y/anchor-is-valid": "off" + } } \ No newline at end of file diff --git a/.gitignore b/.gitignore index 42b1950..0b5e5a0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +cypress/downloads/ +cypress/screenshots/ + .idea /src/types/contracts diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..b8930b5 --- /dev/null +++ b/.npmrc @@ -0,0 +1,2 @@ +@fortawesome:registry=https://npm.fontawesome.com/ +//npm.fontawesome.com/:_authToken=6326A007-3153-4B54-BFF5-8604C0BFF663 \ No newline at end of file diff --git a/cypress.config.ts b/cypress.config.ts new file mode 100644 index 0000000..114f99b --- /dev/null +++ b/cypress.config.ts @@ -0,0 +1,11 @@ +import { defineConfig } from 'cypress' + +export default defineConfig({ + e2e: { + baseUrl: 'http://localhost:3000', + video: false, + defaultCommandTimeout: 10000, + viewportWidth: 1366, + viewportHeight: 768, + }, +}) diff --git a/cypress/e2e/01-wallet.cy.ts b/cypress/e2e/01-wallet.cy.ts new file mode 100644 index 0000000..0c71a69 --- /dev/null +++ b/cypress/e2e/01-wallet.cy.ts @@ -0,0 +1,12 @@ +import { TEST_ADDRESS_NEVER_USE_SHORTENED } from '../utils/data'; + +describe('Wallet', () => { + beforeEach(() => { + cy.setupWeb3Bridge(); + }); + + it('eager connects wallet', () => { + cy.visit('/'); + cy.get('[data-testid=wallet-connect]').contains(TEST_ADDRESS_NEVER_USE_SHORTENED); + }); +}); diff --git a/cypress/e2e/category.cy.ts b/cypress/e2e/category.cy.ts new file mode 100644 index 0000000..36f632b --- /dev/null +++ b/cypress/e2e/category.cy.ts @@ -0,0 +1,41 @@ +import RoutePath, { getRoute, RouteParam } from '../../src/routes'; +import { ArenaHandler } from '../utils/ethbridge/abihandlers/Arena'; +import { SupportedChainId } from '../../src/constants/chains'; +import { ARENA_ADDRESS, MULTICALL2_ADDRESS } from '../../src/constants/addresses'; +import { BaseMulticallHandler } from '../utils/ethbridge/abihandlers/Multicall'; +import { IPFS_SERVER_URL, songMeta } from '../utils/data'; + +describe('Category', () => { + it('loads songs', () => { + cy.intercept( + { + url: `${IPFS_SERVER_URL}**`, + }, + { + statusCode: 404, + }, + ); + cy.intercept( + { + url: `${IPFS_SERVER_URL}/choice/2.json`, + }, + { + body: songMeta, + }, + ); + const topicId = 0; + cy.setupWeb3Bridge(); + cy.setAbiHandler(ARENA_ADDRESS[SupportedChainId.GOERLI], new ArenaHandler()); + cy.setAbiHandler(MULTICALL2_ADDRESS[SupportedChainId.GOERLI], new BaseMulticallHandler()); + + cy.visit( + getRoute(RoutePath.CATEGORY, { + [RouteParam.CATEGORY_ID]: String(topicId), + }), + ); + cy.get('[data-testid=category-list-item-0]').should('exist'); + cy.get('[data-testid=category-list-item-1]').should('exist'); + cy.get('[data-testid=category-list-item-0-meta]').should('not.exist'); + cy.get('[data-testid=category-list-item-1-meta]').should('exist'); + }); +}); diff --git a/cypress/fixtures/example.json b/cypress/fixtures/example.json new file mode 100644 index 0000000..02e4254 --- /dev/null +++ b/cypress/fixtures/example.json @@ -0,0 +1,5 @@ +{ + "name": "Using fixtures to represent data", + "email": "hello@cypress.io", + "body": "Fixtures are a great way to mock data for responses to routes" +} diff --git a/cypress/support/commands.js b/cypress/support/commands.js new file mode 100644 index 0000000..16b78b0 --- /dev/null +++ b/cypress/support/commands.js @@ -0,0 +1,56 @@ +// *********************************************** +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +/* eslint-disable no-undef */ + +import { getCustomizedBridge } from '../utils/ethbridge/CustomizedBridge'; + +Cypress.Commands.add('shouldBeCalled', (alias, timesCalled) => { + expect( + cy.state('requests').filter((call) => call.alias === alias), + `${alias} should have been called ${timesCalled} times`, + ).to.have.length(timesCalled); +}); + +// https://github.com/cypress-io/cypress/issues/2752#issuecomment-1039285381 +Cypress.on('window:before:load', (win) => { + let copyText; + + if (!win.navigator.clipboard) { + win.navigator.clipboard = { + __proto__: {}, + }; + } + + win.navigator.clipboard.__proto__.writeText = (text) => (copyText = text); + win.navigator.clipboard.__proto__.readText = () => copyText; +}); + +Cypress.Commands.add('setupWeb3Bridge', () => { + const web3Bridge = getCustomizedBridge() + cy.wrap(web3Bridge).as('web3Bridge') + cy.on('window:before:load', (win) => { + win.ethereum = web3Bridge; + }); +}); + +Cypress.Commands.add('setAbiHandler', (address, abiHandler) => { + cy.get('@web3Bridge').then(web3Bridge => { + web3Bridge.setHandler(address, abiHandler) + }); +}); + +beforeEach(() => { + cy.on('window:before:load', (win) => { + cy.spy(win.console, 'error').as('spyWinConsoleError'); + cy.spy(win.console, 'warn').as('spyWinConsoleWarn'); + }); +}); + +afterEach(() => { + cy.get('@spyWinConsoleError').should('have.callCount', 0); + cy.get('@spyWinConsoleWarn').should('have.callCount', 0); +}); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts new file mode 100644 index 0000000..698b01a --- /dev/null +++ b/cypress/support/commands.ts @@ -0,0 +1,37 @@ +/// +// *********************************************** +// This example commands.ts shows you how to +// create various custom commands and overwrite +// existing commands. +// +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** +// +// +// -- This is a parent command -- +// Cypress.Commands.add('login', (email, password) => { ... }) +// +// +// -- This is a child command -- +// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... }) +// +// +// -- This is a dual command -- +// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... }) +// +// +// -- This will overwrite an existing command -- +// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... }) +// +// declare global { +// namespace Cypress { +// interface Chainable { +// login(email: string, password: string): Chainable +// drag(subject: string, options?: Partial): Chainable +// dismiss(subject: string, options?: Partial): Chainable +// visit(originalFn: CommandOriginalFn, url: string, options: Partial): Chainable +// } +// } +// } \ No newline at end of file diff --git a/cypress/support/e2e.ts b/cypress/support/e2e.ts new file mode 100644 index 0000000..f80f74f --- /dev/null +++ b/cypress/support/e2e.ts @@ -0,0 +1,20 @@ +// *********************************************************** +// This example support/e2e.ts is processed and +// loaded automatically before your test files. +// +// This is a great place to put global configuration and +// behavior that modifies Cypress. +// +// You can change the location of this file or turn off +// automatically serving support files with the +// 'supportFile' configuration option. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.js using ES2015 syntax: +import './commands' + +// Alternatively you can use CommonJS syntax: +// require('./commands') \ No newline at end of file diff --git a/cypress/support/index.ts b/cypress/support/index.ts new file mode 100644 index 0000000..2feaea7 --- /dev/null +++ b/cypress/support/index.ts @@ -0,0 +1,24 @@ +// *********************************************************** +// This file is processed and loaded automatically before your test files. +// +// You can read more here: +// https://on.cypress.io/configuration +// *********************************************************** + +// Import commands.ts using ES2015 syntax: +import './commands'; +import { AbiHandler } from '../utils/ethbridge/AbiHandler'; + +declare global { + namespace Cypress { + interface Chainable { + /** + * Custom command to select DOM element by data-cy attribute. + * @example cy.dataCy('greeting') + */ + setupWeb3Bridge(): void; + + setAbiHandler(address: string, handler: AbiHandler): void; + } + } +} diff --git a/cypress/tsconfig.json b/cypress/tsconfig.json new file mode 100644 index 0000000..a521526 --- /dev/null +++ b/cypress/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "esModuleInterop": true, + "resolveJsonModule": true, + "strict": true, + "baseUrl": "../node_modules", + "target": "es5", + "lib": [ + "es5", + "dom" + ], + "types": [ + "cypress" + ], + }, + "include": [ + "**/*.ts" + ] +} diff --git a/cypress/utils/data.ts b/cypress/utils/data.ts new file mode 100644 index 0000000..b62a9e8 --- /dev/null +++ b/cypress/utils/data.ts @@ -0,0 +1,61 @@ +import { shortenAddress } from '../../src/utils'; +import { Wallet } from '@ethersproject/wallet'; +import { ChoiceStruct, TopicStruct } from '../../src/types/contracts/Arena'; +import { SongMeta } from '../../src/types'; + +export const TEST_PRIVATE_KEY = '0xe580410d7c37d26c6ad1a837bbae46bc27f9066a466fb3a66e770523b4666d19'; +// address of the above key +export const TEST_ADDRESS_NEVER_USE = new Wallet(TEST_PRIVATE_KEY).address; +export const TEST_ADDRESS_NEVER_USE_SHORTENED = shortenAddress(TEST_ADDRESS_NEVER_USE); + +export const SAMPLE_ERROR_MESSAGE = 'An error occurred'; + +export const IPFS_SERVER_URL = 'https://some.ipfs.server'; + +export const topics: { + [topicId: number]: TopicStruct; +} = { + 0: { + cycleDuration: 2, + startBlock: 0, + sharePerCyclePercentage: 10000, + prevContributorsFeePercentage: 1200, + topicFeePercentage: 500, + maxChoiceFeePercentage: 2500, + relativeSupportThreshold: 0, + fundingPeriod: 0, + fundingPercentage: 0, + funds: '0x211eEBa0ebe516744614C35572555BdFDD13424d', + metaDataUrl: IPFS_SERVER_URL + '/topic/topic1.json', + }, +}; + +export const choices: { + [topicId: number]: { + [choiceId: number]: ChoiceStruct; + }; +} = { + 0: { + ...Object.assign({}, [ + ...[1, 2, 3, 4, 5, 6].map((i) => ({ + description: i + ' Song', + funds: '0x211eEBa0ebe516744614C35572555BdFDD13424d', + feePercentage: 1000, + fundingTarget: '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff', + metaDataUrl: IPFS_SERVER_URL + `/choice/${i}.json`, + })), + ]), + }, +}; + +export const songMeta: SongMeta = { + thumbnail: 'https://bafybeicp7kjqwzzyfuryefv2l5q23exl3dbd6rgmuqzxs3cy6vaa2iekka.ipfs.w3s.link/sample.png', + title: 'Dark Days and Beautiful', + tags: [ + { subject: 'Mood', title: 'Confused' }, + { subject: 'Genre', title: 'Folk' }, + ], + by: 'jonathan.eth', + date: 'June 9, 2022', + opensea: 'somelink', +}; diff --git a/cypress/utils/ethbridge/AbiHandler.ts b/cypress/utils/ethbridge/AbiHandler.ts new file mode 100644 index 0000000..2f09f61 --- /dev/null +++ b/cypress/utils/ethbridge/AbiHandler.ts @@ -0,0 +1,27 @@ +import { decodeEthCall, encodeEthResult } from './abiutils'; +import { CustomizedBridgeContext } from './CustomizedBridge'; + +export class AbiHandler { + abi = {}; + + methods: { [name: string]: (...args: any[]) => any } = {}; + + async handleCall(context: CustomizedBridgeContext, data: string, setResult?: (arg0: string) => void) { + const decoded = decodeEthCall(this.abi, data); + if (decoded.method === 'multicall') { + const [deadline, [data]] = decoded.inputs; + await this.handleCall(context, data, setResult); + return; + } + const method = this.methods[decoded.method]; + if (method) { + const res = await method(context, decoded.inputs); + setResult?.(encodeEthResult(this.abi, decoded.method, res)); + } + } + + async handleTransaction(context: CustomizedBridgeContext, data: string, setResult: (arg0: string) => void) { + await this.handleCall(context, data); + setResult(context.getFakeTransactionHash()); + } +} diff --git a/cypress/utils/ethbridge/CustomizedBridge.ts b/cypress/utils/ethbridge/CustomizedBridge.ts new file mode 100644 index 0000000..dc1a280 --- /dev/null +++ b/cypress/utils/ethbridge/CustomizedBridge.ts @@ -0,0 +1,315 @@ +// *********************************************** +// For more comprehensive examples of custom +// commands please read more here: +// https://on.cypress.io/custom-commands +// *********************************************** + +import { Wallet } from '@ethersproject/wallet'; +import { JsonRpcProvider } from '@ethersproject/providers'; +import { formatChainId } from '../../../src/utils'; + +import { SAMPLE_ERROR_MESSAGE, TEST_ADDRESS_NEVER_USE, TEST_PRIVATE_KEY } from '../data'; +import { BigNumber } from '@ethersproject/bignumber'; +import { + fakeBlockByNumberResponse, + fakeTransactionByHashResponse, + fakeTransactionReceipt, + latestBlock, +} from '../fake_tx_data'; +import { NETWORK_URLS, SupportedChainId } from '../../../src/constants/chains'; +import { keccak256 } from './abiutils'; +import { Eip1193Bridge } from '@ethersproject/experimental'; +import { GENERIC_ERROR_CODE, GENERIC_ERROR_CODE_2, USER_DENIED_REQUEST_ERROR_CODE } from '../../../src/utils/web3'; +import { AbiHandler } from './AbiHandler'; + +function isTheSameAddress(address1: string, address2: string) { + return address1.toLowerCase() === address2.toLowerCase(); +} + +function sleep(ms: number) { + return new Promise((resolve) => setTimeout(resolve, ms)); +} + +export class CustomizedBridgeContext { + chainId = formatChainId(String(SupportedChainId.GOERLI)); + supportedChainIds: string[] = [formatChainId(String(SupportedChainId.GOERLI))]; + + latestBlockNumber = 1; + fakeTransactionIndex = 0; + handlers: { [key: string]: AbiHandler } = {}; + + getLatestBlock() { + this.latestBlockNumber++; + return Object.assign(latestBlock, { + number: this.latestBlockNumber, + }); + } + + getFakeTransactionHash() { + return keccak256([this.fakeTransactionIndex++]); + } + + setHandler(address: string, handler: AbiHandler) { + this.handlers[address] = handler; + } +} + +export enum EventHandlerKey { + CHAIN_CHANGED = 'chainChanged', + ACCOUNTS_CHANGED = 'accountsChanged', + CLOSE = 'close', + NETWORK_CHANGED = 'networkChanged', +} + +export enum TransactionStatus { + SUCCESS = 'success', + INSUFFICIENT_FUND = 'insufficientFund', + USER_DENIED = 'rejected', + FAILED = 'failed', +} + +const insufficientFundTransactionError = { + code: GENERIC_ERROR_CODE_2, + message: `err: insufficient funds for gas * price + value: address ${TEST_ADDRESS_NEVER_USE} have 2000 want 10000000000000000000000000 (supplied gas 14995852)`, +}; +const insufficientFundGasEstimateError = { + code: GENERIC_ERROR_CODE, + message: 'Internal JSON-RPC error.', + data: { + code: GENERIC_ERROR_CODE_2, + message: `insufficient funds for transfer: address ${TEST_ADDRESS_NEVER_USE}`, + }, +}; +const userDeniedTransactionError = { + code: USER_DENIED_REQUEST_ERROR_CODE, + message: 'MetaMask Tx Signature: User denied transaction signature.', + stack: + '{\n "code": 4001,\n "message": "MetaMask Tx Signature: User denied transaction signature.",\n "stack": "Error: MetaMask Tx Signature: User denied transaction signature.\\n...', +}; + +function enumKeys(obj: O): K[] { + return Object.keys(obj).filter((k) => Number.isNaN(+k)) as K[]; +} + +export class CustomizedBridge extends Eip1193Bridge { + context = new CustomizedBridgeContext(); + + eventListeners = { + [EventHandlerKey.CHAIN_CHANGED]: function handleChainChanged(chainId: string | number) {}, + [EventHandlerKey.ACCOUNTS_CHANGED]: function handleAccountsChanged(accounts: string[]) {}, + [EventHandlerKey.CLOSE]: function handleClose(code: number, reason: string) {}, + [EventHandlerKey.NETWORK_CHANGED]: function handleNetworkChanged(networkId: string | number) {}, + }; + + transactionStatus = TransactionStatus.SUCCESS; + transactionWaitTime = 0; + + setTransactionStatus(status: TransactionStatus) { + this.transactionStatus = status; + } + + setTransactionWaitTime(waitTime: number) { + this.transactionWaitTime = waitTime; + } + + on(eventName: string | symbol, listener: (...args: any[]) => void) { + let found = false; + for (const k of enumKeys(EventHandlerKey)) { + if (eventName === EventHandlerKey[k]) { + found = true; + this.eventListeners[eventName] = listener; + break; + } + } + if (!found) { + console.error(`Bridge: Unknown Event Key ${String(eventName)}`); + throw Error(`Bridge: Unknown Event Key ${String(eventName)}`); + } + return this; + } + + switchEthereumChainSpy(chainId: string) {} + + addEthereumChainSpy(chainId: string) {} + + setHandler(address: string, handler: AbiHandler) { + this.context.setHandler(address, handler); + } + + async sendAsync(...args: any[]) { + console.debug('sendAsync called', ...args); + return this.send(...args); + } + + getSendArgs(args: any[]) { + console.debug('send called', ...args); + const isCallbackForm = typeof args[0] === 'object' && typeof args[1] === 'function'; + let callback; + let method; + let params; + if (isCallbackForm) { + callback = args[1]; + method = args[0].method; + params = args[0].params; + } else { + method = args[0]; + params = args[1]; + } + return { + isCallbackForm, + callback, + method, + params, + }; + } + + async send(...args: any[]) { + const { isCallbackForm, callback, method, params } = this.getSendArgs(args); + let result = null; + let resultIsSet = false; + let runError = null; + let errorIsSet = false; + + function setResult(r: any) { + result = r; + resultIsSet = true; + } + + function setError(e: any) { + runError = e; + errorIsSet = true; + } + + if (method === 'eth_requestAccounts' || method === 'eth_accounts') { + setResult([TEST_ADDRESS_NEVER_USE]); + } + if (method === 'wallet_switchEthereumChain') { + this.switchEthereumChainSpy(params[0].chainId); + if (this.context.supportedChainIds.includes(params[0].chainId)) { + this.context.chainId = params[0].chainId; + this.eventListeners[EventHandlerKey.NETWORK_CHANGED](params[0].chainId); + this.eventListeners[EventHandlerKey.CHAIN_CHANGED](params[0].chainId); + setResult(null); + } else { + const chainId = params[0].chainId; + const error = { + code: 4902, // To-be-standardized "unrecognized chain ID" error + message: `Unrecognized chain ID "${chainId}". Try adding the chain using wallet_addEthereumChain first.`, + }; + setError(error); + } + } + if (method === 'wallet_addEthereumChain') { + this.addEthereumChainSpy(params[0].chainId); + this.context.supportedChainIds.push(params[0].chainId); + setResult(null); + } + if (method === 'eth_chainId') { + setResult(formatChainId(String(this.context.chainId))); + } + if (method === 'eth_getBlockByNumber') { + if (params[0] === 'latest') { + setResult(this.context.getLatestBlock()); + } else { + const [blockNumber, returnFullHashes] = params; + setResult( + Object.assign(fakeBlockByNumberResponse, { + number: BigNumber.from(blockNumber).toNumber(), + }), + ); + } + } + if (method === 'eth_getTransactionByHash') { + const [transactionHash] = params; + setResult( + Object.assign(fakeTransactionByHashResponse, { + hash: transactionHash, + }), + ); + } + if (method === 'eth_getTransactionReceipt') { + const [transactionHash] = params; + const latestBlock = this.context.getLatestBlock(); + const resultLocal = Object.assign(fakeTransactionReceipt, { + transactionHash, + blockHash: latestBlock.hash, + blockNumber: latestBlock.number, + logs: fakeTransactionReceipt.logs.map((log) => Object.assign(log, transactionHash)), + }); + setResult(resultLocal); + } + if (method === 'eth_blockNumber') { + setResult(this.context.getLatestBlock().number); + } + if (method === 'eth_call') { + for (const contractAddress in this.context.handlers) { + if (isTheSameAddress(contractAddress, params[0].to)) { + await this.context.handlers[contractAddress].handleCall(this.context, params[0].data, setResult); + } + } + } + if (method === 'eth_estimateGas') { + if (this.transactionStatus === TransactionStatus.INSUFFICIENT_FUND) { + setError(insufficientFundGasEstimateError); + } else { + setResult('0xba7f'); + } + } + if (method === 'eth_sendTransaction') { + for (const contractAddress in this.context.handlers) { + if (isTheSameAddress(contractAddress, params[0].to)) { + await this.context.handlers[contractAddress].handleTransaction(this.context, params[0].data, setResult); + } + } + if (this.transactionStatus === TransactionStatus.SUCCESS) { + setResult(this.context.getFakeTransactionHash()); + } else if (this.transactionStatus === TransactionStatus.USER_DENIED) { + setError(userDeniedTransactionError); + } else if (this.transactionStatus === TransactionStatus.INSUFFICIENT_FUND) { + setError(insufficientFundTransactionError); + } else { + setError({ error: { message: SAMPLE_ERROR_MESSAGE } }); + } + if (this.transactionWaitTime) { + await sleep(this.transactionWaitTime); + } + } + + if (errorIsSet) { + if (isCallbackForm) { + callback(runError, null); + } else { + throw runError; + } + } else if (resultIsSet) { + if (isCallbackForm) { + callback(null, { result }); + } else { + return result; + } + } else { + try { + const result = await super.send(method, params); + if (isCallbackForm) { + callback(null, { result }); + } else { + return result; + } + } catch (error) { + if (isCallbackForm) { + callback(error, null); + } else { + throw error; + } + } + } + } +} + +const defaultChainId = SupportedChainId.GOERLI; +export const provider = new JsonRpcProvider(NETWORK_URLS[defaultChainId], defaultChainId); +export const signer = new Wallet(TEST_PRIVATE_KEY, provider); + +export function getCustomizedBridge() { + return new CustomizedBridge(signer, provider); +} diff --git a/cypress/utils/ethbridge/abihandlers/Arena.ts b/cypress/utils/ethbridge/abihandlers/Arena.ts new file mode 100644 index 0000000..9548090 --- /dev/null +++ b/cypress/utils/ethbridge/abihandlers/Arena.ts @@ -0,0 +1,26 @@ +import { BigNumber } from '@ethersproject/bignumber/lib.esm'; +import ArenaJson from '@attentionstreams/contracts/artifacts/contracts/main/Arena.sol/Arena.json'; + +import { AbiHandler } from '../AbiHandler'; +import { CustomizedBridgeContext } from '../CustomizedBridge'; +import { choices } from '../../data'; + +function isTheSameAddress(address1: string, address2: string) { + return address1.toLowerCase() === address2.toLowerCase(); +} + +export class ArenaHandler extends AbiHandler { + abi = ArenaJson.abi; + + methods = { + async nextChoiceId(context: CustomizedBridgeContext, decodedInput: [BigNumber]) { + const [topicId] = decodedInput; + return [Object.values(choices[topicId.toNumber()]).length]; + }, + + async topicChoices(context: CustomizedBridgeContext, decodedInput: [BigNumber, BigNumber]) { + const [topicId, choiceId] = decodedInput; + return [Object.values(choices[topicId.toNumber()][choiceId.toNumber()])]; + }, + }; +} diff --git a/cypress/utils/ethbridge/abihandlers/Multicall.ts b/cypress/utils/ethbridge/abihandlers/Multicall.ts new file mode 100644 index 0000000..eb0547a --- /dev/null +++ b/cypress/utils/ethbridge/abihandlers/Multicall.ts @@ -0,0 +1,29 @@ +import { FAKE_BLOCK_HASH } from '../../fake_tx_data'; +import MULTICALL2_ABI from '../../../../src/abis/MULTICALL2.json'; +import { CustomizedBridgeContext } from '../CustomizedBridge'; +import { AbiHandler } from '../AbiHandler'; + +function isTheSameAddress(address1: string, address2: string) { + return address1.toLowerCase() === address2.toLowerCase(); +} + +export class BaseMulticallHandler extends AbiHandler { + abi = MULTICALL2_ABI; + methods = { + async tryBlockAndAggregate(context: CustomizedBridgeContext, decodedInput: any[]) { + const [_requireSuccess, calls] = decodedInput; + const results: any[] = []; + for (const call of calls) { + const [callAddress, callInput] = call; + for (const contractAddress in context.handlers) { + if (isTheSameAddress(contractAddress, callAddress)) { + await context.handlers[contractAddress].handleCall(context, callInput, (r: string) => + results.push([true, r]), + ); + } + } + } + return [0, FAKE_BLOCK_HASH, results]; + }, + }; +} diff --git a/cypress/utils/ethbridge/abiutils.ts b/cypress/utils/ethbridge/abiutils.ts new file mode 100644 index 0000000..e3dce6c --- /dev/null +++ b/cypress/utils/ethbridge/abiutils.ts @@ -0,0 +1,26 @@ +import { ethers } from 'ethers'; +import { BigNumber } from '@ethersproject/bignumber/lib.esm'; +import { BytesLike } from '@ethersproject/bytes'; + +const InputDataDecoder = require('ethereum-input-data-decoder'); + +export function keccak256(data: BytesLike): string { + return ethers.utils.keccak256(data); +} + +export function encodeEthResult(abi: any, funcName: string, result: (BigNumber | string | number)[]) { + const iface = new ethers.utils.Interface(abi); + return iface.encodeFunctionResult(funcName, result); +} + +export type DecodedCall = { + inputs: any[]; + method: string; + names: any[]; + types: any[]; +}; + +export function decodeEthCall(abi: any, input: any): DecodedCall { + const decoder = new InputDataDecoder(abi); + return decoder.decodeData(input); +} diff --git a/cypress/utils/fake_tx_data.ts b/cypress/utils/fake_tx_data.ts new file mode 100644 index 0000000..75f2422 --- /dev/null +++ b/cypress/utils/fake_tx_data.ts @@ -0,0 +1,602 @@ +export const FAKE_BLOCK_HASH = '0xeed54f1dd0adad878c624694038ac3c70631ec800b150b9caf9eedd4aea3df95' + +export const fakeBlockByNumberResponse = { + hash: '0x0001ab190000014f9cbe48dd3164830a173dd94b042aaba7b680631f22df6598', + parentHash: '0x0001ab1900000144ea26c0cb4a150aec7d012c9eb3f975073e993aa18d6d20f2', + number: 37678828, + timestamp: 1651832123, + nonce: '0x0000000000000000', + difficulty: 0, + gasLimit: { + type: 'BigNumber', + hex: '0xffffffffffff', + }, + gasUsed: { + type: 'BigNumber', + hex: '0x3da9cd', + }, + miner: '0x0000000000000000000000000000000000000000', + extraData: '0x', + transactions: [ + '0x9a5ea960b2da1b35ac3e04be63cfcc39b4380d67c117246115b17b25e1d2da0a', + '0xedbf93e3853419d01b177c2ad0bf9782cc9b1edd4c53fcb79da6a26075eea0fa', + '0xa273709cf7d68fef46d0f925e1c72287b9e8d639e6ccfb8a7b0363bfb08e9b49', + '0x34afd383c5bf100d754c3494eb2a4f3ebdcf1c41965e5c50b831ad2cd7886f52', + '0x9cd38283852fd8da61da18d8a6caa8eff07b8fe4b82afe81c3be85c6d9471eb4', + '0xd8901b2981acfdd3fab18e8e7c6f03b805a295ff37a1df8159bce41f7ea27a31', + '0x38147a5dc5bfdbe9bbc959a6a94c635b397e3a17962187ff5b5134f8fa175136', + '0x0f36fcd334e0a6cd5567c3cb1912dc9d03d7e28d2465c3c7b69431f07a0158d7', + '0x288126196a214cd226e0894a19f513dbde71a2702243aec03a9ceaf7e3e600fc', + ], + baseFeePerGas: { + type: 'BigNumber', + hex: '0x174876e800', + }, + _difficulty: { + type: 'BigNumber', + hex: '0x00', + }, +} + +export const fakeTransactionByHashResponse = { + hash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + type: 0, + accessList: null, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + blockNumber: 37445406, + transactionIndex: 1, + confirmations: 233051, + from: '0x14C07A5F107a9d15A6C2d0C131DCB3933386503E', + gasPrice: { + type: 'BigNumber', + hex: '0x22ecb25c00', + }, + gasLimit: { + type: 'BigNumber', + hex: '0x0fe215', + }, + to: '0x2e0b129d3Dd14FcD676695c0A7dDeF9FcC13E74E', + value: { + type: 'BigNumber', + hex: '0x00', + }, + nonce: 14545, + data: '0xfdb5a03e', + r: '0xd0f28cc47e863b3d3e0bc1a930b9619301e23232d6e4294fb7160c402373daa8', + s: '0x737f7b77d2ec262383cc0acf0e21d1abdd8101ed6b110631b45a084475e2b0bd', + v: 535, + creates: null, + chainId: 250, +} + +export const fakeTransactionReceipt = { + to: '0x2e0b129d3Dd14FcD676695c0A7dDeF9FcC13E74E', + from: '0x14C07A5F107a9d15A6C2d0C131DCB3933386503E', + contractAddress: null, + transactionIndex: 1, + gasUsed: { + type: 'BigNumber', + hex: '0x0bec85', + }, + logsBloom: + '0x00200800000802000000000081000000000100000400020000000000008001000000000000000200008000000000100108000000802000000000800000200000001000000009048000000008000001220000000000800000000100000000010000000000020000000040400000000800000100000008000000000010000000080000000100000002000000000000000000004880040000080000004000000810060000000400000000000100020000000000000000090010000000000000000000000042000280110100000001020000000000000080001000000400000020210010040000000000000000000000002000400000404200000000000000000002', + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + logs: [ + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x000000000000000000000000b934be56cfdd87ccd547026b8f5a566efb5a768e', + ], + data: '0x000000000000000000000000000000000000000000000000001ad4d3925f1ddd', + logIndex: 0, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724', + '0x000000000000000000000000b934be56cfdd87ccd547026b8f5a566efb5a768e', + ], + data: '0x000000000000000000000000000000000000000000000a736f763d13a7181def000000000000000000000000000000000000000000000a736f9111e739773bcc', + logIndex: 1, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd', + ], + data: '0x000000000000000000000000000000000000000000000000010c5043b7b72aaa', + logIndex: 2, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724', + '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd', + ], + data: '0x0000000000000000000000000000000000000000000019a674d497f5a367f3500000000000000000000000000000000000000000000019a675e0e8395b1f1dfa', + logIndex: 3, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x00000000000000000000000000000000000000000000000015cc78c135d16d3a', + logIndex: 4, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724', + '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd', + ], + data: '0x0000000000000000000000000000000000000000000019a675e0e8395b1f1dfa0000000000000000000000000000000000000000000019a660146f78254db0c0', + logIndex: 5, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000015cc78c135d16d3a', + logIndex: 6, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x0000000000000000000000000000000000000000000000000000000000000000', + logIndex: 7, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x2b2929E785374c651a81A63878Ab22742656DcDd', + topics: [ + '0xf279e6a1f5e320cca91135676d9cb6e44ca8a08c0b88342bcdb1144f6511b568', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000000000000000000000000000000000000000003a', + ], + data: '0x0000000000000000000000000000000000000000000000000000000000000000', + logIndex: 8, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x00000000000000000000000014c07a5f107a9d15a6c2d0c131dcb3933386503e', + ], + data: '0x0000000000000000000000000000000000000000000000000037ce01ee9e4117', + logIndex: 9, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x00000000000000000000000000000000000000000000000015cc78c135d16d3a0000000000000000000000000000000000000000000000001594aabf47332c23', + logIndex: 10, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724', + '0x00000000000000000000000014c07a5f107a9d15a6c2d0c131dcb3933386503e', + ], + data: '0x0000000000000000000000000000000000000000000000002721788b169d10000000000000000000000000000000000000000000000000002759468d053b5117', + logIndex: 11, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000ec7178f4c41f346b2721907f5cf7628e388a7a58', + ], + data: '0x0000000000000000000000000000000000000000000000001594aabf47332c23', + logIndex: 12, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29', + ], + data: '0xffffffffffffffffffffffffffffffffffffffffffffff4ae0c641f53545ad0c', + logIndex: 13, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x0000000000000000000000000000000000000000000000001594aabf47332c230000000000000000000000000000000000000000000000000000000000000000', + logIndex: 14, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x841FAD6EAe12c286d1Fd18d1d525DFfA75C7EFFE', + topics: [ + '0xdec2bacdd2f05b59de34da9b523dff8be42e5e38e818c82fdb0bae774387a724', + '0x000000000000000000000000ec7178f4c41f346b2721907f5cf7628e388a7a58', + ], + data: '0x0000000000000000000000000000000000000000000121c62e34fd556bd356730000000000000000000000000000000000000000000121c643c9a814b3068296', + logIndex: 15, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x000000000000000000000000ec7178f4c41f346b2721907f5cf7628e388a7a58', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x000000000000000000000000000000000000000000000000c340df61ccc04c9f', + logIndex: 16, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0xEc7178F4C41f346b2721907F5cF7628E388A7a58', + topics: ['0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1'], + data: '0x0000000000000000000000000000000000000000000a430252bb8049df6714190000000000000000000000000000000000000000000121c643c9a814b3068296', + logIndex: 17, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0xEc7178F4C41f346b2721907F5cF7628E388A7a58', + topics: [ + '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822', + '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001594aabf47332c23000000000000000000000000000000000000000000000000c340df61ccc04c9f0000000000000000000000000000000000000000000000000000000000000000', + logIndex: 18, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000648a7452da25b4fb4bdb79badf374a8f8a5ea2b5', + ], + data: '0x00000000000000000000000000000000000000000000000061b91a175713f5b6', + logIndex: 19, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', + topics: [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29', + ], + data: '0xfffffffffffffffffffffffffffffffffffffffffffff8d4dcc0b26b09cc6bf1', + logIndex: 20, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x000000000000000000000000648a7452da25b4fb4bdb79badf374a8f8a5ea2b5', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x00000000000000000000000000000000000000000000000138d2689e559928d9', + logIndex: 21, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5', + topics: ['0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1'], + data: '0x0000000000000000000000000000000000000000000034f67922d1b9f6c14ad700000000000000000000000000000000000000000000a9dfdefbd32f0f648609', + logIndex: 22, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5', + topics: [ + '0xd78ad95fa46c994b6551d0da85fc275fe613ce37657fb8d5e3d130840159d822', + '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x00000000000000000000000000000000000000000000000061b91a175713f5b60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000138d2689e559928d9', + logIndex: 23, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000648a7452da25b4fb4bdb79badf374a8f8a5ea2b5', + ], + data: '0x0000000000000000000000000000000000000000000000006187c54a75ac56e9', + logIndex: 24, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x21be370D5312f44cB42ce377BC9b8a0cEF1A4C83', + topics: [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29', + ], + data: '0xfffffffffffffffffffffffffffffffffffffffffffff8d47b38ed2094201508', + logIndex: 25, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000648a7452da25b4fb4bdb79badf374a8f8a5ea2b5', + ], + data: '0x00000000000000000000000000000000000000000000000138d2689e559928d9', + logIndex: 26, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0xF24Bcf4d1e507740041C9cFd2DddB29585aDCe1e', + topics: [ + '0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29', + ], + data: '0xfffffffffffffffffffffffffffffffffffffffffffff82d3b4d31103332b24d', + logIndex: 27, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000003b3fdc40582a957206aed119842f2313de9ee21b', + ], + data: '0x0000000000000000000000000000000000000000000000000009809d62ed0753', + logIndex: 28, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000000000000000000000000000000000000000000000', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + ], + data: '0x000000000000000000000000000000000000000000000000942ea785e044b922', + logIndex: 29, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5', + topics: ['0x1c411e9a96e071241c2f21f7726b17ae89e3cab4c78be50e062b03a9fffbbad1'], + data: '0x0000000000000000000000000000000000000000000034f6daaa97046c6da1c000000000000000000000000000000000000000000000a9e117ce3bcd64fdaee2', + logIndex: 30, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5', + topics: [ + '0x4c209b5fc8ad50758f13e2e1088ba56a560dff690a1c6fef26394f4c03821c4f', + '0x000000000000000000000000f491e7b69e4244ad4002bc14e878a34207e38c29', + ], + data: '0x0000000000000000000000000000000000000000000000006187c54a75ac56e900000000000000000000000000000000000000000000000138d2689e559928d9', + logIndex: 31, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x648a7452DA25B4fB4BDB79bADf374a8f8a5ea2b5', + topics: [ + '0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x0000000000000000000000002b2929e785374c651a81a63878ab22742656dcdd', + ], + data: '0x000000000000000000000000000000000000000000000000942ea785e044b922', + logIndex: 32, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x2b2929E785374c651a81A63878Ab22742656DcDd', + topics: [ + '0x90890809c654f11d6e72a28fa60149770a0d11ec6c92319d6ceb2bb0a4ea1a15', + '0x0000000000000000000000002e0b129d3dd14fcd676695c0a7ddef9fcc13e74e', + '0x000000000000000000000000000000000000000000000000000000000000003a', + ], + data: '0x000000000000000000000000000000000000000000000000942ea785e044b922', + logIndex: 33, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x2e0b129d3Dd14FcD676695c0A7dDeF9FcC13E74E', + topics: [ + '0xc003f45bc224d116b6d079100d4ab57a5b9633244c47a5a92a176c5b79a85f28', + '0x00000000000000000000000014c07a5f107a9d15a6c2d0c131dcb3933386503e', + ], + data: '0x00000000000000000000000000000000000000000000000015cc78c135d16d3a0000000000000000000000000000000000000000000000000037ce01ee9e4117', + logIndex: 34, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + { + transactionIndex: 1, + blockNumber: 37445406, + transactionHash: '0x8c417b4770b68fed1dd27c6aa3c5a399910f6d8f20630b3a588ab8141d5bff43', + address: '0x2e0b129d3Dd14FcD676695c0A7dDeF9FcC13E74E', + topics: ['0x8a0df8ef054fae2c3d2d19a7b322e864870cc9fd3cb07fb9526309c596244bf4'], + data: '0x000000000000000000000000000000000000000000002aac1b6b749155b73638', + logIndex: 35, + blockHash: '0x0001a7ec000014105805725cb9b49784f7b10e9cebd175e3d217a655e35ff057', + }, + ], + blockNumber: 37445406, + confirmations: 233328, + cumulativeGasUsed: { + type: 'BigNumber', + hex: '0x0ca7e2', + }, + effectiveGasPrice: { + type: 'BigNumber', + hex: '0x22ecb25c00', + }, + status: 1, + type: 0, + byzantium: true, +} + +export const latestBlock = { + hash: '0x0000000c000001832042174315212d389e57129df4d9cfef47b5de83e73b7ddb', + parentHash: '0x0000000c00000177f0a985c5f59a474b87ba510f05f243310444a69248652a95', + number: 1280, + timestamp: 1577607303, + nonce: '0x0000000000000000', + difficulty: 0, + gasLimit: { + type: 'BigNumber', + hex: '0xffffffffffff', + }, + gasUsed: { + type: 'BigNumber', + hex: '0x5af7', + }, + miner: '0x0000000000000000000000000000000000000000', + extraData: '0x', + transactions: ['0x18f6f7a00dbb0e3560e3f3955c4521248513ecfeb49b3a4bbee8a15135467ed7'], + _difficulty: { + type: 'BigNumber', + hex: '0x00', + }, +} diff --git a/package.json b/package.json index be4b0cc..ad7644e 100644 --- a/package.json +++ b/package.json @@ -3,16 +3,27 @@ "version": "0.1.0", "private": true, "dependencies": { - "@attentionstreams/contracts": "0.0.4", - "@emotion/react": "^11.9.0", - "@emotion/styled": "^11.8.1", + "@attentionstreams/contracts": "0.0.7", + "@emotion/react": "^11.9.3", + "@emotion/styled": "^11.9.3", "@ethersproject/address": "^5.6.0", "@ethersproject/constants": "^5.6.0", "@ethersproject/contracts": "^5.6.0", "@ethersproject/providers": "^5.6.2", + "@fortawesome/fontawesome-svg-core": "^6.1.2", + "@fortawesome/free-solid-svg-icons": "^6.1.2", + "@fortawesome/pro-duotone-svg-icons": "^6.1.2", + "@fortawesome/pro-light-svg-icons": "^6.1.2", + "@fortawesome/pro-regular-svg-icons": "^6.1.2", + "@fortawesome/pro-solid-svg-icons": "^6.1.2", + "@fortawesome/react-fontawesome": "^0.2.0", + "@headlessui/react": "^1.6.6", + "@mui/base": "^5.0.0-alpha.88", "@mui/icons-material": "^5.6.0", + "@mui/joy": "^5.0.0-alpha.35", "@mui/material": "^5.6.0", "@mui/styles": "^5.6.0", + "@reduxjs/toolkit": "^1.8.3", "@testing-library/jest-dom": "^5.14.1", "@testing-library/react": "^12.0.0", "@testing-library/user-event": "^13.2.1", @@ -23,10 +34,13 @@ "@web3-react/core": "^6.1.9", "ethers": "^5.6.2", "jsbi": "^4.2.0", - "react": "^18.0.0", - "react-dom": "^18.0.0", + "react": "17.0.2", + "react-device-detect": "^2.2.2", + "react-dom": "17.0.2", + "react-redux": "^8.0.2", "react-router-dom": "^6.3.0", "react-scripts": "5.0.0", + "redux-persist": "^6.0.0", "typescript": "^4.4.2", "web-vitals": "^2.1.0" }, @@ -36,7 +50,9 @@ "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test", - "eject": "react-scripts eject" + "eject": "react-scripts eject", + "cypress": "start-server-and-test 'yarn start' http://localhost:3000 'cypress open'", + "cypress:headless": "yarn build && start-server-and-test 'yarn start' http://localhost:3000 'cypress run'" }, "browserslist": { "production": [ @@ -51,9 +67,12 @@ ] }, "devDependencies": { + "@ethersproject/experimental": "^5.6.3", "@typechain/ethers-v5": "^10.0.0", "@typescript-eslint/eslint-plugin": "^5.18.0", "@web3-react/injected-connector": "^6.0.7", + "autoprefixer": "^10.4.7", + "cypress": "^10.4.0", "eslint": "^8.12.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-better-styled-components": "^1.1.2", @@ -62,7 +81,13 @@ "eslint-plugin-react-hooks": "^4.4.0", "eslint-plugin-simple-import-sort": "^7.0.0", "eslint-plugin-unused-imports": "^2.0.0", + "ethereum-input-data-decoder": "^0.4.2", + "node-sass": "^7.0.1", + "postcss": "^8.4.14", "prettier": "^2.6.2", + "sass": "^1.53.0", + "start-server-and-test": "^1.14.0", + "tailwindcss": "^3.1.5", "typechain": "^8.0.0" } } diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/public/category-header.png b/public/category-header.png new file mode 100644 index 0000000..b00fb5c Binary files /dev/null and b/public/category-header.png differ diff --git a/public/index.html b/public/index.html index aa069f2..e1f205a 100644 --- a/public/index.html +++ b/public/index.html @@ -15,6 +15,9 @@ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ --> + + +