From 1a8f83f7fcfc058a7e2f493605ecd2a0d0ca9ff6 Mon Sep 17 00:00:00 2001 From: Someone Date: Fri, 5 Mar 2021 17:10:18 +0100 Subject: [PATCH] (FS)InfCoin contract --- InfCoin.py | 337 ++++++++++++++++++++++++++++++++++++++++++++++++++++ InfCoin.yml | 19 +++ compile.py | 4 + 3 files changed, 360 insertions(+) create mode 100644 InfCoin.py create mode 100644 InfCoin.yml create mode 100755 compile.py diff --git a/InfCoin.py b/InfCoin.py new file mode 100644 index 0000000..4d25ea7 --- /dev/null +++ b/InfCoin.py @@ -0,0 +1,337 @@ +#!/bin/false +# not executable. + +""" +Informatics-Coin NEP5 + +DOGE war gestern. BUY FSINF! FOMO! ALL-IN! +""" + +from boa.interop.Neo.Runtime import GetTrigger, CheckWitness +from boa.interop.Neo.Action import RegisterAction +from boa.interop.Neo.TriggerType import Application, Verification +from boa.interop.Neo.Storage import GetContext, Get, Put, Delete, Find +from boa.interop.System.ExecutionEngine import GetCallingScriptHash +from boa.interop.Neo.Blockchain import GetContract +from boa.interop.Neo.Contract import Migrate, Destroy +from boa.interop.Neo.Iterator import IterNext, IterKey, IterValue +from boa.builtins import concat + +# ------------------------------------------- +# TOKEN SETTINGS +# ------------------------------------------- + +# Script hash of the contract owner +OWNER = b'\x0e\x42\x28\xf9\x68\x68\x07\x9d\xaa\xd2\xa5\xa8\x3e\x93\xab\xbe\x5c\xaf\x22\x1d' + +# Name of the Token +TOKEN_NAME = 'Informatics token v2' + +# Symbol of the Token +TOKEN_SYMBOL = 'Inf' + +# Number of decimal places +TOKEN_DECIMALS = 2 + + +# ------------------------------------------- +# Events +# ------------------------------------------- + +OnTransfer = RegisterAction('transfer', 'addr_from', 'addr_to', 'amount') +OnError = RegisterAction('error', 'message') + + +def Main(operation, args): + """ + This is the main entry point for the Smart Contract + + :param operation: the operation to be performed ( eg `balanceOf`, `transfer`, etc) + :type operation: str + :param args: a list of arguments ( which may be empty, but not absent ) + :type args: list + :return: indicating the successful execution of the smart contract + :rtype: bool + """ + + # The trigger determines whether this smart contract is being + # run in 'verification' mode or 'application' + trigger = GetTrigger() + + # 'Verification' mode is used when trying to spend assets ( eg NEO, Gas) + # on behalf of this contract's address + if trigger == Verification(): + + # if the script that sent this is the owner + # we allow the spend + assert CheckWitness(OWNER), 'Unauthorized' + return True + + + # 'Application' mode is the main body of the smart contract + elif trigger == Application(): + + if operation == 'name': + return TOKEN_NAME + + elif operation == 'decimals': + return TOKEN_DECIMALS + + elif operation == 'symbol': + return TOKEN_SYMBOL + + elif operation == 'totalSupply': + return do_total_supply(ctx=GetContext()) + + elif operation == 'balanceOf': + assert len(args) == 1, 'Incorrect argument count' + return do_balance_of(ctx=GetContext(), _account=args[0]) + + elif operation == 'transfer': + assert len(args) == 3, 'Incorrect argument count' + return do_transfer(ctx=GetContext(), _from=args[0], _to=args[1], _amount=args[2], caller=GetCallingScriptHash()) + + # the following are administrative methods + + elif operation == 'owner_mint': + assert len(args) == 2, 'Incorrect argument count' + return owner_mint(ctx=GetContext(), _to=args[0], _amount=args[1]) + + elif operation == 'owner_burn': + assert len(args) == 1, 'Incorrect argument count' + return owner_burn(ctx=GetContext(), _amount=args[0]) + + elif operation == 'owner_migrate_contract': + assert len(args) == 9, 'Incorrect argument count' + return owner_migrate(ctx=GetContext(), _script=args[0], _param_list=args[1], _return_type=args[2], _properties=args[3], _name=args[4], _version=args[5], _author=args[6], _email=args[7], _description=args[8], caller=GetCallingScriptHash()) + + elif operation == 'owner_destroy_contract': + assert CheckWitness(OWNER), 'Unauthorized' + return Destroy() + + elif operation == 'owner_dump': + return owner_dump(ctx=GetContext()) + + assert False, 'Unknown operation' + + + +def do_total_supply(ctx): + """ + Method to return the total supply of this coin + + :return: the total supply of this coin + :rtype: int + + """ + + res = Get(ctx, "totalSupply") + return res + + + +def do_balance_of(ctx, _account): + """ + Method to return the current balance of an address + + :param _account: the account address to retrieve the balance for + :type _account: bytearray + + :return: the current balance of an address + :rtype: int + + """ + + assert len(_account) == 20, "Invalid address" + return Get(ctx, _account) + + + +def do_transfer(ctx, _from, _to, _amount, caller): + """ + Method to transfer NEP5 tokens of a specified _amount from one account to another + + :param _from: the address to transfer from + :type _from: bytearray + :param _to: the address to transfer to + :type _to: bytearray + :param _amount: the _amount of NEP5 tokens to transfer + :type _amount: int + :param caller: the scripthash of the calling script + :type caller: bytearray + + :return: whether the transfer was successful + :rtype: bool + + """ + + assert _amount > 0, "Invalid amount" + assert len(_from) == 20, "Invalid from address" + assert len(_to) == 20, "Invalid to address" + assert CheckWitnessOrCaller(_from, caller), "Transfer unauthorized" + + if _from == _to: + print("Transfer to self") + return True + + from_balance = Get(ctx, _from) + assert from_balance >= _amount, "Insufficient funds" + + if from_balance == _amount: + print("Removing all funds!") + Delete(ctx, _from) + + else: + difference = from_balance - _amount + Put(ctx, _from, difference) + + to_value = Get(ctx, _to) + to_total = to_value + _amount + Put(ctx, _to, to_total) + + OnTransfer(_from, _to, _amount) + return True + + + +def owner_mint(ctx, _to, _amount): + """ + Method to mint tokens + + :return: whether the token minting was successful + :rtype: bool + + """ + + assert CheckWitness(OWNER), 'Unauthorized' + assert len(_to) == 20, "Invalid to address" + assert _amount > 0, "Invalid amount" + + balance = Get(ctx, _to) +# to discuss for neo3. +# if balance == 0: +# Put(ctx, concat(_to,b'--OM'), 1) + new_total = balance + _amount + Put(ctx, _to, new_total) + + total_supply = Get(ctx, 'totalSupply') + new_total_supply = total_supply + _amount + Put(ctx, 'totalSupply', new_total_supply) + + OnTransfer(None, _to, _amount) + return True + + + +def owner_burn(ctx, _amount): + """ + Method to burn tokens + + :param _amount: the _amount of NEP5 tokens to transfer + :type _amount: int + + :return: whether the token burning was successful + :rtype: bool + + """ + + assert CheckWitness(OWNER), 'Unauthorized' + assert _amount > 0, "Invalid amount" + + from_val = Get(ctx, OWNER) + assert from_val >= _amount, "Insufficient funds" + + if from_val == _amount: + print("Removing all funds!") + Delete(ctx, OWNER) + + else: + difference = from_val - _amount + Put(ctx, OWNER, difference) + + total_supply = Get(ctx, 'totalSupply') + new_total_supply = total_supply - _amount + Put(ctx, 'totalSupply', new_total_supply) + + + OnTransfer(OWNER, None, _amount) + return True + + + +def owner_migrate(ctx, _script, _param_list, _return_type, _properties, _name, _version, _author, _email, _description, caller): + """ + Migrate this contract to a new version + + :param args: list of arguments containing migrating contract info + :return: Contract + """ + assert CheckWitness(OWNER), 'Unauthorized' + + # once migrated, funds held at the contract address are no + # longer retrievable. make sure to transfer all funds + # before doing anything. + current_contract_balance = Get(ctx, caller) + if current_contract_balance > 0: + print("Cannot migrate yet. Please transfer all neo/gas and tokens from contract address") + return False + + Migrate(_script, _param_list, _return_type, _properties, _name, _version, _author, _email, _description) + return True + + + +def owner_dump(ctx): + """ + Dump contract store + + :param args: list of arguments containing migrating contract info + :return: Contract + """ + assert CheckWitness(OWNER), 'Unauthorized' + + items = [] + result_iter = Find(ctx, "") + while result_iter.IterNext(): + items.append([result_iter.IterKey(), result_iter.IterValue()]) + return items + + + +def CheckWitnessOrCaller(scripthash, caller): + """ + Method to check if the transaction is signed by a private key or is the scripthash of a contract that is authorized to perform the requested function for its own address only + + :param scripthash: the scripthash to be checked + :type scripthash: bytearray + :param caller: the scripthash of the calling script + :type caller: bytearray + + :return: whether the scripthash is authorized + :rtype: bool + + """ + + if GetContract(scripthash): + if scripthash == caller: + return True # a contract can spend its own funds + else: + # deny third-party contracts from transferring + # tokens of a user even with the user signature + # (this will break ability of some DEX to list the token) + return False + + return CheckWitness(scripthash) + + + +def AssertionError(msg): + """ + Method to throw an exception (required by assert) - will log a notification to the neo-cli ApplicationLog to aid in post-transaction troubleshooting and analysis + + :param msg: error message + :type msg: string + """ + + OnError(msg) + raise Exception(msg) diff --git a/InfCoin.yml b/InfCoin.yml new file mode 100644 index 0000000..6c82843 --- /dev/null +++ b/InfCoin.yml @@ -0,0 +1,19 @@ +version: 1 +project: + author: | + Someone + email: | + someone@fsinf.at + version: | + v2 + name: | + Inf-Coin NEO2Testnet + description: | + Informatics token v2 + hasstorage: true + hasdynamicinvocation: true + ispayable: false + returntype: ByteArray + parameters: + - String + - Array diff --git a/compile.py b/compile.py new file mode 100755 index 0000000..9c0febe --- /dev/null +++ b/compile.py @@ -0,0 +1,4 @@ +#!/usr/bin/env python3 + +from boa.compiler import Compiler +Compiler.load_and_save('./InfCoin.py') -- 2.43.0