WEB3DEV

Cover image for Entendendo o Orçamento de Opcode TEAL - Algorand
Panegali
Panegali

Posted on • Atualizado em

Entendendo o Orçamento de Opcode TEAL - Algorand

Ao contrário de outras blockchains, como Ethereum, a Algorand não cobra taxas com base no custo computacional de uma chamada de contrato inteligente. Em vez disso, os contratos inteligentes recebem um orçamento de opcode (código de operação) geral de 700 (as chamadas de aplicativos falham se esse limite for ultrapassado). Os custos de opcode para cada um dos opcodes TEAL são fornecidos aqui.

Há muitas vantagens nessa abordagem. Ao contrário da Ethereum, os desenvolvedores não precisam sacrificar a legibilidade por melhorias de desempenho insignificantes, pois as taxas são fixas em vez de dependentes do uso computacional. Esse limite também garante que contratos computacionalmente caros não adicionem inchaço desnecessário a blocos únicos.

Neste guia, veremos como a Máquina Virtual Algorand (AVM) trabalha com esses orçamentos de opcode. Também aproveitaremos uma característica inteligente das transações atômicas para estendê-las (finitamente).

A familiaridade básica com o PyTEAL é presumida, mas não mais do que o que é abordado na visão geral do PyTeal da Algorand.

Requisitos

Para este tutorial, você precisará dos seguintes requisitos:

  • Uma conta Algorand pré-financiada. Você pode financiar uma conta usando o banco da testnet.
  • Uma instância do sandbox em execução.
  • A versão mais recente do PyTeal instalada (0.9.1 no momento da redação). pip install pyteal==0.9.1
  • O Algorand Python SDK usado para interagir com a blockchain Algorand. pip install py-algorand-sdk

Passos

  1. Configuração do Python SDK
  2. Criando e Executando um Contrato Vazio
  3. Visão geral do Opcode
  4. Fluxo de Controle
  5. Expandindo o orçamento
  6. Conclusão

1. Configuração do Python SDK

Para este projeto, você vai querer ter dois arquivos: contracts.py e testing.py. Abaixo estão alguns códigos padrão do Algorand Python SDK para a execução das transações, que devem ir em testing.py.

import base64

    from algosdk.future import transaction
    from algosdk import account, mnemonic, logic
    from algosdk.v2client import algod
    from algosdk.logic import get_application_address

    from contracts import *

    def compile_program(client, source_code):
        compile_response = client.compile(source_code)
        return base64.b64decode(compile_response['result'])


    def create_app(client, private_key, approval_program, clear_program, global_schema, local_schema):

        sender = account.address_from_private_key(private_key)

        on_complete = transaction.OnComplete.NoOpOC.real

        params = client.suggested_params()

        txn = transaction.ApplicationCreateTxn(sender, params, on_complete,
                                            approval_program, clear_program,
                                            global_schema, local_schema)

        return execute_transaction(client, txn, private_key)

    def execute_transaction(client, txn, private_key):
        signed_txn = txn.sign(private_key)
        tx_id = signed_txn.transaction.get_txid()

        client.send_transactions([signed_txn])
        transaction.wait_for_confirmation(client, tx_id, 10)
        return client.pending_transaction_info(tx_id)


    def execute_group_transaction(client, txns, private_key):
        stxns = []
        for txn in txns:
            stxns.append(txn.sign(private_key))

        tx_id = client.send_transactions(stxns)

        transaction.wait_for_confirmation(client, tx_id, 10)


    ALGOD_TOKEN = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
    ALGOD_ADDRESS = "http://localhost:4001"

    algod_client = algod.AlgodClient(ALGOD_TOKEN, ALGOD_ADDRESS)

    MNEMONIC = "YOUR_MNEMONIC_HERE" # substitua por sua própria mnemônica


    pkey = mnemonic.to_private_key(MNEMONIC)

    compiled_approval = compile_program(algod_client, approval_program())
    compiled_clearstate = compile_program(algod_client, clear_state_program())

    global_schema = transaction.StateSchema(0, 0) # sem ints ou bytes armazenados no estado global ou local
    local_schema = transaction.StateSchema(0, 0)

Enter fullscreen mode Exit fullscreen mode

As funções compile_programe create_appsão todas tiradas da visão geral do PyTEAL da Algorand.

Também adicionei duas novas funções: execute_transaction e execute_group_transaction, que chamam uma transação arbitrária e transações agrupadas (dada uma matriz de transações), respectivamente.

A função create_app também foi modificada para usar execute_transaction para evitar redundância.

Como você deve ter notado, estamos importando de contracts.py no topo, além de chamar as funções indefinidas approval_program()e clear_state_program(). Vamos fazê-las.

2. Criando e Executando um Contrato Vazio

Em contracts.py, crie as duas funções a seguir:

