eth-erc20

ERC20 interface and example giftable token contract
Log | Files | Refs | LICENSE

commit 27ea14772f023326963edf127a9d0c55d1d797b6
parent 4d72398adfaadfa2dd7f4bf1ad91ceea0d005c39
Author: nolash <dev@holbrook.no>
Date:   Tue,  1 Dec 2020 23:54:17 +0100

Deploy script to package, dockerfile

Diffstat:
DCHANGELOG | 3---
ADockerfile | 28++++++++++++++++++++++++++++
DGiftableToken.sol | 160-------------------------------------------------------------------------------
DLICENSE | 45---------------------------------------------
Ddeploy.py | 71-----------------------------------------------------------------------
Apython/CHANGELOG | 5+++++
RVERSION -> python/VERSION | 0
Apython/giftable_erc20_token/runnable/deploy.py | 77+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Rrequirements.txt -> python/requirements.txt | 0
Apython/setup.cfg | 32++++++++++++++++++++++++++++++++
Apython/setup.py | 4++++
RGiftableToken.abi.json -> solidity/GiftableToken.abi.json | 0
RGiftableToken.bin -> solidity/GiftableToken.bin | 0
Asolidity/GiftableToken.sol | 160+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
14 files changed, 306 insertions(+), 279 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG @@ -1,3 +0,0 @@ -* 0.0.1 - - Adapt Bancor ERC20 token contract - - Add deploy-and-gift script diff --git a/Dockerfile b/Dockerfile @@ -0,0 +1,28 @@ +FROM ethereum/solc:0.6.12 + +FROM python:3.8.6-alpine + +COPY --from=0 /usr/bin/solc /usr/bin/solc + +RUN apk update &&\ + apk add gcc bash musl-dev + +WORKDIR /usr/src + +# Try to keep everything above here re-usable! + +COPY ./solidity/ /usr/src/giftable_erc20_token/solidity/ +COPY ./python/ /usr/src/giftable_erc20_token/python/ + +RUN cd giftable_erc20_token/solidity && \ + solc GiftableToken.sol --abi | awk 'NR>3' > GiftableToken.abi.json + +RUN cd giftable_erc20_token/solidity && \ + solc GiftableToken.sol --bin | awk 'NR>3' > GiftableToken.bin && \ + truncate -s "$((`stat -t -c "%s" GiftableToken.bin`-1))" GiftableToken.bin + +RUN cd giftable_erc20_token/python && \ + pip install --extra-index-url https://pip.grassrootseconomics.net:8433 . + +# To deploy: +# giftable-token-deploy --contracts-dir giftable_erc20_token/solidity/ <amount> diff --git a/GiftableToken.sol b/GiftableToken.sol @@ -1,160 +0,0 @@ -pragma solidity ^0.6.12; - -// SPDX-License-Identifier: SEE LICENSE IN LICENSE - -/* - * This is a MOCK token used for DEVELOPMENT PURPOSES ONLY. - * It allows anyone to freely mint tokens with themselves as beneficiaries - * Code is based on the ERC20Token contract from Bancor's contracts-solidity repository - * - * @dev ERC20 Standard Token implementation -*/ - -contract GiftableToken { //is IERC20Token, Utils { - //using SafeMath for uint256; - - string public name; - string public symbol; - uint8 public decimals; - uint256 public totalSupply; - mapping (address => uint256) public balanceOf; - mapping (address => mapping (address => uint256)) public allowance; - - /** - * @dev triggered when tokens are transferred between wallets - * - * @param _from source address - * @param _to target address - * @param _value transfer amount - */ - event Transfer(address indexed _from, address indexed _to, uint256 _value); - - /** - * @dev triggered when a wallet allows another wallet to transfer tokens from on its behalf - * - * @param _owner wallet that approves the allowance - * @param _spender wallet that receives the allowance - * @param _value allowance amount - */ - event Approval(address indexed _owner, address indexed _spender, uint256 _value); - - event ChangedSupply(uint256 _oldTotal, uint256 _newTotal); - - /** - * @dev initializes a new ERC20Token instance - * - * @param _name token name - * @param _symbol token symbol - * @param _decimals decimal points, for display purposes - * @param _initialSupply total supply of token units - */ - constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _initialSupply) { - // validate input - require(bytes(_name).length > 0, "ERR_INVALID_NAME"); - require(bytes(_symbol).length > 0, "ERR_INVALID_SYMBOL"); - - name = _name; - symbol = _symbol; - decimals = _decimals; - totalSupply = _initialSupply; - balanceOf[msg.sender] = _initialSupply; - } - - // validates an address - currently only checks that it isn't null - modifier validAddress(address _address) { - _validAddress(_address); - _; - } - - // error message binary size optimization - function _validAddress(address _address) internal pure { - require(_address != address(0), "ERR_INVALID_ADDRESS"); - } - - - function gift(address _to, uint256 _value) - public - virtual - validAddress(_to) - returns (bool) - { - totalSupply = totalSupply + _value; - uint256 oldSupply = balanceOf[_to]; - balanceOf[_to] = balanceOf[_to] + _value; - emit ChangedSupply(oldSupply, balanceOf[_to]); - emit Transfer(address(0x00), _to, _value); - return true; - } - - /** - * @dev transfers tokens to a given address - * throws on any error rather then return a false flag to minimize user errors - * - * @param _to target address - * @param _value transfer amount - * - * @return true if the transfer was successful, false if it wasn't - */ - function transfer(address _to, uint256 _value) - public - virtual - validAddress(_to) - returns (bool) - { - balanceOf[msg.sender] = balanceOf[msg.sender] - _value; - balanceOf[_to] = balanceOf[_to] + _value; - emit Transfer(msg.sender, _to, _value); - return true; - } - - /** - * @dev transfers tokens to a given address on behalf of another address - * throws on any error rather then return a false flag to minimize user errors - * - * @param _from source address - * @param _to target address - * @param _value transfer amount - * - * @return true if the transfer was successful, false if it wasn't - */ - function transferFrom(address _from, address _to, uint256 _value) - public - virtual - validAddress(_from) - validAddress(_to) - returns (bool) - { - allowance[_from][msg.sender] = allowance[_from][msg.sender] - _value; - balanceOf[_from] = balanceOf[_from] - _value; - balanceOf[_to] = balanceOf[_to] + _value; - emit Transfer(_from, _to, _value); - return true; - } - - /** - * @dev allows another account/contract to transfers tokens on behalf of the caller - * throws on any error rather then return a false flag to minimize user errors - * - * also, to minimize the risk of the approve/transferFrom attack vector - * (see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), approve has to be called twice - * in 2 separate transactions - once to change the allowance to 0 and secondly to change it to the new allowance value - * - * @param _spender approved address - * @param _value allowance amount - * - * @return true if the approval was successful, false if it wasn't - */ - function approve(address _spender, uint256 _value) - public - virtual - validAddress(_spender) - returns (bool) - { - // if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal - require(_value == 0 || allowance[msg.sender][_spender] == 0, "ERR_INVALID_AMOUNT"); - - allowance[msg.sender][_spender] = _value; - emit Approval(msg.sender, _spender, _value); - return true; - } -} diff --git a/LICENSE b/LICENSE @@ -1,45 +0,0 @@ - Bprotocol Foundation (Bancor) LICENSE - -1. SUBJECT TO THE PROVISIONS SET FORTH HEREIN, INCLUDING “EFFECTIVE DATE”, YOU CAN - USE THIS CODE, FILE AND/OR SOFTWARE (“SOFTWARE”) ONLY IN CONNECTION WITH THE - BANCOR LIQUIDITY NETWORK AND/OR THE USE OF BNT ("PERMITTED USE"). ANY OTHER USE IS - PROHIBITED UNLESS THE USER SHALL RECEIVE AN EXPLICIT PRIOR WRITTEN APPROVAL FROM - BPROTOCOL FOUNDATION (BANCOR) TO DO SO (PLEASE CONTACT license@bancor.network IN - THIS REGARD), WHICH APPROVAL, IF GIVEN, MAY REQUIRE THE OBTAINMENT OF SEPARATE - LICENSE UNDER A DIFFERENT LICENSING MODEL. USING THIS SOFTWARE NOT IN THE FRAME OF - SUCH PERMITTED USE MAY, AMONG OTHERS, ALSO BREACH PATENT RIGHTS CONCERNING PATENTS - WHICH ARE EMBODIED/INCORPORATED/USED IN THIS SOFTWARE. - -2. ANY SUCH PERMITTED USE SHOULD ALSO COMPLY WITH THE TERMS BELOW. - -3. Redistribution and use in source and binary forms, with or without modification, - are permitted provided that the following conditions are met: -A. Redistributions of source code must retain the above copyright notice, this list - of conditions and the following disclaimer. -B. Redistributions in binary form must reproduce the above copyright notice, this - list of conditions and the following disclaimer in the documentation and/or other - materials provided with the distribution. -C. Neither the name of the copyright holder nor the names of its contributors may be - used to endorse or promote products derived from this software without specific prior - written permission. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY -EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES -OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT -SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT -OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - -EFFECTIVE DATE: THIS LICENSE SHALL APPLY ONLY TO SOFTWARE (OR ANY VERSION THEREOF), -THAT HAS BEEN PUBLISHED AFTER THE DATE AND TIME THIS LICENSE HAS BEEN FIRST PUBLISHED -(“EFFECTIVE DATE”); Any previous versions published prior to the effective date (“Older Versions”) -shall remain licensed under the Apache License, Version 2.0 (the "Older Versions License"); -You may obtain a copy of the Older Version License at http://www.apache.org/licenses/LICENSE-2.0 -you may not use this file except in compliance with the Older Version License. Unless -required by applicable law or agreed to in writing, Older Versions distributed under the -Older Version License are distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS -OF ANY KIND, either express or implied. See the Older Version License for the specific -language governing permissions and limitations under the Older Version License. diff --git a/deploy.py b/deploy.py @@ -1,71 +0,0 @@ -"""Deploys giftable token, and optionally gifts a set amount to all accounts in wallet - -.. moduleauthor:: Louis Holbrook <dev@holbrook.no> -.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 - -""" - -# SPDX-License-Identifier: GPL-3.0-or-later - -# standard imports -import json -import argparse -import logging - -# third-party imports -import web3 - -logging.basicConfig(level=logging.WARNING) -logg = logging.getLogger() - -logging.getLogger('web3').setLevel(logging.WARNING) -logging.getLogger('urllib3').setLevel(logging.WARNING) - -argparser = argparse.ArgumentParser() -argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)') -#argparser.add_argument('-g', '--gift', dest='g', action='store_true', help='If set, tokens will be gifted to all accounts in provider wallet') -argparser.add_argument('-n', '--name', dest='n', default='Giftable Token', type=str, help='Token name') -argparser.add_argument('-s', '--symbol', dest='s', default='GFT', type=str, help='Token symbol') -argparser.add_argument('-d', '--decimals', dest='d', default=18, type=int, help='Token decimals') -argparser.add_argument('-a', '--account', dest='a', action='append', type=str, help='Account to fund') -argparser.add_argument('-v', action='store_true', help='Be verbose') -argparser.add_argument('amount', type=int, help='Initial token supply (will be owned by contract creator)') -args = argparser.parse_args() - -if args.v: - logg.setLevel(logging.DEBUG) - -if __name__ == '__main__': - w3 = web3.Web3(web3.Web3.HTTPProvider(args.p)) - - f = open('./GiftableToken.abi.json', 'r') - abi = json.load(f) - f.close() - - f = open('./GiftableToken.bin', 'r') - bytecode = f.read() - f.close() - - w3.eth.defaultAccount = w3.eth.accounts[0] - - c = w3.eth.contract(abi=abi, bytecode=bytecode) - tx_hash = c.constructor(args.n, args.s, args.d, args.amount).transact() - rcpt = w3.eth.getTransactionReceipt(tx_hash) - address = rcpt.contractAddress - c = w3.eth.contract(abi=abi, address=address) - - logg.debug('construct tx {} address {}'.format(tx_hash.hex(), address)) - balance = c.functions.balanceOf(w3.eth.defaultAccount).call() - logg.info('balance {}: {}'.format(w3.eth.defaultAccount, balance)) - - - if args.a != None: - for a in args.a: - if a == w3.eth.defaultAccount: - continue - tx_hash = c.functions.gift(a, args.amount).transact() - rcpt = w3.eth.getTransactionReceipt(tx_hash) - balance = c.functions.balanceOf(a).call() - logg.info('balance {}: {}'.format(a, balance)) - - print(address) diff --git a/python/CHANGELOG b/python/CHANGELOG @@ -0,0 +1,5 @@ +* 0.0.2 + - Move deploy script to package +* 0.0.1 + - Adapt Bancor ERC20 token contract + - Add deploy-and-gift script diff --git a/VERSION b/python/VERSION diff --git a/python/giftable_erc20_token/runnable/deploy.py b/python/giftable_erc20_token/runnable/deploy.py @@ -0,0 +1,77 @@ +"""Deploys giftable token, and optionally gifts a set amount to all accounts in wallet + +.. moduleauthor:: Louis Holbrook <dev@holbrook.no> +.. pgp:: 0826EDA1702D1E87C6E2875121D2E7BB88C2A746 + +""" + +# SPDX-License-Identifier: GPL-3.0-or-later + +# standard imports +import os +import json +import argparse +import logging + +# third-party imports +import web3 + +logging.basicConfig(level=logging.WARNING) +logg = logging.getLogger() + +logging.getLogger('web3').setLevel(logging.WARNING) +logging.getLogger('urllib3').setLevel(logging.WARNING) + +argparser = argparse.ArgumentParser() +argparser.add_argument('-p', '--provider', dest='p', default='http://localhost:8545', type=str, help='Web3 provider url (http only)') +#argparser.add_argument('-g', '--gift', dest='g', action='store_true', help='If set, tokens will be gifted to all accounts in provider wallet') +argparser.add_argument('-n', '--name', dest='n', default='Giftable Token', type=str, help='Token name') +argparser.add_argument('-s', '--symbol', dest='s', default='GFT', type=str, help='Token symbol') +argparser.add_argument('-d', '--decimals', dest='d', default=18, type=int, help='Token decimals') +argparser.add_argument('-a', '--account', dest='a', action='append', type=str, help='Account to fund') +argparser.add_argument('--contracts-dir', dest='contracts_dir', type=str, default='.', help='Directory containing bytecode and abi') +argparser.add_argument('-v', action='store_true', help='Be verbose') +argparser.add_argument('amount', type=int, help='Initial token supply (will be owned by contract creator)') +args = argparser.parse_args() + +if args.v: + logg.setLevel(logging.DEBUG) + +def main(): + w3 = web3.Web3(web3.Web3.HTTPProvider(args.p)) + + f = open(os.path.join(args.contracts_dir, 'GiftableToken.abi.json'), 'r') + abi = json.load(f) + f.close() + + f = open(os.path.join(args.contracts_dir, 'GiftableToken.bin'), 'r') + bytecode = f.read() + f.close() + + w3.eth.defaultAccount = w3.eth.accounts[0] + + c = w3.eth.contract(abi=abi, bytecode=bytecode) + tx_hash = c.constructor(args.n, args.s, args.d, args.amount).transact() + rcpt = w3.eth.getTransactionReceipt(tx_hash) + address = rcpt.contractAddress + c = w3.eth.contract(abi=abi, address=address) + + logg.debug('construct tx {} address {}'.format(tx_hash.hex(), address)) + balance = c.functions.balanceOf(w3.eth.defaultAccount).call() + logg.info('balance {}: {}'.format(w3.eth.defaultAccount, balance)) + + + if args.a != None: + for a in args.a: + if a == w3.eth.defaultAccount: + continue + tx_hash = c.functions.gift(a, args.amount).transact() + rcpt = w3.eth.getTransactionReceipt(tx_hash) + balance = c.functions.balanceOf(a).call() + logg.info('balance {}: {}'.format(a, balance)) + + print(address) + + +if __name__ == '__main__': + main() diff --git a/requirements.txt b/python/requirements.txt diff --git a/python/setup.cfg b/python/setup.cfg @@ -0,0 +1,32 @@ +[metadata] +name = giftable-erc20-token +version = 0.0.2 +description = Simple ERC20 contract with deployment script that lets any address mint and gift itself tokens. +author = Louis Holbrook +author_email = dev@holbrook.no +url = https://gitlab.com/nolash/giftable-erc-token +keywords = + ethereum +classifiers = + Programming Language :: Python :: 3 + Operating System :: OS Independent + Development Status :: 3 - Alpha + Environment :: No Input/Output (Daemon) + Intended Audience :: Developers + License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+) + Topic :: Internet + #Topic :: Blockchain :: EVM +license = GPL3 +licence_files = + LICENSE + +[options] +python_requires = >= 3.6 +packages = + giftable_erc20_token.runnable +install_requires = + web3==5.12.2 + +[options.entry_points] +console_scripts = + giftable-token-deploy = giftable_erc20_token.runnable.deploy:main diff --git a/python/setup.py b/python/setup.py @@ -0,0 +1,4 @@ +from setuptools import setup + +setup( + ) diff --git a/GiftableToken.abi.json b/solidity/GiftableToken.abi.json diff --git a/GiftableToken.bin b/solidity/GiftableToken.bin diff --git a/solidity/GiftableToken.sol b/solidity/GiftableToken.sol @@ -0,0 +1,160 @@ +pragma solidity ^0.6.12; + +// SPDX-License-Identifier: SEE LICENSE IN LICENSE + +/* + * This is a MOCK token used for DEVELOPMENT PURPOSES ONLY. + * It allows anyone to freely mint tokens with themselves as beneficiaries + * Code is based on the ERC20Token contract from Bancor's contracts-solidity repository + * + * @dev ERC20 Standard Token implementation +*/ + +contract GiftableToken { //is IERC20Token, Utils { + //using SafeMath for uint256; + + string public name; + string public symbol; + uint8 public decimals; + uint256 public totalSupply; + mapping (address => uint256) public balanceOf; + mapping (address => mapping (address => uint256)) public allowance; + + /** + * @dev triggered when tokens are transferred between wallets + * + * @param _from source address + * @param _to target address + * @param _value transfer amount + */ + event Transfer(address indexed _from, address indexed _to, uint256 _value); + + /** + * @dev triggered when a wallet allows another wallet to transfer tokens from on its behalf + * + * @param _owner wallet that approves the allowance + * @param _spender wallet that receives the allowance + * @param _value allowance amount + */ + event Approval(address indexed _owner, address indexed _spender, uint256 _value); + + event ChangedSupply(uint256 _oldTotal, uint256 _newTotal); + + /** + * @dev initializes a new ERC20Token instance + * + * @param _name token name + * @param _symbol token symbol + * @param _decimals decimal points, for display purposes + * @param _initialSupply total supply of token units + */ + constructor(string memory _name, string memory _symbol, uint8 _decimals, uint256 _initialSupply) public { + // validate input + require(bytes(_name).length > 0, "ERR_INVALID_NAME"); + require(bytes(_symbol).length > 0, "ERR_INVALID_SYMBOL"); + + name = _name; + symbol = _symbol; + decimals = _decimals; + totalSupply = _initialSupply; + balanceOf[msg.sender] = _initialSupply; + } + + // validates an address - currently only checks that it isn't null + modifier validAddress(address _address) { + _validAddress(_address); + _; + } + + // error message binary size optimization + function _validAddress(address _address) internal pure { + require(_address != address(0), "ERR_INVALID_ADDRESS"); + } + + + function gift(address _to, uint256 _value) + public + virtual + validAddress(_to) + returns (bool) + { + totalSupply = totalSupply + _value; + uint256 oldSupply = balanceOf[_to]; + balanceOf[_to] = balanceOf[_to] + _value; + emit ChangedSupply(oldSupply, balanceOf[_to]); + emit Transfer(address(0x00), _to, _value); + return true; + } + + /** + * @dev transfers tokens to a given address + * throws on any error rather then return a false flag to minimize user errors + * + * @param _to target address + * @param _value transfer amount + * + * @return true if the transfer was successful, false if it wasn't + */ + function transfer(address _to, uint256 _value) + public + virtual + validAddress(_to) + returns (bool) + { + balanceOf[msg.sender] = balanceOf[msg.sender] - _value; + balanceOf[_to] = balanceOf[_to] + _value; + emit Transfer(msg.sender, _to, _value); + return true; + } + + /** + * @dev transfers tokens to a given address on behalf of another address + * throws on any error rather then return a false flag to minimize user errors + * + * @param _from source address + * @param _to target address + * @param _value transfer amount + * + * @return true if the transfer was successful, false if it wasn't + */ + function transferFrom(address _from, address _to, uint256 _value) + public + virtual + validAddress(_from) + validAddress(_to) + returns (bool) + { + allowance[_from][msg.sender] = allowance[_from][msg.sender] - _value; + balanceOf[_from] = balanceOf[_from] - _value; + balanceOf[_to] = balanceOf[_to] + _value; + emit Transfer(_from, _to, _value); + return true; + } + + /** + * @dev allows another account/contract to transfers tokens on behalf of the caller + * throws on any error rather then return a false flag to minimize user errors + * + * also, to minimize the risk of the approve/transferFrom attack vector + * (see https://docs.google.com/document/d/1YLPtQxZu1UAvO9cZ1O2RPXBbT0mooh4DYKjA_jp-RLM/), approve has to be called twice + * in 2 separate transactions - once to change the allowance to 0 and secondly to change it to the new allowance value + * + * @param _spender approved address + * @param _value allowance amount + * + * @return true if the approval was successful, false if it wasn't + */ + function approve(address _spender, uint256 _value) + public + virtual + validAddress(_spender) + returns (bool) + { + // if the allowance isn't 0, it can only be updated to 0 to prevent an allowance change immediately after withdrawal + require(_value == 0 || allowance[msg.sender][_spender] == 0, "ERR_INVALID_AMOUNT"); + + allowance[msg.sender][_spender] = _value; + emit Approval(msg.sender, _spender, _value); + return true; + } +}