AVM 7 New Features
The latest Algorand release offers an impactful set of updates across different aspects of the blockchain.
Firstly, speed and throughput: transactions per second (TPS) have been upped to 6K, and the average time for a block to complete is now less than 4 seconds!
Secondly, State Proofs are now part of the Algorand protocol. State Proofs allow developers to build light clients that can be used to cryptographically verify any transaction on the chain using small State Proof commits. To read more about State Proofs, take a look at this article, or read about them in our developer documentation.
Thirdly, the AVM got an upgrade. Two new opcodes enable creating an Oracle or Randomness Beacon. To read more about building or using these on Algorand, read over this article. The other AVM-related updates, which are all highly community-sought-for improvements, are described in this article.
Zero Balance Transactions
Many applications on Algorand issue transactions from Smart Contracts using the inner transaction feature. Prior to this release, In order for this feature to work, the Smart Contract had to be funded (have an Algo balance > 0).
Recall that fees are pooled within a transaction group: all the fees paid by the transaction initiators are added together to cover the total cost of the transactions requested. With this new AVM release, the protocol now allows any account to send a transaction even if its Algo balance is 0, as long as all the transaction fees are covered by the fee pooling of its transaction group.
This also allows unfunded accounts to call Smart Contracts and interact with a dapp, while not being funded, as long as they are grouped with a fee paying transaction. Typical use cases are presented below.
- Unfunded accounts interacting with a dapp. A dapp builder can group an unfunded account app call with a fee paying transaction.
- Unfunded Smart Contract issuing inner transactions. Callers of the Smart Contract can cover the fees.
- Unfunded Smart Signatures issuing transactions.
- Allows Smart Contracts or Smart Signatures to function as the clawback, freeze or manager account of an asset, while not being funded.
To see an example of using a zero balance Smart Contract issue inner transactions check out the Algorand DevRel github.
New Opcodes
The AVM has been enhanced to support several new opcodes that can be used within your Smart Contracts. Examples used in this article are available on github in the file application.py
and tested using the main.py
python example.
block and vrf_verify
These two opcodes represent significant capabilities, especially when creating an oracle. Check out this article to read more about randomness oracles on Algorand. The block opcode allows access to the block seed and timestamp of previous blocks. The historical lookback range for this query is limited to using the firstValid and lastValid properties of your transaction. It can be used to retrieve timestamp and seed for a past block in the range of lastValid - 1001 rounds to firstValid - 1. If you are using a standard range of 1000 rounds, this means that the block opcode can only access the block of firstValid-1, no matter where in the 1000 rounds the transaction is actually executed. If you set the range to 500 rounds, then you can use the block opcode to look back 501 rounds before first valid. This opcode only takes one parameter and that is a uint64 which specifies the round number to retrieve.
Below is a simple PyTeal example of using this opcode. Note that the example also uses the beaker framework.
#Beaker PyTeal Block Opcode Sample
from typing import Literal
from pyteal import *
from beaker import *
# define tuple to return timestamp and seed for
# given round
BlockSeed = abi.StaticBytes[Literal[32]]
class BlockDetails(abi.NamedTuple):
ts: abi.Field[abi.Uint64]
seed: abi.Field[BlockSeed]
class DemoBlock(Application):
"""Examples for teal ops that are new for AVM 7"""
# external Smart Contract call
@external
def block(self, round: abi.Uint64, *, output: BlockDetails):
"""New block operations for getting timestamp or seed of a historical round"""
return Seq(
(ts := abi.Uint64()).set(Block.timestamp(round.get())),
(seed := abi.make(BlockSeed)).set(Block.seed(round.get())),
output.set(ts, seed),
)
The vrf_verify opcode is used to verify a VRF proof produced by a VRF for a given public key on a given input message. This opcode is passed three parameters, the proof, the message, and the public key of the VRF. This opcode returns the associated VRF output and a flag indicating whether the proof was valid or not. (Note that producing a VRF proof requires the associated secret key, but verification only requires the public key.)
Below is a simple PyTeal example of using this opcode. Note that the example also uses the beaker framework.
from typing import Literal
from pyteal import *
from beaker import *
# Define some types by name for handy reference
VrfProof = abi.StaticBytes[Literal[80]]
VrfHash = abi.StaticBytes[Literal[64]]
Signature = abi.StaticBytes[Literal[64]]
class DemoVRFVerify(Application):
"""Examples for teal ops that are new for AVM 7"""
@external
def vrf_verify(
self,
msg: abi.DynamicBytes,
proof: VrfProof,
pub_key: abi.Address,
*,
output: VrfHash,
):
"""
Verify that some message was used to generate a proof generated by a given public key
Cost: Takes 5700 ops
"""
return Seq(
# Use the Algorand VRF
vrf_result := VrfVerify.algorand(
# Note: in practice the message is likely to be something like:
# sha512_256(concat(itob(round), block.seed(round)))
# Get the bytes from the message
msg.get(),
# Get the bytes from the proof
proof.get(),
# Note: in practice this is likely to be some hardcoded public key or one of
# a set of "pre-approved" public keys
# Get the pubkey bytes
pub_key.get(),
),
# Check Successful
Assert(vrf_result.output_slots[1].load() == Int(1)),
# Write the result to the output
output.set(vrf_result.output_slots[0].load()),
)
Note
When using the opcode you will need more opcode budget as this call requires 5700 and a Smart Contract call is limited to 700. This can be achieved by grouping calls to this method with additional calls that execute a noop
operation which should return success. To see an example of doing this, checkout the example code.
ed25519verify_bare
The AVM has had the ed2559verify
opcode available since version 1 of the AVM. This opcode is useful, however it requires that the program hash be appended to any data that needs a signature verification. With AVM 7, the ed25519verify_bare
opcode is now available. This opcode allows an arbitrary message signed by a specific ed25519 private key to be verified. This opcode takes three parameters: the message, the signature of the message, and the public key of the signer. The opcode returns a 0 (sig not verified) or 1 (sig verified) depending on whether it succeeds.
Below is a simple PyTeal example of using this opcode. Note that the example also uses the beaker framework.
from pyteal import *
from beaker import *
class DemoEd25519VerifyBare(Application):
"""Examples for teal ops that are new for AVM 7"""
@external
def ed25519verify_bare(self, msg: abi.String, sig: Signature, *, output: abi.Bool):
"""
Verify signature on arbitrary message
Cost: Takes 1000 ops
"""
return output.set(Ed25519Verify_Bare(msg.get(), sig.get(), Txn.sender()))
Note
When using the opcode you will need more opcode budget as this call requires 1900 and a Smart Contract call is limited to 700. This can be achieved by grouping calls to this method with additional calls that execute a noop
operation which should return success. To see an example of doing this checkout the example code.
sha3_256 opcode
The sha3_256
opcode is now available and allows hashing data using SHA3_256. This opcode only takes one parameter: the data to hash. This opcode returns a 32 byte array containing the hash of the data.
Below is a simple PyTeal example of using this opcode. Note that the example also uses the beaker framework.
from pyteal import *
from beaker import *
class DemoSha3256(Application):
"""Examples for teal ops that are new for AVM 7"""
@external
def sha3_256(self, to_hash: abi.String, *, output: abi.DynamicBytes):
"""
Cost: 130
"""
return Seq(
output.set(Sha3_256(to_hash.get())),
)
replace2 and replace3 opcodes
The AVM offers two new opcodes for string replacement. Both opcodes perform a replacement within a string at a specified start location with a second string. The primary difference between the two opcodes is that the first opcode replace2 substitute_string
is passed the substitute string as an argument, not on the stack. The replace3
opcode uses all three parameters from the stack, the first is the string to execute the replace on, the second is a uint64 specifying the start location within the source string to start the replace and the third parameter is substitute string. Note that the result of this operation will fail if the result of a replace would return a string that is longer than the original string.
from pyteal import *
from beaker import *
class DemoReplace(Application):
"""Examples for teal ops that are new for AVM 7"""
@external
def replace(
self,
orig: abi.String,
start: abi.Uint64,
replace_with: abi.String,
*,
output: abi.String,
):
"""
replace(abcdef, 1, xyz) => axyzef
cannot _grow_ original string
"""
return output.set(Replace(orig.get(), start.get(), replace_with.get()))
json_ref opcode
For the vast majority of use cases, JSON objects should be parsed before passing them as parameters to a Smart Contract call. If this is not an option for your application, the json_ref
opcode can be used to parse the various parts of the JSON object. This opcode takes two parameters: the json object as bytes and the key to use to extract a particular value. This opcode supports getting uint64, string, and object values (nested sections of the primary JSON object). This opcode can be called with TEAL using json_ref JSONUint64
, json_ref JSONString
, or json_ref JSONObject
. In Pyteal this can be called using JsonRef.as_uint64(object,key)
, JsonRef.as_string(object,key)
, or JsonRef.as_object(object,key)
. The opcode cost of this opcode is proportional to the size of the object being parsed.
from pyteal import *
from beaker import *
#Using PyTeal ABI named tuples
class JsonExampleResult(abi.NamedTuple):
string_key: abi.Field[abi.String]
uint_key: abi.Field[abi.Uint64]
obj_key: abi.Field[abi.String]
class DemoJsonRef(Application):
"""Examples for teal ops that are new for AVM 7"""
@external
def json_ref(self, json_str: abi.String, *, output: JsonExampleResult):
"""
Cost: 25 + 2 per 7 bytes of A (JSON object)
"""
return Seq(
(s := abi.String()).set(
JsonRef.as_string(json_str.get(), Bytes("string_key"))
),
(i := abi.Uint64()).set(
JsonRef.as_uint64(json_str.get(), Bytes("uint_key"))
),
(o := abi.String()).set(
JsonRef.as_object(json_str.get(), Bytes("obj_key"))
),
output.set(s, i, o),
)
base64_decode opcode
Values that are Base64 encoded (standard or URL) should be decoded before passing them as a parameter to a Smart Contract call. The preferred method is to use non encoded byte strings. If that is not an option for your application, the base64_decode
opcode is now available and will decode the encoded value. This opcode takes one parameter which is the encoded bytes and is called in Teal with either base64_decode URLEncoding
or base64_decode StdEncoding
. In PyTeal this method can be called using either Base64Decode.std(data)
or Base64Decode.url(data)
. This opcodes cost is directly related to the size of the data being decoded.
Below is a simple PyTeal example of using this opcode. Note that the example also uses the beaker framework.
from pyteal import *
from beaker import *
class DemoBase64Decode(Application):
"""Examples for teal ops that are new for AVM 7"""
@external
def b64decode(self, b64encoded: abi.String, *, output: abi.String):
"""Base64Decode can be used to decode either a std or url encoded string
Cost: 1 + 1 per 16 bytes of A
Note:
IF you have the option to decode prior to submitting the app call
transaction, you _should_.
This should _only_ be used in the case that there is no way to decode
the bytestring prior to submitting the transaction.
"""
return output.set(Base64Decode.std(b64encoded.get()))