Skip to content

Commit

Permalink
Fix send() transaction to be working again with EIP-155.
Browse files Browse the repository at this point in the history
Add tests.
  • Loading branch information
cheatfate committed Oct 7, 2023
1 parent 6212e65 commit bbc3656
Show file tree
Hide file tree
Showing 3 changed files with 84 additions and 3 deletions.
20 changes: 18 additions & 2 deletions tests/test_signed_tx.nim
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import pkg/unittest2
import ../web3
import chronos, options, json, stint, eth/keys
import ../web3, ../web3/transaction_signing
import stew/byteutils
import chronos, options, json, stint, eth/keys, eth/common/eth_types
import test_utils

#[ Contract NumberStorage
Expand All @@ -25,6 +26,21 @@ contract(NumberStorage):
const NumberStorageCode = "6060604052341561000f57600080fd5b60bb8061001d6000396000f30060606040526004361060485763ffffffff7c01000000000000000000000000000000000000000000000000000000006000350416633fb5c1cb8114604d578063f2c9ecd8146062575b600080fd5b3415605757600080fd5b60606004356084565b005b3415606c57600080fd5b60726089565b60405190815260200160405180910390f35b600055565b600054905600a165627a7a7230582023e722f35009f12d5698a4ab22fb9d55a6c0f479fc43875c65be46fbdd8db4310029"

suite "Signed transactions":
test "encodeTransaction(Transaction, PrivateKey, ChainId) EIP-155 test vector":
let
privateKey = PrivateKey.fromHex("0x4646464646464646464646464646464646464646464646464646464646464646").tryGet()
publicKey = privateKey.toPublicKey()
address = publicKey.toCanonicalAddress()
var tx: EthSend
tx.nonce = some(Nonce(9))
tx.source = Address(address)
tx.value = some(1000000000000000000.u256)
tx.to = some(Address(hexToByteArray[20]("0x3535353535353535353535353535353535353535")))
tx.gas = some(Quantity(21000'u64))
tx.gasPrice = some(int(20000000000))
tx.data = ""

check "0x" & encodeTransaction(tx, privateKey, ChainId(1)) == "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"

test "contract creation and method invocation":
proc test() {.async.} =
Expand Down
35 changes: 34 additions & 1 deletion web3.nim
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ import
std/[macros, strutils, options, math, json, tables, uri, strformat]

from os import DirSep, AltSep
from eth/common/eth_types import ChainId

import
stint, httputils, chronicles, chronos, nimcrypto/keccak,
json_rpc/[rpcclient, jsonmarshal], stew/byteutils, eth/keys,

chronos/apps/http/httpclient,
web3/[ethtypes, conversions, ethhexstrings, transaction_signing, encoding]

Expand All @@ -14,7 +16,7 @@ template sourceDir: string = currentSourcePath.rsplit({DirSep, AltSep}, 1)[0]
## Generate client convenience marshalling wrappers from forward declarations
createRpcSigs(RpcClient, sourceDir & "/web3/ethcallsigs.nim")

export UInt256, Int256, Uint128, Int128
export UInt256, Int256, Uint128, Int128, ChainId
export ethtypes, conversions, encoding, HttpClientFlag, HttpClientFlags

type
Expand Down Expand Up @@ -573,6 +575,14 @@ proc send*(web3: Web3, c: EthSend): Future[TxHash] {.async.} =
else:
return await web3.provider.eth_sendTransaction(c)

proc send*(web3: Web3, c: EthSend, chainId: ChainId): Future[TxHash] {.async.} =
doAssert(web3.privateKey.isSome())
var cc = c
if cc.nonce.isNone:
cc.nonce = some(await web3.nextNonce())
let t = "0x" & encodeTransaction(cc, web3.privateKey.get(), chainId)
return await web3.provider.eth_sendRawTransaction(t)

proc send*(c: ContractCallBase,
value = 0.u256,
gas = 3000000'u64,
Expand All @@ -595,6 +605,29 @@ proc send*(c: ContractCallBase,

return await web3.send(cc)

proc send*(c: ContractCallBase,
chainId: ChainId,
value = 0.u256,
gas = 3000000'u64,
gasPrice = 0): Future[TxHash] {.async.} =
let
web3 = c.web3
gasPrice = if web3.privateKey.isSome() or gasPrice != 0: some(gasPrice)
else: none(int)
nonce = if web3.privateKey.isSome(): some(await web3.nextNonce())
else: none(Nonce)

cc = EthSend(
data: "0x" & c.data,
source: web3.defaultAccount,
to: some(c.to),
gas: some(Quantity(gas)),
value: some(value),
nonce: nonce,
gasPrice: gasPrice)

return await web3.send(cc, chainId)

proc call*[T](c: ContractCall[T],
value = 0.u256,
gas = 3000000'u64,
Expand Down
32 changes: 32 additions & 0 deletions web3/transaction_signing.nim
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,21 @@ func signTransaction(tr: var Transaction, pk: PrivateKey) =

tr.V = int64(v) + 27 # TODO! Complete this

func signTransactionEip155(tr: var Transaction, pk: PrivateKey) =
let chainId = tr.chainId
tr.V = int64(chainId) * 2 + 35

let h = tr.txHashNoSignature
let s = sign(pk, SkMessage(h.data))

var r = toRaw(s)
let v = r[64]

tr.R = fromBytesBE(UInt256, r.toOpenArray(0, 31))
tr.S = fromBytesBE(UInt256, r.toOpenArray(32, 63))

tr.V = int64(v) + int64(chainId) * 2 + 35

func encodeTransaction*(s: EthSend, pk: PrivateKey): string =
var tr = Transaction(txType: TxLegacy)
tr.gasLimit = GasInt(s.gas.get.uint64)
Expand All @@ -31,3 +46,20 @@ func encodeTransaction*(s: EthSend, pk: PrivateKey): string =
tr.payload = hexToSeqByte(s.data)
signTransaction(tr, pk)
return rlp.encode(tr).toHex

func encodeTransaction*(s: EthSend, pk: PrivateKey, chainId: ChainId): string =
var tr = Transaction(txType: TxLegacy, chainId: chainId)
tr.gasLimit = GasInt(s.gas.get.uint64)
tr.gasPrice = s.gasPrice.get
if s.to.isSome:
tr.to = some(EthAddress(s.to.get))

if s.value.isSome:
tr.value = s.value.get
tr.nonce = uint64(s.nonce.get)
# TODO: The following is a misdesign indication.
# All the encodings should be done into seq[byte], not a hex string.
if s.data.len != 0:
tr.payload = hexToSeqByte(s.data)
signTransactionEip155(tr, pk)
return rlp.encode(tr).toHex

0 comments on commit bbc3656

Please sign in to comment.