github.com/algorand/go-algorand-sdk@v1.24.0/templates/split.go (about)

     1  package templates
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"math"
     7  
     8  	"golang.org/x/crypto/ed25519"
     9  
    10  	"github.com/algorand/go-algorand-sdk/crypto"
    11  	"github.com/algorand/go-algorand-sdk/future"
    12  	"github.com/algorand/go-algorand-sdk/logic"
    13  	"github.com/algorand/go-algorand-sdk/types"
    14  )
    15  
    16  // Split template representation
    17  //
    18  // Deprecated: Use TealCompile source compilation instead.
    19  type Split struct {
    20  	ContractTemplate
    21  	rat1        uint64
    22  	rat2        uint64
    23  	receiverOne types.Address
    24  	receiverTwo types.Address
    25  }
    26  
    27  //GetSplitFundsTransaction returns a group transaction array which transfer funds according to the contract's ratio
    28  // the returned byte array is suitable for passing to SendRawTransaction
    29  // contract: the bytecode of the contract to be used
    30  // amount: uint64 total number of algos to be transferred (payment1_amount + payment2_amount)
    31  // params: is typically received from algod, it defines common-to-all-txns arguments like fee and validity period
    32  //
    33  // Deprecated: Use TealCompile source compilation instead.
    34  func GetSplitFundsTransaction(contract []byte, amount uint64, params types.SuggestedParams) ([]byte, error) {
    35  	ints, byteArrays, err := logic.ReadProgram(contract, nil)
    36  	if err != nil {
    37  		return nil, err
    38  	}
    39  	rat1 := ints[6]
    40  	rat2 := ints[5]
    41  	// Convert the byteArrays[0] to receiver
    42  	var receiverOne types.Address //byteArrays[0]
    43  	n := copy(receiverOne[:], byteArrays[1])
    44  	if n != ed25519.PublicKeySize {
    45  		err = fmt.Errorf("address generated from receiver bytes is the wrong size")
    46  		return nil, err
    47  	}
    48  	// Convert the byteArrays[2] to receiverTwo
    49  	var receiverTwo types.Address
    50  	n = copy(receiverTwo[:], byteArrays[2])
    51  	if n != ed25519.PublicKeySize {
    52  		err = fmt.Errorf("address generated from closeRemainderTo bytes is the wrong size")
    53  		return nil, err
    54  	}
    55  
    56  	ratio := float64(rat2) / float64(rat1)
    57  	amountForReceiverOneFloat := float64(amount) / (1 + ratio)
    58  	amountForReceiverOne := uint64(math.Round(amountForReceiverOneFloat))
    59  	amountForReceiverTwo := amount - amountForReceiverOne
    60  	if rat2*amountForReceiverOne != rat1*amountForReceiverTwo {
    61  		err = fmt.Errorf("could not split funds in a way that satisfied the contract ratio (%d * %d != %d * %d)", rat2, amountForReceiverOne, rat1, amountForReceiverTwo)
    62  		return nil, err
    63  	}
    64  
    65  	from := crypto.AddressFromProgram(contract)
    66  	tx1, err := future.MakePaymentTxn(from.String(), receiverOne.String(), amountForReceiverOne, nil, "", params)
    67  	if err != nil {
    68  		return nil, err
    69  	}
    70  	tx2, err := future.MakePaymentTxn(from.String(), receiverTwo.String(), amountForReceiverTwo, nil, "", params)
    71  	if err != nil {
    72  		return nil, err
    73  	}
    74  	gid, err := crypto.ComputeGroupID([]types.Transaction{tx1, tx2})
    75  	if err != nil {
    76  		return nil, err
    77  	}
    78  	tx1.Group = gid
    79  	tx2.Group = gid
    80  
    81  	logicSig, err := crypto.MakeLogicSig(contract, nil, nil, crypto.MultisigAccount{})
    82  	if err != nil {
    83  		return nil, err
    84  	}
    85  	_, stx1, err := crypto.SignLogicsigTransaction(logicSig, tx1)
    86  	if err != nil {
    87  		return nil, err
    88  	}
    89  	_, stx2, err := crypto.SignLogicsigTransaction(logicSig, tx2)
    90  	if err != nil {
    91  		return nil, err
    92  	}
    93  
    94  	var signedGroup []byte
    95  	signedGroup = append(signedGroup, stx1...)
    96  	signedGroup = append(signedGroup, stx2...)
    97  
    98  	return signedGroup, err
    99  }
   100  
   101  // MakeSplit splits money sent to some account to two recipients at some ratio.
   102  // This is a contract account.
   103  //
   104  // This allows either a two-transaction group, for executing a
   105  // split, or single transaction, for closing the account.
   106  //
   107  // Withdrawals from this account are allowed as a group transaction which
   108  // sends receiverOne and receiverTwo amounts with exactly the ratio of
   109  // rat1/rat2.  At least minPay must be sent to receiverOne.
   110  // (CloseRemainderTo must be zero.)
   111  //
   112  // After expiryRound passes, all funds can be refunded to owner.
   113  //
   114  // Split ratio:
   115  // firstRecipient_amount * rat2 == secondRecipient_amount * rat1
   116  // or phrased another way
   117  // firstRecipient_amount == secondRecipient_amount * (rat1/rat2)
   118  //
   119  // Parameters:
   120  //  - owner: the address to refund funds to on timeout
   121  //  - receiverOne: the first recipient in the split account
   122  //  - receiverTwo: the second recipient in the split account
   123  //  - rat1: fraction determines resource split ratio
   124  //  - rat2: fraction determines resource split ratio
   125  //  - expiryRound: the round at which the account expires
   126  //  - minPay: minimum amount to be paid out of the account to receiverOne
   127  //  - maxFee: half of the maximum fee used by each split forwarding group transaction
   128  //
   129  // Deprecated: Use TealCompile source compilation instead.
   130  func MakeSplit(owner, receiverOne, receiverTwo string, rat1, rat2, expiryRound, minPay, maxFee uint64) (Split, error) {
   131  	const referenceProgram = "ASAIAQUCAAYHCAkmAyCztwQn0+DycN+vsk+vJWcsoz/b7NDS6i33HOkvTpf+YiC3qUpIgHGWE8/1LPh9SGCalSN7IaITeeWSXbfsS5wsXyC4kBQ38Z8zcwWVAym4S8vpFB/c0XC6R4mnPi9EBADsPDEQIhIxASMMEDIEJBJAABkxCSgSMQcyAxIQMQglEhAxAiEEDRAiQAAuMwAAMwEAEjEJMgMSEDMABykSEDMBByoSEDMACCEFCzMBCCEGCxIQMwAIIQcPEBA="
   132  	referenceAsBytes, err := base64.StdEncoding.DecodeString(referenceProgram)
   133  	if err != nil {
   134  		return Split{}, err
   135  	}
   136  	var referenceOffsets = []uint64{ /*fee*/ 4 /*timeout*/, 7 /*rat2*/, 8 /*rat1*/, 9 /*minPay*/, 10 /*owner*/, 14 /*receiver1*/, 47 /*receiver2*/, 80}
   137  	ownerAddr, err := types.DecodeAddress(owner)
   138  	if err != nil {
   139  		return Split{}, err
   140  	}
   141  	receiverOneAddr, err := types.DecodeAddress(receiverOne)
   142  	if err != nil {
   143  		return Split{}, err
   144  	}
   145  	receiverTwoAddr, err := types.DecodeAddress(receiverTwo)
   146  	if err != nil {
   147  		return Split{}, err
   148  	}
   149  	injectionVector := []interface{}{maxFee, expiryRound, rat2, rat1, minPay, ownerAddr, receiverOneAddr, receiverTwoAddr}
   150  	injectedBytes, err := inject(referenceAsBytes, referenceOffsets, injectionVector)
   151  	if err != nil {
   152  		return Split{}, err
   153  	}
   154  
   155  	address := crypto.AddressFromProgram(injectedBytes)
   156  	split := Split{
   157  		ContractTemplate: ContractTemplate{
   158  			address: address.String(),
   159  			program: injectedBytes,
   160  		},
   161  		rat1:        rat1,
   162  		rat2:        rat2,
   163  		receiverOne: receiverOneAddr,
   164  		receiverTwo: receiverTwoAddr,
   165  	}
   166  	return split, err
   167  }