# Nimbus
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed under either of
#  * Apache License, version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or
#    http://www.apache.org/licenses/LICENSE-2.0)
#  * MIT license ([LICENSE-MIT](LICENSE-MIT) or
#    http://opensource.org/licenses/MIT)
# at your option. This file may not be copied, modified, or distributed except
# according to those terms.

import
  ./types, ../forks,
  ./interpreter/[gas_meter, gas_costs, utils/utils_numeric],
  ../errors, stint, eth/[keys, common], chronicles, tables, macros,
  math, nimcrypto, bncurve/[fields, groups], ./blake2b_f, ./blscurve

type
  PrecompileAddresses* = enum
    # Frontier to Spurious Dragron
    paEcRecover = 1
    paSha256
    paRipeMd160
    paIdentity
    # Byzantium and Constantinople
    paModExp
    paEcAdd
    paEcMul
    paPairing
    # Istanbul
    paBlake2bf
    # Berlin
    # EIP-2537: disabled
    # reason: not included in berlin
    # paBlsG1Add
    # paBlsG1Mul
    # paBlsG1MultiExp
    # paBlsG2Add
    # paBlsG2Mul
    # paBlsG2MultiExp
    # paBlsPairing
    # paBlsMapG1
    # paBlsMapG2

iterator activePrecompiles*(): EthAddress =
  var res: EthAddress
  for c in PrecompileAddresses.low..PrecompileAddresses.high:
    res[^1] = c.byte
    yield res

proc getSignature(computation: Computation): (array[32, byte], Signature) =
  # input is Hash, V, R, S
  template data: untyped = computation.msg.data
  var bytes: array[65, byte] # will hold R[32], S[32], V[1], in that order
  let maxPos = min(data.high, 127)

  # if we don't have at minimum 64 bytes, there can be no valid V
  if maxPos >= 63:
    let v = data[63]
    # check if V[32] is 27 or 28
    if not (v.int in 27..28):
      raise newException(ValidationError, "Invalid V in getSignature")
    for x in 32..<63:
      if data[x] != 0:
        raise newException(ValidationError, "Invalid V in getSignature")

    bytes[64] = v - 27

    # if there is more data for R and S, copy it. Else, defaulted zeroes are
    # used for R and S
    if maxPos >= 64:
      # Copy message data to buffer
      bytes[0..(maxPos-64)] = data[64..maxPos]

    let sig = Signature.fromRaw(bytes)
    if sig.isErr:
      raise newException(ValidationError, "Could not recover signature computation")
    result[1] = sig[]

    # extract message hash, only need to copy when there is a valid signature
    result[0][0..31] = data[0..31]
  else:
    raise newException(ValidationError, "Invalid V in getSignature")

proc simpleDecode*(dst: var FQ2, src: openarray[byte]): bool {.noinit.} =
  # bypassing FQ2.fromBytes
  # because we want to check `value > modulus`
  result = false
  if dst.c1.fromBytes(src.toOpenArray(0, 31)) and
     dst.c0.fromBytes(src.toOpenArray(32, 63)):
    result = true

template simpleDecode*(dst: var FQ, src: openarray[byte]): bool =
  fromBytes(dst, src)

proc getPoint[T: G1|G2](t: typedesc[T], data: openarray[byte]): Point[T] =
  when T is G1:
    const nextOffset = 32
    var px, py: FQ
  else:
    const nextOffset = 64
    var px, py: FQ2
  if not px.simpleDecode(data.toOpenArray(0, nextOffset - 1)):
    raise newException(ValidationError, "Could not get point value")
  if not py.simpleDecode(data.toOpenArray(nextOffset, nextOffset * 2 - 1)):
    raise newException(ValidationError, "Could not get point value")

  if px.isZero() and py.isZero():
    result = T.zero()
  else:
    var ap: AffinePoint[T]
    if not ap.init(px, py):
      raise newException(ValidationError, "Point is not on curve")
    result = ap.toJacobian()

