Introdução ao Solidity — Parte 2
No post anterior realizamos a implementação de um token Ethereum. Iremos nesta segunda parte introduzir outros recursos da linguagem Solidity. Modificaremos o código do nosso token ao longo deste post.
Disclaimer: Os exemplos abaixo são para finalidades de estudo. Requisitos de segurança estão sendo negligenciados. Não utilize este código em ambiente de produção (mainnet)!
Eventos
Eventos permitem que realizemos logs em nossos contratos. Os eventos são armazenados na transação responsável pela chamada ao método e por isso são permanentes e irreversíveis. Podemos também monitorar os eventos para que sirvam de gatilhos para aplicação que esteja interagindo com o smart contract (DApp).
De volta ao nosso token, iremos implementar evento que será disparado sempre que uma transferência for efetuada:
pragma solidity ^0.4.23;contract MyToken { mapping(address => uint256) balances;
uint256 totalSupply_; string public constant name = "MyToken";
string public constant symbol = "MYT";
uint8 public constant decimals = 18; event Transfer(address indexed from, address indexed to, uint256 value); constructor() public {
totalSupply_ = 100 * (10 ** uint256(decimals));
balances[msg.sender] = totalSupply_;
} function totalSupply() public view returns (uint256) {
return totalSupply_;
} function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
} function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender] - _value;
balances[_to] = balances[_to] + _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
}
A palavra-chave indexed indica que a realizarmos consultas aos eventos estes atributos (from e to) poderão ser utilizados com finalidade de filtro.
Após compilar e implantar a nova versão do contrato, vamos testar nosso evento:
$ truffle compile && truffle migrate --reset
Para este teste, devemos abrir dois consoles, no primeiro iremos monitorar o evento:
var mytoken = MyToken.deployed().then(function(c) { mytoken = c });
var event = mytoken.Transfer();
event.watch(function(error, response){ console.log(response); });
No segundo, iremos realizar uma transferência de tokens entre endereços:
var mytoken = MyToken.deployed().then(function(c) { mytoken = c });
mytoken.transfer(web3.eth.accounts[1], web3.toWei(10,’ether’));
Imediatamente o log deverá ser impresso na tela do primeiro console:
{ logIndex: 0,
transactionIndex: 0,
transactionHash: ‘0xc909df0771c38c516e72e4f863e02e5f4ba2126bd8149883b613a69164138d79’,
blockHash: ‘0xba137751825763b97e489b046aaed7871a932200e8d6db9e5764619d00739602’,
blockNumber: 33,
address: ‘0x18b5c13eb1a159b33515641d1580f5f7bbdbdf36’,
type: ‘mined’,
event: ‘Transfer’,
args:
{ from: ‘0x6e1db396e76d84301ea3674922b7402c42f76558’,
to: ‘0x9b8bb9e73c609229e86dcb4debdd5fc8dfad3d7f’,
value: BigNumber { s: 1, e: 19, c: [Array] } } }
Na função callback (parâmetro da função watch) a única ação realizada com o evento é a sua impressão no console. Um uso para a esta função seria realizar alguma interação na IU (ex.: carteira identificar recebimento de tokens).
Herança
Assim como nas linguagens orientadas a objetos o Solidity também possui o recurso da herança para melhor modularização e reutilização do código do nosso projeto. Iremos criar o arquivo contracts/BasicToken.sol que conterá o código genérico do token, código esse que reutilizaríamos caso criássemos outro token:
pragma solidity ^0.4.23;contract BasicToken { mapping(address => uint256) balances;
uint256 totalSupply_;
event Transfer(address indexed from, address indexed to, uint256 value); function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
} function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender] — _value;
balances[_to] = balances[_to] + _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
}
Vamos deixar em nosso arquivo contracts/MyToken.sol apenas parâmetrizações e o construtor:
pragma solidity ^0.4.23;import "./BasicToken.sol";contract MyToken is BasicToken { string public constant name = "MyToken";
string public constant symbol = "MYT";
uint8 public constant decimals = 18; constructor() public {
totalSupply_ = 100 * (10 ** uint256(decimals));
balances[msg.sender] = totalSupply_;
}
}
Interface
Iremos modificar nosso projeto para utilizarmos interface em nosso contrato.
Vamos primeiramente criar o arquivo contracts/ERC20.sol com o código abaixo:
pragma solidity ^0.4.23;contract ERC20 {
function totalSupply() public view returns (uint256);
function balanceOf(address who) public view returns (uint256);
function transfer(address to, uint256 value) public returns (bool);
event Transfer(address indexed from, address indexed to, uint256 value);
}
Observação: Na verdade nós criamos um contrato que possui os prótótipos das funções e os eventos, iremos utilizá-lo como se fosse uma interface. O Solidity permite a criação de interfaces porém na linguagem as interfaces possuem algumas limitações, dentre elas a impossibilidade de herdar de outras interfaces e/ou contratos. Como é comum e útil termos herança entre interfaces é prática comum implementar interface em Solidity desta forma.
Agora iremos modificar o arquivo BasicToken.sol para fazer uso da interface ERC20.sol:
pragma solidity ^0.4.23;import "./ERC20.sol";contract BasicToken is ERC20 { mapping(address => uint256) balances;
uint256 totalSupply_;
event Transfer(address indexed from, address indexed to, uint256 value); function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
} function transfer(address _to, uint256 _value) public returns (bool) {
require(_to != address(0));
require(_value <= balances[msg.sender]);
balances[msg.sender] = balances[msg.sender] — _value;
balances[_to] = balances[_to] + _value;
emit Transfer(msg.sender, _to, _value);
return true;
}
}
Após estas modificações no projeto contamos com a interface ERC20 que define os métodos e eventos do nosso token, o contrato BasicToken que possui os atributos e o corpo dos métodos que os acessam. E por fim, nosso contrato MyToken possui o código específico. Nosso projeto está agora mais organizado e caso venhamos criar outro token serão necessárias poucas linhas de código para implementá-lo.
ERC20
É oportuno comentar sobre o ERC20 (Ethereum Request for Comments #20). No Ethereum ERCs são prospostas de padrões feitas pela comunidade de desenvolvedores. Em nov/2015 foi feita a proposta para criação de um padrão de interface a ser utilizado pelos tokens Ethereum. Este padrão foi largamente aceito pela comunidade e hoje está consolidado como padrão. Permitindo que inúmeras soluções se beneficiem deste padrão de API, tanto wallets quanto exchanges.
Cerca de um ano depois foi criado o ERC179 que propôs a simplificação da interface ERC20 já que esta possui funções úteis mas nem sempre utilizadas (allowance, transferFrom e approve).
Conclusão
Ao final destes dois posts sobre o Solidity é esperado que o básico de programação de smart contract tenha sido absorvido pelo leitor. Há muitos outros recursos que a linguagem dispõe mas o objetivo principal é que este conteúdo seja suficientemente utilizado como ponto de partida para estudos mais aprofundados por parte do desenvolvedor interessado.