commit 4788326c4e7348da27c945a5c576582eeacfc14b
Author: nolash <dev@holbrook.no>
Date: Mon, 8 Feb 2021 10:39:42 +0100
Initial commit
Diffstat:
12 files changed, 309 insertions(+), 0 deletions(-)
diff --git a/cic_tools/eth/checksum.py b/cic_tools/eth/checksum.py
@@ -0,0 +1,28 @@
+# third-party imports
+import sha3
+from hexathon import (
+ strip_0x,
+ uniform,
+ )
+
+
+def to_checksum(address_hex):
+
+ address_hex = strip_0x(address_hex)
+ address_hex = uniform(address_hex)
+ h = sha3.keccak_256()
+ h.update(address_hex.encode('utf-8'))
+ z = h.digest()
+
+ checksum_address_hex = '0x'
+
+ for (i, c) in enumerate(address_hex):
+ if c in '1234567890':
+ checksum_address_hex += c
+ elif c in 'abcdef':
+ if z[int(i / 2)] & (0x80 >> ((i % 2) * 4)) > 1:
+ checksum_address_hex += c.upper()
+ else:
+ checksum_address_hex += c
+
+ return checksum_address_hex
diff --git a/cic_tools/eth/connection.py b/cic_tools/eth/connection.py
@@ -0,0 +1,31 @@
+import logging
+import json
+from urllib.request import (
+ Request,
+ urlopen,
+ )
+
+from .error import DefaultErrorParser
+from .method import jsonrpc_result
+
+error_parser = DefaultErrorParser()
+logg = logging.getLogger(__name__)
+
+
+class HTTPConnection:
+
+ def __init__(self, url):
+ self.url = url
+
+
+ def do(self, o, error_parser=error_parser):
+ req = Request(
+ self.url,
+ method='POST',
+ )
+ req.add_header('Content-Type', 'application/json')
+ data = json.dumps(o)
+ logg.debug('(HTTP) send {}'.format(data))
+ res = urlopen(req, data=data.encode('utf-8'))
+ o = json.load(res)
+ return jsonrpc_result(o, error_parser)
diff --git a/cic_tools/eth/constant.py b/cic_tools/eth/constant.py
@@ -0,0 +1,2 @@
+zero_address = '0x{:040x}'.format(0)
+zero_content = '0x{:064x}'.format(0)
diff --git a/cic_tools/eth/error.py b/cic_tools/eth/error.py
@@ -0,0 +1,8 @@
+class EthException(Exception):
+ pass
+
+
+class DefaultErrorParser:
+
+ def translate(self, error):
+ return EthException('default parser codeĀ {}'.format(error))
diff --git a/cic_tools/eth/hash.py b/cic_tools/eth/hash.py
@@ -0,0 +1,7 @@
+import sha3
+
+
+def keccak256_hex(s):
+ h = sha3.keccak_256()
+ h.update(s.encode('utf-8'))
+ return h.digest().hex()
diff --git a/cic_tools/eth/method.py b/cic_tools/eth/method.py
@@ -0,0 +1,61 @@
+import sha3
+import uuid
+
+from hexathon import add_0x
+from eth_abi import encode_single
+
+from .hash import keccak256_hex
+from .constant import zero_address
+
+
+# TODO: move to cic-contracts
+erc20_balance_signature = keccak256_hex('balanceOf(address)')[:8]
+erc20_decimals_signature = keccak256_hex('decimals()')[:8]
+
+
+def jsonrpc_template():
+ return {
+ 'jsonrpc': '2.0',
+ 'id': str(uuid.uuid4()),
+ 'method': None,
+ 'params': [],
+ }
+
+
+def erc20_balance(contract_address, address, sender_address=zero_address):
+ o = jsonrpc_template()
+ o['method'] = 'eth_call'
+ data = erc20_balance_signature
+ data += encode_single('address', address).hex()
+ data = add_0x(data)
+ a = call(contract_address, data=data)
+ o['params'].append(a)
+ o['params'].append('latest')
+ return o
+
+
+def erc20_decimals(contract_address, sender_address=zero_address):
+ o = jsonrpc_template()
+ o['method'] = 'eth_call'
+ arg = add_0x(erc20_decimals_signature)
+ #o['params'].append(arg)
+ a = call(contract_address, arg)
+ o['params'].append(a)
+ o['params'].append('latest')
+ return o
+
+
+def call(contract_address, data, sender_address=zero_address):
+ return {
+ 'from': sender_address,
+ 'to': contract_address,
+ 'data': data,
+ }
+
+
+def jsonrpc_result(o, ep):
+ if o.get('error') != None:
+ raise ep.translate(o)
+ return o['result']
+
+
diff --git a/cic_tools/eth/runnable/__init__.py b/cic_tools/eth/runnable/__init__.py
diff --git a/cic_tools/eth/runnable/balance.py b/cic_tools/eth/runnable/balance.py
@@ -0,0 +1,99 @@
+#!python3
+
+"""Token balance query script
+
+.. moduleauthor:: Louis Holbrook <dev@holbrook.no>
+.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746
+
+"""
+
+# SPDX-License-Identifier: GPL-3.0-or-later
+
+# standard imports
+import os
+import json
+import argparse
+import logging
+
+# third-party imports
+from hexathon import (
+ add_0x,
+ strip_0x,
+ even,
+ )
+import sha3
+from eth_abi import encode_single
+
+# local imports
+from cic_tools.eth.checksum import to_checksum
+from cic_tools.eth.method import (
+ jsonrpc_template,
+ erc20_balance,
+ erc20_decimals,
+ jsonrpc_result,
+ )
+from cic_tools.eth.connection import HTTPConnection
+
+
+logging.basicConfig(level=logging.WARNING)
+logg = logging.getLogger()
+
+default_abi_dir = os.environ.get('ETH_ABI_DIR', '/usr/share/local/cic/solidity/abi')
+default_eth_provider = os.environ.get('ETH_PROVIDER', 'http://localhost:8545')
+
+argparser = argparse.ArgumentParser()
+argparser.add_argument('-p', '--provider', dest='p', default=default_eth_provider, type=str, help='Web3 provider url (http only)')
+argparser.add_argument('-t', '--token-address', dest='t', type=str, help='Token address. If not set, will return gas balance')
+argparser.add_argument('-u', '--unsafe', dest='u', action='store_true', help='Auto-convert address to checksum adddress')
+argparser.add_argument('--abi-dir', dest='abi_dir', type=str, default=default_abi_dir, help='Directory containing bytecode and abi (default {})'.format(default_abi_dir))
+argparser.add_argument('-v', action='store_true', help='Be verbose')
+argparser.add_argument('account', type=str, help='Account address')
+args = argparser.parse_args()
+
+
+if args.v:
+ logg.setLevel(logging.DEBUG)
+
+conn = HTTPConnection(args.p)
+
+def main():
+# w3 = web3.Web3(web3.Web3.HTTPProvider(args.p))
+# REPLACE WITH URLLIB
+
+ account = to_checksum(args.account)
+ if not args.u and account != add_0x(args.account):
+ raise ValueError('invalid checksum address')
+
+ r = None
+ decimals = 18
+ if args.t != None:
+ # determine decimals
+ decimals_o = erc20_decimals(args.t)
+ r = conn.do(decimals_o)
+ decimals = int(strip_0x(r), 16)
+
+ # get balance
+ balance_o = erc20_balance(args.t, account)
+ r = conn.do(balance_o)
+
+ else:
+ o = jsonrpc_template()
+ o['method'] = 'eth_getBalance'
+ o['params'].append(account)
+ r = conn.do(o)
+
+ hx = strip_0x(r)
+ balance = int(hx, 16)
+ logg.debug('balance {} = {} decimals {}'.format(even(hx), balance, decimals))
+
+ balance_str = str(balance)
+ balance_len = len(balance_str)
+ if balance_len < 19:
+ print('0.{}'.format(balance_str.zfill(decimals)))
+ else:
+ offset = balance_len-decimals
+ print('{}.{}'.format(balance_str[:offset],balance_str[offset:]))
+
+
+if __name__ == '__main__':
+ main()
diff --git a/cic_tools/eth/runnable/checksum.py b/cic_tools/eth/runnable/checksum.py
@@ -0,0 +1,8 @@
+# standard imports
+import sys
+
+# local imports
+from cic_tools.eth.checksum import to_checksum
+
+
+print(to_checksum(sys.argv[1]))
diff --git a/requirements.txt b/requirements.txt
@@ -0,0 +1,13 @@
+cryptocurrency-cli-tools==0.0.4
+giftable-erc20-token==0.0.7b6
+eth-accounts-index==0.0.10a6
+erc20-single-shot-faucet==0.2.0a5
+erc20-approval-escrow==0.3.0a4
+cic-eth==0.10.0a21+build.e4161b3e
+vobject==0.9.6.1
+faker==4.17.1
+eth-address-index==0.1.0a5
+crypto-dev-signer==0.4.13rc2
+pysha3==1.0.2
+hexathon==0.0.1a2
+eth-abi==2.1.1
diff --git a/setup.cfg b/setup.cfg
@@ -0,0 +1,36 @@
+[metadata]
+name = cic-tools
+version = 0.0.1a1
+description = Executable tools for CIC network
+author = Louis Holbrook
+author_email = dev@holbrook.no
+url = https://gitlab.com/grassrootseconomics/cic-tools
+keywords =
+ cic
+ cryptocurrency
+ ethereum
+ solidarity
+ mutual_credit
+classifiers =
+ Programming Language :: Python :: 3
+ Operating System :: OS Independent
+ Development Status :: 3 - Alpha
+ Environment :: Console
+ Intended Audience :: Developers
+ License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
+ Topic :: Internet
+# Topic :: Blockchain :: EVM
+license = GPL3
+licence_files =
+ LICENSE.txt
+
+[options]
+python_requires = >= 3.6
+packages =
+ cic_tools.eth
+ cic_tools.eth.runnable
+
+[options.entry_points]
+console_scripts =
+ eth-balance = cic_tools.eth.runnable.balance:main
+ eth-checksum = cic_tools.eth.runnable.checksum:main
diff --git a/setup.py b/setup.py
@@ -0,0 +1,16 @@
+from setuptools import setup
+import configparser
+import os
+
+requirements = []
+f = open('requirements.txt', 'r')
+while True:
+ l = f.readline()
+ if l == '':
+ break
+ requirements.append(l.rstrip())
+f.close()
+
+setup(
+ install_requires=requirements,
+ )