Compare commits

..

22 Commits

Author SHA1 Message Date
lash
5dfd3db2d3
Fix pysha break 2024-04-02 12:43:20 +01:00
lash
54759c3428
Update deps 2023-08-06 16:14:01 +01:00
lash
637080c464
Factor out token publish in giftable token unittest fixture 2023-07-27 07:36:33 +01:00
lash
2fb7143956
Add unittest for static token 2023-06-29 20:55:52 +01:00
lash
d1e5b84cfb
Add missing unittest module to erc20 package 2023-05-30 17:39:01 +01:00
lash
4f2d47778c
BUmp version 2023-03-26 08:08:36 +01:00
lash
46e827390e
Add ERC5679Ext20 fills 2023-03-26 08:07:41 +01:00
lash
f237bd8d82
Update changelog 2023-03-22 12:36:12 +00:00
lash
e449ea647c
Update classifiers for python 2023-03-22 12:34:57 +00:00
lash
13ddb5577a
Upgrade deps 2023-03-22 10:40:37 +00:00
lash
b5d3338f07
Rename total burned method 2023-03-21 20:28:39 +00:00
lash
0937ecb8cd
Add giftable token contract metadata 2023-03-21 20:08:17 +00:00
lash
99e82dd3bf
bump version 2023-02-22 10:49:32 +00:00
lash
04f9b5868b
Add burn to giftable tokebs 2023-02-22 10:48:32 +00:00
lash
d7fb3f232c
Add expire option 2023-02-21 20:28:25 +00:00
lash
ea7c106fa4
Skip unnecessary lookups in balance call 2023-02-19 16:14:48 +00:00
lash
273cf23f21
Correct constructor args method name 2023-02-14 06:39:33 +00:00
lash
15b6b4fa46
Correct optional arg return for bytecode 2023-02-14 06:03:13 +00:00
lash
db30d4aaf4
Add named variables scheme for chainlib gen, args definitions return 2023-02-14 05:24:29 +00:00
lash
7ab3cd14f5
Simplify chainlib find include 2023-02-14 04:25:26 +00:00
lash
fc9fa18e1c
Implement bytecode and contract constructor code for chainlib find tool 2023-02-13 15:06:02 +00:00
lash
d218d93fba
Add sender address option to cli for read calls 2023-02-13 06:38:17 +00:00
39 changed files with 794 additions and 266 deletions

2
.gitignore vendored
View File

@ -6,3 +6,5 @@ gmon.out
*.egg-info *.egg-info
.venv/ .venv/
.idea .idea
solidity/*.json
solidity/*.bin

View File

@ -1,3 +1,33 @@
* 0.7.5
- Factor out token publish method in unittest fixture
* 0.7.4
- Add missing unittest module to python package
* 0.7.3
- Add ERC5679Ext20 fills
* 0.7.2
- Update python classifiers
- Upgrade to beta
* 0.7.1
- Upgrade deps
* 0.7.0
- Implement proper burner interface method
* 0.6.2
- Add Giftable Token contract metadata
* 0.6.1
- Add explicit burn (reduces supply) to giftable token
* 0.6.0
- Add token expiry to giftable token
* 0.5.6
- Remove name and symbol lookup
- Remove decimals lookup for raw output
* 0.5.5
- Implement chainlib-gen for giftable token
* 0.5.4
- Enable setting sender address on contract read calls
* 0.5.3
- Fix giftable token cli commands
* 0.5.2
- Add block height to balance call
* 0.5.1 * 0.5.1
- Change license to AGPL3 and copyright waived to public domain - Change license to AGPL3 and copyright waived to public domain
* 0.5.0 * 0.5.0

View File

@ -1 +1 @@
include **/data/ERC20.json **/data/GiftableToken.json **/data/GiftableToken.bin *requirements.txt CHANGELOG LICENSE WAIVER WAIVER.asc include **/data/ERC20.json **/data/GiftableToken.json **/data/GiftableToken.bin *requirements.txt CHANGELOG LICENSE WAIVER WAIVER.asc **/data/.chainlib

View File

@ -65,7 +65,7 @@ logg = logging.getLogger()
arg_flags = ArgFlag() arg_flags = ArgFlag()
arg = Arg(arg_flags) arg = Arg(arg_flags)
flags = arg_flags.STD_READ | arg_flags.EXEC flags = arg_flags.STD_READ | arg_flags.EXEC | arg_flags.SENDER
argparser = chainlib.eth.cli.ArgumentParser() argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags) argparser = process_args(argparser, arg, flags)
@ -87,33 +87,28 @@ logg.debug('settings loaded:\n{}'.format(settings))
def main(): def main():
token_address = settings.get('EXEC') token_address = settings.get('EXEC')
conn = settings.get('CONN') conn = settings.get('CONN')
sender_address = settings.get('SENDER_ADDRESS')
g = ERC20( g = ERC20(
chain_spec=settings.get('CHAIN_SPEC'), chain_spec=settings.get('CHAIN_SPEC'),
gas_oracle=settings.get('GAS_ORACLE'), gas_oracle=settings.get('GAS_ORACLE'),
) )
# determine decimals # determine decimals
decimals_o = g.decimals(token_address) if not config.get('_RAW'):
decimals_o = g.decimals(token_address, sender_address=sender_address)
r = conn.do(decimals_o) r = conn.do(decimals_o)
decimals = int(strip_0x(r), 16) decimals = int(strip_0x(r), 16)
logg.info('decimals {}'.format(decimals)) logg.info('decimals {}'.format(decimals))
name_o = g.name(token_address)
r = conn.do(name_o)
token_name = g.parse_name(r)
logg.info('name {}'.format(token_name))
symbol_o = g.symbol(token_address)
r = conn.do(symbol_o)
token_symbol = g.parse_symbol(r)
logg.info('symbol {}'.format(token_symbol))
# get balance # get balance
balance_o = g.balance(token_address, settings.get('RECIPIENT')) balance_o = g.balance(token_address, settings.get('RECIPIENT'), sender_address=sender_address)
r = conn.do(balance_o) r = conn.do(balance_o)
hx = strip_0x(r) hx = strip_0x(r)
balance_value = int(hx, 16) balance_value = int(hx, 16)
if config.get('_RAW'):
logg.debug('balance {} = {}'.format(even(hx), balance_value))
else:
logg.debug('balance {} = {} decimals {}'.format(even(hx), balance_value, decimals)) logg.debug('balance {} = {} decimals {}'.format(even(hx), balance_value, decimals))
balance_str = str(balance_value) balance_str = str(balance_value)

