Compare commits

...

14 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
33 changed files with 567 additions and 230 deletions

2
.gitignore vendored
View File

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

View File

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

View File

@ -100,16 +100,6 @@ def main():
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))
# get balance
balance_o = g.balance(token_address, settings.get('RECIPIENT'), sender_address=sender_address)
r = conn.do(balance_o)

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)

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,6 +12,9 @@ from chainlib.eth.contract import (
ABIContractEncoder,
ABIContractType,
)
from chainlib.eth.constant import ZERO_ADDRESS
from chainlib.jsonrpc import JSONRPCRequest
from hexathon import add_0x
# local imports
from giftable_erc20_token.data import data_dir
@ -42,7 +45,9 @@ class GiftableToken(ERC20):
enc.string(symbol)
enc.uint256(decimals)
enc.uint256(expire)
code += enc.get()
args = enc.get()
code += args
logg.debug('constructor code: ' + args)
return code
@ -107,6 +112,48 @@ class GiftableToken(ERC20):
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'))

View File

@ -22,19 +22,11 @@ class TestGiftableToken(EthTesterCase):
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.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)
@ -43,6 +35,22 @@ class TestGiftableToken(EthTesterCase):
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

View File

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

View File

@ -1,6 +1,7 @@
#!/bin/bash
export PYTHONPATH=${PYTHONPATH}:.
>&2 echo "using pythonpath $PYTHONPATH"
set -e
set -x

View File

@ -1,6 +1,6 @@
[metadata]
name = eth-erc20
version = 0.6.0
version = 0.9.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
@ -14,11 +14,12 @@ keywords =
classifiers =
Programming Language :: Python :: 3
Operating System :: OS Independent
Development Status :: 3 - Alpha
Environment :: No Input/Output (Daemon)
Development Status :: 4 - Beta
Environment :: Console
Intended Audience :: Developers
License :: OSI Approved :: GNU Affero General Public License v3 or later (AGPLv3+)
Topic :: Internet
Topic :: Software Development :: Libraries
#Topic :: Blockchain :: EVM
license = AGPLv3+
licence_files =
@ -26,7 +27,7 @@ licence_files =
[options]
include_package_data = True
python_requires = >= 3.7
python_requires = >= 3.8
packages =
giftable_erc20_token
giftable_erc20_token.runnable
@ -35,6 +36,7 @@ packages =
eth_erc20
eth_erc20.data
eth_erc20.runnable
eth_erc20.unittest
static_token.data
[options.package_data]

View File

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

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
py-evm==0.3.0a20
eth_tester==0.10.0b4
py-evm==0.10.0b4
pytest==6.0.1

View File

@ -1,176 +1,13 @@
# standard imports
import logging
# import logging
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
from giftable_erc20_token.unittest import TestGiftableToken
from eth_erc20 import ERC20
logging.basicConfig(level=logging.DEBUG)
logg = logging.getLogger()
from eth_erc20.unittest import TestInterface
class TestToken(TestGiftableToken):
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)
class TestBasic(TestGiftableToken, TestInterface):
pass
if __name__ == '__main__':

View File

@ -80,5 +80,44 @@ class TestExpire(TestGiftableExpireToken):
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
// File-Version: 2
// SPDX-License-Identifier: AGPL-3.0-or-later
// File-Version: 3
contract GiftableToken {
// Implements EIP173
address public owner;
mapping(address => bool) minters;
// Implements Writer
mapping(address => bool) writer;
// Implements ERC20
string public name;
@ -15,62 +18,98 @@ contract GiftableToken {
// Implements ERC20
uint8 public decimals;
// Implements ERC20
uint256 public totalSupply;
// Implements ERC20
mapping (address => uint256) public balanceOf;
// Implements ERC20
mapping (address => mapping (address => uint256)) public allowance;
// timestamp when token contract expires
// 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);
// Implements ERC20
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);
// Implements Minter
event Mint(address indexed _minter, address indexed _beneficiary, uint256 _value);
// Implement Expire
event Expired(uint256 _timestamp);
constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _expireTimestamp) public {
// 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;
name = _name;
symbol = _symbol;
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) {
require(minters[msg.sender] || msg.sender == owner);
require(writers[msg.sender] || msg.sender == owner);
balanceOf[_to] += _value;
totalSupply += _value;
totalMinted += _value;
emit Mint(msg.sender, _to, _value);
return true;
}
// 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);
minters[_minter] = true;
writers[_minter] = true;
return true;
}
function removeWriter(address _minter) public returns (bool) {
// Implements Writer
function deleteWriter(address _minter) public returns (bool) {
require(msg.sender == owner || msg.sender == _minter);
minters[_minter] = false;
writers[_minter] = false;
return true;
}
// Implements Writer
function isWriter(address _minter) public view returns(bool) {
return minters[_minter] || _minter == owner;
return writers[_minter] || _minter == owner;
}
// Implements Expire
function applyExpiry() public returns(uint8) {
if (expires == 0) {
return 0;
@ -97,6 +136,31 @@ contract GiftableToken {
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
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
require(applyExpiry() == 0);
@ -124,11 +188,12 @@ contract GiftableToken {
function transferOwnership(address _newOwner) public returns (bool) {
require(msg.sender == owner);
owner = _newOwner;
return true;
}
// Implements EIP165
function supportsInterface(bytes4 _sum) public returns (bool) {
if (_sum == 0xc6bb4b70) { // ERC20
function supportsInterface(bytes4 _sum) public pure returns (bool) {
if (_sum == 0xb61bc941) { // ERC20
return true;
}
if (_sum == 0x449a52f8) { // Minter
@ -140,6 +205,15 @@ contract GiftableToken {
if (_sum == 0x9493f8b2) { // EIP173
return true;
}
if (_sum == 0xabe1f1f5) { // Writer
return true;
}
if (_sum == 0xb1110c1b) { // Burner
return true;
}
if (_sum == 0x841a0e94) { // Expire
return true;
}
return false;
}
}

View File

@ -2,13 +2,15 @@ SOLC = /usr/bin/solc
all:
$(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) --metadata GiftableToken.sol --evm-version byzantium | awk 'NR>3' > GiftableToken.metadata.json
$(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) --metadata StaticToken.sol --evm-version byzantium | awk 'NR>3' > StaticToken.metadata.json
truncate -s -1 StaticToken.bin
truncate -s -1 GiftableToken.bin
install: all
cp -v Giftable*{json,bin} ../python/giftable_erc20_token/data/
cp -v Static*{json,bin} ../python/static_token/data/
cp -v *.json ../python/giftable_erc20_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 {
@ -20,11 +20,14 @@ contract StaticToken {
// Implements ERC20
mapping (address => mapping (address => uint256)) public allowance;
// Implements ERC20
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);
// Implements ERC20
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;
name = _name;
symbol = _symbol;
@ -64,8 +67,8 @@ contract StaticToken {
}
// Implements EIP165
function supportsInterface(bytes4 _sum) public returns (bool) {
if (_sum == 0xc6bb4b70) { // ERC20
function supportsInterface(bytes4 _sum) public pure returns (bool) {
if (_sum == 0xb61bc941) { // ERC20
return true;
}
if (_sum == 0x01ffc9a7) { // EIP165