Skip to content

Create Publication

We are looking for publications that demonstrate building dApps or smart contracts!
See the full list of Gitcoin bounties that are eligible for rewards.

Creation

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]

Note

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]

Note

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))
Snippet Source

txn OnCompletion
int OptIn
==
bz not_optin

// Allow OptIn
int 1
return

not_optin:
// additional checks...
Snippet Source

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(Txn.sender(), Txn.application_id())
    print(compileTeal(program, Mode.Application))

txn Sender
txn ApplicationID
app_opted_in
Snippet Source

In the above example, txn Sender is the address of the transaction sender. The address can be any account 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.

Info

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.

Argument Type Example
String goal app call --app-arg "str:mystring".....
Integer goal app create --app-arg "int:5".....
Address goal app call --app-arg "addr:address-string".....
Base64 goal app call --app-arg "b64:A==".....

Note

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[1] == Bytes("claim")
    print(compileTeal(program, Mode.Application))
Snippet Source

txna ApplicationArgs 1
byte "claim"
==
Snippet Source

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))
Snippet Source

txn NumAppArgs
int 4
==
Snippet Source

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 btoi opcode.

    program = Btoi(Txn.application_args[0])
    print(compileTeal(program, Mode.Application))
Snippet Source

txna ApplicationArgs 0
btoi
Snippet Source

Info

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]

Note

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[0], Approve(), Reject())
    print(compileTeal(program, Mode.Application))
Snippet Source

byte "myparam"
txna ApplicationArgs 0
==
bz not_myparam
// handle my_param

not_myparam:
// handle not_myparam
Snippet Source

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]

Note

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))
Snippet Source

byte "update"
txna ApplicationArgs 0
==
bz not_update

// Only Creator may update
global CreatorAddress
txn Sender
==
return

not_update:
Snippet Source

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))
Snippet Source

txn OnCompletion
int UpdateApplication
==
bz not_update

// Reject Update
int 0
return

not_update:
Snippet Source

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]

Note

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)
Snippet Source

#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
Snippet Source