Community Smart Contract Reviews

Akita Token Swap

nullun

// Akita Token Swap smart contract review.
// Reviewed by nullun on 2022-01-23
// Conclusion:
// Given the relatively simple nature of the contract there's very little
// attack surface available. Only 2 calls can result in an approval (post
// deployment), 1 of which can only be made by the deployer.
// All transactions coming from the smart contract are controlled by inner
// transactions opposed to smart signatures which greatly improves protection
// against attacks.
// There are some redundant checks and the occasional invalid checks, but this
// only reduces efficency and doesn't affect security.
// The chained OR and AND expressions definitely inflate the logic and make
// things more confusing, but this is likely just the result of using PyTeal.
#pragma version 5

// The following 9 checks are all OR'ed, so if any one of them are true the
// branch to `handle_failure` will be made.

// Sender must not pay a fee of more than 0.002 Algo.
txn Fee
pushint 2000
>

// Sender must not rekey during transaction.
txn RekeyTo
global ZeroAddress
!=
||

// Application call does not allow CloseOut, UpdateApplication, ClearState,
// DeleteApplication, or OptIn.
txn OnCompletion
int CloseOut
==
||
txn OnCompletion
int UpdateApplication
==
||
txn OnCompletion
int ClearState
==
||
txn OnCompletion
pushint DeleteApplication
==
||
txn OnCompletion
int OptIn
==
||

// Sender must not close out during transaction.
// This isn't a valid field for an application call.
txn CloseRemainderTo
global ZeroAddress
!=
||

// Sender must not close out of an ASA during transaction.
// This isn't a valid field for an application call.
txn AssetCloseTo
global ZeroAddress
!=
||
bnz handle_failure

// Check if we're deploying the contract.
txn ApplicationID
int 0
==
bnz handle_setup

// If two arguments have been passed in, branch to `handle_configuration`.
txn NumAppArgs
int 2
==
bnz handle_configuration

// If the application call is of type NoOp, branch to `handle_exchange`.
// Given the logic above, it's guarenteed to be a NoOp call at this point.
txn OnCompletion
int NoOp
==
bnz handle_exchange

err

//////////////
// Handlers //
//////////////

// Exchange
// Group Transactions:
//	+ Asset OptIn by sender
//	+ Old asset sent to contract
//	+ Application Call
//		+ (itxn) New asset sent to sender
handle_exchange:
	// Transactions must be in a fixed group size of 3 transactions.
	global GroupSize
	int 3
	==

	// The first transaction must be an asset transfer.
	gtxn 0 TypeEnum
	int axfer
	==
	&&

	// The first transaction must be for the asset ID as set in global state
	// "New_Asset_ID".
	gtxn 0 XferAsset
	byte "New_Asset_ID"
	app_global_get
	==
	&&

	// The first transaction must not contain an asset amount.
	gtxn 0 AssetAmount
	int 0
	==
	&&

	// The first transaction must be sent to themselves.
	// This results in the sender opting into the asset if they weren't already.
	gtxn 0 Sender
	gtxn 0 AssetReceiver
	==
	&&

	// The first transaction must not rekey during the transaction.
	gtxn 0 RekeyTo
	global ZeroAddress
	==
	&&

	// The first transaction must not close out during the transaction.
	// This isn't a valid field for an asset transfer.
	gtxn 0 CloseRemainderTo
	global ZeroAddress
	==
	&&

	// The first transaction must not close out of an ASA during the transaction.
	gtxn 0 AssetCloseTo
	global ZeroAddress
	==
	&&

	// The second transaction must be an asset transfer.
	gtxn 1 TypeEnum
	int axfer
	==
	&&

	// The second transaction must contain a non-zero asset amount.
	gtxn 1 AssetAmount
	int 0
	>
	&&

	// The second transaction must be for the asset ID as set in global state
	// "Swap_Asset_ID".
	gtxn 1 XferAsset
	byte "Swap_Asset_ID"
	app_global_get
	==
	&&

	// The second transaction must be sent from the same sender as the first
	// transaction.
	gtxn 1 Sender
	gtxn 0 Sender
	==
	&&

	// The second transaction must be sent to the application address.
	gtxn 1 AssetReceiver
	global CurrentApplicationAddress
	==
	&&

	// The second transaction must not rekey during the transaction.
	gtxn 1 RekeyTo
	global ZeroAddress
	==
	&&

	// The second transaction must not close out during the transaction.
	// This isn't a valid field for an asset transfer.
	gtxn 1 CloseRemainderTo
	global ZeroAddress
	==
	&&

	// The second transaction must not close out of an ASA during the
	// transaction.
	gtxn 1 AssetCloseTo
	global ZeroAddress
	==
	&&

	// The third transaction must be a NoOp call.
	gtxn 2 OnCompletion
	int NoOp
	==
	&&

	// The third transaction must not have any arguments passed in.
	gtxn 2 NumAppArgs
	int 0
	==
	&&

	// The third transaction must be sent from the same sender as the second
	// transaction.
	gtxn 2 Sender
	gtxn 1 Sender
	==
	&&
	assert

	// The asset amount sent in the second transaction is multiplied by the
	// global state value of "Multiply" and the result is a parameter of the
	// subroutine `send_asa`.
	// This is done because the old asset had zero decimals and the new asset has
	// six decimals.
	gtxn 1 AssetAmount
	byte "Multiply"
	app_global_get
	*
	callsub send_asa

	// Successfully return.
	int 1
	return

