// Primeiramente, um contrato de um Banco simples // Permite depósitos, retiradas e checagens de saldo // banco_simples.sol (note a extensão .sol) /* **** INCICIO DO EXEMPLO **** */ // Declare a versão do compilador. pragma solidity ^0.4.2; // Inicie com comentários Natspec (as três barras) // usados para documentação - e como dados descritivos para elementos/ação de UI /// @title BancoSimples /// @author nemild /* 'contrato' tem similadirades com 'classes' em outras linguagens (variáveis de class, herança, etc.) */ contract BancoSimples { // CamelCase // Declare variáveis de estado fora da função, para persistí-la durante a // duração do contrato // dicionário que mapeia endereços para saldos // tenha cuidado sobre ataques de overflow com números mapping (address => uint) private saldos; // "private" significa que outros contratos não podem acessar saldos // diretamente, mas o dado ainda é visível para outras partes da blockchain address public dono; // ´public´ é legível (mas sem acesso de escrita) por usuários e contratos // Eventos - ações públicas para ouvintes externo event LogRealizacaoDeDeposito(address numeroDaConta, uint quantidade); // Construtor, pode receber uma ou várias variáveis; apenas uma opção é // permitidas function BancoSimples() { // msg dá detalhes sobre a mensagem mandada pelo contrato // msg.sender é um chamador do contrato (endereço do criador do // contrato) dono = msg.sender; } /// @notice Deposita ether no banco /// @return O saldo do usuário após o depósito function deposito() public returns (uint) { saldos[msg.sender] += msg.value; // Sem necessidade de "this." ou "self." para variáveis de estado // todos as variáveis são inciadas com seu valor default LogRealizacaoDeDeposito(msg.sender, msg.value); // dispara evento return saldos[msg.sender]; } /// @notice Retira ether do banco /// @dev Isto não retorna nenhum ether excendente /// @param quantidadeDeRetirada quantidade que você quer retirar /// @return O saldo restante do usuário function retirada(uint quantidadeDeRetirada) public returns (uint saldoRestate) { if(saldos[msg.sender] >= quantidadeDeRetirada) { // Observe como deduzimos o saldo imediatamente, antes de enviar - // devido ao risco de uma chamada recursiva que permite o chamador // pedir um valor maior que seu saldo saldos[msg.sender] -= quantidadeDeRetirada; if (!msg.sender.send(quantidadeDeRetirada)) { // incremente de volta só se falhar, como pode estar enviando // para o contrato que substituiu 'enviar' no final // do recebimento saldos[msg.sender] += quantidadeDeRetirada; } } return saldos[msg.sender]; } /// @notice Retorna o saldo /// @return O saldo do usuário // 'constant' evita que a função edite variáveis de estado // permite a função executar localmente/fora da blockchain function saldo() constant returns (uint) { return saldos[msg.sender]; } // Função de fallback - Chamada se outras funções não forem chamadas ou // se ether sem dados forem enviados // Tipicamente chamada quando dados inválidos são enviados // Adicionada para que ether enviado neste contrato seja revertido se o // contrato falhar. Se não existisse, o dinheiro do enviante é transferido // para o contrato function () { throw; // 'throw' reverte o estao para antes da chamada } } // ** FIM DO EXEMPLO ** // Agora, o básico de Solidity //1 TIPO DE DADOS E MÉTODOS ASSOCIADOS // uint é usado para quantidade de moeda (não existem doubles ou floats) // e para datas (no sistema de tempo Unix) uint x; // int de 256 bits, não pode ser mudado após sua instanciação int constant a = 8; int256 constant a = 8; // mesmo efeito, mas aqui os 256 bits são explícitos uint constant VERSÃO_ID = 0x123A1; // uma constante hexadecimal // com 'constant', o compilador substitui cada ocorrência com o valor // Para int e uint, é possível determinar o espaço explicitamente, em intervalos // de 8 a 256, e.g., int8, int16, int24 uint8 b; int64 c; uint248 e; // Cuidado contra overflows, e proteja-se contra esse tipo de ataque // Não há funções randômicas padrão, use outros contratos para este objetivo // Casting de tipo int x = int(b); bool b = true; // ou então 'var b = true;' para inferição de tipo // Endereços - comportam 20 bytes/160 bits endereços Ethereum // Não permite operações aritiméticas address public dono; // Tipos de contas: // Conta de contrato: endereço criado ao criar (função do endereço do criador, // número da transação) // Conta externa: (pessoa/entidade externa): endereç criado a partir de chave // pública // Adicione o campo 'public' para indicar visibilidade pública/externa // um getter é automaticamente criado, mas NÃO um setter // Todos os endereços podem enviar ether dono.send(ALGUM_SALDO); // returna falso caso falhe if (dono.send) {} // LEMBRE-SE: encapsule num 'if', dado que endereços de // contrato tem funções executadas no envio e estas podem falhar //Também certifique-se que os saldos deduzidos ANTES de tentar enviar, dado que // há um risco de chamada recursiva que pode drenar um contrato // pode sobrescrever seu próprio // Pode verificar o saldo dona.balance; // o saldo do dono (usuário ou contrato) // Bytes permitidos de 1 a 32 byte a; // byte é o mesmo que bytes1 bytes2 b; bytes32 c; // Bytes dinamicamente criados bytes m; // Um array especial, mesmo que byte[] (mas mais comprimido) // Mais custoso que byte1-byte32, então, prefira estes quando possível // mesmo que bytes, mas não permite verificar tamanho ou acesso por indíce (por // enquanto) string n = "oi"; // guardado em UTF8, note as aspas duplas, não simples // funções de string serão adicionadas no futuro // prefira bytes32/bytes, dado que UTF8 usa mais espaço // Inferência de tipo // var não infere tipos baseados na primeira atribuição, // não pode ser usado em paramêtros de funções var a = true; // use com cuidado, inferência pode resultar em tipos errados // e.g., um int8, quando um contador precisa de int16 // var pode ser usado para assinalar uma função a uma variável function a(uint x) returns (uint) { return x * 2; } var f = a; f(22); // chamada // por padrão, todos os valores são inicializados com 0 // Delete pode ser chamada na maioria dos tipos // (NÃO destroi o valor, mas retorna para o valor 0, o incial) uint x = 5; // Desestruturação/Tuplas (x, y) = (2, 7); // assinada/troca múltiplos valores // 2. ESTRUTURAS DE DADOS // Arrays bytes32[5] apelidos; // array estático bytes32[] nomes; // array dinâmico uint novoTamanho = nomes.push("João"); // adicionando retorna o novo tamanho do // Tamanho nomes.length; // pega o tamanho nomes.length = 1; // tamanhos pode ser alterados (para arrays dinâmicos) // arrays multidimensionais uint[][5] x; // array com 5 arrays dinâmicos como elementos (ordem da maioria // das linguagens) // Dicionários (qualquer tipo para qualquer tipo) mapping (string => uint) public saldos; saldos["charles"] = 1; console.log(saldos["ada"]); // é 0, toda chave não assinalada retorna zero // 'public' permite o seguinte contrato nomeDoContrato.saldos("charles"); // retorna 1 // 'public' cria um getter (mas não um setter) como o seguinte function saldos(string _conta) returns (uint saldo) { return saldos[_conta]; } // Mapeamentos aninhados mapping (endereco => mapping (endereco => uint)) public guardioes; // Para deletar delete saldos["John"]; delete saldos; // assinala zero para todas as chaves // Diferentemente de outras linguages, NÃO É POSSÍVEL iterar sobre todos os // elementos de um mapeamento, sem saber previamente as chaves - é possível // construir estruturas de dados personalizadas para fazer isso // Structs e enums struct Banco { address dono; uint saldo; } Banco b = Banco({ dono: msg.sender, saldo: 5 }); // ou Banco c = Banco(msg.sender, 5); c.quantidade = 5; // cria novo valor delete b; // assinala todos os valores do enum para zero, exceto mapeamentos // Enums enum Estado { Criado, Travado, Inativo }; // geralmente usado para máquina de // estados Estado public estado; // Declara variável para enum estado = Estado.Criado; // enums podem ser explicitamente convertidas em ints uint estadoCriado = uint(Estado.Criado); // 0 // Localização de dados: Memória vs. disco vs. pilha - todos os tipos complexos // (arrays, structs) tem uma localização de dados // 'memória' não é persistida, 'disco' é // Padrão é 'disco' para variáveis locais e de estado; 'memória' para paramêtros // de função. Pilha guarda pequena variáveis locais // a maioria dos tipos podem ter sua localização de dados explicitamente assinalos // 3. Operações simples // Comparações, operadores binários e aritimétricos são providos // exponenciação: ** // ou exclusivo: ^ // negação binária: ~ // 4. Variáveis Globais de nota // ** this ** this; // endereço do contrato // geralmente usado no final do contrato para enviar o saldo restante às partes this.balance; this.algumFuncao(); // chamada de função externa via call, não via jump interno // ** msg - Mensagem corrente recebida pelo contrato ** ** msg.sender; // endereço do enviador msg.value; // quantidade de ether provida para este contrato em wei msg.data; // bytes, todos os dados da chamada msg.gas; // gas restante // ** tx - Esta transação ** tx.origin; // endereço do enviador da transação tx.gasprice; // valor do gas da transação // ** block - Informação do bloco corrente ** now; // tempo corrente (aproxiamdo), substituto para block.timestamp //(usa tempo do Unix) block.number; // número do bloco corrente block.difficulty; // dificuldade do bloco corrente block.blockhash(1); // retorna bytes32, só funciona para os 256 blocos mais //recentes block.gasLimit(); // ** storage - Hash de disco persistente ** storage['abc'] = 'def'; // mapea palavras de 256 bits em palavras de 256 bits // 4. FUNÇÕES E MAIS // A. Funções // Funções simples function incremento(uint x) returns (uint) { x += 1; return x; } // Funções podem retornar muito argumentos, e podem especificar argumentos // retornados sem necessidade de explicitamente usar return function incremento(uint x, uint y) returns (uint x, uint y) { x += 1; y += 1; } // Chamando a função anterior uint (a,b) = incremento(1,1); // 'constant' indica que uam função não altera variáveis persistidas // Funções constantes são executadas localmente, não na blockchain uint y; function incremento(uint x) constant returns (uint x) { x += 1; y += 1; // Esta linha deve falhar // y é uma variável de estado e não pode ser alterada por uma função local } // 'Especificadores de visibilidade de funções' // Estes podem substituitir 'constante', incluíndo: // public - visbilidade externa e interna (padrão) // private - apenas visível no contrato corrente // internal - apenas visível no contrato corrente e seus derivados // Functions hosteada - e pode ser assinalada para variável function a() { var z = b; b(); } function b() { } // Prefira loops sobre recursões (pilha de chamada é no máximo 1024) // B. Eventos // Eventos podem notificar partes externas; facilmente buscáveis e acessáveis // de fora da blockchain (com clientes leves) // tipicamente declarados após os parâmetros do contrato // Tipicamente, com letra maiúscula - e adicione Log na frente para // ser explicito e previnir confusão na chamada da função // Declaração event LogEnvio(address indexed de, address indexed para, uint quantidade); // Observe a letra maíscula no início do nome // Chamada Envio(de, para, quantidade); // Para partes externas (um contrato ou entidade externo), observe: Coin.Envio().watch({}, '', function(erro, resultado) { if (!erro) { console.log("Moeda transferida: " + resultado.args.quantidade + " moedas enviadas de " + resultado.args.de + " para " + resultado.args.para + "."); console.log("Saldo atual:\n" + "Enviador: " + Coin.balances.call(resultado.args.de) + "Recebedor: " + Coin.balances.call(resultado.args.para)); } } // Paradigma comum para um contrato depender de outro (e.g., um contrato que // depende da taxa de troca provida por outro) // C. ModifiCadores // MOdificadores validam entradas de funções, como saldo mínimo e autorização // do usuário; semelhantes a guardas em outras linguagens // '_' (subtraço) geralmente incluído como última linha do corpo, indica que a // função sendo chamada deve ser colocada ali modifier apenasDepois(uint _tempo) { if (agora <= _tempo) throw; _ } modifier apenasDono { if (msg.sender == dono) _ } // geralmente usado para máquina de estado modifier apenasSeEmEstado (Estado estadoCorrente) { if (estadoCorrente != Estado.A) _ } // Concatenado logo após a chamada da função function mudeDona(novoDono) apenasDepois(algumTempo) apenasDono() apenasSeEmEstado(Estado.A) { dono = novoDono; } // subtração pode ser incluído antes do final do corpo, mas retorno explícitos // pode ser ignorado, então, tome cuidado modifier chequeValor(uint quantidade) { _ if (msg.value > quantidade) { uint quantidadeASerDevolvida = quantidade - msg.value; if (!msg.sender.send(quantidadeASerDevolvida)) { throw; } } } // 6. BRANCHES E LOOPS // Todas as lógicas básicas de bloco funcionam - incluindo if/else, // while, break, continue, return - mas não há switch // A sintaxe é semelhante a Javascript, mas sem conversão de tipos // de não-booleanos para booleanos (operadores de comparação precisam // utilizar valores booleanos) // Loops que dependem o comportamento do usuário exigem cuidado - dado // que contratos tem uma quantidade máxima de gas para cada bloco de // de código - falhas acontecerão caso ele seja excedido // Por exemplo: for(uint x = 0; x < listaDeEnderecoDeRefundo.length; x++) { if (!listaDeEnderecoDeRefundo[x].send(ALGUMA_QUANTIDADE)) { throw; } } // Dois erros acima: // 1. Uma falha no enviar para o loop completamente, travando dinheiro // 2. Este loop pode ser abitrariamente longo (basado na quando que // o usuário precisa de refundo), portanto, pode falhar quando exceder // a quantidade máxima de gas do bloco // Ao invés disso, você deve deixar as pessoas retirarem // individualmente de suas subcontas e marcarem a retirada // 7. OBJETOS/CONTRATOS // A. Chamando um contrato externo contract FonteDeInformacoes { function info() returns (uint ret) { return 42; } } contract Consumidor { FonteDeInformacoes fonte; // aponta para um contrato na blockchain // Assinala variável para uma instância do contrato function setFonte(address endereco) { // Cast automático, cuidado; construtor não é chamado fonte = FonteDeInformacoes(endereco); } // Assinala variável para uma nova instância do contrato function createNewFeed() { fonte = new FonteDeInformacoes(); // nova instância criada // construtor é chamado } function chameFonte() { // último parenteses chama o contrato, podendo adicionar // opcionalmente valores ou gas fonte.info.value(10).gas(800)(); } } // B. Herança // Ordem importa, último contrato herdado (i.e., 'def') pode // sobrescrever partes de contratos previamente herdados contract MeuContratdo is abc, def("um argumento personalizado def") { // sobrescrevendo função function z() { if (msg.sender == dono) { def.z(); // chama função sobrescrita de def super.z(); // chama função do pai imeadiato } } } // função abstrata function umaFuncaoAbstrata(uint x); // não pode ser compilada, usada em contratos base/abstratos que // então, a implementam // C. Import import "filename"; import "github.com/ethereum/dapp-bin/library/iterable_mapping.sol"; // 'Import' está sobre desenvolvimento // Atualmente não pode ser usada na linha de comando // 8.OUTRAS PALAVRAS-CHAVE // A. Throwing // Throwing throw; // reverte estado e dinheiro NÃO-USADO é devolvido ao usuários // Atualmente não pode ser capturado // Um padrão de design comum é: if (!endereco.send(123)) { throw; } // B. Selfdestruct // auto-destroe o contrato corrente, enviando fundos para o endereço // (geralmente o criador) selfdestruct(ALGUM_ENDERECO); // remove disco/código dos blocos corrente e futuros // ajuda clientes leves, mas dados persistidos continuam no blockchain // Padrão comum, permite ao dono finalizar o contrato e receber fundos // restantes function remover() { if(msg.sender == criador) { // Apenas o criador do contrato pode // fazer isso selfdestruct(criador); // Inativa o contrato e retorna os fundos } } // Talvez queria desativar o contrato manualmente, ao invés de usar // selfdestruct (ether enviado para contratos selfdestructed é perdido) // 9. NOTAS SOBRE DESIGN DE CONTRATOS // A. Obfuscação // Todas as variáveis são publicamente visíveis na blockchain, então // qualquer coisa privada precisa ser obfuscada (e.g., hash com segredo) // Passo-a-pass: 1. Comprometa-se com algo, 2. Revele compromisso sha3("quantidade_de_lance", "algum segredo"); // compromisso // chame a função reveal (revelar) do contrato no futuros // mostrando o lance mais o segredo para foi hasheado com SHA3 reveal(100, "meuSegredo"); // B. Otimização de disco // Escrever na blockchain pode ser caro, dado que os dados são guardados // para sempre. É encorajado que contratos inteligentes usem memória ( // enventualmente, compilação será melhor, mas por enquanto é benéfico // usar estruturas de dados simples - armazenando minimamente na // blockchain) // Custo pode ser alto para item como arrays multidimensionais // (custo para guardar os dados - não declarar variáveis) // C. Acesso de dados da blockchain // Não pode restringir humanos ou computadores de ler os conteúdos // de transações ou estado de transações // Enquanto 'private' previne outros *contratos* de ler dados ] // diretamente - qualquer outra parte ainda pode ler dados da blockchain // Todos os dados são armazedos na blockchain, para que qualquer um // possa observar dados antigos e mudanças // D. Jobs Cron // Contratos deve ser manualmente chamados para lidar com agendamentos // baseados em tempo; podendo criar código externo para pingar // regularmente ou prover incentivos (ether) para outros fazê-lo // E. Padrão Observador // O Padrão Observador permite que você registre um inscritor e // registre uma função a ser chamada pelo Oráculo (nota, Oráculos pagam // pela ação executada). Similarmente à subscrição em Pub/sub // Este é um contrato abstrato, tanto as classes cliente como a // servidor importam o cliente que deve ser implementado contract AlgumOraculoCallback { function OraculoCallback(int _valor, uint _tempo, bytes32 info) external; } contract AlgumOráculo { AlgumOraculoCallback[] callbacks; // array com todos os inscritos // Registra inscrito function addInscrito(AlgumOraculoCallback a) { callbacks.push(a); } function notificar(valor, tempo, info) private { for(uint i = 0;i < callbacks.length; i++) { // todos os inscritos precisam implementar AlgumOraculoCallback callbacks[i].OraculoCallback(valor, tempo, info); } } function facaAlgo() public { // Código para fazer algo // Notifica todos os inscrito notificar(_valor, _tempo, _info); } } // Agora, seu contrato cliente pode addInscrito importando // AlgumOraculoCallback e registrando algum Oráculo // F. Máquinas de estado // veja o exemplo abaixo para o enum Estado e o modificador noEstado // *** EXEMPLO: Um exemplo de crowdfunding example (similar ao // Kickstarter) *** // ** INCIO DO EXEMPLO ** // FundadorDoCrowdFunding.sol /// @title FundadorDoCrowdFunding /// @author nemild contract FundadorDoCrowdFunding { // Variáveis assinaladas na crição pelo criador address public criador; address public recipiente; // criador pode ser diferente do Recipiente uint public minALevantar; // requisito para pagar, pelo contrário // os doadores recebem o dinheiro de volta string urlDaCampanha; byte constant versao = 1; // Estruturas de dados enum Estado { LevantandoFundos, RefundoExpirado, Sucesso } struct Contribuicao { uint quantidade; address contribuidor; } // Variáveis de Estado State public estado = Estado.LevantandoFundos; // incializado na criação uint public totalLevantado; uint public levantadoPor; uint public completadoEm; Contribution[] contribuidores; event LogRecebimentoDeFundos(address endereco, uint quantidade, uint totalAtual); event LogFundosPagos(address enderecoDoRecebedor); modifier noEstado(Estado _estado) { if (estado != _estado) throw; _ } modifier eOCriador() { if (msg.sender != criador) throw; _ } // Aguarda 6 meses após o final do contrato para destruí-lo modifier noFimDoContrato() { if(!((estado == Estado.RefundoExpirado || estado == Estado.Sucesso) && completadoEm + 6 months < now)) { throw; } _ } function FundadorDoCrowdFunding( uint tempoEmHorasParaFundraising, string _urlDaCampanha, address _recipiente, uint _minALevantar) { criador = msg.sender; recipiente = _recipiente; urlDaCampanha = _urlDaCampanha; minALevantar = _minALevantar; levantadoPor = now + (tempoEmHorasParaFundraising * 1 hours); } function contribuir() public noEstado(Estado.LevantandoFundos) { contribuidores.push( Contribuicao({ quantidade: msg.value, contribuidor: msg.sender }) // use array, para podermos iterar ); totalLevantado += msg.value; LogRecebimentoDeFundos(msg.sender, msg.value, totalRaised); verifiqueSeLevantamentoFoiCompletadoOuExpirado(); return contribuicoes.length - 1; // retorna id } function verifiqueSeLevantamentoFoiCompletadoOuExpirado() { if (totalLevantado > minALevantar) { estado = Estado.Sucesso; pagar(); // pode incentivar enviador que iniciou a mudanção de estado } else if ( now > levantadoPor ) { estado = Estado.RefundoExpirado; // backers podem coletar // o fundo chamando receberRefundo(id) } completadoEm = now; } function pagar() public emEstado(Estado.Sucesso) { if(!recipiente.send(this.balance)) { throw; } LogFundosPagos(fundRecipient); } function receberRefundo(id) public emEstado(Estado.RefundoExpirado) { if (contribuicoes.length <= id || id < 0 || contribuicoes[id].amount == 0 ) { throw; } uint quantidadeDeRefundo = contribuicoes[id].amount; contribuicoes[id].amount = 0; if(!contribuicoes[id].contribuidor.send(quantidadeParaEnviar)) { contribuicoes[id].amount = quantidadeParaEnviar; return false; } return true; } function removerContrato() public eOCriador() noFimDoContrato() { selfdestruct(msg.sender); // criador recebe todo o dinheiro restante{ } function () { throw; } } // ** FIM DO EXEMPLO ** // 10. OUTRAS FUNÇÕES NATIVAS // Unidades monetárias // Moeda é definida usando wei, menor quantidade de ether uint quantidadeMin = 1 wei; uint a = 1 finney; // 1 ether == 1000 finney // Para outras unidades, veja: http://ether.fund/tool/converter // Unidades temporais 1 == 1 second // segundos 1 minutes == 60 seconds // Minutos // Pode multiplicar uma variável de tempo, dado que unidades não são guardadas // na variável uint x = 5; (x * 1 days); // 5 dias // Cuidado com o salto de segundos / anos com declarações de igualdade para o tempo // (em vez disso, prefira maior que / menor que) // Criptografia // Todas as string passadas são concatenadas antes de realizar hashing sha3("ab", "cd"); ripemd160("abc"); sha256("def"); // 11. Segurança // Bugs são desastrosos para contratos Ethereum - até padrões Solidity populares // podem ser considerados anti-padrões // Veja links para segurança no final deste documento // 12. FUNÇÕES DE BAIXO NÍVELS // call - baixo nível, geralmente não usada, não tem segurança de tipos booleanSucesso = algumEnderecoDeContrato.call('nome_da_funcao', 'arg1', 'arg2'); // callcode - Código no endereço alvo executado no *contexto* do contrato // de chamada. Fornece funcionalidade de biblioteca algumEnderecoDeContrato.callcode('nome_da_funcao'); // 13. NOTAS DE ESTILO // Baseado no guia de estilo PEP8 do Python // Resumo rápido: // 4 espaços para identação // Duas linhas separam declaração de contratos (e outras declarações de alto nível) // Evite espaços estranhos entre parênteses // Pode omitir chaves curly para uma declaração de linha(if, for, etc) // else deve ser colocado na própria linha // 14. COMENTÁRIOS NATSPEC // usado para documentação, comentários e UIs externos // Contrato natspec - sempre acima da definição do contrato /// @title Título do Contrato /// @author Nome do autor // Função natspec /// @notice informações sobre o que funciona; mostrado quando a função é executada /// @dev Documentação de função para desenvolvedor // Parâmetro de função / valor de retorno natspec /// @param algumParametro Alguma descrição do que o parametro faz /// @return Descrição do valor de retorno