Initial commit

This commit is contained in:
PMunch 2018-12-21 18:27:09 +01:00
commit eb14b4b706
14 changed files with 1652 additions and 0 deletions

87
.appveyor.yml Normal file
View File

@ -0,0 +1,87 @@
version: '{build}'
cache:
- x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z -> .appveyor.yml
- i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z -> .appveyor.yml
- Nim -> .appveyor.yml
matrix:
# We always want 32 and 64-bit compilation
fast_finish: false
platform:
- x86
- x64
install:
- setlocal EnableExtensions EnableDelayedExpansion
- IF "%PLATFORM%" == "x86" (
SET "MINGW_ARCHIVE=i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z" &
SET "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains%%20targetting%%20Win32/Personal%%20Builds/mingw-builds/4.9.2/threads-win32/dwarf/i686-4.9.2-release-win32-dwarf-rt_v4-rev4.7z" &
SET "MINGW_DIR=mingw32"
) ELSE (
IF "%PLATFORM%" == "x64" (
SET "MINGW_ARCHIVE=x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z" &
SET "MINGW_URL=https://sourceforge.net/projects/mingw-w64/files/Toolchains%%20targetting%%20Win64/Personal%%20Builds/mingw-builds/4.9.2/threads-win32/seh/x86_64-4.9.2-release-win32-seh-rt_v4-rev4.7z" &
SET "MINGW_DIR=mingw64"
) else (
echo "Unknown platform"
)
)
- SET PATH=%CD%\%MINGW_DIR%\bin;%CD%\Nim\bin;%PATH%
# Unpack mingw
- IF NOT EXIST "%MINGW_ARCHIVE%" appveyor DownloadFile "%MINGW_URL%" -FileName "%MINGW_ARCHIVE%"
- 7z x -y "%MINGW_ARCHIVE%" > nul
# build nim from our own branch - this to avoid the day-to-day churn and
# regressions of the fast-paced Nim development while maintaining the
# flexibility to apply patches
- SET "NEED_REBUILD="
- IF NOT EXIST "Nim\\.git\\" (
git clone https://github.com/status-im/Nim.git
) ELSE (
( cd Nim ) &
( git pull ) &
( cd .. )
)
# Rebuild Nim if HEAD has moved or if we don't yet have a cached version
- IF NOT EXIST "Nim\\ver.txt" (
SET NEED_REBUILD=1
) ELSE (
( CD Nim ) &
( git rev-parse HEAD > ..\\cur_ver.txt ) &
( fc ver.txt ..\\cur_ver.txt > nul ) &
( IF NOT ERRORLEVEL == 0 SET NEED_REBUILD=1 ) &
( cd .. )
)
- IF NOT EXIST "Nim\\bin\\nim.exe" SET NEED_REBUILD=1
- IF NOT EXIST "Nim\\bin\\nimble.exe" SET NEED_REBUILD=1
# after building nim, wipe csources to save on cache space
- IF DEFINED NEED_REBUILD (
cd Nim &
( IF EXIST "csources" rmdir /s /q csources ) &
git clone --depth 1 https://github.com/nim-lang/csources &
cd csources &
( IF "%PLATFORM%" == "x64" ( build64.bat ) else ( build.bat ) ) &
cd .. &
bin\nim c koch &
koch boot -d:release &
koch nimble &
git rev-parse HEAD > ver.txt &
rmdir /s /q csources
)
build_script:
- cd C:\projects\%APPVEYOR_PROJECT_SLUG%
- nimble install -y
test_script:
- nimble test
deploy: off

11
.gitignore vendored Normal file
View File

@ -0,0 +1,11 @@
nimcache/
# Executables shall be put in an ignored build/ directory
# Ignore dynamic, static libs and libtool archive files
build/
*.so
*.dylib
*.a
*.la
*.exe
*.dll

39
.travis.yml Normal file
View File

@ -0,0 +1,39 @@
language: c # or other C/C++ variants
sudo: false
# https://docs.travis-ci.com/user/caching/
#
# Caching the whole nim folder is better than relying on ccache - this way, we
# skip the expensive bootstrap process and linking
cache:
directories:
- nim
os:
- linux
- osx
install:
# build nim from our own branch - this to avoid the day-to-day churn and
# regressions of the fast-paced Nim development while maintaining the
# flexibility to apply patches
#
# check version of remote branch
- "export NIMVER=$(git ls-remote https://github.com/status-im/nim.git HEAD | cut -f 1)"
# after building nim, wipe csources to save on cache space
- "{ [ -f nim/$NIMVER/bin/nim ] && [ -f nim/$NIMVER/bin/nimble ] ; } ||
{ rm -rf nim ;
mkdir -p nim ;
git clone --depth=1 https://github.com/status-im/nim.git nim/$NIMVER ;
cd nim/$NIMVER ;
sh build_all.sh ;
rm -rf csources ;
cd ../.. ;
}"
- "export PATH=$PWD/nim/$NIMVER/bin:$PATH"
script:
- nimble install -y
- nimble test

205
LICENSE-APACHEv2 Normal file
View File

@ -0,0 +1,205 @@
web3 is licensed under the Apache License version 2
Copyright (c) 2018 Status Research & Development GmbH
-----------------------------------------------------
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright 2018 Status Research & Development GmbH
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

25
LICENSE-MIT Normal file
View File

@ -0,0 +1,25 @@
web3 is licensed under the MIT License
Copyright (c) 2018 Status Research & Development GmbH
-----------------------------------------------------
The MIT License (MIT)
Copyright (c) 2018 Status Research & Development GmbH
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

26
README.md Normal file
View File