proc getFR(data: openarray[byte]): FR =
  if not result.fromBytes2(data):
    raise newException(ValidationError, "Could not get FR value")

proc ecRecover*(computation: Computation) =
  computation.gasMeter.consumeGas(
    GasECRecover,
    reason="ECRecover Precompile")

  var
    (msgHash, sig) = computation.getSignature()

  var pubkey = recover(sig, SkMessage(msgHash))
  if pubkey.isErr:
    raise newException(ValidationError, "Could not derive public key from computation")

  computation.output.setLen(32)
  computation.output[12..31] = pubkey[].toCanonicalAddress()
  trace "ECRecover precompile", derivedKey = pubkey[].toCanonicalAddress()

proc sha256*(computation: Computation) =
  let
    wordCount = wordCount(computation.msg.data.len)
    gasFee = GasSHA256 + wordCount * GasSHA256Word

  computation.gasMeter.consumeGas(gasFee, reason="SHA256 Precompile")
  computation.output = @(nimcrypto.sha_256.digest(computation.msg.data).data)
  trace "SHA256 precompile", output = computation.output.toHex

proc ripemd160*(computation: Computation) =
  let
    wordCount = wordCount(computation.msg.data.len)
    gasFee = GasRIPEMD160 + wordCount * GasRIPEMD160Word

  computation.gasMeter.consumeGas(gasFee, reason="RIPEMD160 Precompile")
  computation.output.setLen(32)
  computation.output[12..31] = @(nimcrypto.ripemd160.digest(computation.msg.data).data)
  trace "RIPEMD160 precompile", output = computation.output.toHex

proc identity*(computation: Computation) =
  let
    wordCount = wordCount(computation.msg.data.len)
    gasFee = GasIdentity + wordCount * GasIdentityWord

  computation.gasMeter.consumeGas(gasFee, reason="Identity Precompile")
  computation.output = computation.msg.data
  trace "Identity precompile", output = computation.output.toHex

proc modExpInternal(computation: Computation, baseLen, expLen, modLen: int, T: type StUint) =
  template data: untyped {.dirty.} =
    computation.msg.data

  let
    base = data.rangeToPadded[:T](96, 95 + baseLen, baseLen)
    exp = data.rangeToPadded[:T](96 + baseLen, 95 + baseLen + expLen, expLen)
    modulo = data.rangeToPadded[:T](96 + baseLen + expLen, 95 + baseLen + expLen + modLen, modLen)

  # TODO: specs mentions that we should return in "M" format
  #       i.e. if Base and exp are uint512 and Modulo an uint256
  #       we should return a 256-bit big-endian byte array

  # Force static evaluation
  func zero(): array[T.bits div 8, byte] {.compileTime.} = discard
  func one(): array[T.bits div 8, byte] {.compileTime.} =
    when cpuEndian == bigEndian:
      result[0] = 1
    else:
      result[^1] = 1

  # Start with EVM special cases
  let output = if modulo <= 1:
                  # If m == 0: EVM returns 0.
                  # If m == 1: we can shortcut that to 0 as well
                  zero()
              elif exp.isZero():
                  # If 0^0: EVM returns 1
                  # For all x != 0, x^0 == 1 as well
                  one()
              else:
                  powmod(base, exp, modulo).toByteArrayBE

  # maximum output len is the same as modLen
  # if it less than modLen, it will be zero padded at left
  if output.len >= modLen:
    computation.output = @(output[^modLen..^1])
  else:
    computation.output = newSeq[byte](modLen)
    computation.output[^output.len..^1] = output[0..^1]

