WEB3DEV

Cover image for Depuração de Contrato Aprimorada - Algorand
Fatima Lima
Fatima Lima

Posted on • Atualizado em

Depuração de Contrato Aprimorada - Algorand

Image description

smart contract

O time Algorand continua a acrescentar características que melhorarão o diagnóstico e a depuração de contratos inteligentes. Este artigo resume algumas das últimas mudanças adicionadas à Algorand para o ponto de extremidade de dryrun.

Como abordado na documentação do desenvolvedor, atualmente, os desenvolvedores podem depurar problemas usando dois métodos diferentes. O primeiro é a capacidade de usar um GUI (tealdbg) para depurar contratos linha por linha. A segunda forma é usar um ponto de extremidade REST do dryrun, que está incorporado em todos os SDKs. Dado algum contexto de estado, o ponto de extremidade de dryrun executa uma transação ou um grupo de transações e retorna resultados da avaliação da AVM. Este artigo detalha algumas das mudanças nos SDKs para diagnóstico de problemas.

Mudanças SDK

Os SDKs da Algorand foram aperfeiçoados para melhor apoiar o diagnóstico de problemas de código usando uma resposta dryrun a partir do ponto de extremidade REST. O Dryrun é usado para simular a submissão de uma transação à rede. Quando usado para uma aplicação ou chamada de assinatura inteligente, o dryrunendpoint retorna informações sobre como a AVM processou a lógica do programa. Isto oferece uma maneira rápida de encontrar bugs em seus contratos ou assinaturas inteligentes.

Os últimos lançamentos do SDK oferecem algumas mudanças no objeto DryrunResponse e fornecem alguns métodos adicionais que permitirão uma melhor compreensão de como seu contrato está sendo processado.

Exemplo de Contrato PyTeal

Para ilustrar estas características, é utilizado o seguinte contrato PyTeal.


from pyteal import 

args = Txn.application_args

on_complete = lambda oc: Txn.on_completion() == oc

isCreate = Txn.application_id() == Int(0)

isOptIn = on_complete(OnComplete.OptIn)

isClear = on_complete(OnComplete.ClearState)

isClose = on_complete(OnComplete.CloseOut)

isUpdate = on_complete(OnComplete.UpdateApplication)

isDelete = on_complete(OnComplete.DeleteApplication)

isNoOp = on_complete(OnComplete.NoOp)

return_prefix = Bytes("base16", "0x151f7c75")  _# Literally hash('return')[:4]_

@Subroutine(TealType.uint64)

def raise_to_power(x, y):

    i = ScratchVar(TealType.uint64)

    a = ScratchVar(TealType.uint64)

    return Seq(

        a.store(x),

        For(i.store(Int(1)), i.load() <= y, i.store(i.load() + Int(1))).Do(

           a.store(a.load()*x)

        ),

        Log(Concat(return_prefix, Itob(a.load()))),

        a.load(),

    )

def approval():

    router = Cond(

        [args[0] == MethodSignature("raise(uint64,uint64)uint64"), raise_to_power(Btoi(args[1]), Btoi(args[2])-Int(1))],

    )

    return Cond(

        [isCreate, Approve()],

        [isOptIn, Approve()],

        [isClear, Approve()],

        [isClose, Approve()],

        [isUpdate, Approve()],

        [isDelete, Approve()],

        [isNoOp, Return(router)]

    )

def clear():

    return Approve()

def get_approval():

    return compileTeal(approval(), mode=Mode.Application, version=6)

def get_clear():

    return compileTeal(clear(), mode=Mode.Application, version=6)

if __name__ == "__main__":

    with open("app.teal", "w") **as** f:

        f.write(get_approval())

    with open("clear.teal", "w") **as** f:

        f.write(get_clear())

Enter fullscreen mode Exit fullscreen mode

O acima exposto é um simples contrato inteligente compatível com a ABI que suporta um método, denominado raise. Esse método pega dois argumentos uint64 e retorna um uint64. Assuma que eles são x e y. A assinatura pelo método ABI é descrita como ”raise(uint64,uint64)uint64". Qualquer outra transação de chamada de aplicação com OnComplete configurado para NoOp será rejeitada.


 router = Cond(

        [args[0] == MethodSignature("raise(uint64,uint64)uint64"), raise_to_power(Btoi(args[1]), Btoi(args[2])-Int(1))],

    )

Enter fullscreen mode Exit fullscreen mode

A implementação do método leva o primeiro argumento (x) e o eleva à potência do segundo argumento (y). O método usa uma sub-rotina para fazer loop de 1 a y-1. Antes do loop ser executado, o valor x é armazenado na variável a que usa espaço temporário. Para cada iteração, esse valor armazenado é carregado e multiplicado por x e o resultado é armazenado de volta em a. Uma vez que o loop é finalizado, o valor retornado é registrado nos resultados da transação.


