Example #1
0
    def do_approve(self, storage: StorageAPI, t_owner, t_spender, amount):

        if not CheckWitness(t_owner):
            print("Incorrect permission")
            return False

        if amount < 0:
            print("Negative amount")
            return False

        from_balance = storage.get(t_owner)

        # cannot approve an amount that is
        # currently greater than the from balance
        if from_balance >= amount:

            approval_key = concat(t_owner, t_spender)

            if len(approval_key) != 40:
                return False

            if amount == 0:
                storage.delete(approval_key)
            else:
                storage.put(approval_key, amount)

            OnApprove(t_owner, t_spender, amount)

            return True

        return False
Example #2
0
    def add_to_circulation(self, amount: int, storage: StorageAPI):
        """
        Adds an amount of token to circulation

        :param amount: int the amount to add to circulation
        :param storage: StorageAPI A StorageAPI object for storage interaction
        """
        current_supply = storage.get(self.in_circulation_key)

        current_supply += amount

        storage.put(self.in_circulation_key, current_supply)
Example #3
0
    def start_public_sale(self, token: Token):
        storage = StorageAPI()

        owner = storage.get(token.owner_key)
        if not CheckWitness(owner):
            return False

        pub_sale_start_block = storage.get(self.pub_sale_start_block_key)

        if pub_sale_start_block:
            print("can't start the public sale twice")
            return False

        height = GetHeight()

        storage.put(self.pub_sale_start_block_key, height)

        return True
def pause_sale(token: Token):
    """
    Pause the sale
    :param token: Token The token of the sale to pause
    :return:
        bool: Whether the operation was successful
    """
    storage = StorageAPI()

    owner = storage.get(token.owner_key)
    if not CheckWitness(owner):
        print("Must be owner to pause sale")
        return False

    # mark the sale as paused
    storage.put(token.sale_paused_key, True)

    return True
Example #5
0
    def kyc_register(self, args, token: Token):
        """

        :param args:list a list of addresses to register
        :param token: Token A token object with your ICO settings
        :return:
            int: The number of addresses registered for KYC
        """
        ok_count = 0

        storage = StorageAPI()

        owner = storage.get(token.owner_key)
        if CheckWitness(owner):

            for addresses in args:

                # bl: allowing multiple addresses to be encoded into a single parameter. this works around
                # the limitation of list arguments only supporting at most 16 elements
                # can be passed in as follows:
                # testinvoke {script_hash} crowdsale_register [bytearray(b'\x015\x829\x8cm6f\xb3\xac\xcc\xcas\x1dw\x06\xbc\xd2\x9co#\xba\'\x03\xc52c\xe8\xd6\xe5"\xdc2\x2039\xdc\xd8\xee\xe9')]
                # note that neo-python doesn't like spaces in the strings, so convert any spaces to the hex equivalent: '\x20'
                addr_length = len(addresses)

                # addresses are 20 bytes, so the length must be a multiple of 20 or else it's invalid!
                if (addr_length % 20) != 0:
                    continue

                addr_count = addr_length / 20

                i = 0
                while i < addr_count:
                    start = i * 20
                    address = substr(addresses, start, 20)

                    kyc_storage_key = concat(self.kyc_key, address)
                    storage.put(kyc_storage_key, True)

                    OnKYCRegister(address)
                    ok_count += 1
                    i += 1

        return ok_count
Example #6
0
    def exchange(self, token: Token):
        """
        Make a token sale contribution to exchange NEO for NRVE
        :param token: Token The token object with NEP5/sale settings
        :return:
            bool: Whether the exchange was successful
        """

        attachments = get_asset_attachments()  # type:  Attachments

        storage = StorageAPI()

        # this looks up whether the exchange can proceed
        tokens = self.check_and_calculate_tokens(token, attachments, storage,
                                                 False)

        if tokens <= 0:
            print("Cannot exchange value")
            # This should only happen in the case that there are a lot of TX on the final
            # block before the total amount is reached.  An amount of TX will get through
            # the verification phase because the total amount cannot be updated during that phase
            # because of this, there should be a process in place to manually refund tokens
            if attachments.neo_attached > 0:
                OnRefund(attachments.sender_addr, attachments.neo_attached)
            return False

        self.mint_tokens(token, attachments.receiver_addr,
                         attachments.sender_addr, tokens, storage)

        # update the total sold during the public sale
        public_sale_sold = storage.get(token.public_sale_sold_key)

        public_sale_sold += tokens

        storage.put(token.public_sale_sold_key, public_sale_sold)

        # track contributions as a separate event for token sale account page transaction updates
        OnContribution(attachments.sender_addr, attachments.neo_attached,
                       tokens)

        return True
