🚧 This ARC had no activity for at least 6 months.

ARC-4: Application Binary Interface (ABI) Source

Conventions for encoding method calls in Algorand Application

AuthorJannotti, Jason Paulos
Discussions-Tohttps://github.com/algorandfoundation/ARCs/issues/44
StatusStagnant
TypeStandards Track
CategoryInterface
Created2021-07-29

Algorand Transaction Calling Conventions

Abstract

This document introduces conventions for encoding method calls, including argument and return value encoding, in Algorand Application call transactions. The goal is to allow clients, such as wallets and dapp frontends, to properly encode call transactions based on a description of the interface. Further, explorers will be able to show details of these method invocations.

Definitions

  • Application: an Algorand Application, aka “smart contract”, “stateful contract”, “contract”, or “app”.
  • HLL: a higher level language that compiles to TEAL bytecode.
  • dapp (frontend): a decentralized application frontend, interpreted here to mean an off-chain frontend (a webapp, native app, etc.) that interacts with Applications on the blockchain.
  • wallet: an off-chain application that stores secret keys for on-chain accounts and can display and sign transactions for these accounts.
  • explorer: an off-chain application that allows browsing the blockchain, showing details of transactions.

Specification

The key words “MUST”, “MUST NOT”, “REQUIRED”, “SHALL”, “SHALL NOT”, “SHOULD”, “SHOULD NOT”, “RECOMMENDED”, “MAY”, and “OPTIONAL” in this document are to be interpreted as described in RFC-2119.

Comments like this are non-normative.

Interfaces are defined in TypeScript. All the objects that are defined are valid JSON objects, and all JSON string types are UTF-8 encoded.

Overview

This document makes recommendations for encoding method invocations as Application call transactions, and for describing methods for access by higher-level entities. Encoding recommendations are intended to be minimal, intended only to allow interoperability among Applications. Higher level recommendations are intended to enhance user-facing interfaces, such as high-level languages, dapps, and wallets. Applications that follow the recommendations described here are called ARC-4 Applications.

Methods

A method is a section of code intended to be invoked externally with an Application call transaction. A method must have a name, it may take a list of arguments as input when it is invoked, and it may return a single value (which may be a tuple) when it finishes running. The possible types for arguments and return values are described later in the Encoding section.

Invoking a method involves creating an Application call transaction to specifically call that method. Methods are different from internal subroutines that may exist in a contract, but are not externally callable. Methods may be invoked by a top-level Application call transaction from an off-chain caller, or by an Application call inner transaction created by another Application.

Method Signature

A method signature is a unique identifier for a method. The signature is a string that consists of the method’s name, an open parenthesis, a comma-separated list of the types of its arguments, a closing parenthesis, and the method’s return type, or void if it does not return a value. The names of the arguments MUST NOT be included in a method’s signature, and MUST NOT contain any whitespace.

For example, add(uint64,uint64)uint128 is the method signature for a method named add which takes two uint64 parameters and returns a uint128. Signatures are encoded in ASCII.

For the benefit of universal interoperability (especially in HLLs), names MUST satisfy the regular expression [_A-Za-z][A-Za-z0-9_]*. Names starting with an underscore are reserved and MUST only be used as specified in this ARC or future ABI-related ARC.

Method Selector

Method signatures contain all the information needed to identify a method, however the length of a signature is unbounded. Rather than consume program space with such strings, a method selector is used to identify methods in calls. A method selector is the first four bytes of the SHA-512/256 hash of the method signature.

For example, the method selector for a method named add which takes two uint64 parameters and returns a uint128 can be computed as follows:

Method signature: add(uint64,uint64)uint128
SHA-512/256 hash (in hex): 8aa3b61f0f1965c3a1cbfa91d46b24e54c67270184ff89dc114e877b1753254a
Method selector (in hex): 8aa3b61f

Method Description

A method description provides further information about a method beyond its signature. This description is encoded in JSON and consists of a method’s name, description (optional), arguments (their types, and optional names and descriptions), and return type and optional description for the return type. From this structure, the method’s signature and selector can be calculated. The Algorand SDKs provide convenience functions to calculate signatures and selectors from such JSON files.

