Child application deployment from parent application

Hi everyone, I’m struggling a little with a problem regarding the creation of a child application from the parent application.

I’m currently using beaker and pyteal for my project.
Inside the application call transaction ParentApp.parent_app_call() I’m trying to deploy a child application with an InnerTxn.

The definition of the parent_app_call is the following:

@external(authorize=Authorize.only(Global.creator_address()))
    def parent_app_call(self,
        parent_arg1: abi.Uint64,
        parent_arg2: abi.String,
        parent_arg3: abi.Uint64,
        *,
        output: abi.Uint64 # child_app_id
    ):
        return Seq(
            Assert(self.parent_state.get() == Int(1), comment="must be in ## state"),
            # Create the ChildApp
            # child_arg1: abi.Address,
            # child_arg2: abi.Address,
            # child_arg3: abi.Uint64,
            # child_arg4: abi.Uint64,
            # child_arg5: abi.String,
            InnerTxnBuilder.Begin(),
            InnerTxnBuilder.SetFields(
                {
                    TxnField.type_enum: TxnType.ApplicationCall,
                    TxnField.approval_program: self.child_app.approval.binary,
                    TxnField.clear_state_program: self.child_app.clear.binary,
                    TxnField.fee: Int(0),
                    TxnField.application_args: [
                            Global.creator_address(),
                            Global.current_application_address(),
                            Itob(parent_arg1.get()),
                            Itob(parent_arg3.get()),
                            parent_arg2.get()
                        ],
                }
            ),
            InnerTxnBuilder.Submit(),
            self.parent_state.set(Int(2)), # in ## phase
            output.set(InnerTxn.created_application_id())
        )

From the main I call parent_app_call as follows:

sp = creator_app_client.client.suggested_params()
sp.fee = sp.min_fee * 2
sp.flat_fee = True
result = creator_app_client.call(
    ParentApp.parent_app_call,
    parent_arg1=1,
    parent_arg2="string",
    parent_arg3=10,
    suggested_params=sp
)

The main executes without problems deploying the ParentApp and calling its methods, but parent_app_call is raising an error.