proc modExpFee(c: Computation, baseLen, expLen, modLen: Uint256, fork: Fork): GasInt =
  template data: untyped {.dirty.} =
    c.msg.data

  func mulComplexity(x: Uint256): Uint256 =
    ## Estimates the difficulty of Karatsuba multiplication
    if x <= 64.u256: x * x
    elif x <= 1024.u256: x * x div 4.u256 + 96.u256 * x - 3072.u256
    else: x * x div 16.u256 + 480.u256 * x - 199680.u256

  func mulComplexityEIP2565(x: Uint256): Uint256 =
    # gas = ceil(x div 8) ^ 2
    result = x + 7
    result = result div 8
    result = result * result

  let adjExpLen = block:
    let
      baseL = baseLen.safeInt
      expL = expLen.safeInt
      first32 = if baseL.uint64 + expL.uint64 < high(int32).uint64 and baseL < data.len:
                  data.rangeToPadded2[:Uint256](96 + baseL, 95 + baseL + expL, min(expL, 32))
                else:
                  0.u256

    if expLen <= 32:
      if first32.isZero(): 0.u256
      else: first32.log2.u256    # highest-bit in exponent
    else:
      if not first32.isZero:
        8.u256 * (expLen - 32.u256) + first32.log2.u256
      else:
        8.u256 * (expLen - 32.u256)

  template gasCalc(comp, divisor: untyped): untyped =
    (
      max(modLen, baseLen).comp *
      max(adjExpLen, 1.u256)
    ) div divisor

  # EIP2565: modExp gas cost
  let gasFee = if fork >= FkBerlin: gasCalc(mulComplexityEIP2565, GasQuadDivisorEIP2565)
               else: gasCalc(mulComplexity, GasQuadDivisor)

  # let gasFee = gasCalc(mulComplexity, GasQuadDivisor)

  if gasFee > high(GasInt).u256:
    raise newException(OutOfGas, "modExp gas overflow")

  result = gasFee.truncate(GasInt)

  # EIP2565: modExp gas cost
  if fork >= FkBerlin and result < 200.GasInt:
    result = 200.GasInt

proc modExp*(c: Computation, fork: Fork = FkByzantium) =
  ## Modular exponentiation precompiled contract
  ## Yellow Paper Appendix E
  ## EIP-198 - https://github.com/ethereum/EIPs/blob/master/EIPS/eip-198.md
  # Parsing the data
  template data: untyped {.dirty.} =
    c.msg.data

  let # lengths Base, Exponent, Modulus
    baseL = data.rangeToPadded[:Uint256](0, 31)
    expL  = data.rangeToPadded[:Uint256](32, 63)
    modL  = data.rangeToPadded[:Uint256](64, 95)
    baseLen = baseL.safeInt
    expLen  = expL.safeInt
    modLen  = modL.safeInt

  let gasFee = modExpFee(c, baseL, expL, modL, fork)
  c.gasMeter.consumeGas(gasFee, reason="ModExp Precompile")

  if baseLen == 0 and modLen == 0:
    # This is a special case where expLength can be very big.
    c.output = @[]
    return

  let maxBytes = max(baseLen, max(expLen, modLen))
  if maxBytes <= 32:
    c.modExpInternal(baseLen, expLen, modLen, UInt256)
  elif maxBytes <= 64:
    c.modExpInternal(baseLen, expLen, modLen, StUint[512])
  elif maxBytes <= 128:
    c.modExpInternal(baseLen, expLen, modLen, StUint[1024])
  elif maxBytes <= 256:
    c.modExpInternal(baseLen, expLen, modLen, StUint[2048])
  elif maxBytes <= 512:
    c.modExpInternal(baseLen, expLen, modLen, StUint[4096])
  elif maxBytes <= 1024:
    c.modExpInternal(baseLen, expLen, modLen, StUint[8192])
  else:
    raise newException(EVMError, "The Nimbus VM doesn't support modular exponentiation with numbers larger than uint8192")

