Compare commits

..

7 Commits

Author SHA1 Message Date
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
20 changed files with 245 additions and 62 deletions

View File

@@ -1,3 +1,8 @@
* 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

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

@@ -94,20 +94,21 @@ def main():
)
# determine decimals
decimals_o = g.decimals(token_address, sender_address=sender_address)
r = conn.do(decimals_o)
decimals = int(strip_0x(r), 16)
logg.info('decimals {}'.format(decimals))
if not config.get('_RAW'):
decimals_o = g.decimals(token_address, sender_address=sender_address)
r = conn.do(decimals_o)
decimals = int(strip_0x(r), 16)
logg.info('decimals {}'.format(decimals))
name_o = g.name(token_address, sender_address=sender_address)
r = conn.do(name_o)
token_name = g.parse_name(r)
logg.info('name {}'.format(token_name))
symbol_o = g.symbol(token_address, sender_address=sender_address)
r = conn.do(symbol_o)
token_symbol = g.parse_symbol(r)
logg.info('symbol {}'.format(token_symbol))
# name_o = g.name(token_address, sender_address=sender_address)
# r = conn.do(name_o)
# token_name = g.parse_name(r)
# logg.info('name {}'.format(token_name))
#
# symbol_o = g.symbol(token_address, sender_address=sender_address)
# r = conn.do(symbol_o)
# token_symbol = g.parse_symbol(r)
# logg.info('symbol {}'.format(token_symbol))
# get balance
balance_o = g.balance(token_address, settings.get('RECIPIENT'), sender_address=sender_address)
@@ -115,7 +116,10 @@ def main():
hx = strip_0x(r)
balance_value = int(hx, 16)
logg.debug('balance {} = {} decimals {}'.format(even(hx), balance_value, decimals))
if config.get('_RAW'):
logg.debug('balance {} = {}'.format(even(hx), balance_value))
else:
logg.debug('balance {} = {} decimals {}'.format(even(hx), balance_value, decimals))
balance_str = str(balance_value)
balance_len = len(balance_str)

View File

@@ -1 +1,4 @@
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

View File

@@ -15,25 +15,35 @@ from chainlib.eth.contract import (
# local imports
from giftable_erc20_token.data import data_dir
from eth_erc20 import ERC20
logg = logging.getLogger(__name__)
class GiftableToken(TxFactory):
class GiftableToken(ERC20):
__abi = None
__bytecode = None
def constructor(self, sender_address, name, symbol, decimals, tx_format=TxFormat.JSONRPC):
code = GiftableToken.bytecode()
def constructor(self, sender_address, name, symbol, decimals, expire=0, tx_format=TxFormat.JSONRPC, version=None):
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.string(name)
enc.string(symbol)
enc.uint256(decimals)
enc.uint256(expire)
code += enc.get()
tx = self.template(sender_address, None, use_nonce=True)
tx = self.set_code(tx, code)
return self.finalize(tx, tx_format)
return code
@staticmethod
@@ -51,7 +61,7 @@ class GiftableToken(TxFactory):
@staticmethod
def bytecode():
def bytecode(version=None):
if GiftableToken.__bytecode == None:
f = open(os.path.join(data_dir, 'GiftableToken.bin'))
GiftableToken.__bytecode = f.read()
@@ -97,3 +107,17 @@ class GiftableToken(TxFactory):
return tx
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_symbol, '_TOKEN_SYMBOL', False)
config.add(args.token_decimals, '_TOKEN_DECIMALS', False)
config.add(args.token_expire, '_TOKEN_EXPIRE', False)
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('--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('--expire', dest='token_expire', default=0, type=int, help='Token expiry timestamp (after which token cannot be traded)')
args = argparser.parse_args()
logg = process_log(args, logg)
@@ -87,6 +89,7 @@ def main():
config.get('_TOKEN_NAME'),
config.get('_TOKEN_SYMBOL'),
config.get('_TOKEN_DECIMALS'),
expire=config.get('_TOKEN_EXPIRE'),
)
if settings.get('RPC_SEND'):
conn.do(o)

View File

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

View File

@@ -0,0 +1,51 @@
# 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')
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, expire=self.expire)
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 on address {} with hash {}'.format(self.address, tx_hash))
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)
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
chainlib-eth~=0.4.15
chainlib~=0.4.8
potaahto~=0.1.1