@Subroutine(TealType.uint64)

def raise_to_power(x, y):

    i = ScratchVar(TealType.uint64)

    a = ScratchVar(TealType.uint64)

    return Seq(

        a.store(x),

        For(i.store(Int(1)), i.load() <= y, i.store(i.load() + Int(1))).Do(

                a.store(a.load()*x),

        ),

        Log(Concat(return_prefix, Itob(a.load()))),

        a.load(),

    )

Enter fullscreen mode Exit fullscreen mode

Chamando o Contrato Exemplo

Para chamar este contrato implantado do SDK python, a seguinte sintaxe pode ser usada.


   _# Obter os parâmetros sugeridos_

    sp = client.suggested_params()

    _# construir o ATC (Que suporta ABI)_

    atc = AtomicTransactionComposer()   

    _# Criar um objeto signatário_

    signer = AccountTransactionSigner(pk)

    _# Construir o objeto do método_

    meth = Method("raise", [Argument("uint64"), Argument("uint64")], Returns("uint64"))

    _# Adicionar uma chamada ao contrato inteligente para elevar 2 para a 3ª potência_

    atc.add_method_call(app_id, meth, addr, sp, signer, method_args=[2,4])

    _# Executar a transação_

    atc.execute(client, 3);

Enter fullscreen mode Exit fullscreen mode

Neste exemplo, o Atomic Transaction Composer (ATC) é usado porque apóia nativamente os métodos de chamada ABI. O código cria o objeto ATC, cria um objeto signatário para assinar a transação, constrói o método de chamada ABI, e depois o adiciona ao objeto ATC. Finalmente, o objeto ATC é executado.

Usando Dryrun para Depurar o Contrato

Agora suponha que o código acima falhe ou devolva resultados imprecisos. Neste caso, você pode usar o Teal Debugger para depurar o contrato linha por linha ou pode usar um ponto de extremidade REST do Dryrun para simular todo o processamento do contrato. Para usar o Dryrun, você precisa adicionar o seguinte ao código:


   drr = transaction.create_dryrun(client, atc.gather_signatures())

    dr = client.dryrun(drr)

    dryrun_result = DryrunResponse(dr)

Enter fullscreen mode Exit fullscreen mode

A primeira linha cria o objeto DryrunRequest e a segunda linha envia o objeto para o nó conectado para processamento. O objeto DryrunResponse é o objeto principal utilizado para formatar a resposta do nó. Este objeto conterá todas as transações que são especificadas no ATC. Esta lista de transações pode ser iterada em Python usando o seguinte código.


for txn in dryrun_result.txns:

Enter fullscreen mode Exit fullscreen mode

Para cada transação na resposta, você tem vários métodos que agora podem ser usados para listar várias partes do dryrun (estas são as novas atualizações!). O método principal é o app_trace, um método que vai conter todo o rastreamento da pilha do contrato executado. Para imprimir isto, utilize o seguinte código.


print(txn.app_trace(StackPrinterConfig(max_value_width=30, top_of_stack_first=True)))

Enter fullscreen mode Exit fullscreen mode

Note que este método requer um objeto StackPrinterConfig que possui dois parâmetros. O primeiro formata a largura do rastreamento retornado e o segundo parâmetro determina se a pilha é exibida primeiro na lista como a parte superior ou a parte inferior da pilha.

A execução do código acima resulta no seguinte.

Image description

A coluna pc# neste rastreamento é o contador do programa e efetivamente informa o número do byte no contrato compilado. A coluna ln# tem o número da linha no código TEAL correspondente. A coluna scratch representa uma escrita no espaço temporário e a coluna stack exibe o que estava na pilha no momento em que a linha foi processada. Assim, se passarmos os dois argumentos para o contrato de exemplo (x=2,y=4), o seguinte será mostrado no rastreamento da pilha.

Image description

No topo desta imagem, você pode ver onde a sub-rotina é chamada (callsub). A linha store 1 mostra quando o código armazena a variável inicial y-1. Observe que ele aparece no rastreamento temporário na seguinte linha com o valor 1 = 3. Assim, no slot 1 do espaço temporário, está gravado um valor 3. A variável x, então é armazenada no slot 0. A coluna referente à pilha na imagem acima no momento da chamada store 1 contém os valores [3,2], que significa que temos o valor 3 no topo da pilha e o valor 2 logo abaixo dela. Você pode reverter essa ordem usando o parâmetro top_of_stack_first=False para a StackPrinterConfig. Note que ao armazenar o valor, ele é retirado do topo da pilha, como mostrado na linha abaixo da operação. As linhas 72 - 80 mostram o for loop no interior do código. A linha 78 mostra a multiplicação de x vezes x e esse valor é armazenado no espaço temporário 3. Este é o acumulador. Se o contrato é executado corretamente, a parte inferior do rastreamento vai imprimir o seguinte.

Image description

