WEB3DEV

Cover image for Entendendo como o contrato inteligente Reach interage com um front-end da web
Adriano P. Araujo
Adriano P. Araujo

Posted on

Entendendo como o contrato inteligente Reach interage com um front-end da web

Introdução

Para desenvolvedores novos na linguagem de programação Reach, um dos obstáculos mais difíceis a serem enfrentados é entender exatamente como um contrato inteligente Reach interage com o front-end. Isso ocorre principalmente porque o contrato inteligente interage com o front-end de forma assíncrona.

Por si só, a lógica do contrato inRteligente é relativamente direta. O programa pode incluir loops com condições definidas para quantas vezes eles executam. Também pode incluir corridas em que vários participantes correm para enviar dados ao contrato inteligente. Pode incluir forks (garfos) que agem como uma declaração de switch em Javascript convencional. Nada fora do comum. No entanto, quando a lógica do contrato inteligente atende à lógica do front-end, pode ser confuso. Vou tentar desvendar os nós que cercam essa união.

A primeira coisa a observar é que diferentes tipos de entidades podem ser definidos no contrato inteligente. A maneira como o front-end interage com o contrato inteligente depende de qual tipo de entidade o front-end está representando. Algumas dessas entidades incluem:

  • Participant

  • ParticipantClass

  • API

  • View

  • Event

Participant ( Participante )

Na minha opinião, os participants são os mais fáceis de implementar. Eles interagem com o contrato inteligente através de seu objeto interativo. O contrato inteligente decide a estrutura desse objeto, especificando sua interface. O front-end precisa apenas fornecer um objeto que siga a interface esperada e, em seguida, conecte esse objeto ao contrato inteligente. O contrato inteligente pode ler os valores do objeto fornecido, bem como chamar os métodos do objeto.

Através dos valores do objeto, os dados são passados do front-end para o contrato inteligente. Através dos métodos do objeto, os dados vão e voltam entre eles, dependendo dos valores de retorno do método. Para visualizar isso, vejamos um exemplo.

Digamos que há uma participante  Alice que fornece uma quantia  wager para o contrato inteligente e pode executar duas ações getHand (obter uma mão) e seeOutcome (ver o resultado).


const Alice = Participant('Alice', {

    wager: UInt,

    getHand: Fun([], UInt),

    seeOutcome: Fun([UInt], Null)

})

Enter fullscreen mode Exit fullscreen mode
  • wager  é um número e deve ter um valor quando o objeto é inicializado no front-end.

  • getHand é uma função sem argumentos. Retorna um número.

  • seeOutcome  é uma função com um argumento, um número. Não retorna nada.

No front-end, é assim que o objeto é inicializado:


...



// define objeto de iteração

const Alice = {

    wager: 7, // porque os números primos arrasam

    getHand: () => {

        // lógica para computar o valor do retorno

        // valor pego é enviado para o contrato-inteligente

        // valor pode ser computado assincronamente ou sincronicamente

       let hand = 5; 

        return hand

    },

    seeOutcome: (numberFromSmartContract) => {

        console.log(numberFromSmartContract)

    }

}



// conecta ao contrato inteligente

const contract = account.contract(backend);

backend.Alice(contract, Alice);

Enter fullscreen mode Exit fullscreen mode

No contrato inteligente, os valores e métodos no objeto front-end são chamados aqui:


Alice.only(() => {

    const wager = declassify(interact.wager);

    const hand = declassify(interact.getHand());

    interact.seeOutcome(100);

  });

  Alice.publish(wager, hand);

Enter fullscreen mode Exit fullscreen mode

Quando o fluxo do programa no contrato inteligente chega ao interact.wager, o valor da aposta (7) especificado pelo front-end é armazenado na variável wager no contrato inteligente.

Quando o interact.getHand() é chamado, o fluxo do programa do contrato inteligente faz uma pausa e aguarda um valor de retorno do front-end. Até que isso seja fornecido, o contrato inteligente pausa e aguarda. O contrato inteligente se comporta dessa maneira para cada método interativo com um valor de retorno.

