Initial commit, ported from cic-registry

This commit is contained in:
nolash 2021-03-16 13:53:22 +01:00
commit 15cd758fae
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
17 changed files with 424 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
__pycache__
*.pyc

File diff suppressed because one or more lines are too long

View File

@ -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"}]

View File

@ -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)

View File

@ -0,0 +1 @@
from .fixtures_registry import *

View File

@ -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']

View File

@ -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

BIN
python/gmon.out Normal file

Binary file not shown.

3
python/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
confini~=0.3.6rc2
crypto-dev-signer~=0.4.13rc6
chainlib~=0.0.1a25

40
python/setup.cfg Normal file
View File

@ -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

11
python/setup.py Normal file
View File

@ -0,0 +1,11 @@
from setuptools import setup
setup(
package_data={
'': [
'data/*.abi.json',
'data/*.bin',
],
},
include_package_data=True,
)

View File

@ -0,0 +1,3 @@
pytest==6.0.1
eth-tester==0.5.0b3
py-evm==0.3.0a20

4
python/tests/conftest.py Normal file
View File

@ -0,0 +1,4 @@
# external imports
from chainlib.eth.pytest import *
from contract_registry.pytest import *

View File

@ -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

10
solidity/Makefile Normal file
View File

@ -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/

58
solidity/Registry.sol Normal file
View File

@ -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];
}
}