Skip to content
This repository has been archived by the owner on Apr 4, 2022. It is now read-only.

Commit

Permalink
Merge pull request #1093 from gnosis/release/2.7.0_RC0
Browse files Browse the repository at this point in the history
Release 2.7.0-RC.0
  • Loading branch information
henrypalacios committed Apr 1, 2022
2 parents 437d101 + d866d5f commit 34e8ef1
Show file tree
Hide file tree
Showing 53 changed files with 1,668 additions and 229 deletions.
11 changes: 8 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@gnosis.pm/gp-v1",
"version": "2.6.1",
"version": "2.7.0-rc.0",
"description": "",
"main": "src/index.js",
"sideEffects": false,
Expand Down Expand Up @@ -64,8 +64,8 @@
"@material-ui/core": "^4.11.0",
"@material-ui/pickers": "4.0.0-alpha.9",
"@popperjs/core": "^2.0.6",
"@sentry/react": "^6.11.0",
"@sentry/tracing": "^6.11.0",
"@sentry/react": "^6.18.1",
"@sentry/tracing": "^6.18.1",
"@types/react": "^16.9.19",
"@types/react-is": "^16.7.1",
"@types/react-select": "3.0.21",
Expand All @@ -75,6 +75,8 @@
"bignumber.js": "^9.0.0",
"bn.js": "^4.11.9",
"combine-reducers": "^1.0.0",
"cytoscape": "^3.21.0",
"cytoscape-popper": "^2.0.0",
"date-fns": "^2.9.0",
"detect-browser": "^5.1.0",
"eth-json-rpc-middleware": "^4.4.1",
Expand All @@ -90,6 +92,7 @@
"qrcode.react": "^1.0.0",
"react": "^16.12.0",
"react-copy-to-clipboard": "^5.0.2",
"react-cytoscapejs": "^1.2.1",
"react-device-detect": "^1.15.0",
"react-dom": "^16.12.0",
"react-ga": "^3.3.0",
Expand Down Expand Up @@ -124,13 +127,15 @@
"@truffle/hdwallet-provider": "^1.2.0",
"@types/bn.js": "^4.11.6",
"@types/combine-reducers": "^1.0.0",
"@types/cytoscape": "^3.19.4",
"@types/enzyme": "^3.10.4",
"@types/enzyme-adapter-react-16": "^1.0.5",
"@types/hapi__joi": "^16.0.12",
"@types/jest": "^26.0.8",
"@types/node": "^14.0.14",
"@types/qrcode.react": "^1.0.0",
"@types/react-copy-to-clipboard": "^4.3.0",
"@types/react-cytoscapejs": "^1.2.2",
"@types/react-dom": "^16.9.5",
"@types/react-router": "^5.1.4",
"@types/react-router-dom": "^5.1.3",
Expand Down
60 changes: 60 additions & 0 deletions src/api/baseApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
type GetParamsApi<T> = {
[K in keyof T]: T[K]
}

interface GetApiFn<T, R> {
(params?: GetParamsApi<T>): Promise<R>
}

export type FetchQueryApi<T> = {
get: GetApiFn<T, Response>
}

type FetchQueryParams = {
queryString: string
networkId?: string
}

export async function fetchQuery<T>(api: FetchQueryApi<FetchQueryParams>, queryString: string): Promise<T>
export async function fetchQuery<T>(
api: FetchQueryApi<FetchQueryParams>,
queryString: string,
nullOn404: true,
): Promise<T | null>
export async function fetchQuery<T>(
api: FetchQueryApi<FetchQueryParams>,
queryString: string,
nullOn404?: boolean,
): Promise<T | null> {
let response

try {
response = await api.get()
} catch (e) {
const msg = `Failed to fetch ${queryString}`
console.error(msg, e)
throw new Error(msg)
}

if (!response.ok) {
// 404 is not a hard error, return null instead if `nullOn404` is set
if (response.status === 404 && nullOn404) {
return null
}

// Just in case response.text() fails
const responseText = await response.text().catch((e) => {
console.error(`Failed to fetch response text`, e)
throw new Error(`Request failed`)
})

throw new Error(`Request failed: [${response.status}] ${responseText}`)
}

try {
return await response.json()
} catch (e) {
console.error(`Response does not have valid JSON`, e)
throw new Error(`Failed to parse API response`)
}
}
40 changes: 8 additions & 32 deletions src/api/operator/operatorApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
RawTrade,
GetTxOrdersParams,
} from './types'
import { fetchQuery } from 'api/baseApi'

