mirror of
git://holbrook.no/eth-contract-registry
synced 2024-12-12 16:36:08 +01:00
Initial commit, ported from cic-registry
This commit is contained in:
commit
15cd758fae
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
__pycache__
|
||||
*.pyc
|
1
python/contract_registry/data/Registry.bin
Normal file
1
python/contract_registry/data/Registry.bin
Normal file
File diff suppressed because one or more lines are too long
1
python/contract_registry/data/Registry.json
Normal file
1
python/contract_registry/data/Registry.json
Normal 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"}]
|
32
python/contract_registry/encoding.py
Normal file
32
python/contract_registry/encoding.py
Normal 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)
|
1
python/contract_registry/pytest/__init__.py
Normal file
1
python/contract_registry/pytest/__init__.py
Normal file
@ -0,0 +1 @@
|
||||
from .fixtures_registry import *
|
36
python/contract_registry/pytest/fixtures_registry.py
Normal file
36
python/contract_registry/pytest/fixtures_registry.py
Normal 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']
|
||||
|
140
python/contract_registry/registry.py
Normal file
140
python/contract_registry/registry.py
Normal 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
|
0
python/contract_registry/unittest/base.py
Normal file
0
python/contract_registry/unittest/base.py
Normal file
BIN
python/gmon.out
Normal file
BIN
python/gmon.out
Normal file
Binary file not shown.
3
python/requirements.txt
Normal file
3
python/requirements.txt
Normal 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
40
python/setup.cfg
Normal 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
11
python/setup.py
Normal file
@ -0,0 +1,11 @@
|
||||
from setuptools import setup
|
||||
|
||||
setup(
|
||||
package_data={
|
||||
'': [
|
||||
'data/*.abi.json',
|
||||
'data/*.bin',
|
||||
],
|
||||
},
|
||||
include_package_data=True,
|
||||
)
|
3
python/test_requirements.txt
Normal file
3
python/test_requirements.txt
Normal 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
4
python/tests/conftest.py
Normal file
@ -0,0 +1,4 @@
|
||||
# external imports
|
||||
from chainlib.eth.pytest import *
|
||||
from contract_registry.pytest import *
|
||||
|
82
python/tests/test_basic.py
Normal file
82
python/tests/test_basic.py
Normal 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
10
solidity/Makefile
Normal 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
58
solidity/Registry.sol
Normal 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];
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user