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.

Article Thumbnail

Using a Smart Contract to Spawn Additional Smart Contracts

With TEAL version 6, Algorand smart contracts can make app to app calls using inner transactions. This is a critical component in many multi contract applications and having this ability offers developers a great deal of flexibility in how they design their blockchain apps. This change allows developers to not only call another smart contract, but to also use a smart contract to create a new contract, call it and then even delete the contract. This effectively gives you the ability to spawn short lived contracts that can be used and then destroyed, perhaps in a single atomic transaction group.

In this article, we will walk through an example of a parent contract that spawns additional child contracts, calls the newly created child contract and finally destroys the child contract.

EditorImages/2022/05/05 14:20/parentchild.jpg

The example in this article uses the Application Binary Interface(ABI). For more information about how to use ABI with your smart contracts see this article, the ABI specification, or the developer documentation. Developers should use the Atomic Transaction Composer(ATC) to create proper ABI-based transactions. For more information on ATC, see the developer documentation. In this example a shell script is used in conjunction with the goal command tool, as we are focusing on the contracts.

Run The Example

The code for this example is located on the Algorand DevRel github repository. To quickly run this example, first verify that you have the sandbox running in dev mode. To understand how to install and run the sandbox see the [sandbox readme]https://github.com/algorand/sandbox#readme). Once sandbox is installed and running, do the following from a terminal.

git clone https://github.com/algorand-devrel/parent-child-contracts
cd parent-child-contracts

Important

Edit the demo.sh script and set the appropriate location of your sandbox executable.

SANDBOX="$HOME/sandbox/sandbox"

Run the script.

./demo.sh

This demo shell script deploys the parent contract on the sandbox, funds the contract, deploys a child contract through the parent contract, calls the child contract, and then destroys the child contract through the parent contract.

The child contract code in this example simply allows calling it with a string argument and it will reverse and return the string.

Note

This example is not production code and is for educational purposes only. Many security checks are not included.

Code Explanation

The parent smart contract supports three ABI methods (deploy, update, and delete). These three methods can be used to first create the child contract, possibly update the source for the child contract and finally delete the child contract. Their respective ABI method descriptors are shown below.

Deploy ABI method

deploy(pay,byte[],byte[])uint64

The deploy method call takes a payment transaction as the first parameter, which is used to cover the minimum balance required by the parent contract to create the child contract. Note that this will be dependent on the amount of state the child contract uses. The next two parameters contain the compiled bytes for the approval and clear programs for the smart contract. Finally, the method should return an integer containing the app ID of the newly created child contract.

Update ABI method

update(application,byte[],byte[])bool

The update method takes the application of the child contract as the first parameter. The next two parameters contain the compiled bytes for the updated approval and clear programs. The method will return true if the update is successful.

Destroy ABI method

destroy(application)bool

The destroy method only takes the application id of the child contract to delete. If successful the method returns true.

Implementing the Parent Contract

The following section explains how the parent contract is implemented.

Setup Implementation

Before implementing each of the methods we need some initial code to setup the parent contract.

#pragma version 6

txn ApplicationID
bz handle_setup

txn OnCompletion
int UpdateApplication
==
bnz handle_update

First the application ID of the parent contract is checked to see if it has been created yet. This just effectively tells us if the parent contract has already been deployed. If this is the first time the code has been executed (ApplicationID will be 0) we branch to the handle_setup label.

Next the OnCompletion transaction property is checked to see if the transaction is an update to the parent smart contract. If so we branch to the handle_update label.

Routing Implementation

The next part of the contract is specifically used to route specific ABI methods to the appropriate code to execute.

method "deploy(pay,byte[],byte[])uint64"
txn ApplicationArgs 0
==
bnz method_deploy

method "update(application,byte[],byte[])bool"
txn ApplicationArgs 0
==
bnz method_update

method "destroy(application)bool"
txn ApplicationArgs 0
==
bnz method_destroy

err

handle_setup:
    int 1
    return

handle_update:
    txn Sender
    global CreatorAddress
    ==
    return

