Add expire option

This commit is contained in:
lash 2023-02-21 20:28:25 +00:00
parent ea7c106fa4
commit d7fb3f232c
Signed by: lash
GPG Key ID: 21D2E7BB88C2A746
15 changed files with 196 additions and 46 deletions

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,29 +15,33 @@ from chainlib.eth.contract import (
# 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, version=None): def constructor(self, sender_address, name, symbol, decimals, expire=0, tx_format=TxFormat.JSONRPC, version=None):
code = self.cargs(name, symbol, decimals) code = self.cargs(name, symbol, decimals, expire=expire)
tx = self.template(sender_address, None, use_nonce=True) tx = self.template(sender_address, None, use_nonce=True)
tx = self.set_code(tx, code) tx = self.set_code(tx, code)
return self.finalize(tx, tx_format) return self.finalize(tx, tx_format)
@staticmethod @staticmethod
def cargs(name, symbol, decimals, version=None): def cargs(name, symbol, decimals, expire=0, version=None):
if expire == None:
expire = 0
code = GiftableToken.bytecode(version=version) 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)
enc.uint256(expire)
code += enc.get() code += enc.get()
return code return code
@ -108,12 +112,12 @@ def bytecode(**kwargs):
def create(**kwargs): def create(**kwargs):
return GiftableToken.cargs(kwargs['name'], kwargs['symbol'], kwargs['decimals'], version=kwargs.get('version')) return GiftableToken.cargs(kwargs['name'], kwargs['symbol'], kwargs['decimals'], expire=kwargs.get('expire'), version=kwargs.get('version'))
def args(v): def args(v):
if v == 'create': if v == 'create':
return (['name', 'symbol', 'decimals'], ['version'],) return (['name', 'symbol', 'decimals'], ['expire', 'version'],)
elif v == 'default' or v == 'bytecode': elif v == 'default' or v == 'bytecode':
return ([], ['version'],) return ([], ['version'],)
raise ValueError('unknown command: ' + v) 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,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,5 +1,7 @@
#!/bin/bash #!/bin/bash
export 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,6 +1,6 @@
[metadata] [metadata]
name = eth-erc20 name = eth-erc20
version = 0.5.6 version = 0.6.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
@ -30,6 +30,7 @@ python_requires = >= 3.7
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
@ -46,9 +47,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

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 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 import ERC20
logging.basicConfig(level=logging.DEBUG) logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger(__name__) logg = logging.getLogger()
class TestToken(EthTesterCase): class TestToken(TestGiftableToken):
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): def test_balance(self):
c = ERC20(self.chain_spec) 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 // Implements ERC20
mapping (address => mapping (address => uint256)) public allowance; 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 Transfer(address indexed _from, address indexed _to, uint256 _value);
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);
event Approval(address indexed _owner, 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 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; owner = msg.sender;
name = _name; name = _name;
symbol = _symbol; symbol = _symbol;
decimals = _decimals; decimals = _decimals;
minters[msg.sender] = true; minters[msg.sender] = true;
expires = _expireTimestamp;
} }
function mintTo(address _to, uint256 _value) public returns (bool) { function mintTo(address _to, uint256 _value) public returns (bool) {
require(minters[msg.sender]); require(minters[msg.sender] || msg.sender == owner);
balanceOf[_to] += _value; balanceOf[_to] += _value;
totalSupply += _value; totalSupply += _value;
@ -45,7 +51,7 @@ contract GiftableToken {
return true; return true;
} }
function addMinter(address _minter) public returns (bool) { function addWriter(address _minter) public returns (bool) {
require(msg.sender == owner); require(msg.sender == owner);
minters[_minter] = true; minters[_minter] = true;
@ -53,7 +59,7 @@ contract GiftableToken {
return true; return true;
} }
function removeMinter(address _minter) public returns (bool) { function removeWriter(address _minter) public returns (bool) {
require(msg.sender == owner || msg.sender == _minter); require(msg.sender == owner || msg.sender == _minter);
minters[_minter] = false; minters[_minter] = false;
@ -61,8 +67,29 @@ contract GiftableToken {
return true; 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 // 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;
@ -72,6 +99,7 @@ contract GiftableToken {
// 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 +111,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);
} }

File diff suppressed because one or more lines are too long