Skip to content

Add PSWAP note to the standard notes #1202

@phklive

Description

@phklive

Feature description

Pioneers are asking for us to add the SWAPP note script to the Miden-base repository.

This would mean:

  • Cleaning up the code
  • Updating the script to make it work with the new protocol rules (Error: MissalignedMemory)
  • Optimizing the code

These would be the steps for this script to become a standard script like P2ID or SWAP.

This is the current code:

use.std::sys
use.std::math::u64
use.miden::note
use.miden::tx
use.miden::contracts::wallets::basic->wallet

# CONSTANTS
# =================================================================================================

const.PRIVATE_NOTE=2
const.PUBLIC_NOTE=1

# Memory Addresses
const.REQUESTED_ASSET_PTR=1
const.SCRIPT_HASH_PTR=3
const.OFFERED_ASSET_PTR=4
const.FILL_AMOUNT_PTR=5

# Memory Addresses for Price Calculation Procedure
const.AMT_TOKENS_A=64
const.AMT_TOKENS_B=65
const.AMT_TOKENS_B_IN=66
const.RATIO=67

const.FACTOR=0x000186A0 # 1e5
const.MAX_U32=0x0000000100000000

# ERRORS
# =================================================================================================

# SWAPP script expects exactly 10 note inputs
const.ERR_SWAP_WRONG_NUMBER_OF_INPUTS=0x00020055

# SWAPP script requires exactly 1 note asset
const.ERR_SWAP_WRONG_NUMBER_OF_ASSETS=0x00020056

# SWAPP script fill amount should be smaller than requested amount
const.ERR_SWAPP_FILL_AMOUNT_EXCEEDS_REQUESTED_AMOUNT=0x00020057

# SWAPP script fill amount should not be zero
const.ERR_SWAPP_FILL_AMOUNT_IS_ZERO=0x00020058

# HELPERS
# =================================================================================================

#! Returns the amount of tokens_a out given an amount of tokens_b
#!
#! Inputs: [tokens_a, tokens_b, tokens_b_in]
#! Outputs: [tokens_a_out]
#!
proc.calculate_partial_exchange
    mem_store.AMT_TOKENS_A
    mem_store.AMT_TOKENS_B
    mem_store.AMT_TOKENS_B_IN

    mem_load.AMT_TOKENS_B mem_load.AMT_TOKENS_A

    gt
    if.true
        mem_load.AMT_TOKENS_B
        u32split

        push.FACTOR
        u32split

        exec.u64::wrapping_mul

        mem_load.AMT_TOKENS_A
        u32split

        exec.u64::div
        push.MAX_U32 mul add

        mem_store.RATIO

        mem_load.AMT_TOKENS_B_IN
        u32split

        push.FACTOR
        u32split

        exec.u64::wrapping_mul

        mem_load.RATIO
        u32split

        exec.u64::div
        push.MAX_U32 mul add

    else
        mem_load.AMT_TOKENS_A
        u32split

        push.FACTOR
        u32split

        exec.u64::wrapping_mul

        mem_load.AMT_TOKENS_B
        u32split

        exec.u64::div

        mem_load.AMT_TOKENS_B_IN
        u32split

        exec.u64::wrapping_mul

        push.FACTOR
        u32split

        exec.u64::div
        push.MAX_U32 mul add

    end
end

#! Creates a new p2id note using inputs from memory
#!
#! Stack: [ASSET]
#! Output: []
proc.create_p2id_note
    # load RECIPIENT
    padw mem_loadw.0
    # => [RECIPIENT, ASSET]

    swapw
    # => [ASSET, RECIPIENT]

    padw mem_loadw.2 drop drop push.0 push.0
    # => [0, 0, execution_hint, tag, ASSET, RECIPIENT]

    drop drop swap
    # => [tag, execution_hint, ASSET, RECIPIENT]

    # we add aux = 0 to the note assuming we don't need it for the second leg of the SWAP
    push.0 swap
    # => [tag, aux, execution_hint, ASSET, RECIPIENT]

    push.PRIVATE_NOTE movdn.2
    # => [tag, aux, note_type, execution_hint, ASSET, RECIPIENT]

    swapw
    # => [ASSET, tag, aux, note_type, execution_hint, RECIPIENT]

    # create a note using inputs
    padw swapdw padw movdnw.2
    # => [tag, aux, note_type, execution_hint, RECIPIENT, PAD(8), ASSET]
    call.wallet::create_note
    # => [note_idx, PAD(15), ASSET]

    swapw dropw movupw.3
    # => [ASSET, note_idx, PAD(11)]

    # move asset to the note
    call.wallet::move_asset_to_note
    # => [ASSET, note_idx, PAD(11)]

    # clean stack
    dropw dropw dropw dropw
    # => []
end