When using the ABI we rely on method descriptors, to facilitate checking against these descriptors the method TEAL opcode can be used. With ABI methods, the descriptor is placed in the first application argument. This allows comparing this argument to the exact descriptor defined in the ABI method specification. The above code is looking to branch on either deploy, update’, ordestroy`. Finally in the above code the setup or update from the initial code is handled. In the case of setup (i.e. when we first deploy the parent contract) the contract just returns with 1 on top of the stack, allowing the transaction to be successful. The update code handles the situation when the parent contract code is being updated. This code simply checks that the code can only be updated by the account that originally created the contract.

The deploy method implementation

The actual implementation of the deploy method is implemented with the following code. This code expects the first transaction in the group to be a payment transaction that funds the parent smart contract, so it can create the child contract.

method_deploy:
    // Check sender is funding the application with
    // enough Algo to deploy a smart contract.
    txn GroupIndex
    int 1
    -
    dup
    gtxns Receiver
    global CurrentApplicationAddress
    ==
    assert
    gtxns Amount
    int 100000
    >=
    assert

This code gets the current position index of the application call within the transaction group. Next it subtracts 1 because the payment transaction precedes the application call by 1 position. The dup command simply makes a copy of the final number, which should leave two integers on top of the stack. Both of these are equal and represent the position of the payment transaction in the group. The code then uses the top most integer to reference into the transaction group (using gtxns opcode) and look at the receiver of the previous transaction. It should be the address of the parent smart contract. If not, the code will fail at the assert opcode. This code will leave the final integer on the top of the stack that represents the previous transaction in the group. Using this index the gtxns opcode is used again to look up the last transaction’s amount value. If this is greater than or equal to 100000 microalgos the code will continue. If not the code will fail at the second assert.

Next the inner transaction is created to deploy the child contract.

    // Begin the inner transaction to deploy the new smart contract
    // using the approval and clear programs passed in as arguments.
    itxn_begin

    int appl
    itxn_field TypeEnum

    int NoOp
    itxn_field OnCompletion

    // Get the length and extract it, removing the first 2 bytes.
    txn ApplicationArgs 1
    dup
    len
    int 2
    swap
    substring3
    itxn_field ApprovalProgram

    // Get the length and extract it, removing the first 2 bytes.
    txn ApplicationArgs 2
    dup
    len
    int 2
    swap
    substring3
    itxn_field ClearStateProgram

    int 0
    itxn_field Fee

    itxn_submit

This code uses the itxn opcodes to setup and transmit the inner transaction. See the developer documentation for more details on inner transactions.

This code first sets the type of transaction to be an application transaction, sets the sub type of application transaction to be a NoOp, which is a general call to a smart contract, sets the approval program property to the bytes stored in the second argument, sets the clear program property to the bytes stored in the third argument, sets the transaction fee for the inner transaction to 0 (this means the transaction calling this method in the parent contract must pay the fee for the inner transaction), and then submits the transaction.

One important note here is that when the ABI encodes byte array parameters, the length of the byte array is stored in the first two bytes of the array. To deploy the child contract we need to strip off those first two bytes. This code handles that operation.

    txn ApplicationArgs 1
    dup
    len
    int 2
    swap
    substring3

EditorImages/2022/05/05 14:41/stringarg.jpg

If the submit fails, the parent application call will fail. If its successful, the code handles the ABI return type. In this case its an integer containing the value of the created child contract.

    // Using the ARC4 return string, concat the newly created appID.
    byte 0x151f7c75
    itxn CreatedApplicationID
    itob
    concat
    log

    int 1
    return

When using the ABI, return types are prepended with a set of bytes 0x151f7c75. In the above code these bytes are placed on top of the stack and the itxn CreatedApplicationID opcode is used to get the child contract’s application ID. This is first converted to bytes using itob and concatenated (concat opcode) to the above bytes. The log opcode logs the result to the completed transaction. The SDKs or goal look for the prepended bytes in the logs to extract the result of the method call. Finally a 1 is pushed to the strack and the return opcode is used to exit the method successfully.

The update method implementation

This method is used by the parent contract to update the smart contract code of the child contract. The update method is almost identical to the deploy method. The primary differences are that the application ID of the child contract is placed is in the second argument, and the approval and clear programs are in the third and forth argument. This method also returns a true value when successful, instead of the application ID of the child contract. The sub application transaction type is also set to UpdateApplication, which is an application transaction to update a smart contract, instead of NoOp.

method_update:
    itxn_begin

    int appl
    itxn_field TypeEnum

    int UpdateApplication
    itxn_field OnCompletion

    txn ApplicationArgs 1
    btoi
    txnas Applications
    itxn_field ApplicationID

    txn ApplicationArgs 2
    dup
    len
    int 2
    swap
    substring3
    itxn_field ApprovalProgram

    txn ApplicationArgs 3
    dup
    len
    int 2
    swap
    substring3
    itxn_field ClearStateProgram

    int 0
    itxn_field Fee

    itxn_submit

    byte 0x151f7c7580
    log

    int 1
    return

When passing an application ID as an ABI argument, the SDKs and goal automatically put this application ID in the applications array. This array limits what additional contracts can be examined or called within the current application. For more information on smart contract arrays, see the developer documentation. This code reads the application array from the call to the parent contract and sets the ApplicationID inner transaction property.

    txn ApplicationArgs 1
    btoi
    txnas Applications
    itxn_field ApplicationID

This property determines what contract to call in the inner transaction.

This code uses the same return code as in the deploy method but instead of concatenating the new child application ID, it adds 0x80 to the return bytes. This effectively signals the ABI a return type of true for boolean return values.

    byte 0x151f7c7580
    log

    int 1
    return

The destroy method implementation

This method is called to delete a child smart contract. This method has many of the same fields as the previous two methods. The application sub-type is set to DeletApplication, which is used to delete a smart contract. After deleting the child contract the parent contract also issues a payment transaction to return the original 10000 micro Algos deposited during the deploy method call. The TypeEnum property of the inner transaction is set to pay instead of appl to indicate a payment transaction. The sender defaults to the parent contract address, the receiver is set to the sender of the destroy method caller and the amount is set to 10000 micro Algos.

method_destroy:
    itxn_begin

    int appl
    itxn_field TypeEnum

    int DeleteApplication
    itxn_field OnCompletion

    txn ApplicationArgs 1
    btoi
    txnas Applications
    itxn_field ApplicationID

    int 0
    itxn_field Fee

    Itxn_submit

    //return the original deposit
    itxn_begin

    int pay
    itxn_field TypeEnum

    txn Sender
    itxn_field Receiver

    int 100000
    itxn_field Amount

    int 0
    itxn_field Fee

    itxn_submit

    byte 0x151f7c7580
    log

    int 1
    return

After issuing the two transactions, if they complete successfully, a true value is returned similar to the update method. If either of these fail all transactions including the original parent call all fail.