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.

Tutorial Thumbnail
Intermediate · 30 minutes

Using TEAL debugger with algo-buiilder

This is the seventh tutorial from the Algo Builder series:

In this tutorial we present how to use TEAL debugger with algob (debugging smart contracts with dry run OR launch a live debugging session).

Requirements

  • Brief knowledge about Blockchain and Algorand.
  • Brief introduction to algob.
  • Brief knowledge about assets, accounts, transactions and signatures.
  • Brief knowledge about tealdbg command.

Info

Algo Builder fills a gap in the Algorand ecosystem to create and manage projects. Our goal is to make shipping Algorand applications simple, efficient, and scalable. Think about it as a Truffle suite for Algorand. For more info, check our articles in the developer portal.

Background

We can debug Algorand Smart Contracts & Signatures using:

  1. TEAL Debugger: the tealdbg command-line tool launches an interactive session where a smart contract can be examined as the contract is being evaluated. You can debug individual transactions or group of transaction (eg atomic transfers). Read more about tealdbg in the project’s README.

  2. Dryrun for Debugging a TEAL Program: the Algorand SDKs and the goal command-line tool provide functionality to do a test run of a TEAL smart contract. This allows to test a TEAL logic and shows how a TEAL program is executed.

Debugging Smart contracts with Algo builder

Creating a transaction (via goal --dryrun-dump or SDK) could be a lengthy process, especially when using a transaction group. Algob provides an easy way to create transaction and use debugger. By simply supplying the transactions as parameters to the TealDbg algob object (the constructor has same transaction parameters as the executeTransaction function).
You can create a new TealDbg object in an algob script:

async function run (runtimeEnv, deployer) {
  ..
  const tealdbg = new TealDbg(deployer, txParams); // txParams are the input transaction parameters
}

Using dry run for debugging TEAL

TealDbg object uses the Alogrand SDK to dry-run a smart contract.

 await tealdbg.dryRunResponse('dryrun.json');

This will dump the dry run execution in assets/dryrun.json file. It includes: disassembly, logic sig messages with PASS/REJECT, a sig trace, app call messages, and an app call trace. To get a dryrun response call:

Info

The dry run REST API is only available if the node has been enabled EnableDeveloperAPI = true. If you have created private-net using our make scripts (setup-private-net, recreate-private-net), then this would already be set as true.

Using the TEAL Debugger

You can also launch an interactive debugging session (for example, with Chrome Developer Tools) in an algob script. This is helpful for setting up breakpoints in code, inspecting state (top of stack, local state, app global state ..) and do line by line execution.

await tealdbg.run({ tealFile: '<file.teal>' }); // teal file must be present in assets/**/

This will start a remote debugging session in your Chrome Developer Tools.