A linha 86 mostra onde o código carrega a string de bytes retornada. As linhas 87-90 ilustram o carregamento do valor acumulado, convertem-no em bytes, concatenam os dois, e finalmente registram o valor. A linha 91 carrega o valor acumulado no topo da pilha, retorna da sub-rotina e sai do programa com o retorno final. Como temos um 16 (um valor não zero) no topo da pilha, a execução do programa retornará com sucesso.

Métodos adicionais disponíveis com o objeto de resposta do dryrun permitem imprimir mudanças nas variáveis de estado globais ou locais, os registros, o custo do opcode da execução específica e o resultado da chamada do aplicativo. Estes podem ser adicionados usando o seguinte código python.


print(txn.app_trace(StackPrinterConfig(max_value_width=30, top_of_stack_first=**True**)))

            print("Mudanças Globais: ", txn.local_deltas)

            print("Mudanças Locais: ",txn.global_delta)

            print("Custo do Opcode: ",txn.cost)

            print("Registros: ",txn.logs)

            print("Mensagem do Aplicativo ",txn.app_call_messages)

Enter fullscreen mode Exit fullscreen mode

O método de custo pode ser valioso para determinar quanto de custo você ainda tem dentro de seu contrato. Cada chamada de inscrição autônoma tem um orçamento de 700. Isto pode ser expandido com o uso de transações internas adicionais, mas se fizer esta chamada com nosso contrato de teste, isto imprimirá os seguintes valores.

Image description

Para os parâmetros que passamos não temos mudanças globais ou locais do estado, o custo do opcode foi 97, os registros contêm os bytes para a string de retorno concatenada e o valor 16, e finalmente as mensagens do aplicativo indicam que o programa de aprovação foi chamado e passou na execução. Se alterarmos os parâmetros para x=2 e y=100, os resultados serão muito diferentes.

Image description

Na verdade, temos aqui dois erros. O primeiro é o overflow, que ocorre no loop quando o resultado de x * x excederia a capacidade do uint64. Além disso, o custo do opcode é de 859, o que excede nosso limite de 700. Se não tivéssemos o overflow, a chamada ainda falharia por causa da restrição orçamentária do opcode. A mensagem do aplicativo explica que a chamada é rejeitada e retorna as últimas linhas do programa executado.

Se você quiser apenas mostrar falhas na transação, você pode mudar o código para usar o método app_call_rejected.


   for txn in dryrun_result.txns:

        if txn.app_call_rejected():

            print(txn.app_trace(StackPrinterConfig(max_value_width=30, top_of_stack_first=**True**)))

            print(txn.local_deltas)

            print(txn.global_delta)

            print(txn.cost)

            print(txn.logs)

Enter fullscreen mode Exit fullscreen mode

Depurando com a GUI

O objeto DryRunRequest também pode ser gravado em disco e utilizado com a ferramenta de linha de comando tealdbg para percorrer cada linha do programa Teal. Para fazer isso, pode-se adicionar o seguinte ao código acima.

Informação

Esta seção cobre sucintamente o uso do Teal Debugger. Para mais informações sobre o depurador, veja a seção documentação do desenvolvedor.


 filename = "./dryrun.msgp"

    with open(filename, "wb") as f:

        import base64

        f.write(base64.b64decode(msgpack_encode(drr)))

Enter fullscreen mode Exit fullscreen mode

Isto salvará a instância codificada do pacote de mensagens do DryRunRequest para o arquivo dryrun.msgp. Para executar o depurador visual, os desenvolvedores podem simplesmente executar o seguinte comando. Note que estou definindo a porta de depuração específica, pois você pode ter um conflito de portas se estiver rodando o Sandbox ao mesmo tempo.


tealdbg debug -d dryrun.msgp --remote-debugging-port 9399

Enter fullscreen mode Exit fullscreen mode

Isto lançará o ouvinte debugger que pode ser conectado com as Ferramentas para Desenvolvedores do Chrome. Abra o navegador Chrome e entre na URL chrome://inspect.

Image description

Em seguida, selecione o botão configure e insira localhost: seguido da porta específica que o ouvinte está escutando no momento.

Image description

Clique em done e, em Remote Target, o depurador Algorand TEAL será listado.

Image description

Clique no link inspecionar para iniciar a sessão de depuração.

Image description

Avanços futuros

A equipe de engenharia continua a melhorar a experiência de depuração e já está implementando uma versão aprimorada do recurso dryrun para melhor apoiar as transações agrupadas de depuração. A próxima versão do dryrun irá emular corretamente a lógica on-chain levando em conta todas as mudanças de estado dentro de um grupo. Isto significa que se uma transação em um grupo mudar de estado, as transações de depuração subsequentes refletirão estas mudanças.

Este artigo foi escrito por Jason Weathersby, traduzido por Fátima Lima e seu original pode ser lido aqui.

02 de junho, 2022

Latest comments (0)