encoding.ts 4.25 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151
/* External Imports */
import { ethers } from 'hardhat'
import { constants, Wallet } from 'ethers'

/* Internal Imports */
import { remove0x, fromHexString } from '@eth-optimism/core-utils'

export interface EIP155Transaction {
  nonce: number
  gasLimit: number
  gasPrice: number
  to: string
  data: string
  chainId: number
}

export interface SignatureParameters {
  messageHash: string
  v: string
  r: string
  s: string
}

export const DEFAULT_EIP155_TX: EIP155Transaction = {
  to: `0x${'12'.repeat(20)}`,
  nonce: 100,
  gasLimit: 1000000,
  gasPrice: 100000000,
  data: `0x${'99'.repeat(10)}`,
  chainId: 420,
}

export const getRawSignedComponents = (signed: string): any[] => {
  return [signed.slice(130, 132), signed.slice(2, 66), signed.slice(66, 130)]
}

export const getSignedComponents = (signed: string): any[] => {
  return ethers.utils.RLP.decode(signed).slice(-3)
}

export const encodeCompactTransaction = (transaction: any): string => {
  const nonce = ethers.utils.zeroPad(transaction.nonce, 3)
  const gasLimit = ethers.utils.zeroPad(transaction.gasLimit, 3)
  if (transaction.gasPrice % 1000000 !== 0)
    throw Error('gas price must be a multiple of 1000000')
  const compressedGasPrice: any = transaction.gasPrice / 1000000
  const gasPrice = ethers.utils.zeroPad(compressedGasPrice, 3)
  const to = !transaction.to.length
    ? fromHexString(constants.AddressZero)
    : fromHexString(transaction.to)
  const data = fromHexString(transaction.data)

  return Buffer.concat([
    Buffer.from(gasLimit),
    Buffer.from(gasPrice),
    Buffer.from(nonce),
    Buffer.from(to),
    data,
  ]).toString('hex')
}

export const serializeEthSignTransaction = (
  transaction: EIP155Transaction
): string => {
  return ethers.utils.defaultAbiCoder.encode(
    ['uint256', 'uint256', 'uint256', 'uint256', 'address', 'bytes'],
    [
      transaction.nonce,
      transaction.gasLimit,
      transaction.gasPrice,
      transaction.chainId,
      transaction.to,
      transaction.data,
    ]
  )
}

export const serializeNativeTransaction = (
  transaction: EIP155Transaction
): string => {
  return ethers.utils.serializeTransaction(transaction)
}

export const signEthSignMessage = async (
  wallet: Wallet,
  transaction: EIP155Transaction
): Promise<SignatureParameters> => {
  const serializedTransaction = serializeEthSignTransaction(transaction)
  const transactionHash = ethers.utils.keccak256(serializedTransaction)
  const transactionHashBytes = ethers.utils.arrayify(transactionHash)
  const transactionSignature = await wallet.signMessage(transactionHashBytes)

  const messageHash = ethers.utils.hashMessage(transactionHashBytes)
  const [v, r, s] = getRawSignedComponents(transactionSignature).map(
    (component) => {
      return remove0x(component)
    }
  )
  return {
    messageHash,
    v: '0' + (parseInt(v, 16) - 27),
    r,
    s,
  }
}

export const signNativeTransaction = async (
  wallet: Wallet,
  transaction: EIP155Transaction
): Promise<SignatureParameters> => {
  const serializedTransaction = serializeNativeTransaction(transaction)
  const transactionSignature = await wallet.signTransaction(transaction)

  const messageHash = ethers.utils.keccak256(serializedTransaction)
  const [v, r, s] = getSignedComponents(transactionSignature).map(
    (component) => {
      return remove0x(component)
    }
  )
  return {
    messageHash,
    v: '0' + (parseInt(v, 16) - transaction.chainId * 2 - 8 - 27),
    r,
    s,
  }
}

export const signTransaction = async (
  wallet: Wallet,
  transaction: EIP155Transaction,
  transactionType: number
): Promise<SignatureParameters> => {
  return transactionType === 2
    ? signEthSignMessage(wallet, transaction) //ETH Signed tx
    : signNativeTransaction(wallet, transaction) //Create EOA tx or EIP155 tx
}

export const encodeSequencerCalldata = async (
  wallet: Wallet,
  transaction: EIP155Transaction,
  transactionType: number
) => {
  const sig = await signTransaction(wallet, transaction, transactionType)
  const encodedTransaction = encodeCompactTransaction(transaction)
  const dataPrefix = `0x0${transactionType}${sig.r}${sig.s}${sig.v}`
  const calldata =
    transactionType === 1
      ? `${dataPrefix}${remove0x(sig.messageHash)}` // Create EOA tx
      : `${dataPrefix}${encodedTransaction}` // EIP155 tx or ETH Signed Tx
  return calldata
}