Following params could be provided to the debugger context: * tealFile: Name of (py)TEAL file (present in assets/**). * mode: Execution mode, either signature or application. * --group-index: In case of a transaction group, group index should be passed to specify the current transaction in group for debugger session.

Info

Passing tealFile is optional, but recommended. If not passed, debugger is run with the decompiled version of teal code. Supplying the program will allow debugging the original source and not the decompiled version.

In the next section we will try to debug some contracts from our /examples using TealDbg in algob scripts.

Steps

1. Debugging Single transaction: Smart Contract/Smart Signature

Smart Signature

In this section we will try to debug a smart signature(logicsic) contract transaction in /examples/asa in delegated approval mode.

Smart signature:

#pragma version 2
// Accepts only if (transaction type is OPT-IN OR (transaction type is asset transfer,
// sender is goldOwnerAccount and asset transfer amount is less than equal to 1000 ))
global GroupSize
int 1
==
txn GroupIndex
int 0
==
&&
txn AssetAmount
int 0
==
&&
txn TypeEnum
int 4
==
txn Sender
addr EDXG4GGBEHFLNX6A7FGT3F6Z3TQGIU6WVVJNOXGYLVNTLWDOCEJJ35LWJY
==
&&
txn AssetAmount
int 1000
<=
&&
||
txn TypeEnum
int 4
==
txn RekeyTo
global ZeroAddress
==
&&
txn CloseRemainderTo
global ZeroAddress
==
&&
txn Fee
int 10000
<=
&&
&&

This contract ensures:
+ Transaction type is asset transfer and AssetAmount <= 1000.
+ Sender is goldOwner account.

Creating transaction:
Let’s create a transfer asset transaction from the above lsig contract to john:

// load signed lsig (by gold owner account) from checkpoint
const lsigGoldOwner = deployer.getDelegatedLsig('4-gold-asa.teal');
const txnParam = {
  type: types.TransactionType.TransferAsset,
  sign: types.SignType.LogicSignature,
  fromAccountAddr: goldOwner.addr,
  toAccountAddr: john.addr,
  amount: 500,
  assetID: 'gold',
  lsig: lsigGoldOwner,
  payFlags: { totalFee: 1000 }
};

Dry Run

After setting up the transaction, let’s try a dry run by adding the following lines to the script:

const debug = new Tealdbg(deployer, txnParam);
// Transaction PASS (logic-sig-messages == PASS in assets/dry-run-pass.json)
await debug.dryRunResponse('dryrun-pass.json');

It will create a assets/dryrun-pass.json file, which looks like (notice the logic-sig-message is PASS):

{
  "error": "",
  "protocol-version": "https://github.com/algorandfoundation/specs/tree/d050b3cade6d5c664df8bd729bf219f179812595",
  "txns": [
    {
      "disassembly": [
        "#pragma version 2",
        "intcblock 1 0 4 1000 10000",
        "bytecblock 0x20ee6e18c121cab6dfc0f94d3d97d9dce06453d6ad52d75cd85d5b35d86e1112",
        "global GroupSize",
        "intc_0 // 1",
        "==",
        "txn GroupIndex",
        "intc_1 // 0",
        "==",
        "&&",
        "txn AssetAmount",
        "intc_1 // 0",
        "==",
        "&&",
        "txn TypeEnum",
        "intc_2 // 4",
        "==",
        "txn Sender",
        "bytec_0 // addr EDXG4GGBEHFLNX6A7FGT3F6Z3TQGIU6WVVJNOXGYLVNTLWDOCEJJ35LWJY",
        "==",
        "&&",
        "txn AssetAmount",
        "intc_3 // 1000",
        "<=",
        "&&",
        "||",
        "txn TypeEnum",
        "intc_2 // 4",
        "==",
        "txn RekeyTo",
        "global ZeroAddress",
        "==",
        "&&",
        "txn CloseRemainderTo",
        "global ZeroAddress",
        "==",
        "&&",
        "txn Fee",
        "intc 4 // 10000",
        "<=",
        "&&",
        "&&",
        ""
      ],
      "logic-sig-messages": [
        "PASS"
      ],
      "logic-sig-trace": [
        {
          "line": 1,
          "pc": 1,
          "stack": []
        },
        {
          "line": 2,
          "pc": 10,
          "stack": []
        },
        {
          "line": 3,
          "pc": 45,
          "stack": []
        },
        {
          "line": 4,
          "pc": 47,
          "stack": [
            {
              "bytes": "",
              "type": 2,
              "uint": 1
            }
          ]
        },
        ...

Example: Dry Run for a failing scenario (asset amount > 1000):

// Transaction FAIL - rejected by lsig because amount is not <= 1000
// (logic-sig-messages = "REJECT" in assets/dry-run-fail.json)
debug.execParams = { ...txnParam, amount: 1500 };
await debug.dryRunResponse('dryrun-fail.json', true); // passing true overwrites existing .json file with same name

Launching an interactive debugging session:

Let’s try to launch a new debugger session with the same txnParam as in the above section:

await debug.run({ tealFile: '4-gold-asa.teal' });

This will start a new debugger session. Console looks like:

2021/07/16 04:29:12 Using proto: https://github.com/algorandfoundation/specs/tree/d050b3cade6d5c664df8bd729bf219f179812595
2021/07/16 04:29:12 Run mode: logicsig
2021/07/16 04:29:12 ------------------------------------------------
2021/07/16 04:29:12 CDT debugger listening on: ws://127.0.0.1:9392/75c19568422ff120671707bc2682e7a3ae6861fe3bdac37571b860efd417e7d7
2021/07/16 04:29:12 Or open in Chrome:
2021/07/16 04:29:12 devtools://devtools/bundled/js_app.html?experiments=true&v8only=false&ws=127.0.0.1:9392/75c19568422ff120671707bc2682e7a3ae6861fe3bdac37571b860efd417e7d7
2021/07/16 04:29:12 ------------------------------------------------

Now, we have a remote target set up in chrome://inspect

image

Click on inspect and start playing with the TEAL code!

image

Smart Contract

In this section we will debug a simple smart contract which stores a counter in it’s global state. This contract can be found here. Each NoOp call to this contract increments the global state by 1.

Let’s set up this transaction:

// load app info from checkpoint
const  appInfo = deployer.getApp('approval_program.teal', 'clear_program.teal');
const txParam = {
  type: types.TransactionType.CallNoOpSSC,
  sign: types.SignType.SecretKey,
  fromAccount: creatorAccount,
  appID: appInfo.appID,
  payFlags: {}
};

Dry Run for this tx:

const debug = new Tealdbg(deployer, txParam);
await debug.dryRunResponse('dryrun.json');

This should output the global state (i.e current global state value of counter incremented by 1) in response.

Launch TEAL debugger

To launch the TEAL debugger, simply do:

await new Tealdbg(deployer, txParam)
  .run({ tealFile: "approval_program.teal" });

2. Debugging group transaction: [Stateless TEAL + Stateful TEAL + transfer Algo]

In this section we will try to debug a stateful transaction (in a group) using /examples/permissioned-token-freezing example. This example demonstrates how to create a permissioned ASA using smart contracts to control ASA transfers. For more details, check the project README.

There are 3 smart contracts (2 stateful & 1 signature): * poi-approval.teal: Asserts a minimum level(stored in global state) set of a user. Rejects if assertion is failed. * poi-clear.teal: Clear program (returns 1). * clawback-escrow.py: A clawback account that is a signature contract (lsig).

Creating a transaction group:

// load app, asset info from checkpoint
const appInfo = deployer.getApp('poi-approval.teal', 'poi-clear.teal');
const assetInfo = deployer.asa.get('gold');

// load logic signature
const escrowParams = {
  ASSET_ID: assetInfo.assetIndex,
  APP_ID: appInfo.appID
};
const escrowLsig = await deployer.loadLogic('clawback-escrow.py', escrowParams);
const escrowAddress = escrowLsig.address();

const txGroup = [
  {
    type: types.TransactionType.CallNoOpSSC,
    sign: types.SignType.SecretKey,
    fromAccount: creator,
    appID: appInfo.appID,
    payFlags: { totalFee: 1000 },
    appArgs: ['str:check-level'],
    accounts: [bob.addr] //  AppAccounts
  },
  {
    type: types.TransactionType.RevokeAsset,
    sign: types.SignType.LogicSignature,
    fromAccountAddr: escrowAddress,
    recipient: bob.addr,
    assetID: assetInfo.assetIndex,
    revocationTarget: creator.addr,
    amount: 1000,
    lsig: escrowLsig,
    payFlags: { totalFee: 1000 }
  },
  {
    type: types.TransactionType.TransferAlgo,
    sign: types.SignType.SecretKey,
    fromAccount: creator,
    toAccountAddr: escrowAddress,
    amountMicroAlgos: 1000,
    payFlags: { totalFee: 1000 }
  }
];

In this group:
* tx0 is an application call which checks minimum level of an account (in local state)
* tx1 is an asset transfer transaction using a clawback lsig (clawback-escrow.py)
* tx2 is an algo transfer transaction to pay fees of tx1

NOTE: Account’s level can be set by executing /permissioned-token-freezing/scripts/transfer/set-clear-level.js. If a min level is set, then the dry run transaction will PASS, otherwise the “app-call-messages” would be REJECT.

Dry Run

To get dry run response of the transaction group, simply do:

// reponse is dumped to assets/dryrun.json
await debug.dryRunResponse('dryrun.json');

The dry run response has all 3 txns, with “app-call-messages”(PASS/REJECT) for tx0, and “logic-sig-messages” for tx1. Response will be null for tx2 (as this is a simple algo transfer tx).
As explained above, the message could be PASS/REJECT depending on the minlevel set.

Start Debugger

To debug a specific transaction in a group, pass the groupIndex parameter. Let’s try to start the debugger for tx0:

await debug.run({ tealFile: "poi-approval.teal", groupIndex: 0 });

That’s it, the code above will start a live debugging session.
image
Notice that in Scope we have appGlobal, appLocals as we upload the applications and ledger state while constructing request for tealdbg debug.

To debug 2nd transaction (clawbackLsig) in group, update the tealFile & groupIndex:

await debug.run({
  tealFile: "clawback-escrow.py",
  scInitParam: escrowParams, // template paramters to pyTEAL file
  groupIndex: 1 // pass index of transaction to debug
});

image