proc bn256ecAdd*(computation: Computation, fork: Fork = FkByzantium) =
  let gasFee = if fork < FkIstanbul: GasECAdd else: GasECAddIstanbul
  computation.gasMeter.consumeGas(gasFee, reason = "ecAdd Precompile")

  var
    input: array[128, byte]
    output: array[64, byte]
  # Padding data
  let len = min(computation.msg.data.len, 128) - 1
  input[0..len] = computation.msg.data[0..len]
  var p1 = G1.getPoint(input.toOpenArray(0, 63))
  var p2 = G1.getPoint(input.toOpenArray(64, 127))
  var apo = (p1 + p2).toAffine()
  if isSome(apo):
    # we can discard here because we supply proper buffer
    discard apo.get().toBytes(output)

  computation.output = @output

proc bn256ecMul*(computation: Computation, fork: Fork = FkByzantium) =
  let gasFee = if fork < FkIstanbul: GasECMul else: GasECMulIstanbul
  computation.gasMeter.consumeGas(gasFee, reason="ecMul Precompile")

  var
    input: array[96, byte]
    output: array[64, byte]

  # Padding data
  let len = min(computation.msg.data.len, 96) - 1
  input[0..len] = computation.msg.data[0..len]
  var p1 = G1.getPoint(input.toOpenArray(0, 63))
  var fr = getFR(input.toOpenArray(64, 95))
  var apo = (p1 * fr).toAffine()
  if isSome(apo):
    # we can discard here because we supply buffer of proper size
    discard apo.get().toBytes(output)

  computation.output = @output

proc bn256ecPairing*(computation: Computation, fork: Fork = FkByzantium) =
  let msglen = len(computation.msg.data)
  if msglen mod 192 != 0:
    raise newException(ValidationError, "Invalid input length")

  let numPoints = msglen div 192
  let gasFee = if fork < FkIstanbul:
                 GasECPairingBase + numPoints * GasECPairingPerPoint
               else:
                 GasECPairingBaseIstanbul + numPoints * GasECPairingPerPointIstanbul
  computation.gasMeter.consumeGas(gasFee, reason="ecPairing Precompile")

  var output: array[32, byte]
  if msglen == 0:
    # we can discard here because we supply buffer of proper size
    discard BNU256.one().toBytes(output)
  else:
    # Calculate number of pairing pairs
    let count = msglen div 192
    # Pairing accumulator
    var acc = FQ12.one()

    for i in 0..<count:
      let s = i * 192
      # Loading AffinePoint[G1], bytes from [0..63]
      var p1 = G1.getPoint(computation.msg.data.toOpenArray(s, s + 63))
      # Loading AffinePoint[G2], bytes from [64..191]
      var p2 = G2.getPoint(computation.msg.data.toOpenArray(s + 64, s + 191))
      # Accumulate pairing result
      acc = acc * pairing(p1, p2)

    if acc == FQ12.one():
      # we can discard here because we supply buffer of proper size
      discard BNU256.one().toBytes(output)

  computation.output = @output

proc blake2bf*(c: Computation) =
  template input: untyped =
    c.msg.data

  if len(input) == blake2FInputLength:
    let gasFee = GasInt(beLoad32(input, 0))
    c.gasMeter.consumeGas(gasFee, reason="blake2bf Precompile")

  var output: array[64, byte]
  if not blake2b_F(input, output):
    raise newException(ValidationError, "Blake2b F function invalid input")
  else:
    c.output = @output

proc blsG1Add*(c: Computation) =
  template input: untyped =
    c.msg.data

  if input.len != 256:
    raise newException(ValidationError, "blsG1Add invalid input len")

  c.gasMeter.consumeGas(Bls12381G1AddGas, reason="blsG1Add Precompile")

  var a, b: BLS_G1
  if not a.decodePoint(input.toOpenArray(0, 127)):
    raise newException(ValidationError, "blsG1Add invalid input A")

  if not b.decodePoint(input.toOpenArray(128, 255)):
    raise newException(ValidationError, "blsG1Add invalid input B")

  a.add b

  c.output = newSeq[byte](128)
  if not encodePoint(a, c.output):
    raise newException(ValidationError, "blsG1Add encodePoint error")

