def remove_liquidity(liquidity: int, amount_token_a_min: int, amount_token_b_min: int, user_address: UInt160) -> List[int]: """ Remove liquidity from the pool, burning the AMM token in the process and giving token_a and token_b back to the user. :param liquidity: how much liquidity will be removed from the liquidity pool :type liquidity: int :param amount_token_a_min: minimum amount of token_a that will be transferred to the user :type amount_token_a_min: int :param amount_token_b_min: minimum amount of token_b that will be transferred to the user :type amount_token_b_min: int :param user_address: the user's address :type user_address: UInt160 :return: at index 0 and 1, the amount of token_a and token_b tokens that were transferred, respectively :rtype: list :raise AssertionError: raised if the user doesn't have enough liquidity, or if the amount of token_a or token_b received after burning the liquidity is not greater than the minimum amount that the user wanted, or if the one calling this function is not the user_address """ # this verification exists thanks to a limitation in the TestEngine, it's returning None when using calling_script_hash # using just `user_address = calling_script hash` should be enough if runtime.calling_script_hash is not None: # TODO: remove the verification when the TestEngine starts sending a calling script hash user_address = runtime.calling_script_hash else: assert runtime.check_witness(user_address) assert runtime.check_witness(user_address) assert liquidity <= balance_of(user_address) amount = burn(liquidity, user_address) # Verify if the amount of token_a and token_b are equal or greater than the minimum amount assert amount[0] >= amount_token_a_min and amount[1] >= amount_token_b_min return amount
def swap_tokens(amount_in: int, amount_out_min: int, token_in: UInt160, user_address: UInt160) -> int: """ Swaps two tokens with a small fee in the process. :param amount_in: the amount of tokens that the user is trying to swap :type amount_in: int :param amount_out_min: the minimum amount of tokens that the user wants to receive :type amount_out_min: int :param token_in: the address of the token that the user is trying to use in the swap :type token_in: UInt160 :param user_address: the user's address :type user_address: UInt160 :return: the amount of tokens that the user received from the swap :rtype: int """ # this verification exists thanks to a limitation in the TestEngine, it's returning None when using calling_script_hash # using just `user_address = calling_script hash` should be enough if calling_script_hash is not None: # TODO: remove the verification when the TestEngine starts sending a calling script hash user_address = calling_script_hash else: assert check_witness(user_address) assert check_witness(user_address) assert token_in == UInt160(get(TOKEN_A)) or token_in == UInt160(get(TOKEN_B)) # Verifies if the user is trying to swap token_a or token_b and set the variables accordingly if token_in == UInt160(get(TOKEN_A)): reserve_token_in = get(SUPPLY_KEY + TOKEN_A).to_int() reserve_token_out = get(SUPPLY_KEY + TOKEN_B).to_int() amount_token_a_in = amount_in amount_token_b_in = 0 else: reserve_token_in = get(SUPPLY_KEY + TOKEN_B).to_int() reserve_token_out = get(SUPPLY_KEY + TOKEN_A).to_int() amount_token_a_in = 0 amount_token_b_in = amount_in # Calculates the amount of tokens the user will receive amount_in_fee = amount_in * (1000 - FEE) amount_out = amount_in_fee * reserve_token_out // (reserve_token_in * 1000 + amount_in_fee) assert amount_out >= amount_out_min # Checks if the user allowed enough tokens amount_allowed = call_contract(token_in, 'allowance', [user_address, executing_script_hash]) if isinstance(amount_allowed, int): assert amount_allowed >= amount_in else: abort() call_contract(token_in, 'transfer_from', [executing_script_hash, user_address, executing_script_hash, amount_in, None]) if amount_token_a_in != 0: swap(0, amount_out, user_address) else: swap(amount_out, 0, user_address) return amount_out
def getFeesMap() -> Iterator: """ How many fees of each kind of token can be collected :return: collateral_token: amount_of_fees.to_bytes() """ administrator = get(ADMINISTRATOR_KEY) if not (check_witness(administrator) or calling_script_hash == administrator): fee_receiver = get( FEE_RECEIVER_KEY ) # if not admin, get fee_receiver. This saves GAS for executing this API assert check_witness( fee_receiver) or calling_script_hash == fee_receiver return find(b'feesMap')
def update_sc(nef_file: bytes, manifest: bytes, data: Any = None): """ Updates the smart contract. In this example there is a bugged method, so, the smart contract will be updated to fix the bug. """ if check_witness(OWNER): ContractManagement.update(nef_file, manifest, data)
def approve(originator: UInt160, to_address: UInt160, amount: int) -> bool: """ Approves the to account to transfer amount tokens from the originator account. :param originator: the address that have the tokens :type originator: UInt160 :param to_address: the address that is authorized to use the tokens :type to_address: UInt160 :param amount: the amount of NEP17 tokens to transfer :type amount: int :return: whether the approval was successful :raise AssertionError: raised if `originator` or `to_address` length is not 20 or if `amount` if less than zero. """ assert len(originator) == 20 and len(to_address) == 20 assert amount >= 0 if not runtime.check_witness(originator): return False if originator == to_address: return False if not is_valid_address(originator) or not is_valid_address(to_address): # one of the address doesn't passed the kyc yet return False if balance_of(originator) < amount: return False storage.put(TRANSFER_ALLOWANCE_PREFIX + originator + to_address, amount) return True
def cancel_pool(pool_id: UInt256): creator_on_storage = get(POOL_OWNER_KEY + pool_id) if len(creator_on_storage) == 0: raise Exception("Pool doesn't exist.") creator = UInt160(creator_on_storage) if not check_witness(creator): raise Exception('No authorization.') if len(get(POOL_RESULT_KEY + pool_id)) > 0: raise Exception('Pool is finished already') executing_contract = executing_script_hash # refund players bets_key_prefix = POOL_BET_KEY + pool_id bet = find(bets_key_prefix) while bet.next(): result_pair: List[bytes] = bet.value storage_key = result_pair[0] account = UInt160(storage_key[len(bets_key_prefix):]) transfer_gas(executing_contract, account, PRICE_IN_GAS) # set result put(POOL_RESULT_KEY + pool_id, serialize('Cancelled by owner'))
def deploy(administrator: UInt160) -> bool: """ administrating access is mostly related to flashLoan """ assert check_witness(administrator) or calling_script_hash == administrator if get(DEPLOYED_KEY) == b'': # not deployed assert get(ADMINISTRATOR_KEY) == b'' put(ADMINISTRATOR_KEY, administrator) put(FEE_RECEIVER_KEY, administrator) put(DEPLOYED_KEY, 1) else: original_administrator = get(ADMINISTRATOR_KEY) assert check_witness(original_administrator ) or calling_script_hash == original_administrator put(ADMINISTRATOR_KEY, administrator) return True
def mint(account: UInt160, meta: bytes, lockedContent: bytes, royalties: bytes, data: Any) -> bytes: """ Mint new token. :param account: the address of the account that is minting token :type account: UInt160 :param meta: the metadata to use for this token :type meta: bytes :param lockedContent: the lock content to use for this token :type lockedContent: bytes :param royalties: the royalties to use for this token :type royalties: bytes :param data: whatever data is pertinent to the mint method :type data: Any :return: tokenId of the token minted :raise AssertionError: raised if mint fee is less than than 0 or if the account does not have enough to pay for it or if the contract is paused or if check witness fails. """ assert not isPaused(), "GhostMarket contract is currently paused" fee = get_mint_fee() assert fee > 0, "Mint fee can't be < 0" assert check_witness(account), "Invalid witness" if fee > 0: # TODO use calling_script_hash instead of account success: bool = call_contract( GAS, 'transfer', [account, executing_script_hash, fee, None]) assert success, "Fee payment failed!" return internal_mint(account, meta, lockedContent, royalties, data)
def is_administrator() -> bool: """ Validates if the invoker has administrative rights :return: whether the contract's invoker is an administrator """ return runtime.check_witness(TOKEN_OWNER)
def burn(account: UInt160, amount: int): """ Burns zNEO tokens. :param account: the address of the account that is pulling out cryptocurrency of this contract :type account: UInt160 :param amount: the amount of gas to be refunded :type amount: int :raise AssertionError: raised if `account` length is not 20, amount is less than than 0 or the account doesn't have enough zNEO to burn """ assert len(account) == 20 assert amount >= 0 if check_witness(account): if amount != 0: current_total_supply = totalSupply() account_balance = balanceOf(account) assert account_balance >= amount put(SUPPLY_KEY, current_total_supply - amount) if account_balance == amount: delete(account) else: put(account, account_balance - amount) on_transfer(account, None, amount) post_transfer(account, None, amount, None, False) call_contract(NEO, 'transfer', [executing_script_hash, account, amount, None])
def withdraw(stream_id: int, amount: int) -> bool: """ Withdraw funds from contract to recipient. Can be triggered by either recipient or sender Args: stream_id (int): Stream ID amount (int): Amount to withdraw Returns: bool: Success or failure """ stream = loadStream(stream_id) recipient = base64_decode(cast(str, stream['recipient'])) sender = base64_decode(cast(str, stream['sender'])) requester = "" if check_witness(recipient): print("Recipient requesting withdrawal") requester = cast(str, stream['recipient']) elif check_witness(sender): # TODO: should sender be able to request an advance to recipient? print("Sender requesting withdrawal to recipient") requester = cast(str, stream['sender']) else: print("Must be sender or recipient to withdraw") abort() remaining = cast(int, stream['remaining']) available = getAmountAvailableForWithdrawal(stream) assert amount > 0, 'nothing to do' assert available >= amount, 'withdrawal amount exceeds available funds' stream['remaining'] = remaining - amount call_contract(GAS, 'transfer', [executing_script_hash, recipient, amount, None]) if cast(int, stream['remaining']) == 0: deleteStream(stream) on_complete(stream_id) else: put(b'streams/' + stream_id.to_bytes(), json_serialize(stream)) on_withdraw(stream_id, requester, amount) return True
def verify() -> bool: """ When this contract address is included in the transaction signature, this method will be triggered as a VerificationTrigger to verify that the signature is correct. For example, this method needs to be called when withdrawing token from the contract. :return: whether the transaction signature is correct """ return check_witness(OWNER)
def method(account: UInt160): """ This method is now working properly. """ # some omitted code # now there is a verification to this method if check_witness(OWNER): storage.put(account, storage.get(account).to_int() + 2 * 10**8) on_transfer(None, account, 2 * 10**8)
def transfer(from_address: UInt160, to_address: UInt160, amount: int, data: Any) -> bool: """ Transfers a specified amount of NEP17 tokens from one account to another If the method succeeds, it must fire the `transfer` event and must return true, even if the amount is 0, or from and to are the same address. :param from_address: the address to transfer from :type from_address: UInt160 :param to_address: the address to transfer to :type to_address: UInt160 :param amount: the amount of NEP17 tokens to transfer :type amount: int :param data: whatever data is pertinent to the onPayment method :type data: Any :return: whether the transfer was successful :raise AssertionError: raised if `from_address` or `to_address` length is not 20 or if `amount` if less than zero. """ # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. assert len(from_address) == 20 and len(to_address) == 20 # the parameter amount must be greater than or equal to 0. If not, this method should throw an exception. assert amount >= 0 # The function MUST return false if the from account balance does not have enough tokens to spend. from_balance = storage.get(from_address).to_int() if from_balance < amount: return False # The function should check whether the from address equals the caller contract hash. # If so, the transfer should be processed; # If not, the function should use the check_witness to verify the transfer. if from_address != runtime.calling_script_hash: if not runtime.check_witness(from_address): return False # skip balance changes if transferring to yourself or transferring 0 cryptocurrency if from_address != to_address and amount != 0: if from_balance == amount: storage.delete(from_address) else: storage.put(from_address, from_balance - amount) to_balance = storage.get(to_address).to_int() storage.put(to_address, to_balance + amount) # if the method succeeds, it must fire the transfer event on_transfer(from_address, to_address, amount) # if the to_address is a smart contract, it must call the contracts onPayment post_transfer(from_address, to_address, amount, data) # and then it must return true return True
def transfer(from_address: bytes, to_address: bytes, amount: int) -> bool: """ Transfers a specified amount of NEP5 tokens from one account to another If the method succeeds, it must fire the `transfer` event and must return true, even if the amount is 0, or from and to are the same address. :param from_address: the address to transfer from :type from_address: bytes :param to_address: the address to transfer to :type to_address: bytes :param amount: the amount of NEP5 tokens to transfer :type amount: int :return: whether the transfer was successful :raise AssertionError: raised if `from_address` or `to_address` length is not 20 or if `amount` if less than zero. """ # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception. assert len(from_address) == 20 and len(to_address) == 20 # the parameter amount must be greater than or equal to 0. If not, this method should throw an exception. assert amount >= 0 # The function MUST return false if the from account balance does not have enough tokens to spend. from_balance = get(from_address).to_int() if from_balance < amount: return False # The function should check whether the from address equals the caller contract hash. # If so, the transfer should be processed; # If not, the function should use the check_witness to verify the transfer. if from_address != calling_script_hash: if not check_witness(from_address): return False # if the `to_address` is a deployed contract, the function should check the payable flag of this contract # TODO: include example when objects are implemented if from_address == to_address: # transfer to self return True if from_balance == amount: delete(from_address) else: put(from_address, from_balance - amount) to_balance = get(to_address).to_int() put(to_address, to_balance + amount) # if the method succeeds, it must fire the transfer event, and must return true on_transfer(from_address, to_address, amount) return True
def cancelStream(stream_id: int) -> bool: """ Cancel stream and make final disbursal of funds from contract to recipient and remainder to sender. Can be triggered by either recipient or sender Args: stream_id (int): Stream ID Returns: bool: Success or failure """ stream = loadStream(stream_id) recipient = base64_decode(cast(str, stream['recipient'])) sender = base64_decode(cast(str, stream['sender'])) requester = '' if check_witness(recipient): requester = cast(str, stream['recipient']) elif check_witness(sender): requester = cast(str, stream['sender']) else: print("Must be sender or recipient to cancel stream") abort() available = getAmountAvailableForWithdrawal(stream) remaining = cast(int, stream['remaining']) - available if available > 0: call_contract(GAS, 'transfer', [executing_script_hash, recipient, available, None]) if remaining > 0: call_contract(GAS, 'transfer', [executing_script_hash, sender, remaining, None]) deleteStream(stream) on_cancel(stream_id, requester) return True
def burnByRuler(account: UInt160, amount: int): assert amount > 0 ruler = get(RULER_KEY) assert calling_script_hash == ruler or check_witness(ruler) remaining_balance = balanceOf(account) assert remaining_balance >= amount, "No enough Token to burn. Maybe you requested an overly large amount." put(account, remaining_balance - amount) total_supply = totalSupply() put(SUPPLY_KEY, total_supply - amount) on_transfer(account, None, amount) # somehow meaningless post_transfer(account, None, amount, "Burn rToken by Ruler") # somehow meaningless
def cancel_player_bet(player: UInt160, bet_id: UInt256): if len(get(POOL_OWNER_KEY + bet_id)) == 0: raise Exception("Pool doesn't exist.") if not check_witness(player): raise Exception('No authorization.') if len(get(POOL_RESULT_KEY + bet_id)) > 0: raise Exception('Pool is finished already') if len(get(POOL_BET_KEY + bet_id + player)) == 0: raise Exception("Player didn't bet on this pool") # 5% fee of the bet for cancelling refund_value = PRICE_IN_GAS - PRICE_IN_GAS * 5 // 100 transfer_gas(executing_script_hash, player, refund_value) delete(POOL_BET_KEY + bet_id + player)
def setActive(_col: UInt160, _paired: UInt160, _expiry: int, _mintRatio: int) -> bool: """ allow a pair's new deposits, by setting the pair's attribute 'active' to True :param _col: pair attribute: address of the collateral token :param _paired: pair attribute: address of the paired token :param _expiry: pair attribute: expiry timestamp in milliseconds :param _mintRatio: pair attribute: mint ratio: 1 collateral token for how many rToken. DECIMAL_BASE applied. :return: True """ administrator = get(ADMINISTRATOR_KEY) assert check_witness(administrator) or calling_script_hash == administrator pair_index = _get_pair_with_assertion(_col, _paired, _expiry, _mintRatio) pair_map.put(gen_pair_key(pair_index, 'active'), True) return True
def deploy() -> bool: """ Deploy the smart contract in the blockchain. :return: whether the deploy was successful. This method must return True only during the smart contract's deploy. """ if not check_witness(OWNER): return False if get(DEPLOYED).to_bool(): return False put(DEPLOYED, True) return True
def deploy() -> bool: """ Initializes OWNER and change values of NOT_INITIALIZED and DEPLOYED when the smart contract is deployed. :return: whether the deploy was successful. This method must return True only during the smart contract's deploy. """ if not check_witness(OWNER): return False if get(DEPLOYED).to_bool(): return False put(OWNER, OWNER) put(NOT_INITIALIZED, True) put(DEPLOYED, True) return True
def getLockedContent(tokenId: bytes) -> bytes: """ Get lock content of a token. :param tokenId: the token to query :type tokenId: ByteString :return: the lock content of this token. :raise AssertionError: raised if witness is not owner """ owner = get_owner_of(tokenId) assert check_witness(owner), "Prohibited access to locked content!" set_locked_view_counter(tokenId) debug(['getLockedContent: ', get_locked_content(tokenId)]) return get_locked_content(tokenId)
def deploy() -> bool: """ Initializes the storage when the smart contract is deployed. :return: whether the deploy was successful. This method must return True only during the smart contract's deploy. """ if not check_witness(OWNER): return False if get(SUPPLY_KEY).to_int() > 0: return False put(SUPPLY_KEY, TOKEN_TOTAL_SUPPLY) put(OWNER, TOKEN_TOTAL_SUPPLY) on_transfer(b'', OWNER, TOKEN_TOTAL_SUPPLY) return True
def isWhitelisted() -> bool: """ Check if the address is allowed to mint without fees. If the address is whitelisted, it's allowed to mint without any fees. :return: whether the address is allowed to mint without fees """ serialized = get(WL_ADDRESSES) auth = cast(list[UInt160], deserialize(serialized)) for addr in auth: if check_witness(addr): debug(["Verification successful", addr]) return True debug(["Verification failed", addr]) return False
def finish_pool(pool_id: UInt256, winner_options: List[str]): creator = get(POOL_OWNER_KEY + pool_id) if len(creator) == 0: raise Exception("Pool doesn't exist.") if not check_witness(creator): raise Exception('No authorization.') if len(get(POOL_RESULT_KEY + pool_id)) > 0: raise Exception('Pool is finished already') if len(winner_options) == 0: raise Exception('At least one winner is required') # validate all winner options are valid options winner_options: List[str] = remove_duplicates(winner_options) pool_options: List[str] = deserialize(get(POOL_OPTIONS_KEY + pool_id)) for option in winner_options: if option not in pool_options: raise Exception('Invalid option for this pool') winners: List[UInt160] = [] # get winner players bets_key_prefix = POOL_BET_KEY + pool_id bet = find(bets_key_prefix) while bet.next(): result_pair = bet.value storage_key = cast(bytes, result_pair[0]) account_bet = cast(str, result_pair[1]) if account_bet in winner_options: address = storage_key[len(bets_key_prefix):] account = UInt160(address) winners.append(account) # distribute the prizes if len(winners) > 0: total_stake = get(POOL_TOTAL_STAKE_KEY + pool_id).to_int() prize_per_winner = total_stake // len(winners) executing_contract = executing_script_hash for winner in winners: transfer_gas(executing_contract, winner, prize_per_winner) # set result put(POOL_RESULT_KEY + pool_id, serialize(winner_options))
def mint(account: UInt160, amount: int): """ Mints DSTONE tokens. :param account: the address of the account that is sending cryptocurrency to this contract :param amount: the amount of gas to be refunded :raise AssertionError: raised if amount is less than than 0 or the check_witness using ADMIN goes wrong """ assert amount >= 0 and check_witness(ADMIN) if amount != 0: current_total_supply = totalSupply() account_balance = balanceOf(account) put(TOTAL_SUPPLY, current_total_supply + amount) put(account, account_balance + amount) on_transfer(None, account, amount) post_transfer(None, account, amount, None)
def verify() -> bool: """ Check if the address is allowed. When this contract address is included in the transaction signature, this method will be triggered as a VerificationTrigger to verify that the signature is correct. For example, this method needs to be called when withdrawing token from the contract. :return: whether the transaction signature is correct """ serialized = get(AUTH_ADDRESSES) auth = cast(list[UInt160], deserialize(serialized)) for addr in auth: if check_witness(addr): debug(["Verification successful", addr]) return True debug(["Verification failed", addr]) return False
def main(operation: str, arg: Any) -> Any: if operation == 'get_trigger': return get_trigger() elif operation == 'check_witness' and isinstance(arg, bytes): return check_witness(arg) elif operation == 'get_time': return get_time elif operation == 'log' and isinstance(arg, str): log(arg) return True elif operation == 'notify': notify(arg) return True return 'unknown'
def transfer(to: UInt160, tokenId: bytes, data: Any) -> bool: """ Transfers the token with id tokenId to address to The parameter to SHOULD be a 20-byte address. If not, this method SHOULD throw an exception. The parameter tokenId SHOULD be a valid NFT. If not, this method SHOULD throw an exception. If the method succeeds, it MUST fire the Transfer event, and MUST return true, even if the token is sent to the owner. If the receiver is a deployed contract, the function MUST call onNEP11Payment method on receiver contract with the data parameter from transfer AFTER firing the Transfer event. The function SHOULD check whether the owner address equals the caller contract hash. If so, the transfer SHOULD be processed; If not, the function SHOULD use the SYSCALL Neo.Runtime.CheckWitness to verify the transfer. If the transfer is not processed, the function SHOULD return false. :param to: the address to transfer to :type to: UInt160 :param tokenId: the token to transfer :type tokenId: ByteString :param data: whatever data is pertinent to the onPayment method :type data: Any :return: whether the transfer was successful :raise AssertionError: raised if `to` length is not 20 or if `tokenId` is not a valid NFT or if the contract is paused. """ assert len(to) == 20, "Incorrect `to` length" assert not isPaused(), "GhostMarket contract is currently paused" token_owner = get_owner_of(tokenId) if not check_witness(token_owner): return False if (token_owner != to): set_balance(token_owner, -1) set_balance(to, 1) set_owner_of(tokenId, to) post_transfer(token_owner, to, tokenId, data) return True
def mint(account: UInt160, amount: int): """ Mints new tokens. This is not a NEP-17 standard method. :param account: the address of the account that is sending cryptocurrency to this contract :type account: UInt160 :param amount: the amount of gas to be refunded :type amount: int :raise AssertionError: raised if amount is less than than 0 """ ruler = get(RULER_KEY) assert calling_script_hash == ruler or check_witness( ruler), "No permission to mint rToken" assert amount > 0, "mint amount <= 0" current_total_supply = totalSupply() account_balance = balanceOf(account) put(SUPPLY_KEY, current_total_supply + amount) put(account, account_balance + amount) on_transfer(None, account, amount) post_transfer(None, account, amount, None)