Debugging TEAL

Algorand provides the tealdbg command-line tool to launch an interactive session where a smart contract can be examined as the contract is being evaluated. The debugger supports both stateful and stateless smart contracts. You can debug individual transactions or group of transaction (eg atomic transfers). The debugger supports setting the specific context for debugging purposes, including transactions, round number, latest timestamp, balance records, etc. The debugger runs either local programs or accepts HTTP connections from remote evaluators configured to run with a remote debugger hook.

Using TEAL debugger with algob

Creating transaction data (via goal --dryrun-dump or SDK) could be a lengthy process, especially when using a transaction group. Algob provides an easy way to use debugger: by simply supplying the transactions as an input to the TealDbg method (same transaction parameters that we supply to executeTransaction to execute same transaction on network).

NOTE: You use the TealDbg method in an algob script, which can be run using algob deploy/algob run commands.

Using dry run for debugging a TEAL program in an algob script

Algob provides the functionality to do a test run of a TEAL smart contract. This option is useful to capture a transaction in an output file with associated state of the smart contract. This allows testing the TEAL logic in a dry run which allows to step by step follow the TEAL execution and inspect transaction approval or rejection.

The dry run response from the Algorand REST API includes disassembly, logic signature messages with PASS/REJECT, a signature trace, app call messages, and an app call trace.

NOTE: The dry run REST API is only available on a node if it has been enabled in the node’s configuration (EnableDeveloperAPI = true). Example:

// txnParams are the input transactions
const debugger = new Tealdbg(deployer, txnParam);
await debugger.dryRunResponse('dryrun.json');

The dry run Response will be dumped to assets/dryrun.json.

Starting a debugging session

Instead of a dry run execution, you can also start a debugging session (for example, with Chrome Developer Tools) in an algob script. This is helpful for setting up breakpoints in code, inspecting state after line by line execution etc.

Following params could be passed to debugger context:

  • tealFile: Name of teal file (present in assets/) to pass to debugger.
  • mode: Execution mode, either signature or application. Matches to Algod’s evaluation mode for logic signature TEAL or application call TEAL. Read more about execution modes here.
  • --group-index: In case of a transaction group, group index should be passed to specify the current transaction in group in a debugger session.

NOTE: 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.

// txnParams are the input transactions
const debugger = new Tealdbg(deployer, txnParam);
await debugger.run({ tealFile: '4-gold-asa.teal' }); // 4-gold-asa.teal present in assets/**/

NOTE: To configure the chrome listener for a debugging session, read Configure the Listener.

Example Walkthrough (Stateless TEAL)

In this section we will try to debug a stateless transaction in /examples/asa.

The smart contract used is 4-gold-asa.teal which ensures:

  • Transaction type is asset transfer and AssetAmount <= 1000.
  • Sender is goldOwner account.

First we need to deploy the contracts using algob deploy. We will debug a transaction defined in scripts/transfer/gold-delegated-lsig.js.

Dry Run

Setting up transaction params (note that this is a passing scenario as amount = 500 <= 1000):

// load signed lsig 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 }
};

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

const debug = new Tealdbg(deployer, txnParam);
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 4",
        "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
            }
          ]
        },
        ...

Starting a 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

Script used in this walkthrough is gold-delegated-lsig.debug.js and can be found here.

Example Walkthrough (Stateful TEAL)

In this section we will try to debug a stateful transaction (in a group) in /examples/permissioned-token-freezing.

There are 2 smart contracts (1 stateful & 1 stateless):

  • poi-approval.teal: Asserts a minimum level(stored in global state) set of a user. Rejects if assertion is failed.
  • clawback-escrow.py: A clawback account that is a stateless contract (lsig). For more details, check the project README.

Setting up 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.CallApp,
    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 construcing 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

This walkthrough can be found in /examples/permissioned-token-freezing/scripts/transfer/transfer-asset.debug.js