Ejemplo n.º 1
0
def _deploy(data: Any, upgrade: bool):
    """
    The contracts initial entry point, on deployment.
    """
    if upgrade:
        return

    if get(DEPLOYED).to_bool():
        abort()

    tx = cast(Transaction, script_container)
    #DEBUG_START
    #custom owner for tests
    if tx.sender is None:
        owner = UInt160(b'\x96Wl\x0e**\x1c!\xc4\xac^\xbd)31\x15%A\x1f@')


#DEBUG_END

    put(DEPLOYED, True)
    put(PAUSED, False)
    put(TOKEN_COUNT, 0)
    put(MINT_FEE, MINT_FEE_ON_DEPLOY)

    auth: List[UInt160] = []
    auth.append(tx.sender)
    serialized = serialize(auth)
    put(AUTH_ADDRESSES, serialized)

    wl: List[UInt160] = []
    wl.append(tx.sender)
    wl_serialized = serialize(auth)
    put(WL_ADDRESSES, wl_serialized)
Ejemplo n.º 2
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    """
    NEP-17 affirms :"if the receiver is a deployed contract, the function MUST call onPayment method on receiver
    contract with the data parameter from transfer AFTER firing the Transfer event. If the receiver doesn't want to
    receive this transfer it MUST call ABORT." Therefore, since this is a smart contract, onPayment must exists.

    There is no guideline as to how it should verify the transaction and it's up to the user to make this verification.

    For instance, this onPayment method checks if this smart contract is receiving NEO or GAS so that it can mint a
    NEP17 token. If it's not receiving a native token, than it will abort.

    :param from_address: the address of the one who is trying to send cryptocurrency to this smart contract
    :type from_address: UInt160
    :param amount: the amount of cryptocurrency that is being sent to the this smart contract
    :type amount: int
    :param data: any pertinent data that might validate the transaction
    :type data: Any
    """
    # Use calling_script_hash to identify if the incoming token is NEO or GAS
    if calling_script_hash == NEO:
        corresponding_amount = amount * AMOUNT_PER_NEO
        mint(from_address, corresponding_amount)
    elif calling_script_hash == GAS:
        corresponding_amount = amount * AMOUNT_PER_GAS
        mint(from_address, corresponding_amount)
    else:
        abort()
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    """
    This contract is currently not accepting any transfers.

    :param from_address: the address of the one who is trying to send cryptocurrency to this smart contract
    :param amount: the amount of cryptocurrency that is being sent to the this smart contract
    :param data: any pertinent data that might validate the transaction
    """
    abort()
Ejemplo n.º 5
0
def mint(user_address: UInt160) -> int:
    """
    Mints AMM tokens, this function will be called by `add_liquidity()`.

    It's best practice to separate `add_liquidity` and `mint` into different contracts, `add_liquidity` should be in a
    Router, while `mint` should be in another smart contract, however, since this is just an example, they both are in
    this same smart contract.

    :param user_address: the address of the user that wants to add liquidity to the pool
    :type user_address: UInt160

    :return: the amount of liquidity tokens that were minted
    :rtype: int

    :raise AssertionError: raised if the liquidity ends up being equal or less than 0
    """
    # reserve_token_a and reserve_token_b are the amount of token_a and token_b tokens that the smart contract has saved in the
    # storage, it's not the actual amount that is in the balance, because the amount is not updated after transferring
    # the token_a and token_b tokens, it will be update only after minting
    reserve_token_a = get(SUPPLY_KEY + TOKEN_A).to_int()
    reserve_token_b = get(SUPPLY_KEY + TOKEN_B).to_int()

    # 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])

    liquidity: int

    if isinstance(balance_token_a, int) and isinstance(balance_token_b, int):
        # amount_token_a and amount_token_b are the quantity of tokens that were deposited in the balance of this smart contract
        amount_token_a = balance_token_a - reserve_token_a
        amount_token_b = balance_token_b - reserve_token_b

        total_supply = get(SUPPLY_KEY).to_int()
        # if there are no AMM tokens, then the quantity of AMM tokens that will be minted are calculated multiplying
        # amount_token_a and amount_token_b
        if total_supply == 0:
            liquidity = sqrt(amount_token_b * amount_token_a)
        # if the pool is not empty then the amount of AMM tokens that will be minted are calculated the way shown below
        else:
            liquidity = min(amount_token_a * total_supply // reserve_token_a, amount_token_b * total_supply // reserve_token_b)

        assert liquidity > 0

        # updates the total supply of AMM tokens
        put(SUPPLY_KEY, total_supply + liquidity)
        # change the amount of liquidity the user has
        put(user_address, get(user_address).to_int() + liquidity)
        on_transfer(None, user_address, liquidity)

        update(balance_token_a, balance_token_b)
        on_mint(user_address, amount_token_a, amount_token_b)

    else:
        abort()

    return liquidity
Ejemplo n.º 6
0
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]
Ejemplo n.º 7
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    """
    :param from_address: the address of the one who is trying to send cryptocurrency to this smart contract
    :type from_address: UInt160
    :param amount: the amount of cryptocurrency that is being sent to the this smart contract
    :type amount: int
    :param data: any pertinent data that might validate the transaction
    :type data: Any
    """
    if calling_script_hash != GAS:
        abort()
    debug(["onNEP17Payment", data])