from pyteal import *

    def approval_program():
        return compileTeal(Approve(), Mode.Application, version=5)

    def clear_state_program():
        return compileTeal(Approve(), Mode.Application, version=5)
Enter fullscreen mode Exit fullscreen mode

Essas duas funções básicas lidam com cada chamada de aplicativo em potencial com uma instrução Approve(). Você pode ver alguns exemplos usando Return(Int(1)) como valor de retorno de aprovação padrão; isto foi substituído pela instrução Approve() equivalente (com Reject() correspondente a Return(Int(0))).

Agora tente executar testing.py. Você não deve receber nada de retorno. Fantástico!

Em seguida, vamos configurar corretamente nosso approval_program() para trabalhar com diferentes chamadas de aplicativos:

def approval_program():

        handle_noop=Seq([
            Approve(),
        ])

        program = Cond(
            [Txn.application_id() == Int(0), Approve()],
            [Txn.on_completion() == OnComplete.OptIn, Reject()],
            [Txn.on_completion() == OnComplete.CloseOut, Reject()],
            [Txn.on_completion() == OnComplete.UpdateApplication, Reject()],
            [Txn.on_completion() == OnComplete.DeleteApplication, Reject()],
            [Txn.on_completion() == OnComplete.NoOp, handle_noop]
        )

        return compileTeal(program, Mode.Application, version=5)

Enter fullscreen mode Exit fullscreen mode

Aqui, nós (obviamente) permitimos a criação de aplicativos (quando uma transação de chamada de aplicativo é enviada com uma ID de aplicativo de 0). As transações de atualização e exclusão não são permitidas, e as chamadas de ativação/encerramento também são rejeitadas, pois nosso contrato inteligente não lidará com o estado local. O tipo de transação mais importante a ser tratada aqui é uma transação NoOp, que alimentamos para um Seq() vazio agora.

Agora, vamos implantar e executar este contrato inteligente com o Python SDK. Na parte inferior de testing.py, adicione o seguinte:

createAppTxn = create_app(algod_client, pkey, compiled_approval, compiled_clearstate, global_schema, local_schema)

    app_id = createAppTxn['application-index']

    sender = account.address_from_private_key(pkey)

    noopTxn = transaction.ApplicationNoOpTxn(sender, algod_client.suggested_params(), app_id)

    execute_transaction(algod_client, noopTxn, pkey)

Enter fullscreen mode Exit fullscreen mode

O contrato agora executará a instrução Approve() em handle_noop, mas nada deverá ser impresso no console.

3. Visão geral do código de operação

Vamos agora explorar a mecânica por trás do sistema de orçamento do opcode da Máquina Virtual Algorand (AVM). Estaremos trabalhando antes da instrução Approve() no handle_noop Seq, pois é isso que será executado durante uma chamada de aplicativo padrão. Na lista de orçamento do opcode, vemos que o hash Keccak256 tem um custo de 130. Para testar isso, adicione as seguintes linhas ao seu Seq:

 handle_noop=Seq([
        Pop(Keccak256(Bytes("a"))),
        Pop(Keccak256(Bytes("b"))),
        Pop(Keccak256(Bytes("c"))),
        Pop(Keccak256(Bytes("d"))),
        Pop(Keccak256(Bytes("e"))),
        Approve(),
    ])

Enter fullscreen mode Exit fullscreen mode

Não há significado para as letras que estamos alimentando; o custo será o mesmo independentemente dos bytes de entrada. Para garantir que a pilha de execução esteja limpa antes que seja alcançado Approve(), estamos desbloqueando cada hash gerado à medida que avançamos. Se tentarmos executar testing.py novamente, não veremos nada acontecer, como esperado: (5 hashes) * (130 por hash) = 650 < 700. No entanto, se adicionarmos outro hash ao nosso Seq:

 handle_noop=Seq([
        Pop(Keccak256(Bytes("a"))),
        Pop(Keccak256(Bytes("b"))),
        Pop(Keccak256(Bytes("c"))),
        Pop(Keccak256(Bytes("d"))),
        Pop(Keccak256(Bytes("e"))),
        Pop(Keccak256(Bytes("f"))),
        Approve(),
    ])

Enter fullscreen mode Exit fullscreen mode

A execução falha, com “erro de avaliação lógica: orçamento de custo dinâmico excedido”.

4. Fluxo de Controle

Como os orçamentos de opcode são calculados em diferentes caminhos de execução em potencial? Nas versões anteriores da AVM, os orçamentos de opcode eram calculados linha por linha, independentemente de quais declarações seriam executadas. Por exemplo, um par If-Else com hashes Keccak256 em cada bloco de código contribuiria com 2*130 para o orçamento geral, apesar de apenas um hash ser calculado durante a execução. No entanto, a AVM agora registra os opcodes à medida que um programa é executado, garantindo que haja orçamento suficiente restante antes de executar cada instrução e falhando apenas se o orçamento de 700 for excedido.

