WEB3DEV

Cover image for Chamadas de Contrato a Contrato e Uma ABI Chegam à Algorand
Panegali
Panegali

Posted on • Atualizado em

Chamadas de Contrato a Contrato e Uma ABI Chegam à Algorand

Visão rápida do artigo

Máquina Virtual Algorand (AVM) 1.1

A AVM agora suporta chamadas de Contrato a Contrato e transações internas agrupadas, com aninhamento de até 8 níveis de profundidade!
A Algorand agora tem uma Interface Binária de Aplicação (ABI) e suporte total no SDKS para invocar métodos sem ter que conhecer os internos do contrato que está sendo chamado. A ABI agora permite que padrões adicionais sejam criados em cima da ABI.
Um novo Compositor Atômico está agora disponível nos SDKs, que permite a construção de grupos atômicos de forma simplificada e suporta plenamente a invocação de contratos em conformidade com a ABI.
Novos códigos de operação da AVM 1.1.


Neste lançamento, os desenvolvedores estarão equipados com ferramentas poderosas para expandir a funcionalidade de seus modelos de aplicação. Com suporte contrato a contrato, as aplicações podem criar ou invocar outros contratos na rede. Os contratos inteligentes implantados podem agora ser descobertos e invocados usando a Interface Binária de Aplicação (ABI). Além disso, grupos de transações podem ser criados de forma simplificada, permitindo uma forma muito mais versátil de invocar contratos que requerem transações adicionais.

Confira os principais recursos lançados na versão 1.1 abaixo.

Chamadas de Contrato a Contrato

No último lançamento, a Algorand acrescentou a capacidade dos contratos inteligentes de emitir "transações internas", onde um contrato inteligente pode emitir pagamentos adicionais e transações de ativos. Isto não apenas deu aos contratos inteligentes a capacidade de possuir (como uma garantia) e transferir Algos e ASAs, mas também a capacidade de criar ASAs que foram totalmente gerenciados pelo contrato inteligente. Neste lançamento, o recurso foi expandido para incluir a capacidade de um contrato inteligente chamar ou criar outro contrato inteligente usando transações internas. Isto deve permitir muitos cenários únicos para desenvolvedores de aplicativos, incluindo a criação programática de contratos dinâmicos e de vida curta na camada 1 do protocolo. Isto também permitirá que um contrato inteligente escolha seletivamente qual contrato secundário chamar com base na lógica inicial dos contratos. Além disso, são permitidas chamadas de contratos inteligentes profundamente aninhadas (até 8 níveis).

Chamada de um contrato inteligente usando a transação interna

1

Com o lançamento das chamadas de Contrato a Contrato, vem um modelo aprimorado para limites de transações. Na versão anterior, você podia emitir 16 transações internas por transação de aplicativo. Essa transação de aplicativo pode ser agrupada atomicamente com transações de aplicativo adicionais para estender esse limite. Com esta versão, um grupo atômico de transações (mesmo que o grupo consista em apenas uma transação) pode emitir até 256 transações internas no total. Isso oferece efetivamente a capacidade de uma transação de aplicativo não apenas emitir 256 transações internas, mas também usar o orçamento de opcode (código de operação) agrupado cumulativo de todas as 256 transações. Onde uma chamada de aplicativo tem um orçamento de 700 opcodes, se todas as transações forem usadas, isso equivale a um orçamento de opcodes no total de quase 180.000. Isso permite que os desenvolvedores façam alguns aplicativos muito sofisticados que se vinculam a muitos contratos inteligentes adicionais que transferem funções complexas para contratos de estilo utilitário.

InnerTxnBuilder.Begin(),
        InnerTxnBuilder.SetFields({
            TxnField.type_enum: TxnType.ApplicationCall,
            #  id específico do aplicativo a ser chamado
            TxnField.application_id: Int(1234),
            # Passe Argumentos para a aplicação
            TxnField.application_args: ["my first arg"],
        }),
        InnerTxnBuilder.Submit(),
Enter fullscreen mode Exit fullscreen mode

Ao utilizar transações internas, não são permitidas chamadas reentrantes. Por exemplo, no diagrama acima (figura 1), se o Contrato A chama o Contrato B, o Contrato B não pode chamar o Contrato A. No entanto, o Contrato A pode chamar o Contrato B várias vezes.

