Introdução aos Smart Contracts¶
Um simples Smart Contract¶
Deixe-nos começar com o exemplo mais básico. Sem problemas se você não entender tudo neste momento; Iremos entrar em maiores detalhes depois.
Armazenamento¶
pragma solidity ^0.4.0;
contract SimpleStorage {
uint storedData;
function set(uint x) {
storedData = x;
}
function get() constant returns (uint) {
return storedData;
}
}
A primeira linha simplesmente nos diz que o código fonte foi escrito para
a versão 0.4.0 do Solidity ou mais recente e não quebra sua funcionalidade
(até, mas não incluindo a versão 0.5.0). Isso é para garantir que o
contrato não se comporte de forma diferente com uma nova versão do compilador.
A palavra chave pragma
é chamada desta maneira, porque, no geral
pragmas são instruções para o compilador sobre como tratar o
código fonte (por exemplo pragma once <https://en.wikipedia.org/wiki/Pragma_once>).
Um contrato, no conceito do Solidity, é um conjunto de códigos (functions) e
dados (state), que residem em um específico endereço na rede blockchain Ethereum.
A linha uint storedData;
declara uma variável de estado chamada storedData
do
tipo uint
(integer não assinado de 256 bits). Você pode pensar nisso como um único slot
em um banco de dados que pode ser consultado e alterado chamando funções do
código que gerencia o banco de dados. No caso do Ethereum, ele é sempre o contrato
proprietário. E neste caso, as funções set
e get
podem ser usadas para modificar
ou recuperar o valor da variável.
Para acessar uma variável de estado, você não precisa do prefixo this.
, como é comum em
outras linguagens.
Este contrato ainda não faz muita coisa (devido à infra-estrutura
construida pelo Ethereum), além de permitir que qualquer um possa armazenar um simples número que é acessível por
qualquer um no mundo sem uma maneira (viável) de impedir você de publicar
este número. Naturalmente, qualquer um pode somente chamar novamente a função set
, com um valor diferente
e sobrescrever seu número original, mas o número permanecerá armazenado no histórico
do blockchain. Mais tarde, iremos ver como você pode impor restrições de acesso,
de maneira que somente você possa alterar este número.
Nota
Todos os identificadores (nomes de contratos, nomes de funções e nomes de varáveis) estão restritos à tabela de caracteres ASCII. É possível armazenar dados codificados UTF-8 em variáveis de string.
Aviso
Seja cuidadoso com o uso do texto Unicode, pois caracteres similares (ou mesmo idênticos) podem ter pontos de código diferentes e, como tal, serão codificados como uma matriz de bytes diferente.
Exemplo de Sub Moeda¶
O contrato seguinte irá implementar a forma mais simples de uma criptomoeda. É possível gerar moedas do nada, mas somente a pessoa que criou o contrato terá a capacidade de fazer isto (é trivial implementar um esquema de emissão diferente). Além disso, qualquer um pode enviar moedas para outros, sem necessidade de se registrar com usuário e senha. Tudo o que você precisa é de um par de chaves Ethereum.
pragma solidity ^0.4.0;
contract Coin {
// The keyword "public" makes those variables
// readable from outside.
address public minter;
mapping (address => uint) public balances;
// Events allow light clients to react on
// changes efficiently.
event Sent(address from, address to, uint amount);
// This is the constructor whose code is
// run only when the contract is created.
function Coin() {
minter = msg.sender;
}
function mint(address receiver, uint amount) {
if (msg.sender != minter) return;
balances[receiver] += amount;
}
function send(address receiver, uint amount) {
if (balances[msg.sender] < amount) return;
balances[msg.sender] -= amount;
balances[receiver] += amount;
Sent(msg.sender, receiver, amount);
}
}
Este contrato introduz alguns novos conceitos, Passaremos por eles passo à passo.
A linha address public minter;
declara uma variável de estado do tipo endereço (address),
que é publicamente acessível. O tipo address
é um valor de 160 bits
que não permite nenhuma operação aritmética. É adequada para armazenar endereços de contratos ou
pares de chaves pertencentes à entidades externas. A palavra-chave public
automaticamente
gera uma função que lhe permite acessar o valor atual do estado da variável.
Sem esta palavra-chave, outros contratos podem não ter acesso à variável.
A função irá aparecer dessa maneira:
function minter() returns (address) { return minter; }
Naturalmente, adicionando-se a função, exatamente como está, não irá funcionar porque nós devemos ter a função e uma variável de estado com o mesmo nome, mas felizmente, você pegou a ideia - o compilador irá entender para você.
A próxima linha, mapping (address => uint) public balances;
, também
cria uma variável públca de estado, mas é um tipo de dado mais complexo.
O tipo mapa endereça integers não assinadas.
Mappings podem ser vistas como hash tables
Praticamente inicializado de tal forma que cada chave possível existe e é mapeado para
um valor que representação em bytes é toda em zeros. Esta analogia não vai muito
longe, porém, como não é possível obter uma lista de todas as chaves
de um mapeamento nem uma lista de todos os valores. Tenha em mente (ou
melhor, mantenha uma lista ou use um tipo de dados mais avançado) o que você
adicionou no mapeamento ou use em um contexto onde isto não é necessário,
como este. A função getter getter function criada pela palavra chave public
é um pouco mais complexa neste caso. Parece aproximadamente como o seguinte:
function balances(address _account) returns (uint) {
return balances[_account];
}
Como você pode ver, você pode usar esta função para facilmente pesquisar o saldo de uma conta simples.
A linha event Sent(address from, address to, uint amount);
declara
um auto-nomeado «event» que é eliminado na última linha da função
send
. Interfaces de usuário (assim como uma aplicação de servidor, naturalmente) pode
escutar estes eventos sendo eliminados no blockchain sem muito custo.
Assim que é eliminado, o listener irá receber o
argumento from
, to
e amount
, que torna fácil rastrear
transações. Para escutar estes eventos, você pode usar
Coin.Sent().watch({}, '', function(error, result) {
if (!error) {
console.log("Coin transfer: " + result.args.amount +
" coins were sent from " + result.args.from +
" to " + result.args.to + ".");
console.log("Balances now:\n" +
"Sender: " + Coin.balances.call(result.args.from) +
"Receiver: " + Coin.balances.call(result.args.to));
}
})
Perceba como a função ``balances``, gerada automaticamente, é chamada a partir
do interface do usuário.
A função especial Coin
é o contrutor que é executado durante a criação do contrato e
não pode ser chamada depois. Ela armazena permanentemente o endereço da pessoa que criou
o contrato; msg
(junto com tx
e block
) é uma variável global mágica que
contém algumas propriedades que permitem acessar o blockchain. msg.sender
é
sempre o endereço onde a função corrente (externa) é originada.
Finalmente, a função que era realmente encerrar com o contrato e pode ser chamada
pelos usuários e contratos são mint
and send
.
Se mint
é chamada por alguém que não a conta que criou o contrato,
nada irá acontecer. Por outro lado, send
pode ser usado por qualquer um (que já
tenha alguma dessa moeda) para enviar moedas para alguém. Perceba que se você usar
este contrato para enviar moedas para um endereço, você não irá ver nada quando você
olhar para este endereço através de um explorador blockchain, porque o fato de você ter enviado
as moedas e os saldos alterados só são armazenados no armazenamento de dados deste
Contrato de moeda particular. Com o uso de eventos, é relativamente fácil criar
um «blockchain explorer» que rastreia transações e saldos da sua nova moeda.
Princípios do Blockchain¶
Blockchain como um conceito não é muito difícil de entender por programadores. A razão é que a maioria das complicações (mining, hashing, elliptic-curve cryptography, peer-to-peer networks, etc.) é justamente para prover um conjunto de características e promessas. Uma vez que você aceitas estas características como certas, você não precisa se preocupar com a tecnologia adjacente - ou você precisa saber como a Amazon´s AWZ trabalham internamente para usá-las?
Transações¶
O blockchain é uma base de dados transacional globalmente distribuída. Isso significa que qualquer um pode ler as entradas do banco de dados, somente por participar da rede. Se você deseja mudar alguma coisa no banco de dados, você tem que criar uma transação que precisa ser aceita por todos os demais (participantes).
A palava transação implica que a mudança que você quer fazer (assumindo que você queira mudar dois valores ao mesmo tempo) não é feita nem aplicada completamente. Além disso, enquanto sua transação é aplicada ao banco de dados, nenhuma outra transação pode alterá-la.
Por exemplo, imagine uma tabela que lista os saldos de todas as contas em um moeda eletrônica. Se uma transferência de uma conta para outra for solicitada, a natureza transacional do banco de dados garante que, se o valor for subtraído de uma conta, é sempre adicionado à outra conta. Se, por qualquer motivo, não for possível adicionar o valor à conta destino, a conta de origem também não será modificada.
Por exemplo, imagine uma tabela que lista os saldos de todas as contas de uma moeda eletrônica. Se uma transferência de uma conta para outra for solicitada, a natureza transacional do banco de dados garante que, se o valor for subtraído de uma conta, é sempre adicionado à outra conta. Se, por qualquer motivo, não for possível adicionar o valor à conta destino, a conta de origem também não será modificada.
Além disso, uma transação sempre é criptograficamente assinada pelo remetente (seu criador). Isso torna direto proteger o acesso a modificações específicas da base de dados. No exemplo da moeda eletrônica, uma verificação simples garante que apenas a pessoa que possua as chaves da conta possa transferir dinheiro com ela.
Blocks¶
Um dos principais obstáculos a superar é o que, em termos Bitcoin, é chamado de «ataque de dupla despesa»: O que acontece se ocorrer duas transações na rede que querem esvaziar uma conta, um chamado conflito?
A resposta abstrata à isso é que você não precisa se preocupar. Uma ordem das transações será selecionada para você, as transações serão empacotadas no que é chamado de «bloco» (block) e então eles serão executados e distribuídos entre todos os nós participantes. Se duas transações se contradizem, o que acaba sendo a segunda transação será rejeitada e não se tornará parte do bloco.
Esses blocos formam uma seqüência linear no tempo e é aí que deriva o termo «bloco em cadeia» (blockchain). Os blocos são adicionados à cadeia em intervalos bastante regulares - para o Ethereum é aproximadamente a cada 17 segundos.
Como parte do «mecanismo de seleção de pedidos» (que é chamado de (mining) «mineração»), pode acontecer que os blocos são revertidos de tempos em tempos, mas apenas na «ponta» da corrente. Quanto mais blocos são adicionados no topo, menos provável é. Então, pode ser que suas transações sejam revertidas e até mesmo removidas do blockchain, mas quanto mais você aguardar, menor a probabilidade.
A Máquina Virtual Ethereum (EVM)¶
Visão Geral¶
A Máquina Virtual Ethereum (Ethereum Virtual Machine - EVM) é o ambiente em tempo de execução para contratos inteligentes (smart contracts) no Ethereum. Não é apenas sandbox, mas realmente isolado completamente, o que significa que o código está sendo executado pelo EVM não tem acesso à rede, sistema de arquivos ou outros processos. Os contratos inteligentes (smart contracts) têm acesso limitado à outros contratos inteligentes.
Contas (Accounts)¶
Existem dois tipos de contas (accounts) no Ethereum que compartilham o mesmo espaço de endereço: External accounts (contas externas) que são controladas por pares de chaves público-privadas (isto é, por humanos) e contract accounts (contas contratuais) que são controladas pelo código armazenado junto com a conta.
O endereço de uma conta externa é determinado a partir da chave pública enquanto o endereço de um contrato é determinado no momento em que o contrato é criado (é derivado do endereço do criador e do número das transações enviadas a partir desse endereço, o chamado «nonce»).
Independentemente ou não do código de armazenamento das contas, os dois tipos são tratados igualmente pelo EVM.
Cada conta possui um valor-chave persistente mapeado de 256-bits chamado storage de 256-bits
Além disso, cada conta tem balance (saldo) em Ether (em «Wei» para ser exato) que pode ser modificado enviando transações que incluem Ether.
Transações (Transactions)¶
Cada transação (transaction) é uma mensagem que é enviada de uma conta para outra conta (que pode ser a mesma ou uma conta especial zero-account, veja abaixo). Pode incluir dados binários (sua carga útil) e Ether.
Se a conta alvo contiver um código, esse código é executado e a carga útil é fornecida como dados de entrada.
Se a conta alvo for do tipo conta zero (a conta com o
endereço 0
), a transação cria um novo contrato new contract.
Como já mencionado, o endereço desse contrato não é o endereço zero, mas um endereço derivado do remetente e o número de transações enviadas (o «nonce»). A carga útil de tal transação de criação de contrato é considerada como sendo Bytecode EVM e executado. A saída desta execução é permanentemente armazenada como o código do contrato. Isso significa que, para criar um contrato, você não envia o código real do contrato, mas, de fato, o código que retorna esse código.
Gas¶
Após a criação, cada transação é cobrada com uma certa quantidade de gas, cujo objetivo é limitar a quantidade de trabalho que é necessária para executar a transação e para pagar esta execução. Enquanto o EVM executa o transação, o gás é gradualmente usado de acordo com regras específicas.
O gas price é um valor definido pelo criador da transação, quem
tem que pagar o o resultado de gas_price * gas
antes da conta de envio.
Se algum gás for deixado após a execução, ele será reembolsado da mesma maneira.
Se o gás for esgotado em qualquer ponto (isto é, o saldo disponível de Gas fica negativo), é desencadeada uma exceção em gas (out-of-gas), que reverte todas as modificações feito para o estado no quadro de chamada atual.
Armazenamento, Memória e Pilha¶
Cada conta tem uma área de memória persistente chamada storage. Storage é um valor-chave que mapeia palavras de 256-bits para palavras de 256-bits. Não é possível enumerar o armazenamento dentro de um contrato e é comparativamente caro para ler e ainda mais, para modificar armazenamento. Um contrato não pode ler nem escrever em qualquer armazenamento separado por conta própria.
A segunda área de memória é chamada de memory, de que um contrato obtém uma instância recém autorizada para cada chamada de mensagem. A memória é linear e pode ser endereçada no nível do byte, mas as leituras são limitadas a uma largura de 256 bits, enquanto escrever pode ser de 8 bits ou 256 bits de largura. A memória é expandida por uma palavra (256 bits), enquanto acessando (quer lendo ou escrevendo) uma palavra de memória anteriormente intacta (ou seja, qualquer deslocamento dentro de uma palavra). No momento da expansão, o custo em Gas deve ser pago. A memória é mais cara a medida que cresce (em escala quadrática).
O EVM não é uma register machine mas uma stack machine, portanto todas os comandos são realizados em uma área chamada stack. Tem o tamanho máximo de 1024 elementos e contém palavras de 256 bits. O acesso à pilha (stack) é limitado ao topo (top end) na seguinte maneira: É possível copiar um dos os 16 elementos superiores para o topo da pilha ou trocar o elemento superior com um dos 16 elementos abaixo. Todas as outras operações levam os dois primeiros (ou uma, ou mais, dependendo de a operação) elementos da pilha e empurre o resultado para a pilha. Naturalmente é possível mover elementos de pilha para armazenamento ou memória, mas não é possível acessar elementos arbitrários mais profundos na pilha sem primeiro remover o topo da pilha.
Lista de Instruções¶
A lista de instruções do EVM é mantido no mínimo para evitar implementações incorretas que podem causar problemas de consenso. Todas as instruções operam no tipo básico de dados, palavra de 256-bits. As operações mais usuais de aritmética, bit, lógica e comparação estão presentes. Desvios condicionais e não-condicionais são possíveis. Além disso, contratos podem acessar propriedades relevantes do bloco atual como seu número e carimbo de tempo (timestamp).
Chamadas por Mensagem¶
Contratos podem chamar outros contratos ou enviar Ether para contas não-contrato através de chamadas de mensagens. As chamadas de mensagens são semelhantes para as transações, na medida em que eles têm uma origem, um destino, carga útil de dados, dados de Ether, Gas e informações de retorno. Na verdade, cada transação consiste em uma chamada de mensagem de nível superior que, por sua vez, pode criar mais chamadas de mensagens.
Um contrato pode decidir quanto do gas restante deve ser enviado com a chamada de mensagem interna e quanto ele deseja reter. Se ocorrer uma exceção out-of-gas (sem gas) na chamada interna (ou qualquer outra exceção), isso será sinalizado por um valor de erro colocado na pilha. Neste caso, somente o gas enviado junto com a chamada é consumido. No Solidity, o contrato de chamada causa uma exceção manual por padrão em tais situações, de modo que as exceções «expandam» a pilha de chamadas.
Como já dissemos, o contrato chamado (que pode ser o mesmo que o chamador) receberá uma instância de memória recém autorizada e terá acesso à chamada carga útil - que será fornecida em uma área separada chamada calldata. Depois de terminar a execução, pode retornar dados que serão armazenados em uma localização na memória do chamador pré-alocada pelo chamador.
As chamadas são limitadas (limited) para uma profundidade de 1024, o que significa que para operações mais complexas, loops devem ser preferidos em relação a chamadas recursivas.
Delegatecall / Callcode and Libraries¶
Existe uma variante especial de chamada de mensagem, chamada delegatecall
que é identica à chamada de mensagem, exceto pelo fato que
o código no endereço de destino é executado no contexto do contrato
chamado e msg.sender
e msg.value
não alteram seus valores.
Isto significa que um contrato pode dinamicamente carregar código de diferentes endereços em tempo de execução. Armazenamento (Storage), Endereço Atual (current address) e saldo (Balance) podem ainda se referir ao contrato chamador, apenas o código é retirado do endereço chamado.
Isto torna possível a implementação da característica de «library» no Solidity: Códigos reusáveis de «library» podem ser aplicados ao armazenamento (storage) dos contratos, normalmente para implementar estrutura de dsos complexas.
Logs¶
É possível armazenar dados em uma estrutura de dados especialmente indexada que mapeia até o nível do bloco. Esta característica chamada logs é usada pelo Solidity para implementar eventos (events). Os contratos não podem acessar os dados de log depois de terem sido criados, mas eles pode ser acessado de forma eficiente, fora da cadeia de blocos. Desde que algumas partes dos dados do log são armazenados no bloom filters, é possível pesquisar por estes dados de uma maneira eficiente e criptograficamente segura, sendo assim, outros participantes (clientes) da rede que não baixarem o blockchain todo («light clients»), podem, asinda, assim, encontrar estes logs.
Create¶
Contratos podem ainda criar outros contratos usando um opcode especial (em geral, eles não chamam o zero address). A única diferença entre esses create calls e uma chamada de mensagem normal é que a carga útil (payload) é executada e o resultado é armazenado como code e o chamador/criador recebe o endereço deste novo contrato na pilha (stack).
Self-destruct¶
A única maneira de um código ser removido do blockchain é
quando o contrato que ele endereça realizar a operação selfdestruct
.
O Ether remanescente, armazenado neste endereço, é enviado para um
destino designado e então o armazenamento e o código é removido.
Aviso
mesmo quando o código do contrato não contém uma chamada para selfdestruct
,
ele pode ainda realizar esta operação chamando delegatecall
or callcode
.
Nota
A poda de contratos antigos pode ou não ser implementada pelos Clientes Ethereum. Além disso, os nós de arquivo podem optar por manter o armazenamento do contrato e código indefinidamente.
Nota
Atualmente external accounts não podem ser removidas do estado.