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}
Aquí, comenzaremos declarando nuestro contrato supplyChain
. Dentro del contrato, hay una declaración enum llamada productStatus
que 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;
}
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;
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 products
almacena información del producto basada en sus IDs únicos mientras que productCount
sigue 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);
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.");
_;
}
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);
}
La función addProduct
añ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);
}
La función processProduct
procesa 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);
}
La función sellProduct
permite 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);
}
La función buyProduct
permite 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);
}
La función shipProduct
asegura 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);
}
}
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
Extensión de la Cartera Celo
Este artículo fue escrito por Kyrian y traducido por Juan José Gouvêa. El artículo original se puede encontrar aquí.
Discussion (0)