Quando o interact.seeOutcome(100) é chamado, o valor 100 é passado para o front-end como um argumento para o método seeOutcome do objeto Alice. Este passo não é assíncrono.

Classe do participant

O ParticipantClass interage com o front-end da mesma maneira que o Participant. Mas se comporta de maneira estranha quando o contrato inteligente espera dados da função ParticipantClass de interação. O ParticipantClass foi preterido pelo Reach e deve ser totalmente evitado.

API

APIs são funções que você pode chamar em um contrato inteligente. Ao contrário do Participant, essas funções não são chamadas pelo contrato inteligente, mas pelo front-end. O contrato inteligente decide quando a função pode ser chamada, mas somente durante essa janela a função pode ser chamada. Chamar a função para fora dessa janela enviaria uma mensagem de erro ao front-end.

A mensagem de erro se parece com isso: Error: Expected the DApp to be in state(s) [2], but it was actually in state 1.

Como com o Participant, o contrato inteligente define a interface para as funções da API. No contrato inteligente, é assim que a classe API é definida:


const UserActions = API('UserActions', {

    checkValue: Fun([], UInt),

    incrementValue: Fun([UInt], Null)

})

Enter fullscreen mode Exit fullscreen mode

Aqui, a classe API tem duas funções que podem ser chamadas no front-end. A função checkValue não tem argumentos. Isso significa que nenhum dado chega ao contrato inteligente do front-end. Isso ocorre porque a função é chamada no front-end e o chamador de uma função é responsável por fornecer seus argumentos. Ele tem um valor de retorno, o que significa que os dados são enviados do contrato inteligente para o front-end. Isso fará mais sentido para você quando você vir um trecho de código da chamada de função no front-end.

O incrementValue tem um argumento de função. Isso significa que os dados são enviados para o contrato inteligente pelo front-end. No entanto, ele não possui um valor de retorno, o que significa que nenhum dado é enviado de volta ao front-end do contrato inteligente.

Existem duas maneiras de iniciar uma função de API em um contrato inteligente: como parte de um fork (o equivalente a uma declaração switch em Javascript) ou por si só. Aqui está um exemplo de iniciar a Função API checkValue por si só.


...

//O programa faz uma pausa aqui até que a função checkValue seja chamada no

//front-end

const [_, resolve] =

    call(UserActions.checkValue);

    resolve(10);

commit();



...

Enter fullscreen mode Exit fullscreen mode
  •  _  indica que a função não possui argumentos.

  •  resolve é como os dados são enviados de volta ao front-end. Neste exemplo, o valor 10 é enviado de volta para o front-end. 10 deve ser do mesmo tipo de dados definido na função da interface  checkValue.

Para a Função API incrementValue:


...



//O programa faz uma pausa aqui até que a função incrementValue seja chamada no front-end  


const [newValue, resolve] =

    call(UserActions.incrementValue);

    resolve();

commit();



//newValue está disponível para o resto do programa



...

Enter fullscreen mode Exit fullscreen mode

O newValue é o argumento passado para a função quando chamado no front-end. Torna-se disponível para cálculos no contrato inteligente.

No front-end, é assim que se chama a função checkValue:


...



//deve estar dentro de um escopo de função assíncrona

const contract = account.contract(backend, contractInfo);

const returnedValue = await contract.apis.UserActions.checkValue();

console.log(returnedValue);



...

Enter fullscreen mode Exit fullscreen mode
  • O valor de retorno é armazenado em returnedValue.

  • Encerre a chamada de função em um try-catch para lidar com possíveis erros devido a tempo incorreto ou argumentos inválidos.

Para a função incrementValue:


...



//deve estar dentro de um escopo de função assíncrona

await contract.apis.UserActions.incrementValue(14);



...

Enter fullscreen mode Exit fullscreen mode

O incrementValue não possui valores de retorno e, portanto, não precisa ser armazenado em uma variável.

Garantir que as funções da API sejam chamadas pelo front-end em sincronia adequada com o contrato inteligente pode ser um desafio. A melhor maneira de fazer isso é acompanhar o fluxo do programa no contrato inteligente e apenas disponibilizá-las quando viáveis.