These details will enable high-level languages and dapps/wallets to properly encode arguments, call methods, and decode return values. This description can populate UIs in dapps, wallets, and explorers with description of parameters, as well as populate information about methods in IDEs for HLLs.

The JSON structure for such an object is:

interface Method {
  /** The name of the method */
  name: string;
  /** Optional, user-friendly description for the method */
  desc?: string;
  /** The arguments of the method, in order */
  args: Array<{
    /** The type of the argument */
    type: string;
    /** Optional, user-friendly name for the argument */
    name?: string;
    /** Optional, user-friendly description for the argument */
    desc?: string;
  }>;
  /** Information about the method's return value */
  returns: {
    /** The type of the return value, or "void" to indicate no return value. */
    type: string;
    /** Optional, user-friendly description for the return value */
    desc?: string;
  };
}

For example:

{
  "name": "add",
  "desc": "Calculate the sum of two 64-bit integers",
  "args": [
    { "type": "uint64", "name": "a", "desc": "The first term to add" },
    { "type": "uint64", "name": "b", "desc": "The second term to add" }
  ],
  "returns": { "type": "uint128", "desc": "The sum of a and b" }
}

Interfaces

An Interface is a logically grouped set of methods. All method selectors in an Interface MUST be unique. Method names MAY not be unique, as long as the corresponding method selectors are different. Method names in Interfaces MUST NOT begin with an underscore.

An Algorand Application implements an Interface if it supports all of the methods from that Interface. An Application MAY implement zero, one, or multiple Interfaces.

Interface designers SHOULD try to prevent collisions of method selectors between Interfaces that are likely to be implemented together by the same Application.

For example, an Interface Calculator providing addition and subtraction of integer methods and an Interface NumberFormating providing formatting methods for numbers into strings are likely to be used together. Interface designers should ensure that all the methods in Calculator and NumberFormatting have distinct method selectors.

Interface Description

An Interface description is a JSON object containing the JSON descriptions for each of the methods in the Interface.

The JSON structure for such an object is:

interface Interface {
  /** A user-friendly name for the interface */
  name: string;
  /** Optional, user-friendly description for the interface */
  desc?: string;
  /** All of the methods that the interface contains */
  methods: Method[];
}

Interface names MUST satisfy the regular expression [_A-Za-z][A-Za-z0-9_]*. Interface names starting with ARC are reserved to interfaces defined in ARC. Interfaces defined in ARC-XXXX (where XXXX is a 0-padded number) SHOULD start with ARC_XXXX.

For example:

{
  "name": "Calculator",
  "desc": "Interface for a basic calculator supporting additions and multiplications",
  "methods": [
    {
      "name": "add",
      "desc": "Calculate the sum of two 64-bit integers",
      "args": [
        { "type": "uint64", "name": "a", "desc": "The first term to add" },
        { "type": "uint64", "name": "b", "desc": "The second term to add" }
      ],
      "returns": { "type": "uint128", "desc": "The sum of a and b" }
    },
    {
      "name": "multiply",
      "desc": "Calculate the product of two 64-bit integers",
      "args": [
        { "type": "uint64", "name": "a", "desc": "The first factor to multiply" },
        { "type": "uint64", "name": "b", "desc": "The second factor to multiply" }
      ],
      "returns": { "type": "uint128", "desc": "The product of a and b" }
    }
  ]
}

Contracts

A Contract is a declaration of what an Application implements. It includes the complete list of the methods implemented by the related Application. It is similar to an Interface, but it may include further details about the concrete implementation, as well as implementation-specific methods that do not belong to any Interface. All methods in a Contract MUST be unique; specifically, each method MUST have a unique method selector.

Method names in Contracts MAY begin with underscore, but these names are reserved for use by this ARC and future extensions of this ARC.

OnCompletion Actions and Creation

In addition to the set of methods from the Contract’s definition, a Contract MAY allow Application calls with zero arguments, also known as bare Application calls. Since method invocations with zero arguments still encode the method selector as the first Application call argument, bare Application calls are always distinguishable from method invocations.