def deploy(token: Token):
    """

    :param token: Token The token to deploy
    :return:
        bool: Whether the operation was successful
    """
    if not CheckWitness(token.original_owner):
        print("Must be original_owner to deploy")
        return False

    storage = StorageAPI()

    # can only deploy once, so if we already have an owner, no-op
    if storage.get(token.owner_key):
        return False

    # mark the current owner, which can be changed later
    storage.put(token.owner_key, token.original_owner)

    return True
Example #8
0
    def do_transfer(self, storage: StorageAPI, t_from, t_to, amount):

        if amount <= 0:
            return False

        if len(t_to) != 20:
            return False

        if CheckWitness(t_from):

            if t_from == t_to:
                print("transfer to self!")
                return True

            from_val = storage.get(t_from)

            if from_val < amount:
                print("insufficient funds")
                return False

            if from_val == amount:
                storage.delete(t_from)

            else:
                difference = from_val - amount
                storage.put(t_from, difference)

            to_value = storage.get(t_to)

            to_total = to_value + amount

            storage.put(t_to, to_total)

            OnTransfer(t_from, t_to, amount)

            return True
        else:
            print("from address is not the tx sender")

        return False
Example #9
0
    def mint_tokens(self, token: Token, from_address, to_address, tokens,
                    storage: StorageAPI):
        """
        Mint tokens for an address
        :param token: the token being minted
        :param from_address: the address from which the tokens are being minted (should always be the contract address)
        :param to_address: the address to transfer the minted tokens to
        :param tokens: the number of tokens to mint
        :param storage: StorageAPI
        """
        # lookup the current balance of the address
        current_balance = storage.get(to_address)

        # add it to the exchanged tokens and persist in storage
        new_total = tokens + current_balance
        storage.put(to_address, new_total)

        # update the in circulation amount
        token.add_to_circulation(tokens, storage)

        # dispatch transfer event
        OnTransfer(from_address, to_address, tokens)
def change_owner(token: Token, new_owner):
    """
    Record a transfer request to a new owner. The new owner must accept the request via accept_owner
    :param token: Token The token to change the owner for
    :param new_owner: the new owner of the contract
    :return:
        bool: Whether the operation was successful
    """
    storage = StorageAPI()

    owner = storage.get(token.owner_key)
    if not owner:
        print("Must deploy before changing owner")
        return False

    if not CheckWitness(owner):
        print("Must be owner to change owner")
        return False

    # setup the new owner. will need to be accepted by the new owner in order to finalize
    storage.put(token.new_owner_key, new_owner)

    return True
def accept_owner(token: Token):
    """
    Change the owner of this smart contract who will be able to perform protected operations
    :param token: Token The token to change the owner for
    :return:
        bool: Whether the operation was successful
    """
    storage = StorageAPI()

    new_owner = storage.get(token.new_owner_key)
    if not new_owner:
        print("Must call change_owner before accept_owner")
        return False

    if not CheckWitness(new_owner):
        print("Must be new_owner to accept owner")
        return False

    # setup the new owner.
    storage.put(token.owner_key, new_owner)
    # now that it's accepted, make the change official by removing the pending new_owner
    storage.delete(token.new_owner_key)

    return True
Example #12
0
    def do_transfer_from(self, storage: StorageAPI, t_from, t_to, amount):

        if amount <= 0:
            return False

        available_key = concat(t_from, t_to)

        if len(available_key) != 40:
            return False

        available_to_to_addr = storage.get(available_key)

        if available_to_to_addr < amount:
            print("Insufficient funds approved")
            return False

        from_balance = storage.get(t_from)

        if from_balance < amount:
            print("Insufficient tokens in from balance")
            return False

        to_balance = storage.get(t_to)

        new_from_balance = from_balance - amount

        new_to_balance = to_balance + amount

        storage.put(t_to, new_to_balance)
        storage.put(t_from, new_from_balance)

        print("transfer complete")

        new_allowance = available_to_to_addr - amount

        if new_allowance == 0:
            print("removing all balance")
            storage.delete(available_key)
        else:
            print("updating allowance to new allowance")
            storage.put(available_key, new_allowance)

        OnTransfer(t_from, t_to, amount)

        return True
