def post_transfer(from_address: Union[UInt160, None], to_address: Union[UInt160, None], amount: int, data: Any): """ Checks if the one receiving DWHEAT tokens is a smart contract and if it's one the onPayment method will be called :param from_address: the address of the sender :param to_address: the address of the receiver :param amount: the amount of cryptocurrency that is being sent :param data: any pertinent data that might validate the transaction """ if not isinstance(to_address, None): contract = get_contract(to_address) if not isinstance(contract, None): call_contract(to_address, 'onNEP17Payment', [from_address, amount, data])
def _sendAmtPostFeesOptionalAccrue(invoker: UInt160, _token: UInt160, _amount: int, _feeRate: int, _accrue: bool) -> int: """ Transfer collateral or paired tokens to invoker. Fees are not considered in _amount, but is always wiped in this method. If _accrue == True, accrue fees on the transferred tokens. Make sure you do not accrue fees if the fees have been accrued before. (Usually for paired tokens) And make sure you accrue fees if the fees have not been accrued before. (Usually for collateral) :param invoker: The wallet to receive the transferred tokens :param _token: the address of transferred token :param _amount: the amount of transferred token. Fees will be wiped out from _amount :param _feeRate: A fee is charged for all the tokens given out from Ruler. DECIMAL_BASE applied :param _accrue: Whether fees are accrued. If fees have already been accrued before, set this to False :return: the actual amount of token paid to invoker """ fees = _amount * _feeRate // DECIMAL_BASE amount_to_pay = _amount - fees assert call_contract(_token, "transfer", [ executing_script_hash, invoker, amount_to_pay, "collect paired token from ruler with rcTokens" ]) if _accrue: original_fee = feesMap.get(_token).to_int() feesMap.put(_token, original_fee + fees) return amount_to_pay
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 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 calling_mint(address: UInt160, from_address: UInt160, data: Any) -> Any: """ Transfer NEO to an account :return: whether the transfer was successful. :rtype: bool """ return call_contract(address, 'mint', [from_address, data])
def post_transfer(from_address: Union[UInt160, None], to_address: Union[UInt160, None], amount: int, data: Any): """ Checks if the one receiving NEP17 tokens is a smart contract and if it's one the onPayment method will be called :param from_address: the address of the sender :type from_address: UInt160 :param to_address: the address of the receiver :type to_address: UInt160 :param amount: the amount of cryptocurrency that is being sent :type amount: int :param data: any pertinent data that might validate the transaction :type data: Any """ if not isinstance(to_address, None): # TODO: change to 'is not None' when `is` semantic is implemented contract = get_contract(to_address) if not isinstance(contract, None): # TODO: change to 'is not None' when `is` semantic is implemented call_contract(to_address, 'onPayment', [from_address, amount, data])
def claim_bounty(owner: UInt160, index: str, material: str) -> bool: unclaimed = get_unclaimed(index, material) # dynamic invoke to `mint` on relevant contract res: bool = call_contract(MATERIAL_WOOD, "mint", (owner, unclaimed)) if res == True: put(CLAIM_TIMESTAMP + index.to_bytes() + material.to_bytes(), get_time) return True
def getFeeBalance() -> Any: """ Get mint fees balance. :return: balance of mint fees. """ balance = call_contract(GAS, 'balanceOf', [executing_script_hash]) debug(['getFeeBalance: ', balance]) return balance
def burn(liquidity: int, user_address: UInt160) -> List[int]: """ Burns AMM tokens, this function will be called by `remove_liquidity()`. It's best practice to separate `remove_liquidity` and `mint` into different contracts, `add_liquidity` should be in a Router, while `burn` should be in another smart contract, however, since this is just an example, they both are in this same smart contract. :param liquidity: how many AMM tokens will be removed from the pool :type liquidity: int :param user_address: the address of the user that wants to remove liquidity of the pool :type user_address: int :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 amount_token_a or amount_token_b is equal or less than zero """ # balance_token_a and balance_token_b are the actual amount that are in the balance of this smart contract balance_token_a = call_contract(UInt160(get(TOKEN_A)), 'balanceOf', [executing_script_hash]) balance_token_b = call_contract(UInt160(get(TOKEN_B)), 'balanceOf', [executing_script_hash]) amount_token_a: int = 0 amount_token_b: int = 0 if isinstance(balance_token_a, int) and isinstance(balance_token_b, int): total_supply = get(SUPPLY_KEY).to_int() # amount_token_a and amount_token_b are the amount that will be transferred to the user after burning the liquidity amount_token_a = liquidity * balance_token_a // total_supply amount_token_b = liquidity * balance_token_b // total_supply assert amount_token_a > 0 and amount_token_b > 0 # changing the user balance after burning the liquidity put(user_address, balanceOf(user_address) - liquidity) # update the amount of AMM tokens in this smart contract put(SUPPLY_KEY, total_supply - liquidity) on_transfer(user_address, None, liquidity) call_contract(UInt160(get(TOKEN_A)), 'transfer', [executing_script_hash, user_address, amount_token_a, None]) call_contract(UInt160(get(TOKEN_B)), 'transfer', [executing_script_hash, user_address, amount_token_b, None]) balance_token_a = call_contract(UInt160(get(TOKEN_A)), 'balanceOf', [executing_script_hash]) balance_token_b = call_contract(UInt160(get(TOKEN_B)), 'balanceOf', [executing_script_hash]) if isinstance(balance_token_a, int) and isinstance(balance_token_b, int): update(balance_token_a, balance_token_b) on_burn(user_address, amount_token_a, amount_token_b) else: abort() else: abort() return [amount_token_a, amount_token_b]
def _getColAmtFromRTokenAmt(_rTokenAmt: int, _col: UInt160, _rToken: UInt160, _mintRatio: int) -> int: """ Compute the amount of collateral of a pair, given the amount of rTokens :param _rTokenAmt: How many rTokens are given :param _col: pair attribute: address of the collateral token :param _rToken: pair attribute: address of the rToken :param _mintRatio: pair attribute: mint ratio: 1 collateral token for how many rToken. DECIMAL_BASE applied. :return: the amount of collateral """ r_token_decimals = call_contract(_rToken, "decimals") collateral_token_decimals = call_contract(_col, "decimals") delta_decimals = cast(int, collateral_token_decimals) - cast( int, r_token_decimals) if delta_decimals >= 0: return _rTokenAmt * (10**delta_decimals) * DECIMAL_BASE // _mintRatio else: delta_decimals = -delta_decimals return _rTokenAmt * DECIMAL_BASE // (_mintRatio * 10**delta_decimals)
def post_transfer(from_address: Union[UInt160, None], to_address: Union[UInt160, None], amount: int, data: Any): """ Checks if the one receiving NEP17 tokens is a smart contract and if it's one the onPayment method will be called :param from_address: the address of the sender :type from_address: UInt160 :param to_address: the address of the receiver :type to_address: UInt160 :param amount: the amount of cryptocurrency that is being sent :type amount: int :param data: any pertinent data that might validate the transaction :type data: Any """ if to_address is not None: contract = ContractManagement.get_contract(to_address) if contract is not None: call_contract(to_address, 'onNEP17Payment', [from_address, amount, data])
def transfer_gas(from_address: UInt160, to_address: UInt160, amount: UInt160, data: Any) -> Any: """ Transfer GAS to an account :return: whether the transfer was successful. :rtype: bool """ return call_contract(GAS, 'transfer', [from_address, to_address, amount, data])
def _getRTokenAmtFromColAmt(_colAmt: int, _col: UInt160, _paired: UInt160, _mintRatio: int) -> int: """ Compute the amount of rTokens of a pair, given the amount of collateral :param _colAmt: How many collateral tokens are given :param _col: pair attribute: address of the collateral token :param _paired: pair attribute: address of the paired token :param _mintRatio: pair attribute: mint ratio: 1 collateral token for how many rToken. DECIMAL_BASE applied. :return: the amount of rTokens """ # potential optimization: save the number of decimals as an attribute of pair? parity_token_decimals = call_contract(_paired, "decimals") collateral_token_decimals = call_contract(_col, "decimals") delta_decimals = cast(int, parity_token_decimals) - cast( int, collateral_token_decimals) if delta_decimals >= 0: return _colAmt * _mintRatio * (10**delta_decimals) // DECIMAL_BASE else: delta_decimals = -delta_decimals return _colAmt * _mintRatio // (DECIMAL_BASE * 10**delta_decimals)
def withdrawFee(account: UInt160) -> bool: """ Withdraw mint fees. :param account: the address of the account that is withdrawing fees :type account: UInt160 :return: whether the transaction was successful. :emits MintFeeWithdrawn: on success emits MintFeeWithdrawn :raise AssertionError: raised if witness is not verified. """ assert verify(), '`acccount` is not allowed for withdrawFee' current_balance = cast( int, call_contract(GAS, 'balanceOf', [executing_script_hash])) on_withdraw_mint_fee(account, current_balance) debug(['withdrawFee: ', current_balance]) status: bool = call_contract( GAS, 'transfer', [executing_script_hash, account, current_balance, None]) return status
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 repay(invoker: UInt160, _col: UInt160, _paired: UInt160, _expiry: int, _mintRatio: int, _rrTokenAmt: int) -> int: """ Borrower repays the contract with rrTokens and paired tokens before expiry, and borrower receives collateral. NO fees charged on collateral :param invoker: The wallet that pays rrTokens and paired tokens to get collateral. :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. :param _rrTokenAmt: How many rTokens and paired tokens will be paid to get collateral :return: the amount of collateral paid back """ pair = _get_pair_with_assertion(_col, _paired, _expiry, _mintRatio) assert get_pair_attribute(pair, "expiry").to_int() > time, "Ruler: pair expired" assert call_contract(_paired, "transfer", [ invoker, executing_script_hash, _rrTokenAmt, "Transfer from caller to Ruler" ]) rrToken_address = cast(UInt160, get_pair_attribute(pair, "rrToken")) call_contract(rrToken_address, "burnByRuler", [invoker, _rrTokenAmt]) feesMap.put( _paired, feesMap.get(_paired).to_int() + _rrTokenAmt * get_pair_attribute(pair, 'feeRate').to_int() // DECIMAL_BASE) rcToken_address = cast(UInt160, get_pair_attribute(pair, "rcToken")) colAmountToPay = _getColAmtFromRTokenAmt( _rrTokenAmt, _col, rcToken_address, get_pair_attribute(pair, "mintRatio").to_int()) assert call_contract(_col, "transfer", [ executing_script_hash, invoker, colAmountToPay, "Transfer from Ruler to caller" ]) return colAmountToPay
def post_transfer(from_address: Union[UInt160, None], to_address: Union[UInt160, None], token_id: ByteString, data: Any): """ Checks if the one receiving NEP11 tokens is a smart contract and if it's one the onPayment method will be called. :param from_address: the address of the sender :type from_address: UInt160 :param to_address: the address of the receiver :type to_address: UInt160 :param token_id: the id of the token that is being sent :type token_id: ByteString :param data: any pertinent data that might validate the transaction :type data: Any """ # the transfer event will be fired on_transfer(from_address, to_address, 1, token_id) if not isinstance(to_address, None): contract = ContractManagement.get_contract(to_address) if not isinstance(contract, None): call_contract(to_address, 'onNEP11Payment', [from_address, 1, token_id, data])
def mmDeposit(invoker: UInt160, _col: UInt160, _paired: UInt160, _expiry: int, _mintRatio: int, _rcTokenAmt: int) -> bool: """ Special API: market make deposit Deposit paired token into the contract to receive a same amount of rcTokens immediately. I do not know the purpose of using this method. Trying to receive collateral instead of paired token? :param invoker: The wallet address that will pay paired tokens to get rcTokens :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 rTokens. DECIMAL_BASE applied. :param _rcTokenAmt: how many paired tokens to be deposited for the same amount of rcTokens :return: True (since the amount of minted rcToken always equals the amount of paired token paid) """ pair_index = _get_pair_with_assertion(_col, _paired, _expiry, _mintRatio) _validateDepositInputs(pair_index) assert call_contract(_paired, "transfer", [ invoker, executing_script_hash, _rcTokenAmt, "Transfer from caller to Ruler" ]), "Failed to transfer paired token from caller to Ruler" rcToken_address = cast(UInt160, get_pair_attribute(pair_index, 'rcToken')) call_contract(rcToken_address, "mint", [invoker, _rcTokenAmt]) feeRate = get_pair_attribute(pair_index, 'feeRate').to_int() feesMap.put( _paired, feesMap.get(_paired).to_int() + _rcTokenAmt * feeRate // DECIMAL_BASE) colAmount = _getColAmtFromRTokenAmt(_rcTokenAmt, _col, rcToken_address, _mintRatio) colTotal_key = gen_pair_key(pair_index, 'colTotal') colTotal = pair_map.get(colTotal_key).to_int() pair_map.put(colTotal_key, colTotal + colAmount) return True
def collectFee(token: UInt160) -> int: """ Withdraw the fee of a single token. No need to check witness? Because the fee is always given to the fee receiver. :param token: the fee in this token is withdrawn :return: the amount of fee withdrawn """ # no need to check witness fee_receiver = get(FEE_RECEIVER_KEY) amount = feesMap.get(token).to_int() if amount > 0: feesMap.put(token, 0) assert call_contract(token, 'transfer', [ executing_script_hash, fee_receiver, amount, bytearray(b'collect ') + token ]) return amount
def swap(amount_token_a_out: int, amount_token_b_out: int, user_address: UInt160): """ Swaps one token with another, this function will be called by `swap_tokens`. It's best practice to separate `swap_tokens` and `swap` into different contracts, `swap_tokens` should be in a Router, while `swap` should be in another smart contract, however, since this is just an example, they both are in this same smart contract. :param amount_token_a_out: the amount of token_a that will be given to the user :type amount_token_a_out: int :param amount_token_b_out: the amount of token_b that will be given to the user :type amount_token_b_out: int :param user_address: the user's address :type user_address: UInt160 :raise AssertionError: raised if the amount_token_a_out and amount_token_b_out are equal or less than zero, if the amount the user is going to receive is greater than the amount in the reserve, if the smart contract didn't receive any token from the user, or if the constant k after the swap ends up being lower than the one at the beginning """ assert amount_token_a_out > 0 or amount_token_b_out > 0 reserve_token_a = get(SUPPLY_KEY + TOKEN_A).to_int() reserve_token_b = get(SUPPLY_KEY + TOKEN_B).to_int() assert amount_token_a_out < reserve_token_a and amount_token_b_out < reserve_token_b if amount_token_a_out > 0: call_contract(UInt160(get(TOKEN_A)), 'transfer', [executing_script_hash, user_address, amount_token_a_out, None]) if amount_token_b_out > 0: call_contract(UInt160(get(TOKEN_B)), 'transfer', [executing_script_hash, user_address, amount_token_b_out, None]) # balance_token_a and balance_token_b are the actual amount that are in the balance of this smart contract balance_token_a = call_contract(UInt160(get(TOKEN_A)), 'balanceOf', [executing_script_hash]) balance_token_b = call_contract(UInt160(get(TOKEN_B)), 'balanceOf', [executing_script_hash]) if isinstance(balance_token_a, int) and isinstance(balance_token_b, int): amount_token_a_in = balance_token_a - (reserve_token_a - amount_token_a_out) if balance_token_a > reserve_token_a - amount_token_a_out else 0 amount_token_b_in = balance_token_b - (reserve_token_b - amount_token_b_out) if balance_token_b > reserve_token_b - amount_token_b_out else 0 assert amount_token_a_in > 0 or amount_token_b_in > 0 balance_token_a_adjusted = balance_token_a * 1000 - amount_token_a_in * FEE balance_token_b_adjusted = balance_token_b * 1000 - amount_token_b_in * FEE constant_k_new = balance_token_a_adjusted * balance_token_b_adjusted constant_k_old = reserve_token_a * 1000 * reserve_token_b * 1000 assert constant_k_new >= constant_k_old update(balance_token_a, balance_token_b) on_swap(user_address, amount_token_a_in, amount_token_b_in, amount_token_a_out, amount_token_b_out) else: abort()
def collectFees() -> bool: """ Collect all the fees No need to check witness? Because the fee is always given to the fee receiver. :return: True """ iterator = find(b'feesMap') fee_receiver = get(FEE_RECEIVER_KEY) while iterator.next(): token_bytes = cast(bytes, iterator.value[0]) token_bytes = token_bytes[ 7:] # cut 'feesMap' at the beginning of the bytes token = cast(UInt160, token_bytes) fee_amount = cast(bytes, iterator.value[1]).to_int() if fee_amount > 0: feesMap.put(token, 0) assert call_contract(token, 'transfer', [ executing_script_hash, fee_receiver, fee_amount, 'Collect Fees' ]) return True
def main(scripthash: UInt160, method: str, args: list) -> Any: return contract.call_contract(scripthash, method, args)
def calling_approve(address: UInt160, spender: UInt160, amount: int) -> Any: return call_contract(address, 'approve', [spender, amount])
def transfer(cls, from_address: UInt160, to_address: UInt160, amount: int, data: Any) -> bool: return cast( bool, call_contract(cls._hash, 'transfer', [from_address, to_address, amount, data]))
def balance_of(cls, account: UInt160) -> int: return cast(int, call_contract(cls._hash, 'balanceOf', [account]))
def total_supply(cls) -> int: return cast(int, call_contract(cls._hash, 'totalSupply'))
def decimals(cls) -> int: return cast(int, call_contract(cls._hash, 'decimals'))
def symbol(cls) -> str: return cast(str, call_contract(cls._hash, 'symbol'))
def Main(scripthash: bytes, method: str, args: list): call_contract(scripthash, method, args)
def Main(scripthash: bytes): call_contract(scripthash)