#! Creates a new swapp note using inputs from memory
#!
#! Stack: []
#! Output: []
proc.create_swapp_note
    # load payback recipient
    padw mem_loadw.0
    # => [PAYBACK_RECIPIENT]

    # load remaining requested asset amount
    padw mem_loadw.REQUESTED_ASSET_PTR mem_load.FILL_AMOUNT_PTR dup.4 swap sub swap.4 drop
    # => [REQUESTED_ASSET_REMAINING, PAYBACK_RECIPIENT]

    # load tag and hint
    padw mem_loadw.2
    # => [0, swapp_tag, execution_hint, payback_tag, REQUESTED_ASSET_REMAINING, PAYBACK_RECIPIENT]

    # load script hash
    padw mem_loadw.SCRIPT_HASH_PTR
    # => [SCRIPT_HASH, 0, swapp_tag, execution_hint, payback_tag, REQUESTED_ASSET_REMAINING, PAYBACK_RECIPIENT]

    # compute inputs hash
    mem_storew.83 dropw
    mem_storew.82 dropw
    mem_storew.81 dropw
    mem_storew.80 dropw
    push.16.80
    # => [inputs_ptr, num_inputs]
    exec.note::compute_inputs_hash
    # => [INPUTS_HASH]

    # compute swapp recipient
    padw mem_loadw.SCRIPT_HASH_PTR
    push.1.2.3.4 # SERIAL_NUM
    # => [SERIAL_NUM, SCRIPT_HASH, INPUT_HASH]
    exec.tx::build_recipient_hash
    # => [SWAPP_RECIPIENT]

    # build swapp note inputs
    padw mem_loadw.2 drop movup.2 drop push.0 swap push.PUBLIC_NOTE movdn.2
    padw mem_loadw.OFFERED_ASSET_PTR
    # => [ASSET, tag, aux, note_type, execution_hint, SWAPP_RECIPIENT]

    # create note using inputs
    padw swapdw padw movdnw.2
    # => [tag, aux, note_type, execution_hint, RECIPIENT, PAD(8), ASSET]
    call.wallet::create_note
    # => [note_idx, PAD(15), ASSET]

    swapw dropw movupw.3
    # => [ASSET, note_idx, PAD(11)]

    # move asset to the note
    call.wallet::move_asset_to_note
    # => [ASSET, note_idx, PAD(11)]

    # clean stack
    dropw dropw dropw dropw
    # => []
end

#! Executes a partial swap
#!
#! Stack: []
#! Output: []
proc.partial_swap
    # update the requested asset with the fill amount for note creation
    padw mem_loadw.REQUESTED_ASSET_PTR mem_load.FILL_AMOUNT_PTR swap.4 drop
    # => [REQUESTED_ASSET_FILL]

    # create p2id note (payback note)
    exec.create_p2id_note
    # => []

    # get inputs for partial exchange
    mem_load.FILL_AMOUNT_PTR
    padw mem_loadw.REQUESTED_ASSET_PTR drop drop drop
    padw mem_loadw.OFFERED_ASSET_PTR drop drop drop
    # => [offered_amount, requested_amount, fill_amount]

    # calculate partial exchange
    exec.calculate_partial_exchange
    # => [final_offered_amount]

    # get asset for consumer account
    padw mem_loadw.OFFERED_ASSET_PTR
    # => [OFFERED_ASSET, final_offered_amount]

    # update offered asset (remaining)
    dupw dup.8 movup.4 swap sub movdn.3 mem_storew.OFFERED_ASSET_PTR dropw
    # => [OFFERED_ASSET, final_offered_amount]

    # add assets to consumer account
    movup.4 drop call.wallet::receive_asset dropw
    # => []

    # create swapp note
    exec.create_swapp_note
    # => []
end

#! Executes a full swap
#!
#! Stack: []
#! Output: []
proc.full_swap
    # load the offered asset and add it to the account
    mem_loadw.OFFERED_ASSET_PTR call.wallet::receive_asset dropw
    # => []

    # load requested asset for p2id
    mem_loadw.REQUESTED_ASSET_PTR
    # => [REQUESTED_ASSET]

    # create p2id note
    exec.create_p2id_note
    # => []
end

begin
    # drop the script hash
    # dropw
    # => [NOTE_ARGS]

    # populate memory with note inputs
    push.0 exec.note::get_inputs
    # => [num_inputs, inputs_ptr]

    # make sure the number of inputs is 16
    eq.16 assert.err=ERR_SWAP_WRONG_NUMBER_OF_INPUTS drop
    # => []

    # store OFFERED_ASSET into memory
    push.OFFERED_ASSET_PTR exec.note::get_assets assert.err=ERR_SWAP_WRONG_NUMBER_OF_ASSETS drop
    # => []

    # get fill amount from note args
    drop drop drop
    # => [fill_amount]

    # store fill amount in memory
    dup mem_store.FILL_AMOUNT_PTR
    # => [fill_amount]

    # get fill amount and requested amount
    padw mem_loadw.REQUESTED_ASSET_PTR drop drop drop
    # => [requested_amount, fill_amount]

    # throw an error if fill amount is greater than requested amount
    dup.1 dup.1 lte assert.err=ERR_SWAPP_FILL_AMOUNT_EXCEEDS_REQUESTED_AMOUNT
    # => [requested_amount, fill_amount]

    # throw an error if fill amount is zero
    dup.1 neq.0 assert.err=ERR_SWAPP_FILL_AMOUNT_IS_ZERO
    # => [requested_amount, fill_amount]

    # check if fill amount is equal to requested amount
    eq
    # => [fill_requested_eq]

    if.true
      # perform a full swap
      exec.full_swap
    else
      # perform a partial swap
      exec.partial_swap
    end

    # clean stack
    exec.sys::truncate_stack
    # => []
end

Why is this feature needed?

This would be needed to simplify the integration of SWAPP in the codebase of different Pioneers as partial swaps are a norm in DeFi.

Metadata

Metadata

Assignees

No one assigned

    Labels

    standardsRelated to standard note scripts or account components

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions