mirror of
git://holbrook.no/eth-contract-registry
synced 2025-04-20 11:01:02 +02: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