proc blsG1Mul*(c: Computation) =
  template input: untyped =
    c.msg.data

  if input.len != 160:
    raise newException(ValidationError, "blsG1Mul invalid input len")

  c.gasMeter.consumeGas(Bls12381G1MulGas, reason="blsG1Mul Precompile")

  var a: BLS_G1
  if not a.decodePoint(input.toOpenArray(0, 127)):
    raise newException(ValidationError, "blsG1Mul invalid input A")

  var scalar: BLS_SCALAR
  if not scalar.fromBytes(input.toOpenArray(128, 159)):
    raise newException(ValidationError, "blsG1Mul invalid scalar")

  a.mul(scalar)

  c.output = newSeq[byte](128)
  if not encodePoint(a, c.output):
    raise newException(ValidationError, "blsG1Mul encodePoint error")

const
  Bls12381MultiExpDiscountTable = [
    1200, 888, 764, 641, 594, 547, 500, 453, 438, 423,
    408, 394, 379, 364, 349, 334, 330, 326, 322, 318,
    314, 310, 306, 302, 298, 294, 289, 285, 281, 277,
    273, 269, 268, 266, 265, 263, 262, 260, 259, 257,
    256, 254, 253, 251, 250, 248, 247, 245, 244, 242,
    241, 239, 238, 236, 235, 233, 232, 231, 229, 228,
    226, 225, 223, 222, 221, 220, 219, 219, 218, 217,
    216, 216, 215, 214, 213, 213, 212, 211, 211, 210,
    209, 208, 208, 207, 206, 205, 205, 204, 203, 202,
    202, 201, 200, 199, 199, 198, 197, 196, 196, 195,
    194, 193, 193, 192, 191, 191, 190, 189, 188, 188,
    187, 186, 185, 185, 184, 183, 182, 182, 181, 180,
    179, 179, 178, 177, 176, 176, 175, 174
  ]

func calcBlsMultiExpGas(K: int, gasCost: GasInt): GasInt =
  # Calculate G1 point, scalar value pair length
  if K == 0:
    # Return 0 gas for small input length
    return 0.GasInt

  const dLen = Bls12381MultiExpDiscountTable.len
  # Lookup discount value for G1 point, scalar value pair length
  let discount = if K < dLen: Bls12381MultiExpDiscountTable[K-1]
                 else: Bls12381MultiExpDiscountTable[dLen-1]

  # Calculate gas and return the result
  result = (K * gasCost * discount) div 1000

proc blsG1MultiExp*(c: Computation) =
  template input: untyped =
    c.msg.data

  const L = 160
  if (input.len == 0) or ((input.len mod L) != 0):
    raise newException(ValidationError, "blsG1MultiExp invalid input len")

  let
    K = input.len div L
    gas = K.calcBlsMultiExpGas(Bls12381G1MulGas)

  c.gasMeter.consumeGas(gas, reason="blsG1MultiExp Precompile")

  var
    p: BLS_G1
    s: BLS_SCALAR
    acc: BLS_G1

  # Decode point scalar pairs
  for i in 0..<K:
    let off = L * i

    # Decode G1 point
    if not p.decodePoint(input.toOpenArray(off, off+127)):
      raise newException(ValidationError, "blsG1MultiExp invalid input P")

    # Decode scalar value
    if not s.fromBytes(input.toOpenArray(off+128, off+159)):
      raise newException(ValidationError, "blsG1MultiExp invalid scalar")

    p.mul(s)
    if i == 0:
      acc = p
    else:
      acc.add(p)

  c.output = newSeq[byte](128)
  if not encodePoint(acc, c.output):
    raise newException(ValidationError, "blsG1MuliExp encodePoint error")