The primary purpose of bare Application calls is to allow the execution of an OnCompletion (apan) action which requires no inputs and has no return value. A Contract MAY allow this for all OnCompletion actions, for only a subset of them, or for none at all. Great care should be taken when allowing these operations.

If a Contract elects to allow bare Application calls for some OnCompletion actions, then that Contract SHOULD also allow any of its methods to be called with those OnCompletion actions, as long as this would not cause undesirable or nonsensical behavior.

The reason for this is because if it’s acceptable to allow an OnCompletion action to take place in isolation inside of a bare Application call, then it’s most likely acceptable to allow the same action to take place at the same time as an ABI method call. And since the latter can be accomplished in just one transaction, it can be more efficient.

If a Contract requires an OnCompletion action to take inputs or to return a value, then the RECOMMENDED behavior of the Contract is to not allow bare Application calls for that OnCompletion action. Rather, the Contract should have one or more methods that are meant to be called with the appropriate OnCompletion action set in order to process that action.

If an Application is called with greater than zero Application call arguments (NOT a bare Application call), the Application MUST always treat the first argument as a method selector and invoke the specified method, regardless of the OnCompletion action of the Application call. This applies to Application creation transactions as well, where the supplied Application ID is 0.

Similar to OnCompletion actions, if a Contract requires its creation transaction to take inputs or to return a value, then the RECOMMENDED behavior of the Contract should be to not allow bare Application calls for creation. Rather, the Contract should have one or more methods that are meant to be called in order to create the Contract.

Contract Description

A Contract description is a JSON object containing the JSON descriptions for each of the methods in the Contract.

The JSON structure for such an object is:

interface Contract {
  /** A user-friendly name for the contract */
  name: string;
  /** Optional, user-friendly description for the interface */
  desc?: string;
  /**
   * Optional object listing the contract instances across different networks
   */
  networks?: {
    /**
     * The key is the base64 genesis hash of the network, and the value contains
     * information about the deployed contract in the network indicated by the
     * key
     */
    [network: string]: {
      /** The app ID of the deployed contract in this network */
      appID: number;
    }
  }
  /** All of the methods that the contract implements */
  methods: Method[];
}

Contract names MUST satisfy the regular expression [_A-Za-z][A-Za-z0-9_]*.

The desc fields of the Contract and the methods inside the Contract SHOULD contain informations that is not explicitly encoded in the other fields, such as support of bare Application calls, requirement of specific OnCompletion action for specific methods, and methods to call for creation (if creation cannot be done via a bare Application call).

For example:

{
  "name": "Calculator",
  "desc": "Contract of a basic calculator supporting additions and multiplications. Implements the Calculator interface.",
  "networks": {
    "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=": { "appID": 1234 },
    "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=": { "appID": 5678 },
  },
  "methods": [
    {
      "name": "add",
      "desc": "Calculate the sum of two 64-bit integers",
      "args": [
        { "type": "uint64", "name": "a", "desc": "The first term to add" },
        { "type": "uint64", "name": "b", "desc": "The second term to add" }
      ],
      "returns": { "type": "uint128", "desc": "The sum of a and b" }
    },
    {
      "name": "multiply",
      "desc": "Calculate the product of two 64-bit integers",
      "args": [
        { "type": "uint64", "name": "a", "desc": "The first factor to multiply" },
        { "type": "uint64", "name": "b", "desc": "The second factor to multiply" }
      ],
      "returns": { "type": "uint128", "desc": "The product of a and b" }
    }
  ]
}

Method Invocation

In order for a caller to invoke a method, the caller and the method implementation (callee) must agree on how information will be passed to and from the method. This ABI defines a standard for where this information should be stored and for its format.

Standard Format

The method selector must be the first Application call argument (index 0), accessible as txna ApplicationArgs 0 from TEAL (except for bare Application calls, which use zero application call arguments).

If a method has 15 or fewer arguments, each argument MUST be placed in order in the following Application call argument slots (indexes 1 through 15). The arguments MUST be encoded as defined in the Encoding section.

Otherwise, if a method has 16 or more arguments, the first 14 MUST be placed in order in the following Application call argument slots (indexes 1 through 14), and the remaining arguments MUST be encoded as a tuple in the final Application call argument slot (index 15). The arguments must be encoded as defined in the Encoding section.