Por exemplo, a função incrementValue só pode ser chamada após a função checkValue ser chamada.

View ( Visualização )

Como a API, Views são funções que podem ser chamadas no contrato inteligente do front-end. No entanto, as visualizações se comportam de maneira diferente da classe API. Depois que uma função View é definida, ela pode ser chamada no front-end a qualquer momento, desde que o contrato inteligente ainda esteja ativado.

Através do seu valor de retorno, os dados são passados do contrato inteligente para o front-end. No entanto, esse valor é envolvido em um tipo Maybe (porque esta visualização pode não ser inicializada).

Vamos definir uma função de exibição simples que retorne o quadrado de qualquer número passado para ela.


const Square = View({

    getSquare: Fun([UInt], UInt), 

    //aceita um número do front-end e retorna um número para o front-end 

});



...



//Durante a etapa de consenso

Square.getSquare.set((m) => m * m);



...

Enter fullscreen mode Exit fullscreen mode

Agora a função  View pode ser acessada no front-end do programa, mesmo que o contrato tenha passado da linha de código em que a função foi implementada.

No front-end:


...



/* envolve o escopo na função assíncrona */



const contract = account.contract(backend);

const square = await contract.v.getSquare(4); //16

console.log(`The square of 4 is ${square}`);



...

Enter fullscreen mode Exit fullscreen mode

Chamando o getSquare ele desencadeará sua contraparte inteligente do contrato, sem problemas com os quais se preocupar.

Events ( Eventos )

Os events funcionam de modo bastante interessante. Eles permitem que os dados fluam apenas do contrato inteligente para o front-end. Com os events, o front-end pode acompanhar o fluxo do programa no contrato inteligente, como o console.log() do Javscript .

Depois que um event é criado no contrato inteligente, o front-end pode se inscrever e ser notificado sempre que o evento for acionado no contrato inteligente.

Suponha que haja um event chamado fullCycle que é acionado toda vez que um loop while completa um ciclo. Ele seria escrito assim no contrato inteligente:


...



const Notify = Events({

    fullCycle: [UInt]  //os dados fluem em apenas uma direção

});



...



var [x] = [0];

invariant(balance() == 0);

while(x < 10){



    ...



    //Deve estar na etapa de consenso

    Notify.fullCycle(x);



    ...



    [x] = [ x + 1 ]

    continue;

}

Enter fullscreen mode Exit fullscreen mode

O valor iterado x é passado para o front-end através do event fullCycle. Uma coisa muito legal sobre Events é que outros dados também são passados para o front-end, não apenas para o argumento da função. O timeStamp para esse event, em network time, é enviado também.

No front-end, é assim que o evento é inscrito:


...



const contract = acc.contract(backend);



contract.e.fullCycle.monitor((evt) => {

    const { when, what: [ iteration ] } = evt;

    console.log(`${iteration} registered at ${when}`);

});



...

Enter fullscreen mode Exit fullscreen mode

Ao contrário da classes API e View que são funções front-end, o Event é um objeto com métodos. Cada método fornece uma maneira diferente de interagir com o Event no contrato inteligente. Nossa única preocupação por enquanto é o método  monitor.

O método monitor tem um argumento evt. O evt é o objeto { when: Time, what: T ou quando: Tempo, o que: T }, onde T são os dados passados do contrato inteligente para o front-end através do evento, e when é o carimbo de data / hora da rede do event.O T é passado como uma tuple, e deve ser destruído como um.

Sempre que o event é acionado no contrato inteligente, o método monitor é chamado. A lógica de programação pode ser adicionada a esse método para executar ações como acionar uma notificação para os usuários ou levá-las para uma página da web diferente etc.

Conclusão

Brincar com cada entidade melhorará sua compreensão de como usá-las de maneira eficaz e permitirá que você tome melhores decisões sobre qual entidade usar em diferentes situações. Cada um deles tem cenários específicos onde melhor se encaixa.


Este artigo foi escrito por  Timothy Ogwulumba e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.

Oldest comments (0)