Skip to content

Commit

Permalink
Merge pull request #836 from DarkFlorist/no-gas-limit-eth_calls
Browse files Browse the repository at this point in the history
no gas limit for eth_simulateV1 eth_call's and `isEthSimulateV1Node` refactor
  • Loading branch information
KillariDev authored Jan 31, 2024
2 parents 5066af1 + cf9e971 commit 872936c
Show file tree
Hide file tree
Showing 10 changed files with 44 additions and 28 deletions.
2 changes: 2 additions & 0 deletions app/ts/background/settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,8 @@ export const defaultRpcs = [
},
] as const

export const isEthSimulateV1Node = (httpsRpc: string) => httpsRpc === 'https://rpc.dark.florist/winedancemuffinborrow' || httpsRpc === 'https://rpc.dark.florist/birdchalkrenewtip' || httpsRpc === 'https://rpc-goerli.dark.florist/flipcardtrustone'

export async function getSettings() : Promise<Settings> {
const results = await browserStorageLocalGet([
'activeSimulationAddress',
Expand Down
14 changes: 5 additions & 9 deletions app/ts/background/simulationModeHanders.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EthereumClientService } from '../simulation/services/EthereumClientService.js'
import { createEthereumSubscription, removeEthereumSubscription } from '../simulation/services/EthereumSubscriptionService.js'
import { simulationGasLeft, getSimulatedBalance, getSimulatedBlock, getSimulatedBlockNumber, getSimulatedCode, getSimulatedLogs, getSimulatedStack, getSimulatedTransactionByHash, getSimulatedTransactionCount, getSimulatedTransactionReceipt, simulatedCall, simulateEstimateGas, getInputFieldFromDataOrInput, getSimulatedBlockByHash, getSimulatedFeeHistory } from '../simulation/services/SimulationModeEthereumClientService.js'
import { ERROR_INTERCEPTOR_GAS_ESTIMATION_FAILED, ERROR_INTERCEPTOR_GET_CODE_FAILED, KNOWN_CONTRACT_CALLER_ADDRESSES } from '../utils/constants.js'
import { getSimulatedBalance, getSimulatedBlock, getSimulatedBlockNumber, getSimulatedCode, getSimulatedLogs, getSimulatedStack, getSimulatedTransactionByHash, getSimulatedTransactionCount, getSimulatedTransactionReceipt, simulatedCall, simulateEstimateGas, getInputFieldFromDataOrInput, getSimulatedBlockByHash, getSimulatedFeeHistory } from '../simulation/services/SimulationModeEthereumClientService.js'
import { DEFAULT_CALL_ADDRESS, ERROR_INTERCEPTOR_GAS_ESTIMATION_FAILED, ERROR_INTERCEPTOR_GET_CODE_FAILED, KNOWN_CONTRACT_CALLER_ADDRESSES } from '../utils/constants.js'
import { RPCReply } from '../types/interceptor-messages.js'
import { WebsiteTabConnections } from '../types/user-interface-types.js'
import { SimulationState } from '../types/visualizer-types.js'
Expand All @@ -15,8 +15,6 @@ import { Simulator } from '../simulation/simulator.js'
import { Website } from '../types/websiteAccessTypes.js'
import { SignMessageParams } from '../types/jsonRpc-signing-types.js'

const defaultCallAddress = 0x1n

export async function getBlockByHash(ethereumClientService: EthereumClientService, simulationState: SimulationState | undefined, request: EthBlockByHashParams) {
return { method: request.method, result: await getSimulatedBlockByHash(ethereumClientService, simulationState, request.params[0], request.params[1]) }
}
Expand Down Expand Up @@ -74,24 +72,22 @@ async function singleCallWithFromOverride(ethereumClientService: EthereumClientS
value,
input: getInputFieldFromDataOrInput(callParams),
accessList: [],
gasLimit: callParams.gas === undefined ? simulationGasLeft(simulationState, await ethereumClientService.getBlock()) : callParams.gas
}

return await simulatedCall(ethereumClientService, simulationState, callTransaction, blockTag)
}

export async function call(ethereumClientService: EthereumClientService, simulationState: SimulationState | undefined, request: EthCallParams) {
const callParams = request.params[0]
const from = callParams.from !== undefined && !KNOWN_CONTRACT_CALLER_ADDRESSES.includes(callParams.from) ? callParams.from : defaultCallAddress
const from = callParams.from !== undefined && !KNOWN_CONTRACT_CALLER_ADDRESSES.includes(callParams.from) ? callParams.from : DEFAULT_CALL_ADDRESS
const callResult = await singleCallWithFromOverride(ethereumClientService, simulationState, request, from)

if (callResult.error !== undefined && callResult.error.code === ERROR_INTERCEPTOR_GAS_ESTIMATION_FAILED ) return { method: request.method, ...callResult }

// if we fail our call because we are calling from a contract, retry and change address to our default calling address
// TODO: Remove this logic and KNOWN_CONTRACT_CALLER_ADDRESSES when multicall supports calling from contracts
if (callResult.error !== undefined && 'data' in callResult.error && callResult.error?.data === 'sender has deployed code' && from !== defaultCallAddress) {
const callerChangeResult = await singleCallWithFromOverride(ethereumClientService, simulationState, request, defaultCallAddress)
if (callerChangeResult.error !== undefined) return { method: request.method, ...callerChangeResult }
if (callResult.error !== undefined && 'data' in callResult.error && callResult.error.data === 'sender has deployed code' && from !== DEFAULT_CALL_ADDRESS) {
const callerChangeResult = await singleCallWithFromOverride(ethereumClientService, simulationState, request, DEFAULT_CALL_ADDRESS)
return { method: request.method, ...callerChangeResult }
}
return { method: request.method, ...callResult }
Expand Down
7 changes: 3 additions & 4 deletions app/ts/components/pages/PersonalSign.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { TransactionCreated } from '../simulationExplaining/SimulationSummary.js
import { EnrichedSolidityTypeComponent } from '../subcomponents/solidityType.js'
import { QuarantineReasons } from '../simulationExplaining/Transactions.js'
import { ModifyAddressWindowState } from '../../types/visualizer-types.js'
import { isEthSimulateV1Node } from '../../background/settings.js'

type SignatureCardParams = {
VisualizedPersonalSignRequest: VisualizedPersonalSignRequest
Expand Down Expand Up @@ -473,8 +474,7 @@ export function PersonalSign() {

function isConfirmDisabled(VisualizedPersonalSignRequest: VisualizedPersonalSignRequest, activeAddress: bigint) {
return !isPossibleToSend(VisualizedPersonalSignRequest, activeAddress) && !forceSend
&& !(VisualizedPersonalSignRequest.rpcNetwork.httpsRpc === 'https://rpc.dark.florist/birdchalkrenewtip' // todo remove this check
|| VisualizedPersonalSignRequest.rpcNetwork.httpsRpc === 'https://rpc.dark.florist/winedancemuffinborrow')
&& !(VisualizedPersonalSignRequest.rpcNetwork.httpsRpc !== undefined && isEthSimulateV1Node(VisualizedPersonalSignRequest.rpcNetwork.httpsRpc))
}

function Buttons() {
Expand Down Expand Up @@ -562,8 +562,7 @@ export function PersonalSign() {
</div>
: <></>
}
{ !(VisualizedPersonalSignRequest.rpcNetwork.httpsRpc === 'https://rpc.dark.florist/birdchalkrenewtip' // todo remove this check
|| VisualizedPersonalSignRequest.rpcNetwork.httpsRpc === 'https://rpc.dark.florist/winedancemuffinborrow')
{ !(VisualizedPersonalSignRequest.rpcNetwork.httpsRpc !== undefined && isEthSimulateV1Node(VisualizedPersonalSignRequest.rpcNetwork.httpsRpc))
&& VisualizedPersonalSignRequest.simulationMode && (VisualizedPersonalSignRequest.activeAddress.address === undefined || VisualizedPersonalSignRequest.activeAddress.address !== MOCK_PRIVATE_KEYS_ADDRESS || VisualizedPersonalSignRequest.method !== 'personal_sign')
? <div style = 'display: grid'>
<ErrorComponent text = 'Unfortunately we cannot simulate message signing as it requires private key access 😢.'/>
Expand Down
3 changes: 2 additions & 1 deletion app/ts/components/simulationExplaining/SimulationSummary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { getEthDonator } from '../../background/storageVariables.js'
import { RpcNetwork } from '../../types/rpc.js'
import { AddressBookEntry, Erc1155Entry, Erc20TokenEntry, Erc721Entry } from '../../types/addressBookTypes.js'
import { Website } from '../../types/websiteAccessTypes.js'
import { isEthSimulateV1Node } from '../../background/settings.js'

type EtherChangeParams = {
textColor: string,
Expand Down Expand Up @@ -463,7 +464,7 @@ export function TokenLogAnalysisCard({ simTx, renameAddressCallBack }: TokenLogA
const [showLogs, setShowLogs] = useState<boolean>(false)
const identifiedSwap = identifySwap(simTx)
if (simTx === undefined) return <></>
const hasEthLogs = simTx.transaction.rpcNetwork.httpsRpc === 'https://rpc.dark.florist/birdchalkrenewtip' || simTx.transaction.rpcNetwork.httpsRpc === 'https://rpc.dark.florist/winedancemuffinborrow' //todo, remove this check, all txs should have ETH logs
const hasEthLogs = simTx.transaction.rpcNetwork.httpsRpc !== undefined && isEthSimulateV1Node(simTx.transaction.rpcNetwork.httpsRpc)
const tokenEventsPlural = hasEthLogs ? 'token events or ETH transactions' : 'token events'
const tokenEventsSingular = hasEthLogs ? 'One token event or an ETH transaction' : 'One token event'
return <>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { sendPopupMessageToBackgroundPage } from '../../../background/backgroundUtils.js'
import { isEthSimulateV1Node } from '../../../background/settings.js'
import { AddressBookEntry } from '../../../types/addressBookTypes.js'
import { MessageToPopup, GovernanceVoteInputParameters, SimulateGovernanceContractExecutionReply } from '../../../types/interceptor-messages.js'
import { RenameAddressCallBack, RpcConnectionStatus } from '../../../types/user-interface-types.js'
Expand Down Expand Up @@ -104,15 +105,13 @@ const ShowSuccessOrFailure = ({ currentBlockNumber, rpcConnectionStatus, simulat
const missingAbiText = 'The governance contract is missing an ABI. Add an ABI to simulate execution of this proposal.'
if (simulateGovernanceContractExecutionReply === undefined) {
return <div style = 'display: flex; justify-content: center;'>
{ !(simulationAndVisualisationResults.rpcNetwork.httpsRpc === 'https://rpc.dark.florist/birdchalkrenewtip' // todo remove this check
|| simulationAndVisualisationResults.rpcNetwork.httpsRpc=== 'https://rpc.dark.florist/winedancemuffinborrow') ? <p class = 'paragraph'> experimental rpc client required </p> : <></> }
{ !(simulationAndVisualisationResults.rpcNetwork.httpsRpc !== undefined && isEthSimulateV1Node(simulationAndVisualisationResults.rpcNetwork.httpsRpc)) ? <p class = 'paragraph'> experimental rpc client required </p> : <></> }

{ simTx.transaction.to !== undefined && 'abi' in simTx.transaction.to && simTx.transaction.to.abi !== undefined ?
<button
class = { `button is-primary` }
onClick = { () => simulateGovernanceVote(simTx.transactionIdentifier) }
disabled = { !(simulationAndVisualisationResults.rpcNetwork.httpsRpc === 'https://rpc.dark.florist/birdchalkrenewtip' // todo remove this check
|| simulationAndVisualisationResults.rpcNetwork.httpsRpc=== 'https://rpc.dark.florist/winedancemuffinborrow')
disabled = { !(simulationAndVisualisationResults.rpcNetwork.httpsRpc !== undefined && isEthSimulateV1Node(simulationAndVisualisationResults.rpcNetwork.httpsRpc))
}>
Simulate execution on a passing vote
</button>
Expand Down
3 changes: 2 additions & 1 deletion app/ts/simulation/services/EthereumClientService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { assertNever } from '../../utils/typescript.js'
import { MessageHashAndSignature, SignatureWithFakeSignerAddress, simulatePersonalSign } from './SimulationModeEthereumClientService.js'
import { getEcRecoverOverride } from '../../utils/ethereumByteCodes.js'
import * as funtypes from 'funtypes'
import { isEthSimulateV1Node } from '../../background/settings.js'

export type IEthereumClientService = Pick<EthereumClientService, keyof EthereumClientService>
export class EthereumClientService {
Expand Down Expand Up @@ -196,7 +197,7 @@ export class EthereumClientService {

public readonly multicall = async (transactions: readonly EthereumUnsignedTransaction[], spoofedSignatures: readonly SignatureWithFakeSignerAddress[], blockNumber: bigint, extraAccountOverrides: StateOverrides = {}) => {
const httpsRpc = this.requestHandler.getRpcEntry().httpsRpc
if (httpsRpc === 'https://rpc.dark.florist/winedancemuffinborrow' || httpsRpc === 'https://rpc.dark.florist/birdchalkrenewtip') {
if (isEthSimulateV1Node(httpsRpc)) {
//TODO: Remove this when we get rid of our old multicall

const transactionsWithRemoveZeroPricedOnes = transactions.map((transaction) => {
Expand Down
30 changes: 24 additions & 6 deletions app/ts/simulation/services/SimulationModeEthereumClientService.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { EthereumClientService } from './EthereumClientService.js'
import { EthereumUnsignedTransaction, EthereumSignedTransactionWithBlockData, EthereumBlockTag, EthereumAddress, EthereumBlockHeader, EthereumBlockHeaderWithTransactionHashes, EthereumSignedTransaction, EthereumData, EthereumQuantity, EthereumBytes32 } from '../../types/wire-types.js'
import { EthereumUnsignedTransaction, EthereumSignedTransactionWithBlockData, EthereumBlockTag, EthereumAddress, EthereumBlockHeader, EthereumBlockHeaderWithTransactionHashes, EthereumSignedTransaction, EthereumData, EthereumQuantity, EthereumBytes32, OptionalEthereumUnsignedTransaction } from '../../types/wire-types.js'
import { addressString, bytes32String, calculateWeightedPercentile, dataStringWith0xStart, max, min, stringToUint8Array } from '../../utils/bigint.js'
import { CANNOT_SIMULATE_OFF_LEGACY_BLOCK, ERROR_INTERCEPTOR_GAS_ESTIMATION_FAILED, ETHEREUM_LOGS_LOGGER_ADDRESS, ETHEREUM_EIP1559_BASEFEECHANGEDENOMINATOR, ETHEREUM_EIP1559_ELASTICITY_MULTIPLIER, MOCK_ADDRESS, MULTICALL3, Multicall3ABI } from '../../utils/constants.js'
import { CANNOT_SIMULATE_OFF_LEGACY_BLOCK, ERROR_INTERCEPTOR_GAS_ESTIMATION_FAILED, ETHEREUM_LOGS_LOGGER_ADDRESS, ETHEREUM_EIP1559_BASEFEECHANGEDENOMINATOR, ETHEREUM_EIP1559_ELASTICITY_MULTIPLIER, MOCK_ADDRESS, MULTICALL3, Multicall3ABI, DEFAULT_CALL_ADDRESS } from '../../utils/constants.js'
import { Interface, TypedDataEncoder, ethers, hashMessage, keccak256, } from 'ethers'
import { WebsiteCreatedEthereumUnsignedTransaction, SimulatedTransaction, SimulationState, TokenBalancesAfter, EstimateGasError, SignedMessageTransaction, WebsiteCreatedEthereumUnsignedTransactionOrFailed } from '../../types/visualizer-types.js'
import { EthereumUnsignedTransactionToUnsignedTransaction, IUnsignedTransaction1559, serializeSignedTransactionToBytes } from '../../utils/ethereum.js'
Expand All @@ -12,6 +12,7 @@ import { SignMessageParams } from '../../types/jsonRpc-signing-types.js'
import { UniqueRequestIdentifier, doesUniqueRequestIdentifiersMatch } from '../../utils/requests.js'
import { StateOverrides } from '../../types/multicall-types.js'
import { getCodeByteCode } from '../../utils/ethereumByteCodes.js'
import { isEthSimulateV1Node } from '../../background/settings.js'

const MOCK_PUBLIC_PRIVATE_KEY = 0x1n // key used to sign mock transactions
const MOCK_SIMULATION_PRIVATE_KEY = 0x2n // key used to sign simulated transatons
Expand Down Expand Up @@ -661,19 +662,23 @@ export const getSimulatedTransactionByHash = async (ethereumClientService: Ether
return await ethereumClientService.getTransactionByHash(hash)
}

export const simulatedCall = async (ethereumClientService: EthereumClientService, simulationState: SimulationState | undefined, params: Pick<IUnsignedTransaction1559, 'to' | 'from' | 'input' | 'value' | 'maxFeePerGas' | 'maxPriorityFeePerGas' | 'gasLimit'>, blockTag: EthereumBlockTag = 'latest') => {
export const simulatedCall = async (ethereumClientService: EthereumClientService, simulationState: SimulationState | undefined, params: Pick<IUnsignedTransaction1559, 'to' | 'maxFeePerGas' | 'maxPriorityFeePerGas' | 'input' | 'value'> & Partial<Pick<IUnsignedTransaction1559, 'from' | 'gasLimit'>>, blockTag: EthereumBlockTag = 'latest') => {
const currentBlock = await ethereumClientService.getBlockNumber()
const blockNumToUse = blockTag === 'latest' || blockTag === 'pending' ? currentBlock : min(blockTag, currentBlock)
const simulationStateToUse = blockNumToUse >= currentBlock ? simulationState : undefined

const from = params.from ?? DEFAULT_CALL_ADDRESS
const transaction = {
...params,
type: '1559',
gas: params.gasLimit,
nonce: await getSimulatedTransactionCount(ethereumClientService, simulationStateToUse, params.from, blockTag),
from,
nonce: await getSimulatedTransactionCount(ethereumClientService, simulationStateToUse, from, blockTag),
chainId: ethereumClientService.getChainId(),
} as const
const multicallResult = await simulatedMulticall(ethereumClientService, simulationStateToUse, [transaction], blockNumToUse)

const multicallResult = isEthSimulateV1Node(ethereumClientService.getRpcEntry().httpsRpc) ?
await simulated484Multicall(ethereumClientService, simulationStateToUse, [transaction], blockNumToUse)
: await simulatedMulticall(ethereumClientService, simulationStateToUse, [{ ...transaction, gas: params.gasLimit === undefined ? simulationGasLeft(simulationState, await ethereumClientService.getBlock()) : params.gasLimit }], blockNumToUse)
const callResult = multicallResult[multicallResult.length - 1]
if (callResult === undefined) throw new Error('call result was undefined')
if (callResult.statusCode === 'failure') {
Expand All @@ -697,6 +702,19 @@ export const simulatedMulticall = async (ethereumClientService: EthereumClientSe
return await ethereumClientService.multicall(mergedTxs.concat(transactions), getSignedMessagesWithFakeSigner(simulationState), blockNumber, extraAccountOverrides)
}

export const simulated484Multicall = async (ethereumClientService: EthereumClientService, simulationState: SimulationState | undefined, transactions: OptionalEthereumUnsignedTransaction[], blockNumber: bigint, extraAccountOverrides: StateOverrides = {}) => {
const mergedTxs: OptionalEthereumUnsignedTransaction[] = getTransactionQueue(simulationState)
const transactionsWithRemoveZeroPricedOnes = mergedTxs.concat(transactions).map((transaction) => {
if (transaction.type !== '1559') return transaction
const { maxFeePerGas, ...transactionWithoutMaxFee } = transaction
return {
...transactionWithoutMaxFee,
...maxFeePerGas === 0n ? {} : { maxFeePerGas }
}
})
return await ethereumClientService.executionSpec383MultiCallOnlyTransactionsAndSignatures(transactionsWithRemoveZeroPricedOnes, getSignedMessagesWithFakeSigner(simulationState), blockNumber, extraAccountOverrides)
}

// use time as block hash as that makes it so that updated simulations with different states are different, but requires no additional calculation
export const getHashOfSimulatedBlock = (simulationState: SimulationState) => BigInt(simulationState.simulationConductedTimestamp.getTime())

Expand Down
3 changes: 1 addition & 2 deletions app/ts/types/wire-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,13 +241,13 @@ export const OptionalEthereumUnsignedTransaction1559 = funtypes.Intersect(
type: funtypes.Literal('0x2').withParser(LiteralConverterParserFactory('0x2', '1559' as const)),
from: EthereumAddress,
nonce: EthereumQuantity,
gas: EthereumQuantity,
to: funtypes.Union(EthereumAddress, funtypes.Null),
value: EthereumQuantity,
input: EthereumInput,
chainId: EthereumQuantity,
}).asReadonly(),
funtypes.Partial({
gas: EthereumQuantity,
maxFeePerGas: EthereumQuantity,
maxPriorityFeePerGas: EthereumQuantity,
accessList: EthereumAccessList,
Expand All @@ -257,7 +257,6 @@ export const OptionalEthereumUnsignedTransaction1559 = funtypes.Intersect(
export type OptionalEthereumUnsignedTransaction = funtypes.Static<typeof OptionalEthereumUnsignedTransaction>
export const OptionalEthereumUnsignedTransaction = funtypes.Union(EthereumUnsignedTransactionLegacy, EthereumUnsignedTransaction2930, OptionalEthereumUnsignedTransaction1559)


export const EthereumTransaction2930And1559Signature = funtypes.Intersect(
funtypes.ReadonlyObject({
r: EthereumQuantity,
Expand Down
1 change: 1 addition & 0 deletions app/ts/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ export const MAKE_YOU_RICH_TRANSACTION = {
},
transactionSendingFormat: 'eth_sendTransaction' as const,
}
export const DEFAULT_CALL_ADDRESS = 0x1n

export const TIME_BETWEEN_BLOCKS = 12
export const METAMASK_LOGO = '../img/signers/metamask.svg'
Expand Down
2 changes: 1 addition & 1 deletion test/tests/nethermindComparison.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ export async function main() {
const rpcNetwork = {
name: 'Goerli',
chainId: 5n,
httpsRpc: 'https://rpc-goerli.dark.florist/flipcardtrustone',
httpsRpc: 'https://some-goerli-rpc',
currencyName: 'Goerli Testnet ETH',
currencyTicker: 'GÖETH',
primary: true,
Expand Down

0 comments on commit 872936c

Please sign in to comment.