Todas as chamadas inteligentes do contrato, sejam chamadas por uma transação de aplicação ou uma transação interna, têm uma visão limitada do livro razão. Isto é feito para otimizar a rede para maior velocidade. O que pode ser visto no livro razão (usando vários opcodes) é restrito por um conjunto de vetores que são passados com cada transação de aplicação, seja uma transação de aplicação de nível superior ou uma transação interna. Estes vetores são discutidos na documentação. Estes vetores consistem em contas adicionais, aplicações (outros contratos inteligentes), e ativos que o contrato inteligente pode interrogar. Além destes vetores, o contrato também tem visibilidade para o remetente do endereço da transação, a aplicação que chamou o contrato inteligente atual (se esta for uma transação interna) e a identificação do ativo ou aplicação de qualquer ativo ou aplicação criada no grupo da transação atual desde que o ativo ou aplicação já tenha sido criado antes da execução do opcode específico. Cada aplicação também tem um conjunto de parâmetros, portanto, ao utilizar transações internas, estes valores também podem ser passados para chamadas subsequentes de transação de aplicação utilizando estes parâmetros.

Transações internas agrupadas

Outra nova característica das transações internas é a capacidade de criar e apresentar transações agrupadas atomicamente dentro de um contrato inteligente. Estas transações, como transações padrão agrupadas atomicamente, todas são bem sucedidas ou todas fracassam. Isto permite que os contratos inteligentes componham adequadamente os grupos de transações requeridos por outros contratos antes de chamá-los. Como em todas as transações internas, qualquer falha resultará na rejeição da transação de aplicação e qualquer mudança de estado dentro do contrato não será enviada para a blockchain. Para criar uma transação interna agrupada, os desenvolvedores devem usar o novo opcode itxn_next. Este opcode é usado depois de criar primeiro uma transação interna e significa que outra transação interna será agrupada com a transação interna anterior.

itxn_begin
    int pay
    itxn_field TypeEnum
    txn Accounts 1
    itxn_field Receiver
    int 1
    itxn_field Amount

    itxn_next
    int pay
    itxn_field TypeEnum
    txn Accounts 1
    itxn_field Receiver
    int 2
    itxn_field Amount

    itxn_submit
Enter fullscreen mode Exit fullscreen mode

O código TEAL acima simplesmente agrupa duas operações de pagamento em conjunto. Para fazer transações internas agrupadas em PyTeal use a função InnerTxnBuilder.Next() como mostrado abaixo.

        # Iniciar grupo de construção
        InnerTxnBuilder.Begin(),
        # Definir campos no primeiro grupo
        InnerTxnBuilder.SetFields({
            TxnField.type_enum: TxnType.Payment,
            TxnField.receiver: Txn.accounts[acct_ref1],
            TxnField.amount: amt1,
            TxnField.fee: Int(0) # make caller cover fees 
        }),
        # Comece a construir o próximo txn em grupo
        InnerTxnBuilder.Next(),
        # Definir campos no segundo grupo
        InnerTxnBuilder.SetFields({
            TxnField.type_enum: TxnType.Payment,
            TxnField.receiver: Txn.accounts[acct_ref2],
            TxnField.amount: amt2,
            TxnField.fee: Int(0) # make caller cover fees 
        }),
        # Enviar grupo
        InnerTxnBuilder.Submit(),
Enter fullscreen mode Exit fullscreen mode

Para mais informações sobre transações internas, consulte a documentação do desenvolvedor.

Interface Binária de Aplicação (ABI)

Contratos inteligentes Algorand são muito flexíveis sobre como os métodos do contrato podem ser chamados e como os resultados podem ser tratados. Embora este modelo ofereça muitas vantagens, uma desvantagem é que qualquer aplicação que possa querer fazer uso de seu contrato tem que saber como seu contrato lida com os parâmetros e processa os resultados. Isto também significa que muitas aplicações existentes podem ter que ser alteradas para suportar seu contrato inteligente específico.

Para resolver este problema, a Algorand implementou um padrão específico que define a chamada de um método e como processar tanto os argumentos de entrada quanto os valores de retorno. Esta norma descreve uma interface binária de aplicação (ABI) que pode ser usada para padronizar os contratos inteligentes. A ABI é descrita no padrão ARC4. Isto permite que as aplicações definam interações específicas de contratos inteligentes que sua aplicação irá suportar. Por exemplo, um fornecedor de carteira pode implementar uma forma padrão de listar os valores de estado dos contratos armazenados em um contrato. Se você quiser que sua aplicação trabalhe com esta carteira, você pode escrever seu contrato para implementar o padrão que a carteira fornece. Se houver um contrato inteligente existente em conformidade com a ABI que já esteja na blockchain, novos desenvolvedores de aplicativos podem implementar o padrão implementado no contrato publicado para apoiá-lo também.