Ejemplo n.º 8
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    """
    Since this is a deployed contract, transfer will be calling this onPayment method with the data parameter from
    transfer. If someone is doing a not required transfer, then ABORT will be called.

    :param from_address: the address of the one who is trying to transfer cryptocurrency to this smart contract
    :type from_address: UInt160
    :param amount: the amount of cryptocurrency that is being sent to this smart contract
    :type amount: int
    :param data: any pertinent data that may validate the transaction
    :type data: Any

    :raise AssertionError: raised if `from_address` length is not 20
    """
    # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception.
    aux_var = from_address is not None  # TODO: using identity operators or isinstance as a condition of an if is bugged
    if aux_var:
        assert len(from_address) == 20

    # this validation will verify if Neo is trying to mint GAS to this smart contract
    aux_var = from_address is None  # TODO: using identity operators or isinstance as a condition of an if is bugged
    if aux_var and runtime.calling_script_hash == GAS_SCRIPT:
        return

    if not storage.get(NOT_INITIALIZED).to_bool():
        # Used to check if the one who's transferring to this contract is the PERSON_A
        address = storage.get(ADDRESS_PREFIX + PERSON_A)
        # Used to check if PERSON_A already transfer to this smart contract
        funded_crypto = storage.get(FUNDED_PREFIX + PERSON_A).to_int()
        # Used to check if PERSON_A is transferring the correct amount
        amount_crypto = storage.get(AMOUNT_PREFIX + PERSON_A).to_int()
        # Used to check if PERSON_A is transferring the correct token
        token_crypto = storage.get(TOKEN_PREFIX + PERSON_A)
        if (from_address == address and funded_crypto == 0
                and amount == amount_crypto
                and runtime.calling_script_hash == token_crypto):
            storage.put(FUNDED_PREFIX + PERSON_A, amount)
            return
        else:
            # Used to check if the one who's transferring to this contract is the OTHER_PERSON
            address = storage.get(ADDRESS_PREFIX + PERSON_B)
            # Used to check if PERSON_B already transfer to this smart contract
            funded_crypto = storage.get(FUNDED_PREFIX + PERSON_B).to_int()
            # Used to check if PERSON_B is transferring the correct amount
            amount_crypto = storage.get(AMOUNT_PREFIX + PERSON_B).to_int()
            # Used to check if PERSON_B is transferring the correct token
            token_crypto = storage.get(TOKEN_PREFIX + PERSON_B)
            if (from_address == address and funded_crypto == 0
                    and amount == amount_crypto
                    and runtime.calling_script_hash == token_crypto):
                storage.put(FUNDED_PREFIX + PERSON_B, amount)
                return
    abort()
Ejemplo n.º 9
0
def onNEP11Payment(from_address: UInt160, amount: int, tokenId: bytes,
                   data: Any):
    """
    :param from_address: the address of the one who is trying to send cryptocurrency to this smart contract
    :type from_address: UInt160
    :param amount: the amount of cryptocurrency that is being sent to the this smart contract
    :type amount: int
    :param token: the token hash as bytes
    :type token: bytes
    :param data: any pertinent data that might validate the transaction
    :type data: Any
    """
    abort()