Traceback (most recent call last):
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/venv/lib/python3.10/site-packages/algosdk/v2client/algod.py", line 78, in algod_request
    resp = urlopen(req)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/urllib/request.py", line 216, in urlopen
    return opener.open(url, data, timeout)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/urllib/request.py", line 525, in open
    response = meth(req, response)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/urllib/request.py", line 634, in http_response
    response = self.parent.error(
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/urllib/request.py", line 563, in error
    return self._call_chain(*args)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/urllib/request.py", line 496, in _call_chain
    result = func(*args)
  File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/urllib/request.py", line 643, in http_error_default
    raise HTTPError(req.full_url, code, msg, hdrs, fp)
urllib.error.HTTPError: HTTP Error 400: Bad Request

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/venv/lib/python3.10/site-packages/beaker/client/application_client.py", line 514, in call
    result = atc.execute(self.client, 4)
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/venv/lib/python3.10/site-packages/algosdk/atomic_transaction_composer.py", line 483, in execute
    self.submit(client)
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/venv/lib/python3.10/site-packages/algosdk/atomic_transaction_composer.py", line 450, in submit
    client.send_transactions(self.signed_txns)
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/venv/lib/python3.10/site-packages/algosdk/v2client/algod.py", line 330, in send_transactions
    return self.send_raw_transaction(
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/venv/lib/python3.10/site-packages/algosdk/v2client/algod.py", line 260, in send_raw_transaction
    return self.algod_request("POST", req, data=txn, **kwargs)["txId"]
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/venv/lib/python3.10/site-packages/algosdk/v2client/algod.py", line 85, in algod_request
    raise error.AlgodHTTPError(e, code)
algosdk.error.AlgodHTTPError: TransactionPool.Remember: transaction JL5X7K4ELISIYP5AJYFBU6F5JFMIDANL5NSSHSRQUJMD45JJ7OUQ: logic eval error: logic eval error: err opcode executed. Details: pc=186, opcodes===
bnz label3
err
label3:
. Details: pc=1337, opcodes=extract 2 0
itxn_field ApplicationArgs
itxn_submit


During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/main_crowdfunding.py", line 129, in <module>
    demo()
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/main_crowdfunding.py", line 109, in demo
    result = creator_app_client.call(
  File "/Users/usr1/Projects/Blockchain/Algorand/PyTealLearning/project/venv/lib/python3.10/site-packages/beaker/client/application_client.py", line 517, in call
    raise self.wrap_approval_exception(e)
beaker.client.logic_error.LogicException: Txn JL5X7K4ELISIYP5AJYFBU6F5JFMIDANL5NSSHSRQUJMD45JJ7OUQ had error 'logic eval error: err opcode executed' at PC 186 and Source Line 2: 

        #pragma version 7
        intcblock 0 1 2 3
        bytecblock 0x63616d706169676e5f7374617465 0x636f6c6c65637465645f66756e6473 0x616d6f756e745f6261636b6564 0x63616d706169676e5f676f616c 0x66756e645f656e645f64617465 0x6d696c6573746f6e655f617070726f76616c5f6170705f6964 0x 0x746f74616c5f6261636b657273 0x746f74616c5f6d696c6573746f6e6573 0x726561636865645f6d696c6573746f6e65 0x66756e64735f7265636569766572 0x66756e645f73746172745f64617465 0x66756e64735f305f6d696c6573746f6e65 0x66756e64735f315f6d696c6573746f6e65 0x524e46545f6964 0x7265776172645f6d65746164617461            <-- Error
        txn NumAppArgs
        intc_0 // 0
        ==
        bnz main_l10

Trying to debug it with tealdbg and the following transaction, the exception is raised when the inner transaction is submitted itxn_submit.

goal app method --app-id 1 --method "parent_app_call(uint64,string,uint64)uint64" \
--from $ADDR --arg 1 --arg '"string"' --arg 10 \
--fee 2000 --dryrun-dump -o test.dr

Do you have any suggestions on how to tackle this problem? What am I missing?

Have you tried to call directly the child application from the SDK/goal?
Does it work?

Note also that if you’re using ABI for the inputs of the child application call, then:

            InnerTxnBuilder.SetFields(
                {
                    TxnField.type_enum: TxnType.ApplicationCall,
                    TxnField.approval_program: self.child_app.approval.binary,
                    TxnField.clear_state_program: self.child_app.clear.binary,
                    TxnField.fee: Int(0),
                    TxnField.application_args: [
                            Global.creator_address(),
                            Global.current_application_address(),
                            Itob(parent_arg1.get()),
                            Itob(parent_arg3.get()),
                            parent_arg2.get()
                        ],
                }
            ),

is most likely incorrect: arguments are most likely not encoded as expected.
Note for example that parent_arg2,get() returns the content of the string while the ABI encoded type actually requires you to prefix the string by its length (as a uint16).

You may want to instead use ABI Support — PyTeal documentation.

1 Like

Thanks for your answer @fabrice.

The child application is correctly deployed as a standalone using its create method.

I also think encoding the arguments on the inner transaction could be the problem.
What would be the correct way to invoke the create method using InnerTxnBuilder.MethodCall?

I’m trying as follows

InnerTxnBuilder.Begin(),
InnerTxnBuilder.MethodCall(
    app_id=Int(0),
    method_signature="create(address,address,uint64,uint64,string)void",
    args=[
        Global.creator_address(),
        Global.current_application_address(),
        parent_arg1,
        parent_arg3,
        parent_arg2
    ],
    extra_fields={
        TxnField.approval_program: self. child_app.approval.binary,
        TxnField.clear_state_program: self. child_app.clear.binary,
        TxnField.fee: Int(0)
    }
),
InnerTxnBuilder.Submit(),

but app_id=Int(0) is not an acceptable app reference:

beaker.client.logic_error.LogicException: Txn XSZKJYKCHVXFOPSWUPNWDOAUXC3Z4A3BP6SQ56VTJFUGZ2E6A5MA had error 'invalid App reference 0' at PC 858 and Source Line 418: 

        assert
        itxn_begin
        pushint 6 // appl
        itxn_field TypeEnum
        intc_0 // 0
        itxn_field ApplicationID                <-- Error
        pushbytes 0x22418c77 // "create(address,address,uint64,uint64,string)void"
        itxn_field ApplicationArgs
        global CreatorAddress
        itxn_field ApplicationArgs

The MethodCall function will try to submit an app call to the app id passed. Since 0 is not a valid app id its failing here.

You’ll have to use the normal way of executing inner transactions with SetField or Execute to call an ABI method during create until something is changed in pyteal.

The initial example you had should be close, but you need to pass the MethodSelector (4 byte hash of method sig) in as the first argument. You can get the correct MethodSelector by passing the same signature you have in the second example to the pyteal MethodSignature and setting that as the first arg of the arguments array of the inner transaction.

1 Like

I’ve submitted a PR to allow MethodCall to be used for an App Create but I’m not sure when it’ll be available through pip

1 Like

Hi @Ben,

This is definitely one of the problems I had. Moreover, I forgot to set also the global and local schema. With this solution, encoding the abi.String, now the transaction is executed correctly.

InnerTxnBuilder.Begin(),
InnerTxnBuilder.SetFields(
    {
        TxnField.type_enum: TxnType.ApplicationCall,
        TxnField.approval_program: self.child_app.approval.binary,
        TxnField.clear_state_program: self.child_app.clear.binary,
        TxnField.global_num_uints: Int(5),
        TxnField.global_num_byte_slices: Int(3),
        TxnField.local_num_uints: Int(1),
        TxnField.local_num_byte_slices: Int(0),
        TxnField.fee: Int(0),
        TxnField.application_args: [
                MethodSignature("create(address,address,uint64,uint64,string)void"),
                Global.creator_address(),
                Global.current_application_address(),
                parent_arg1.encode(),
                parent_arg3.encode(),
                parent_arg2.encode()
            ],
    }
),
InnerTxnBuilder.Submit(),

Great! In the latest beaker you can get most of those fields from the Precompile object itself:

note this example does not call an ABI method but returns a dict[TxnField, Expr] you can combine with other fields

That’s great!

In this case, for the previous example, I would have to create a comprehensive dictionary composed of the one returned by get_create_config() and the additional information for the other TxnFields I want to specify. Then the comprehensive dictionary could be passed to InnerTxnBuilder.Execute(comprehensive_dict).

Is my understanding correct?

That is exactly right.

Please file issues if you come across any problems w/ Beaker :handshake: