Skip to content

v2.0.0

Latest

Choose a tag to compare

@cusma cusma released this 23 Oct 13:10
816a64b

ARC-20 Reference Implementation v2.0.0

v2.0.0 is a revamp of the Smart ASA reference implementation, together with an
ARC-20 specification update. It replaces App Local State with Boxes, redefines the
freeze model, adopts ARC-65 errors, and adds extension points for custom transfer
rules and circulating supply definitions.

⚠️ This is a major release: the state schema, holders' setup and error reporting
change. Existing deployments are not upgradable (the App is immutable by design):
adopt v2.0.0 with a new deployment.

⚠️ The reference implementation has not been audited: review and test it before
using it in production.

Breaking changes

No Smart ASA Application opt-in

  • asset_opt_in and asset_close_out are removed, and the App has no Local
    State (schema: 0 local uints, previously 2).
  • Holders only opt in to the Controlled ASA, as for any ASA: 0.1 ALGO of MBR instead
    of 0.2285 ALGO.
  • Holders leave by closing out the Controlled ASA to the Smart ASA Application: any
    balance left is surrendered (redeemed). To keep the units, transfer them first, e.g.
    in the same group.
  • Transfers of 0 units need no holding, as for native ASA (freeze checks still
    apply).

Account freezes in Boxes, with deposits

Account freezes are stored in empty Boxes (freeze_exceptions box map, key prefix
f + account address): a box exists only for an account whose freeze state differs
from default_frozen.

  • Deposit: a freeze that creates a box needs a payment of at least the box MBR
    (0.0157 ALGO) to the Smart ASA Application, just before account_freeze in the
    group, from any sender: [pay 0.0157 ALGO to the App, account_freeze]. Otherwise
    ERR:boxMbrUnfunded.
  • Refund: resetting an account to default_frozen deletes its box and refunds
    its MBR to Freeze with an inner payment (the reset fee must cover 2 transactions).
  • Box references: calls checking freezes (asset_transfer, account_freeze,
    get_account_is_frozen) need the boxes of the involved accounts. Clients can
    populate them by simulation.
  • Freezes now stick in both profiles: holders can't reset them (no ClearState
    escape), they survive leaving, and Freeze can freeze any address before it holds
    units (e.g., from a sanctions list).
  • get_account_is_frozen works for any address (no opt-in needed).

default_frozen set on creation only

asset_config ignores a new default_frozen, as native ASA reconfiguration does.
Choose the freeze profile (Blacklist False, Whitelist True) at creation.

Freeze model

  • The Reserve obeys Freeze: issuing and redeeming fail if the Smart ASA or the
    Reserve is frozen (and issuing if the receiver is frozen). A Whitelist Smart ASA
    needs Freeze to approve the Reserve.
  • The Clawback ignores Freeze, both global and account freezes.
  • Roles held by the same account compose: each action follows its own role's
    rules (e.g., a Reserve that is also the Clawback issues as Reserve, claws back as
    Clawback).

Destroy

asset_destroy now destroys the Controlled ASA, deletes the Smart ASA
Application
, and closes its account, sending all its ALGO to the Manager.
It requires:

  • No issued units (ERR:unitsIssued);
  • No account freeze box left (ERR:freezeBoxesLeft): Freeze resets all accounts to
    default_frozen first, getting the deposits back. Disabling Freeze with boxes left
    makes the Smart ASA permanent.

The caller fee must cover the inner asset destroy and account close (3 transactions).

ARC-65 errors

Failed calls log an ARC-65
error (ERR:<code>) before failing, and ARC-56 clients report the same code. Old
versions used plain assertion messages. Codes are defined in smart_contracts/errors.py:

Area Codes
Schema wrongGlobalBytes, wrongGlobalUints, wrongLocalBytes, wrongLocalUints
Authorization unauthorizedCreator, unauthorizedManager, unauthorizedReserve, unauthorizedFreeze, unauthorizedClawback
Controlled ASA ctrlAsaNotExist, ctrlAsaExist, ctrlAsaInvalid
Roles reserveDisabled, freezeDisabled, clawbackDisabled
Account freeze boxes boxMbrUnfunded
Freeze globalFrozen, senderFrozen, receiverFrozen, reserveFrozen
Configuration totalInvalid, metadataHashInvalid
Transfers selfIssue, overIssue, clawbackRedeem
Destroy unitsIssued, freezeBoxesLeft
ASA Metadata Registry registryNotSet, registryMbrPaymentInvalid, registryCallInvalid, ctrlAsaRbacInvalid

Checks generated by the compiler (ARC-4 routing, ABI decoding, group transaction
types) fail without an ARC-65 error.

ABI: ARC-20 method selectors

v1.1.0 encoded ASA and account arguments as uint64 and address, so its method
selectors didn't match the ARC-20 interface. v2.0.0 uses the ARC-20 reference types
(asset, account):

