lib/order/teal/templates/ASA_Delegate_v7.template.teal.js

// ///////////////////////////
// Alexander Trefonas      //
// 7/9/2021                //
// Copyright Algodev Inc   //
// All Rights Reserved.    //
// ///////////////////////////

/**
 * # Algodex Sell Orderbook Delegate
 *
 * > Escrow limit order to SELL ASAs. These limit orders contain a minimum algo balance and then the ASA to sell.
 *
 * Stateful smart contract, TODO: Describe in detail with
 * {@link makeApplicationCreateTxn}
 *
 *
 * @type {string}
 * @memberOf module:teal
 */
module.exports = `

#pragma version 4

////////////////////////////////////
// ASA ESCROW (SELL ORDER) VERSION 4
//   Escrow limit order to SELL ASAs. These limit orders contain a minimum algo balance and then the ASA to sell.
//////////////////////////////////////

/////////////////////////////////
// CHECKS THAT APPLY TO ALL TXNS
////////////////////////////////

    global GroupSize
    int 5
    <=
    assert
    txn Fee
    global MinTxnFee
    ==
    assert

    int 0
    store 9

    checkAllTxns: // This is basically a for loop that checks all transactions

    load 9
    gtxns RekeyTo
    global ZeroAddress
    ==
    assert
    load 9
    gtxns AssetSender
    global ZeroAddress
    ==
    assert

    load 9
    int 1
    +
    store 9
    load 9
    global GroupSize
    <
    bnz checkAllTxns

///////////////////////////////////////////////////////////////////////
// OPEN - ORDER BOOK OPT IN & REGISTRATION
//   Placing an ASA Escrow Order. The escrow opts into the order book.
///////////////////////////////////////////////////////////////////////

    // TXN 0 - SELLER TO ESCROW:    pay transaction into escrow
    // TXN 1 - ESCROW TO ORDERBOOK: application opt in
    // TXN 2 - ESCROW TO ESCROW:    asset opt in
    // TXN 3 - SELLER TO ESCROW:    asset transfer

    global GroupSize
    int 4
    ==
    gtxn 1 ApplicationID
    int <orderBookId> //stateful contract app id. orderBookId
    ==
    &&
    gtxn 0 Sender // escrow address
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 0 Receiver
    txn Sender // escrow address
    ==
    &&
    gtxn 1 Sender
    txn Sender // escrow address
    ==
    &&
    gtxn 2 Sender
    txn Sender // escrow address
    ==
    &&
    gtxn 2 Sender
    gtxn 2 AssetReceiver
    ==
    &&
    txn Sender // escrow address
    gtxn 3 AssetReceiver
    ==
    &&
    gtxn 3 Sender // escrow address
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 0 TypeEnum
    int pay
    ==
    &&
    gtxn 1 TypeEnum
    int appl
    ==
    &&
    gtxn 2 TypeEnum
    int axfer
    ==
    &&
    gtxn 3 TypeEnum
    int axfer
    ==
    &&
    gtxn 0 Amount // amount should be higher than 0.5 algo
    int 500000
    >= 
    && 
    gtxn 1 Amount
    int 0
    ==
    &&
    gtxn 2 AssetAmount // this is an optin
    int 0
    ==
    &&
    gtxn 3 AssetAmount // this is an optin
    int 1 // Needs to put at least one ASA into the account
    >=
    &&
    int <assetid>  // asset id to trade for
    gtxn 2 XferAsset
    ==
    &&
    int <assetid>  // asset id to trade for
    gtxn 3 XferAsset
    ==
    &&
    gtxn 0 CloseRemainderTo
    global ZeroAddress
    ==
    &&
    gtxn 1 CloseRemainderTo
    global ZeroAddress
    ==
    &&
    gtxn 2 CloseRemainderTo
    global ZeroAddress
    ==
    &&
    gtxn 3 CloseRemainderTo
    global ZeroAddress
    ==
    &&
    gtxn 0 OnCompletion
    int NoOp
    ==
    &&
    gtxn 1 OnCompletion
    int OptIn
    ==
    &&
    gtxn 2 OnCompletion
    int NoOp
    ==
    &&
    gtxn 3 OnCompletion
    int NoOp
    ==
    &&
    gtxn 0 AssetCloseTo
    global ZeroAddress
    ==
    &&
    gtxn 1 AssetCloseTo
    global ZeroAddress
    ==
    &&
    gtxn 2 AssetCloseTo
    global ZeroAddress
    ==
    &&
    gtxn 3 AssetCloseTo
    global ZeroAddress
    ==
    &&
    
    bz notOptInOrOrderReg 
    // If the above are not true, this is a closeout (without order execution) or a trade execution
    // Otherwise it is Opt-in so return early
    int 1
    
    return

////////////////////////////////////////////////////////
/// CLOSE ORDER
//    Cancelling an order and refunding the amounts
////////////////////////////////////////////////////////

    // TXN 0 - ESCROW TO ORDERBOOK: app call to close order
    // TXN 1 - ESCROW TO SELLER: asset transfer (escrow to owner)
    // TXN 2 - ESCROW TO SELLER: pay transaction (from escrow to owner)
    // TXN 3 - SELLER TO SELLER: proof of ownership pay transaction (owner to owner)

    notOptInOrOrderReg:
    // Check for close out transaction (without execution)
    global GroupSize
    int 4
    ==
    gtxn 0 ApplicationID
    int <orderBookId> //stateful contract app id. orderBookId
    ==
    &&
    gtxn 0 CloseRemainderTo
    global ZeroAddress // This is an app call so should be set to 0 address
    ==
    &&
    gtxn 0 AssetCloseTo
    global ZeroAddress // should not matter, but add just in case
    ==
    &&
    gtxn 1 CloseRemainderTo
    global ZeroAddress  // should not matter, but add just in case. We are closing ASAs not algos
    ==
    &&
    gtxn 1 AssetCloseTo // asset close transaction
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 2 CloseRemainderTo // close algo minimum balance transaction
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 2 AssetCloseTo // should not matter. Third transaction is for closing algos
    global ZeroAddress // contractWriterAddr
    ==
    &&
    gtxn 3 CloseRemainderTo // should not matter. Fourth transaction is a proof of ownership
    global ZeroAddress
    ==
    &&
    gtxn 3 AssetCloseTo // should not matter. Fourth transaction is a proof of ownership
    global ZeroAddress
    ==
    &&
    gtxn 0 Sender
    txn Sender // escrow address
    ==
    &&
    gtxn 1 Sender
    txn Sender // escrow address   
    ==
    &&
    gtxn 2 Sender
    txn Sender // escrow address 
    ==
    &&
    gtxn 3 Sender // proof the close is coming from sender
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 0 Receiver
    global ZeroAddress // This is an app call, so no receiver
    ==
    &&
    gtxn 1 AssetReceiver
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 2 Receiver // 0 funds are being transferred, but still expected to be set
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 3 Receiver // 0 funds are being transferred, but still expected to be set
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 0 TypeEnum
    int appl
    ==
    &&
    gtxn 1 TypeEnum
    int axfer
    ==
    &&
    gtxn 2 TypeEnum
    int pay
    ==
    &&
    gtxn 3 TypeEnum
    int pay
    ==
    &&
    gtxn 0 Amount // Should not matter since this is an app call
    int 0 //Check all the funds are being sent to the CloseRemainderTo address
    ==
    &&
    gtxn 1 Amount
    int 0 // Should not matter since this is an ASA transfer
    ==
    &&
    gtxn 1 AssetAmount
    int 0 //Check all the funds are being sent to the CloseRemainderTo address
    ==
    &&
    gtxn 2 Amount
    int 0 //Check all the funds are being sent to the CloseRemainderTo address
    ==
    &&
    gtxn 2 AssetAmount
    int 0 //Should not matter since this is a pay transaction
    ==
    &&
    gtxn 3 Amount
    int 0 //This is a proof of ownership so the amount should be 0
    ==
    &&
    gtxn 3 AssetAmount
    int 0 //Should not matter since this is a pay transaction
    ==
    &&
    gtxn 0 OnCompletion
    int ClearState // App Call OnCompletion needs to be ClearState (OptOut), which will clear from the order book
    ==
    &&
    gtxn 1 OnCompletion
    int NoOp
    ==
    &&
    gtxn 2 OnCompletion
    int NoOp
    ==
    &&
    gtxn 3 OnCompletion
    int NoOp
    ==
    &&

    bz anyExecute // If the above are not true, this is a pay transaction. Otherwise it is CloseOut so ret success
    
    int 1
    return

///////////////////////////////////////////////////////
// ANY EXECUTE (with close or not)
//   Preamble for any order execution transaction
///////////////////////////////////////////////////////
    // TXN 0   - ESCROW TO ORDERBOOK: Application call to execute
    // TXN 1   - BUYER TO SELLER:     Pay transaction (from buyer/executor to escrow owner)
    // TXN 2   - BUYER TO BUYER:      (Optional) asset opt-in transaction (for buyer/executor)
    // TXN 2/3 - ESCROW TO BUYER:     Asset transfer (from escrow to buyer/executor)
    // TXN 3/4 - DEPENDS: don't check this here - different on whether closing or not
    //              Either: Pay transaction for fee refund (from buyer/executor to escrow)
    //              OR:     Pay transaction to close out to escrow owner (from escrow to escrow owner)

anyExecute:

///////////////////////
// OPTIONAL ASSET-OPT IN CHECK
// First check if we have the optional asset opt-in transaction for the buyer's wallet
// This happens for both execute and execute_with_close
// If this exists, it's the third transaction (gtxn 2).
    gtxn 2 TypeEnum
    int axfer
    ==
    gtxn 2 AssetAmount
    int 0
    ==
    &&
    gtxn 2 Sender
    gtxn 2 AssetReceiver
    ==
    &&
    gtxn 2 XferAsset
    int <assetid> // asset id to trade for
    ==
    &&
    gtxn 2 AssetCloseTo
    global ZeroAddress
    ==
    &&
    gtxn 2 Sender
    txn Sender // Sender must come from the user's wallet, not the escrow
    != // should *not* be originating from escrow
    &&
    store 0 //this will store the next transaction offset depending if opt in exists


    load 0
    int 2
    +
    store 2 // store offset of transaction 2, depending on if opt-in exists

    load 0
    int 3
    +
    store 3 // store offset of transaction 3, depending on if opt-in exists

/// END OPTIONAL ASSET OPT IN CHECK

// NOW CHECK TRANSACTIONS

    int 4
    load 0
    +
    global GroupSize // GroupSize must be 4 or 5 according to whether optional ASA opt in exists
    ==
    assert

    gtxn 0 Sender
    txn Sender // escrow account
    ==
    gtxn 1 Sender
    txn Sender // escrow account
    != // should *not* be originating from escrow
    &&
    load 2 
    gtxns Sender // Asset transfer comes from escrow account
    txn Sender // Escrow account
    ==
    &&
    gtxn 1 Sender // The buyer
    load 2 
    gtxns AssetReceiver // Asset transfer is sent to the buyer
    ==
    &&
    gtxn 1 Receiver
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    gtxn 0 TypeEnum // First Transaction must be a call to a stateful contract
    int appl
    ==
    &&
    gtxn 1 TypeEnum // The second transaction must be a payment transaction
    int pay
    ==
    &&
    gtxn 2 TypeEnum // third transaction must be an asset opt-in/transfer
    int axfer
    ==
    &&
    load 2
    gtxns TypeEnum //The next transaction must be an asset transfer
    int axfer
    ==
    &&
    load 3
    gtxns TypeEnum     // The last transaction must be a payment transfer
    int pay
    ==
    &&
    gtxn 0 ApplicationID // The specific App ID must be called
    int <orderBookId> //stateful contract app id. orderBookId
    ==
    &&
    gtxn 0 CloseRemainderTo
    global ZeroAddress
    == 
    &&
    gtxn 1 CloseRemainderTo
    global ZeroAddress
    ==
    &&
    load 2
    gtxns CloseRemainderTo
    global ZeroAddress
    ==
    &&
    gtxn 0 AssetCloseTo
    global ZeroAddress
    ==
    &&
    gtxn 1 AssetCloseTo
    global ZeroAddress
    ==
    &&
    load 2
    gtxns XferAsset
    int <assetid> // asset id to trade for
    ==
    &&
    assert

//////////////////////////////////////////////////////////////////////////////
// EXECUTE (partial)                                                        
//  Partial execution of an ASA escrow, where an ASA balance remains in it  
//////////////////////////////////////////////////////////////////////////////
    // TXN 0   - ESCROW TO ORDERBOOK: Application call to execute
    // TXN 1   - BUYER TO SELLER:     Pay transaction (from buyer/executor to escrow owner)
    // TXN 2   - BUYER TO BUYER:      (Optional) asset opt-in transaction (for buyer/executor)
    // TXN 2/3 - ESCROW TO BUYER:     Asset transfer (from escrow to buyer/executor)
    // TXN 3/4 - BUYER TO ESCROW:     Pay transaction for fee refund (from buyer/executor to escrow)

    gtxna 0 ApplicationArgs 0
    byte "execute_with_closeout"
    ==
    bnz execute_with_closeout

    global MinTxnFee
    global MinTxnFee
    +
    store 8 // store 2x minimum fee

    gtxn 0 OnCompletion // The application call must be a general application call
    int NoOp
    ==
    load 2
    gtxns AssetCloseTo
    global ZeroAddress
    ==
    &&
    load 3
    gtxns CloseRemainderTo // check fee refund has no close remainder to
    global ZeroAddress
    ==
    &&
    load 3
    gtxns Fee // check fee refund has no close remainder to
    global MinTxnFee
    ==
    &&
    load 3
    gtxns Amount // check fee refund amount is 2x minimum fee
    load 8
    ==
    &&
    load 3
    gtxns Receiver // receiver of fee transaction
    txn Sender  // escrow addr. check fee must be received by escrow account
    ==
    &&
    load 3 
    gtxns Sender // The fee sender must be the ASA buyer
    gtxn 1 Sender
    ==
    &&
    load 3 
    gtxns Sender // The fee sender
    txn Sender // Escrow account
    != // Fee sender should *not* be originating from escrow
    &&

    assert

    b finalExecuteChecks  //If the above result is 0, skip next section

/////////////////////////////////////////////////////////////////////////////////////////////////
// EXECUTE WITH CLOSE
//   Full order execution where the remaining minimum algo balance is closed to the escrow owner
/////////////////////////////////////////////////////////////////////////////////////////////////
    // TXN 0   - ESCROW TO ORDERBOOK: Application call to execute
    // TXN 1   - BUYER TO SELLER:     Pay transaction (from buyer/executor to escrow owner)
    // TXN 2   - BUYER TO BUYER:      (Optional) asset opt-in transaction (for buyer/executor)
    // TXN 2/3 - ESCROW TO BUYER:     Asset transfer (from escrow to buyer/executor)
    //                                 - closes out any remaining ASA to seller (escrow owner) as well
    // TXN 3/4 - ESCROW TO SELLER:    Pay transaction to close out to escrow owner
    execute_with_closeout:

    // The first transaction must be 
    // an ApplicationCall (ie call stateful smart contract)
   
    gtxn 0 OnCompletion
    int CloseOut
    ==
    gtxn 1 CloseRemainderTo
    global ZeroAddress
    ==
    &&
    load 2
    gtxns AssetCloseTo // remainder of ASA escrow is being closed out to escrow owner
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    load 3
    gtxns CloseRemainderTo
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    load 3
    gtxns Receiver
    addr <contractWriterAddr> // contractWriterAddr
    ==
    &&
    load 3
    gtxns Sender // escrow account
    txn Sender // escrow account
    ==
    &&
    load 3
    gtxns Amount
    int 0 // probably shouldn't matter, but the full amount should be in the CloseTo, not the Amount
    ==
    &&

    assert

    b finalExecuteChecks

finalExecuteChecks:

    gtxn 1 Amount // min algos spent
    //int <min> // NOTE** We have intentionally disabled the custom min amount check 
    int 1  // must be at least one algo spent
    >=
    load 2
    gtxns AssetAmount
    int 1  // must be at least one ASA spent
    >=
    &&
    bz fail

    /////////////////////////////////////
    /// finalizing execution ratio checks
    /////////////////////////////////////

    // handle the rate
    // SELL ORDER
    // D/N is the price of the asset. For example, if D/N = 0.25, then with 5 microAlgos you can buy 20 of the ASA in base units
    //
    // gtxn[2].AssetAmount * D <= gtxn[1].Amount * N 
    // N units of the asset per D microAlgos
    load 2
    gtxns AssetAmount
    int <D> // put <D> value here
    mulw // AssetAmount * D => (high 64 bits, low 64 bits)
    store 2 // move aside low 64 bits
    store 1 // move aside high 64 bits
    gtxn 1 Amount
    int <N> // put <N> value here
    mulw
    store 4 // move aside low 64 bits
    store 3 // move aside high 64 bits
    // compare high bits to high bits
    load 1
    load 3
    <
    bnz done
    load 1
    load 3
    ==
    load 2
    load 4
    <=
    && // high bits are equal and low bits are ok
    bnz done

    err
    done:
    int 1
    return
    fail:
    int 0 
    return
`;