Skip to content

Commit

Permalink
Fix send() transaction to be working again with EIP-155. (#105)
Browse files Browse the repository at this point in the history
* Fix send() transaction to be working again with EIP-155.
Add tests.
---------

Co-authored-by: jangko <jangko128@gmail.com>
  • Loading branch information
cheatfate and jangko authored Dec 13, 2023
1 parent 45d09b2 commit 195c8c6
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 9 deletions.
31 changes: 26 additions & 5 deletions tests/test_signed_tx.nim
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,12 @@
import
std/[options, json],
pkg/unittest2,
chronos, stint, eth/keys,
chronos, stint,
eth/keys,
eth/common/eth_types,
stew/byteutils,
../web3,
../web3/transaction_signing,
./helpers/utils

#[ Contract NumberStorage
Expand All @@ -36,14 +40,31 @@ 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(Quantity(9))
tx.`from` = Address(address)
tx.value = some(1000000000000000000.u256)
tx.to = some(Address(hexToByteArray[20]("0x3535353535353535353535353535353535353535")))
tx.gas = some(Quantity(21000'u64))
tx.gasPrice = some(Quantity(20000000000'i64))
tx.data = @[]

let txBytes = encodeTransaction(tx, privateKey, ChainId(1))
let txHex = "0x" & txBytes.toHex
check txHex == "0xf86c098504a817c800825208943535353535353535353535353535353535353535880de0b6b3a76400008025a028ef61340bd939bc2195fe537567866003e1a15d3c71ff63e1590620aa636276a067cbe9d8997f761aecb703304b3800ccf555c9f3dc64214b297fb1966a3b6d83"

test "contract creation and method invocation":
proc test() {.async.} =
let theRNG = HmacDrbgContext.new()

let web3 = await newWeb3("ws://127.0.0.1:8545/")
let accounts = await web3.provider.eth_accounts()
let gasPrice = int(await web3.provider.eth_gasPrice())
let gasPrice = await web3.provider.eth_gasPrice()
web3.defaultAccount = accounts[0]

let pk = PrivateKey.random(theRNG[])
Expand All @@ -53,7 +74,7 @@ suite "Signed transactions":
tx.`from` = accounts[0]
tx.value = some(ethToWei(10.u256))
tx.to = some(acc)
tx.gasPrice = some(gasPrice.Quantity)
tx.gasPrice = some(gasPrice)

# Send 10 eth to acc
discard await web3.send(tx)
Expand All @@ -71,14 +92,14 @@ suite "Signed transactions":
assert(balance in ethToWei(4.u256)..ethToWei(5.u256)) # 5 minus gas costs

# Creating the contract with a signed tx
let receipt = await web3.deployContract(NumberStorageCode, gasPrice = gasPrice)
let receipt = await web3.deployContract(NumberStorageCode, gasPrice = gasPrice.int)
let contractAddress = receipt.contractAddress.get
balance = await web3.provider.eth_getBalance(acc, "latest")
assert(balance < ethToWei(5.u256))

let c = web3.contractSender(NumberStorage, contractAddress)
# Calling a methof with a signed tx
discard await c.setNumber(5.u256).send(gasPrice = gasPrice)
discard await c.setNumber(5.u256).send(gasPrice = gasPrice.int)

let n = await c.getNumber().call()
assert(n == 5.u256)
Expand Down
27 changes: 24 additions & 3 deletions web3.nim
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,12 @@ import
std/[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/[eth_api_types, conversions, ethhexstrings, transaction_signing, encoding, contract_dsl]

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

export UInt256, Int256, Uint128, Int128
export UInt256, Int256, Uint128, Int128, ChainId
export eth_api_types, conversions, encoding, contract_dsl, HttpClientFlag, HttpClientFlags

type
Expand Down Expand Up @@ -239,11 +241,20 @@ 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 = encodeTransaction(cc, web3.privateKey.get(), chainId)
return await web3.provider.eth_sendRawTransaction(t)

proc sendData(sender: Web3SenderImpl,
data: seq[byte],
value: UInt256,
gas: uint64,
gasPrice: int): Future[TxHash] {.async.} =
gasPrice: int,
chainId = none(ChainId)): Future[TxHash] {.async.} =
let
web3 = sender.web3
gasPrice = if web3.privateKey.isSome() or gasPrice != 0: some(gasPrice.Quantity)
Expand All @@ -261,14 +272,24 @@ proc sendData(sender: Web3SenderImpl,
gasPrice: gasPrice,
)

return await web3.send(cc)
if chainId.isNone:
return await web3.send(cc)
else:
return await web3.send(cc, chainId.get)

proc send*[T](c: ContractInvocation[T, Web3SenderImpl],
value = 0.u256,
gas = 3000000'u64,
gasPrice = 0): Future[TxHash] =
sendData(c.sender, c.data, value, gas, gasPrice)

proc send*[T](c: ContractInvocation[T, Web3SenderImpl],
chainId: ChainId,
value = 0.u256,
gas = 3000000'u64,
gasPrice = 0): Future[TxHash] =
sendData(c.sender, c.data, value, gas, gasPrice, some(chainId))

proc call*[T](c: ContractInvocation[T, Web3SenderImpl],
value = 0.u256,
gas = 3000000'u64,
Expand Down
31 changes: 30 additions & 1 deletion web3/transaction_signing.nim
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,24 @@ 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): seq[byte] =
var tr = Transaction(txType: TxLegacy)
tr.gasLimit = GasInt(s.gas.get.uint64)
tr.gasLimit = s.gas.get.GasInt
tr.gasPrice = s.gasPrice.get.GasInt
if s.to.isSome:
tr.to = some(EthAddress(s.to.get))
Expand All @@ -37,3 +52,17 @@ func encodeTransaction*(s: EthSend, pk: PrivateKey): seq[byte] =
tr.payload = s.data
signTransaction(tr, pk)
return rlp.encode(tr)

func encodeTransaction*(s: EthSend, pk: PrivateKey, chainId: ChainId): seq[byte] =
var tr = Transaction(txType: TxLegacy, chainId: chainId)
tr.gasLimit = s.gas.get.GasInt
tr.gasPrice = s.gasPrice.get.GasInt
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)
tr.payload = s.data
signTransactionEip155(tr, pk)
return rlp.encode(tr)

0 comments on commit 195c8c6

Please sign in to comment.