Example #13
0
    def mint_rewards_tokens(self, token: Token, args):
        """
        Mint tokens for the rewards pool
        :param token: the token being minted for the rewards pool
        :param args: the address and number of tokens to mint
        :return: True if successful
        """
        storage = StorageAPI()

        owner = storage.get(token.owner_key)
        if not CheckWitness(owner):
            return False

        if len(args) != 2:
            return False

        address = args[0]
        tokens = args[1]

        if len(address) != 20:
            return False
        if tokens <= 0:
            return False

        now = get_now()

        # no minting rewards tokens until after the token sale ends
        if now < self.sale_end:
            print("can't mint_rewards_tokens before sale ends")
            return False

        rewards_fund_tokens_distributed = storage.get(
            self.rewards_fund_token_distribution_key)

        rewards_fund_tokens_distributed += tokens

        # don't allow more than the max tokens to be distributed
        if rewards_fund_tokens_distributed > self.rewards_fund_tokens_max:
            print("can't exceed mint_rewards_tokens limit")
            return False

        storage.put(self.rewards_fund_token_distribution_key,
                    rewards_fund_tokens_distributed)

        attachments = get_asset_attachments()  # type:  Attachments

        #self.mint_tokens(token, attachments.receiver_addr, address, tokens, storage)
        from_address = attachments.receiver_addr
        to_address = address

        # bl: the following is an exact copy of the mint_tokens function. invoking self.mint_tokens will break the
        # execution of this method due to a neo-boa compiler issue. this results in a lot of code duplication,
        # but it's preferable to the alternative of a broken smart contract. refer: https://github.com/CityOfZion/neo-boa/issues/40

        # lookup the current balance of the address
        current_balance = storage.get(to_address)

        # add it to the exchanged tokens and persist in storage
        new_total = tokens + current_balance
        storage.put(to_address, new_total)

        # update the in circulation amount
        token.add_to_circulation(tokens, storage)

        # dispatch transfer event
        OnTransfer(from_address, to_address, tokens)

        return True
Example #14
0
    def transfer_company_tokens(self, token: Token, args):
        """
        Transfer company tokens to a wallet address according to the 2-year company token vesting schedule
        :param token: the token being minted for the company
        :param args: the address and number of tokens to mint
        :return: True if successful
        """
        storage = StorageAPI()

        owner = storage.get(token.owner_key)
        if not CheckWitness(owner):
            return False

        if len(args) != 2:
            return False

        address = args[0]
        tokens = args[1]

        if len(address) != 20:
            return False
        if tokens <= 0:
            return False

        now = get_now()

        seconds_in_year = 31536000

        # no company token distribution until after the ICO ends
        if now < self.sale_end:
            print("can't transfer_company_tokens before sale ends")
            return False

        # in the first year, allow 50% token distribution
        if now < (self.sale_end + seconds_in_year):
            max_token_distribution = self.company_tokens_max * 5 / 10
        # in the second year, allow 75% total token distribution
        elif now < (self.sale_end + (2 * seconds_in_year)):
            max_token_distribution = self.company_tokens_max * 75 / 100
        # beyond the second year, allow 100% total token distribution
        else:
            max_token_distribution = self.company_tokens_max

        company_tokens_distributed = storage.get(
            self.company_token_distribution_key)

        company_tokens_distributed += tokens

        # don't allow more than the max tokens to be distributed
        if company_tokens_distributed > max_token_distribution:
            print("can't exceed transfer_company_tokens vesting limit")
            return False

        storage.put(self.company_token_distribution_key,
                    company_tokens_distributed)

        attachments = get_asset_attachments()  # type:  Attachments

        #self.mint_tokens(token, attachments.receiver_addr, address, tokens, storage)
        from_address = attachments.receiver_addr
        to_address = address

        # bl: the following is an exact copy of the mint_tokens function. invoking self.mint_tokens will break the
        # execution of this method due to a neo-boa compiler issue. this results in a lot of code duplication,
        # but it's preferable to the alternative of a broken smart contract. refer: https://github.com/CityOfZion/neo-boa/issues/40

        # lookup the current balance of the address
        current_balance = storage.get(to_address)

        # add it to the exchanged tokens and persist in storage
        new_total = tokens + current_balance
        storage.put(to_address, new_total)

        # update the in circulation amount
        token.add_to_circulation(tokens, storage)

        # dispatch transfer event
        OnTransfer(from_address, to_address, tokens)

        return True
