Compare commits

...

7 Commits

Author SHA1 Message Date
lash 638e45adcd
Add unittest module to packaging 2023-05-07 00:02:19 +01:00
lash 27a0ac92b0
Add unittest module, factor out common test code 2023-05-06 23:01:45 +01:00
lash e6a8140d2d
Add error messages to revert 2023-03-26 08:12:50 +01:00
lash 6061c8244e
Split mutable and immutable accounts index interface defs 2023-03-25 13:15:50 +00:00
lash 7a1c3ede9e
Update interface id, simplify events more 2023-03-25 12:52:59 +00:00
lash 14f1861f78
Simplify events 2023-03-24 16:22:49 +00:00
lash 7a508fa113
Improve tests, check negative have 2023-03-24 16:13:35 +00:00
9 changed files with 119 additions and 75 deletions

View File

@ -1,3 +1,16 @@
* 0.5.3
- Add unittest module to packaging
* 0.5.2
- Move test fixture to unittest module
* 0.5.1
- Simplify contract events
- Split interfaces for mutable and immutable registries
* 0.4.x, 0.5.0
- Update packaging to beta
- Change licence to AGPL
* 0.3.6
- Correctly implement writer interface in contract
- Add contract json metadata in python package data
* 0.3.5
- Remove contract compile warnings
* 0.3.4

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_executor","type":"address"},{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"AddressActive","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_executor","type":"address"},{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"AddressAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_executor","type":"address"},{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"AddressInactive","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_executor","type":"address"},{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"AddressRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_executor","type":"address"},{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"WriterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_executor","type":"address"},{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"WriterDeleted","type":"event"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"activate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"add","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"addWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"deactivate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"deleteWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_i","type":"uint256"}],"name":"entry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"entryCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"entryIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"have","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"isActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"remove","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_sum","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"time","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]
[{"inputs":[],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_account","type":"address"},{"indexed":false,"internalType":"bool","name":"_active","type":"bool"}],"name":"AddressActive","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"AddressAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"AddressRemoved","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnershipTransferred","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"WriterAdded","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"_account","type":"address"}],"name":"WriterDeleted","type":"event"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"activate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"add","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"addWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"deactivate","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_writer","type":"address"}],"name":"deleteWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_i","type":"uint256"}],"name":"entry","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"entryCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"have","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"isActive","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"isWriter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"remove","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_sum","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"_account","type":"address"}],"name":"time","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_newOwner","type":"address"}],"name":"transferOwnership","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
from .base import TestAccountsIndex

View File

