Commit 1335d991 authored by Wyatt Barnes's avatar Wyatt Barnes Committed by GitHub

Merge pull request #7536 from ethereum-optimism/wyatt/ufm/init-fee-tracking

Init Metamask Fee Estimation Tracking
parents e7685d1b 68553660
......@@ -18,7 +18,6 @@
"editable": true,
"fiscalYearStartMonth": 0,
"graphTooltip": 0,
"id": 1,
"links": [],
"liveNow": false,
"panels": [
......@@ -36,6 +35,7 @@
"axisCenteredZero": true,
"axisColorMode": "series",
"axisGridShow": false,
"axisLabel": "",
"axisPlacement": "auto",
"barAlignment": 0,
"drawStyle": "line",
......@@ -134,6 +134,229 @@
],
"title": "Self Transferring on OP Goerli (positive number = success, negative = failures)",
"type": "timeseries"
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"fieldConfig": {
"defaults": {
"color": {
"mode": "fixed"
},
"custom": {
"axisCenteredZero": false,
"axisColorMode": "text",
"axisGridShow": false,
"axisLabel": "",
"axisPlacement": "auto",
"axisSoftMin": 0,
"barAlignment": 0,
"drawStyle": "line",
"fillOpacity": 0,
"gradientMode": "none",
"hideFrom": {
"legend": false,
"tooltip": false,
"viz": false
},
"insertNulls": false,
"lineInterpolation": "stepAfter",
"lineWidth": 1,
"pointSize": 5,
"scaleDistribution": {
"type": "linear"
},
"showPoints": "never",
"spanNulls": false,
"stacking": {
"group": "A",
"mode": "none"
},
"thresholdsStyle": {
"mode": "off"
}
},
"decimals": 0,
"mappings": [],
"thresholds": {
"mode": "absolute",
"steps": [
{
"color": "text",
"value": null
}
]
}
},
"overrides": [
{
"matcher": {
"id": "byName",
"options": "metamask_self_send_fee_estimation_low"
},
"properties": [
{
"id": "displayName",
"value": "Low (Slow 🐢)"
},
{
"id": "color",
"value": {
"fixedColor": "green",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "metamask_self_send_fee_estimation_medium"
},
"properties": [
{
"id": "displayName",
"value": "Medium (Market 🦊)"
},
{
"id": "color",
"value": {
"fixedColor": "orange",
"mode": "fixed"
}
}
]
},
{
"matcher": {
"id": "byName",
"options": "metamask_self_send_fee_estimation_high"
},
"properties": [
{
"id": "displayName",
"value": "High (Aggressive 🦍)"
}
]
},
{
"matcher": {
"id": "byName",
"options": "metamask_self_send_fee_estimation_actual"
},
"properties": [
{
"id": "displayName",
"value": "Actual transaction fee"
},
{
"id": "color",
"value": {
"fixedColor": "blue",
"mode": "fixed"
}
}
]
}
]
},
"gridPos": {
"h": 11,
"w": 24,
"x": 0,
"y": 8
},
"id": 2,
"options": {
"legend": {
"calcs": [
"last"
],
"displayMode": "table",
"placement": "bottom",
"showLegend": true
},
"timezone": [
"browser"
],
"tooltip": {
"mode": "single",
"sort": "none"
}
},
"targets": [
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"disableTextWrap": false,
"editorMode": "builder",
"expr": "metamask_self_send_fee_estimation_low",
"fullMetaSearch": false,
"includeNullMetadata": true,
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "A",
"useBackend": false
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"disableTextWrap": false,
"editorMode": "builder",
"expr": "metamask_self_send_fee_estimation_medium",
"fullMetaSearch": false,
"hide": false,
"includeNullMetadata": true,
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "B",
"useBackend": false
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"disableTextWrap": false,
"editorMode": "builder",
"expr": "metamask_self_send_fee_estimation_high",
"fullMetaSearch": false,
"hide": false,
"includeNullMetadata": true,
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "C",
"useBackend": false
},
{
"datasource": {
"type": "prometheus",
"uid": "PBFA97CFB590B2093"
},
"disableTextWrap": false,
"editorMode": "builder",
"expr": "metamask_self_send_fee_estimation_actual",
"fullMetaSearch": false,
"hide": false,
"includeNullMetadata": true,
"instant": false,
"legendFormat": "__auto",
"range": true,
"refId": "D",
"useBackend": false
}
],
"title": "Self Transferring on OP Goerli Fee Estimates",
"type": "timeseries"
}
],
"refresh": "5s",
......@@ -144,7 +367,7 @@
"list": []
},
"time": {
"from": "now-30m",
"from": "now-3h",
"to": "now"
},
"timepicker": {},
......
import 'dotenv/config'
import { z } from 'zod'
import metamask from '@synthetixio/synpress/commands/metamask.js'
import synpressPlaywright from '@synthetixio/synpress/commands/playwright.js'
import { confirmPageElements } from '@synthetixio/synpress/pages/metamask/notification-page.js'
import { expect, test, type Page } from '@playwright/test'
import { mnemonicToAccount, privateKeyToAccount } from 'viem/accounts'
import { formatGwei, parseGwei } from 'viem'
import { testWithSynpress } from './testWithSynpressUtil'
import {
incrementSelfSendTxGauge,
setFeeEstimationGauge,
} from './prometheusUtils'
const env = z.object({
METAMASK_SECRET_WORDS_OR_PRIVATEKEY: z.string(),
METAMASK_OP_GOERLI_RPC_URL: z.string().url(),
METAMASK_DAPP_URL: z.string().url()
}).parse(process.env)
const expectedSender =
env.METAMASK_SECRET_WORDS_OR_PRIVATEKEY?.startsWith('0x')
? privateKeyToAccount(
env.METAMASK_SECRET_WORDS_OR_PRIVATEKEY as `0x${string}`
).address.toLowerCase()
: mnemonicToAccount(
env.METAMASK_SECRET_WORDS_OR_PRIVATEKEY as string
).address.toLowerCase()
const env = z
.object({
METAMASK_SECRET_WORDS_OR_PRIVATEKEY: z.string(),
METAMASK_OP_GOERLI_RPC_URL: z.string().url(),
METAMASK_DAPP_URL: z.string().url(),
})
.parse(process.env)
const expectedSender = env.METAMASK_SECRET_WORDS_OR_PRIVATEKEY?.startsWith('0x')
? privateKeyToAccount(
env.METAMASK_SECRET_WORDS_OR_PRIVATEKEY as `0x${string}`
).address.toLowerCase()
: mnemonicToAccount(
env.METAMASK_SECRET_WORDS_OR_PRIVATEKEY as string
).address.toLowerCase()
const expectedRecipient = expectedSender
let sharedPage: Page
......@@ -119,25 +124,51 @@ test('Send an EIP-1559 transaction and verify success', async () => {
})
})
const notificationPage =
await synpressPlaywright.switchToMetamaskNotification()
const lowFeeEstimate = await getFeeEstimateInGwei(
confirmPageElements.gasOptionLowButton,
'Low',
notificationPage
)
const highFeeEstimate = await getFeeEstimateInGwei(
confirmPageElements.gasOptionHighButton,
'Aggressive',
notificationPage
)
// Medium needs to be last because that's the gas option we want to submit the tx with
const mediumFeeEstimate = await getFeeEstimateInGwei(
confirmPageElements.gasOptionMediumButton,
'Market',
notificationPage
)
await metamask.confirmTransactionAndWaitForMining()
const txHash = await txHashPromise
const transactionReceiptPromise = new Promise<Record<string, string>>(
(resolve) => {
sharedPage.on('load', async () => {
const responseText = await sharedPage.locator('body > main').innerText()
const transactionReceipt = JSON.parse(
responseText.replace('Response: ', '')
)
resolve(transactionReceipt)
})
}
)
// Metamask test dApp allows us access to the Metamask RPC provider via loading this URL.
// The RPC reponse will be populated onto the page that's loaded.
// The RPC response will be populated onto the page that's loaded.
// More info here: https://github.com/MetaMask/test-dapp/tree/main#usage
await sharedPage.goto(
`${env.METAMASK_DAPP_URL}/request.html?method=eth_getTransactionReceipt&params=["${txHash}"]`
)
// Waiting for RPC response to be populated on the page
await sharedPage.waitForTimeout(2_000)
const transactionReceipt = JSON.parse(
(await sharedPage.locator('body > main').innerText()).replace(
'Response: ',
''
)
)
const transactionReceipt = await transactionReceiptPromise
try {
expect(transactionReceipt.status).toBe('0x1')
......@@ -149,4 +180,41 @@ test('Send an EIP-1559 transaction and verify success', async () => {
throw error
}
console.log('Sent an EIP-1559 transaction and verified success')
await setFeeEstimationGauge('low', lowFeeEstimate)
await setFeeEstimationGauge('medium', mediumFeeEstimate)
await setFeeEstimationGauge('high', highFeeEstimate)
await setFeeEstimationGauge('actual', getActualTransactionFee(transactionReceipt))
})
const getFeeEstimateInGwei = async (
gasOptionButton: string,
waitForText: 'Low' | 'Market' | 'Aggressive',
notificationPage: Page
) => {
const regexParseEtherValue = /(\d+\.\d+)\sOPG/
await synpressPlaywright.waitAndClick(
confirmPageElements.editGasFeeButton,
notificationPage
)
await synpressPlaywright.waitAndClick(gasOptionButton, notificationPage)
await synpressPlaywright.waitForText(
`${confirmPageElements.editGasFeeButton} .edit-gas-fee-button__label`,
waitForText,
notificationPage
)
const feeValue = (
await synpressPlaywright.waitAndGetValue(
confirmPageElements.totalLabel,
notificationPage
)
).match(regexParseEtherValue)[1]
return parseInt(parseGwei(feeValue).toString())
}
const getActualTransactionFee = (transactionReceipt: Record<string, string>) => {
const effectiveGasPrice = BigInt(transactionReceipt.effectiveGasPrice)
const l2GasUsed = BigInt(transactionReceipt.gasUsed)
const l1Fee = BigInt(transactionReceipt.l1Fee)
return parseInt(formatGwei(effectiveGasPrice * l2GasUsed + l1Fee, 'wei'))
}
import 'dotenv/config'
import { z } from 'zod'
import { Gauge, Pushgateway } from 'prom-client'
import { Gauge, Pushgateway, Registry } from 'prom-client'
const env = z
.object({
......@@ -10,10 +10,41 @@ const env = z
.parse(process.env)
const selfSendTransactionMetricName = 'metamask_self_send'
const feeEstimateLowMetricName = 'metamask_self_send_fee_estimation_low'
const feeEstimateMediumMetricName = 'metamask_self_send_fee_estimation_medium'
const feeEstimateHighMetricName = 'metamask_self_send_fee_estimation_high'
const feeEstimateActualMetricName = 'metamask_self_send_fee_estimation_actual'
const selfSendRegistry = new Registry()
const feeEstimateLowRegistry = new Registry()
const feeEstimateMediumRegistry = new Registry()
const feeEstimateHighRegistry = new Registry()
const feeEstimateActualRegistry = new Registry()
const selfSendGauge = new Gauge({
name: selfSendTransactionMetricName,
help: 'A gauge signifying the number of transactions sent with Metamask',
registers: [selfSendRegistry]
})
const feeEstimateLowGauge = new Gauge({
name: feeEstimateLowMetricName,
help: 'A gauge signifying the latest fee estimation from Metamask for Low transaction speed',
registers: [feeEstimateLowRegistry]
})
const feeEstimateMediumGauge = new Gauge({
name: feeEstimateMediumMetricName,
help: 'A gauge signifying the latest fee estimation from Metamask for Medium transaction speed',
registers: [feeEstimateMediumRegistry]
})
const feeEstimateHighGauge = new Gauge({
name: feeEstimateHighMetricName,
help: 'A gauge signifying the latest fee estimation from Metamask for High transaction speed',
registers: [feeEstimateHighRegistry]
})
const feeEstimateActualGauge = new Gauge({
name: feeEstimateActualMetricName,
help: 'A gauge signifying the latest actual transaction fee',
registers: [feeEstimateActualRegistry]
})
export const getSelfSendGaugeValue = async () => {
......@@ -71,10 +102,10 @@ export const getSelfSendGaugeValue = async () => {
}
export const setSelfSendTxGauge = async (valueToSetTo: number) => {
console.log(`Setting ${selfSendTransactionMetricName} to ${valueToSetTo}`)
console.log(`Setting ${selfSendTransactionMetricName} to ${valueToSetTo}...`)
selfSendGauge.set(valueToSetTo)
const pushGateway = new Pushgateway(env.PROMETHEUS_PUSHGATEWAY_URL)
const pushGateway = new Pushgateway(env.PROMETHEUS_PUSHGATEWAY_URL, undefined, selfSendRegistry)
await pushGateway.pushAdd({ jobName: 'metamask_self_send_tx_count' })
}
......@@ -89,7 +120,41 @@ export const incrementSelfSendTxGauge = async (isSuccess: boolean) => {
}
console.log(
`Current value of ${selfSendTransactionMetricName} is ${currentMetricValue}, incrementing to ${newMetricValue}`
`Current value of ${selfSendTransactionMetricName} is ${currentMetricValue}, incrementing to ${newMetricValue}...`
)
await setSelfSendTxGauge(newMetricValue)
}
export const setFeeEstimationGauge = async (txSpeed: 'low' | 'medium' | 'high' | 'actual', fee: number) => {
console.log(
txSpeed !== 'actual'
? `Setting Metamask fee estimation for ${txSpeed} to ${fee}...`
: `Setting actual transaction fee to ${fee}`
)
let prometheusRegistry: Registry
switch (txSpeed) {
case 'low':
feeEstimateLowGauge.set(fee)
prometheusRegistry = feeEstimateLowRegistry
break;
case 'medium':
feeEstimateMediumGauge.set(fee)
prometheusRegistry = feeEstimateMediumRegistry
break;
case 'high':
feeEstimateHighGauge.set(fee)
prometheusRegistry = feeEstimateHighRegistry
break;
case 'actual':
feeEstimateActualGauge.set(fee)
prometheusRegistry = feeEstimateActualRegistry
break;
default:
throw new Error(`unsupported transaction speed given: ${txSpeed}`)
}
const pushGateway = new Pushgateway(env.PROMETHEUS_PUSHGATEWAY_URL, undefined, prometheusRegistry)
await pushGateway.pushAdd({ jobName: `metamask_self_send_tx_fee_estimation_${txSpeed}` })
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment