Introdução ao Solidity — Parte 1

Marcelo Morgado
5 min readJun 5, 2018

--

Neste post irei expor através de exemplos alguns conceitos básicos da linguagem Solidity, utilizada para desenvolvimento de smart contracts Ethereum (O conhecimento dos fundamentos sobre criptomoedas e plataforma Ethereum é um pré-requisito).

Nos posts anteriores (Utilizando ganache (testrpc) para testes e desenvolvimento de smart contracts e Utilizando truffle para desenvolvimento de smart contracts) apresentei as ferramentas que recomendo para o setup do ambiente de desenvolvimento.
Para a codificação, aconselho o editor Atom (com a posterior instalação do package language-solidity). O Atom é um IDE simples e muito utilizado para programação Solidity.

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)!

Para este estudo iremos criar nosso próprio token. De forma bem resumida, um token é basicamente 1) Mapeamento dos addresses(holders) e seus saldos e 2) Operações sobre a estrutura de dados (ex.: consultar saldo, realizar transferência, emitir novos tokens e etc). Abaixo primeiro exemplo:

pragma solidity ^0.4.23;contract MyToken {  mapping(address => uint256) balances;
uint256 totalSupply_ = 100 * 10 ** 18;
constructor() public {
balances[msg.sender] = totalSupply_;
}
}

A primeira linha informa ao compilador para qual a versão da liguagem nosso código está sendo desenvolvido. Neste exemplo, os compiladores da versão 0.4.23 ou superiores da mesma release (0.4.x) são compatíveis.

Criamos no nosso contrato MyToken os atributos balances e totalSupply_.
São utilizados dois tipos de dados:

  1. address: armazena 20 bytes (tamando de um endereço Ethereum);
  2. uint256: armazena inteiro não negativo com tamanho de 256 bits.

O mapping é uma estrutura de dados que relaciona uma chave à um valor. Em nosso exemplo utilizamos o balances para vincular o saldo de cada endereço detentor de tokens.
A variável totalSupply_ será utilizada para determinar a quantidade de tokens em circulação. Em nosso exemplo, serão 100 tokens em circulação. (Obs: A unidade de medida utilizada é o wei e o operador de exponencial em Solidity é **).

Implementamos também o construtor do contrato atribuindo ao account usado para o deploy do contrato como sendo o detentor incial de todos os tokens. Agora vamos implementar os métodos responsáveis por realizar a leitura dos atributos do contrato:

pragma solidity ^0.4.23;contract MyToken {  mapping(address => uint256) balances;
uint256 totalSupply_ = 100 * 10 ** 18;
constructor() public {
balances[msg.sender] = totalSupply_;
}
function totalSupply() public view returns (uint256) {
return totalSupply_;
}
function balanceOf(address _owner) public view returns (uint256) {
return balances[_owner];
}
}

Ambas as funções são públicas e retornam valor do tipo uint256. O modificador view indica que a função não modifica o estado do contrato, apenas realiza acesso para leitura.

$ truffle compile && truffle migrate
$ truffle console
truffle(development)> MyToken.deployed().then(function(c) { mytoken = c });
undefined
truffle(development)> mytoken.totalSupply().then(function(supply) { return web3.fromWei(supply.toString(), 'ether'); });
'100'
truffle(development)> mytoken.balanceOf(web3.eth.accounts[0]).then(function(b) { return web3.fromWei(b.toString(), 'ether') } );
'100'

Após a compilação e deploy do contrato podemos verificar que o suprimento total são de 100 tokens e todos pertencentes ao nosso account principal (usado no deploy do contrato).

Vamos agora implementar a transferência de tokens entre accounts:

pragma solidity ^0.4.23;contract MyToken {  mapping(address => uint256) balances;
uint256 totalSupply_ = 100 * 10 ** 18;
constructor() public {
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;
}

O método transfer() permite aos detentores enviarem seus tokens para outros endereços.
A função require() é utilizada como guarda para validar as condições que precisam ser satisfeitas para a operação ser executada com sucesso. Se a condição não for satisfeita a execução é interrompida.
A transferência deve ser destinada à endereço válido (não nulo) e o emissor deve possui saldo suficiente para realizar a transferência. Se essas condições forem satisfeitas os fundos são movidos entre as contas.

Como este método altera o estado do contrato, ele é executado através de uma transação Ethereum. O Solidity possui o objeto especial msg que representa a transação utilizada para interagir com o método. Através do atributo msg.sender podemos obter o endereço do account que realizou a transação.

$ truffle compile && truffle migrate --reset
$ truffle console
truffle(development)> MyToken.deployed().then(function(c) { mytoken = c });
undefined
truffle(development)> var me = web3.eth.accounts[0];
undefined
truffle(development)> var alice = web3.eth.accounts[1];
undefined
truffle(development)> var bob = web3.eth.accounts[2];
undefined
truffle(development)> mytoken.balanceOf(me).then(function(b) { return web3.fromWei(b.toString(), 'ether') } );
'100'
truffle(development)> mytoken.balanceOf(alice).then(function(b) { return web3.fromWei(b.toString(), 'ether') } );
'0'
truffle(development)> mytoken.transfer(alice, web3.toWei(40, 'ether'));
{ tx: '0xbd95f6265768af9709ad44ae2f0e294089ff05af7b4576d7a76c92355b13dbc2',
receipt:
{ transactionHash: '0xbd95f6265768af9709ad44ae2f0e294089ff05af7b4576d7a76c92355b13dbc2',
transactionIndex: 0,
blockHash: '0x23da4a98312f59fd6a977274f9e933b102802a1bc0ab7c51cc030ea10cc8b5ea',
blockNumber: 23,
gasUsed: 49765,
cumulativeGasUsed: 49765,
contractAddress: null,
logs: [],
status: '0x01',
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' },
logs: [] }

truffle(development)> mytoken.balanceOf(me).then(function(b) { return web3.fromWei(b.toString(), 'ether') } );
'60'
truffle(development)> mytoken.balanceOf(alice).then(function(b) { return web3.fromWei(b.toString(), 'ether') } );
'40'
truffle(development)> mytoken.transfer(bob, web3.toWei(20, 'ether'), { from: alice });
{ tx: '0xa1725984ef646e33d141cb4ae2411d1c620c161509f91d91c588b5da5aea4009',
receipt:
{ transactionHash: '0xa1725984ef646e33d141cb4ae2411d1c620c161509f91d91c588b5da5aea4009',
transactionIndex: 0,
blockHash: '0x23c2336cdf01f38c9dd3482e4f29fc331d2c0872637f6096e25064e586c8ee8a',
blockNumber: 24,
gasUsed: 49765,
cumulativeGasUsed: 49765,
contractAddress: null,
logs: [],
status: '0x01',
logsBloom: '0x00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000' },
logs: [] }
truffle(development)> mytoken.balanceOf(alice).then(function(b) { return web3.fromWei(b.toString(), 'ether') } );
'20'
truffle(development)> mytoken.balanceOf(bob).then(function(b) { return web3.fromWei(b.toString(), 'ether') } );
'20'
truffle(development)> mytoken.totalSupply().then(function(supply) { return web3.fromWei(supply.toString(), 'ether'); });
'100'

Nos comandos acima nós realizamos as seguintes operações:
1) Criamos varíaveis com 3 de nossos addresses para facilitar nosso teste;
2) Transferência de 40 tokens de me para alice;
3) Transferência de 20 tokens de alice para bob;
O saldo dos 3 endereços foram modificados corretamente e o total de tokens em circulação não se alterou (já que nenhum token foi criado, apenas transferido).

Por último, vamos dar nome e símbolo ao nosso token:

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;
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;
}
}

Também é feita alteração para setar o suprimento total através do construtor.

Conclusão

Neste post fizemos a implementação e testes de versão básica de um token Ethereum.

--

--