Introdução
Neste tutorial, construiremos um contrato inteligente para cadeia de suprimentos (supply chain) que rastreia os produtos agrícolas desde o agricultor até o consumidor final. O contrato é construído usando Solidity e implantado na blockchain Celo. Ao empregar esta abordagem, os participantes na cadeia de suprimentos podem aumentar a transparência, reduzir custos e reforçar a segurança.
Para ilustrar melhor isso, consideremos um cenário hipotético: um agricultor produz um produto e adiciona-o ao inventário de itens disponíveis que podem ser adquiridos por um distribuidor. O distribuidor então processa o produto e o prepara para venda. Posteriormente, um varejista adquire o produto do distribuidor e, por fim, o oferece para venda ao consumidor final.
Pré-requisitos
Para seguir este tutorial, você precisará de:
- Node.js
- Algum conhecimento de Solidity
Requisitos
Escrevendo o contrato inteligente
Navegue até a pasta do Hardhat e crie um novo arquivo supplyChain.sol
SupplyChain.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0
contract SupplyChain {
enum ProductStatus { ForSale, Processed, Sold, Shipped}
Aqui, começaremos declarando nosso contrato supplyChain.
Dentro do contrato, há uma declaração enum chamada productStatus
que define quatro valores possíveis: ForSale (a venda), Processed _(processado), Shipped_ (enviado) e Sold (vendido). O enum é usado para representar o status de um produto na cadeia de suprimentos.
Struct
struct Product {
uint productId;
string productName;
uint quantity;
uint price;
address payable farmer;
address payable distributor;
address payable retailer;
ProductStatus status;
}
uint productId
: um número inteiro sem sinal que representa o identificador exclusivo do produto.
string productName
: uma string que representa o nome do produto.
uint quantity
: Um número inteiro sem sinal que indica a quantidade do produto.
uint price
: um número inteiro sem sinal que indica o preço do produto.
address payable farmer
: Este é o endereço de pagamento do agricultor associado ao produto.
address payable distributor:
Um endereço Ethereum pagável que representa o endereço do distribuidor associado ao produto.
address payable retailer:
um endereço Ethereum pagável que representa o endereço do varejista associado ao produto.
ProductStatus status:
: uma variável do tipo enum ProductStatus que indica o estado atual do produto.
Construtor
constructor() {
owner = msg.sender;
}
address public owner;
mapping(uint => Product) public products;
uint public productCount;
O construtor inicializa a variável owner
(proprietário) com o endereço do implantador do contrato e declara-o como público.
O mapeamento público products
armazena informações do produto com base em seus IDs exclusivos enquanto productCount
acompanha o número total de produtos na cadeia de suprimentos.
Event
event ProductAdded(uint productId, string productName, uint quantity, uint price, address farmer);
event ProductProcessed(uint productId, address distributor);
event ProductSold(uint productId, address retailer);
event ProductShipped(uint productId, address retailer);
event ProductAdded
é acionado quando um produto é adicionado. Emite o ID do produto, nome, quantidade, preço e endereço do agricultor associado.
event ProductProcessed
é acionado quando um produto é processado. Emite o ID do produto e o endereço do distribuidor responsável pelo processamento.
event ProductSold
é acionado quando um produto é vendido. Emite o ID do produto e o endereço do revendedor que adquiriu o produto.
event ProductShipped
é acionado quando um produto é enviado. Emite o ID do produto e o endereço do varejista que envia o produto ao consumidor.
Modifier
modifier onlyFarmer(uint _productId) {
require(msg.sender == products[_productId].farmer, "Apenas o fazendeiro pode realizar essa ação.");
_;
}
modifier onlyDistributor(uint _productId) {
require(msg.sender == products[_productId].distributor, "Apennas o distribuidor pode realizar essa ação.");
_;
}
modifier onlyRetailer(uint _productId) {
require(msg.sender == products[_productId].retailer, "Apenas o varejista pode realizar essa ação.");
_;
}
modifier productExists(uint _productId) {
require(_productId <= productCount, "O produto não existe.");
_;
}
O modifiers (modificadores) garantem que apenas as funções associadas a um produto específico possam executar uma ação. Verifica se o msg.sender corresponde ao endereço da respectiva função associada ao produto. Caso contrário, gera uma mensagem de erro.
add Product
function addProduct(string memory _productName, uint _quantity, uint _price) public {
productCount++;
products[productCount] = Product(productCount, _productName, _quantity, _price, payable(msg.sender), payable(address(0)), payable(address(0)), ProductStatus.ForSale);
emit ProductAdded(productCount, _productName, _quantity, _price, msg.sender);
}
A funçãoaddProduct
adiciona um produto ao mapeamento, incrementa a variável productCount e emite o evento correspondente ProductAdded
.
processProduct
function processProduct(uint _productId) public productExists(_productId)
onlyDistributor(_productId) {
require(products[_productId].status == ProductStatus.ForSale, "Produto não disponivel para distribuição.");
products[_productId].distributor = payable(msg.sender);
products[_productId].status = ProductStatus.Processed;
emit ProductProcessed(_productId, msg.sender);
}
A função processProduct
processa um produto para distribuição atualizando o endereço do distribuidor, alterando o status do produto e emitindo o evento correspondente. Requer que o produto exista e apenas o distribuidor associado pode executar esta função. Além disso, o produto deve estar no estado ForSale para que seja elegível para distribuição.
sellProduct
function sellProduct(uint _productId) public productExists(_productId) onlyDistributor(_productId) {
require(products[_productId].status == ProductStatus.Processed, "Product not available for sale.");
products[_productId].retailer = payable(msg.sender);
products[_productId].status = ProductStatus.Sold;
emit ProductSold(_productId, msg.sender);
}
A função sellProduct
permite que o produto seja vendido realizando tarefas como atualização de seu status, atribuindo o varejista e emitindo um evento para avisar sobre a venda.
buyProduct
function buyProduct(uint _productId) public payable productExists(_productId) {
require(products[_productId].status == ProductStatus.ForSale, "Produto não disponível para venda.");
require(msg.value >= produtos[_productId].price, "Pagamento insuficiente.");
produtos[_productId].retailer = a pagar(msg.sender);
produtos[_productId].status = ProductStatus.Sold;
emitir ProdutoVendido(_produtoId, msg.sender);
}
A função buyProduct
permite que um comprador compre um produto fornecendo o pagamento necessário. Verifica se o produto está disponível para venda e se o pagamento é suficiente. Se as condições forem atendidas, atualiza o revendedor e o status do produto.
shipProduct
function shipProduct(uint _productId) public productExists(_productId)
onlyRetailer(_productId) {
require(products[_productId].status == ProductStatus.Sold, "Produto ainda não vendido.");
products[_productId].status = ProductStatus.Shipped;
emit ProductShipped(_productId, msg.sender);
}
A função shipProduct
garante que apenas o varejista associado a um produto específico possa executar a função shipProduct. Ele verifica se o produto foi vendido e atualiza seu status para “Enviado” antes de emitir o evento ProductShipped.
Seu código final deve ficar assim:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
contract SupplyChain {
enum ProductStatus { ForSale, Processed, Sold, Shipped }
struct Product {
uint productId;
string productName;
uint quantity;
uint price;
address payable farmer;
address payable distributor;
address payable retailer;
ProductStatus status;
}
constructor() {
owner = msg.sender;
}
address public owner;
mapping(uint => Product) public products;
uint public productCount;
event ProductAdded(uint productId, string productName, uint quantity, uint price, address farmer);
event ProductProcessed(uint productId, address distributor);
event ProductSold(uint productId, address retailer);
event ProductShipped(uint productId, address retailer);
modifier onlyFarmer(uint _productId) {
require(msg.sender == products[_productId].farmer, "Apenas o fazendeiro pode realizar essa ação.");
_;
}
modifier onlyDistributor(uint _productId) {
require(msg.sender == products[_productId].distributor, "Apenas o distribuidor pode realizar essa ação.");
_;
}
modifier onlyRetailer(uint _productId) {
require(msg.sender == products[_productId].retailer, "Apenas o varejista pode realizar essa ação.");
_;
}
modifier productExists(uint _productId) {
require(_productId <= productCount, "Produto não existe.");
_;
}
function addProduct(string memory _productName, uint _quantity, uint _price) public {
productCount++;
products[productCount] = Product(productCount, _productName, _quantity, _price, payable(msg.sender), payable(address(0)), payable(address(0)), ProductStatus.ForSale);
emit ProductAdded(productCount, _productName, _quantity, _price, msg.sender);
}
function processProduct(uint _productId) public productExists(_productId) onlyDistributor(_productId) {
require(products[_productId].status == ProductStatus.ForSale, "O produto não está disponível para distribuição.");
products[_productId].distributor = payable(msg.sender);
products[_productId].status = ProductStatus.Processed;
emit ProductProcessed(_productId, msg.sender);
}
function sellProduct(uint _productId) public productExists(_productId) onlyDistributor(_productId) {
require(products[_productId].status == ProductStatus.Processed, "O produto não está disponível para venda.");
products[_productId].retailer = payable(msg.sender);
products[_productId].status = ProductStatus.Sold;
emit ProductSold(_productId, msg.sender);
}
function buyProduct(uint _productId) public payable productExists(_productId) {
require(products[_productId].status == ProductStatus.ForSale, "O produtonão está disponível para venda.");
require(msg.value >= products[_productId].price, "Pagamento Insuficiente.");
products[_productId].retailer = payable(msg.sender);
products[_productId].status = ProductStatus.Sold;
emit ProductSold(_productId, msg.sender);
}
function shipProduct(uint _productId) public productExists(_productId) onlyRetailer(_productId) {
require(products[_productId].status == ProductStatus.Sold, "O produto não foi vendido ainda.");
products[_productId].status = ProductStatus.Shipped;
emit ProductShipped(_productId, msg.sender);
}
}
Implantação
Para garantir uma implantação bem-sucedida do nosso contrato inteligente, precisamos ter a extensão da carteira Celo. Você pode baixá-la aqui.
Para prosseguir, também precisamos financiar a carteira que acabamos de criar. Isto pode ser feito usando a torneira Celo Alfajores.
Assim que essas etapas iniciais forem concluídas, localize o logotipo do plugin posicionado no canto inferior esquerdo e inicie uma busca pelo plugin Celo.
Instale o plugin e você notará o logotipo Celo aparecendo na aba lateral assim que a instalação for concluída.
Após conectar sua carteira Celo, você poderá escolher o contrato desejado para implantação.
Para implantar, compile o contrato SupplyChain.sol
e clique no botão deploy
.
Conclusão
Chegamos agora ao final deste tutorial. Seguindo este tutorial, você deverá ter um bom entendimento de como construir um contrato inteligente para cadeia de suprimentos agro com Solidity na blockchain Celo.
Referências
- Extensão da Carteira Celo
- Torneira Celo Alfajores
- Código fonte
Este artigo foi escrito por Kyrian e traduzido por Diogo Jorge. O artigo original pode ser encontrado aqui.
Latest comments (0)