Queremos criar contratos inteligentes para resolver um problema real no mundo real. Imagine um grupo de pessoas que deseja contribuir com dinheiro para um projeto específico. E quando arrecadamos o dinheiro, o proprietário deseja gastar esse dinheiro com um fornecedor específico. Além disso, é necessário votar e selecionar um fornecedor pelos contribuintes. Queremos garantir que tudo seja executado sem trapaça. A melhor solução é usar contratos inteligentes para manter tudo imutável.
Isso também pode ser usado em muitos tópicos diferentes, como arrecadação de impostos, gerenciamento de construção...
As tentativas de solicitação de gastos retiram dinheiro do contrato e o enviam para um endereço externo.
Documento Struct em Solidity
Remoção de Recursos Não Utilizados ou Inseguros
Mapeamentos fora do Armazenamento
Se uma struct ou array contiver um mapeamento, ele só pode ser usado no armazenamento. Anteriormente, os membros do mapeamento eram silenciosamente ignorados na memória, o que é confuso e propenso a erros.
As atribuições a structs ou arrays no armazenamento não funcionam se contiverem mapeamentos. Anteriormente, os mapeamentos eram silenciosamente ignorados durante a operação de cópia, o que é enganoso e propenso a erros.
Armazenamento vs. Memória em Solidity
As palavras-chave Storage e Memory em Solidity são análogas ao disco rígido e à RAM do computador. Assim como a RAM, a Memory em Solidity é um local temporário para armazenar dados, enquanto o Storage mantém dados entre chamadas de função. O Contrato Inteligente em Solidity pode usar qualquer quantidade de memória durante a execução, mas uma vez que a execução para, a Memória é completamente apagada para a próxima execução. Enquanto o Storage, por outro lado, é persistente, cada execução do contrato inteligente tem acesso aos dados previamente armazenados na área de armazenamento.
Cada transação na Máquina Virtual Ethereum nos custa uma certa quantidade de Gás. Quanto menor o consumo de Gás, melhor é o seu código Solidity. O consumo de Gás da Memória não é muito significativo em comparação com o consumo de Gás do Armazenamento. Portanto, é sempre melhor usar a Memória para cálculos intermediários e armazenar o resultado final no Armazenamento.
As variáveis de estado e as variáveis locais de structs e arrays são sempre armazenadas no armazenamento por padrão.
Os argumentos de função estão na memória.
Sempre que uma nova instância de um array é criada usando a palavra-chave 'memory', uma nova cópia dessa variável é criada. Alterar o valor do array da nova instância não afeta o array original.
A chave do mapeamento não é armazenada no contrato inteligente, apenas temos um valor no contrato inteligente.
Condição de Corrida em Contrato Inteligente
Todas as transações no Ethereum são executadas serialmente. Uma após a outra. Tudo o que sua transação faz, incluindo chamadas de um contrato para outro, acontece dentro do contexto da sua transação e nada mais é executado até que seu contrato esteja concluído.
Portanto, condições de corrida não são absolutamente uma preocupação. Você pode chamar balanceOf() em outro contrato, colocar o resultado em uma variável local e usá-lo sem se preocupa,que o saldo no outro contrato mudará antes de você terminar.
Existem algumas maneiras diferentes de evitar condições de corrida em contratos inteligentes. Uma abordagem comum é usar bloqueios, conforme descrito acima. Outra abordagem é usar um conceito chamado carimbo de data e hora. O carimbo de data e hora envolve atribuir um timestamping (carimbo de data e hora) a cada transação. As transações são então processadas na ordem de seus carimbos de data e hora, o que ajuda a evitar conflitos.
Em geral, é importante estar ciente do potencial de condições de corrida ao escrever contratos inteligentes. Existem várias técnicas que podem ser usadas para evitar condições de corrida, mas é importante escolher a técnica apropriada para a aplicação específica.
_Preste atenção ao usar iteração em contratos inteligentes, isso requer mais gás para ser executado.
Quando lançamos uma nova implantação de contratos inteligentes, eles têm um código de hash diferente e não estão relacionados entre si._
Todo projeto web3 segue estes passos:
Escrever em Solidity e verificar com https://remix.ethereum.org
Compilar o projeto Solidity em JSON e ABI
Implantar o arquivo compilado em uma rede
Escrever testes para verificar a funcionalidade
Vamos fazer isso
Temos um gerente e uma lista de contribuintes, e um valor mínimo de contribuição.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity >=0.5.0 <0.9.0;
// Avisos do linter (sublinhado vermelho) sobre a versão da pragma podem ser ignorados!
// O código do contrato irá aqui
contract Campaing {
address public manager;
uint256 public minimumContribution;
mapping (address => bool ) public approvers;
constructor(uint256 min ) {
manager = msg.sender;
minimumContribution = min;
}
}
Adicionar contribuição com valor mínimo na lista de contribuições
function contibute() public payable {
require(
msg.value >= minimumContribution,
"minimum contribute is required."
);
approvers[msg.sender] = true;
approversCount ++;
}
Se o valor for inferior ao mínimo, obtemos um erro e reduzimos o gás.
Podemos verificar se o endereço contribuiu ou não:
function isPaied() public view returns (bool) {
return (approvers[msg.sender]);
}
Obter saldo da conta do contrato inteligente
function getBalance() public view returns(uint256){
return (address(this).balance);
}
Agora, foi adicionada uma lista de solicitações para cada provedor. Os provedores são os destinatários que o gerente deseja gastar dinheiro dos contribuidores.
struct Request {
string description;
uint256 value;
address payable recipient;
bool complete;
uint256 approvalCount;
mapping(address => bool) approvals;
}
O gerente só pode chamar essas funções, então precisamos criar um modificador para elas.
modifier onlyManager() {
require(
msg.sender == manager,
"Only the campaign manager can call this function."
);
_;
}
Add request é uma função que o gerente pode fazer para criar um novo provedor para gastar os contribuidores.
function addRequest(
string memory description,
uint256 value,
address payable recipient
) public onlyManager {
Request storage req = requests[numRequests++];
req.description = description;
req.value = value;
req.recipient = recipient;
req.complete = false;
req.approvalCount = 0;
}
function approveRequest(uint256 index) public{
Request storage req = requests[index];
require(
approvers[msg.sender] ,
"Only contributors can approve a specific payment request"
);
require(
!req.approvals[msg.sender],
"You have already voted to approve this request"
);
require(index < numRequests,
"You can vote only in valid index. more than valid value");
require(index >= 0,
"You can vote only in valid index. minus value");
req.approvals[msg.sender] = true;
req.approvalCount++;
}
"water",1000,0xdD870fA1b7C4700F2BD7f44238821C26f7392148
O teste desta parte requer três contas diferentes: gerente, contribuidor, provedor
Gerente: implantar o contrato
Contribuidor: contribuir dentro do sistema
Gerente: criar uma solicitação para o provedor
Contribuidor: aprovar a solicitação
O gerente pode finalizar a solicitação e enviar dinheiro ao provedor
function finalizeRequest(uint256 index) public onlyManager {
require(index < numRequests,
"You can vote only in valid index. more than valid");
require(index >= 0,
"You can vote only in valid index. minus value");
Request storage req = requests[index];
require(!req.complete ,
"This request has been completed.");
req.recipient.transfer(req.value);
req.complete = true;
}
Também precisamos verificar o número de votos que foram enviados para a solicitação:
require(
req.approvalCount > (approversCount / 2),
"This request needs more approvals before it can be finalized"
);
O código final está disponível no meu repositório:
](https://github.com/mobintmu/kickstart/?source=post_page-----bd5dc7c8f84f--------------------------------)## Implantar código Solidity com Go Ethereum:
Conectar-se à rede
type Client struct {
Client *ethclient.Client
}
func (c *Client) NewClient(address string) {
client, err := ethclient.Dial(address)
if err != nil {
log.Fatal(err)
}
fmt.Println("connect to network ...")
c.Client = client
}
Usando Ganache
ganache-cli
Definição de Contas
Coloque três contas no arquivo de ambiente (env).
ADDRESS = http://localhost:8545
MANAGER_ADDRESS = 0xBad21545CDAc5eA56050a5441E435B2437fE4770
MANAGER_PRIVATE_ADDRESS = 0xbcab03c0fb40ccdd5d75b8024731dd772113b02d01132d81c584027d8a7280b9
PROVIDER_ADDRESS = 0xEc89A6a668206286a875BB906B79BbB756f09E22
PROVIDER_PRIVATE_ADDRESS = 0xbf6e3ba07b0ea7a98e5cbd0f0206bcdb35fd9520571c28cc16c803cae702dc89
CONTRIBUTOR_ADDRESS = 0x6BC477cE822a30331D0D5Ce4B5CeA23b2f2eEaEb
CONTRIBUTOR_PRIVATE_ADDRESS = 0x2b7b6e43aca333d79375eb8cb3d225b795480596d4fff6e0abf70c111628a410
Obtendo o saldo das contas
type Account struct {
PublicAddress common.Address
PrivateAddress string
Name string
client *ethclient.Client
}
// NewAccount cria uma nova instância de Account
func NewAccount(publicAddress string,
privateAddress string,
client *client.Client) *Account {
return &Account{
PublicAddress: common.HexToAddress(publicAddress),
client: client.Client,
}
}
func (a *Account) GetBalance() *big.Int {
balance, err := a.client.BalanceAt(context.Background(), a.PublicAddress, nil)
if err != nil {
log.Fatal(err)
}
return balance
}
Resultado:
go run *.go
connect to network ...
balance manager : 1000000000000000000000
balance provider : 1000000000000000000000
contributor manager : 1000000000000000000000
Instalação do Abigen:
$ cd $GOPATH/src/github.com/ethereum/go-ethereum
$ go build ./cmd/abigen
build.sol
cd contracts/
solc --abi Campaign.sol -o build
solc --bin Campaign.sol -o Campaign.bin
Então, o abigen pode ser executado novamente, desta vez passando o Campaign.bin:
abigen --abi ./build/Campaign.abi --pkg main --type Campaign --out Campaign.go --bin ./Campaign.bin/Campaign.bin
Se o código não for implantado na rede:
ou se ocorrer o seguinte erro:
vm exception while processing transaction: invalid opcode error
invalid opcode error in ganache 2.7.1
pip3 install solc-select
solc-select use <version> --always-install
solc --version
solc, the solidity compiler commandline interface
Version: 0.8.7+commit.e28d00a7.Linux.g++
Implantação na rede com a conta de gerenciamento:
Primeiro, precisamos obter a chave privada da conta.
privateKey, err := crypto.HexToECDSA(strings.TrimPrefix(privateAddress, "0x"))
if err != nil {
log.Fatal(err)
}
// HexToECDSA analisa uma chave privada secp256k1.
Função de implantação
type Campaign struct {
instance *contracts.Campaign
txAddress common.Address
tx *types.Transaction
Min *big.Int
client *ethclient.Client
}
func NewCampaign(min *big.Int, client *ethclient.Client) *Campaign {
return &Campaign{
Min: min,
client: client,
}
}
func (c *Campaign) Deploy(manager *account.Account) {
nonce := manager.GetNonce()
gasPrice, err := c.client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
chainID, err := c.client.ChainID(context.Background())
if err != nil {
log.Fatal(err)
}
auth, err := bind.NewKeyedTransactorWithChainID(manager.PrivateKey, chainID)
if err != nil {
fmt.Println(err)
}
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) // in wei
auth.GasLimit = uint64(300000) // in units
auth.GasPrice = gasPrice
txAddress, tx, instance, err := contracts.DeployCampaign(auth, c.client, c.Min)
if err != nil {
fmt.Println("DeployLottery")
log.Fatal(err.Error())
}
c.instance = instance
c.txAddress = txAddress
c.tx = tx
fmt.Println(txAddress.Hex())
fmt.Println(tx.Hash().Hex())
}
Execução
go run *.go
connect to network ...
balance manager : 999999400000000000000
balance provider : 1000000000000000000000
contributor manager : 1000000000000000000000
nonce manager : 1
deploying contract ... _____________________
0x8195b8a7757977AA6ae6Df873dA452a65Ab4791F
0x1e75f1dfa1a7454599acce94e4e1ccfeaa4d9633e0e11ec75cc896c0981e77bb
balance manager : 999998800000000000000
Terminal
Transaction: 0x1e75f1dfa1a7454599acce94e4e1ccfeaa4d9633e0e11ec75cc896c0981e77bb
Contract created: 0x8195b8a7757977aa6ae6df873da452a65ab4791f
Gas usage: 300000
Block number: 2
Block time: Wed Dec 27 2023 16:40:48 GMT
Runtime error: code size to deposit exceeds maximum code size
Agora, implantamos nosso contrato na rede Ganache.
Obter informações do bloco
func (a *Account) GetHeader() *big.Int {
header, err := a.client.HeaderByNumber(context.Background(), nil)
if err != nil {
log.Fatal(err)
}
fmt.Println(header.Number.String()) // 5671744
return header.Number
}
func (a *Account) GetDataFromBlock(blockNumber *big.Int) {
block, err := a.client.BlockByNumber(context.Background(), blockNumber)
if err != nil {
log.Fatal(err)
}
fmt.Println("block.Number().Uint64() ", block.Number().Uint64()) // 5671744
fmt.Println("block.Time()", block.Time()) // 1527211625
fmt.Println("block.Difficulty().Uint64()", block.Difficulty().Uint64()) // 3217000136609065
fmt.Println("block.Hash().Hex()", block.Hash().Hex()) // 0x9e8751ebb5069389b855bba72d94902cc385042661498a415979b7b6ee9ba4b9
fmt.Println("len(block.Transactions())", len(block.Transactions())) // 144
}
Uma vez implantado, use apenas a transação do contrato.
my := common.HexToAddress("0xdd3f3c8afb70786e3bfb7a8cd8d44a3c5049268e")
var err error
campaign.Instance, err = contracts.NewCampaign(my, client.Client)
if err != nil {
fmt.Println(err)
}
Contribua na rede.
func (c *Campaign) Contribute(contributor *account.Account) {
nonce := contributor.GetNonce()
gasPrice, err := c.client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
chainID, err := c.client.ChainID(context.Background())
if err != nil {
log.Fatal(err)
}
auth, err := bind.NewKeyedTransactorWithChainID(contributor.PrivateKey, chainID)
if err != nil {
log.Fatal(err)
}
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = c.Min // in wei
auth.GasLimit = uint64(3000000) // in units
auth.GasPrice = gasPrice
tx, err := c.instance.Contribute(auth)
if err != nil {
log.Fatal(err)
}
fmt.Println("transaction hash")
fmt.Println(tx.Hash())
}
go run *.go
connect to network ...
block.Number().Uint64() 21
block.Time() 1703691148
block.Difficulty().Uint64() 0
block.Hash().Hex() 0x06c93b2954bed6bd1f76340b4b504c985293abbff6b21006dc359daf0d65198c
len(block.Transactions()) 1
>>>>>>>>>>>>>>>>>>>>>>Balance>>>>>>>>>>>>>>>>
balance manager : 999992200000000000000
balance provider : 1000000000000000000000
balance contributor : 999999662975999994800
<<<<<<<<<<<<<<<<<<<<<<Balance<<<<<<<<<<<<<<<<<
deploying contract ...
0xaf9087a308a23E335Dd8bB73E5b77AD0bF625Cd0
0xd56b10c190f65cec3be4f437f22da9fce4f3149d770b65fb494ca5f79af70d48
>>>>>>>>>>>>>>>>>>>>>>Balance>>>>>>>>>>>>>>>>
balance manager : 999991600000000000000
balance provider : 1000000000000000000000
balance contributor : 999999662975999994800
<<<<<<<<<<<<<<<<<<<<<<Balance<<<<<<<<<<<<<<<<<
contribute ...
transaction hash ...
0xfc2028dbaa4235e4a19a0b2e437db48b8cdbb6e05f0bebdd93eda4209e0541c6
>>>>>>>>>>>>>>>>>>>>>>Balance>>>>>>>>>>>>>>>>
balance manager : 999991600000000000000
balance provider : 1000000000000000000000
balance contributor : 999999620847999993800
<<<<<<<<<<<<<<<<<<<<<<Balance<<<<<<<<<<<<<<<<<
Resultado no console:
implementação:
Transaction: 0xd56b10c190f65cec3be4f437f22da9fce4f3149d770b65fb494ca5f79af70d48
Contract created: 0xaf9087a308a23e335dd8bb73e5b77ad0bf625cd0
Gas usage: 300000
Block number: 22
contribuição:
Transaction: 0xfc2028dbaa4235e4a19a0b2e437db48b8cdbb6e05f0bebdd93eda4209e0541c6
Gas usage: 21064
Block number: 23
Chame a função IsPaid.
func (c *Campaign) IsPaid(contributor *account.Account) bool {
result, err := c.Instance.IsPaid(&bind.CallOpts{
From: contributor.PublicAddress,
Context: context.Background(),
})
if err != nil {
fmt.Println("IsPaid")
fmt.Println(err)
}
fmt.Println(result)
return result
}
Chame a função GetBalance.
func (c *Campaign) GetBalance() *big.Int {
result, err := c.Instance.GetBalance(nil)
if err != nil {
fmt.Println("IsPaid")
fmt.Println(err)
}
fmt.Println(result)
return result
}
Adicionando a requisição
func (c *Campaign) AddRequest(request entity.Request, manager *account.Account) {
nonce := manager.GetNonce()
gasPrice, err := c.client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
auth, err := bind.NewKeyedTransactorWithChainID(manager.PrivateKey, c.chainID)
if err != nil {
log.Fatal(err)
}
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) // em wei
auth.GasLimit = uint64(6721975) // em units 6721975
auth.GasPrice = gasPrice
tx, err := c.Instance.AddRequest(auth, request.Description, request.Value, request.ProviderAddress)
if err != nil {
fmt.Println("CreateRequest")
fmt.Println(err.Error())
log.Fatal(err)
}
fmt.Println("transaction hash ...")
fmt.Println(tx.Hash())
}
Função para obter o número de requisições
func (c *Campaign) GetNumberOfRequests() *big.Int {
result, err := c.Instance.NumRequests(nil)
if err != nil {
fmt.Println("GetNumberOfRequests")
fmt.Println(err)
}
fmt.Println(result)
return result
}
Função para aprovar requisição
func (c *Campaign) ApproveRequest(contributor *account.Account, index *big.Int) {
nonce := contributor.GetNonce()
gasPrice, err := c.client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
auth, err := bind.NewKeyedTransactorWithChainID(contributor.PrivateKey, c.chainID)
if err != nil {
log.Fatal(err)
}
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) // em wei
auth.GasLimit = uint64(6721975) // em units 6721975
auth.GasPrice = gasPrice
tx, err := c.Instance.ApproveRequest(auth, index)
if err != nil {
fmt.Println("ApproveRequest")
fmt.Println(err.Error())
log.Fatal(err)
}
fmt.Println("transaction hash ...")
fmt.Println(tx.Hash())
}
Função para Finalizar Requisição
func (c *Campaign) FinalizeRequest(manager *account.Account, index *big.Int) {
nonce := manager.GetNonce()
gasPrice, err := c.client.SuggestGasPrice(context.Background())
if err != nil {
log.Fatal(err)
}
auth, err := bind.NewKeyedTransactorWithChainID(manager.PrivateKey, c.chainID)
if err != nil {
log.Fatal(err)
}
auth.Nonce = big.NewInt(int64(nonce))
auth.Value = big.NewInt(0) // em wei
auth.GasLimit = uint64(6721975) // em units 6721975
auth.GasPrice = gasPrice
tx, err := c.Instance.FinalizeRequest(auth, index)
if err != nil {
fmt.Println("FinalizeRequest")
fmt.Println(err.Error())
log.Fatal(err)
}
fmt.Println("transaction hash ...")
fmt.Println(tx.Hash())
}
Código Final
Este artigo foi escrito por mobin shaterian e traduzido por Adriano P. de Araujo. O original em inglês pode ser encontrado aqui.
Latest comments (0)