If a method has a non-void return type, then the return value of the method MUST be located in the final logged value of the method’s execution, using the log opcode. The logged value MUST contain a specific 4 byte prefix, followed by the encoding of the return value as defined in the Encoding section. The 4 byte prefix is defined as the first 4 bytes of the SHA-512/256 hash of the ASCII string return. In hex, this is 151f7c75.

For example, if the method add(uint64,uint64)uint128 wanted to return the value 4160, it would log the byte array 151f7c7500000000000000000000000000001040 (shown in hex).

Implementing a Method

An ARC-4 Application implementing a method:

  1. MUST check if txn NumAppArgs equals 0. If true, then this is a bare Application call. If the Contract supports bare Application calls for the current transaction parameters (it SHOULD check the OnCompletion action and whether the transaction is creating the application), it MUST handle the call appropriately and either approve or reject the transaction. The following steps MUST be ignored in this case. Otherwise, if the Contract does not support this bare application call, the Contract MUST reject the transaction.

  2. MUST examine txna ApplicationArgs 0 to identify the selector of the method being invoked. If the contract does not implement a method with that selector, the Contract MUST reject the transaction.

  3. MUST execute the actions required to implement the method being invoked. In general, this works by branching to the body of the method indicated by the selector.

  4. The code for that method MAY extract the arguments it needs, if any, from the application call arguments as described in the Encoding section. If the method has more than 15 arguments and the contract needs to extract an argument beyond the 14th, it MUST decode txna ApplicationArgs 15 as a tuple to access the arguments contained in it.

  5. If the method is non-void, the Application MUST encode the return value as described in the Encoding section and then log it with the prefix 151f7c75. Other values MAY be logged before the return value, but other values MUST NOT be logged after the return value.

Calling a Method from Off-Chain

To invoke an ARC-4 Application, an off-chain system, such as a dapp or wallet, would first obtain the Interface or Contract description JSON object for the app. The client may now:

  1. Create an Application call transaction with the following parameters:
    1. Use the ID of the desired Application whose program code implements the method being invoked, or 0 if they wish to create the Application.
    2. Use the selector of the method being invoked as the first Application call argument.
    3. Encode all arguments for the method, if any, as described in the Encoding section. If the method has more than 15 arguments, encode all arguments beyond (but not including) the 14th as a tuple into the final Application call argument.
  2. Submit this transaction and wait until it successfully commits to the blockchain.
  3. Decode the return value, if any, from the ApplyData’s log information.

Clients MAY ignore the return value.

An exception to the above instructions is if the app supports bare Application calls for some transaction parameters, and the client wishes to invoke this functionality. Then the client may simply create and submit to the network an Application call transaction with the ID of the Application (or 0 if they wish to create the application) and the desired OnCompletion value set. Application arguments MUST NOT be present.

Encoding

This section describes how ABI types can be represented as byte strings.

Like the EthereumABI, this encoding specification is designed to have the following two properties:

  1. The number of non-sequential “reads” necessary to access a value is at most the depth of that value inside the encoded array structure. For example, at most 4 reads are needed to retrieve a value at a[i][k][l][r].
  2. The encoding of a value or array element is not interleaved with other data and it is relocatable, i.e. only relative “addresses” (indexes to other parts of the encoding) are used.

Types

The following types are supported in the Algorand ABI.

  • uint<N>: An N-bit unsigned integer, where 8 <= N <= 512 and N % 8 = 0. When this type is used as part of a method signature, N must be written as a base 10 number without any leading zeros.
  • byte: An alias for uint8.
  • bool: A boolean value that is restricted to either 0 or 1. When encoded, up to 8 consecutive bool values will be packed into a single byte.
  • ufixed<N>x<M>: An N-bit unsigned fixed-point decimal number with precision M, where 8 <= N <= 512, N % 8 = 0, and 0 < M <= 160, which denotes a value v as v / (10^M). When this type is used as part of a method signature, N and M must be written as base 10 numbers without any leading zeros.
  • <type>[<N>]: A fixed-length array of length N, where N >= 0. type can be any other type. When this type is used as part of a method signature, N must be written as a base 10 number without any leading zeros, unless N is zero, in which case only a single 0 character should be used.
  • address: Used to represent a 32-byte Algorand address. This is equivalent to byte[32].
  • <type>[]: A variable-length array. type can be any other type.
  • string: A variable-length byte array (byte[]) assumed to contain UTF-8 encoded content.
  • (T1,T2,…,TN): A tuple of the types T1, T2, …, TN, N >= 1.
  • reference types account, asset, application: MUST NOT be used as the return type. For encoding purposes they are an alias for uint8. See section “Reference Types” below.