function getOperatorUrl(): Partial<Record<Network, string>> {
if (isProd || isStaging) {
Expand Down Expand Up @@ -277,38 +278,13 @@ export async function getTrades(params: GetTradesParams): Promise<RawTrade[]> {
return _fetchQuery(networkId, queryString)
}

async function _fetchQuery<T>(networkId: Network, queryString: string): Promise<T>
async function _fetchQuery<T>(networkId: Network, queryString: string, nullOn404: true): Promise<T | null>
async function _fetchQuery<T>(networkId: Network, queryString: string, nullOn404?: boolean): Promise<T | null> {
let response

try {
response = await _get(networkId, queryString)
} catch (e) {
const msg = `Failed to fetch ${queryString}`
console.error(msg, e)
throw new Error(msg)
function _fetchQuery<T>(networkId: Network, queryString: string): Promise<T>
function _fetchQuery<T>(networkId: Network, queryString: string, nullOn404: true): Promise<T | null>
function _fetchQuery<T>(networkId: Network, queryString: string, nullOn404?: boolean): Promise<T | null> {
const get = (): Promise<Response> => _get(networkId, queryString)
if (nullOn404) {
return fetchQuery({ get }, queryString, nullOn404)
}

if (!response.ok) {
// 404 is not a hard error, return null instead if `nullOn404` is set
if (response.status === 404 && nullOn404) {
return null
}

// Just in case response.text() fails
const responseText = await response.text().catch((e) => {
console.error(`Failed to fetch response text`, e)
throw new Error(`Request failed`)
})

throw new Error(`Request failed: [${response.status}] ${responseText}`)
}

try {
return response.json()
} catch (e) {
console.error(`Response does not have valid JSON`, e)
throw new Error(`Failed to parse API response`)
}
return fetchQuery({ get }, queryString)
}
3 changes: 3 additions & 0 deletions src/api/tenderly/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './tenderlyApi'

export type { PublicTrade as Trade, Transfer, Account } from './types'
173 changes: 173 additions & 0 deletions src/api/tenderly/tenderlyApi.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
import { TENDERLY_API_URL, ETH_NULL_ADDRESS, APP_NAME } from 'const'
import { Network } from 'types'
import { fetchQuery } from 'api/baseApi'
import {
Account,
Contract,
Trace,
PublicTrade as Trade,
Transfer,
TypeOfTrace,
IndexTradeInput,
IndexTransferInput,
TxTradesAndTransfers,
} from './types'
import { abbreviateString } from 'utils'

export const ALIAS_TRADER_NAME = 'Trader'
const COW_PROTOCOL_CONTRACT_NAME = 'GPv2Settlement'
const API_BASE_URLs = _urlAvailableNetwork()

function _urlAvailableNetwork(): Partial<Record<Network, string>> {
const urlNetwork = (_networkId: Network): string => `${TENDERLY_API_URL}/${_networkId}`

return {
[Network.Mainnet]: urlNetwork(Network.Mainnet),
[Network.Rinkeby]: urlNetwork(Network.Rinkeby),
[Network.xDAI]: urlNetwork(Network.xDAI),
}
}

function _getApiBaseUrl(networkId: Network): string {
const baseUrl = API_BASE_URLs[networkId]

if (!baseUrl) {
throw new Error('Unsupported Network. The tenderly API is not available in the Network ' + networkId)
} else {
return baseUrl
}
}

function _get(networkId: Network, url: string): Promise<Response> {
const baseUrl = _getApiBaseUrl(networkId)
return fetch(baseUrl + url)
}

function _fetchTrace(networkId: Network, txHash: string): Promise<Trace> {
const queryString = `/trace/${txHash}`
console.log(`[tenderlyApi:fetchTrace] Fetching trace tx ${txHash} on network ${networkId}`)

return fetchQuery<Trace>({ get: () => _get(networkId, queryString) }, queryString)
}

function _fetchTradesAccounts(networkId: Network, txHash: string): Promise<Contract[]> {
const queryString = `/tx/${txHash}/contracts`
console.log(`[tenderlyApi:fetchTradesAccounts] Fetching tx trades account on network ${networkId}`)

return fetchQuery<Array<Contract>>({ get: () => _get(networkId, queryString) }, queryString)
}

export async function getTradesAndTransfers(networkId: Network, txHash: string): Promise<TxTradesAndTransfers> {
const trace = await _fetchTrace(networkId, txHash)

return traceToTransfersTrades(trace)
}

export function traceToTransfersTrades(trace: Trace): TxTradesAndTransfers {
const transfers: Array<Transfer> = []
const trades: Array<Trade> = []

try {
trace.logs.forEach((log) => {
if (log.name === TypeOfTrace.TRANSFER) {
transfers.push({
token: log.raw.address,
from: log.inputs[IndexTransferInput.from].value,
to: log.inputs[IndexTransferInput.to].value,
value: log.inputs[IndexTransferInput.value].value,
})
} else if (log.name === TypeOfTrace.TRADE) {
const trade = {
owner: log.inputs[IndexTradeInput.owner].value,
sellToken: log.inputs[IndexTradeInput.sellToken].value,
buyToken: log.inputs[IndexTradeInput.buyToken].value,
sellAmount: log.inputs[IndexTradeInput.sellAmount].value,
buyAmount: log.inputs[IndexTradeInput.buyAmount].value,
feeAmount: log.inputs[IndexTradeInput.feeAmount].value,
orderUid: log.inputs[IndexTradeInput.orderUid].value,
}
if (trade.buyToken === ETH_NULL_ADDRESS) {
//ETH transfers are not captured by ERC20 events, so we need to manually add them to the Transfer list
transfers.push({
token: ETH_NULL_ADDRESS,
from: log.raw.address,
to: trade.owner,
value: trade.buyAmount,
})
}
trades.push(trade)
}
})
} catch (error) {
console.error(`Unable to analyze the JSON trace trades`, error)
throw new Error(`Failed to parse the JSON of tenderly trace API`)
}

return { transfers, trades }
}

export async function getTradesAccount(
networkId: Network,
txHash: string,
trades: Array<Trade>,
transfers: Array<Transfer>,
): Promise<Map<string, Account>> {
const contracts = await _fetchTradesAccounts(networkId, txHash)

return accountAddressesInvolved(contracts, trades, transfers)
}

/**
* Allows to obtain a description of addresses involved
* in a tx
*/
export function accountAddressesInvolved(
contracts: Contract[],
trades: Array<Trade>,
transfers: Array<Transfer>,
): Map<string, Account> {
const result = new Map()

try {
contracts
.filter((contract: Contract) => {
// Only usecontracts which are involved in a transfer
return transfers.find((transfer) => {
return transfer.from === contract.address || transfer.to === contract.address
})
})
.forEach((contract: Contract) => {
result.set(contract.address, {
alias: _contractName(contract.contract_name),
})
})
trades.forEach((trade) => {
result.set(trade.owner, {
alias: ALIAS_TRADER_NAME,
})
})
// Track any missing from/to contract as unknown
transfers
.flatMap((transfer) => {
return [transfer.from, transfer.to]
})
.forEach((address) => {
if (!result.get(address)) {
result.set(address, {
alias: abbreviateString(address, 6, 4),
})
}
})
} catch (error) {
console.error(`Unable to set contracts details transfers and trades`, error)
throw new Error(`Failed to parse accounts addresses of tenderly API`)
}

return result
}

function _contractName(name: string): string {
if (name === COW_PROTOCOL_CONTRACT_NAME) return APP_NAME

return name
}

0 comments on commit 34e8ef1

Please sign in to comment.