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 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
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
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
def Main(operation, args): """ :param operation: str The name of the operation to perform :param args: list A list of arguments along with the operation :return: bytearray: The result of the operation """ trigger = GetTrigger() token = Token() # This is used in the Verification portion of the contract # To determine whether a transfer of system assets (NEO/Gas) involving # This contract's address can proceed if trigger == Verification: storage = StorageAPI() owner = storage.get(token.owner_key) if owner: # If the invoker is the owner of this contract, proceed if CheckWitness(owner): return True else: # check original_owner if not deployed yet (i.e. no owner in storage) if CheckWitness(token.original_owner): return True # Otherwise, we need to lookup the assets and determine # If attachments of assets is ok attachments = get_asset_attachments() # type:Attachments crowdsale = Crowdsale() # the exchange will be allowed if the number of tokens to convert to is greater than zero. # zero indicates that there is a reason this contribution will not be allowed return crowdsale.check_and_calculate_tokens(token, attachments, storage, True) > 0 elif trigger == Application: if operation != None: nep = NEP5Handler() for op in nep.get_methods(): if operation == op: return nep.handle_nep51(operation, args, token) if operation == 'deploy': return deploy(token) if operation == 'circulation': storage = StorageAPI() return token.get_circulation(storage) sale = Crowdsale() if operation == 'mintTokens': return sale.exchange(token) if operation == 'crowdsale_register': return sale.kyc_register(args, token) if operation == 'crowdsale_deregister': return sale.kyc_deregister(args, token) if operation == 'crowdsale_status': return sale.kyc_status(args) if operation == 'crowdsale_available': return token.crowdsale_available_amount() if operation == 'transfer_presale_tokens': return sale.transfer_presale_tokens(token, args) if operation == 'transfer_team_tokens': return sale.transfer_team_tokens(token, args) if operation == 'transfer_company_tokens': return sale.transfer_company_tokens(token, args) if operation == 'mint_rewards_tokens': return sale.mint_rewards_tokens(token, args) if operation == 'change_owner': new_owner = args[0] return change_owner(token, new_owner) if operation == 'accept_owner': return accept_owner(token) if operation == 'cancel_change_owner': return cancel_change_owner(token) if operation == 'start_public_sale': return sale.start_public_sale(token) if operation == 'pause_sale': return pause_sale(token) if operation == 'resume_sale': return resume_sale(token) return 'unknown operation' return False