@ -0,0 +1,26 @@
# web3
[![Build Status (Travis)](https://img.shields.io/travis/status-im/nim-web3/master.svg?label=Linux%20/%20macOS "Linux/macOS build status (Travis)")](https://travis-ci.org/status-im/nim-web3)
[![Windows build status (Appveyor)](https://img.shields.io/appveyor/ci/nimbus/nim-web3/master.svg?label=Windows "Windows build status (Appveyor)")](https://ci.appveyor.com/project/nimbus/nim-web3)
[![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
[![License: Apache](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
![Stability: experimental](https://img.shields.io/badge/stability-experimental-orange.svg)
The humble beginnings of a Nim library similar to web3.[js|py]
## Installation
You can install the developement version of the library through nimble with the following command
```
nimble install https://github.com/status-im/nim-web3@#master
```
## License
Licensed and distributed under either of
* MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT
* Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
at your option. This file may not be copied, modified, or distributed except according to those terms.

63
src/ethcallsigs.nim Normal file
View File

@ -0,0 +1,63 @@
## This module contains signatures for the Ethereum client RPCs.
## The signatures are not imported directly, but read and processed with parseStmt,
## then a procedure body is generated to marshal native Nim parameters to json and visa versa.
import json, stint, ethtypes
proc web3_clientVersion(): string
proc web3_sha3(data: string): string
proc net_version(): string
proc net_peerCount(): int
proc net_listening(): bool
proc eth_protocolVersion(): string
proc eth_syncing(): JsonNode
proc eth_coinbase(): string
proc eth_mining(): bool
proc eth_hashrate(): int
proc eth_gasPrice(): int64
proc eth_accounts(): seq[array[20, byte]]
proc eth_blockNumber(): int
proc eth_getBalance(data: array[20, byte], quantityTag: string): int
proc eth_getStorageAt(data: array[20, byte], quantity: int, quantityTag: string): seq[byte]
proc eth_getTransactionCount(data: array[20, byte], quantityTag: string)
proc eth_getBlockTransactionCountByHash(data: array[32, byte])
proc eth_getBlockTransactionCountByNumber(quantityTag: string)
proc eth_getUncleCountByBlockHash(data: array[32, byte])
proc eth_getUncleCountByBlockNumber(quantityTag: string)
proc eth_getCode(data: array[20, byte], quantityTag: string): seq[byte]
proc eth_sign(data: array[20, byte], message: seq[byte]): seq[byte]
proc eth_sendTransaction(obj: EthSend): UInt256
proc eth_sendRawTransaction(data: string, quantityTag: int): UInt256
proc eth_call(call: EthCall, quantityTag: string): UInt256
proc eth_estimateGas(call: EthCall, quantityTag: string): UInt256
proc eth_getBlockByHash(data: array[32, byte], fullTransactions: bool): BlockObject
proc eth_getBlockByNumber(quantityTag: string, fullTransactions: bool): BlockObject
proc eth_getTransactionByHash(data: Uint256): TransactionObject
proc eth_getTransactionByBlockHashAndIndex(data: UInt256, quantity: int): TransactionObject
proc eth_getTransactionByBlockNumberAndIndex(quantityTag: string, quantity: int): TransactionObject
proc eth_getTransactionReceipt(data: UInt256): ReceiptObject
proc eth_getUncleByBlockHashAndIndex(data: UInt256, quantity: int64): BlockObject
proc eth_getUncleByBlockNumberAndIndex(quantityTag: string, quantity: int64): BlockObject
proc eth_getCompilers(): seq[string]
proc eth_compileLLL(): seq[byte]
proc eth_compileSolidity(): seq[byte]
proc eth_compileSerpent(): seq[byte]
proc eth_newFilter(filterOptions: FilterOptions): int
proc eth_newBlockFilter(): int
proc eth_newPendingTransactionFilter(): int
proc eth_uninstallFilter(filterId: int): bool
proc eth_getFilterChanges(filterId: int): seq[LogObject]
proc eth_getFilterLogs(filterId: int): seq[LogObject]
proc eth_getLogs(filterOptions: FilterOptions): seq[LogObject]
proc eth_getWork(): seq[UInt256]
proc eth_submitWork(nonce: int64, powHash: Uint256, mixDigest: Uint256): bool
proc eth_submitHashrate(hashRate: UInt256, id: Uint256): bool
proc shh_post(): string
proc shh_version(message: WhisperPost): bool
proc shh_newIdentity(): array[60, byte]
proc shh_hasIdentity(identity: array[60, byte]): bool
proc shh_newGroup(): array[60, byte]
proc shh_addToGroup(identity: array[60, byte]): bool
proc shh_newFilter(filterOptions: FilterOptions, to: array[60, byte], topics: seq[UInt256]): int
proc shh_uninstallFilter(id: int): bool
proc shh_getFilterChanges(id: int): seq[WhisperMessage]
proc shh_getMessages(id: int): seq[WhisperMessage]

159
src/ethhexstrings.nim Normal file
View File

@ -0,0 +1,159 @@
type
HexQuantityStr* = distinct string
HexDataStr* = distinct string
# Hex validation
template stripLeadingZeros(value: string): string =
var cidx = 0
# ignore the last character so we retain '0' on zero value
while cidx < value.len - 1 and value[cidx] == '0':
cidx.inc
value[cidx .. ^1]
proc encodeQuantity*(value: SomeUnsignedInt): string =
var hValue = value.toHex.stripLeadingZeros
result = "0x" & hValue
func hasHexHeader*(value: string): bool =
if value != "" and value[0] == '0' and value[1] in {'x', 'X'} and value.len > 2: true
else: false
template hasHexHeader*(value: HexDataStr|HexQuantityStr): bool =
value.string.hasHexHeader
func isHexChar*(c: char): bool =
if c notin {'0'..'9'} and
c notin {'a'..'f'} and
c notin {'A'..'F'}: false
else: true
proc validate*(value: HexQuantityStr): bool =
template strVal: untyped = value.string
if not value.hasHexHeader:
return false
# No leading zeros
if strVal[2] == '0': return false
for i in 2..<strVal.len:
let c = strVal[i]
if not c.isHexChar:
return false
return true
proc validate*(value: HexDataStr): bool =
template strVal: untyped = value.string
if not value.hasHexHeader:
return false
# Leading zeros are allowed
for i in 2..<strVal.len:
let c = strVal[i]
if not c.isHexChar:
return false
# Must be even number of digits
if strVal.len mod 2 != 0: return false
return true
# Initialisation
template hexDataStr*(value: string): HexDataStr = value.HexDataStr
template hexQuantityStr*(value: string): HexQuantityStr = value.HexQuantityStr
# Converters
import json
from json_rpc/rpcserver import expect
proc `%`*(value: HexDataStr): JsonNode =
if not value.validate:
raise newException(ValueError, "HexDataStr: Invalid hex for Ethereum: " & value.string)
else:
result = %(value.string)
proc `%`*(value: HexQuantityStr): JsonNode =
if not value.validate:
raise newException(ValueError, "HexQuantityStr: Invalid hex for Ethereum: " & value.string)
else:
result = %(value.string)
proc fromJson*(n: JsonNode, argName: string, result: var HexDataStr) =
# Note that '0x' is stripped after validation
n.kind.expect(JString, argName)
let hexStr = n.getStr()
if not hexStr.hexDataStr.validate:
raise newException(ValueError, "Parameter \"" & argName & "\" value is not valid as a Ethereum data \"" & hexStr & "\"")
result = hexStr[2..hexStr.high].hexDataStr
proc fromJson*(n: JsonNode, argName: string, result: var HexQuantityStr) =
# Note that '0x' is stripped after validation
n.kind.expect(JString, argName)
let hexStr = n.getStr()
if not hexStr.hexQuantityStr.validate:
raise newException(ValueError, "Parameter \"" & argName & "\" value is not valid as an Ethereum hex quantity \"" & hexStr & "\"")
result = hexStr[2..hexStr.high].hexQuantityStr
# testing
when isMainModule:
import unittest
suite "Hex quantity":
test "Empty string":
expect ValueError:
let
source = ""
x = hexQuantityStr source
check %x == %source
test "Even length":
let
source = "0x123"
x = hexQuantityStr source
check %x == %source
test "Odd length":
let
source = "0x123"
x = hexQuantityStr"0x123"
check %x == %source
test "Missing header":
expect ValueError:
let
source = "1234"
x = hexQuantityStr source
check %x != %source
expect ValueError:
let
source = "01234"
x = hexQuantityStr source
check %x != %source
expect ValueError:
let
source = "x1234"
x = hexQuantityStr source
check %x != %source
suite "Hex data":
test "Even length":
let
source = "0x1234"
x = hexDataStr source
check %x == %source
test "Odd length":
expect ValueError:
let
source = "0x123"
x = hexDataStr source
check %x != %source
test "Missing header":
expect ValueError:
let
source = "1234"
x = hexDataStr source
check %x != %source
expect ValueError:
let
source = "01234"
x = hexDataStr source
check %x != %source
expect ValueError:
let
source = "x1234"
x = hexDataStr source
check %x != %source

453
src/ethprocs.nim Normal file
View File

@ -0,0 +1,453 @@
import
strutils, json,
nimcrypto, stint,
ethtypes, ethhexstrings, stintjson, json_rpc/rpcserver
#[
For details on available RPC calls, see: https://github.com/ethereum/wiki/wiki/JSON-RPC
Note that many of the calls return hashes and even 'ints' as hex strings.
This module will likely have to be split into smaller sections for ease of use.
Information:
Default block parameter: https://github.com/ethereum/wiki/wiki/JSON-RPC#the-default-block-parameter
Parameter types
Changes might be required for parameter types.
For example:
* String might be more appropriate than seq[byte], for example for addresses, although would need length constraints.
* Int return values might actually be more hex string than int.
* array[32, byte] could be UInt256 or Int256, but default to UInt256.
* EthTypes such as BlockObject and TransactionObject might be better as existing Nimbus objects if present.
NOTE:
* as `from` is a keyword, this has been replaced with `source` for variable names. TODO: Related - eplace `to` with `dest`?
TODO:
* Some values can be returned as different types (eg, int or bool)
* Currently implemented as variant types, but server macros need to support
initialisation of these types before any use as `kind` can only be
specified once without invoking `reset`.
]#
proc addEthRpcs*(server: RpcServer) =
server.rpc("web3_clientVersion") do() -> string:
## Returns the current client version.
result = "Nimbus-RPC-Test"
server.rpc("web3_sha3") do(data: HexDataStr) -> HexDataStr:
## Returns Keccak-256 (not the standardized SHA3-256) of the given data.
##
## data: the data to convert into a SHA3 hash.
## Returns the SHA3 result of the given string.
# TODO: Capture error on malformed input
var rawData: seq[byte]
rawData = data.string.fromHex
# data will have 0x prefix
result = hexDataStr "0x" & $keccak_256.digest(rawData)
server.rpc("net_version") do() -> string:
## Returns string of the current network id:
## "1": Ethereum Mainnet
## "2": Morden Testnet (deprecated)
## "3": Ropsten Testnet
## "4": Rinkeby Testnet
## "42": Kovan Testnet
#[ Note, See:
https://github.com/ethereum/interfaces/issues/6
https://github.com/ethereum/EIPs/issues/611
]#
result = ""
server.rpc("net_listening") do() -> bool:
## Returns boolean true when listening, otherwise false.
result = true
server.rpc("net_peerCount") do() -> int:
## Returns integer of the number of connected peers.
discard
server.rpc("eth_protocolVersion") do() -> string:
## Returns string of the current ethereum protocol version.
discard
server.rpc("eth_syncing") do() -> JsonNode:
## Returns SyncObject or false when not syncing.
var
res: JsonNode
sync: SyncObject
if true: res = %sync
else: res = newJBool(false)
result = res
server.rpc("eth_coinbase") do() -> string:
## Returns the current coinbase address.
result = ""
server.rpc("eth_mining") do() -> bool:
## Returns true of the client is mining, otherwise false.
discard
server.rpc("eth_hashrate") do() -> int:
## Returns the number of hashes per second that the node is mining with.
discard
server.rpc("eth_gasPrice") do() -> int64:
## Returns an integer of the current gas price in wei.
discard
server.rpc("eth_accounts") do() -> seq[array[20, byte]]:
## Returns a list of addresses owned by client.
# TODO: this might be easier to use as seq[string]
# This is what's expected: "result": ["0x407d73d8a49eeb85d32cf465507dd71d507100c1"]
discard
server.rpc("eth_blockNumber") do() -> int:
## Returns integer of the current block number the client is on.
discard
server.rpc("eth_getBalance") do(data: array[20, byte], quantityTag: string) -> int:
## Returns the balance of the account of given address.
##
## data: address to check for balance.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns integer of the current balance in wei.
discard
server.rpc("eth_getStorageAt") do(data: array[20, byte], quantity: int, quantityTag: string) -> seq[byte]:
## Returns the value from a storage position at a given address.
##
## data: address of the storage.
## quantity: integer of the position in the storage.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns: the value at this storage position.
# TODO: More appropriate return type?
# For more details, see: https://github.com/ethereum/wiki/wiki/JSON-RPC#eth_getstorageat
result = @[]
server.rpc("eth_getTransactionCount") do(data: array[20, byte], quantityTag: string):
## Returns the number of transactions sent from an address.
##
## data: address.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns integer of the number of transactions send from this address.
discard
server.rpc("eth_getBlockTransactionCountByHash") do(data: array[32, byte]) -> int:
## Returns the number of transactions in a block from a block matching the given block hash.
##
## data: hash of a block
## Returns integer of the number of transactions in this block.
discard
server.rpc("eth_getBlockTransactionCountByNumber") do(quantityTag: string) -> int:
## Returns the number of transactions in a block matching the given block number.
##
## data: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
## Returns integer of the number of transactions in this block.
discard
server.rpc("eth_getUncleCountByBlockHash") do(data: array[32, byte]):
## Returns the number of uncles in a block from a block matching the given block hash.
##
## data: hash of a block.
## Returns integer of the number of uncles in this block.
discard
server.rpc("eth_getUncleCountByBlockNumber") do(quantityTag: string):
## Returns the number of uncles in a block from a block matching the given block number.
##
## quantityTag: integer of a block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns integer of uncles in this block.
discard
server.rpc("eth_getCode") do(data: array[20, byte], quantityTag: string) -> seq[byte]:
## Returns code at a given address.
##
## data: address
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns the code from the given address.
result = @[]
server.rpc("eth_sign") do(data: array[20, byte], message: seq[byte]) -> seq[byte]:
## The sign method calculates an Ethereum specific signature with: sign(keccak256("\x19Ethereum Signed Message:\n" + len(message) + message))).
## By adding a prefix to the message makes the calculated signature recognisable as an Ethereum specific signature.
## This prevents misuse where a malicious DApp can sign arbitrary data (e.g. transaction) and use the signature to impersonate the victim.
## Note the address to sign with must be unlocked.
##
## data: address.
## message: message to sign.
## Returns signature.
discard
server.rpc("eth_sendTransaction") do(obj: EthSend) -> UInt256:
## Creates new message call transaction or a contract creation, if the data field contains code.
##
## obj: the transaction object.
## Returns the transaction hash, or the zero hash if the transaction is not yet available.
## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract.
discard
server.rpc("eth_sendRawTransaction") do(data: string, quantityTag: int) -> UInt256: # TODO: string or array of byte?
## Creates new message call transaction or a contract creation for signed transactions.
##
## data: the signed transaction data.
## Returns the transaction hash, or the zero hash if the transaction is not yet available.
## Note: Use eth_getTransactionReceipt to get the contract address, after the transaction was mined, when you created a contract.
discard
server.rpc("eth_call") do(call: EthCall, quantityTag: string) -> UInt256:
## Executes a new message call immediately without creating a transaction on the block chain.
##
## call: the transaction call object.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns the return value of executed contract.
# TODO: Should return value be UInt256 or seq[byte] or string?
discard
server.rpc("eth_estimateGas") do(call: EthCall, quantityTag: string) -> UInt256: # TODO: Int or U/Int256?
## Generates and returns an estimate of how much gas is necessary to allow the transaction to complete.
## The transaction will not be added to the blockchain. Note that the estimate may be significantly more than
## the amount of gas actually used by the transaction, for a variety of reasons including EVM mechanics and node performance.
##
## call: the transaction call object.
## quantityTag: integer block number, or the string "latest", "earliest" or "pending", see the default block parameter.
## Returns the amount of gas used.
discard
server.rpc("eth_getBlockByHash") do(data: array[32, byte], fullTransactions: bool) -> BlockObject:
## Returns information about a block by hash.
##
## data: Hash of a block.
## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions.
## Returns BlockObject or nil when no block was found.
discard
server.rpc("eth_getBlockByNumber") do(quantityTag: string, fullTransactions: bool) -> BlockObject:
## Returns information about a block by block number.
##
## quantityTag: integer of a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
## fullTransactions: If true it returns the full transaction objects, if false only the hashes of the transactions.
## Returns BlockObject or nil when no block was found.
discard
server.rpc("eth_getTransactionByHash") do(data: Uint256) -> TransactionObject:
## Returns the information about a transaction requested by transaction hash.
##
## data: hash of a transaction.
## Returns requested transaction information.
discard
server.rpc("eth_getTransactionByBlockHashAndIndex") do(data: UInt256, quantity: int) -> TransactionObject:
## Returns information about a transaction by block hash and transaction index position.
##
## data: hash of a block.
## quantity: integer of the transaction index position.
## Returns requested transaction information.
discard
server.rpc("eth_getTransactionByBlockNumberAndIndex") do(quantityTag: string, quantity: int) -> TransactionObject:
## Returns information about a transaction by block number and transaction index position.
##
## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
## quantity: the transaction index position.
discard
server.rpc("eth_getTransactionReceipt") do(data: UInt256) -> ReceiptObject:
## Returns the receipt of a transaction by transaction hash.
##
## data: hash of a transaction.
## Returns transaction receipt.
discard
server.rpc("eth_getUncleByBlockHashAndIndex") do(data: UInt256, quantity: int64) -> BlockObject:
## Returns information about a uncle of a block by hash and uncle index position.
##
## data: hash a block.
## quantity: the uncle's index position.
## Returns BlockObject or nil when no block was found.
discard
server.rpc("eth_getUncleByBlockNumberAndIndex") do(quantityTag: string, quantity: int64) -> BlockObject:
# Returns information about a uncle of a block by number and uncle index position.
##
## quantityTag: a block number, or the string "earliest", "latest" or "pending", as in the default block parameter.
## quantity: the uncle's index position.
## Returns BlockObject or nil when no block was found.
discard
server.rpc("eth_getCompilers") do() -> seq[string]:
## Returns a list of available compilers in the client.
##
## Returns a list of available compilers.
result = @[]
server.rpc("eth_compileSolidity") do(sourceCode: string) -> seq[byte]:
## Returns compiled solidity code.
##
## sourceCode: source code as string.
## Returns compiles source code.
result = @[]
server.rpc("eth_compileLLL") do(sourceCode: string) -> seq[byte]:
## Returns compiled LLL code.
##
## sourceCode: source code as string.
## Returns compiles source code.
result = @[]
server.rpc("eth_compileSerpent") do(sourceCode: string) -> seq[byte]:
## Returns compiled serpent code.
##
## sourceCode: source code as string.
## Returns compiles source code.
result = @[]
server.rpc("eth_newFilter") do(filterOptions: FilterOptions) -> int:
## Creates a filter object, based on filter options, to notify when the state changes (logs).
## To check if the state has changed, call eth_getFilterChanges.
## Topics are order-dependent. A transaction with a log with topics [A, B] will be matched by the following topic filters:
## [] "anything"
## [A] "A in first position (and anything after)"
## [null, B] "anything in first position AND B in second position (and anything after)"
## [A, B] "A in first position AND B in second position (and anything after)"
## [[A, B], [A, B]] "(A OR B) in first position AND (A OR B) in second position (and anything after)"
##
## filterOptions: settings for this filter.
## Returns integer filter id.
discard
server.rpc("eth_newBlockFilter") do() -> int:
## Creates a filter in the node, to notify when a new block arrives.
## To check if the state has changed, call eth_getFilterChanges.
##
## Returns integer filter id.
discard
server.rpc("eth_newPendingTransactionFilter") do() -> int:
## Creates a filter in the node, to notify when a new block arrives.
## To check if the state has changed, call eth_getFilterChanges.
##
## Returns integer filter id.
discard
server.rpc("eth_uninstallFilter") do(filterId: int) -> bool:
## Uninstalls a filter with given id. Should always be called when watch is no longer needed.
## Additonally Filters timeout when they aren't requested with eth_getFilterChanges for a period of time.
##
## filterId: The filter id.
## Returns true if the filter was successfully uninstalled, otherwise false.
discard
server.rpc("eth_getFilterChanges") do(filterId: int) -> seq[LogObject]:
## Polling method for a filter, which returns an list of logs which occurred since last poll.
##
## filterId: the filter id.
result = @[]
server.rpc("eth_getFilterLogs") do(filterId: int) -> seq[LogObject]:
## filterId: the filter id.
## Returns a list of all logs matching filter with given id.
result = @[]
server.rpc("eth_getLogs") do(filterOptions: FilterOptions) -> seq[LogObject]:
## filterOptions: settings for this filter.
## Returns a list of all logs matching a given filter object.
result = @[]
server.rpc("eth_getWork") do() -> seq[UInt256]:
## Returns the hash of the current block, the seedHash, and the boundary condition to be met ("target").
## Returned list has the following properties:
## DATA, 32 Bytes - current block header pow-hash.
## DATA, 32 Bytes - the seed hash used for the DAG.
## DATA, 32 Bytes - the boundary condition ("target"), 2^256 / difficulty.
result = @[]
server.rpc("eth_submitWork") do(nonce: int64, powHash: Uint256, mixDigest: Uint256) -> bool:
## Used for submitting a proof-of-work solution.
##
## nonce: the nonce found.
## headerPow: the header's pow-hash.
## mixDigest: the mix digest.
## Returns true if the provided solution is valid, otherwise false.
discard
server.rpc("eth_submitHashrate") do(hashRate: UInt256, id: Uint256) -> bool:
## Used for submitting mining hashrate.
##
## hashRate: a hexadecimal string representation (32 bytes) of the hash rate.
## id: a random hexadecimal(32 bytes) ID identifying the client.
## Returns true if submitting went through succesfully and false otherwise.
discard
server.rpc("shh_version") do() -> string:
## Returns string of the current whisper protocol version.
discard
server.rpc("shh_post") do(message: WhisperPost) -> bool:
## Sends a whisper message.
##
## message: Whisper message to post.
## Returns true if the message was send, otherwise false.
discard
server.rpc("shh_newIdentity") do() -> array[60, byte]:
## Creates new whisper identity in the client.
##
## Returns the address of the new identiy.
discard
server.rpc("shh_hasIdentity") do(identity: array[60, byte]) -> bool:
## Checks if the client holds the private keys for a given identity.
##
## identity: the identity address to check.
## Returns true if the client holds the privatekey for that identity, otherwise false.
discard
server.rpc("shh_newGroup") do() -> array[60, byte]:
## (?) - This has no description information in the RPC wiki.
##
## Returns the address of the new group. (?)
discard
server.rpc("shh_addToGroup") do(identity: array[60, byte]) -> bool:
## (?) - This has no description information in the RPC wiki.
##
## identity: the identity address to add to a group (?).
## Returns true if the identity was successfully added to the group, otherwise false (?).
discard
server.rpc("shh_newFilter") do(filterOptions: FilterOptions, to: array[60, byte], topics: seq[UInt256]) -> int: # TODO: Is topic of right type?
## Creates filter to notify, when client receives whisper message matching the filter options.
##
## filterOptions: The filter options:
## to: DATA, 60 Bytes - (optional) identity of the receiver. When present it will try to decrypt any incoming message if the client holds the private key to this identity.
## topics: Array of DATA - list of DATA topics which the incoming message's topics should match. You can use the following combinations:
## [A, B] = A && B
## [A, [B, C]] = A && (B || C)
## [null, A, B] = ANYTHING && A && B null works as a wildcard
## Returns the newly created filter.
discard
server.rpc("shh_uninstallFilter") do(id: int) -> bool:
## Uninstalls a filter with given id.
## Should always be called when watch is no longer needed.
## Additonally Filters timeout when they aren't requested with shh_getFilterChanges for a period of time.
##
## id: the filter id.
## Returns true if the filter was successfully uninstalled, otherwise false.
discard
server.rpc("shh_getFilterChanges") do(id: int) -> seq[WhisperMessage]:
## Polling method for whisper filters. Returns new messages since the last call of this method.
## Note: calling the shh_getMessages method, will reset the buffer for this method, so that you won't receive duplicate messages.
##
## id: the filter id.
discard
server.rpc("shh_getMessages") do(id: int) -> seq[WhisperMessage]:
## Get all messages matching a filter. Unlike shh_getFilterChanges this returns all messages.
##
## id: the filter id.
## Returns a list of messages received since last poll.
discard

171
src/ethtypes.nim Normal file
View File

@ -0,0 +1,171 @@
import options, json
import stint
type
SyncObject* = object
startingBlock*: int
currentBlock*: int
highestBlock*: int
EthSend* = object
source*: array[20, byte] # the address the transaction is send from.
to*: Option[array[20, byte]] # (optional when creating new contract) the address the transaction is directed to.
gas*: Option[int] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas.
gasPrice*: Option[int] # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas.
value*: Option[int] # (optional) integer of the value sent with this transaction.
data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI.
nonce*: Option[int] # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce
#EthSend* = object
# source*: array[20, byte] # the address the transaction is send from.
# to*: array[20, byte] # (optional when creating new contract) the address the transaction is directed to.
# gas*: int # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas.
# gasPrice*: int # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas.
# value*: int # (optional) integer of the value sent with this transaction.
# data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI.
# nonce*: int # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce
EthCall* = object
source*: array[20, byte] # (optional) The address the transaction is send from.
to*: array[20, byte] # The address the transaction is directed to.
gas*: int # (optional) Integer of the gas provided for the transaction execution. eth_call consumes zero gas, but this parameter may be needed by some executions.
gasPrice*: int # (optional) Integer of the gasPrice used for each paid gas.
value*: int # (optional) Integer of the value sent with this transaction.
data*: int # (optional) Hash of the method signature and encoded parameters. For details see Ethereum Contract ABI.
## A block object, or null when no block was found
BlockObject* = ref object
number*: int # the block number. null when its pending block.
hash*: UInt256 # hash of the block. null when its pending block.
parentHash*: UInt256 # hash of the parent block.
nonce*: int64 # hash of the generated proof-of-work. null when its pending block.
sha3Uncles*: UInt256 # SHA3 of the uncles data in the block.
logsBloom*: array[256, byte] # the bloom filter for the logs of the block. null when its pending block.
transactionsRoot*: UInt256 # the root of the transaction trie of the block.
stateRoot*: UInt256 # the root of the final state trie of the block.
receiptsRoot*: UInt256 # the root of the receipts trie of the block.
miner*: array[20, byte] # the address of the beneficiary to whom the mining rewards were given.
difficulty*: int # integer of the difficulty for this block.
totalDifficulty*: int # integer of the total difficulty of the chain until this block.
extraData*: string # the "extra data" field of this block.
size*: int # integer the size of this block in bytes.
gasLimit*: int # the maximum gas allowed in this block.
gasUsed*: int # the total used gas by all transactions in this block.
timestamp*: int # the unix timestamp for when the block was collated.
transactions*: seq[Uint256] # list of transaction objects, or 32 Bytes transaction hashes depending on the last given parameter.
uncles*: seq[Uint256] # list of uncle hashes.
TransactionObject* = object # A transaction object, or null when no transaction was found:
hash*: UInt256 # hash of the transaction.
nonce*: int64 # TODO: Is int? the number of transactions made by the sender prior to this one.
blockHash*: UInt256 # hash of the block where this transaction was in. null when its pending.
blockNumber*: int64 # block number where this transaction was in. null when its pending.
transactionIndex*: int64 # integer of the transactions index position in the block. null when its pending.
source*: array[20, byte] # address of the sender.
to*: array[20, byte] # address of the receiver. null when its a contract creation transaction.
value*: int64 # value transferred in Wei.
gasPrice*: int64 # gas price provided by the sender in Wei.
gas*: int64 # gas provided by the sender.
input*: seq[byte] # the data send along with the transaction.
ReceiptKind* = enum rkRoot, rkStatus
ReceiptObject* = object
# A transaction receipt object, or null when no receipt was found:
transactionHash*: UInt256 # hash of the transaction.
transactionIndex*: int # integer of the transactions index position in the block.
blockHash*: UInt256 # hash of the block where this transaction was in.
blockNumber*: int # block number where this transaction was in.
cumulativeGasUsed*: int # the total amount of gas used when this transaction was executed in the block.
gasUsed*: int # the amount of gas used by this specific transaction alone.
contractAddress*: array[20, byte] # the contract address created, if the transaction was a contract creation, otherwise null.
logs*: seq[string] # TODO: See Wiki for details. list of log objects, which this transaction generated.
logsBloom*: array[256, byte] # bloom filter for light clients to quickly retrieve related logs.
# TODO:
#case kind*: ReceiptKind
#of rkRoot: root*: UInt256 # post-transaction stateroot (pre Byzantium).
#of rkStatus: status*: int # 1 = success, 0 = failure.
FilterDataKind* = enum fkItem, fkList
FilterData* = object
# Difficult to process variant objects in input data, as kind is immutable.
# TODO: This might need more work to handle "or" options
kind*: FilterDataKind
items*: seq[FilterData]
item*: UInt256
# TODO: I don't think this will work as input, need only one value that is either UInt256 or seq[UInt256]
FilterOptions* = object
fromBlock*: string # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions.
toBlock*: string # (optional, default: "latest") integer block number, or "latest" for the last mined block or "pending", "earliest" for not yet mined transactions.
address*: seq[array[20, byte]] # (optional) contract address or a list of addresses from which logs should originate.
topics*: seq[FilterData] # (optional) list of DATA topics. Topics are order-dependent. Each topic can also be a list of DATA with "or" options.
LogObject* = object
removed*: bool # true when the log was removed, due to a chain reorganization. false if its a valid log.
logIndex*: int # integer of the log index position in the block. null when its pending log.
transactionIndex*: ref int # integer of the transactions index position log was created from. null when its pending log.
transactionHash*: UInt256 # hash of the transactions this log was created from. null when its pending log.
blockHash*: ref UInt256 # hash of the block where this log was in. null when its pending. null when its pending log.
blockNumber*: ref int64 # the block number where this log was in. null when its pending. null when its pending log.
address*: array[20, byte] # address from which this log originated.
data*: seq[UInt256] # contains one or more 32 Bytes non-indexed arguments of the log.
topics*: array[4, UInt256] # array of 0 to 4 32 Bytes DATA of indexed log arguments.
# (In solidity: The first topic is the hash of the signature of the event.
# (e.g. Deposit(address,bytes32,uint256)), except you declared the event with the anonymous specifier.)
WhisperPost* = object
# The whisper post object:
source*: array[60, byte] # (optional) the identity of the sender.
to*: array[60, byte] # (optional) the identity of the receiver. When present whisper will encrypt the message so that only the receiver can decrypt it.
topics*: seq[UInt256] # TODO: Correct type? list of DATA topics, for the receiver to identify messages.
payload*: UInt256 # TODO: Correct type - maybe string? the payload of the message.
priority*: int # integer of the priority in a rang from ... (?).
ttl*: int # integer of the time to live in seconds.
WhisperMessage* = object
# (?) are from the RPC Wiki, indicating uncertainty in type format.
hash*: UInt256 # (?) the hash of the message.
source*: array[60, byte] # the sender of the message, if a sender was specified.
to*: array[60, byte] # the receiver of the message, if a receiver was specified.
expiry*: int # integer of the time in seconds when this message should expire (?).
ttl*: int # integer of the time the message should float in the system in seconds (?).
sent*: int # integer of the unix timestamp when the message was sent.
topics*: seq[UInt256] # list of DATA topics the message contained.
payload*: string # TODO: Correct type? the payload of the message.
workProved*: int # integer of the work this message required before it was send (?).
# EthSend* = object
# source*: array[20, byte] # the address the transaction is send from.
# to*: Option[array[20, byte]] # (optional when creating new contract) the address the transaction is directed to.
# gas*: Option[int] # (optional, default: 90000) integer of the gas provided for the transaction execution. It will return unused gas.
# gasPrice*: Option[int] # (optional, default: To-Be-Determined) integer of the gasPrice used for each paid gas.
# value*: Option[int] # (optional) integer of the value sent with this transaction.
# data*: string # the compiled code of a contract OR the hash of the invoked method signature and encoded parameters. For details see Ethereum Contract ABI.
# nonce*: Option[int] # (optional) integer of a nonce. This allows to overwrite your own pending transactions that use the same nonce
var x: array[20, byte] = [1.byte, 2, 3, 4, 5, 6, 7, 0xab, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20]
const hchars = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
proc toStr*[N: static[int]](x: array[N, byte]): string =
result = newString(2 + N*2)
result[0] = '0'
result[1] = 'x'
for i, c in x:
result[2 + i*2] = hchars[(c and 0xf0) shr 4]
result[2 + i*2 + 1] = hchars[c and 0x0f]
proc `%`*(x: EthSend): JsonNode =
result = newJobject()
result["from"] = %x.source.toStr
if x.to.isSome:
result["to"] = %x.to.unsafeGet.toStr
if x.gas.isSome:
result["gas"] = %x.gas.unsafeGet
if x.gasPrice.isSome:
result["gasPrice"] = %x.gasPrice.unsafeGet
if x.value.isSome:
result["value"] = %x.value.unsafeGet
result["data"] = %x.data
if x.nonce.isSome:
result["nonce"] = %x.nonce.unsafeGet

32
src/stintjson.nim Normal file
View File

@ -0,0 +1,32 @@
import json, stint
from json_rpc/rpcserver import expect
template stintStr(n: UInt256|Int256): JsonNode =
var s = n.toHex
if s.len mod 2 != 0: s = "0" & s
s = "0x" & s
%s
proc `%`*(n: UInt256): JsonNode = n.stintStr
proc `%`*(n: Int256): JsonNode = n.stintStr
# allows UInt256 to be passed as a json string
proc fromJson*(n: JsonNode, argName: string, result: var UInt256) =
# expects base 16 string, starting with "0x"
n.kind.expect(JString, argName)
let hexStr = n.getStr()
if hexStr.len > 64 + 2: # including "0x"
raise newException(ValueError, "Parameter \"" & argName & "\" value too long for UInt256: " & $hexStr.len)
result = hexStr.parse(StUint[256], 16) # TODO: Handle errors
# allows ref UInt256 to be passed as a json string
proc fromJson*(n: JsonNode, argName: string, result: var ref UInt256) =
# expects base 16 string, starting with "0x"
n.kind.expect(JString, argName)
let hexStr = n.getStr()
if hexStr.len > 64 + 2: # including "0x"
raise newException(ValueError, "Parameter \"" & argName & "\" value too long for UInt256: " & $hexStr.len)
new result
result[] = hexStr.parse(StUint[256], 16) # TODO: Handle errors

336
src/web3.nim Normal file
View File

@ -0,0 +1,336 @@
import macros, strutils, options, math, json
from os import getCurrentDir, DirSep
import
nimcrypto, stint, httputils, chronicles, asyncdispatch2, json_rpc/rpcclient
import ethtypes, ethprocs, stintjson, ethhexstrings
template sourceDir: string = currentSourcePath.rsplit(DirSep, 1)[0]
## Generate client convenience marshalling wrappers from forward declarations
createRpcSigs(RpcHttpClient, sourceDir & DirSep & "ethcallsigs.nim")
type
Web3 = object
eth*: RpcHttpClient
proc initWeb3*(address: string, port: int): Web3 =
## Just creates a simple dummy wrapper object for now. Functionality should
## increase as the web3 interface is fleshed out.
var client = newRpcHttpClient()
client.httpMethod(MethodPost)
waitFor client.connect(address, Port(port))
result.eth = client
func encode[bits: static[int]](x: Stuint[bits]): string =
## Encodes a `Stuint` to a textual representation for use in the JsonRPC
## `sendTransaction` call.
'0'.repeat((256 - bits) div 4) & x.dumpHex
func encode[bits: static[int]](x: Stint[bits]): string =
## Encodes a `Stint` to a textual representation for use in the JsonRPC
## `sendTransaction` call.
if x.isNegative:
'f'.repeat((256 - bits) div 4) & x.dumpHex
else:
'0'.repeat((256 - bits) div 4) & x.dumpHex
macro makeTypeEnum(): untyped =
## This macro creates all the various types of Solidity contracts and maps
## them to the type used for their encoding. It also creates an enum to
## identify these types in the contract signatures, along with encoder
## functions used in the generated procedures.
result = newStmtList()
var fields: seq[NimNode]
var lastpow2: int
for i in countdown(256, 8, 8):
let
identUint = newIdentNode("Uint" & $i)
identInt = newIdentNode("Int" & $i)
if ceil(log2(i.float)) == floor(log2(i.float)):
lastpow2 = i
result.add quote do:
type
`identUint`* = Stuint[`lastpow2`]
`identInt`* = Stint[`lastpow2`]
fields.add ident("Uint" & $i)
fields.add ident("Int" & $i)
let
identAddress = ident("Address")
identUint = ident("Uint")
identInt = ident("Int")
identBool = ident("Bool")
result.add quote do:
type
`identAddress`* = Uint160
`identUint`* = Uint256
`identInt`* = Int256
`identBool`* = distinct Int256
func encode*(x: `identBool`): string = encode(Int256(x))
fields.add [
identAddress,
identUint,
identInt,
identBool
]
for m in countup(8, 256, 8):
let
identInt = ident("Int" & $m)
identUint = ident("Uint" & $m)
identFixed = ident "Fixed" & $m
identUfixed = ident "Ufixed" & $m
identT = ident "T"
result.add quote do:
# Fixed stuff is not actually implemented yet, these procedures don't
# do what they are supposed to.
type
`identFixed`[N: static[int]] = distinct `identInt`
`identUfixed`[N: static[int]] = distinct `identUint`
func to*(x: `identInt`, `identT`: typedesc[`identFixed`]): `identT` =
T(x)
func to*(x: `identUint`, `identT`: typedesc[`identUfixed`]): `identT` =
T(x)
func encode*[N: static[int]](x: `identFixed`[N]): string =
encode(`identInt`(x) * (10 ^ N).to(`identInt`))
func encode*[N: static[int]](x: `identUfixed`[N]): string =
encode(`identUint`(x) * (10 ^ N).to(`identUint`))
fields.add ident("Fixed" & $m)
fields.add ident("Ufixed" & $m)
let
identFixed = ident("Fixed")
identUfixed = ident("Ufixed")
fields.add identFixed
fields.add identUfixed
result.add quote do:
type
`identFixed` = distinct Int128
`identUfixed` = distinct Uint128
for i in 1..32:
let
identBytes = ident("Bytes" & $i)
identResult = ident "result"
fields.add identBytes
result.add quote do:
type
`identBytes` = array[0..(`i`-1), byte]
func encode(x: `identBytes`): string =
`identResult` = ""
for y in x:
`identResult` &= y.toHex.toLower
`identResult` &= "00".repeat(32 - x.len)
fields.add [
ident("Function"),
ident("Bytes"),
ident("String")
]
result.add quote do:
type
Bytes = seq[byte]
result.add newEnum(ident "FieldKind", fields, public = true, pure = true)
echo result.repr
makeTypeEnum()
type
InterfaceObjectKind = enum
function, constructor, event
MutabilityKind = enum
pure, view, nonpayable, payable
FunctionInputOutput = object
name: string
kind: FieldKind
EventInput = object
name: string
kind: FieldKind
indexed: bool
FunctionObject = object
name: string
stateMutability: MutabilityKind
inputs: seq[FunctionInputOutput]
outputs: seq[FunctionInputOutput]
ConstructorObject = object
stateMutability: MutabilityKind
inputs: seq[FunctionInputOutput]
outputs: seq[FunctionInputOutput]
EventObject = object
name: string
inputs: seq[EventInput]
anonymous: bool
InterfaceObject = object
case kind: InterfaceObjectKind
of function: functionObject: FunctionObject
of constructor: constructorObject: ConstructorObject
of event: eventObject: EventObject
proc getMethodSignature(function: FunctionObject): string =
var signature = function.name & "("
for i, input in function.inputs:
signature.add(
case input.kind:
of FieldKind.Uint: "uint256"
of FieldKind.Int: "int256"
else: ($input.kind).toLower
)
if i != function.inputs.high:
signature.add ","
signature.add ")"
return signature
proc parseContract(body: NimNode): seq[InterfaceObject] =
proc parseOutputs(outputNode: NimNode): seq[FunctionInputOutput] =
if outputNode.kind == nnkIdent:
result.add FunctionInputOutput(
name: "",
kind: parseEnum[FieldKind]($outputNode.ident)
)
proc parseInputs(inputNodes: NimNode): seq[FunctionInputOutput] =
for i in 1..<inputNodes.len:
let input = inputNodes[i]
if input.kind == nnkIdentDefs:
result.add FunctionInputOutput(
name: $input[0].ident,
kind: parseEnum[FieldKind]($input[1].ident)
)
proc parseEventInputs(inputNodes: NimNode): seq[EventInput] =
for i in 1..<inputNodes.len:
let input = inputNodes[i]
if input.kind == nnkIdentDefs:
case input[1].kind:
of nnkIdent:
result.add EventInput(
name: $input[0].ident,
kind: parseEnum[FieldKind]($input[1].ident),
indexed: false
)
of nnkBracketExpr:
doAssert($input[1][0].ident == "indexed",
"Only `indexed` is allowed as option for event inputs")
result.add EventInput(
name: $input[0].ident,
kind: parseEnum[FieldKind]($input[1][1].ident),
indexed: true
)
else:
doAssert(false,
"Can't have anything but ident or bracket expression here")
echo body.treeRepr
var
constructor: Option[ConstructorObject]
functions: seq[FunctionObject]
events: seq[EventObject]
for procdef in body:
doAssert(procdef.kind == nnkProcDef,
"Contracts can only be built with procedures")
let
isconstructor = procdef[4].findChild(it.ident == !"constructor") != nil
isevent = procdef[4].findChild(it.ident == !"event") != nil
doAssert(not (isconstructor and constructor.isSome),
"Contract can only have a single constructor")
doAssert(not (isconstructor and isevent),
"Can't be both event and constructor")
if not isevent:
let
ispure = procdef[4].findChild(it.ident == !"pure") != nil
isview = procdef[4].findChild(it.ident == !"view") != nil
ispayable = procdef[4].findChild(it.ident == !"payable") != nil
doAssert(not (ispure and isview),
"can't be both `pure` and `view`")
doAssert(not ((ispure or isview) and ispayable),
"can't be both `pure` or `view` while being `payable`")
if isconstructor:
constructor = some(ConstructorObject(
stateMutability: if ispure: pure elif isview: view elif ispayable: payable else: nonpayable,
inputs: parseInputs(procdef[3]),
outputs: parseOutputs(procdef[3][0])
))
else:
functions.add FunctionObject(
name: $procdef[0].ident,
stateMutability: if ispure: pure elif isview: view elif ispayable: payable else: nonpayable,
inputs: parseInputs(procdef[3]),
outputs: parseOutputs(procdef[3][0])
)
else:
let isanonymous = procdef[4].findChild(it.ident == !"anonymous") != nil
doAssert(procdef[3][0].kind == nnkEmpty,
"Events can't have return values")
events.add EventObject(
name: $procdef[0].ident,
inputs: parseEventInputs(procdef[3]),
anonymous: isanonymous
)
echo constructor
echo functions
echo events
if constructor.isSome:
result.add InterfaceObject(kind: InterfaceObjectKind.constructor, constructorObject: constructor.unsafeGet)
for function in functions:
result.add InterfaceObject(kind: InterfaceObjectKind.function, functionObject: function)
for event in events:
result.add InterfaceObject(kind: InterfaceObjectKind.event, eventObject: event)
macro contract(cname: untyped, body: untyped): untyped =
var objects = parseContract(body)
result = newStmtList()
for obj in objects:
if obj.kind == function:
let
signature = getMethodSignature(obj.functionObject)
procName = ident obj.functionObject.name
var encodedParams = newLit("")
for input in obj.functionObject.inputs:
encodedParams = nnkInfix.newTree(
ident "&",
encodedParams,
nnkCall.newTree(ident "encode", ident input.name)
)
# This is not the final output, but the signature printed out here can
# be used in sendTransaction to execute the method of the contract.
var procDef = quote do:
proc `procName`() =
echo "0x" & ($keccak_256.digest(`signature`))[0..<8].toLower & `encodedParams`
for input in obj.functionObject.inputs:
procDef[3].add nnkIdentDefs.newTree(
ident input.name,
ident $input.kind,
newEmptyNode()
)
result.add procDef
let test = "InterfaceObject(kind: InterfaceObjectKind.constructor, constructorObqect: " & $objects[0] & ")"
echo result.repr
contract(Test):
proc sendCoin(receiver: Address, amount: Uint): Bool
# This call will generate the `cc.data` part to call that contract method in the code below
sendCoin(fromHex(Stuint[256], "e375b6fb6d0bf0d86707884f3952fee3977251fe"), 600.to(Stuint[256]))
# Set up a JsonRPC call to send a transaction
# The idea here is to let the Web3 object contain the RPC calls, then allow the
# above DSL to create helpers to create the EthSend object and perform the
# transaction. The current idea is to make all this reduce to something like:
# var
# w3 = initWeb3("127.0.0.1", 8545)
# myContract = contract:
# <DSL>
# myContract.sender("0x780bc7b4055941c2cb0ee10510e3fc837eb093c1").sendCoin(
# fromHex(Stuint[256], "e375b6fb6d0bf0d86707884f3952fee3977251fe"),
# 600.to(Stuint[256])
# )
# If the address of the contract on the chain should be part of the DSL or
# dynamically registered is still not decided.
var cc: EthSend
cc.source = [0x78.byte, 0x0b, 0xc7, 0xb4, 0x05, 0x59, 0x41, 0xc2, 0xcb, 0x0e, 0xe1, 0x05, 0x10, 0xe3, 0xfc, 0x83, 0x7e, 0xb0, 0x93, 0xc1]
cc.to = some([0x0a.byte, 0x78, 0xc0, 0x8F, 0x31, 0x4E, 0xB2, 0x5A, 0x35, 0x1B, 0xfB, 0xA9, 0x03,0x21, 0xa6, 0x96, 0x04, 0x74, 0xbD, 0x79])
cc.data = "0x90b98a11000000000000000000000000e375b6fb6d0bf0d86707884f3952fee3977251FE0000000000000000000000000000000000000000000000000000000000000258"
var w3 = initWeb3("127.0.0.1", 8545)
let response = waitFor w3.eth.eth_sendTransaction(cc)
echo response

16
tests/all_tests.nim Normal file
View File

@ -0,0 +1,16 @@
# web3
# Copyright (c) 2018 Status Research & Development GmbH
# Licensed and distributed under either of
# * MIT license: [LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT
# * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
# at your option. This file may not be copied, modified, or distributed except according to those terms.
import unittest,
../src/web3
suite "Your first test suite":
test "Your first test":
block: # independant block of subtest
discard
block:
discard

29
web3.nimble Normal file
View File

@ -0,0 +1,29 @@
packageName = "web3"
version = "0.0.1"
author = "Status Research & Development GmbH"
description = "This is the humble begginings of library similar to web3.[js|py]"
license = "MIT or Apache License 2.0"
srcDir = "src"
bin = @["web3"]
### Dependencies
requires "nim >= 0.18.0"
requires "nimcrypto"
requires "stint"
requires "httputils"
requires "chronicles"
requires "asyncdispatch2"
requires "json_rpc"
### Helper functions
proc test(name: string, defaultLang = "c") =
# TODO, don't forget to change defaultLang to `cpp` if the project requires C++
if not dirExists "build":
mkDir "build"
--run
switch("out", ("./build/" & name))
setCommand defaultLang, "tests/" & name & ".nim"
### tasks
task test, "Run all tests":
test "all_tests"