proc blsG2Add*(c: Computation) =
  template input: untyped =
    c.msg.data

  if input.len != 512:
    raise newException(ValidationError, "blsG2Add invalid input len")

  c.gasMeter.consumeGas(Bls12381G2AddGas, reason="blsG2Add Precompile")

  var a, b: BLS_G2
  if not a.decodePoint(input.toOpenArray(0, 255)):
    raise newException(ValidationError, "blsG2Add invalid input A")

  if not b.decodePoint(input.toOpenArray(256, 511)):
    raise newException(ValidationError, "blsG2Add invalid input B")

  a.add b

  c.output = newSeq[byte](256)
  if not encodePoint(a, c.output):
    raise newException(ValidationError, "blsG2Add encodePoint error")

proc blsG2Mul*(c: Computation) =
  template input: untyped =
    c.msg.data

  if input.len != 288:
    raise newException(ValidationError, "blsG2Mul invalid input len")

  c.gasMeter.consumeGas(Bls12381G2MulGas, reason="blsG2Mul Precompile")

  var a: BLS_G2
  if not a.decodePoint(input.toOpenArray(0, 255)):
    raise newException(ValidationError, "blsG2Mul invalid input A")

  var scalar: BLS_SCALAR
  if not scalar.fromBytes(input.toOpenArray(256, 287)):
    raise newException(ValidationError, "blsG2Mul invalid scalar")

  a.mul(scalar)

  c.output = newSeq[byte](256)
  if not encodePoint(a, c.output):
    raise newException(ValidationError, "blsG2Mul encodePoint error")

proc blsG2MultiExp*(c: Computation) =
  template input: untyped =
    c.msg.data

  const L = 288
  if (input.len == 0) or ((input.len mod L) != 0):
    raise newException(ValidationError, "blsG2MultiExp invalid input len")

  let
    K = input.len div L
    gas = K.calcBlsMultiExpGas(Bls12381G2MulGas)

  c.gasMeter.consumeGas(gas, reason="blsG2MultiExp Precompile")

  var
    p: BLS_G2
    s: BLS_SCALAR
    acc: BLS_G2

  # Decode point scalar pairs
  for i in 0..<K:
    let off = L * i

    # Decode G1 point
    if not p.decodePoint(input.toOpenArray(off, off+255)):
      raise newException(ValidationError, "blsG2MultiExp invalid input P")

    # Decode scalar value
    if not s.fromBytes(input.toOpenArray(off+256, off+287)):
      raise newException(ValidationError, "blsG2MultiExp invalid scalar")

    p.mul(s)
    if i == 0:
      acc = p
    else:
      acc.add(p)

  c.output = newSeq[byte](256)
  if not encodePoint(acc, c.output):
    raise newException(ValidationError, "blsG2MuliExp encodePoint error")

proc blsPairing*(c: Computation) =
  template input: untyped =
    c.msg.data

  const L = 384
  if (input.len == 0) or ((input.len mod L) != 0):
    raise newException(ValidationError, "blsG2Pairing invalid input len")

  let
    K = input.len div L
    gas = Bls12381PairingBaseGas + K.GasInt * Bls12381PairingPerPairGas

  c.gasMeter.consumeGas(gas, reason="blsG2Pairing Precompile")

  var
    g1: BLS_G1P
    g2: BLS_G2P
    acc: BLS_ACC

  # Decode pairs
  for i in 0..<K:
    let off = L * i

    # Decode G1 point
    if not g1.decodePoint(input.toOpenArray(off, off+127)):
      raise newException(ValidationError, "blsG2Pairing invalid G1")

    # Decode G2 point
    if not g2.decodePoint(input.toOpenArray(off+128, off+383)):
      raise newException(ValidationError, "blsG2Pairing invalid G2")

    # 'point is on curve' check already done,
    # Here we need to apply subgroup checks.
    if not g1.subgroupCheck:
      raise newException(ValidationError, "blsG2Pairing invalid G1 subgroup")

    if not g2.subgroupCheck:
      raise newException(ValidationError, "blsG2Pairing invalid G2 subgroup")

    # Update pairing engine with G1 and G2 points
    if i == 0:
      acc = millerLoop(g1, g2)
    else:
      acc.mul(millerLoop(g1, g2))

  c.output = newSeq[byte](32)
  if acc.check():
    c.output[^1] = 1.byte