Additional special use types are defined in Reference Types and Transaction Types.

Static vs Dynamic Types

For encoding purposes, the types are divided into two categories: static and dynamic.

The dynamic types are:

  • <type>[] for any type
    • This includes string since it is an alias for byte[].
  • <type>[<N>] for any dynamic type
  • (T1,T2,...,TN) if Ti is dynamic for some 1 <= i <= N

All other types are static. For a static type, all encoded values of that type have the same length, irrespective of their actual value.

Encoding Rules

Let len(a) be the number of bytes in the binary string a. The returned value shall be considered to have the ABI type uint16.

Let enc be a mapping from values of the ABI types to binary strings. This mapping defines the encoding of the ABI.

For any ABI value x, we recursively define enc(x) to be as follows:

  • If x is a tuple of N types, (T1,T2,...,TN), where x[i] is the value at index i, starting at 1:
    • enc(x) = head(x[1]) ... head(x[N]) tail(x[1]) ... tail(x[N])
    • Let head and tail be mappings from values in this tuple to binary strings. For each i such that 1 <= i <= N, these mappings are defined as:
      • If Ti (the type of x[i]) is static:
        • If Ti is bool:
          • Let after be the largest integer such that all T(i+j) are bool, for 0 <= j <= after.
          • Let before be the largest integer such that all T(i-j) are bool, for 0 <= j <= before.
          • If before % 8 == 0:
            • head(x[i]) = enc(x[i]) | (enc(x[i+1]) >> 1) | ... | (enc(x[i + min(after,7)]) >> min(after,7)), where >> is bitwise right shift which pads with 0, | is bitwise or, and min(x,y) returns the minimum value of the integers x and y.
            • tail(x[i]) = "" (the empty string)
          • Otherwise:
            • head(x[i]) = "" (the empty string)
            • tail(x[i]) = "" (the empty string)
        • Otherwise:
          • head(x[i]) = enc(x[i])
          • tail(x[i]) = "" (the empty string)
      • Otherwise:
        • head(x[i]) = enc(len( head(x[1]) ... head(x[N]) tail(x[1]) ... tail(x[i-1]) ))
        • tail(x[i]) = enc(x[i])
  • If x is a fixed-length array T[N]:
    • enc(x) = enc((x[0], ..., x[N-1])), i.e. it’s encoded as if it were an N element tuple where every element is type T.
  • If x is a variable-length array T[] with k elements:
    • enc(x) = enc(k) enc([x[0], ..., x[k-1]]), i.e. it’s encoded as if it were a fixed-length array of k elements, prefixed with its length, k encoded as a uint16.
  • If x is an N-bit unsigned integer, uint<N>:
    • enc(x) is the N-bit big-endian encoding of x.
  • If x is an N-bit unsigned fixed-point decimal number with precision M, ufixed<N>x<M>:
    • enc(x) = enc(x * 10^M), where x * 10^M is interpreted as a uint<N>.
  • If x is a boolean value bool:
    • enc(x) is a single byte whose most significant bit is either 1 or 0, if x is true or false respectively. All other bits are 0. Note: this means that a value of true will be encoded as 0x80 (10000000 in binary) and a value of false will be encoded as 0x00. This is in contrast to most other encoding schemes, where a value of true is encoded as 0x01.

Other aliased types’ encodings are already covered:

  • string and address are aliases for byte[] and byte[32] respectively
  • byte is an alias for uint8
  • each of the reference types is an alias for uint8