Estas interações com contratos inteligentes são escritas em arquivos JSON contendo os metadados associados. Atualmente, isto inclui três tipos distintos de JSON. Estes três tipos são Method, Interface e Class. Uma interface Method define um método com os argumentos esperados e os tipos de retorno.

{   
    "name":"add",
    "desc":"method description",
    "args":[{"type":"uint32","desc":"first description"},
            {"type":"uint16","desc":"second description"}],
    "returns":{"type":"uint32","desc":"return description"}
}
Enter fullscreen mode Exit fullscreen mode

No exemplo acima, a interface Method define um método chamado add que leva dois argumentos inteiros não assinados de 32 e 16 bytes respectivamente. O método retorna um inteiro de 32 bytes não assinados.

Uma descrição Interface define um conjunto de métodos que devem ser implementados por um contrato a fim de aderir a algum padrão. Isto permite que os projetos em conformidade com a ABI implementem novos padrões em cima do ARC4 que ampliam ainda mais o ecossistema do desenvolvedor.

{
    "name":"interface",
    "methods":[
        {
            "name":"add",
            "args":[{"type":"uint32"},{"type":"uint32"}],
            "returns":{"type":"uint32"}
        },        
        {
            "name":"sub",
            "args":[{"type":"uint32"},{"type":"uint32"}],
            "returns":{"type":"uint32"}
        },

    ]
}
Enter fullscreen mode Exit fullscreen mode

Esta Interface fornece um exemplo de dois métodos (add e sub) que precisam ser implementados para suportar esta interface.

A descrição Contract define um contrato específico que existe atualmente na blockchain, e especifica todos os métodos que um contrato implementa. Isto pode ser pensado como uma implementação concreta de uma interface. No exemplo anterior da Interface, os contratos inteligentes podem implementar métodos adicionais além dos definidos e ainda considerados em conformidade com essa norma.

{
    "name":"demo-abi",
    "networks": {
        "wGHE2Pwdvd7S12BL5FaOP20EGYesN73ktiC1qzkkit8=": { "appID": 1234 },
        "SGO1GKSzyE7IEPItTxCByw9x8FmnrCDexi9/cOUJOiI=": { "appID": 5678 },
    },

    "methods":[
        {
            "name":"add",
            "description":"Add 2 integers",
            "args":[ { "type":"uint64" }, { "type":"uint64" } ],
            "returns": {"type":"uint64"}
        },
        {
            "name":"sub",
            "description":"Subtract 2 integers",
            "args":[ { "type":"uint64" }, { "type":"uint64" } ],
            "returns": {"type":"uint64"}
        },
        {
            "name":"mul",
            "description":"Multiply 2 integers",
            "args":[ { "type":"uint64" }, { "type":"uint64" } ],
            "returns": {"type":"uint64"}
        }
    ]
}
Enter fullscreen mode Exit fullscreen mode

No exemplo acima, o hash específico de gênese da rede e o id do aplicativo estão especificados no arquivo JSON. Além disso, este Contract define três métodos que o contrato específico oferece, juntamente com as expectativas de parâmetros e valores de retorno. Este contrato é considerado em conformidade com o exemplo Interface anterior.

Ao desenvolver um novo contrato inteligente, os desenvolvedores podem criar estes arquivos JSON antes de implantar seus contratos na blockchain.

2

Na fase de implantação e testes, outras aplicações podem ler estes arquivos JSON e então invocar corretamente seus métodos e processar os valores de retorno.

3

Todos os SDKs Algorand foram atualizados para suportar o consumo destes arquivos JSON e fornecer métodos para invocar chamadas contratuais específicas. Por exemplo, o seguinte JavaScript imprime todos os métodos em um contrato.

const buffC = fs.readFileSync("contract.json");
    const c1 = new algosdk.ABIContract( JSON.parse(buffC.toString()));
    console.log("Nome do Contrato: " + c1.name);
    console.log("APP ID: " + c1.appId);
    for (let mc of c1.methods) {
        console.log("Nome do Método " + mc.name);
    }
Enter fullscreen mode Exit fullscreen mode

A fim de invocar facilmente os métodos de contrato compatíveis com ABI, um novo recurso também foi adicionado a todos os SDKs. Isto é chamado de Compositor de Transações Atômicas, que é descrito na próxima seção.

