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)
})
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);
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);
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)
})
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();
...
_
indica que a função não possui argumentos.resolve
é como os dados são enviados de volta ao front-end. Neste exemplo, o valor10
é enviado de volta para o front-end.10
deve ser do mesmo tipo de dados definido na função da interfacecheckValue
.
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
...
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);
...
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);
...
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);
...
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}`);
...
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;
}
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}`);
});
...
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.
Latest comments (0)