WEB3DEV Español

Cover image for Construyendo un Contrato Inteligente para Cadena de Suministros Agrícolas con Solidity en Celo
Juan José Gouvêa
Juan José Gouvêa

Posted on

Construyendo un Contrato Inteligente para Cadena de Suministros Agrícolas con Solidity en Celo

Introducción

En este tutorial, construiremos un contrato inteligente para la cadena de suministros que rastrea los productos agrícolas desde el agricultor hasta el consumidor final. El contrato se construye utilizando Solidity y se despliega en la blockchain de Celo. Al emplear este enfoque, los participantes en la cadena de suministros pueden aumentar la transparencia, reducir costos y reforzar la seguridad.

Para ilustrar mejor esto, consideremos un escenario hipotético: un agricultor produce un producto y lo añade al inventario de artículos disponibles que pueden ser adquiridos por un distribuidor. El distribuidor luego procesa el producto y lo prepara para la venta. Posteriormente, un minorista adquiere el producto del distribuidor y, finalmente, lo ofrece para la venta al consumidor final.

Prerrequisitos

Para seguir este tutorial, necesitarás:

  • Node.js

  • Algunos conocimientos de Solidity

Requisitos

Escribiendo el contrato inteligente

Navega hasta la carpeta de Hardhat y crea un nuevo archivo 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

Aquí, comenzaremos declarando nuestro contrato supplyChain. Dentro del contrato, hay una declaración enum llamada productStatusque define cuatro valores posibles: ForSale (a la venta), *Processed *(procesado), *Shipped *(enviado) y *Sold *(vendido). El enum se usa para representar el estado de un producto en la cadena de suministros.

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: un número entero sin signo que representa el identificador único del producto.

string productName: una cadena que representa el nombre del producto.

uint quantity: Un número entero sin signo que indica la cantidad del producto.

uint price: un número entero sin signo que indica el precio del producto.

address payable farmer: Esta es la dirección de pago del agricultor asociado al producto.

address payable distributor: Una dirección Ethereum pagable que representa la dirección del distribuidor asociado al producto.

address payable retailer: una dirección Ethereum pagable que representa la dirección del minorista asociado al producto.

ProductStatus status: una variable del tipo enum ProductStatus que indica el estado actual del producto.

Constructor

constructor() {
 owner = msg.sender;  
 }
address public owner;

mapping(uint => Product) public products;
uint public productCount;
Enter fullscreen mode Exit fullscreen mode

El constructor inicializa la variable owner(propietario) con la dirección del implementador del contrato y la declara como pública.

El mapeo público productsalmacena información del producto basada en sus IDs únicos mientras que productCountsigue la cuenta total de productos en la cadena de suministros.

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 se activa cuando se añade un producto. Emite el ID del producto, nombre, cantidad, precio y dirección del agricultor asociado.

event ProductProcessed se activa cuando un producto es procesado. Emite el ID del producto y la dirección del distribuidor responsable del procesamiento.

event ProductSold se activa cuando un producto es vendido. Emite el ID del producto y la dirección del minorista que adquirió el producto.

event ProductShipped se activa cuando un producto es enviado. Emite el ID del producto y la dirección del minorista que envía el producto al consumidor.

Modifier

modifier onlyFarmer(uint _productId) {
 require(msg.sender == products[_productId].farmer, "Solo el agricultor puede realizar esta acción.");
 _;
 }

modifier onlyDistributor(uint _productId) {
 require(msg.sender == products[_productId].distributor, "Solo el distribuidor puede realizar esta acción.");
 _;
 }

modifier onlyRetailer(uint _productId) {
 require(msg.sender == products[_productId].retailer, "Solo el minorista puede realizar esta acción.");
 _;
 }

modifier productExists(uint _productId) {
 require(_productId <= productCount, "El producto no existe.");
 _;
 }
Enter fullscreen mode Exit fullscreen mode

Los modifiers (modificadores) aseguran que solo las funciones asociadas a un producto específico puedan ejecutar una acción. Verifica si el msg.sender corresponde a la dirección de la respectiva función asociada al producto. De lo contrario, genera un mensaje de error.

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

La función addProductañade un producto al mapeo, incrementa la variable productCount y emite el evento correspondiente ProductAdded.

processProduct

function processProduct(uint _productId) public productExists(_productId)  
     onlyDistributor(_productId) {
     require(products[_productId].status == ProductStatus.ForSale, "Producto no disponible para distribución.");

     products[_productId].distributor = payable(msg.sender);
     products[_productId].status = ProductStatus.Processed;
     emit ProductProcessed(_productId, msg.sender);
}
Enter fullscreen mode Exit fullscreen mode

La función processProductprocesa un producto para distribución actualizando la dirección del distribuidor, cambiando el estado del producto y emitiendo el evento correspondiente. Requiere que el producto exista y solo el distribuidor asociado puede ejecutar esta función. Además, el producto debe estar en el estado ForSale para ser elegible para distribución.

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

La función sellProductpermite que el producto sea vendido realizando tareas como la actualización de su estado, asignando al minorista y emitiendo un evento para avisar sobre la venta.

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

La función buyProductpermite que un comprador adquiera un producto proporcionando el pago necesario. Verifica si el producto está disponible para la venta y si el pago es suficiente. Si las condiciones se cumplen, actualiza el minorista y el estado del producto.

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

La función shipProductasegura que solo el minorista asociado a un producto específico pueda ejecutar la función shipProduct. Verifica si el producto ha sido vendido y actualiza su estado a “Enviado” antes de emitir el evento ProductShipped.

Su código final debería verse así:

// 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

Implementación

Para asegurar una implementación exitosa de nuestro contrato inteligente, necesitamos tener la extensión de la cartera Celo. Puedes descargarla aquí.

Para proceder, también necesitamos financiar la cartera que acabamos de crear. Esto puede hacerse utilizando el grifo Celo Alfajores.

Una vez completados estos pasos iniciales, localiza el logotipo del complemento situado en la esquina inferior izquierda e inicia una búsqueda del complemento Celo.

Instala el complemento y notarás el logotipo de Celo apareciendo en la barra lateral una vez que la instalación se haya completado.

Tras conectar tu cartera Celo, podrás elegir el contrato deseado para la implementación.

Para implementar, compila el contratoSupplyChain.sol y haz clic en el botón de despliegue.

Conclusión

Hemos llegado al final de este tutorial. Siguiendo este tutorial, deberías tener un buen entendimiento de cómo construir un contrato inteligente para la cadena de suministros agrícolas con Solidity en la blockchain de Celo.

Referencias

Este artículo fue escrito por Kyrian y traducido por Juan José Gouvêa. El artículo original se puede encontrar aquí.

Discussion (0)