Reference Types

Three special types are supported only as the type of an argument. They can be embedded in arrays and tuples.

  • account represents an Algorand account, stored in the Accounts (apat) array
  • asset represents an Algorand Standard Asset (ASA), stored in the Foreign Assets (apas) array
  • application represents an Algorand Application, stored in the Foreign Apps (apfa) array

Some AVM opcodes require specific values to be placed in the “foreign arrays” of the Application call transaction. These three types allow methods to describe these requirements. To encode method calls that have these types as arguments, the value in question is placed in the Accounts (apat), Foreign Assets (apas), or Foreign Apps (apfa) arrays, respectively, and a uint8 containing the index of the value in the appropriate array is encoded in the normal location for this argument.

Note that the Accounts and Foreign Apps arrays have an implicit value at index 0, the Sender of the transaction or the called Application, respectively. Therefore, indexes of any additional values begin at 1. Additionally, for efficiency, callers of a method that wish to pass the transaction Sender as an account value or the called Application as an application value SHOULD use 0 as the index of these values and not explicitly add them to Accounts or Foreign Apps arrays.

When passing addresses, ASAs, or apps that are not required to be accessed by such opcodes, ARC-4 Contracts SHOULD use the base types for passing these types: address for accounts and uint64 for asset or Application IDs.

Transaction Types

Some apps require that they are invoked as part of a larger transaction group, containing specific additional transactions. Seven additional special types are supported (only) as argument types to describe such requirements.

  • txn represents any Algorand transaction
  • pay represents a PaymentTransaction (algo transfer)
  • keyreg represents a KeyRegistration transaction (configure consensus participation)
  • acfg represent a AssetConfig transaction (create, configure, or destroy ASAs)
  • axfer represents an AssetTransfer transaction (ASA transfer)
  • afrz represents an AssetFreezeTx transaction (freeze or unfreeze ASAs)
  • appl represents an ApplicationCallTx transaction (create/invoke a Application)

Arguments of these types are encoded as consecutive transactions in the same transaction group as the Application call, placed in the position immediately preceding the Application call. Unlike “foreign” references, these special types are not encoded in ApplicationArgs as small integers “pointing” to the associated object. In fact, they occupy no space at all in the Application Call transaction itself. Allowing explicit references would create opportunities for multiple transaction “values” to point to the same transaction in the group, which is undesirable. Instead, the locations of the transactions are implied entirely by the placement of the transaction types in the argument list.

For example, to invoke the method deposit(string,axfer,pay,uint32)void, a client would create a transaction group containing, in this order:

  1. an asset transfer
  2. a payment
  3. the actual Application call

When encoding the other (non-transaction) arguments, the client MUST act as if the transaction arguments were completely absent from the method signature. The Application call would contain the method selector in ApplicationArgs[0], the first (string) argument in ApplicationArgs[1], and the fourth (uint32) argument in ApplicationArgs[2].

ARC-4 Applications SHOULD be constructed to allow their invocations to be combined with other contract invocations in a single atomic group if they can do so safely. For example, they SHOULD use gtxns to examine the previous index in the group for a required pay transaction, rather than hardcode an index with gtxn.

In general, an ARC-4 Application method with n transactions as arguments SHOULD only inspect the n previous transactions. In particular, it SHOULD NOT inspect transactions after and it SHOULD NOT check the size of a transaction group (if this can be done safely). In addition, a given method SHOULD always expect the same number of transactions before itself. For example, the method deposit(string,axfer,pay,uint32)void is always preceded by two transactions. It is never the case that it can be called only with one asset transfer but no payment transfer.

The reason for the above recommendation is to provide minimal composability support while preventing obvious dangerous attacks. For example, if some apps expect payment transactions after them while other expect payment transaction before them, then the same payment may be counted twice.

Rationale

Security Considerations

None.

Copyright and related rights waived via CCO.

Citation

Please cite this document as:

Jannotti, Jason Paulos, "ARC-4: Application Binary Interface (ABI)," Algorand Requests for Comments, no. 4, July 2021. [Online serial]. Available: https://algorandfoundation.github.io/ARCS/arc-4.