From 220b8afbebeea6852202e1433dfd6959109d405a Mon Sep 17 00:00:00 2001 From: lash Date: Tue, 6 Jun 2023 14:46:55 +0100 Subject: [PATCH] Add capped interface, python interface and test for capped, expire --- Makefile | 5 +- python/MANIFEST.in | 2 +- python/Makefile | 17 ++++++ python/eth_capped/__init__.py | 1 + python/eth_capped/capped.py | 22 ++++++++ python/eth_capped/unittest/__init__.py | 1 + python/eth_capped/unittest/base.py | 67 ++++++++++++++++++++++++ python/eth_capped/unittest/interface.py | 38 ++++++++++++++ python/eth_expire/__init__.py | 1 + python/eth_expire/expire.py | 37 +++++++++++++ python/eth_expire/unittest/__init__.py | 1 + python/eth_expire/unittest/base.py | 69 +++++++++++++++++++++++++ python/eth_expire/unittest/interface.py | 37 +++++++++++++ python/eth_writer/__init__.py | 1 + python/eth_writer/interface.py | 44 ++++++++++++++++ python/eth_writer/unittest/__init__.py | 1 + python/eth_writer/unittest/base.py | 45 ++++++++++++++++ python/requirements.txt | 2 +- python/run_tests.sh | 15 ++++++ python/setup.cfg | 4 +- python/solidity/CappedTest.bin | 1 + python/solidity/CappedTest.sol | 29 +++++++++++ python/solidity/ExpireTest.bin | 1 + python/solidity/ExpireTest.sol | 47 +++++++++++++++++ python/test_requirements.txt | 2 + python/tests/test_capped.py | 30 +++++++++++ python/tests/test_expire.py | 30 +++++++++++ solidity/Capped.sol | 12 +++++ solidity/Writer.sol | 2 +- 29 files changed, 559 insertions(+), 5 deletions(-) create mode 100644 python/Makefile create mode 100644 python/eth_capped/__init__.py create mode 100644 python/eth_capped/capped.py create mode 100644 python/eth_capped/unittest/__init__.py create mode 100644 python/eth_capped/unittest/base.py create mode 100644 python/eth_capped/unittest/interface.py create mode 100644 python/eth_expire/__init__.py create mode 100644 python/eth_expire/expire.py create mode 100644 python/eth_expire/unittest/__init__.py create mode 100644 python/eth_expire/unittest/base.py create mode 100644 python/eth_expire/unittest/interface.py create mode 100644 python/eth_writer/__init__.py create mode 100644 python/eth_writer/interface.py create mode 100644 python/eth_writer/unittest/__init__.py create mode 100644 python/eth_writer/unittest/base.py create mode 100755 python/run_tests.sh create mode 100644 python/solidity/CappedTest.bin create mode 100644 python/solidity/CappedTest.sol create mode 100644 python/solidity/ExpireTest.bin create mode 100644 python/solidity/ExpireTest.sol create mode 100644 python/tests/test_capped.py create mode 100644 python/tests/test_expire.py create mode 100644 solidity/Capped.sol diff --git a/Makefile b/Makefile index 36d252b..3bed9cf 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ INPUTS = $(wildcard solidity/*.sol) OUTPUTS_JSON = $(patsubst %.sol, %.json, $(INPUTS)) OUTPUTS_INTERFACE = $(patsubst %.sol, %.interface, $(INPUTS)) OUTPUTS = $(OUTPUTS_JSON) $(OUTPUTS_INTERFACE) -PREFIX = /usr/local/share/cic/solidity/abi +PREFIX = $(DESTDIR)/usr/local/share/cic/solidity/abi #%.abi.json: $(wildcard *.sol) # install -vDm0644 $@ $(PREFIX)/$@ @@ -44,4 +44,7 @@ readme: make -C doc/texinfo readme pandoc -f docbook -t gfm doc/texinfo/build/docbook.xml > README.md +python: + make -C python + .PHONY: clean install diff --git a/python/MANIFEST.in b/python/MANIFEST.in index e705e7e..0882628 100644 --- a/python/MANIFEST.in +++ b/python/MANIFEST.in @@ -1 +1 @@ -include *requirements.txt +include *requirements.txt solidity/* LICENSE README* diff --git a/python/Makefile b/python/Makefile new file mode 100644 index 0000000..b6699f6 --- /dev/null +++ b/python/Makefile @@ -0,0 +1,17 @@ +INPUTS = $(wildcard solidity/*.sol) +OUTPUTS = $(patsubst %.sol, %.bin, $(INPUTS)) + +all: outs package + +.SUFFIXES: .sol .bin + +solidity: + +.sol.bin: + solc $(basename $@).sol --evm-version=byzantium --bin | awk 'NR>3' > $@ + truncate -s -1 $@ + +outs: $(OUTPUTS) + +package: + python setup.py sdist diff --git a/python/eth_capped/__init__.py b/python/eth_capped/__init__.py new file mode 100644 index 0000000..4dab2f6 --- /dev/null +++ b/python/eth_capped/__init__.py @@ -0,0 +1 @@ +from .capped import EthCapped diff --git a/python/eth_capped/capped.py b/python/eth_capped/capped.py new file mode 100644 index 0000000..10c6215 --- /dev/null +++ b/python/eth_capped/capped.py @@ -0,0 +1,22 @@ +# external imports +from chainlib.eth.tx import TxFormat +from chainlib.eth.tx import TxFactory +from chainlib.jsonrpc import JSONRPCRequest +from chainlib.eth.constant import ZERO_ADDRESS +from chainlib.eth.contract import ABIContractEncoder +from chainlib.eth.contract import ABIContractType +from hexathon import add_0x + +class EthCapped(TxFactory): + + def max_supply(self, contract_address, sender_address=ZERO_ADDRESS, id_generator=None): + j = JSONRPCRequest(id_generator) + o = j.template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('maxSupply') + data = add_0x(enc.get()) + 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/eth_capped/unittest/__init__.py b/python/eth_capped/unittest/__init__.py new file mode 100644 index 0000000..91e723a --- /dev/null +++ b/python/eth_capped/unittest/__init__.py @@ -0,0 +1 @@ +from .interface import TestEthCappedInterface diff --git a/python/eth_capped/unittest/base.py b/python/eth_capped/unittest/base.py new file mode 100644 index 0000000..23c60de --- /dev/null +++ b/python/eth_capped/unittest/base.py @@ -0,0 +1,67 @@ +# standard imports +import os +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.tx import receipt +from chainlib.eth.tx import TxFactory +from chainlib.eth.tx import TxFormat +from chainlib.eth.contract import ABIContractEncoder +from chainlib.eth.contract import ABIContractType + +# local imports +from eth_capped import EthCapped + +script_dir = os.path.dirname(os.path.realpath(__file__)) +contract_dir = os.path.join(script_dir, '..', '..', 'solidity') + +logg = logging.getLogger(__name__) + + +class TestEthCapped(EthTesterCase): + + def setUp(self): + super(TestEthCapped, self).setUp() + + self.set_method = None + + self.conn = RPCConnection.connect(self.chain_spec, 'default') + nonce_oracle = RPCNonceOracle(self.accounts[0], self.conn) + + f = open(os.path.join(contract_dir, 'CappedTest.bin')) + code = f.read() + f.close() + + txf = TxFactory(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + tx = txf.template(self.accounts[0], None, use_nonce=True) + + self.max_supply_value = 1024 + enc = ABIContractEncoder() + enc.uint256(self.max_supply_value) + args = enc.get() + + tx = txf.set_code(tx, code + args) + (tx_hash_hex, o) = txf.build(tx) + self.conn.do(o) + o = receipt(tx_hash_hex) + r = self.conn.do(o) + self.assertEqual(r['status'], 1) + self.address = r['contract_address'] + logg.debug('published capped test contract with hash {}'.format(r)) + + + def set_max_supply(self, contract_address, sender_address, v, tx_format=TxFormat.JSONRPC): + nonce_oracle = RPCNonceOracle(self.accounts[0], self.conn) + txf = TxFactory(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + enc = ABIContractEncoder() + enc.method('setMaxSupply') + enc.typ(ABIContractType.UINT256) + enc.uint256(v) + data = enc.get() + tx = txf.template(sender_address, contract_address, use_nonce=True) + tx = txf.set_code(tx, data) + tx = txf.finalize(tx, tx_format) + return tx diff --git a/python/eth_capped/unittest/interface.py b/python/eth_capped/unittest/interface.py new file mode 100644 index 0000000..2469cb1 --- /dev/null +++ b/python/eth_capped/unittest/interface.py @@ -0,0 +1,38 @@ +# external imports +from chainlib.eth.tx import receipt + +# local imports +from eth_capped import EthCapped + + +class TestEthCappedInterface: + + def __init__(self): + self.set_method = None + self.max_supply_value = 0 + + + def test_supply(self): + if self.max_supply_value == 0: + return + c = EthCapped(self.chain_spec) + o = c.max_supply(self.address, sender_address=self.accounts[0]) + r = self.rpc.do(o) + self.assertEqual(self.max_supply_value, int(r, 16)) + + + def test_supply_change(self): + if self.set_method == None: + return + + self.max_supply_value = 2048 + (tx_hash_hex, o) = self.set_method(self.address, self.accounts[0], self.max_supply_value) + self.rpc.do(o) + o = receipt(tx_hash_hex) + r = self.conn.do(o) + self.assertEqual(r['status'], 1) + + c = EthCapped(self.chain_spec) + o = c.max_supply(self.address, sender_address=self.accounts[0]) + r = self.rpc.do(o) + self.assertEqual(self.max_supply_value, int(r, 16)) diff --git a/python/eth_expire/__init__.py b/python/eth_expire/__init__.py new file mode 100644 index 0000000..9e15ef4 --- /dev/null +++ b/python/eth_expire/__init__.py @@ -0,0 +1 @@ +from .expire import EthExpire diff --git a/python/eth_expire/expire.py b/python/eth_expire/expire.py new file mode 100644 index 0000000..4fa590b --- /dev/null +++ b/python/eth_expire/expire.py @@ -0,0 +1,37 @@ +# external imports +from chainlib.eth.tx import TxFormat +from chainlib.eth.tx import TxFactory +from chainlib.jsonrpc import JSONRPCRequest +from chainlib.eth.constant import ZERO_ADDRESS +from chainlib.eth.contract import ABIContractEncoder +from chainlib.eth.contract import ABIContractType +from chainlib.eth.contract import abi_decode_single +from hexathon import add_0x + +class EthExpire(TxFactory): + + def apply_expiry(self, method, contract_address, sender_address, tx_format=TxFormat.JSONRPC): + enc = ABIContractEncoder() + enc.method('applyExpiry') + data = enc.get() + tx = self.template(sender_address, contract_address, use_nonce=True) + tx = self.set_code(tx, data) + tx = self.finalize(tx, tx_format) + return tx + + + def expires(self, contract_address, sender_address=ZERO_ADDRESS, id_generator=None): + j = JSONRPCRequest(id_generator) + o = j.template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('expires') + data = add_0x(enc.get()) + tx = self.template(sender_address, contract_address) + tx = self.set_code(tx, data) + o['params'].append(self.normalize(tx)) + return o + + + def parse_expires(self, v): + return abi_decode_single(ABIContractType.UINT256, v) diff --git a/python/eth_expire/unittest/__init__.py b/python/eth_expire/unittest/__init__.py new file mode 100644 index 0000000..619570f --- /dev/null +++ b/python/eth_expire/unittest/__init__.py @@ -0,0 +1 @@ +from .interface import TestEthExpireInterface diff --git a/python/eth_expire/unittest/base.py b/python/eth_expire/unittest/base.py new file mode 100644 index 0000000..de58edf --- /dev/null +++ b/python/eth_expire/unittest/base.py @@ -0,0 +1,69 @@ +# standard imports +import os +import logging +import datetime + +# external imports +from chainlib.eth.unittest.ethtester import EthTesterCase +from chainlib.connection import RPCConnection +from chainlib.eth.nonce import RPCNonceOracle +from chainlib.eth.tx import receipt +from chainlib.eth.tx import TxFactory +from chainlib.eth.tx import TxFormat +from chainlib.eth.contract import ABIContractEncoder +from chainlib.eth.contract import ABIContractType + +# local imports +from eth_capped import EthCapped + +script_dir = os.path.dirname(os.path.realpath(__file__)) +contract_dir = os.path.join(script_dir, '..', '..', 'solidity') + +logg = logging.getLogger(__name__) + + +class TestEthExpire(EthTesterCase): + + def setUp(self): + super(TestEthExpire, self).setUp() + + self.set_method = None + + self.conn = RPCConnection.connect(self.chain_spec, 'default') + nonce_oracle = RPCNonceOracle(self.accounts[0], self.conn) + + f = open(os.path.join(contract_dir, 'ExpireTest.bin')) + code = f.read() + f.close() + + txf = TxFactory(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + tx = txf.template(self.accounts[0], None, use_nonce=True) + + date_expire = datetime.datetime.utcnow() + datetime.timedelta(seconds=10000) + self.expire = int(date_expire.timestamp()) + enc = ABIContractEncoder() + enc.uint256(self.expire) + args = enc.get() + + tx = txf.set_code(tx, code + args) + (tx_hash_hex, o) = txf.build(tx) + self.conn.do(o) + o = receipt(tx_hash_hex) + r = self.conn.do(o) + self.assertEqual(r['status'], 1) + self.address = r['contract_address'] + logg.debug('published expire test contract with hash {}'.format(r)) + + + def set_expiry(self, contract_address, sender_address, v, tx_format=TxFormat.JSONRPC): + nonce_oracle = RPCNonceOracle(self.accounts[0], self.conn) + txf = TxFactory(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + enc = ABIContractEncoder() + enc.method('setExpiry') + enc.typ(ABIContractType.UINT256) + enc.uint256(v) + data = enc.get() + tx = txf.template(sender_address, contract_address, use_nonce=True) + tx = txf.set_code(tx, data) + tx = txf.finalize(tx, tx_format) + return tx diff --git a/python/eth_expire/unittest/interface.py b/python/eth_expire/unittest/interface.py new file mode 100644 index 0000000..a809cc5 --- /dev/null +++ b/python/eth_expire/unittest/interface.py @@ -0,0 +1,37 @@ +# external imports +from chainlib.eth.tx import receipt + +# local imports +from eth_expire import EthExpire + + +class TestEthExpireInterface: + + def __init__(self): + self.set_method = None + + + def test_expire(self): + if self.expire == 0: + return + c = EthExpire(self.chain_spec) + o = c.expires(self.address, sender_address=self.accounts[0]) + r = self.rpc.do(o) + self.assertEqual(self.expire, int(r, 16)) + + + def test_expire_change(self): + if self.set_method == None: + return + + self.expire += 43200 + (tx_hash_hex, o) = self.set_method(self.address, self.accounts[0], self.expire) + self.rpc.do(o) + o = receipt(tx_hash_hex) + r = self.conn.do(o) + self.assertEqual(r['status'], 1) + + c = EthExpire(self.chain_spec) + o = c.expires(self.address, sender_address=self.accounts[0]) + r = self.rpc.do(o) + self.assertEqual(self.expire, int(r, 16)) diff --git a/python/eth_writer/__init__.py b/python/eth_writer/__init__.py new file mode 100644 index 0000000..e07b40a --- /dev/null +++ b/python/eth_writer/__init__.py @@ -0,0 +1 @@ +from .interface import * diff --git a/python/eth_writer/interface.py b/python/eth_writer/interface.py new file mode 100644 index 0000000..b2345f9 --- /dev/null +++ b/python/eth_writer/interface.py @@ -0,0 +1,44 @@ +# external imports +from chainlib.eth.tx import TxFormat +from chainlib.eth.tx import TxFactory +from chainlib.jsonrpc import JSONRPCRequest +from chainlib.eth.constant import ZERO_ADDRESS +from chainlib.eth.contract import ABIContractEncoder +from chainlib.eth.contract import ABIContractType +from hexathon import add_0x + +class EthWriter(TxFactory): + + def __single_address_method(self, method, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC): + enc = ABIContractEncoder() + enc.method(method) + enc.typ(ABIContractType.ADDRESS) + enc.address(address) + data = enc.get() + tx = self.template(sender_address, contract_address, use_nonce=True) + tx = self.set_code(tx, data) + tx = self.finalize(tx, tx_format) + return tx + + + def add_writer(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC): + return self.__single_address_method('addWriter', contract_address, sender_address, address, tx_format) + + + def delete_writer(self, contract_address, sender_address, address, tx_format=TxFormat.JSONRPC): + return self.__single_address_method('deleteWriter', contract_address, sender_address, address, tx_format) + + + def is_writer(self, contract_address, address, sender_address=ZERO_ADDRESS, id_generator=None): + j = JSONRPCRequest(id_generator) + o = j.template() + o['method'] = 'eth_call' + enc = ABIContractEncoder() + enc.method('isWriter') + enc.typ(ABIContractType.ADDRESS) + enc.address(address) + data = add_0x(enc.get()) + 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/eth_writer/unittest/__init__.py b/python/eth_writer/unittest/__init__.py new file mode 100644 index 0000000..23027db --- /dev/null +++ b/python/eth_writer/unittest/__init__.py @@ -0,0 +1 @@ +from .base import TestEthWriterInterface diff --git a/python/eth_writer/unittest/base.py b/python/eth_writer/unittest/base.py new file mode 100644 index 0000000..1015211 --- /dev/null +++ b/python/eth_writer/unittest/base.py @@ -0,0 +1,45 @@ +# external imports +from chainlib.eth.unittest.ethtester import EthTesterCase +from chainlib.connection import RPCConnection +from chainlib.eth.nonce import RPCNonceOracle +from chainlib.eth.tx import receipt + +# local imports +from eth_writer import EthWriter + + +class TestEthWriterInterface: + + def test_add_delete(self): + writer_contract_address = self.contracts['writer'] + publisher_address = self.roles.get('publisher') + writer_account = self.roles['writer'] + + nonce_oracle = RPCNonceOracle(self.publisher, conn=self.conn) + c = EthWriter(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle) + + o = c.is_writer(writer_contract_address, publisher_address, sender_address=publisher_address) + r = self.rpc.do(o) + self.assertEqual(int(r, 16), 1) + + self.alice = self.accounts[1] + (tx_hash, o) = c.add_writer(writer_contract_address, publisher_address, writer_account) + self.rpc.do(o) + o = receipt(tx_hash) + r = self.rpc.do(o) + self.assertEqual(r['status'], 1) + + o = c.is_writer(writer_contract_address, writer_account, sender_address=publisher_address) + r = self.rpc.do(o) + self.assertEqual(int(r, 16), 1) + + self.alice = self.accounts[1] + (tx_hash, o) = c.delete_writer(writer_contract_address, publisher_address, writer_account) + self.rpc.do(o) + o = receipt(tx_hash) + r = self.rpc.do(o) + self.assertEqual(r['status'], 1) + + o = c.is_writer(writer_contract_address, self.alice, sender_address=publisher_address) + r = self.rpc.do(o) + self.assertEqual(int(r, 16), 0) diff --git a/python/requirements.txt b/python/requirements.txt index b5b2195..f3daf0f 100644 --- a/python/requirements.txt +++ b/python/requirements.txt @@ -1 +1 @@ -chainlib-eth~=0.4.7 +chainlib-eth~=0.4.24 diff --git a/python/run_tests.sh b/python/run_tests.sh new file mode 100755 index 0000000..926a9eb --- /dev/null +++ b/python/run_tests.sh @@ -0,0 +1,15 @@ +#!/bin/bash + +export PYTHONPATH=${PYTHONPATH}:. +>&2 echo "using pythonpath $PYTHONPATH" + +set -e +set -x +for f in `ls tests/*.py`; do + python $f + if [ $? -gt 0 ]; then + exit 1 + fi +done +set +x +set +e diff --git a/python/setup.cfg b/python/setup.cfg index a6b900d..b55f1ff 100644 --- a/python/setup.cfg +++ b/python/setup.cfg @@ -26,6 +26,8 @@ licence_files = [options] include_package_data = True -python_requires = >= 3.6 +python_requires = >= 3.7 packages = cic_contracts + eth_capped + eth_expire diff --git a/python/solidity/CappedTest.bin b/python/solidity/CappedTest.bin new file mode 100644 index 0000000..28db2c3 --- /dev/null +++ b/python/solidity/CappedTest.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516104003803806104008339818101604052810190610032919061007a565b80600081905550506100a7565b600080fd5b6000819050919050565b61005781610044565b811461006257600080fd5b50565b6000815190506100748161004e565b92915050565b6000602082840312156100905761008f61003f565b5b600061009e84828501610065565b91505092915050565b61034a806100b66000396000f3fe608060405234801561001057600080fd5b506004361061005e576000357c01000000000000000000000000000000000000000000000000000000009004806301ffc9a7146100635780636f8b44b014610093578063d5abeb01146100af575b600080fd5b61007d60048036038101906100789190610224565b6100cd565b60405161008a919061026c565b60405180910390f35b6100ad60048036038101906100a891906102bd565b61017d565b005b6100b76101c1565b6040516100c491906102f9565b60405180910390f35b60006301ffc9a77c010000000000000000000000000000000000000000000000000000000002827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916036101215760019050610178565b63869f75947c010000000000000000000000000000000000000000000000000000000002827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916036101735760019050610178565b600090505b919050565b6000547f9722adea12ab7ef86fc45b88f0e0b567639e8dddaae60261e08c03d747fbbfe6826040516101af91906102f9565b60405180910390a28060008190555050565b60005481565b600080fd5b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b610201816101cc565b811461020c57600080fd5b50565b60008135905061021e816101f8565b92915050565b60006020828403121561023a576102396101c7565b5b60006102488482850161020f565b91505092915050565b60008115159050919050565b61026681610251565b82525050565b6000602082019050610281600083018461025d565b92915050565b6000819050919050565b61029a81610287565b81146102a557600080fd5b50565b6000813590506102b781610291565b92915050565b6000602082840312156102d3576102d26101c7565b5b60006102e1848285016102a8565b91505092915050565b6102f381610287565b82525050565b600060208201905061030e60008301846102ea565b9291505056fea2646970667358221220f6bc26e7c6827142ccee8c0cadc8cb76d33712a68eacd67b69e4f8108aa2b59064736f6c63430008130033 \ No newline at end of file diff --git a/python/solidity/CappedTest.sol b/python/solidity/CappedTest.sol new file mode 100644 index 0000000..e4f7427 --- /dev/null +++ b/python/solidity/CappedTest.sol @@ -0,0 +1,29 @@ +pragma solidity >=0.6.3; + +// Author: Louis Holbrook 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 +// SPDX-License-Identifier: AGPL-3.0-or-later + +contract CappedTest { + event Cap(uint256 indexed _oldCap, uint256 _newCap); + + uint256 public maxSupply; + + constructor(uint256 _supply) { + maxSupply = _supply; + } + + function setMaxSupply(uint256 _supply) public { + emit Cap(maxSupply, _supply); + maxSupply = _supply; + } + + function supportsInterface(bytes4 _sum) public pure returns (bool) { + if (_sum == 0x01ffc9a7) { // EIP165 + return true; + } + if (_sum == 0x869f7594) { // Capped + return true; + } + return false; + } +} diff --git a/python/solidity/ExpireTest.bin b/python/solidity/ExpireTest.bin new file mode 100644 index 0000000..716efa8 --- /dev/null +++ b/python/solidity/ExpireTest.bin @@ -0,0 +1 @@ +608060405234801561001057600080fd5b506040516105143803806105148339818101604052810190610032919061007a565b80600081905550506100a7565b600080fd5b6000819050919050565b61005781610044565b811461006257600080fd5b50565b6000815190506100748161004e565b92915050565b6000602082840312156100905761008f61003f565b5b600061009e84828501610065565b91505092915050565b61045e806100b66000396000f3fe608060405234801561001057600080fd5b5060043610610069576000357c01000000000000000000000000000000000000000000000000000000009004806301cceb381461006e57806301ffc9a71461008a5780635f408c04146100ba578063b1cb0db3146100d8575b600080fd5b610088600480360381019061008391906102df565b6100f6565b005b6100a4600480360381019061009f9190610364565b610162565b6040516100b191906103ac565b60405180910390f35b6100c2610212565b6040516100cf91906103e3565b60405180910390f35b6100e061029e565b6040516100ed919061040d565b60405180910390f35b600160009054906101000a900460ff161561011057600080fd5b600054811161011e57600080fd5b6000547ff5bd6cb27a0006b5ea8618058a0d84719695cb6d984f4840bc1a54ca12ae4b7c82604051610150919061040d565b60405180910390a28060008190555050565b60006301ffc9a77c010000000000000000000000000000000000000000000000000000000002827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff1916036101b6576001905061020d565b63841a0e947c010000000000000000000000000000000000000000000000000000000002827bffffffffffffffffffffffffffffffffffffffffffffffffffffffff191603610208576001905061020d565b600090505b919050565b6000600160009054906101000a900460ff1615610232576001905061029b565b600054421015610245576000905061029b565b60018060006101000a81548160ff0219169083151502179055507ff80dbaea4785589e52984ca36a31de106adc77759539a5c7d92883bf49692fe94260405161028e919061040d565b60405180910390a1600190505b90565b60005481565b600080fd5b6000819050919050565b6102bc816102a9565b81146102c757600080fd5b50565b6000813590506102d9816102b3565b92915050565b6000602082840312156102f5576102f46102a4565b5b6000610303848285016102ca565b91505092915050565b60007fffffffff0000000000000000000000000000000000000000000000000000000082169050919050565b6103418161030c565b811461034c57600080fd5b50565b60008135905061035e81610338565b92915050565b60006020828403121561037a576103796102a4565b5b60006103888482850161034f565b91505092915050565b60008115159050919050565b6103a681610391565b82525050565b60006020820190506103c1600083018461039d565b92915050565b600060ff82169050919050565b6103dd816103c7565b82525050565b60006020820190506103f860008301846103d4565b92915050565b610407816102a9565b82525050565b600060208201905061042260008301846103fe565b9291505056fea26469706673582212200ea18a383a2b4a17a3ce884a7787899f37d703ede95cd1ba000e15710432f90b64736f6c63430008130033 \ No newline at end of file diff --git a/python/solidity/ExpireTest.sol b/python/solidity/ExpireTest.sol new file mode 100644 index 0000000..2657d57 --- /dev/null +++ b/python/solidity/ExpireTest.sol @@ -0,0 +1,47 @@ +pragma solidity >=0.6.3; + +// Author: Louis Holbrook 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 +// SPDX-License-Identifier: AGPL-3.0-or-later + +contract ExpireTest { + + uint256 public expires; + bool expired; + + event Expired(uint256 _timestamp); + event ExpiryChange(uint256 indexed _oldTimestamp, uint256 _newTimestamp); + + constructor(uint256 _timestamp) { + expires = _timestamp; + } + + function setExpiry(uint256 _timestamp) public { + require(!expired); + require(_timestamp > expires); + emit ExpiryChange(expires, _timestamp); + expires = _timestamp; + } + + function applyExpiry() public returns(uint8) { + if (expired) { + return 1; + } + if (block.timestamp < expires) { + return 0; + } + expired = true; + emit Expired(block.timestamp); + return 1; + } + + function supportsInterface(bytes4 _sum) public pure returns (bool) { + if (_sum == 0x01ffc9a7) { // EIP165 + return true; + } + if (_sum == 0x841a0e94) { // Capped + return true; + } + return false; + } +} + diff --git a/python/test_requirements.txt b/python/test_requirements.txt index 63f9901..30b6c73 100644 --- a/python/test_requirements.txt +++ b/python/test_requirements.txt @@ -1,2 +1,4 @@ eth_tester==0.5.0b3 py-evm==0.3.0a20 +eth-erc20~=0.7.4 +eth-interface~=0.1.2 diff --git a/python/tests/test_capped.py b/python/tests/test_capped.py new file mode 100644 index 0000000..205ae90 --- /dev/null +++ b/python/tests/test_capped.py @@ -0,0 +1,30 @@ +# standard imports +import unittest +import logging +import os +from chainlib.eth.nonce import RPCNonceOracle +from chainlib.eth.tx import receipt +from eth_erc20 import ERC20 +from giftable_erc20_token import GiftableToken +from eth_interface.unittest import TestERC165 + +# local imports +from eth_capped import EthCapped +from eth_capped.unittest import TestEthCappedInterface +from eth_capped.unittest.base import TestEthCapped + + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + + +class TestCappedBase(TestEthCapped, TestEthCappedInterface, TestERC165): + + def setUp(self): + super(TestCappedBase, self).setUp() + self.add_interface_check('869f7594') + self.set_method = self.set_max_supply + + +if __name__ == '__main__': + unittest.main() diff --git a/python/tests/test_expire.py b/python/tests/test_expire.py new file mode 100644 index 0000000..255e0d0 --- /dev/null +++ b/python/tests/test_expire.py @@ -0,0 +1,30 @@ +# standard imports +import unittest +import logging +import os +from chainlib.eth.nonce import RPCNonceOracle +from chainlib.eth.tx import receipt +from eth_erc20 import ERC20 +from giftable_erc20_token import GiftableToken +from eth_interface.unittest import TestERC165 + +# local imports +from eth_expire import EthExpire +from eth_expire.unittest import TestEthExpireInterface +from eth_expire.unittest.base import TestEthExpire + + +logging.basicConfig(level=logging.DEBUG) +logg = logging.getLogger() + + +class TestExpireBase(TestEthExpire, TestEthExpireInterface, TestERC165): + + def setUp(self): + super(TestExpireBase, self).setUp() + self.add_interface_check('841a0e94') + self.set_method = self.set_expiry + + +if __name__ == '__main__': + unittest.main() diff --git a/solidity/Capped.sol b/solidity/Capped.sol new file mode 100644 index 0000000..43c8b95 --- /dev/null +++ b/solidity/Capped.sol @@ -0,0 +1,12 @@ +pragma solidity >=0.6.12; + +// Author: Louis Holbrook 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 +// SPDX-License-Identifier: AGPL-3.0-or-later +// File-version: 1 + +interface ICapped { + // Supply limit is changed. + event Cap(uint256 indexed _oldCap, uint256 _newCap); + // Return the supply limit. + function maxSupply() external returns(uint256); +} diff --git a/solidity/Writer.sol b/solidity/Writer.sol index de0348e..faa3aad 100644 --- a/solidity/Writer.sol +++ b/solidity/Writer.sol @@ -18,5 +18,5 @@ interface IWriter { function deleteWriter(address _writer) external returns (bool); // Check whether the given address is a writer. - function isWriter(address _writer) external returns (bool); + function isWriter(address _writer) external view returns (bool); }