Ejemplo n.º 10
0
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()
Ejemplo n.º 11
0
def on_nep11_payment(from_address: UInt160, amount: int, token_id: ByteString,
                     data: Any):
    """
    This contract will not receive another NEP-11 token.

    :param from_address: the address of the one who is trying to send cryptocurrency to this smart contract
    :type from_address: UInt160
    :param amount: the amount of cryptocurrency that is being sent to the this smart contract
    :type amount: int
    :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
    """
    abort()
Ejemplo n.º 12
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    """
    Since this is a deployed contract, transfer will be calling this onPayment method with the data parameter from
    transfer. If someone is doing a not required transfer, then ABORT will be called.

    :param from_address: the address of the one who is trying to transfer cryptocurrency to this smart contract
    :type from_address: UInt160
    :param amount: the amount of cryptocurrency that is being sent to this smart contract
    :type amount: int
    :param data: any pertinent data that may validate the transaction
    :type data: Any

    :raise AssertionError: raised if `from_address` length is not 20
    """
    # the parameters from and to should be 20-byte addresses. If not, this method should throw an exception.
    assert len(from_address) == 20

    if not get(NOT_INITIALIZED).to_bool():
        # Used to check if the one who's transferring to this contract is the OWNER
        address = get(ADDRESS_PREFIX + OWNER)
        # Used to check if OWNER already transfer to this smart contract
        funded_crypto = get(FUNDED_PREFIX + OWNER).to_int()
        # Used to check if OWNER is transferring the correct amount
        amount_crypto = get(AMOUNT_PREFIX + OWNER).to_int()
        # Used to check if OWNER is transferring the correct token
        token_crypto = get(TOKEN_PREFIX + OWNER)
        if (from_address == address and
                funded_crypto == 0 and
                amount == amount_crypto and
                calling_script_hash == token_crypto):
            put(FUNDED_PREFIX + OWNER, amount)
            return
        else:
            # Used to check if the one who's transferring to this contract is the OTHER_PERSON
            address = get(ADDRESS_PREFIX + OTHER_PERSON)
            # Used to check if OTHER_PERSON already transfer to this smart contract
            funded_crypto = get(FUNDED_PREFIX + OTHER_PERSON).to_int()
            # Used to check if OTHER_PERSON is transferring the correct amount
            amount_crypto = get(AMOUNT_PREFIX + OTHER_PERSON).to_int()
            # Used to check if OTHER_PERSON is transferring the correct token
            token_crypto = get(TOKEN_PREFIX + OTHER_PERSON)
            if (from_address == address and
                    funded_crypto == 0 and
                    amount == amount_crypto and
                    calling_script_hash == token_crypto):
                put(FUNDED_PREFIX + OTHER_PERSON, amount)
                return
    abort()
Ejemplo n.º 13
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    """
    If this smart contract receives NEO, it will mint an amount of wrapped NEO

    :param from_address: the address of the one who is trying to send cryptocurrency to this smart contract
    :type from_address: UInt160
    :param amount: the amount of cryptocurrency that is being sent to the this smart contract
    :type amount: int
    :param data: any pertinent data that might validate the transaction
    :type data: Any
    """
    # Use calling_script_hash to identify if the incoming token is NEO
    if calling_script_hash == NEO:
        mint(from_address, amount)
    else:
        abort()
Ejemplo n.º 14
0
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
Ejemplo n.º 15
0
def onNEP17Payment(t_from: UInt160, t_amount: int, data: List[Any]):
    """
    Triggered by a payment to the contract, which creates a new stream

    Args:
        t_from (UInt160): Sender of GAS
        t_amount (int): Amount of GAS sent
        data (List[Any]): Parameters for operations
    """
    if calling_script_hash == GAS:
        assert len(t_from) == 20, 'invalid address'
        assert t_amount > 0, 'no funds transferred'

        p_len = len(data)
        assert p_len > 1, 'incorrect data length'

        p_operation = cast(str, data[0])

        if p_operation == 'createStream':
            assert p_len == 4, 'incorrect arguments to createStream'
            recipient = cast(bytes, data[1])
            start_time = cast(int, data[2])
            stop_time = cast(int, data[3])

            current_time = get_time

            assert len(recipient) == 20, 'invalid recipient scripthash'
            # assert start_time >= current_time, 'start time cannot be in the past'
            assert stop_time > start_time, 'stop time must be greater than start time'

            stream = newStream()

            stream['start'] = start_time
            stream['stop'] = stop_time
            stream['deposit'] = t_amount
            stream['remaining'] = t_amount
            stream['sender'] = base64_encode(t_from)
            stream['recipient'] = base64_encode(recipient)

            saveStream(stream)
            return
    abort()
