import { RuntimeError , RETURN_TYPES , TransactionLog } from "@ganache/ethereum-utils" ; import { Data , Quantity , RPCQUANTITY_ONE , BUFFER_ZERO , RPCQUANTITY_EMPTY } from "@ganache/utils" ; import { TypedRpcTransaction } from "./rpc-transaction" ; import type Common from "@ethereumjs/common" ; import { GanacheRawExtraTx , TypedDatabasePayload , TypedDatabaseTransaction } from "./raw" ; import type { RunTxResult } from "@ethereumjs/vm/dist/runTx" ; import { EncodedPart , encode } from "@ganache/rlp" ; import { BaseTransaction } from "./base-transaction" ; import { TransactionReceipt } from "./transaction-receipt" ; import { Address } from "@ganache/ethereum-address" ; export const toValidLengthAddress = ( address , fieldName ) => { const buffer = Data . from ( address ) . toBuffer ( ) ; if ( buffer . byteLength !== Address . ByteLength ) { throw new Error ( `The field ${ fieldName } must have byte length of ${ Address . ByteLength }` ) ; } return Address . from ( buffer ) ; } ; export const hasPartialSignature = ( data ) : data is TypedRpcTransaction & { from ? : string ; v ? : string ; r ? : string ; s ? : string ; } => { return data [ "v" ] != null || data [ "r" ] != null || data [ "s" ] != null ; } ; type TransactionFinalization = { status : "confirmed" ; error ? : Error ; } | { status : "rejected" ; error : Error ; } ; const ONE_BUFFER = RPCQUANTITY_ONE . toBuffer ( ) ; /**
 * A RuntimeTransaction can be changed; its hash is not finalized and it is not
 * yet part of a block.
 */ export abstract class RuntimeTransaction extends BaseTransaction { public hash : Data | null ; /**
     * used by the miner to mark if this transaction is eligible for reordering or
     * removal
     */ public locked : boolean = false ; public logs : TransactionLog [ ] ; public receipt : TransactionReceipt ; public execException : RuntimeError ; public raw : TypedDatabaseTransaction | null ; public serialized : Buffer ; public encodedData : EncodedPart ; public encodedSignature : EncodedPart ; private finalizer : ( eventData : TransactionFinalization ) => void ; private finalized : Promise < TransactionFinalization > ; constructor ( data , common , extra ? ) { super ( common , extra ) ; let finalizer ; this . finalized = new Promise < TransactionFinalization > ( resolve => { finalizer = ( ... args ) => process . nextTick ( resolve , ... args ) ; } ) ; this . finalizer = finalizer ; if ( ! Array . isArray ( data ) ) { this . nonce = Quantity . from ( data . nonce , true ) ; this . gas = Quantity . from ( data . gas == null ? data . gasLimit : data . gas ) ; this . to = data . to == null ? RPCQUANTITY_EMPTY : toValidLengthAddress ( data . to , "to" ) ; this . value = Quantity . from ( data . value || 0 ) ; const dataVal = data . data == null ? data . input == null ? "0x" : data . input : data . data ; this . data = Data . from ( dataVal ) ; } } param privateKey protected abstract signAndHash ( privateKey ) ; public serializeForDb ( blockHash , blockNumber , transactionIndex ) { const txAndExtraData = [ this . raw , [ this . from . toBuffer ( ) , this . hash . toBuffer ( ) , blockHash . toBuffer ( ) , blockNumber . toBuffer ( ) , transactionIndex . toBuffer ( ) , this . effectiveGasPrice . toBuffer ( ) ] ] ; return encode ( txAndExtraData ) ; } abstract toJSON ( common ) ; param result returns public fillFromResult ( result , cumulativeGasUsed ) { const vmResult = result . execResult ; const execException = vmResult . exceptionError ; let status ; if ( execException ) { status = BUFFER_ZERO ; this . execException = new RuntimeError ( this . hash , result , RETURN_TYPES . TRANSACTION_HASH ) ; } else { status = ONE_BUFFER ; } const receipt = ( this . receipt = TransactionReceipt . fromValues ( status , Quantity . from ( cumulativeGasUsed ) . toBuffer ( ) , result . bloom . bitvector , ( this . logs = vmResult . logs || ( [ ] as TransactionLog [ ] ) ) , result . gasUsed . toArrayLike ( Buffer ) , result . createdAddress ? result . createdAddress . buf : null , this . type ) ) ; return receipt . serialize ( false ) ; } public getReceipt ( ) { return this . receipt ; } public getLogs ( ) { return this . logs ; } validateAndSetSignature = ( data ) => { if ( hasPartialSignature ( data ) ) { if ( data . v == null || data . r == null || data . s == null ) { throw new Error ( "Transaction signature is incomplete; v, r, and s are required." ) ; } if ( data . nonce == null ) { throw new Error ( "Signed transaction is incomplete; nonce is required." ) ; } this . v = Quantity . from ( data . v , true ) ; this . r = Quantity . from ( data . r , true ) ; this . s = Quantity . from ( data . s , true ) ; const raw = this . toEthRawTransaction ( this . v . toBuffer ( ) , this . r . toBuffer ( ) , this . s . toBuffer ( ) ) ; this . raw = raw ; if ( ! this . from ) { const { from , serialized , hash , encodedData , encodedSignature } = this . computeIntrinsics ( this . v , raw , this . common . chainId ( ) ) ; if ( data . from !== null ) { const userFrom = toValidLengthAddress ( data . from , "from" ) ; if ( ! from . toBuffer ( ) . equals ( userFrom . toBuffer ( ) ) ) { throw new Error ( "Transaction is signed and contains a `from` field, but the signature doesn't match." ) ; } } this . from = from ; this . serialized = serialized ; this . hash = hash ; this . encodedData = encodedData ; this . encodedSignature = encodedSignature ; } } else if ( data . from != null ) { this . from = toValidLengthAddress ( data . from , "from" ) ; } } ; param event public once ( _event ) { return this . finalized ; } param status param error public finalize ( status , error = null ) { this . finalizer ( { status , error } ) ; } protected abstract toEthRawTransaction ( v , r , s ) ; protected abstract computeIntrinsics ( v , raw , chainId ) ; protected abstract toVmTransaction ( ) ; protected abstract updateEffectiveGasPrice ( baseFeePerGas ? ) ; }