// Configuration
// Group Transactions:
//	+ Send minimum balance requirement to application
//	+ Application Call
//		+ (itxn) OptIn to old asset
//		+ (itxn) OptIn to new asset
handle_configuration:
	// The global state value for "Swap_Asset_ID" must be 0 (or not set).
	byte "Swap_Asset_ID"
	app_global_get
	int 0
	==

	// The global state value for "New_Asset_ID" must be 0 (or not set).
	byte "New_Asset_ID"
	app_global_get
	int 0
	==
	&&

	// Transactions must be in a fixed group size of 2 transactions.
	global GroupSize
	int 2
	==
	&&

	// The first transaction must be a payment transaction.
	gtxn 0 TypeEnum
	int pay
	==
	&&

	// The first transaction must be sent to the application address.
	gtxn 0 Receiver
	global CurrentApplicationAddress
	==
	&&

	// The first transaction must be sent from the same address that created
	// deployed the contract.
	gtxn 0 Sender
	global CreatorAddress
	==
	&&

	// The first transaction must contain an amount of 0.302 Algo.
	// This is the minimum balance requirement for the application address plus
	// 0.002 Algo to cover the cost of sending the optin transactions.
	// 0.1 for Algo
	// 0.2 for the old ASA and the new ASA
	// 0.002 Algo to send 2 asset transfer optin transactions.
	gtxn 0 Amount
	pushint 302000
	==
	&&

	// The second transaction must be an application call to the application.
	gtxn 1 ApplicationID
	global CurrentApplicationID
	==
	&&

	// The second transaction must be a NoOp call.
	gtxn 1 OnCompletion
	int NoOp
	==
	&&
	assert

	// The first argument in the application call should contain the asset ID of
	// the old asset, whilst the second argument should contain the asset ID of
	// the new asset.

	// Using the first argument (old asset ID) call the `optin_asa` subroutine.
	txna ApplicationArgs 0
	btoi
	callsub optin_asa

	// Using the second argument (new asset ID) call the `optin_asa` subroutine.
	txna ApplicationArgs 1
	btoi
	callsub optin_asa

	// Set the global state value of "Swap_Asset_ID" to the old asset ID.
	byte "Swap_Asset_ID"
	txna ApplicationArgs 0
	btoi
	app_global_put

	// Set the global state value of "New_Asset_ID" to the new asset ID.
	byte "New_Asset_ID"
	txna ApplicationArgs 1
	btoi
	app_global_put

	// Using the global state value of "New_Asset_ID", get the decimal value of
	// the new asset and store the result in scratch space.
	byte "New_Asset_ID"
	app_global_get
	asset_params_get AssetDecimals
	store 0
	store 1
	load 0
	assert

	// Set the global state value of "Multiply" to the value 10 to the power of
	// the number of decimals, retrieved from scratch space.
	byte "Multiply"
	pushint 10
	load 1
	exp
	app_global_put

	// Successfully return.
	int 1
	return

// Setup
handle_setup:
	int 1
	return

// Failure
handle_failure:
	int 0
	return

/////////////////
// Subroutines //
/////////////////

// Parameter: AssetID (uint64)
optin_asa:
	store 2

	// Create and submit an asset transfer inner transaction. Sending 0 asset
	// amount to itself for the asset ID provided before the call.
	itxn_begin

	int axfer
	itxn_field TypeEnum

	load 2
	itxn_field XferAsset

	global CurrentApplicationAddress
	itxn_field AssetReceiver

	itxn_submit

	retsub

// Parameter: Asset Amount (uint64)
send_asa:
	store 3

	// Create and submit an asset transfer inner transaction. Sending an asset
	// amount provided before the call, to the sender of the application call.
	itxn_begin

	int axfer
	itxn_field TypeEnum

	txn Sender
	itxn_field AssetReceiver

	load 3
	itxn_field AssetAmount

	byte "New_Asset_ID"
	app_global_get

	itxn_field XferAsset

	itxn_submit

	retsub