proc blsMapG1*(c: Computation) =
  template input: untyped =
    c.msg.data

  if input.len != 64:
    raise newException(ValidationError, "blsMapG1 invalid input len")

  c.gasMeter.consumeGas(Bls12381MapG1Gas, reason="blsMapG1 Precompile")

  var fe: BLS_FE
  if not fe.decodeFE(input):
    raise newException(ValidationError, "blsMapG1 invalid field element")

  let p = fe.mapFPToG1()

  c.output = newSeq[byte](128)
  if not encodePoint(p, c.output):
    raise newException(ValidationError, "blsMapG1 encodePoint error")

proc blsMapG2*(c: Computation) =
  template input: untyped =
    c.msg.data

  if input.len != 128:
    raise newException(ValidationError, "blsMapG2 invalid input len")

  c.gasMeter.consumeGas(Bls12381MapG2Gas, reason="blsMapG2 Precompile")

  var fe: BLS_FE2
  if not fe.decodeFE(input):
    raise newException(ValidationError, "blsMapG2 invalid field element")

  let p = fe.mapFPToG2()

  c.output = newSeq[byte](256)
  if not encodePoint(p, c.output):
    raise newException(ValidationError, "blsMapG2 encodePoint error")

proc getMaxPrecompileAddr(fork: Fork): PrecompileAddresses =
  if fork < FkByzantium: paIdentity
  elif fork < FkIstanbul: paPairing
  # EIP 2537: disabled
  # reason: not included in berlin
  # elif fork < FkBerlin: paBlake2bf
  else: PrecompileAddresses.high

proc execPrecompiles*(computation: Computation, fork: Fork): bool {.inline.} =
  for i in 0..18:
    if computation.msg.codeAddress[i] != 0: return

  let lb = computation.msg.codeAddress[19]
  let maxPrecompileAddr = getMaxPrecompileAddr(fork)
  if lb in PrecompileAddresses.low.byte .. maxPrecompileAddr.byte:
    result = true
    let precompile = PrecompileAddresses(lb)
    trace "Call precompile", precompile = precompile, codeAddr = computation.msg.codeAddress
    try:
      case precompile
      of paEcRecover: ecRecover(computation)
      of paSha256: sha256(computation)
      of paRipeMd160: ripeMd160(computation)
      of paIdentity: identity(computation)
      of paModExp: modExp(computation, fork)
      of paEcAdd: bn256ecAdd(computation, fork)
      of paEcMul: bn256ecMul(computation, fork)
      of paPairing: bn256ecPairing(computation, fork)
      of paBlake2bf: blake2bf(computation)
      # EIP 2537: disabled
      # reason: not included in berlin
      # of paBlsG1Add: blsG1Add(computation)
      # of paBlsG1Mul: blsG1Mul(computation)
      # of paBlsG1MultiExp: blsG1MultiExp(computation)
      # of paBlsG2Add: blsG2Add(computation)
      # of paBlsG2Mul: blsG2Mul(computation)
      # of paBlsG2MultiExp: blsG2MultiExp(computation)
      # of paBlsPairing: blsPairing(computation)
      # of paBlsMapG1: blsMapG1(computation)
      # of paBlsMapG2: blsMapG2(computation)
    except OutOfGas as e:
      # cannot use setError here, cyclic dependency
      computation.error = Error(info: e.msg, burnsGas: true)
    except CatchableError as e:
      if fork >= FKByzantium and precompile > paIdentity:
        computation.error = Error(info: e.msg, burnsGas: true)
      else:
        # swallow any other precompiles errors
        debug "execPrecompiles validation error", msg=e.msg