Compositor de Transações Atômicas

Com este lançamento, a Algorand oferece uma nova maneira de construir transações e grupos de transações. Esta característica é chamada de Compositor de Transações Atômicas e também suporta totalmente o padrão ABI explicado na seção anterior. Por exemplo, para invocar o método add da seção anterior, o seguinte JavaScript pode ser usado.

const buff = fs.readFileSync("../contract.json")
    const contract = new algosdk.ABIContract( JSON.parse(buff.toString()))

    function getMethodByName(name: string): algosdk.ABIMethod  {
        const m = contract.methods.find((mt: algosdk.ABIMethod)=>{ return mt.name==name })
        if(m === undefined)
            throw Error("Método indefinido")
        return m
    }

    const sum       = getMethodByName("add")

    const sp = await client.getTransactionParams().do()
    const commonParams = {
        appId:contract.appId,
        sender:acct.addr,
        suggestedParams:sp,
        signer: algosdk.makeBasicAccountTransactionSigner(myAccount)
    }

    const comp = new algosdk.AtomicTransactionComposer()
    comp.addMethodCall({
        method: sum, methodArgs: [1,1], ...commonParams
    })
    comp.buildGroup()
    const result = await comp.execute(client, 2)
Enter fullscreen mode Exit fullscreen mode

Este exemplo simplesmente lê o arquivo Contract JSON, encontra o método add, e usa o Compositor de Transações Atômicas para invocá-lo. O compositor processa os argumentos do método, constrói uma assinatura específica do método para chamar o contrato, e processa o valor de retorno do contrato. O método addMethodCall do compositor é projetado para suportar a invocação de contratos inteligentes. Além disso, as transações padrão também podem usar o compositor. Por exemplo, para criar uma transação de pagamento simples com o compositor, o seguinte código pode ser usado.

const comp = new algosdk.AtomicTransactionComposer()
    const receiver = "HZ57J3K46JIJXILONBBZOHX6BKPXEM2VVXNRFSUED6DKFD5ZD24PMJ3MVA";
    let amount = 1000000;
    let sender = acct.addr;
    let txn = algosdk.makePaymentTxnWithSuggestedParams(sender, 
receiver, amount, undefined, undefined, sp);
    let transactionWithSigner = {
        txn: txn,
        signer: algosdk.makeBasicAccountTransactionSigner(acct)
      };

    comp.addTransaction(transactionWithSigner)
    const group = comp.buildGroup()
    const result = await comp.execute(client, 2)
    const r = result
    console.log(r)
Enter fullscreen mode Exit fullscreen mode

O método addTransaction do compositor permite que qualquer transação padrão seja adicionada ao grupo. O compositor suporta uma ou muitas transações e agora é a maneira padrão de construir grupos atômicos de transações. Por exemplo, para construir um grupo de transações, os desenvolvedores podem simplesmente chamar os métodos compositores addMethod ou addTransaction para cada transação subsequente.

comp.addMethodCall({
        method: sum, methodArgs: [1,1], ...commonParams
    })
    comp.addMethodCall({
        method: sub, methodArgs: [3,1], ...commonParams
    })
    comp.addMethodCall({
        method: div, methodArgs: [4,2], ...commonParams
    })
Enter fullscreen mode Exit fullscreen mode

O Compositor Atômico também contém uma máquina de estado que permite que o status do grupo atômico seja verificado em qualquer ponto. O grupo pode estar em um estado de construção, construído, assinado, apresentado e enviado. Estes representam as várias fases pelas quais um grupo atômico passa antes de ser enviado para a blockchain. Além da máquina de estado, o compositor é projetado para permitir chamadas do método addMethod que esperam que transações adicionais estejam disponíveis em um grupo de transações, antes de chamar um método de contrato específico. Suponha que tenhamos um método chamado myCall que leva dois parâmetros inteiros mas requer que esta chamada seja agrupada com uma transação de pagamento. Isto seria implementado da seguinte forma com o novo compositor.

let txn = algosdk.makePaymentTxnWithSuggestedParams(
acct.addr, acct.addr, 10000, undefined, undefined, sp);
comp.addMethodCall({
       method: myCall,
       methodArgs: [
           {
               txn: txn,
               signer: algosdk.makeBasicAccountTransactionSigner(acct)
           },
           1,
           2
       ],
       ...commonParams
   })