Method v1.1.0 v2.0.0
asset_config (uint64,uint64,…) (asset,uint64,…)
get_asset_config (uint64) (asset)
asset_transfer (uint64,uint64,address,address) (asset,uint64,account,account)
asset_freeze (uint64,bool) (asset,bool)
account_freeze (uint64,address,bool) (asset,account,bool)
get_asset_is_frozen (uint64) (asset)
get_account_is_frozen (uint64,address) (asset,account)
asset_destroy (uint64) (asset)
get_circulating_supply (uint64) (asset)

Regenerate clients from the new ARC-56 App Specs.

New features

Issued and circulating supply

  • The issued supply is the amount of units outside the Smart ASA Application
    (total of the Controlled ASA minus the App balance): what wallets already compute
    with the ASA convention. The Smart ASA total caps it (ERR:overIssue,
    ERR:totalInvalid).
  • The circulating supply (get_circulating_supply) is:
    • for the basic Smart ASA, the issued supply (units held by the Reserve circulate);
    • for the Smart ASA with ASA Metadata Registry integration, the issued supply
      minus the units held by the Reserve, so the Reserve can keep a treasury, issued
      but not circulating. This variant implements [ARC-62]((https://github.com/algorandfoundation/ARCs/blob/main/ARCs/arc-0062.md)
      (arc62_get_circulating_supply), and both getters always return the same value.
      Nothing is excluded while the Reserve is disabled or set to the App. The getters
      need the Reserve account reference (populated by simulation).

withdraw_excess_algo() (non-normative)

The Manager can withdraw the App ALGO above its MBR at any time. All the App ALGO
only ever leaves to the Manager: the excess with withdraw_excess_algo, everything
with asset_destroy. Callers cover all inner transaction fees.

Extension points

  • _regular_transfer_hook(asset_sender, asset_receiver, asset_amount): override it
    in a subclass to add checks to regular transfers. It runs after the built-in
    checks, never on issuances, redemptions or clawbacks. See the Capped Smart ASA
    example extension (smart_contracts/capped_smart_asa/), with its tests.
  • _circulating_supply: override it to refine the circulating supply, as the ARC-89
    variant does.

AVM clients

The artifacts now include an AVM client (on-chain) for each Smart ASA variant
(smart_asa_avm_client.py, arc89_smart_asa_avm_client.py), generated with
puyapy-clientgen, so Algorand Python contracts can call a Smart ASA with typed
arc4.abi_calls. The example extension has none.

Other changes

  • Terminology: issue and redeem replace mint and burn (Smart ASA units
    return to the App and can be issued again); holders surrender units by closing
    out.
  • Metadata hash: asset_config now also requires a 32-byte metadata_hash, as
    asset_create already did (ERR:metadataHashInvalid), stricter than ARC-20.
  • ARC-89 variant: creation fails without the ASA Metadata Registry App ID
    (ARC89_APP_ID template variable, ERR:registryNotSet). The ASA Metadata Registry
    dependency is pinned to v1.0.0.
  • Deployment scripts: the deployer now holds all the Smart ASA roles, Reserve
    included.

Migration checklist

  1. Deploy a new Smart ASA Application (v1.1.0 Apps can't be upgraded).
  2. Regenerate clients from the ARC-56 App Specs (new selectors, box map).
  3. Drop App opt-in and close-out flows: holders only opt in to the Controlled ASA.
  4. Choose default_frozen at creation; in a Whitelist, approve the Reserve.
  5. Send [deposit, account_freeze] for freezes that create a box, and pay the
    refund fee on resets.
  6. Match ARC-65 error codes instead of assertion messages.
  7. Review fees: issue, redeem, clawback, regular transfers and resets 2× min fee;
    asset_destroy 3× min fee; withdraw_excess_algo 2× min fee.
  8. Use get_circulating_supply (or ARC-62 for the ARC-89 variant) for supply data.

TestNet Deployment

This deployment demonstrates the full composability of 5 ARCs:

Smart ASA

  • with metadata (ARC-3)
  • used to discover the Smart ASA App (ARC-20)
  • and the ASA Circulating Supply end-point (ARC-62)
  • from the ASA Metadata Registry (ARC-89)
  • via Algorand standard URI (ARC-90).

What's Changed

  • test: improve coverage, general refactiring by @cusma in #93
  • fix: arc20 abi by @cusma in #94
  • fix: clawback reach, metadata hash and frozen getter by @cusma in #95
  • refactor: share the transfer check and simplify close-out by @cusma in #96
  • fix: deployer as Reserve, document app ID discovery by @cusma in #97
  • docs: roles and transfer paths by @cusma in #98
  • fix: close the app account to manager on destroy by @cusma in #99
  • feat: reserve obeys freeze, roles compose by @cusma in #100
  • fix: explicit opt-in error, drop asset ID from local state by @cusma in #101
  • feat: ARC-65 errors by @cusma in #102
  • feat: account freeze boxes by @cusma in #103
  • feat: transfer hook extension by @cusma in #104
  • chore: bump dependencies by @cusma in #105
  • feat: issued and circulating supply by @cusma in #106
  • feat: AVM client by @cusma in #107

Full Changelog: v1.1.0...v2.0.0