Ejemplo n.º 16
0
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
Ejemplo n.º 17
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    if data != "Transfer from caller to Ruler" and data != "Transfer from Ruler to caller":
        # just a mechanism to prevent accidental wrong payment
        abort()
Ejemplo n.º 18
0
def main(check: bool) -> int:
    if check:
        abort()
    return 123
Ejemplo n.º 19
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    # accept GAS only
    if calling_script_hash != GAS:
        abort()
Ejemplo n.º 20
0
def add_liquidity(amount_token_a_desired: int, amount_token_b_desired: int,
                  amount_token_a_min: int, amount_token_b_min: int,
                  user_address: UInt160) -> List[int]:
    """
    Adds liquidity to the pool, minting AMM tokens in the process.

    :param amount_token_a_desired: the quantity of token_a that the user wants to send to the liquidity pool
    :type amount_token_a_desired: int
    :param amount_token_b_desired: the quantity of token_b that the user wants to send to the liquidity pool
    :type amount_token_b_desired: int
    :param amount_token_a_min: the minimum quantity of token_a that the user wants to send to the liquidity pool
    :type amount_token_a_min: int
    :param amount_token_b_min: the minimum quantity of token_b that the user wants to send to the liquidity pool
    :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, and at
    index 2 the liquidity created in the mint
    :rtype: list

    :raise AssertionError: raised if the best value of a token is less than the minimum amount desired, if the user
    didn't allow enough money, 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)

    reserve_token_a = storage.get(SUPPLY_KEY + TOKEN_A).to_int()
    reserve_token_b = storage.get(SUPPLY_KEY + TOKEN_B).to_int()
    # If there is no liquidity pool, then the values that will be used to mint and create a pool are the desired ones
    if reserve_token_a == 0 and reserve_token_b == 0:
        amount_token_a = amount_token_a_desired
        amount_token_b = amount_token_b_desired
    # If there is already a liquidity pool, the best value of token_b or token_a will be calculated and then they will be
    # minted and added to the pool
    else:
        amount_token_b_best = quote(amount_token_a_desired, reserve_token_a,
                                    reserve_token_b)
        if amount_token_b_best <= amount_token_b_desired:
            assert amount_token_b_best >= amount_token_b_min
            amount_token_a = amount_token_a_desired
            amount_token_b = amount_token_b_best
        else:
            amount_token_a_best = quote(amount_token_b_desired,
                                        reserve_token_b, reserve_token_a)
            assert amount_token_a_best <= amount_token_a_desired
            assert amount_token_a_best >= amount_token_a_min
            amount_token_a = amount_token_a_best
            amount_token_b = amount_token_b_desired
    amount_allowed_token_a = call_contract(
        UInt160(storage.get(TOKEN_A)), 'allowance',
        [user_address, runtime.executing_script_hash])
    amount_allowed_token_b = call_contract(
        UInt160(storage.get(TOKEN_B)), 'allowance',
        [user_address, runtime.executing_script_hash])
    if isinstance(amount_allowed_token_a, int) and isinstance(
            amount_allowed_token_b, int):
        assert amount_allowed_token_a >= amount_token_a and amount_allowed_token_b >= amount_token_b
    else:
        abort()
    call_contract(UInt160(storage.get(TOKEN_A)), 'transferFrom', [
        runtime.executing_script_hash, user_address,
        runtime.executing_script_hash, amount_token_a, None
    ])
    call_contract(UInt160(storage.get(TOKEN_B)), 'transferFrom', [
        runtime.executing_script_hash, user_address,
        runtime.executing_script_hash, amount_token_b, None
    ])

    # mint() will return the AMM tokens that were minted
    liquidity = mint(user_address)

    return [amount_token_a, amount_token_b, liquidity]
Ejemplo n.º 21
0
def onNEP17Payment(from_address: UInt160, amount: int, data: Any):
    if not runtime.calling_script_hash == storage.get(
            TOKEN_A) and not runtime.calling_script_hash == storage.get(
                TOKEN_B):
        abort()