View File

@@ -1,5 +1,7 @@
#!/bin/bash
export PYTHONPATH=${PYTHONPATH}:.
set -e
set -x
for f in `ls tests/*.py`; do

View File

@@ -1,6 +1,6 @@
[metadata]
name = eth-erc20
version = 0.5.4
version = 0.6.0
description = ERC20 interface and simple contract with deployment script that lets any address mint and gift itself tokens.
author = Louis Holbrook
author_email = dev@holbrook.no
@@ -30,6 +30,7 @@ python_requires = >= 3.7
packages =
giftable_erc20_token
giftable_erc20_token.runnable
giftable_erc20_token.unittest
giftable_erc20_token.data
eth_erc20
eth_erc20.data
@@ -46,9 +47,8 @@ packages =
[options.entry_points]
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-minter = giftable_erc20_token.runnable.minter:main
erc20-transfer = eth_erc20.runnable.transfer:main
erc20-balance = eth_erc20.runnable.balance:main
erc20-info = eth_erc20.runnable.info:main

File diff suppressed because one or more lines are too long

View File

@@ -15,38 +15,14 @@ from chainlib.eth.address import to_checksum_address
from hexathon import strip_0x
# local imports
from giftable_erc20_token import GiftableToken
from giftable_erc20_token.unittest import TestGiftableToken
from eth_erc20 import ERC20
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger(__name__)
logg = logging.getLogger()
class TestToken(EthTesterCase):
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)
class TestToken(TestGiftableToken):
def test_balance(self):
c = ERC20(self.chain_spec)

View File

@@ -0,0 +1,84 @@
# 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)
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

@@ -21,21 +21,27 @@ contract GiftableToken {
// Implements ERC20
mapping (address => mapping (address => uint256)) public allowance;
// timestamp when token contract expires
uint256 public expires;
bool expired;
event Transfer(address indexed _from, address indexed _to, uint256 _value);
event TransferFrom(address indexed _from, address indexed _to, address indexed _spender, uint256 _value);
event Approval(address indexed _owner, address indexed _spender, uint256 _value);
event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value);
event Expired(uint256 _timestamp);
constructor(string memory _name, string memory _symbol, uint8 _decimals) public {
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _expireTimestamp) public {
owner = msg.sender;
name = _name;
symbol = _symbol;
decimals = _decimals;
minters[msg.sender] = true;
expires = _expireTimestamp;
}
function mintTo(address _to, uint256 _value) public returns (bool) {
require(minters[msg.sender]);
require(minters[msg.sender] || msg.sender == owner);
balanceOf[_to] += _value;
totalSupply += _value;
@@ -45,7 +51,7 @@ contract GiftableToken {
return true;
}
function addMinter(address _minter) public returns (bool) {
function addWriter(address _minter) public returns (bool) {
require(msg.sender == owner);
minters[_minter] = true;
@@ -53,7 +59,7 @@ contract GiftableToken {
return true;
}
function removeMinter(address _minter) public returns (bool) {
function removeWriter(address _minter) public returns (bool) {
require(msg.sender == owner || msg.sender == _minter);
minters[_minter] = false;
@@ -61,8 +67,29 @@ contract GiftableToken {
return true;
}
function isWriter(address _minter) public view returns(bool) {
return minters[_minter] || _minter == owner;
}
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
function transfer(address _to, uint256 _value) public returns (bool) {
require(applyExpiry() == 0);
require(balanceOf[msg.sender] >= _value);
balanceOf[msg.sender] -= _value;
balanceOf[_to] += _value;
@@ -72,6 +99,7 @@ contract GiftableToken {
// Implements ERC20
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(applyExpiry() == 0);
require(allowance[_from][msg.sender] >= _value);
require(balanceOf[_from] >= _value);
allowance[_from][msg.sender] = allowance[_from][msg.sender] - _value;
@@ -83,6 +111,7 @@ contract GiftableToken {
// Implements ERC20
function approve(address _spender, uint256 _value) public returns (bool) {
require(applyExpiry() == 0);
if (_value > 0) {
require(allowance[msg.sender][_spender] == 0);
}

File diff suppressed because one or more lines are too long