Creating the smart contract¶
Before creating a smart contract, the code for the
ApprovalProgram and the
ClearStateProgram program should be written. The SDKs and the
goal CLI tool can be used to create a smart contract application. To create the application with
goal use a command similar to the following.
$ goal app create --creator [address] --approval-prog [approval_program.teal] --clear-prog [clear_state_program.teal] --global-byteslices [number-of-global-byteslices] --global-ints [number-of-global-ints] --local-byteslices [number-of-local-byteslices] --local-ints [number-local-ints] --extra-pages [number of extra 2KB pages]
See Creating the smart contract for details on using the SDKs to deploy a smart contract.
The creator is the account that is creating the application and this transaction is signed by this account. The approval program and the clear state program should also be provided. The number of global and local byte slices (byte-array value) and integers also needs to be specified. These represent the absolute on-chain amount of space that the smart contract will use. Once set, these values can never be changed. The key is limited to 64 bytes. The key plus the value is limited to 128 bytes total. When the smart contract is created the network will return a unique ApplicationID. This ID can then be used to make
ApplicationCall transactions to the smart contract. The smart contract will also have a unique Algorand address that is generated from this ID. This address allows the contract to function as an escrow account.
When creating a smart contract, there is a limit of 64 key-value pairs that can be used by the contract for global storage and 16 key-value pairs that can be used for local storage. When creating the smart contract the amount of storage can never be changed once the contract is created. Additionally, the minimum balance is raised for any account that participates in the contract. See Minimum Balance Requirement for Smart Contracts described below for more detail.
Smart contracts are limited to 2KB total for the compiled approval and clear programs. This size can be increased up to 3 additional 2KB pages, which would result in an 8KB limit for both programs. Note the size increases will also increase the minimum balance requirement for creating the application. To request additional pages, the setting (
extra-pages) is available when creating the smart contract using
goal. These extra pages can also be requested using the SDKs. This setting allows setting up to 3 additional 2KB pages.
Opt into the smart contract¶
Before any account, including the creator of the smart contract, can begin to make Application Transaction calls that use local state, it must first opt into the smart contract. This prevents accounts from being spammed with smart contracts. To opt in, an
ApplicationCall transaction of type
OptIn needs to be signed and submitted by the account desiring to opt into the smart contract. This can be done with the
goal CLI or the SDKs.
$ goal app optin --app-id [ID-of-Contract] --from [ADDRESS]
See Opt-in for details on using the SDKs to opt into a smart contract.
When this transaction is submitted, the
ApprovalProgram of the smart contract is called and if the call succeeds the account will be opted into the smart contract. The simplest program to handle this call would just put 1 on the stack and return.
# this would reject _ANY_ transaction that isn't an opt-in # and approve _ANY_ transaction that is an opt-in program = If(OnComplete.OptIn == Txn.on_completion(), Approve(), Reject()) print(compileTeal(program, Mode.Application))
txn OnCompletion int OptIn == bz not_optin // Allow OptIn int 1 return not_optin: // additional checks...
Other contracts may have much more complex opt in logic. TEAL also provides an opcode to check whether an account has already opted into the contract.
program = App.optedIn(Int(0), Txn.application_id()) print(compileTeal(program, Mode.Application))
int 0 txn ApplicationID app_opted_in
In the above example, the int 0 is a reference index into the accounts array, where 0 is the sender. A 1 would be the first account passed into the call and so on. The actual address may also be specified as long as it is in the accounts array. The
txn ApplicationID refers to the current application ID, but technically any application ID could be used as long as its ID is in the applications array. See Reference arrays for more details.
Applications that only use global state do not require accounts to opt in.
Passing arguments to smart contracts¶
Arguments can be passed to any of the supported application transaction calls, including create. The number and type can also be different for any subsequent calls to the smart contract. The
goal CLI supports passing strings, ints, base64 encoded data, and addresses as parameters. To pass a parameter supply the
--app-arg option to the call and supply the value according to the format shown below.
See Call with arguments, for more information on passing parmeters with SDKs.
These parameters are loaded into the arguments array. TEAL opcodes are available to get the values within the array. The primary argument opcode is the
ApplicationArgs opcode and can be used as shown below.
program = Txn.application_args == Bytes("claim") print(compileTeal(program, Mode.Application))
txna ApplicationArgs 1 byte "claim" ==
This call gets the second passed in argument and compares it to the string "claim".
A global variable is also available to check the size of the transaction argument array. This size can be checked with the following contract code.
program = Txn.application_args.length() == Int(4) print(compileTeal(program, Mode.Application))
txn NumAppArgs int 4 ==
The above contract code will push a 0 on the top of the stack if the number of parameters in this specific transaction is anything other than 4, else it will push a 1 on the top of the stack. Internally all transaction parameters are stored as byte slices (byte-array value). Integers can be converted using the
program = Btoi(Txn.application_args) print(compileTeal(program, Mode.Application))
txna ApplicationArgs 0 btoi
Argument passing for smart contracts is very different from passing arguments to smart signatures.
The total size of all parameters is limited to 2KB in size.
Call the smart contract¶
Any account can make a call to the smart contract. These calls will be in the form of
ApplicationCall transactions that can be submitted with
goal or the SDKs. Depending on the individual type of transaction as described in The Lifecycle of a Smart Contract, either the
ApprovalProgram or the
ClearStateProgram will be called. Generally, individual calls will supply application arguments. See Passing Arguments to a Smart Contract for details on passing arguments.
$ goal app call --app-id 1 --app-arg "str:myparam" --from [ADDRESS]
See Call(NoOp) for details on using the SDKs to call a smart contract. If using ABI compliant contracts, use the AtomicTransactionComposer to interact with the smart contract.
The call must specify the intended contract using the
--app-id option. Additionally, the
--from option specifies the sender’s address.
# this would approve _ANY_ transaction that has its # first app arg set to the byte string "myparam" # and reject all others program = If(Bytes("myparm") == Txn.application_args, Approve(), Reject()) print(compileTeal(program, Mode.Application))
byte "myparam" txna ApplicationArgs 0 == bz not_myparam // handle my_param not_myparam: // handle not_myparam
Update smart contract¶
A smart contract’s programs can be updated at any time. This is done by an
ApplicationCall transaction type of
UpdateApplication. This operation can be done with
goal or the SDKs and requires passing the new programs and specifying the application ID.
goal app update --app-id=[APPID] --from [ADDRESS] --approval-prog [new_approval_program.teal] --clear-prog [new_clear_state_program.teal]
See Update for details on using the SDKs to update a smart contract.
The one caveat to this operation is that global or local state requirements for the smart contract can never be updated. Updating a smart contract's programs does not affect any values currently in state.
As stated earlier, anyone can update the program. If this is not desired and you want only the original creator to be able to update the programs, code must be added to your
ApprovalProgram to handle this situation. This can be done by comparing the global
CreatorAddress to the sender address.
program = Assert( Txn.on_completion() == OnComplete.UpdateApplication, Global.creator_address() == Txn.sender(), ) print(compileTeal(program, Mode.Application))
byte "update" txna ApplicationArgs 0 == bz not_update // Only Creator may update global CreatorAddress txn Sender == return not_update:
Or alternatively, the contract code can always return a 0 when an
UpdateApplication application call is made to prevent anyone from ever updating the application code.
program = If( OnComplete.UpdateApplication == Txn.on_completion(), Reject(), # placeholder, update with actual logic Approve(), ) print(compileTeal(program, Mode.Application))
txn OnCompletion int UpdateApplication == bz not_update // Reject Update int 0 return not_update:
Delete smart contract¶
To delete a smart contract, an
ApplicationCall transaction of type
DeleteApplication must be submitted to the blockchain. The
ApprovalProgram handles this transaction type and if the call returns true, the application will be deleted. This can be done using
goal or the SDKs.
$ goal app delete --app-id=[APPID] --from [ADDRESS]
See Delete for details on using the SDKs to delete a smart contract.
When making this call the
--app-id and the
--from options are required. Anyone can delete a smart contract. If this is not desired, logic in the program must reject the call. Using a method described in Update Smart Contract must be supplied.
Boilerplate smart contract¶
As a way of getting started writing smart contracts, the following boilerplate template is supplied. The code provides labels or handling different
ApplicationCall transactions and also prevents updating and deleting the smart contract.
# Handle each possible OnCompletion type. We don't have to worry about # handling ClearState, because the ClearStateProgram will execute in that # case, not the ApprovalProgram. def approval_program(): handle_noop = Seq([Return(Int(1))]) handle_optin = Seq([Return(Int(1))]) handle_closeout = Seq([Return(Int(1))]) handle_updateapp = Err() handle_deleteapp = Err() program = Cond( [Txn.on_completion() == OnComplete.NoOp, handle_noop], [Txn.on_completion() == OnComplete.OptIn, handle_optin], [Txn.on_completion() == OnComplete.CloseOut, handle_closeout], [Txn.on_completion() == OnComplete.UpdateApplication, handle_updateapp], [Txn.on_completion() == OnComplete.DeleteApplication, handle_deleteapp], ) return program with open("boilerplate_approval_pyteal.teal", "w") as f: compiled = compileTeal(approval_program(), Mode.Application, version=5) f.write(compiled)
#pragma version 8 // Handle each possible OnCompletion type. We don't have to worry about // handling ClearState, because the ClearStateProgram will execute in that // case, not the ApprovalProgram. txn OnCompletion int NoOp == bnz handle_noop txn OnCompletion int OptIn == bnz handle_optin txn OnCompletion int CloseOut == bnz handle_closeout txn OnCompletion int UpdateApplication == bnz handle_updateapp txn OnCompletion int DeleteApplication == bnz handle_deleteapp // Unexpected OnCompletion value. Should be unreachable. err handle_noop: // Handle NoOp int 1 return handle_optin: // Handle OptIn int 1 return handle_closeout: // Handle CloseOut int 1 return // By default, disallow updating or deleting the app. Add custom authorization // logic below to allow updating or deletion in certain circumstances. handle_updateapp: handle_deleteapp: err