Skip to content

Commit

Permalink
Limit order TEAL template (#101)
Browse files Browse the repository at this point in the history
Review: #101
Resolves: #98
  • Loading branch information
EvanJRichard committed Jan 6, 2020
1 parent efd957f commit bc0af1a
Show file tree
Hide file tree
Showing 3 changed files with 144 additions and 3 deletions.
5 changes: 2 additions & 3 deletions templates/hashTimeLockedContract.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ type HTLC struct {
ContractTemplate
}

// MakeHTLC allows a user to recieve the Algo prior to a deadline (in terms of a round) by proving a knowledge
// MakeHTLC allows a user to receive the Algo prior to a deadline (in terms of a round) by proving a knowledge
// of a special value or to forfeit the ability to claim, returning it to the payer.
// This contract is usually used to perform cross-chained atomic swaps
//
Expand All @@ -22,8 +22,7 @@ type HTLC struct {
// 2. To owner if txn.FirstValid > expiry_round
// ...
//
//Parameters
//----------
// Parameters:
// - owner : string an address that can receive the asset after the expiry round
// - receiver: string address to receive Algos
// - hashFunction : string the hash function to be used (must be either sha256 or keccak256)
Expand Down
125 changes: 125 additions & 0 deletions templates/limitOrder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
package templates

import (
"encoding/base64"
"github.com/algorand/go-algorand-sdk/crypto"
"github.com/algorand/go-algorand-sdk/transaction"
"github.com/algorand/go-algorand-sdk/types"
)

// Split template representation
type LimitOrder struct {
ContractTemplate
assetID uint64
owner string
}

// GetSwapAssetsTransaction returns a group transaction array which transfer funds according to the contract's ratio
// assetAmount: amount of assets to be sent
// contract: byteform of the contract from the payer
// secretKey: secret key for signing transactions
// fee: fee per byte used for the transactions
// algoAmount: number of algos to transfer
// firstRound: first round on which these txns will be valid
// lastRound: last round on which these txns will be valid
// genesisHash: genesisHash indicating the network for the txns
// the first payment sends money (Algos) from contract to the recipient (we'll call him Buyer), closing the rest of the account to Owner
// the second payment sends money (the asset) from Buyer to the Owner
// these transactions will be rejected if they do not meet the restrictions set by the contract
func (lo LimitOrder) GetSwapAssetsTransaction(assetAmount uint64, contract, secretKey []byte, fee, algoAmount, firstRound, lastRound uint64, genesisHash []byte) ([]byte, error) {
var buyerAddress types.Address
copy(buyerAddress[:], secretKey[32:])
contractAddress := crypto.AddressFromProgram(contract)
algosForAssets, err := transaction.MakePaymentTxn(contractAddress.String(), buyerAddress.String(), fee, algoAmount, firstRound, lastRound, nil, lo.owner, "", genesisHash)
if err != nil {
return nil, err
}

assetsForAlgos, err := transaction.MakeAssetTransferTxn(buyerAddress.String(), lo.owner, "", assetAmount, fee, firstRound, lastRound, nil, lo.owner, "", lo.assetID)
if err != nil {
return nil, err
}
gid, err := crypto.ComputeGroupID([]types.Transaction{algosForAssets, assetsForAlgos})
if err != nil {
return nil, err
}
algosForAssets.Group = gid
assetsForAlgos.Group = gid

logicSig, err := crypto.MakeLogicSig(contract, nil, nil, crypto.MultisigAccount{})
if err != nil {
return nil, err
}
_, algosForAssetsSigned, err := crypto.SignLogicsigTransaction(logicSig, algosForAssets)
if err != nil {
return nil, err
}
_, assetsForAlgosSigned, err := crypto.SignTransaction(secretKey, assetsForAlgos)
if err != nil {
return nil, err
}

var signedGroup []byte
signedGroup = append(signedGroup, assetsForAlgosSigned...)
signedGroup = append(signedGroup, algosForAssetsSigned...)

return signedGroup, nil
}

// MakeLimitOrder allows a user to exchange some number of assets for some number of algos.
// Fund the contract with some number of Algos to limit the maximum number of
// Algos you're willing to trade for some other asset.
//
// Works on two cases:
// * trading Algos for some other asset
// * closing out Algos back to the originator after a timeout
//
// trade case, a 2 transaction group:
// gtxn[0] (this txn) Algos from Me to Other
// gtxn[1] asset from Other to Me
//
// We want to get _at least_ some amount of the other asset per our Algos
// gtxn[1].AssetAmount / gtxn[0].Amount >= N / D
// ===
// gtxn[1].AssetAmount * D >= gtxn[0].Amount * N
//
// close-out case:
// txn alone, close out value after timeout
//
// Parameters:
// - owner: the address to refund funds to on timeout
// - assetID: ID of the transferred asset
// - ratn: exchange rate (N asset per D Algos, or better)
// - ratd: exchange rate (N asset per D Algos, or better)
// - expiryRound: the round at which the account expires
// - minTrade: the minimum amount (of Algos) to be traded away
// - maxFee: maximum fee used by the limit order transaction
func MakeLimitOrder(owner string, assetID, ratn, ratd, expiryRound, minTrade, maxFee uint64) (LimitOrder, error) {
const referenceProgram = "ASAKAAEFAgYEBwgJCiYBIP68oLsUSlpOp7Q4pGgayA5soQW8tgf8VlMlyVaV9qITMRYiEjEQIxIQMQEkDhAyBCMSQABVMgQlEjEIIQQNEDEJMgMSEDMBECEFEhAzAREhBhIQMwEUKBIQMwETMgMSEDMBEiEHHTUCNQExCCEIHTUENQM0ATQDDUAAJDQBNAMSNAI0BA8QQAAWADEJKBIxAiEJDRAxBzIDEhAxCCISEBA="
referenceAsBytes, err := base64.StdEncoding.DecodeString(referenceProgram)
if err != nil {
return LimitOrder{}, err
}

var referenceOffsets = []uint64{ /*maxFee*/ 5 /*minTrade*/, 7 /*assetID*/, 9 /*ratd*/, 10 /*ratn*/, 11 /*expiryRound*/, 12 /*ownerAddr*/, 16}
ownerAddr, err := types.DecodeAddress(owner)
if err != nil {
return LimitOrder{}, err
}
injectionVector := []interface{}{maxFee, minTrade, assetID, ratd, ratn, expiryRound, ownerAddr}
injectedBytes, err := inject(referenceAsBytes, referenceOffsets, injectionVector)
if err != nil {
return LimitOrder{}, err
}

address := crypto.AddressFromProgram(injectedBytes)
lo := LimitOrder{
ContractTemplate: ContractTemplate{
address: address.String(),
program: injectedBytes,
},
owner: owner,
assetID: assetID,
}
return lo, err
}
17 changes: 17 additions & 0 deletions templates/templates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,20 @@ func TestHTLC(t *testing.T) {
goldenAddress := "KNBD7ATNUVQ4NTLOI72EEUWBVMBNKMPHWVBCETERV2W7T2YO6CVMLJRBM4"
require.Equal(t, goldenAddress, c.GetAddress())
}

func TestLimitOrder(t *testing.T) {
// Inputs
owner := "726KBOYUJJNE5J5UHCSGQGWIBZWKCBN4WYD7YVSTEXEVNFPWUIJ7TAEOPM"
assetid := uint64(12345)
ratn, ratd := uint64(30), uint64(100)
expiryRound := uint64(123456)
minTrade := uint64(10000)
maxFee := uint64(5000000)
c, err := MakeLimitOrder(owner, assetid, ratn, ratd, expiryRound, minTrade, maxFee)
// Outputs
require.NoError(t, err)
goldenProgram := "ASAKAAHAlrECApBOBLlgZB7AxAcmASD+vKC7FEpaTqe0OKRoGsgObKEFvLYH/FZTJclWlfaiEzEWIhIxECMSEDEBJA4QMgQjEkAAVTIEJRIxCCEEDRAxCTIDEhAzARAhBRIQMwERIQYSEDMBFCgSEDMBEzIDEhAzARIhBx01AjUBMQghCB01BDUDNAE0Aw1AACQ0ATQDEjQCNAQPEEAAFgAxCSgSMQIhCQ0QMQcyAxIQMQgiEhAQ"
require.Equal(t, goldenProgram, base64.StdEncoding.EncodeToString(c.GetProgram()))
goldenAddress := "LXQWT2XLIVNFS54VTLR63UY5K6AMIEWI7YTVE6LB4RWZDBZKH22ZO3S36I"
require.Equal(t, goldenAddress, c.GetAddress())
}

0 comments on commit bc0af1a

Please sign in to comment.