@ -0,0 +1,38 @@
# standard imports
import logging
# external imports
from chainlib.eth.unittest.ethtester import EthTesterCase
from chainlib.connection import RPCConnection
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.address import to_checksum_address
from chainlib.eth.tx import receipt
# local imports
from eth_accounts_index.registry import AccountRegistry
logg = logging.getLogger(__name__)
class TestAccountsIndex(EthTesterCase):
def setUp(self):
super(TestAccountsIndex, self).setUp()
self.conn = RPCConnection.connect(self.chain_spec, 'default')
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
c = AccountRegistry(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.constructor(self.accounts[0])
r = self.conn.do(o)
logg.debug(f'published with hash {r}')
o = receipt(r)
r = self.conn.do(o)
self.address = to_checksum_address(r['contract_address'])
(tx_hash, o) = c.add_writer(self.address, self.accounts[0], self.accounts[0])
r = self.conn.do(o)
o = receipt(r)
r = self.conn.do(o)
self.assertEqual(r['status'], 1)

View File

@ -1,6 +1,6 @@
[metadata]
name = eth-accounts-index
version = 0.5.0
version = 0.5.3
description = Accounts index evm contract tooling with permissioned writes
author = Louis Holbrook
author_email = dev@holbrook.no
@ -27,6 +27,7 @@ python_requires = >= 3.8
packages =
eth_accounts_index
eth_accounts_index.runnable
eth_accounts_index.unittest
[options.extras_require]
testing =

View File

@ -26,6 +26,7 @@ from chainlib.eth.block import (
# local imports
from eth_accounts_index.registry import AccountRegistry
from eth_accounts_index import AccountsIndex
from eth_accounts_index.unittest import TestAccountsIndex
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
@ -33,39 +34,10 @@ logg = logging.getLogger()
testdir = os.path.dirname(__file__)
class TestNonceOracle:
def __init__(self, address, default_value=0):
self.nonce = default_value
def next_nonce(self):
nonce = self.nonce
self.nonce += 1
return nonce
class Test(EthTesterCase):
class Test(TestAccountsIndex):
def setUp(self):
super(Test, self).setUp()
nonce_oracle = TestNonceOracle(self.accounts[0])
c = AccountRegistry(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.constructor(self.accounts[0])
self.conn = RPCConnection.connect(self.chain_spec, 'default')
r = self.conn.do(o)
logg.debug(f'deployed with hash {r}')
o = receipt(r)
r = self.conn.do(o)
self.address = to_checksum_address(r['contract_address'])
(tx_hash, o) = c.add_writer(self.address, self.accounts[0], self.accounts[0])
r = self.conn.do(o)
o = receipt(r)
r = self.conn.do(o)
self.assertEqual(r['status'], 1)
def test_1_count(self):
@ -80,19 +52,25 @@ class Test(EthTesterCase):
def test_2_add(self):
b = os.urandom(20)
a = to_checksum_address(b.hex())
b = os.urandom(20)
b = to_checksum_address(b.hex())
nonce_oracle = RPCNonceOracle(self.accounts[0], self.conn)
c = AccountsIndex(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.add(self.address, self.accounts[0], a)
r = self.conn.do(o)
self.assertEqual(tx_hash, r)
self.conn.do(o)
o = receipt(tx_hash)
rcpt = self.conn.do(o)
r = self.conn.do(o)
self.assertEqual(r['status'], 1)
self.helper.mine_block()
o = c.have(self.address, a, sender_address=self.accounts[0])
r = self.conn.do(o)
self.assertEqual(int(r, 16), 1)
o = c.have(self.address, b, sender_address=self.accounts[0])
r = self.conn.do(o)
self.assertEqual(int(r, 16), 0)
def test_3_add_rlpsigned(self):

View File

@ -1,45 +1,55 @@
pragma solidity >0.6.12;
pragma solidity >=0.8.0;
// SPDX-License-Identifier: GPL-3.0-or-later
// File-Version: 2
contract CustodialAccountIndex {
// SPDX-License-Identifier: AGPL-3.0-or-later
// File-Version: 3
contract AccountsIndex {
uint256 constant blockedField = 1 << 128;
address[] entryList;
mapping(address => uint256) public entryIndex;
mapping(address => bool) public isWriter;
address public owner;
address newOwner;
mapping(address => uint256) entryIndex;
event AddressAdded(address indexed _executor, address _account); // AccountsIndex
event AddressActive(address indexed _executor, address _account);
event AddressInactive(address indexed _executor, address _account);
event AddressRemoved(address indexed _executor, address _account);
// Implements Writer
mapping(address => bool) public isWriter;
// Implements ERC173
address public owner;
// Implements AccountsIndex
event AddressAdded(address _account); // AccountsIndex
// Implements AccountsIndexMutable
event AddressActive(address indexed _account, bool _active);
// Implements AccountsIndexMutable
event AddressRemoved(address _account);
// Implements ERC173
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); // EIP173
event WriterAdded(address indexed _executor, address _account); // AccountsIndex
event WriterDeleted(address indexed _executor, address _account);
// Implements Writer
event WriterAdded(address _account); // AccountsIndex
// Implements Writer
event WriterDeleted(address _account);
constructor() {
owner = msg.sender;
entryList.push(address(0));
}
// Implements AccountsIndex
function entryCount() external view returns (uint256) {
return entryList.length - 1;
}
// Implements Writer
function addWriter(address _writer) public returns (bool) {
require(owner == msg.sender);
require(owner == msg.sender, 'ERR_AXX');
isWriter[_writer] = true;
emit WriterAdded(msg.sender, _writer);
emit WriterAdded(_writer);
return true;
}
// Implements Writer
function deleteWriter(address _writer) public returns (bool) {
require(owner == msg.sender);
require(owner == msg.sender, 'ERR_AXX');
delete isWriter[_writer];
emit WriterDeleted(msg.sender, _writer);
emit WriterDeleted(_writer);
return true;
}
@ -56,17 +66,17 @@ contract CustodialAccountIndex {
_entry = uint64(i);
_entry |= block.timestamp << 64;
entryIndex[_account] = _entry;
emit AddressAdded(msg.sender, _account);
emit AddressAdded(_account);
return true;
}
// Implements AccountsIndex
// Implements AccountsIndexMutable
function remove(address _account) external returns (bool) {
uint256 i;
uint256 l;
require(isWriter[msg.sender]);
require(this.have(_account));
require(isWriter[msg.sender], 'ERR_AXX');
require(this.have(_account), 'ERR_ACL');
l = entryList.length - 1;
@ -76,28 +86,28 @@ contract CustodialAccountIndex {
}
entryList.pop();
entryIndex[_account] = 0;
emit AddressRemoved(msg.sender, _account);
emit AddressRemoved(_account);
return true;
}
// Implements AccountsIndex
// Activate previously deactivated account. Will not affect the entry count.
// Implements AccountsIndexMutable
function activate(address _account) external returns (bool) {
require(isWriter[msg.sender]);
require(entryIndex[_account] > 0 && entryIndex[_account] & blockedField == blockedField);
require(isWriter[msg.sender], 'ERR_AXX');
require(entryIndex[_account] > 0, 'ERR_NOT_FOUND');
require(entryIndex[_account] & blockedField == blockedField, 'ERR_NOT_BLOCKED');
entryIndex[_account] >>= 129;
emit AddressActive(msg.sender, _account);
emit AddressActive(_account, true);
return true;
}
// Implements AccountsIndex
// Deactivate account, without removing the entry. The entry will still be part of the entry count.
// Implements AccountsIndexMutable
function deactivate(address _account) external returns (bool) {
require(isWriter[msg.sender]);
require(entryIndex[_account] > 0 && entryIndex[_account] & blockedField == 0);
require(entryIndex[_account] > 0, 'ERR_NOT_FOUND');
require(entryIndex[_account] & blockedField == 0, 'ERR_NOT_ACTIVE');
entryIndex[_account] <<= 129;
entryIndex[_account] |= blockedField;
emit AddressInactive(msg.sender, _account);
emit AddressActive(_account, false);
return true;
}
@ -118,7 +128,7 @@ contract CustodialAccountIndex {
return entryIndex[_account] > 0;
}
// Implements AccountsIndex
// Implements AccountsIndexMutable
function isActive(address _account) external view returns (bool) {
return this.have(_account) && entryIndex[_account] & blockedField != blockedField;
}
@ -126,7 +136,7 @@ contract CustodialAccountIndex {
// Implements EIP173
function transferOwnership(address _newOwner) public returns (bool) {
address oldOwner;
require(msg.sender == owner);
require(msg.sender == owner, 'ERR_AXX');
oldOwner = owner;
owner = _newOwner;
@ -137,7 +147,10 @@ contract CustodialAccountIndex {
// Implements EIP165
function supportsInterface(bytes4 _sum) public pure returns (bool) {
if (_sum == 0xcbdb05c7) { // AccountsIndex
if (_sum == 0xb7bca625) { // AccountsIndex
return true;
}
if (_sum == 0x9479f0ae) { // AccountsIndexMutable
return true;
}
if (_sum == 0x01ffc9a7) { // EIP165
@ -146,7 +159,7 @@ contract CustodialAccountIndex {
if (_sum == 0x9493f8b2) { // EIP173
return true;
}
if (_sum == 0x80c84bd6) { // Writer
if (_sum == 0xabe1f1f5) { // Writer
return true;
}
return false;