Example #15
0
    def transfer_presale_tokens(self, token: Token, args):
        """
        Transfer pre-sale tokens to a wallet address according to the 800 NEO minimum and 3,000 NEO maximum individual limits
        :param token: the token being minted for the team
        :param args: the address and number of neo for the contribution
        :return: True if successful
        """
        storage = StorageAPI()

        owner = storage.get(token.owner_key)
        if not CheckWitness(owner):
            return False

        if len(args) != 2:
            return False

        address = args[0]
        neo = args[1]

        if len(address) != 20:
            return False
        if neo <= 0:
            return False

        presale_minted = storage.get(token.presale_minted_key)

        max_neo_remaining = (self.presale_token_limit -
                             presale_minted) / self.presale_tokens_per_neo

        # calculate the number of tokens based on the neo value supplied
        tokens = neo * self.presale_tokens_per_neo

        new_presale_minted = presale_minted + tokens

        # don't allow more than the presale token limit to be distributed
        if new_presale_minted > self.presale_token_limit:
            print("transfer would exceed presale token limit")
            return False

        # protect against scenarios where we could deadlock the contract by making
        # a mistake in our manual distribution. allow amount smaller than 800 NEO
        # if we're down to fewer than 800 NEO remaining to close the pre-sale
        if neo < self.presale_minimum and self.presale_minimum < max_neo_remaining:
            print("insufficient presale contribution")
            return False

        # check if they have already exchanged in the limited round
        phase_key = concat(self.presale_phase_key, address)

        total_amount_contributed = storage.get(phase_key)

        # add on the amount of the new contribution
        total_amount_contributed += neo

        if total_amount_contributed > self.presale_individual_limit:
            print("transfer would exceed presale individual limit")
            return False

        storage.put(phase_key, total_amount_contributed)

        attachments = get_asset_attachments()  # type:  Attachments

        #self.mint_tokens(token, attachments.receiver_addr, address, tokens, storage)
        from_address = attachments.receiver_addr
        to_address = address

        # bl: the following is an exact copy of the mint_tokens function (except for the OnPreSaleMint). invoking self.mint_tokens will break the
        # execution of this method due to a neo-boa compiler issue. this results in a lot of code duplication,
        # but it's preferable to the alternative of a broken smart contract. refer: https://github.com/CityOfZion/neo-boa/issues/40

        # lookup the current balance of the address
        current_balance = storage.get(to_address)

        # add it to the exchanged tokens and persist in storage
        new_total = tokens + current_balance
        storage.put(to_address, new_total)

        # update the in circulation amount
        token.add_to_circulation(tokens, storage)

        # update the total pre-sale tokens that have been minted
        storage.put(token.presale_minted_key, new_presale_minted)

        # track pre-sale mint as a separate event for easier tracking
        OnPreSaleMint(to_address, neo, tokens)

        # dispatch transfer event
        OnTransfer(from_address, to_address, tokens)

        return True
Example #16
0
    def calculate_tokens(self, token: Token, neo_attached: int, address,
                         verify_only: bool):
        """
        Perform custom token exchange calculations here.

        :param token: Token The token settings for the sale
        :param neo_attached: int Number of NEO to convert to tokens
        :param address: bytearray The address to mint the tokens to
        :param verify_only: boolean to indicate whether we are only verifying the tx.
               when verifying, we will skip any put side effects.
        :return:
            int: Total amount of tokens to distribute, or 0 if this isn't a valid contribution
        """
        height = GetHeight()

        storage = StorageAPI()

        pub_sale_start_block = storage.get(self.pub_sale_start_block_key)

        if not pub_sale_start_block:
            print("main sale not started")
            return 0
        elif height > (pub_sale_start_block + self.sale_blocks):
            print("crowdsale ended")
            return 0
        elif height > (pub_sale_start_block + (2 * self.blocks_per_day)):
            # if we are in main sale, post-day 2, then any contribution is allowed
            phase_key_prefix = None
            individual_limit = -1
            tokens_per_neo = self.sale_tokens_per_neo
        elif height > (pub_sale_start_block + self.blocks_per_day):
            phase_key_prefix = self.day2_phase_key
            individual_limit = self.day2_individual_limit
            tokens_per_neo = self.day2_tokens_per_neo
        else:
            phase_key_prefix = self.day1_phase_key
            individual_limit = self.day1_individual_limit
            tokens_per_neo = self.day1_tokens_per_neo

        # this value will always be an int value, but is converted to float by the division. cast back to int, which should always be safe.
        # note that the neo_attached has a value mirroring GAS. so, even though NEO technically doesn't have any decimals of precision,
        # the value still needs to be divided to get down to the whole NEO unit
        tokens = neo_attached / 100000000 * tokens_per_neo

        public_sale_sold = storage.get(token.public_sale_sold_key)

        new_public_sale_sold = public_sale_sold + tokens

        if new_public_sale_sold > token.public_sale_token_limit:
            print("purchase would exceed token sale limit")
            return 0

        # in the main sale, all contributions are allowed, up to the tokens in circulation limit defined above
        if individual_limit <= 0:
            # note that we do not need to store the contribution at this point since there is no limit
            return tokens

        if neo_attached <= individual_limit:

            # check if they have already exchanged in the limited round
            phase_key = concat(phase_key_prefix, address)

            total_amount_contributed = storage.get(phase_key)

            # add on the amount of the new contribution
            total_amount_contributed += neo_attached

            # if the total amount is less than the individual limit, they're good!
            if total_amount_contributed <= individual_limit:
                # note that this method can be invoked during the Verification trigger, so we have the
                # verify_only param to avoid the Storage.Put during the read-only Verification trigger.
                # this works around a "method Neo.Storage.Put not found in ->" error in InteropService.py
                # since Verification is read-only and thus uses a StateReader, not a StateMachine
                if not verify_only:
                    storage.put(phase_key, total_amount_contributed)
                return tokens

            print("contribution limit exceeded in round")
            return 0

        print("too much for limited round")

        return 0