WEB3DEV

Cover image for Construindo um Contrato Inteligente para Cadeia de Suprimentos Agro com Solidity na Celo
Diogo Jorge
Diogo Jorge

Posted on

Construindo um Contrato Inteligente para Cadeia de Suprimentos Agro com Solidity na Celo

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}
Enter fullscreen mode Exit fullscreen mode

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;
    }
Enter fullscreen mode Exit fullscreen mode

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;
Enter fullscreen mode Exit fullscreen mode

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);
Enter fullscreen mode Exit fullscreen mode

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.");
       _;
   }
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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);
    }
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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);
    }
}
Enter fullscreen mode Exit fullscreen mode

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

Este artigo foi escrito por Kyrian e traduzido por Diogo Jorge. O artigo original pode ser encontrado aqui.

Latest comments (0)