Enter fullscreen mode Exit fullscreen mode

Observe que o primeiro argumento para a chamada é na verdade uma transação de pagamento. Isto fará com que o compositor construa um grupo de duas transações, sendo a primeira a transação de pagamento e a segunda a chamada da aplicação e passando os dois parâmetros inteiros.

Para maiores informações sobre o Compositor Atômico, veja a documentação do desenvolvedor.

AVM 1.1

Com este lançamento, a Algorand atualizou a AVM para a versão 1.1 (TEAL 6), com suporte para mais opcodes. Vários destes opcodes são usados para melhor suportar chamadas de contratos para contratar e transações internas agrupadas, mas também foram adicionados opcodes de utilidades adicionais.

Usando o novo OpcodeBudget global permite que um contrato determine quantos ops ainda estão disponíveis para o orçamento de execução. Este limite será baseado no número de chamadas de aplicação que são utilizadas em um grupo específico de transações. Como um contrato pode tomar múltiplos caminhos através da lógica de qualquer contrato, isto permite que o código determine se o código tem orçamento suficiente antes de executar algum código que possa levar o contrato além do limite, resultando em uma falha. Usando isto em conjunto com o registro, os desenvolvedores devem ser capazes de ver onde em seu código que eles podem estar perto dos limites.

Quando uma transação interna é usada para chamar outro contrato inteligente, o contrato interno pode usar os dois novos globais CallerApplicationID e CalllerApplicationAddress para obter a ID e o endereço do contrato que os invocou.

O opcode acct_params_get está agora disponível para permitir que os desenvolvedores recuperem o saldo e o saldo mínimo de uma conta específica, que é baseado nos ativos optados e no número e armazenamento dos contratos que a conta optou ou criou. Por exemplo, um contrato pode querer ver se uma conta específica tem pelo menos 10 Algos livres para gastar. Isto pode ser feito com o seguinte TEAL.

// Obtenha o atual saldo Algo do remetente.
    int 0
    acct_params_get AcctBalance
    assert

    // Obtenha o saldo mínimo do remetente.
    int 0
    acct_params_get AcctMinBalance
    assert

    // Subtraia o saldo mínimo do remetente do saldo total e verifique
    // se é maior que 10,000000 Algo.
    -
    int 10000000
    >
    assert
Enter fullscreen mode Exit fullscreen mode

Este mesmo opcode pode ser usado para recuperar o endereço de autenticação das contas correntes. Este endereço será definido para o endereço ZeroAddress para contas que não tenham sido chaveadas novamente. Se o endereço auth não for o endereço ZeroAddress, a conta foi chaveada novamente para o endereço auth. Para verificar isto em código, o seguinte código pode ser usado.

// Obtenha o endereço de autenticação do remetente atual. Se não estiver definido, então um
    // ZeroAddress é devolvido.
    int 0
    acct_params_get AcctAuthAddr
    assert

    // Compare o endereço de retorno com o endereço ZeroAddress.
    global ZeroAddress
    ==
    assert
Enter fullscreen mode Exit fullscreen mode

Ao criar grupos internos de transação, muitas vezes um contrato pode precisar examinar propriedades específicas de transação para transações neste grupo interno. Dois novos opcodes gitxn e gitxna suportam a obtenção das propriedades do último grupo interno de transação.

Por exemplo, se tivermos duas transações de pagamento agrupadas semelhantes ao primeiro exemplo neste artigo, estes dois valores poderiam ser adicionados juntos usando o seguinte código TEAL.

itxn_begin
    int pay
    itxn_field TypeEnum
    txn Accounts 1
    itxn_field Receiver
    int 1
    itxn_field Amount

    itxn_next
    int pay
    itxn_field TypeEnum
    txn Accounts 1
    itxn_field Receiver
    int 2
    itxn_field Amount

    itxn_submit
    gitxn 0 Amount
    +
    gitxn 1 Amount
Enter fullscreen mode Exit fullscreen mode

Opcodes TEAL adicionais estão disponíveis, portanto, verifique a documentação de opcodes.

Conclusão

Muitos exemplos de chamadas de contrato a contrato, transações internas agrupadas, transações de compositores atômicos e os novos opcodes TEAL podem ser vistos em nosso repositório GitHub de relações com desenvolvedores.


Artigo escrito por Jason Weathersby, e publicado no Portal do Desenvolvedor Algorand. Traduzido e adaptado por Marcelo Panegali.

Top comments (0)