2021-02-04 19:15:23 +01:00
import { ethErrors } from 'eth-rpc-errors' ;
import { addHexPrefix } from '../../../lib/util' ;
2021-06-29 21:25:56 +02:00
import {
TRANSACTION _ENVELOPE _TYPES ,
TRANSACTION _STATUSES ,
} from '../../../../../shared/constants/transaction' ;
2021-08-02 17:38:01 +02:00
import { isEIP1559Transaction } from '../../../../../shared/modules/transaction.utils' ;
2021-05-17 21:00:59 +02:00
import { isValidHexAddress } from '../../../../../shared/modules/hexstring-utils' ;
2018-04-06 20:07:20 +02:00
2018-04-13 22:18:45 +02:00
const normalizers = {
2021-06-29 21:25:56 +02:00
from : addHexPrefix ,
2020-11-03 00:41:28 +01:00
to : ( to , lowerCase ) =>
lowerCase ? addHexPrefix ( to ) . toLowerCase ( ) : addHexPrefix ( to ) ,
2021-06-29 21:25:56 +02:00
nonce : addHexPrefix ,
value : addHexPrefix ,
data : addHexPrefix ,
gas : addHexPrefix ,
gasPrice : addHexPrefix ,
maxFeePerGas : addHexPrefix ,
maxPriorityFeePerGas : addHexPrefix ,
type : addHexPrefix ,
2021-10-22 22:42:20 +02:00
estimateSuggested : ( estimate ) => estimate ,
estimateUsed : ( estimate ) => estimate ,
2021-02-04 19:15:23 +01:00
} ;
2018-04-19 20:29:26 +02:00
2021-03-30 16:54:05 +02:00
export function normalizeAndValidateTxParams ( txParams , lowerCase = true ) {
const normalizedTxParams = normalizeTxParams ( txParams , lowerCase ) ;
validateTxParams ( normalizedTxParams ) ;
return normalizedTxParams ;
}
2019-07-31 22:17:11 +02:00
/ * *
2020-04-20 21:12:24 +02:00
* Normalizes the given txParams
2022-01-07 16:57:33 +01:00
*
2022-08-24 20:57:47 +02:00
* @ param { object } txParams - The transaction params
2020-06-23 18:12:11 +02:00
* @ param { boolean } [ lowerCase ] - Whether to lowercase the 'to' address .
* Default : true
2022-08-24 20:57:47 +02:00
* @ returns { object } the normalized tx params
2018-04-13 22:18:45 +02:00
* /
2020-11-03 00:41:28 +01:00
export function normalizeTxParams ( txParams , lowerCase = true ) {
2018-04-13 22:18:45 +02:00
// apply only keys in the normalizers
2021-02-04 19:15:23 +01:00
const normalizedTxParams = { } ;
2018-04-19 20:29:26 +02:00
for ( const key in normalizers ) {
2019-11-20 01:03:20 +01:00
if ( txParams [ key ] ) {
2021-02-04 19:15:23 +01:00
normalizedTxParams [ key ] = normalizers [ key ] ( txParams [ key ] , lowerCase ) ;
2019-11-20 01:03:20 +01:00
}
2018-04-13 22:18:45 +02:00
}
2021-02-04 19:15:23 +01:00
return normalizedTxParams ;
2018-04-06 20:07:20 +02:00
}
2021-06-29 21:25:56 +02:00
/ * *
* Given two fields , ensure that the second field is not included in txParams ,
* and if it is throw an invalidParams error .
2022-01-07 16:57:33 +01:00
*
2022-08-24 20:57:47 +02:00
* @ param { object } txParams - the transaction parameters object
2021-06-29 21:25:56 +02:00
* @ param { string } fieldBeingValidated - the current field being validated
* @ param { string } mutuallyExclusiveField - the field to ensure is not provided
2022-01-07 16:57:33 +01:00
* @ throws { ethErrors . rpc . invalidParams } Throws if mutuallyExclusiveField is
2021-06-29 21:25:56 +02:00
* present in txParams .
* /
function ensureMutuallyExclusiveFieldsNotProvided (
txParams ,
fieldBeingValidated ,
mutuallyExclusiveField ,
) {
if ( typeof txParams [ mutuallyExclusiveField ] !== 'undefined' ) {
throw ethErrors . rpc . invalidParams (
` Invalid transaction params: specified ${ fieldBeingValidated } but also included ${ mutuallyExclusiveField } , these cannot be mixed ` ,
) ;
}
}
/ * *
* Ensures that the provided value for field is a string , throws an
* invalidParams error if field is not a string .
2022-01-07 16:57:33 +01:00
*
2022-08-24 20:57:47 +02:00
* @ param { object } txParams - the transaction parameters object
2021-06-29 21:25:56 +02:00
* @ param { string } field - the current field being validated
2022-01-07 16:57:33 +01:00
* @ throws { ethErrors . rpc . invalidParams } Throws if field is not a string
2021-06-29 21:25:56 +02:00
* /
function ensureFieldIsString ( txParams , field ) {
if ( typeof txParams [ field ] !== 'string' ) {
throw ethErrors . rpc . invalidParams (
` Invalid transaction params: ${ field } is not a string. got: ( ${ txParams [ field ] } ) ` ,
) ;
}
}
/ * *
* Ensures that the provided txParams has the proper 'type' specified for the
* given field , if it is provided . If types do not match throws an
* invalidParams error .
2022-01-07 16:57:33 +01:00
*
2022-08-24 20:57:47 +02:00
* @ param { object } txParams - the transaction parameters object
2021-06-29 21:25:56 +02:00
* @ param { 'gasPrice' | 'maxFeePerGas' | 'maxPriorityFeePerGas' } field - the
* current field being validated
2022-01-07 16:57:33 +01:00
* @ throws { ethErrors . rpc . invalidParams } Throws if type does not match the
2021-06-29 21:25:56 +02:00
* expectations for provided field .
* /
function ensureProperTransactionEnvelopeTypeProvided ( txParams , field ) {
switch ( field ) {
case 'maxFeePerGas' :
case 'maxPriorityFeePerGas' :
if (
txParams . type &&
txParams . type !== TRANSACTION _ENVELOPE _TYPES . FEE _MARKET
) {
throw ethErrors . rpc . invalidParams (
` Invalid transaction envelope type: specified type " ${ txParams . type } " but including maxFeePerGas and maxPriorityFeePerGas requires type: " ${ TRANSACTION _ENVELOPE _TYPES . FEE _MARKET } " ` ,
) ;
}
break ;
case 'gasPrice' :
default :
if (
txParams . type &&
txParams . type === TRANSACTION _ENVELOPE _TYPES . FEE _MARKET
) {
throw ethErrors . rpc . invalidParams (
` Invalid transaction envelope type: specified type " ${ txParams . type } " but included a gasPrice instead of maxFeePerGas and maxPriorityFeePerGas ` ,
) ;
}
}
}
2019-07-31 22:17:11 +02:00
/ * *
2020-04-20 21:12:24 +02:00
* Validates the given tx parameters
2022-01-07 16:57:33 +01:00
*
2022-08-24 20:57:47 +02:00
* @ param { object } txParams - the tx params
2021-08-02 17:38:01 +02:00
* @ param { boolean } eip1559Compatibility - whether or not the current network supports EIP - 1559 transactions
2020-04-20 21:12:24 +02:00
* @ throws { Error } if the tx params contains invalid fields
2018-04-19 20:29:26 +02:00
* /
2021-08-02 17:38:01 +02:00
export function validateTxParams ( txParams , eip1559Compatibility = true ) {
2020-12-04 03:15:59 +01:00
if ( ! txParams || typeof txParams !== 'object' || Array . isArray ( txParams ) ) {
throw ethErrors . rpc . invalidParams (
'Invalid transaction params: must be an object.' ,
2021-02-04 19:15:23 +01:00
) ;
2020-12-04 03:15:59 +01:00
}
if ( ! txParams . to && ! txParams . data ) {
throw ethErrors . rpc . invalidParams (
'Invalid transaction params: must specify "data" for contract deployments, or "to" (and optionally "data") for all other types of transactions.' ,
2021-02-04 19:15:23 +01:00
) ;
2020-12-04 03:15:59 +01:00
}
2021-08-02 17:38:01 +02:00
if ( isEIP1559Transaction ( { txParams } ) && ! eip1559Compatibility ) {
throw ethErrors . rpc . invalidParams (
'Invalid transaction params: params specify an EIP-1559 transaction but the current network does not support EIP-1559' ,
) ;
}
2020-12-04 03:15:59 +01:00
2021-03-30 16:54:05 +02:00
Object . entries ( txParams ) . forEach ( ( [ key , value ] ) => {
// validate types
switch ( key ) {
case 'from' :
validateFrom ( txParams ) ;
break ;
case 'to' :
validateRecipient ( txParams ) ;
break ;
2021-06-29 21:25:56 +02:00
case 'gasPrice' :
ensureProperTransactionEnvelopeTypeProvided ( txParams , 'gasPrice' ) ;
ensureMutuallyExclusiveFieldsNotProvided (
txParams ,
'gasPrice' ,
'maxFeePerGas' ,
) ;
ensureMutuallyExclusiveFieldsNotProvided (
txParams ,
'gasPrice' ,
'maxPriorityFeePerGas' ,
) ;
ensureFieldIsString ( txParams , 'gasPrice' ) ;
break ;
case 'maxFeePerGas' :
ensureProperTransactionEnvelopeTypeProvided ( txParams , 'maxFeePerGas' ) ;
ensureMutuallyExclusiveFieldsNotProvided (
txParams ,
'maxFeePerGas' ,
'gasPrice' ,
) ;
ensureFieldIsString ( txParams , 'maxFeePerGas' ) ;
break ;
case 'maxPriorityFeePerGas' :
ensureProperTransactionEnvelopeTypeProvided (
txParams ,
'maxPriorityFeePerGas' ,
) ;
ensureMutuallyExclusiveFieldsNotProvided (
txParams ,
'maxPriorityFeePerGas' ,
'gasPrice' ,
) ;
ensureFieldIsString ( txParams , 'maxPriorityFeePerGas' ) ;
break ;
2021-03-30 16:54:05 +02:00
case 'value' :
2021-06-29 21:25:56 +02:00
ensureFieldIsString ( txParams , 'value' ) ;
2021-03-30 16:54:05 +02:00
if ( value . toString ( ) . includes ( '-' ) ) {
throw ethErrors . rpc . invalidParams (
` Invalid transaction value " ${ value } ": not a positive number. ` ,
) ;
}
2018-04-06 20:07:20 +02:00
2021-03-30 16:54:05 +02:00
if ( value . toString ( ) . includes ( '.' ) ) {
throw ethErrors . rpc . invalidParams (
` Invalid transaction value of " ${ value } ": number must be in wei. ` ,
) ;
}
2021-10-15 14:04:14 +02:00
if ( ! value . match ( /^0x[a-fA-F0-9]+$/u ) ) {
throw ethErrors . rpc . invalidParams (
` Invalid transaction value of " ${ value } ": not a valid hex string. ` ,
) ;
}
2021-03-30 16:54:05 +02:00
break ;
case 'chainId' :
if ( typeof value !== 'number' && typeof value !== 'string' ) {
throw ethErrors . rpc . invalidParams (
` Invalid transaction params: ${ key } is not a Number or hex string. got: ( ${ value } ) ` ,
) ;
}
break ;
default :
2021-06-29 21:25:56 +02:00
ensureFieldIsString ( txParams , key ) ;
2018-04-06 20:07:20 +02:00
}
2021-03-30 16:54:05 +02:00
} ) ;
2018-04-06 20:07:20 +02:00
}
2019-07-31 22:17:11 +02:00
/ * *
2020-04-20 21:12:24 +02:00
* Validates the { @ code from } field in the given tx params
2022-01-07 16:57:33 +01:00
*
2022-08-24 20:57:47 +02:00
* @ param { object } txParams
2020-04-20 21:12:24 +02:00
* @ throws { Error } if the from address isn ' t valid
2018-04-19 20:29:26 +02:00
* /
2020-11-03 00:41:28 +01:00
export function validateFrom ( txParams ) {
2019-11-20 01:03:20 +01:00
if ( ! ( typeof txParams . from === 'string' ) ) {
2020-12-04 03:15:59 +01:00
throw ethErrors . rpc . invalidParams (
` Invalid "from" address " ${ txParams . from } ": not a string. ` ,
2021-02-04 19:15:23 +01:00
) ;
2019-11-20 01:03:20 +01:00
}
2021-05-17 21:00:59 +02:00
if ( ! isValidHexAddress ( txParams . from , { allowNonPrefixed : false } ) ) {
2021-02-04 19:15:23 +01:00
throw ethErrors . rpc . invalidParams ( 'Invalid "from" address.' ) ;
2019-11-20 01:03:20 +01:00
}
2018-04-06 20:07:20 +02:00
}
2019-07-31 22:17:11 +02:00
/ * *
2020-04-20 21:12:24 +02:00
* Validates the { @ code to } field in the given tx params
2022-01-07 16:57:33 +01:00
*
2022-08-24 20:57:47 +02:00
* @ param { object } txParams - the tx params
* @ returns { object } the tx params
2020-04-20 21:12:24 +02:00
* @ throws { Error } if the recipient is invalid OR there isn ' t tx data
2018-04-19 20:29:26 +02:00
* /
2020-11-03 00:41:28 +01:00
export function validateRecipient ( txParams ) {
2018-04-10 23:53:40 +02:00
if ( txParams . to === '0x' || txParams . to === null ) {
2018-04-06 20:07:20 +02:00
if ( txParams . data ) {
2021-02-04 19:15:23 +01:00
delete txParams . to ;
2018-04-06 20:07:20 +02:00
} else {
2021-02-04 19:15:23 +01:00
throw ethErrors . rpc . invalidParams ( 'Invalid "to" address.' ) ;
2018-04-06 20:07:20 +02:00
}
2021-05-17 21:00:59 +02:00
} else if (
txParams . to !== undefined &&
! isValidHexAddress ( txParams . to , { allowNonPrefixed : false } )
) {
2021-02-04 19:15:23 +01:00
throw ethErrors . rpc . invalidParams ( 'Invalid "to" address.' ) ;
2018-04-06 20:07:20 +02:00
}
2021-02-04 19:15:23 +01:00
return txParams ;
2018-04-10 23:53:40 +02:00
}
2018-04-19 20:29:26 +02:00
2022-02-18 17:48:38 +01:00
export const validateConfirmedExternalTransaction = ( {
txMeta ,
pendingTransactions ,
confirmedTransactions ,
} = { } ) => {
if ( ! txMeta || ! txMeta . txParams ) {
throw ethErrors . rpc . invalidParams (
'"txMeta" or "txMeta.txParams" is missing' ,
) ;
}
if ( txMeta . status !== TRANSACTION _STATUSES . CONFIRMED ) {
throw ethErrors . rpc . invalidParams (
'External transaction status should be "confirmed"' ,
) ;
}
const externalTxNonce = txMeta . txParams . nonce ;
if ( pendingTransactions && pendingTransactions . length > 0 ) {
const foundPendingTxByNonce = pendingTransactions . find (
( el ) => el . txParams ? . nonce === externalTxNonce ,
) ;
if ( foundPendingTxByNonce ) {
throw ethErrors . rpc . invalidParams (
'External transaction nonce should not be in pending txs' ,
) ;
}
}
if ( confirmedTransactions && confirmedTransactions . length > 0 ) {
const foundConfirmedTxByNonce = confirmedTransactions . find (
( el ) => el . txParams ? . nonce === externalTxNonce ,
) ;
if ( foundConfirmedTxByNonce ) {
throw ethErrors . rpc . invalidParams (
'External transaction nonce should not be in confirmed txs' ,
) ;
}
}
} ;
2019-07-31 22:17:11 +02:00
/ * *
2020-04-20 21:12:24 +02:00
* Returns a list of final states
2022-01-07 16:57:33 +01:00
*
2020-04-20 21:12:24 +02:00
* @ returns { string [ ] } the states that can be considered final states
* /
2020-11-03 00:41:28 +01:00
export function getFinalStates ( ) {
2018-04-19 20:29:26 +02:00
return [
2020-11-07 08:38:12 +01:00
TRANSACTION _STATUSES . REJECTED , // the user has responded no!
TRANSACTION _STATUSES . CONFIRMED , // the tx has been included in a block.
TRANSACTION _STATUSES . FAILED , // the tx failed for some reason, included on tx data.
TRANSACTION _STATUSES . DROPPED , // the tx nonce was already used
2021-02-04 19:15:23 +01:00
] ;
2018-04-19 20:29:26 +02:00
}
2022-02-18 17:48:38 +01:00
/ * *
* Normalizes tx receipt gas used to be a hexadecimal string .
* It seems that sometimes the numerical values being returned from
* this . query . getTransactionReceipt are BN instances and not strings .
*
* @ param { string or BN instance } gasUsed
* @ returns normalized gas used as hexadecimal string
* /
export function normalizeTxReceiptGasUsed ( gasUsed ) {
return typeof gasUsed === 'string' ? gasUsed : gasUsed . toString ( 16 ) ;
}