View File

@ -74,7 +74,7 @@ def process_config_local(config, arg, args, flags):
arg_flags = ArgFlag() arg_flags = ArgFlag()
arg = Arg(arg_flags) arg = Arg(arg_flags)
flags = arg_flags.STD_READ | arg_flags.EXEC | arg_flags.TAB flags = arg_flags.STD_READ | arg_flags.EXEC | arg_flags.TAB | arg_flags.SENDER
argparser = chainlib.eth.cli.ArgumentParser() argparser = chainlib.eth.cli.ArgumentParser()
argparser = process_args(argparser, arg, flags) argparser = process_args(argparser, arg, flags)
@ -96,6 +96,7 @@ logg.debug('settings loaded:\n{}'.format(settings))
def main(): def main():
token_address = config.get('_CONTRACT') token_address = config.get('_CONTRACT')
conn = settings.get('CONN') conn = settings.get('CONN')
sender_address = settings.get('SENDER_ADDRESS')
g = ERC20( g = ERC20(
chain_spec=settings.get('CHAIN_SPEC'), chain_spec=settings.get('CHAIN_SPEC'),
gas_oracle=settings.get('GAS_ORACLE'), gas_oracle=settings.get('GAS_ORACLE'),
@ -104,7 +105,7 @@ def main():
outkeys = config.get('_OUTARG') outkeys = config.get('_OUTARG')
if not outkeys or 'address' in outkeys: if not outkeys or 'address' in outkeys:
name_o = g.name(token_address) name_o = g.name(token_address, sender_address=sender_address)
r = conn.do(name_o) r = conn.do(name_o)
token_name = g.parse_name(r) token_name = g.parse_name(r)
s = '' s = ''
@ -114,7 +115,7 @@ def main():
print(s) print(s)
if not outkeys or 'symbol' in outkeys: if not outkeys or 'symbol' in outkeys:
symbol_o = g.symbol(token_address) symbol_o = g.symbol(token_address, sender_address=sender_address)
r = conn.do(symbol_o) r = conn.do(symbol_o)
token_symbol = g.parse_symbol(r) token_symbol = g.parse_symbol(r)
s = '' s = ''
@ -124,7 +125,7 @@ def main():
print(s) print(s)
if not outkeys or 'decimals' in outkeys: if not outkeys or 'decimals' in outkeys:
decimals_o = g.decimals(token_address) decimals_o = g.decimals(token_address, sender_address=sender_address)
r = conn.do(decimals_o) r = conn.do(decimals_o)
decimals = int(strip_0x(r), 16) decimals = int(strip_0x(r), 16)
s = '' s = ''
@ -134,7 +135,7 @@ def main():
print(s) print(s)
if not outkeys or 'supply' in outkeys: if not outkeys or 'supply' in outkeys:
supply_o = g.total_supply(token_address) supply_o = g.total_supply(token_address, sender_address=sender_address)
r = conn.do(supply_o) r = conn.do(supply_o)
supply = int(strip_0x(r), 16) supply = int(strip_0x(r), 16)
s = '' s = ''

View File

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

View File

@ -0,0 +1,169 @@
# standard imports
import logging
# external imports
from chainlib.eth.unittest.ethtester import EthTesterCase
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.gas import OverrideGasOracle
from chainlib.eth.tx import (
transaction,
receipt,
)
from hexathon import strip_0x
# local imports
from eth_erc20 import ERC20
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger(__name__)
class TestInterface:
def test_balance(self):
c = ERC20(self.chain_spec)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.conn.do(o)
balance = ERC20.parse_balance(r)
self.assertEqual(self.initial_supply, balance)
def test_supply(self):
c = ERC20(self.chain_spec)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.conn.do(o)
supply = ERC20.parse_total_supply(r)
self.assertEqual(self.initial_supply, supply)
def test_name(self):
c = ERC20(self.chain_spec)
o = c.name(self.address, sender_address=self.accounts[0])
r = self.conn.do(o)
name = ERC20.parse_name(r)
self.assertEqual(self.name, name)
def test_symbol(self):
c = ERC20(self.chain_spec)
o = c.symbol(self.address, sender_address=self.accounts[0])
r = self.conn.do(o)
symbol = ERC20.parse_symbol(r)
self.assertEqual(self.symbol, symbol)
def test_direct_transfer(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
gas_oracle = OverrideGasOracle(limit=100000, conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.transfer(self.address, self.accounts[0], self.accounts[1], 1000)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, self.initial_supply - 1000)
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, 1000)
o = transaction(tx_hash)
r = self.rpc.do(o)
data = c.parse_transfer_request(r['data'])
self.assertEqual(data[0], strip_0x(self.accounts[1]))
self.assertEqual(data[1], 1000)
def test_transfer_from(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
gas_oracle = OverrideGasOracle(limit=100000, conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 1000)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.allowance(self.address, self.accounts[0], self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
allowance = c.parse_allowance(r)
self.assertEqual(allowance, 1000)
o = transaction(tx_hash)
r = self.rpc.do(o)
data = c.parse_approve_request(r['data'])
self.assertEqual(data[0], strip_0x(self.accounts[1]))
self.assertEqual(data[1], 1000)
nonce_oracle = RPCNonceOracle(self.accounts[1], conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.transfer_from(self.address, self.accounts[1], self.accounts[0], self.accounts[2], 1001)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
o = transaction(tx_hash)
r = self.rpc.do(o)
data = c.parse_transfer_from_request(r['data'])
self.assertEqual(data[0], strip_0x(self.accounts[0]))
self.assertEqual(data[1], strip_0x(self.accounts[2]))
self.assertEqual(data[2], 1001)
(tx_hash, o) = c.transfer_from(self.address, self.accounts[1], self.accounts[0], self.accounts[2], 1000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, self.initial_supply - 1000)
o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, 1000)
def test_revoke_approve(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
gas_oracle = OverrideGasOracle(limit=100000, conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 1000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 999)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 0)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.allowance(self.address, self.accounts[0], self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
allowance = c.parse_allowance(r)
self.assertEqual(allowance, 0)
nonce_oracle = RPCNonceOracle(self.accounts[1], conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.transfer_from(self.address, self.accounts[1], self.accounts[0], self.accounts[2], 1)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)

View File

@ -1 +1,4 @@
from .factory import GiftableToken from .factory import GiftableToken
from .factory import bytecode
from .factory import create
from .factory import args

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1 @@
[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"},{"internalType":"uint256","name":"_supply","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"TransferFrom","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_sum","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]

View File

@ -0,0 +1 @@
{"compiler":{"version":"0.8.18+commit.87f61d96"},"language":"Solidity","output":{"abi":[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"},{"internalType":"uint256","name":"_supply","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"TransferFrom","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_sum","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}],"devdoc":{"kind":"dev","methods":{},"version":1},"userdoc":{"kind":"user","methods":{},"version":1}},"settings":{"compilationTarget":{"StaticToken.sol":"StaticToken"},"evmVersion":"byzantium","libraries":{},"metadata":{"bytecodeHash":"ipfs"},"optimizer":{"enabled":false,"runs":200},"remappings":[]},"sources":{"StaticToken.sol":{"keccak256":"0xfb467ccf3465a3f2c8ad23bc4933f7e1ac1964d7ee1aa185e72e4698b015b5b3","license":"AGPL-3.0-or-later","urls":["bzz-raw://6c2230ba6ddadf062f5f3391f2a852da8617fbefd0c0511d9bc372c6c66c6424","dweb:/ipfs/QmWSYF3WxmKyDEHp3NZMWjE6Yu7mY6XVB6tuTeFKpjxL9P"]}},"version":1}

View File

@ -12,28 +12,43 @@ from chainlib.eth.contract import (
ABIContractEncoder, ABIContractEncoder,
ABIContractType, ABIContractType,
) )
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.jsonrpc import JSONRPCRequest
from hexathon import add_0x
# local imports # local imports
from giftable_erc20_token.data import data_dir from giftable_erc20_token.data import data_dir
from eth_erc20 import ERC20
logg = logging.getLogger(__name__) logg = logging.getLogger(__name__)
class GiftableToken(TxFactory): class GiftableToken(ERC20):
__abi = None __abi = None
__bytecode = None __bytecode = None
def constructor(self, sender_address, name, symbol, decimals, tx_format=TxFormat.JSONRPC): def constructor(self, sender_address, name, symbol, decimals, expire=0, tx_format=TxFormat.JSONRPC, version=None):
code = GiftableToken.bytecode() code = self.cargs(name, symbol, decimals, expire=expire)
tx = self.template(sender_address, None, use_nonce=True)
tx = self.set_code(tx, code)
return self.finalize(tx, tx_format)
@staticmethod
def cargs(name, symbol, decimals, expire=0, version=None):
if expire == None:
expire = 0
code = GiftableToken.bytecode(version=version)
enc = ABIContractEncoder() enc = ABIContractEncoder()
enc.string(name) enc.string(name)
enc.string(symbol) enc.string(symbol)
enc.uint256(decimals) enc.uint256(decimals)
code += enc.get() enc.uint256(expire)
tx = self.template(sender_address, None, use_nonce=True) args = enc.get()
tx = self.set_code(tx, code) code += args
return self.finalize(tx, tx_format) logg.debug('constructor code: ' + args)
return code
@staticmethod @staticmethod
@ -51,7 +66,7 @@ class GiftableToken(TxFactory):
@staticmethod @staticmethod
def bytecode(): def bytecode(version=None):
if GiftableToken.__bytecode == None: if GiftableToken.__bytecode == None:
f = open(os.path.join(data_dir, 'GiftableToken.bin')) f = open(os.path.join(data_dir, 'GiftableToken.bin'))
GiftableToken.__bytecode = f.read() GiftableToken.__bytecode = f.read()
@ -97,3 +112,59 @@ class GiftableToken(TxFactory):
return tx return tx
def burn(self, contract_address, sender_address, value, tx_format=TxFormat.JSONRPC):
enc = ABIContractEncoder()
enc.method('burn')
enc.typ(ABIContractType.UINT256)
enc.uint256(value)
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 burned(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('totalBurned')
data = add_0x(enc.get())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
o['params'].append(self.normalize(tx))
o['params'].append('latest')
o = j.finalize(o)
return o
def total_minted(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('totalMinted')
data = add_0x(enc.get())
tx = self.template(sender_address, contract_address)
tx = self.set_code(tx, data)
o['params'].append(self.normalize(tx))
o['params'].append('latest')
o = j.finalize(o)
return o
def bytecode(**kwargs):
return GiftableToken.bytecode(version=kwargs.get('version'))
def create(**kwargs):
return GiftableToken.cargs(kwargs['name'], kwargs['symbol'], kwargs['decimals'], expire=kwargs.get('expire'), version=kwargs.get('version'))
def args(v):
if v == 'create':
return (['name', 'symbol', 'decimals'], ['expire', 'version'],)
elif v == 'default' or v == 'bytecode':
return ([], ['version'],)
raise ValueError('unknown command: ' + v)

View File

@ -45,6 +45,7 @@ def process_config_local(config, arg, args, flags):
config.add(args.token_name, '_TOKEN_NAME', False) config.add(args.token_name, '_TOKEN_NAME', False)
config.add(args.token_symbol, '_TOKEN_SYMBOL', False) config.add(args.token_symbol, '_TOKEN_SYMBOL', False)
config.add(args.token_decimals, '_TOKEN_DECIMALS', False) config.add(args.token_decimals, '_TOKEN_DECIMALS', False)
config.add(args.token_expire, '_TOKEN_EXPIRE', False)
return config return config
@ -57,6 +58,7 @@ argparser = process_args(argparser, arg, flags)
argparser.add_argument('--name', dest='token_name', required=True, type=str, help='Token name') argparser.add_argument('--name', dest='token_name', required=True, type=str, help='Token name')
argparser.add_argument('--symbol', dest='token_symbol', required=True, type=str, help='Token symbol') argparser.add_argument('--symbol', dest='token_symbol', required=True, type=str, help='Token symbol')
argparser.add_argument('--decimals', dest='token_decimals', default=18, type=int, help='Token decimals') argparser.add_argument('--decimals', dest='token_decimals', default=18, type=int, help='Token decimals')
argparser.add_argument('--expire', dest='token_expire', default=0, type=int, help='Token expiry timestamp (after which token cannot be traded)')
args = argparser.parse_args() args = argparser.parse_args()
logg = process_log(args, logg) logg = process_log(args, logg)
@ -87,6 +89,7 @@ def main():
config.get('_TOKEN_NAME'), config.get('_TOKEN_NAME'),
config.get('_TOKEN_SYMBOL'), config.get('_TOKEN_SYMBOL'),
config.get('_TOKEN_DECIMALS'), config.get('_TOKEN_DECIMALS'),
expire=config.get('_TOKEN_EXPIRE'),
) )
if settings.get('RPC_SEND'): if settings.get('RPC_SEND'):
conn.do(o) conn.do(o)

View File

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

View File

@ -0,0 +1,59 @@
# standard imports
import logging
import time
# 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.address import to_checksum_address
# local imports
from giftable_erc20_token import GiftableToken
logg = logging.getLogger(__name__)
class TestGiftableToken(EthTesterCase):
expire = 0
def setUp(self):
super(TestGiftableToken, self).setUp()
self.conn = RPCConnection.connect(self.chain_spec, 'default')
address = self.publish_giftable_token('Foo Token', 'FOO', 16, expire=self.expire)
self.address = to_checksum_address(address)
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
c = GiftableToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
self.initial_supply = 1 << 40
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], self.initial_supply)
r = self.conn.do(o)
o = receipt(tx_hash)
r = self.conn.do(o)
self.assertEqual(r['status'], 1)
def publish_giftable_token(self, name, symbol, decimals=16, expire=None):
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
c = GiftableToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
self.symbol = name
self.name = symbol
self.decimals = decimals
(tx_hash, o) = c.constructor(self.accounts[0], self.name, self.symbol, self.decimals, expire=expire)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
address = r['contract_address']
logg.debug('published on address {} with hash {}'.format(address, tx_hash))
return address
class TestGiftableExpireToken(TestGiftableToken):
expire = int(time.time()) + 100000
def setUp(self):
super(TestGiftableExpireToken, self).setUp()

View File

@ -1,3 +1,4 @@
confini~=0.6.1 confini~=0.6.1
chainlib-eth~=0.4.2 chainlib-eth~=0.6.0
chainlib~=0.5.0
potaahto~=0.1.1 potaahto~=0.1.1

View File

@ -1,5 +1,8 @@
#!/bin/bash #!/bin/bash
export PYTHONPATH=${PYTHONPATH}:.
>&2 echo "using pythonpath $PYTHONPATH"
set -e set -e
set -x set -x
for f in `ls tests/*.py`; do for f in `ls tests/*.py`; do

View File

@ -1,10 +1,10 @@
[metadata] [metadata]
name = eth-erc20 name = eth-erc20
version = 0.5.3 version = 0.9.0
description = ERC20 interface and simple contract with deployment script that lets any address mint and gift itself tokens. description = ERC20 interface and simple contract with deployment script that lets any address mint and gift itself tokens.
author = Louis Holbrook author = Louis Holbrook
author_email = dev@holbrook.no author_email = dev@holbrook.no
url = https://git.defalsify.org/eth-erc20.git url = https://git.defalsify.org/eth-erc20
keywords = keywords =
dlt dlt
ethereum ethereum
@ -14,11 +14,12 @@ keywords =
classifiers = classifiers =
Programming Language :: Python :: 3 Programming Language :: Python :: 3
Operating System :: OS Independent Operating System :: OS Independent
Development Status :: 3 - Alpha Development Status :: 4 - Beta
Environment :: No Input/Output (Daemon) Environment :: Console
Intended Audience :: Developers Intended Audience :: Developers
License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+) License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Topic :: Internet Topic :: Internet
Topic :: Software Development :: Libraries
#Topic :: Blockchain :: EVM #Topic :: Blockchain :: EVM
license = AGPLv3+ license = AGPLv3+
licence_files = licence_files =
@ -26,14 +27,16 @@ licence_files =
[options] [options]
include_package_data = True include_package_data = True
python_requires = >= 3.7 python_requires = >= 3.8
packages = packages =
giftable_erc20_token giftable_erc20_token
giftable_erc20_token.runnable giftable_erc20_token.runnable
giftable_erc20_token.unittest
giftable_erc20_token.data giftable_erc20_token.data
eth_erc20 eth_erc20
eth_erc20.data eth_erc20.data
eth_erc20.runnable eth_erc20.runnable
eth_erc20.unittest
static_token.data static_token.data
[options.package_data] [options.package_data]
@ -46,9 +49,8 @@ packages =
[options.entry_points] [options.entry_points]
console_scripts = console_scripts =
giftable-token-deploy = giftable_erc20_token.runnable.deploy:main giftable-token-publish = giftable_erc20_token.runnable.publish:main
giftable-token-gift = giftable_erc20_token.runnable.gift:main giftable-token-gift = giftable_erc20_token.runnable.gift:main
giftable-token-minter = giftable_erc20_token.runnable.minter:main
erc20-transfer = eth_erc20.runnable.transfer:main erc20-transfer = eth_erc20.runnable.transfer:main
erc20-balance = eth_erc20.runnable.balance:main erc20-balance = eth_erc20.runnable.balance:main
erc20-info = eth_erc20.runnable.info:main erc20-info = eth_erc20.runnable.info:main

View File

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

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,3 @@
import os
data_dir = os.path.realpath(os.path.dirname(__file__))

View File

@ -0,0 +1,60 @@
# standard imports
import os
import logging
# external imports
from chainlib.eth.tx import (
TxFactory,
TxFormat,
)
from chainlib.hash import keccak256_string_to_hex
from chainlib.eth.contract import (
ABIContractEncoder,
ABIContractType,
)
# local imports
from static_token.data import data_dir
logg = logging.getLogger(__name__)
class StaticToken(TxFactory):
__abi = None
__bytecode = None
def constructor(self, sender_address, name, symbol, decimals, supply, tx_format=TxFormat.JSONRPC):
code = StaticToken.bytecode()
enc = ABIContractEncoder()
enc.string(name)
enc.string(symbol)
enc.uint256(decimals)
enc.uint256(supply)
code += enc.get()
tx = self.template(sender_address, None, use_nonce=True)
tx = self.set_code(tx, code)
return self.finalize(tx, tx_format)
@staticmethod
def gas(code=None):
return 2000000
@staticmethod
def abi():
if StaticToken.__abi == None:
f = open(os.path.join(data_dir, 'StaticToken.json'), 'r')
StaticToken.__abi = json.load(f)
f.close()
return StaticToken.__abi
@staticmethod
def bytecode():
if StaticToken.__bytecode == None:
f = open(os.path.join(data_dir, 'StaticToken.bin'))
StaticToken.__bytecode = f.read()
f.close()
return StaticToken.__bytecode

View File

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

View File

@ -0,0 +1,36 @@
# standard imports
import logging
import time
# 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.address import to_checksum_address
# local imports
from static_token import StaticToken
from eth_erc20.unittest import TestInterface
logg = logging.getLogger(__name__)
class TestStaticToken(EthTesterCase, TestInterface):
def setUp(self):
super(TestStaticToken, self).setUp()
self.conn = RPCConnection.connect(self.chain_spec, 'default')
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
c = StaticToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
self.symbol = 'FOO'
self.name = 'Foo Token'
self.decimals = 16
self.initial_supply = 1 << 24
(tx_hash, o) = c.constructor(self.accounts[0], self.name, self.symbol, self.decimals, self.initial_supply)
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
self.address = to_checksum_address(r['contract_address'])
logg.debug('published statictoken on address {} with hash {}'.format(self.address, tx_hash))

View File

@ -1,3 +1,3 @@
eth_tester==0.5.0b3 eth_tester==0.10.0b4
py-evm==0.3.0a20 py-evm==0.10.0b4
pytest==6.0.1 pytest==6.0.1

View File

@ -1,200 +1,13 @@
# standard imports # import logging
import logging
import unittest import unittest
# external imports
from chainlib.eth.unittest.ethtester import EthTesterCase
from chainlib.connection import RPCConnection
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.gas import OverrideGasOracle
from chainlib.eth.tx import (
transaction,
receipt,
)
from chainlib.eth.address import to_checksum_address
from hexathon import strip_0x
# local imports # local imports
from giftable_erc20_token import GiftableToken from giftable_erc20_token.unittest import TestGiftableToken
from eth_erc20 import ERC20 from eth_erc20.unittest import TestInterface
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger(__name__)
class TestToken(EthTesterCase): class TestBasic(TestGiftableToken, TestInterface):
pass
def setUp(self):
super(TestToken, self).setUp()
self.conn = RPCConnection.connect(self.chain_spec, 'default')
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
c = GiftableToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
self.symbol = 'FOO'
self.name = 'Foo Token'
self.decimals = 16
(tx_hash, o) = c.constructor(self.accounts[0], self.name, self.symbol, self.decimals)
r = self.conn.do(o)
logg.debug('deployed with hash {}'.format(r))
o = receipt(r)
r = self.conn.do(o)
self.address = to_checksum_address(r['contract_address'])
self.initial_supply = 1 << 40
(tx_hash, o) = c.mint_to(self.address, self.accounts[0], self.accounts[0], self.initial_supply)
r = self.conn.do(o)
o = receipt(tx_hash)
r = self.conn.do(o)
self.assertEqual(r['status'], 1)
def test_balance(self):
c = ERC20(self.chain_spec)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.conn.do(o)
balance = ERC20.parse_balance(r)
self.assertEqual(self.initial_supply, balance)
def test_supply(self):
c = ERC20(self.chain_spec)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.conn.do(o)
supply = ERC20.parse_total_supply(r)
self.assertEqual(self.initial_supply, supply)
def test_name(self):
c = ERC20(self.chain_spec)
o = c.name(self.address, sender_address=self.accounts[0])
r = self.conn.do(o)
name = ERC20.parse_name(r)
self.assertEqual(self.name, name)
def test_symbol(self):
c = ERC20(self.chain_spec)
o = c.symbol(self.address, sender_address=self.accounts[0])
r = self.conn.do(o)
symbol = ERC20.parse_symbol(r)
self.assertEqual(self.symbol, symbol)
def test_transfer(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
gas_oracle = OverrideGasOracle(limit=100000, conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.transfer(self.address, self.accounts[0], self.accounts[1], 1000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, self.initial_supply - 1000)
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, 1000)
o = transaction(tx_hash)
r = self.rpc.do(o)
data = c.parse_transfer_request(r['data'])
self.assertEqual(data[0], strip_0x(self.accounts[1]))
self.assertEqual(data[1], 1000)
def test_transfer_from(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
gas_oracle = OverrideGasOracle(limit=100000, conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 1000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.allowance(self.address, self.accounts[0], self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
allowance = c.parse_allowance(r)
self.assertEqual(allowance, 1000)
o = transaction(tx_hash)
r = self.rpc.do(o)
data = c.parse_approve_request(r['data'])
self.assertEqual(data[0], strip_0x(self.accounts[1]))
self.assertEqual(data[1], 1000)
nonce_oracle = RPCNonceOracle(self.accounts[1], conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.transfer_from(self.address, self.accounts[1], self.accounts[0], self.accounts[2], 1001)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
o = transaction(tx_hash)
r = self.rpc.do(o)
data = c.parse_transfer_from_request(r['data'])
self.assertEqual(data[0], strip_0x(self.accounts[0]))
self.assertEqual(data[1], strip_0x(self.accounts[2]))
self.assertEqual(data[2], 1001)
(tx_hash, o) = c.transfer_from(self.address, self.accounts[1], self.accounts[0], self.accounts[2], 1000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, self.initial_supply - 1000)
o = c.balance_of(self.address, self.accounts[2], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, 1000)
def test_revoke_approve(self):
nonce_oracle = RPCNonceOracle(self.accounts[0], conn=self.conn)
gas_oracle = OverrideGasOracle(limit=100000, conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 1000)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 999)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
(tx_hash, o) = c.approve(self.address, self.accounts[0], self.accounts[1], 0)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.allowance(self.address, self.accounts[0], self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
allowance = c.parse_allowance(r)
self.assertEqual(allowance, 0)
nonce_oracle = RPCNonceOracle(self.accounts[1], conn=self.conn)
c = ERC20(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle, gas_oracle=gas_oracle)
(tx_hash, o) = c.transfer_from(self.address, self.accounts[1], self.accounts[0], self.accounts[2], 1)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -0,0 +1,123 @@
# standard imports
import os
import unittest
import json
import logging
import datetime
# external imports
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import receipt
from chainlib.eth.block import (
block_latest,
block_by_number,
)
# local imports
from giftable_erc20_token import GiftableToken
from giftable_erc20_token.unittest import TestGiftableExpireToken
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
testdir = os.path.dirname(__file__)
class TestExpire(TestGiftableExpireToken):
def setUp(self):
super(TestExpire, self).setUp()
def test_expires(self):
mint_amount = self.initial_supply
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = GiftableToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.transfer(self.address, self.accounts[0], self.accounts[1], int(mint_amount / 2))
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
self.backend.time_travel(self.expire + 60)
o = block_latest()
r = self.rpc.do(o)
o = block_by_number(r)
r = self.rpc.do(o)
self.assertGreaterEqual(r['timestamp'], self.expire)
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = GiftableToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.transfer(self.address, self.accounts[0], self.accounts[1], 1)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
c = GiftableToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.transfer(self.address, self.accounts[1], self.accounts[0], 1)
r = self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, int(mint_amount / 2))
o = c.balance_of(self.address, self.accounts[1], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance += c.parse_balance(r)
self.assertEqual(balance, mint_amount)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
supply = c.parse_balance(r)
self.assertEqual(supply, mint_amount)
def test_burn(self):
mint_amount = self.initial_supply
nonce_oracle = RPCNonceOracle(self.accounts[1], self.rpc)
c = GiftableToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.burn(self.address, self.accounts[1], int(mint_amount / 4))
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 0)
nonce_oracle = RPCNonceOracle(self.accounts[0], self.rpc)
c = GiftableToken(self.chain_spec, signer=self.signer, nonce_oracle=nonce_oracle)
(tx_hash, o) = c.burn(self.address, self.accounts[0], int(mint_amount / 4))
self.rpc.do(o)
o = receipt(tx_hash)
r = self.rpc.do(o)
self.assertEqual(r['status'], 1)
o = c.burned(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
burned = c.parse_balance(r)
self.assertEqual(burned, int(mint_amount / 4))
o = c.balance_of(self.address, self.accounts[0], sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, mint_amount - burned)
o = c.total_supply(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, mint_amount - burned)
o = c.total_minted(self.address, sender_address=self.accounts[0])
r = self.rpc.do(o)
balance = c.parse_balance(r)
self.assertEqual(balance, mint_amount)
if __name__ == '__main__':
unittest.main()

View File

@ -0,0 +1,38 @@
# standard imports
import os
import unittest
import json
import logging
import datetime
# external imports
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.eth.nonce import RPCNonceOracle
from chainlib.eth.tx import receipt
from chainlib.eth.block import (
block_latest,
block_by_number,
)
# local imports
from static_token import StaticToken
from static_token.unittest import TestStaticToken
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
testdir = os.path.dirname(__file__)
class TestExpire(TestStaticToken):
def setUp(self):
super(TestExpire, self).setUp()
def test_static_interface(self):
pass
if __name__ == '__main__':
unittest.main()

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -1,12 +1,15 @@
pragma solidity >=0.6.11; pragma solidity >=0.8.0;
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
// File-Version: 2 // File-Version: 3
contract GiftableToken { contract GiftableToken {
// Implements EIP173
address public owner; address public owner;
mapping(address => bool) minters;
// Implements Writer
mapping(address => bool) writer;
// Implements ERC20 // Implements ERC20
string public name; string public name;
@ -15,54 +18,117 @@ contract GiftableToken {
// Implements ERC20 // Implements ERC20
uint8 public decimals; uint8 public decimals;
// Implements ERC20 // Implements ERC20
uint256 public totalSupply;
// Implements ERC20
mapping (address => uint256) public balanceOf; mapping (address => uint256) public balanceOf;
// Implements ERC20 // Implements ERC20
mapping (address => mapping (address => uint256)) public allowance; mapping (address => mapping (address => uint256)) public allowance;
// Implements Burner
uint256 public totalMinted;
// Implements Burner
uint256 public totalBurned;
// Implements Expire
uint256 public expires;
bool expired;
// Implements ERC20
event Transfer(address indexed _from, address indexed _to, uint256 _value); event Transfer(address indexed _from, address indexed _to, uint256 _value);
// Implements ERC20
event TransferFrom(address indexed _from, address indexed _to, address indexed _spender, uint256 _value); event TransferFrom(address indexed _from, address indexed _to, address indexed _spender, uint256 _value);
// Implements ERC20
event Approval(address indexed _owner, address indexed _spender, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value);
// Implements Minter
event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value); event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value);
constructor(string memory _name, string memory _symbol, uint8 _decimals) public { // Implement Expire
event Expired(uint256 _timestamp);
// Implements Writer
event WriterAdded(address _writer);
// Implements Writer
event WriterRemoved(address _writer);
// Implements Burner
event Burn(uint256 _value);
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _expireTimestamp) {
owner = msg.sender; owner = msg.sender;
name = _name; name = _name;
symbol = _symbol; symbol = _symbol;
decimals = _decimals; decimals = _decimals;
minters[msg.sender] = true; expires = _expireTimestamp;
} }
// Implements ERC20
function totalSupply() public view returns (uint256) {
return totalMinted - totalBurned;
}
mapping(address => bool) writers;
// Implements Minter
function mintTo(address _to, uint256 _value) public returns (bool) { function mintTo(address _to, uint256 _value) public returns (bool) {
require(minters[msg.sender]); require(writers[msg.sender] || msg.sender == owner);
balanceOf[_to] += _value; balanceOf[_to] += _value;
totalSupply += _value; totalMinted += _value;
emit Mint(msg.sender, _to, _value); emit Mint(msg.sender, _to, _value);
return true; return true;
} }
function addMinter(address _minter) public returns (bool) { // Implements Minter
// Implements ERC5679Ext20
function mint(address _to, uint256 _value, bytes calldata _data) public {
_data;
mintTo(_to, _value);
}
// Implements Writer
function addWriter(address _minter) public returns (bool) {
require(msg.sender == owner); require(msg.sender == owner);
minters[_minter] = true; writers[_minter] = true;
return true; return true;
} }
function removeMinter(address _minter) public returns (bool) { // Implements Writer
function deleteWriter(address _minter) public returns (bool) {
require(msg.sender == owner || msg.sender == _minter); require(msg.sender == owner || msg.sender == _minter);
minters[_minter] = false; writers[_minter] = false;
return true; return true;
} }
// Implements Writer
function isWriter(address _minter) public view returns(bool) {
return writers[_minter] || _minter == owner;
}
// Implements Expire
function applyExpiry() public returns(uint8) {
if (expires == 0) {
return 0;
}
if (expired) {
return 1;
}
if (block.timestamp >= expires) {
expired = true;
emit Expired(block.timestamp);
return 2;
}
return 0;
}
// Implements ERC20 // Implements ERC20
function transfer(address _to, uint256 _value) public returns (bool) { function transfer(address _to, uint256 _value) public returns (bool) {
require(applyExpiry() == 0);
require(balanceOf[msg.sender] >= _value); require(balanceOf[msg.sender] >= _value);
balanceOf[msg.sender] -= _value; balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value; balanceOf[_to] += _value;
@ -70,8 +136,34 @@ contract GiftableToken {
return true; return true;
} }
// Implements Burner
function burn(uint256 _value) public returns (bool) {
require(msg.sender == owner, 'ERR_ACCESS');
require(balanceOf[msg.sender] >= _value, 'ERR_FUNDS');
balanceOf[msg.sender] -= _value;
totalBurned += _value;
emit Burn(_value);
return true;
}
// Implements Burner
function burn() public returns(bool) {
return burn(balanceOf[msg.sender]);
}
// Implements Burner
// Implements ERC5679Ext20
function burn(address _from, uint256 _value, bytes calldata _data) public {
require(msg.sender == _from, 'ERR_NOT_SELF');
_data;
burn(_value);
}
// Implements ERC20 // Implements ERC20
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) { function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(applyExpiry() == 0);
require(allowance[_from][msg.sender] >= _value); require(allowance[_from][msg.sender] >= _value);
require(balanceOf[_from] >= _value); require(balanceOf[_from] >= _value);
allowance[_from][msg.sender] = allowance[_from][msg.sender] - _value; allowance[_from][msg.sender] = allowance[_from][msg.sender] - _value;
@ -83,6 +175,7 @@ contract GiftableToken {
// Implements ERC20 // Implements ERC20
function approve(address _spender, uint256 _value) public returns (bool) { function approve(address _spender, uint256 _value) public returns (bool) {
require(applyExpiry() == 0);
if (_value > 0) { if (_value > 0) {
require(allowance[msg.sender][_spender] == 0); require(allowance[msg.sender][_spender] == 0);
} }
@ -95,11 +188,12 @@ contract GiftableToken {
function transferOwnership(address _newOwner) public returns (bool) { function transferOwnership(address _newOwner) public returns (bool) {
require(msg.sender == owner); require(msg.sender == owner);
owner = _newOwner; owner = _newOwner;
return true;
} }
// Implements EIP165 // Implements EIP165
function supportsInterface(bytes4 _sum) public returns (bool) { function supportsInterface(bytes4 _sum) public pure returns (bool) {
if (_sum == 0xc6bb4b70) { // ERC20 if (_sum == 0xb61bc941) { // ERC20
return true; return true;
} }
if (_sum == 0x449a52f8) { // Minter if (_sum == 0x449a52f8) { // Minter
@ -111,6 +205,15 @@ contract GiftableToken {
if (_sum == 0x9493f8b2) { // EIP173 if (_sum == 0x9493f8b2) { // EIP173
return true; return true;
} }
if (_sum == 0xabe1f1f5) { // Writer
return true;
}
if (_sum == 0xb1110c1b) { // Burner
return true;
}
if (_sum == 0x841a0e94) { // Expire
return true;
}
return false; return false;
} }
} }

View File

@ -2,13 +2,15 @@ SOLC = /usr/bin/solc
all: all:
$(SOLC) --bin GiftableToken.sol --evm-version byzantium | awk 'NR>3' > GiftableToken.bin $(SOLC) --bin GiftableToken.sol --evm-version byzantium | awk 'NR>3' > GiftableToken.bin
truncate -s -1 GiftableToken.bin
$(SOLC) --abi GiftableToken.sol --evm-version byzantium | awk 'NR>3' > GiftableToken.json $(SOLC) --abi GiftableToken.sol --evm-version byzantium | awk 'NR>3' > GiftableToken.json
$(SOLC) --metadata GiftableToken.sol --evm-version byzantium | awk 'NR>3' > GiftableToken.metadata.json
$(SOLC) --bin StaticToken.sol --evm-version byzantium | awk 'NR>3' > StaticToken.bin $(SOLC) --bin StaticToken.sol --evm-version byzantium | awk 'NR>3' > StaticToken.bin
truncate -s -1 StaticToken.bin
$(SOLC) --abi StaticToken.sol --evm-version byzantium | awk 'NR>3' > StaticToken.json $(SOLC) --abi StaticToken.sol --evm-version byzantium | awk 'NR>3' > StaticToken.json
$(SOLC) --metadata StaticToken.sol --evm-version byzantium | awk 'NR>3' > StaticToken.metadata.json
truncate -s -1 StaticToken.bin
truncate -s -1 GiftableToken.bin
install: all install: all
cp -v Giftable*{json,bin} ../python/giftable_erc20_token/data/ cp -v *.json ../python/giftable_erc20_token/data/
cp -v Static*{json,bin} ../python/static_token/data/ cp -v *.bin ../python/giftable_erc20_token/data/

File diff suppressed because one or more lines are too long

View File

@ -1 +1 @@
[{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"},{"internalType":"uint256","name":"_supply","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"TransferFrom","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_sum","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}] [{"inputs":[{"internalType":"string","name":"_name","type":"string"},{"internalType":"string","name":"_symbol","type":"string"},{"internalType":"uint8","name":"_decimals","type":"uint8"},{"internalType":"uint256","name":"_supply","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_owner","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"_from","type":"address"},{"indexed":true,"internalType":"address","name":"_to","type":"address"},{"indexed":true,"internalType":"address","name":"_spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"_value","type":"uint256"}],"name":"TransferFrom","type":"event"},{"inputs":[{"internalType":"address","name":"","type":"address"},{"internalType":"address","name":"","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_spender","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"_sum","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"pure","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_from","type":"address"},{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"}]

View File

@ -1,6 +1,6 @@
pragma solidity >0.6.11; pragma solidity >=0.8.0;
// SPDX-License-Identifier: GPL-3.0-or-later // SPDX-License-Identifier: AGPL-3.0-or-later
contract StaticToken { contract StaticToken {
@ -20,11 +20,14 @@ contract StaticToken {
// Implements ERC20 // Implements ERC20
mapping (address => mapping (address => uint256)) public allowance; mapping (address => mapping (address => uint256)) public allowance;
// Implements ERC20
event Transfer(address indexed _from, address indexed _to, uint256 _value); event Transfer(address indexed _from, address indexed _to, uint256 _value);
// Implements ERC20
event TransferFrom(address indexed _from, address indexed _to, address indexed _spender, uint256 _value); event TransferFrom(address indexed _from, address indexed _to, address indexed _spender, uint256 _value);
// Implements ERC20
event Approval(address indexed _owner, address indexed _spender, uint256 _value); event Approval(address indexed _owner, address indexed _spender, uint256 _value);
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _supply) public { constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _supply) {
owner = msg.sender; owner = msg.sender;
name = _name; name = _name;
symbol = _symbol; symbol = _symbol;
@ -64,8 +67,8 @@ contract StaticToken {
} }
// Implements EIP165 // Implements EIP165
function supportsInterface(bytes4 _sum) public returns (bool) { function supportsInterface(bytes4 _sum) public pure returns (bool) {
if (_sum == 0xc6bb4b70) { // ERC20 if (_sum == 0xb61bc941) { // ERC20
return true; return true;
} }
if (_sum == 0x01ffc9a7) { // EIP165 if (_sum == 0x01ffc9a7) { // EIP165