The purpose of this contract is to allow two addresses,
TMPL_RCV2, to withdraw funds in a particular ratio specified by
TMPL_RAT2. Additionally, for each withdrawal pair,
TMPL_RCV1 must receive at least
TMPL_MINPAY microAlgos for the group to be approved.
For example, if
TMPL_RAT1 is set to 1,
TMPL_RAT2 is set to 5, and
TMPL_MINPAY is set to 1,000,000 microAlgos, then this contract will approve a group transaction that sends 1,000,000 microAlgo to
TMPL_RCV1 and 5,000,000 microAlgos to
This contract additionally takes a timeout round
TMPL_TIMEOUT, after which all remaining funds may be recovered to the address
TMPL_OWN and the contract will be closed. Note that the regular split withdrawal functionality will still work until after the contract is explicitly closed.
This contract is intended to be used as a contract-only account, and not as a delegated contract (in other words, this contract should never be signed by an account).
TMPL_RCV1: the first recipient of the funds
TMPL_RCV2: the second recipient of the funds
TMPL_RAT1: for each
TMPL_RAT2microAlgos received by
TMPL_RAT1specifies how many are received by
TMPL_RAT2: for each
TMPL_RAT1microAlgos received by
TMPL_RAT2specifies how many are received by
TMPL_MINPAY: the minimum number of microAlgos that must be received by
TMPL_RCV1for a split withdrawal to succeed
TMPL_TIMEOUT: the round after which funds may be closed back to
TMPL_OWN: the address that funds may be closed to after
TMPL_FEE: the maximum fee that may be used by any individual transaction approved by this contract
First, check that this is a payment transaction. This should be the case for any transaction approved by this contract. The possible valid values of this enum may be found here.
txn TypeEnum int 1 ==
Next, check that the fee of this transaction is less than or equal to
TMPL_FEE. Fold this check into the above check with a logical
txn Fee int TMPL_FEE <= &&
Next, we will check if we are trying to make a split withdrawal. We will assume this is the case when the transaction appears in a group of size two. If this is the case, jump ahead to the "split" section. Otherwise, fall through.
global GroupSize int 2 == bnz split
Fall through: closeout case¶
If the transaction group size was not two, assume that we want to close out the contract. We will also hit this case if the group size is greater than 2; because we will only approve closeout transactions in that case, this is not a vulnerability.
Check that the
Receiver field, and
Amount field are set such that we will close out all funds to
txn CloseRemainderTo addr TMPL_OWN == txn Receiver global ZeroAddress == && txn Amount int 0 == &&
Check that we are after the
TMPL_TIMEOUT round, when closing out is allowed.
txn FirstValid int TMPL_TIMEOUT > &&
Unconditionally jump to the
int 1 bnz done
If we made it here, the transaction group must have exactly two transactions, indicating we want to split funds.
Check that the
Sender of the two transactions is the same. Since we're evaluating this contract, this implicitly means that both transactions are evaluating this contract. This also means that both transactions went through the same checks in the "Initial checks" section.
split: gtxn 0 Sender gtxn 1 Sender ==
CloseRemainderTo is not set during a split transaction. This contract only wants to close out in the closeout case mentioned above. This check will apply to both transactions since they are both running this script.
txn CloseRemainderTo global ZeroAddress == &&
Check that the recipients of the funds are
TMPL_RCV2 in the first and second transactions, respectively.
gtxn 0 Receiver addr TMPL_RCV1 == && gtxn 1 Receiver addr TMPL_RCV2 == &&
Now check that funds are being split as described by
TMPL_RAT2. We want to ensure that:
group txn 0 amount / group txn 1 amount = TMPL_RAT1 / TMPL_RAT2
Cross multiplying, this is the same as:
(group txn 0 amount) * (TMPL_RAT2) = (group txn 1 amount) * (TMPL_RAT1)
Perform this check.
gtxn 0 Amount int TMPL_RAT2 * gtxn 1 Amount int TMPL_RAT1 * == &&
Check that we are paying at least
TMPL_MINPAY microAlgos to
gtxn 0 Amount int TMPL_MINPAY >= &&
Finally, fold all of the checks together into a single boolean with a logical
At this point, the stack contains just one value: a boolean indicating whether or not it has been approved by this contract.