utils.ts 6.15 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
import {
  BigNumber,
  BigNumberish,
  BytesLike,
  ContractReceipt,
  ethers,
  Event,
} from 'ethers'

const formatNumber = (value: BigNumberish, name: string): Uint8Array => {
11
  const result = ethers.utils.stripZeros(BigNumber.from(value).toHexString())
12 13 14 15 16 17
  if (result.length > 32) {
    throw new Error(`invalid length for ${name}`)
  }
  return result
}

18
const handleNumber = (value: string): BigNumber => {
19
  if (value === '0x') {
20
    return ethers.constants.Zero
21 22 23 24
  }
  return BigNumber.from(value)
}

25
const handleAddress = (value: string): string => {
26 27 28 29
  if (value === '0x') {
    // @ts-ignore
    return null
  }
30
  return ethers.utils.getAddress(value)
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
}

export enum SourceHashDomain {
  UserDeposit = 0,
  L1InfoDeposit = 1,
}

interface DepositTxOpts {
  sourceHash?: string
  from: string
  to: string | null
  mint: BigNumberish
  value: BigNumberish
  gas: BigNumberish
  data: string
  domain?: SourceHashDomain
  l1BlockHash?: string
  logIndex?: BigNumberish
  sequenceNumber?: BigNumberish
}

interface DepostTxExtraOpts {
  domain?: SourceHashDomain
  l1BlockHash?: string
  logIndex?: BigNumberish
  sequenceNumber?: BigNumberish
}

export class DepositTx {
  public type = '0x7E'
  private _sourceHash?: string
  public from: string
  public to: string | null
  public mint: BigNumberish
  public value: BigNumberish
  public gas: BigNumberish
  public data: BigNumberish

  public domain?: SourceHashDomain
  public l1BlockHash?: string
  public logIndex?: BigNumberish
  public sequenceNumber?: BigNumberish

  constructor(opts: Partial<DepositTxOpts> = {}) {
    this._sourceHash = opts.sourceHash
    this.from = opts.from!
    this.to = opts.to!
    this.mint = opts.mint!
    this.value = opts.value!
    this.gas = opts.gas!
    this.data = opts.data!
    this.domain = opts.domain
    this.l1BlockHash = opts.l1BlockHash
    this.logIndex = opts.logIndex
    this.sequenceNumber = opts.sequenceNumber
  }

  hash() {
    const encoded = this.encode()
90
    return ethers.utils.keccak256(encoded)
91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111
  }

  sourceHash() {
    if (!this._sourceHash) {
      let marker: string
      switch (this.domain) {
        case SourceHashDomain.UserDeposit:
          marker = BigNumber.from(this.logIndex).toHexString()
          break
        case SourceHashDomain.L1InfoDeposit:
          marker = BigNumber.from(this.sequenceNumber).toHexString()
          break
        default:
          throw new Error(`Unknown domain: ${this.domain}`)
      }

      if (!this.l1BlockHash) {
        throw new Error('Need l1BlockHash to compute sourceHash')
      }

      const l1BlockHash = this.l1BlockHash
112 113 114 115
      const input = ethers.utils.hexConcat([
        l1BlockHash,
        ethers.utils.zeroPad(marker, 32),
      ])
116
      const depositIDHash = ethers.utils.keccak256(input)
117
      const domain = BigNumber.from(this.domain).toHexString()
118 119 120 121
      const domainInput = ethers.utils.hexConcat([
        ethers.utils.zeroPad(domain, 32),
        depositIDHash,
      ])
122
      this._sourceHash = ethers.utils.keccak256(domainInput)
123 124 125 126 127 128 129
    }
    return this._sourceHash
  }

  encode() {
    const fields: any = [
      this.sourceHash() || '0x',
130 131
      ethers.utils.getAddress(this.from) || '0x',
      this.to != null ? ethers.utils.getAddress(this.to) : '0x',
132 133 134 135 136 137
      formatNumber(this.mint || 0, 'mint'),
      formatNumber(this.value || 0, 'value'),
      formatNumber(this.gas || 0, 'gas'),
      this.data || '0x',
    ]

138
    return ethers.utils.hexConcat([this.type, ethers.utils.RLP.encode(fields)])
139 140 141
  }

  decode(raw: BytesLike, extra: DepostTxExtraOpts = {}) {
142 143
    const payload = ethers.utils.arrayify(raw)
    const transaction = ethers.utils.RLP.decode(payload.slice(1))
144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172

    this._sourceHash = transaction[0]
    this.from = handleAddress(transaction[1])
    this.to = handleAddress(transaction[2])
    this.mint = handleNumber(transaction[3])
    this.value = handleNumber(transaction[4])
    this.gas = handleNumber(transaction[5])
    this.data = transaction[6]

    if ('l1BlockHash' in extra) {
      this.l1BlockHash = extra.l1BlockHash
    }
    if ('domain' in extra) {
      this.domain = extra.domain
    }
    if ('logIndex' in extra) {
      this.logIndex = extra.logIndex
    }
    if ('sequenceNumber' in extra) {
      this.sequenceNumber = extra.sequenceNumber
    }
    return this
  }

  static decode(raw: BytesLike, extra?: DepostTxExtraOpts): DepositTx {
    return new this().decode(raw, extra)
  }

  fromL1Receipt(receipt: ContractReceipt, index: number): DepositTx {
173 174 175
    if (!receipt.events) {
      throw new Error('cannot parse receipt')
    }
176 177 178 179 180 181 182 183 184 185 186 187
    const event = receipt.events[index]
    if (!event) {
      throw new Error(`event index ${index} does not exist`)
    }
    return this.fromL1Event(event)
  }

  static fromL1Receipt(receipt: ContractReceipt, index: number): DepositTx {
    return new this({}).fromL1Receipt(receipt, index)
  }

  fromL1Event(event: Event): DepositTx {
188
    if (event.event !== 'TransactionDeposited') {
189
      throw new Error(`incorrect event type: ${event.event}`)
190 191 192 193 194
    }
    if (typeof event.args === 'undefined') {
      throw new Error('no event args')
    }
    if (typeof event.args.from === 'undefined') {
195
      throw new Error('"from" undefined')
196
    }
197
    this.from = event.args.from
198
    if (typeof event.args.isCreation === 'undefined') {
199
      throw new Error('"isCreation" undefined')
200 201 202 203
    }
    if (typeof event.args.to === 'undefined') {
      throw new Error('"to" undefined')
    }
204
    this.to = event.args.isCreation ? null : event.args.to
205
    if (typeof event.args.mint === 'undefined') {
206
      throw new Error('"mint" undefined')
207
    }
208
    this.mint = event.args.mint
209
    if (typeof event.args.value === 'undefined') {
210
      throw new Error('"value" undefined')
211
    }
212
    this.value = event.args.value
213
    if (typeof event.args.gasLimit === 'undefined') {
214
      throw new Error('"gasLimit" undefined')
215
    }
216
    this.gas = event.args.gasLimit
217
    if (typeof event.args.data === 'undefined') {
218
      throw new Error('"data" undefined')
219
    }
220 221 222 223 224 225 226 227 228 229 230
    this.data = event.args.data
    this.domain = SourceHashDomain.UserDeposit
    this.l1BlockHash = event.blockHash
    this.logIndex = event.logIndex
    return this
  }

  static fromL1Event(event: Event): DepositTx {
    return new this({}).fromL1Event(event)
  }
}