Para demonstrar, envolva os dois hashes Keccak256 finais em um par If-Else:

handle_noop=Seq([
        Pop(Keccak256(Bytes("a"))),
        Pop(Keccak256(Bytes("b"))),
        Pop(Keccak256(Bytes("c"))),
        Pop(Keccak256(Bytes("d"))),
        If(Int(1)).Then(
            Pop(Keccak256(Bytes("e"))),
        ).Else(
            Pop(Keccak256(Bytes("f"))),
        ),
        Approve(),
    ])
Enter fullscreen mode Exit fullscreen mode

Como esperado, a transação permanece dentro de seu orçamento de opcode _e não falha. Embora esta instrução if obviamente não seja útil, ela demonstra muito bem o mecanismo subjacente para calcular o uso do _opcode; como qualquer um dos caminhos na instrução if resulta em 5 cálculos de hash no total.

5. Expandindo o orçamento

E se você precisar de um orçamento maior que 700? A partir do TEAL 4, os orçamentos de opcode são compartilhados entre transações de grupo, portanto, seu orçamento compartilhado total para uma transação agrupada pode ser de até 16 * 700 = 11200 (do número máximo de 16 transações em uma transação atômica). Para mostrar como isso funciona, vamos primeiro construir a seguinte transação de grupo em testing.py:

  noopTxn = transaction.ApplicationNoOpTxn(sender, algod_client.suggested_params(), APP_ID, [0])

    noopTxn2 = transaction.ApplicationNoOpTxn(sender, algod_client.suggested_params(), APP_ID, [1])

    groupTxnId = transaction.calculate_group_id([noopTxn, noopTxn2])

    noopTxn.group = groupTxnId
    noopTxn2.group = groupTxnId

    execute_group_transaction(algod_client, [noopTxn, noopTxn2], pkey)

Enter fullscreen mode Exit fullscreen mode

Aqui, estamos criando duas chamadas quase idênticas no aplicativo NoOp, diferindo apenas por um único parâmetro (0 para noopTxn e 1 para noopTxn2 ). Em seguida, nós os empacotamos em uma única transação de grupo e as executamos usando a função auxiliar execute_group_transaction fornecida no início. Em seguida, vamos atualizar contracts.py para trabalhar com esses diferentes parâmetros. Dentro do handle_noop Seq(), vamos modificar nosso If-Else:

  handle_noop=Seq([
        Pop(Keccak256(Bytes("a"))),
        Pop(Keccak256(Bytes("b"))),
        Pop(Keccak256(Bytes("c"))),
        Pop(Keccak256(Bytes("d"))),
        If(Btoi(Txn.application_args[0])).Then(Seq([
            Pop(Keccak256(Bytes("e"))),
            Pop(Keccak256(Bytes("f"))),
        ]))
        .Else(Seq([
            Pop(Keccak256(Bytes("g"))),
        ])),
        Approve(),
    ])
Enter fullscreen mode Exit fullscreen mode

O Btoi(Txn.application_args[0]) será avaliado como false (Int(0)) se nosso argumento for 0 e true caso contrário. Da nossa transação de grupo, vemos que a primeira transação NoOp será avaliada como falsa, enquanto a segunda será verdadeira. Executando testing.py novamente, descobrimos que a execução falha: embora tenhamos um orçamento total de (700-10) * 2 = 1380, a primeira transação calcula hashes para as letras a-d e g para um custo de opcode de 5 * 130 = 650, enquanto a segunda transação calcula hashes para a-f para um custo de opcode _de 6 * 130 = 780. O custo total de _opcode para a transação de grupo, portanto, é 650 + 780 = 1430, excedendo o limite de 1400.

No entanto, se removermos o hash “g” do bloco Else (apenas comentando), a execução é bem-sucedida. Enquanto a segunda transação sozinha (aquela em que o primeiro bloco If é executado) irá computar hashes para as letras a-f para um custo de opcode de 6 * 130 = 780 > 700, a primeira transação apenas computa as quatro hashes antes do bloco if-else, para um custo de opcode de 4 * 130 = 520. O custo total de opcode para a transação de grupo é então 520 + 780 = 1300, que é menor que o limite de 1400.

6. Conclusão

Parabéns! Agora você entende o orçamento de opcode TEAL e pode usar transações atômicas para aumentar esse valor de orçamento. O TEAL está evoluindo rapidamente, portanto, mantenha-se informado sobre mudanças futuras, como aumentos de orçamento além dos 700 atuais ou outras maneiras de aumentar o orçamento você mesmo, como criando “aplicativos internos”.


Este artigo foi escrito por Santiago Lisa e publicado no Portal do desenvolvedor Algorand. Traduzido por Marcelo Panegali.

Top comments (0)