contract-registry

Ethereum Smart Contract key-value registry
Log | Files | Refs

commit 15cd758fae50278fc80c2fae5a009074261f0ee3
Author: nolash <dev@holbrook.no>
Date:   Tue, 16 Mar 2021 13:53:22 +0100

Initial commit, ported from cic-registry

Diffstat:
A.gitignore | 2++
Apython/contract_registry/data/Registry.bin | 2++
Apython/contract_registry/data/Registry.json | 1+
Apython/contract_registry/encoding.py | 32++++++++++++++++++++++++++++++++
Apython/contract_registry/pytest/__init__.py | 1+
Apython/contract_registry/pytest/fixtures_registry.py | 36++++++++++++++++++++++++++++++++++++
Apython/contract_registry/registry.py | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Apython/contract_registry/unittest/base.py | 0
Apython/gmon.out | 0
Apython/requirements.txt | 3+++
Apython/setup.cfg | 40++++++++++++++++++++++++++++++++++++++++
Apython/setup.py | 11+++++++++++
Apython/test_requirements.txt | 3+++
Apython/tests/conftest.py | 4++++
Apython/tests/test_basic.py | 82+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Asolidity/Makefile | 10++++++++++
Asolidity/Registry.sol | 58++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
17 files changed, 425 insertions(+), 0 deletions(-)

diff --git a/.gitignore b/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +*.pyc diff --git a/python/contract_registry/data/Registry.bin b/python/contract_registry/data/Registry.bin @@ -0,0 +1 @@ +60806040523480156200001157600080fd5b5060405162000a7138038062000a718339818101604052810190620000379190620001c5565b336000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060005b815181101562000104576001828281518110620000c0577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b602002602001015190806001815401808255809150506001900390600052602060002001600090919091909150558080620000fb90620002ac565b9150506200007a565b505062000383565b6000620001236200011d8462000233565b6200020a565b905080838252602082019050828560208602820111156200014357600080fd5b60005b858110156200017757816200015c8882620001ae565b84526020840193506020830192505060018101905062000146565b5050509392505050565b600082601f8301126200019357600080fd5b8151620001a58482602086016200010c565b91505092915050565b600081519050620001bf8162000369565b92915050565b600060208284031215620001d857600080fd5b600082015167ffffffffffffffff811115620001f357600080fd5b620002018482850162000181565b91505092915050565b60006200021662000229565b905062000224828262000276565b919050565b6000604051905090565b600067ffffffffffffffff82111562000251576200025062000329565b5b602082029050602081019050919050565b6000819050919050565b6000819050919050565b620002818262000358565b810181811067ffffffffffffffff82111715620002a357620002a262000329565b5b80604052505050565b6000620002b9826200026c565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415620002ef57620002ee620002fa565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b6000601f19601f8301169050919050565b620003748162000262565b81146200038057600080fd5b50565b6106de80620003936000396000f3fe608060405234801561001057600080fd5b506004361061007f576000357c01000000000000000000000000000000000000000000000000000000009004806328f2d4da146100845780635deed44e146100b45780638da5cb5b146100e45780639648eca214610102578063bb34534c14610132578063f0f86f7114610162575b600080fd5b61009e600480360381019061009991906104f2565b610192565b6040516100ab919061057e565b60405180910390f35b6100ce60048036038101906100c99190610466565b6101b6565b6040516100db919061057e565b60405180910390f35b6100ec6101d3565b6040516100f99190610548565b60405180910390f35b61011c60048036038101906101179190610466565b6101f7565b604051610129919061057e565b60405180910390f35b61014c60048036038101906101479190610466565b610214565b6040516101599190610548565b60405180910390f35b61017c6004803603810190610177919061048f565b610251565b6040516101899190610563565b60405180910390f35b600181815481106101a257600080fd5b906000526020600020016000915090505481565b600060046000838152602001908152602001600020549050919050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600060036000838152602001908152602001600020549050919050565b60006002600083815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff169050919050565b60008060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146102ac57600080fd5b600073ffffffffffffffffffffffffffffffffffffffff166002600087815260200190815260200160002060009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461031857600080fd5b6000805b60018054905081101561038d578660018281548110610364577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b9060005260206000200154141561037a57600191505b8080610385906105eb565b91505061031c565b508061039857600080fd5b846002600088815260200190815260200160002060006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508360036000888152602001908152602001600020819055508260046000868152602001908152602001600020819055506001915050949350505050565b60008135905061043681610663565b92915050565b60008135905061044b8161067a565b92915050565b60008135905061046081610691565b92915050565b60006020828403121561047857600080fd5b60006104868482850161043c565b91505092915050565b600080600080608085870312156104a557600080fd5b60006104b38782880161043c565b94505060206104c487828801610427565b93505060406104d58782880161043c565b92505060606104e68782880161043c565b91505092959194509250565b60006020828403121561050457600080fd5b600061051284828501610451565b91505092915050565b61052481610599565b82525050565b610533816105ab565b82525050565b610542816105b7565b82525050565b600060208201905061055d600083018461051b565b92915050565b6000602082019050610578600083018461052a565b92915050565b60006020820190506105936000830184610539565b92915050565b60006105a4826105c1565b9050919050565b60008115159050919050565b6000819050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b60006105f6826105e1565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82141561062957610628610634565b5b600182019050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b61066c81610599565b811461067757600080fd5b50565b610683816105b7565b811461068e57600080fd5b50565b61069a816105e1565b81146106a557600080fd5b5056fea2646970667358221220c55a8666b79abd3b9abfdd11a7f7afb300ab9783847b841595839f89b1401e6064736f6c63430008020033 +\ No newline at end of file diff --git a/python/contract_registry/data/Registry.json b/python/contract_registry/data/Registry.json @@ -0,0 +1 @@ +[{"inputs":[{"internalType":"bytes32[]","name":"_identifiers","type":"bytes32[]"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[{"internalType":"bytes32","name":"_identifier","type":"bytes32"}],"name":"addressOf","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_identifier","type":"bytes32"}],"name":"chainOf","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_chain","type":"bytes32"}],"name":"configSumOf","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"identifiers","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"_identifier","type":"bytes32"},{"internalType":"address","name":"_address","type":"address"},{"internalType":"bytes32","name":"_chainDescriptor","type":"bytes32"},{"internalType":"bytes32","name":"_chainConfig","type":"bytes32"}],"name":"set","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] diff --git a/python/contract_registry/encoding.py b/python/contract_registry/encoding.py @@ -0,0 +1,32 @@ +# standard imports +import logging + +# external imports +from hexathon import strip_0x + +logg = logging.getLogger(__name__) + + +def to_text(b): + b = b[:b.find(0)] + # TODO: why was this part of this method previously? + # if len(b) % 2 > 0: + # b = b'\x00' + b + return b.decode('utf-8') + + +def from_text(txt): + return '0x{:0<64s}'.format(txt.encode('utf-8').hex()) + + +def from_identifier(b): + return to_text(b) + + +def from_identifier_hex(hx): + b = bytes.fromhex(strip_0x(hx)) + return from_identifier(b) + + +def to_identifier(txt): + return from_text(txt) diff --git a/python/contract_registry/pytest/__init__.py b/python/contract_registry/pytest/__init__.py @@ -0,0 +1 @@ +from .fixtures_registry import * diff --git a/python/contract_registry/pytest/fixtures_registry.py b/python/contract_registry/pytest/fixtures_registry.py @@ -0,0 +1,36 @@ +# standard imports +import logging + +# external imports +import pytest +from chainlib.eth.connection import RPCConnection +from chainlib.eth.tx import receipt + +# local imports +from contract_registry.registry import Registry + +logg = logging.getLogger(__name__) + +valid_identifiers = [ + 'ContractRegistry', + ] + +@pytest.fixture(scope='function') +def registry( + init_eth_tester, + init_eth_rpc, + eth_accounts, + ): + + conn = RPCConnection.connect('default') + builder = Registry(signer=conn.signer) + (tx_hash_hex, o) = builder.constructor(eth_accounts[0], valid_identifiers) + r = conn.do(o) + logg.debug('r {}'.format(r)) + + o = receipt(r) + rcpt = conn.do(o) + + assert rcpt['status'] == 1 + return rcpt['contract_address'] + diff --git a/python/contract_registry/registry.py b/python/contract_registry/registry.py @@ -0,0 +1,140 @@ +# standard imports +import os +import logging +import json + +# third-party imports +from chainlib.eth.contract import ( + ABIContractEncoder, + ABIContractType, + abi_decode_single, + ) +from chainlib.chain import ChainSpec +from chainlib.eth.constant import ( + ZERO_ADDRESS, + ZERO_CONTENT, + MAX_UINT, + ) +from chainlib.eth.rpc import ( + jsonrpc_template, + ) +from hexathon import ( + even, + add_0x, + ) +from chainlib.eth.tx import TxFactory + +# local imports +from .encoding import to_identifier + +logg = logging.getLogger(__name__) + +moddir = os.path.dirname(__file__) +datadir = os.path.join(moddir, 'data') + + +class Registry(TxFactory): + + default_chain_spec = None + __chains_registry = {} + + __abi = None + __bytecode = None + + + + @staticmethod + def abi(): + if Registry.__abi == None: + f = open(os.path.join(datadir, 'Registry.json'), 'r') + Registry.__abi = json.load(f) + f.close() + return Registry.__abi + + + @staticmethod + def bytecode(): + if Registry.__bytecode == None: + f = open(os.path.join(datadir, 'Registry.bin')) + Registry.__bytecode = f.read() + f.close() + return Registry.__bytecode + + + def constructor(self, sender_address, identifier_strings=[]): + # TODO: handle arrays in chainlib encode instead + enc = ABIContractEncoder() + enc.uint256(32) + enc.uint256(len(identifier_strings)) + for s in identifier_strings: + enc.bytes32(to_identifier(s)) + data = enc.get_contents() + + tx = self.template(sender_address, b'', use_nonce=True) + tx = self.set_code(tx, Registry.bytecode() + data) + logg.debug('bytecode {}\ndata {}\ntx {}'.format(Registry.bytecode(), data, tx)) + return self.build(tx) + + + @staticmethod + def address(address=None): + if address != None: + Registry.__address = address + return Registry.__address + + + @staticmethod + def load_for(chain_spec): + chain_str = str(chain_spec) + raise NotImplementedError() + + + def address_of(self, contract_address, identifier_string, sender_address=ZERO_ADDRESS): + o = jsonrpc_template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('addressOf') + enc.typ(ABIContractType.BYTES32) + identifier = to_identifier(identifier_string) + enc.bytes32(identifier) + data = add_0x(enc.encode()) + tx = self.template(sender_address, contract_address) + tx = self.set_code(tx, data) + o['params'].append(self.normalize(tx)) + return o + + + def parse_address_of(self, v): + return abi_decode_single(ABIContractType.ADDRESS, v) + + + def set(self, contract_address, sender_address, identifier_string, address, chain_descriptor_hash, chain_config_hash): + enc = ABIContractEncoder() + enc.method('set') + enc.typ(ABIContractType.BYTES32) + enc.typ(ABIContractType.ADDRESS) + enc.typ(ABIContractType.BYTES32) + enc.typ(ABIContractType.BYTES32) + identifier = to_identifier(identifier_string) + enc.bytes32(identifier) + enc.address(address) + enc.bytes32(chain_descriptor_hash) + enc.bytes32(chain_config_hash) + data = enc.encode() + tx = self.template(sender_address, contract_address, use_nonce=True) + tx = self.set_code(tx, data) + return self.build(tx) + + + def identifier(self, contract_address, idx, sender_address=ZERO_ADDRESS): + o = jsonrpc_template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('identifiers') + enc.typ(ABIContractType.UINT256) + enc.uint256(idx) + data = add_0x(enc.encode()) + tx = self.template(sender_address, contract_address) + tx = self.set_code(tx, data) + o['params'].append(self.normalize(tx)) + return o diff --git a/python/contract_registry/unittest/base.py b/python/contract_registry/unittest/base.py diff --git a/python/gmon.out b/python/gmon.out Binary files differ. diff --git a/python/requirements.txt b/python/requirements.txt @@ -0,0 +1,3 @@ +confini~=0.3.6rc2 +crypto-dev-signer~=0.4.13rc6 +chainlib~=0.0.1a25 diff --git a/python/setup.cfg b/python/setup.cfg @@ -0,0 +1,40 @@ +[metadata] +name = eth-contract-registry +version = 0.5.3a25 +description = Contract registry +author = Louis Holbrook +author_email = dev@holbrook.no +url = https://gitlab.com/grassrootseconomics/cic-registry +keywords = + cryptocurrency + ethereum + smartcontracts +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 :: Software Development :: Libraries + Topic :: Internet + #Topic :: Blockchain :: EVM +license = GPL3 +licence_files = + LICENSE.txt + +[options] +include_package_data = True +python_requires = >= 3.6 +packages = + contract_registry + contract_registry.helper + contract_registry.pytest + contract_registry.runnable + +[options.entry_points] +console_scripts = + contract-registry-deploy = contract_registry.runnable.deploy:main + contract-registry-set = conrtact_registry.runnable.set:main + contract-registry-seal = contract_registry.runnable.seal:main + contract-registry-list = contract_registry.runnable.list:main diff --git a/python/setup.py b/python/setup.py @@ -0,0 +1,11 @@ +from setuptools import setup + +setup( + package_data={ + '': [ + 'data/*.abi.json', + 'data/*.bin', + ], + }, + include_package_data=True, + ) diff --git a/python/test_requirements.txt b/python/test_requirements.txt @@ -0,0 +1,3 @@ +pytest==6.0.1 +eth-tester==0.5.0b3 +py-evm==0.3.0a20 diff --git a/python/tests/conftest.py b/python/tests/conftest.py @@ -0,0 +1,4 @@ +# external imports +from chainlib.eth.pytest import * +from contract_registry.pytest import * + diff --git a/python/tests/test_basic.py b/python/tests/test_basic.py @@ -0,0 +1,82 @@ +# standard imports +import os + +# external imports +import logging +import pytest +from chainlib.eth.tx import ( + receipt, + transaction, + ) +from chainlib.eth.connection import RPCConnection +from chainlib.eth.contract import ( + ABIContractEncoder, + ABIContractType, + abi_decode_single, + ) +from chainlib.eth.nonce import NodeNonceOracle +from chainlib.eth.address import to_checksum_address +from hexathon import ( + add_0x, + strip_0x, + ) + +# local imports +from contract_registry.registry import Registry +from contract_registry.encoding import from_identifier_hex +from contract_registry.pytest.fixtures_registry import valid_identifiers + +logg = logging.getLogger() + +valid_identifiers += [ + 'FooContract', + ] + +def test_set( + registry, + eth_accounts, + eth_rpc, + eth_signer, + ): + + addr_registry = to_checksum_address(os.urandom(20).hex()) + addr_foo = to_checksum_address(os.urandom(20).hex()) + bogus_hash = add_0x(os.urandom(32).hex()) + + #conn = RPCConnection.connect('default') + nonce_oracle = NodeNonceOracle(eth_accounts[0], eth_rpc) + builder = Registry(signer=eth_signer, nonce_oracle=nonce_oracle) + (tx_hash_hex, o) = builder.set(registry, eth_accounts[0], 'ContractRegistry', addr_registry, bogus_hash, bogus_hash) + r = eth_rpc.do(o) + + o = receipt(r) + rcpt = eth_rpc.do(o) + assert rcpt['status'] == 1 + + o = builder.identifier(registry, 0, sender_address=eth_accounts[0]) + r = eth_rpc.do(o) + r = from_identifier_hex(r) + assert r == 'ContractRegistry' + + o = builder.address_of(registry, 'ContractRegistry', sender_address=eth_accounts[0]) + r = eth_rpc.do(o) + r = abi_decode_single(ABIContractType.ADDRESS, r) + assert r == addr_registry + + (tx_hash_hex, o) = builder.set(registry, eth_accounts[0], 'ContractRegistry', addr_registry, bogus_hash, bogus_hash) + r = eth_rpc.do(o) + o = receipt(r) + rcpt = eth_rpc.do(o) + assert rcpt['status'] == 0 + + (tx_hash_hex, o) = builder.set(registry, eth_accounts[0], 'FooContract', addr_foo, bogus_hash, bogus_hash) + r = eth_rpc.do(o) + o = receipt(r) + rcpt = eth_rpc.do(o) + assert rcpt['status'] == 1 + + o = builder.address_of(registry, 'FooContract', sender_address=eth_accounts[0]) + r = eth_rpc.do(o) + r = abi_decode_single(ABIContractType.ADDRESS, r) + assert r == addr_foo + diff --git a/solidity/Makefile b/solidity/Makefile @@ -0,0 +1,10 @@ +SOLC = /usr/bin/solc + +all: + $(SOLC) --bin Registry.sol --evm-version byzantium | awk 'NR>3' > Registry.bin + truncate -s -1 Registry.bin + $(SOLC) --abi Registry.sol --evm-version byzantium | awk 'NR>3' > Registry.json + +install: all + cp -v *{json,bin} ../python/contract_registry/data/ + diff --git a/solidity/Registry.sol b/solidity/Registry.sol @@ -0,0 +1,58 @@ +pragma solidity >0.6.11; + +// Author: Louis Holbrook <dev@holbrook.no> 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 +// SPDX-License-Identifier: GPL-3.0-or-later +// File-version: 2 +// Description: Top-level smart contract registry for the CIC network + + +contract CICRegistry { + address public owner; + + bytes32[] public identifiers; + mapping (bytes32 => address) entries; // contractidentifier -> address + mapping (bytes32 => bytes32) chainIdentifiers; // contractidentifier -> chainidentifier + mapping (bytes32 => bytes32) chainConfigs; // chainidentifier -> chainconfig + + constructor(bytes32[] memory _identifiers) public { + owner = msg.sender; + for (uint i = 0; i < _identifiers.length; i++) { + identifiers.push(_identifiers[i]); + } + } + + function set(bytes32 _identifier, address _address, bytes32 _chainDescriptor, bytes32 _chainConfig) public returns (bool) { + require(msg.sender == owner); + require(entries[_identifier] == address(0)); + bool found = false; + for (uint i = 0; i < identifiers.length; i++) { + if (identifiers[i] == _identifier) { + found = true; + } + } + require(found); + + entries[_identifier] = _address; + chainIdentifiers[_identifier] = _chainDescriptor; + chainConfigs[_chainDescriptor] = _chainConfig; + return true; + } + +// function seal() public returns (bool) { +// require(msg.sender == owner); +// owner = address(0); +// return true; +// } + + function addressOf(bytes32 _identifier) public view returns (address) { + return entries[_identifier]; + } + + function chainOf(bytes32 _identifier) public view returns (bytes32) { + return chainIdentifiers[_identifier]; + } + + function configSumOf(bytes32 _chain) public view returns (bytes32) { + return chainConfigs[_chain]; + } +}