WEB3DEV

Cover image for Construindo um Contrato Inteligente de Plataforma de Seguro Agrícola Usando Solidity e JavaScript na Celo
Isabela Curado Nehme
Isabela Curado Nehme

Posted on

Construindo um Contrato Inteligente de Plataforma de Seguro Agrícola Usando Solidity e JavaScript na Celo

Introdução

As plataformas de seguros agrícolas desempenham um papel vital ao ajudar os agricultores a gerir os riscos associados a condições climáticas adversas, infestações de pragas e outras circunstâncias imprevistas.

Este tutorial irá orientá-lo na criação de uma plataforma descentralizada de seguro agrícola na blockchain Celo, usando Solidity, Hardhat e JavaScript. Essa plataforma utilizará o Celo Dollar (cUSD) como meio de troca (exchange), e apenas os seguradores autorizados poderão segurar os agricultores.

Pré-requisitos

Antes de começarmos, certifique-se de ter o seguinte instalado:

Node.js e npm
Hardhat
Solidity versão 0.8.0 ou superior
Uma carteira Celo

Configurando o Ambiente de Desenvolvimento

Crie um novo diretório e inicialize o npm:

$ mkdir cropInsurance && cd cropInsurance
$ npm init -y
Enter fullscreen mode Exit fullscreen mode

Em seguida, instale o Hardhat e outras dependências necessárias:

$ npm install --save-dev hardhat
$ npm install --save-dev @nomiclabs/hardhat-waffle ethereum-waffle chai @nomiclabs/hardhat-ethers ethers @openzeppelin/contracts
$ npm install dotenv --save
Enter fullscreen mode Exit fullscreen mode

Inicialize o Hardhat:

$ npx hardhat
Enter fullscreen mode Exit fullscreen mode

Siga os prompts para criar um projeto de amostra básico.

Escrevendo o Contrato Inteligente

Crie um novo arquivo na pasta de contratos, nomeie-o como CropInsurance.sol e adicione o seguinte código:

// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";

contract Insurance is ERC20 {
    using SafeMath for uint256;
    IERC20 public immutable cUSD;

    uint256 constant minimumPremium = 50 ether;
    uint256 constant minimumClaimInsurance = 1000 ether;
    uint256 constant tokenRation = 20;

    struct Members {
        uint256 usdValue; 
        uint256 citValue;
        uint256 premi;
        uint256 registerDate;
        uint256 lastPayment;
        uint256 nextPayment;
    }

    mapping(address => Members) private _addressToMembers;
    mapping(address => bool) private _addressStatus;
    mapping (address => uint256[]) private _addressClaimHistory;
    mapping (address => uint256[]) private _addressPaymentHistory;

    event NewRegistration(address indexed members, uint256 timestamp);

    constructor(address _cUSD)ERC20("CropInsuranceToken", "CIT") {
        cUSD = IERC20(_cUSD);
    }

    function register(uint256 _usdAmount) external {
        require(_usdAmount >= minimumPremium, "Insurance: Minimum premium is 50 cUSD");
        require(!_addressStatus[msg.sender], "Insurance: You are already registered");
        cUSD.transferFrom(msg.sender, address(this), _usdAmount);
        uint256 _citAmount = _usdAmount.mul(tokenRation);
        _addressToMembers[msg.sender] = Members(
            _usdAmount,
            _citAmount,
            _usdAmount,
            block.timestamp,
            block.timestamp,
            block.timestamp.add(30 days)
        );
        _addressStatus[msg.sender] = true; 
        _addressPaymentHistory[msg.sender].push(block.timestamp);
        _mint(msg.sender, _citAmount);
        emit NewRegistration(msg.sender, block.timestamp);
    }

    function claim(uint256 _citAmount) external {
        require(_citAmount >= minimumClaimInsurance, "Insurance: Minimum claim is 1000 CIT");
        require(_addressStatus[msg.sender], "Insurance: You are not registered");
        uint256 _usdAmount = _citAmount.div(tokenRation);
        Members storage members = _addressToMembers[msg.sender];
        members.usdValue = members.usdValue.sub(_usdAmount);
        members.citValue = members.citValue.sub(_citAmount);
        _addressClaimHistory[msg.sender].push(block.timestamp);
        _burn(msg.sender, _citAmount);
        cUSD.transfer(msg.sender, _usdAmount);
    }

    function pay(uint256 _usdAmount) external {
        require(_addressStatus[msg.sender], "Insurance: You are not registered");
        Members storage members = _addressToMembers[msg.sender];
        require(_usdAmount >= members.premi, "Insurance: Payment amount is less than the premium");
        require(block.timestamp >= members.nextPayment, "Insurance: Payment is not due");
        cUSD.transferFrom(msg.sender, address(this), _usdAmount);
        uint256 _citAmount = _usdAmount.mul(tokenRation);
        members.usdValue = members.usdValue.add(_usdAmount);
        members.citValue = members.citValue.add(_citAmount);
        members.lastPayment = block.timestamp;
        members.nextPayment = block.timestamp.add(30 days);
        _addressPaymentHistory[msg.sender].push(block.timestamp)           
        _mint(msg.sender, _citAmount);
    }

    function getInsurance() external view returns (Members memory) {
        return _addressToMembers[msg.sender];
    }
}
Enter fullscreen mode Exit fullscreen mode

Este é um contrato Solidity chamado Insurance para um sistema de seguros onde os usuários podem se registrar, fazer pedidos de indenização e pagamentos em um token chamado cUSD (presumivelmente uma stablecoin atrelada ao dólar americano) e receber tokens chamados CropInsuranceToken (CIT) em troca. Esse sistema utiliza o padrão ERC20 para o CropInsuranceToken.

Vamos examinar cada seção:

  • Pragma e Importações:
// SPDX-License-Identifier: MIT
pragma solidity 0.8.15;

import "@openzeppelin/contracts/utils/math/SafeMath.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
Enter fullscreen mode Exit fullscreen mode

Esta seção define a versão do compilador Solidity a ser usada (0.8.15) e importa diversas bibliotecas do OpenZeppelin: SafeMath para operações matemáticas seguras, ERC20 para a funcionalidade básica do padrão ERC20 e IERC20 que é a interface para o padrão ERC20.

  • Declaração do contrato:
contract Insurance is ERC20 {
Enter fullscreen mode Exit fullscreen mode

Esta linha declara o contrato Insurence (Seguro) que estende o contrato ERC20. Isso dá ao Insurance todas as funcionalidades de um token ERC20.

  • Variáveis ​​globais:
using SafeMath for uint256;
IERC20 public immutable cUSD;

uint256 constant minimumPremium = 50 ether;
uint256 constant minimumClaimInsurance = 1000 ether;
uint256 constant tokenRation = 20;
Enter fullscreen mode Exit fullscreen mode

A biblioteca SafeMath é usada para realizar operações matemáticas com segurança (evitando overflows e underflows). cUSD é uma instância pública e imutável de um token IERC20, que será inicializado no construtor e não poderá ser alterado posteriormente. O contrato também define três variáveis ​​constantes: minimumPremium (50 ethers), minimumClaimInsurance (1000 ethers) e tokenRation (20 ethers).

  • Structs e mapeamentos:
struct Members {
    uint256 usdValue; 
    uint256 citValue;
    uint256 premi;
    uint256 registerDate;
    uint256 lastPayment;
    uint256 nextPayment;
}

mapping(address => Members) private _addressToMembers;
mapping(address => bool) private _addressStatus;
mapping (address => uint256[]) private _addressClaimHistory;
mapping (address => uint256[]) private _addressPaymentHistory;
Enter fullscreen mode Exit fullscreen mode

A struct Members (membros) armazena informações sobre cada segurado, incluindo o valor de cUSD que eles contribuíram (usdValue), o valor do CIT que receberam (citValue), o valor do prêmio que devem pagar (premi), a data de registro (registerDate), a hora do último pagamento (lastPayment) e a hora do próximo pagamento (nextPayment).

Quatro mapeamentos são declarados: _addressToMembers mapeia um endereço para uma struct Members, _addressStatus mapeia um endereço para um bool indicando se o endereço está registrado ou não, _addressClaimHistory mapeia um endereço para um array de marcas temporais indicando quando eles fizeram reivindicações e _addressPaymentHistory mapeia um endereço a uma série de marcas temporais indicando quando eles fizeram pagamentos.

  • Eventos:
event NewRegistration(address indexed members, uint256 timestamp);
Enter fullscreen mode Exit fullscreen mode

Este evento é emitido sempre que ocorre um novo cadastro, informando o endereço do associado e a data e hora do evento.

  • Construtor:
constructor(address _cUSD)ERC20("CropInsuranceToken", "CIT") {
    cUSD = IERC20(_cUSD);
}
Enter fullscreen mode Exit fullscreen mode

O construtor aceita um endereço _cUSD como parâmetro e o atribui a cUSD (que é um token IERC20). Esse endereço é o endereço do contrato do token cUSD. O contrato também chama o construtor do contrato ERC20, definindo o nome do token como “CropInsuranceToken” e o símbolo como “CIT”.

  • Função de registro:
function register(uint256 _usdAmount) external {
    // ... omitido por razões de brevidade
}
Enter fullscreen mode Exit fullscreen mode

Esta função permite que um usuário se registre no seguro enviando _usdAmount de cUSD para o contrato. O usuário recebe _usdAmount vezes tokenRation de tokens CIT em troca. Várias condições devem ser atendidas para que a transação seja bem-sucedida: o valor enviado deve ser igual ou superior ao prêmio mínimo e o usuário ainda não deve estar cadastrado.

  • Função de reivindicação:
function claim(uint256 _citAmount) external {
    // ... omitido por razões de brevidade
}
Enter fullscreen mode Exit fullscreen mode

Esta função permite que um usuário registrado faça uma reivindicação, queimando seus tokens CIT e recebendo cUSD em troca. O valor da reinvindicação no CIT deverá ser igual ou superior ao minimumClaimInsurance e o usuário deverá estar cadastrado.

  • Função de pagamento:
function pay(uint256 _usdAmount) external {
    // ... omitido por razões de brevidade
}
Enter fullscreen mode Exit fullscreen mode

Esta função permite que um usuário registrado faça um pagamento em cUSD, o que aumenta seu usdValue e citValue nos valores apropriados. O valor do pagamento deve ser pelo menos o valor do prêmio e o pagamento deve ser devido (ou seja, a marca temporal atual deve ser maior ou igual ao nextPayment).

  • Função getInsurance:
function getInsurance() external view returns (Members memory) {
    return _addressToMembers[msg.sender];
}
Enter fullscreen mode Exit fullscreen mode

Esta função permite que um usuário visualize os detalhes do seu seguro, retornando a struct Members associada ao seu endereço.

Observe que todas as funções deste contrato são externas, o que significa que só podem ser chamadas de fora do contrato. Além disso, este contrato pressupõe que a aprovação para transferências de tokens foi feita fora deste contrato, ou seja, os usuários aprovaram este contrato para gastar seus tokens cUSD.

Compilando o Contrato Inteligente

Compile o contrato usando Hardhat:

$ npx hardhat compile
Enter fullscreen mode Exit fullscreen mode

Escrevendo testes

Em seguida, escreva testes para o contrato. Crie um novo arquivo no diretório de teste chamado CropInsuranceTest.js e adicione o seguinte código:

const { expect } = require("chai");
const { ethers } = require("hardhat");
const {
    time,
    loadFixture
} = require("@nomicfoundation/hardhat-network-helpers");

describe("Insurance", function(){
    async function setup() {
        const [ deployer, otherAccount ] = await ethers.getSigners();

        // Carregar contrato
        const Insurance = await ethers.getContractFactory("Insurance");
        const Token = await ethers.getContractFactory("TestToken");

        // Implantar contrato
        const cUSD = await Token.deploy();
        const insurance = await Insurance.deploy(await cUSD.getAddress());

        // Solicitação de torneira OtherAccount 
        await cUSD.connect(otherAccount).faucet();

        return { deployer, otherAccount, cUSD, insurance };
    }

    it("Register", async function(){
        const { otherAccount, cUSD, insurance } = await loadFixture(setup);
        const amount = ethers.parseEther("60");

        // Registrar seguro
        await cUSD.connect(otherAccount).approve(await insurance.getAddress(), amount);
        await insurance.connect(otherAccount).register(amount);

        // Verificar dados
        const insuranceData = await insurance.connect(otherAccount).getInsurance();
        expect(insuranceData[0]).to.be.equal(amount);
        expect(insuranceData[1]).to.be.equal(ethers.parseEther("1200"));
        expect(insuranceData[2]).to.be.equal(amount);
        expect(await insurance.balanceOf(otherAccount.address)).to.be.equal(ethers.parseEther("1200"));
        expect(await cUSD.balanceOf(await insurance.getAddress())).to.be.equal(ethers.parseEther("60"));
    });

    it("Claim", async function(){
        const { otherAccount, cUSD, insurance } = await loadFixture(setup);
        const amount = ethers.parseEther("60");

        // Registrar seguro
        await cUSD.connect(otherAccount).approve(await insurance.getAddress(), amount);
        await insurance.connect(otherAccount).register(amount);

        // Obter dados de seguro 
        const citAmount = await insurance.balanceOf(otherAccount.address);
        const insuranceData = await insurance.connect(otherAccount).getInsurance();

        // Aumentar o tempo
        time.increaseTo(insuranceData[5]);

        // Reivindicar
        await insurance.connect(otherAccount).claim(citAmount);
        const newInsuranceData = await insurance.connect(otherAccount).getInsurance();
        expect(newInsuranceData[0]).to.be.equal(ethers.parseEther("0"));
        expect(newInsuranceData[1]).to.be.equal(ethers.parseEther("0"));
        expect(await insurance.balanceOf(otherAccount.address)).to.be.equal(ethers.parseEther("0"));
        expect(await cUSD.balanceOf(await otherAccount.address)).to.be.equal(ethers.parseEther("200"));
   });

   it("Pay Insurance", async function(){
       const { otherAccount, cUSD, insurance } = await loadFixture(setup);
       const amount = ethers.parseEther("60");

       // Registrar seguro 
       await cUSD.connect(otherAccount).approve(await insurance.getAddress(), amount);
       await insurance.connect(otherAccount).register(amount);

       // Obter dados de seguro
       const citAmount = await insurance.balanceOf(otherAccount.address);
       const insuranceData = await insurance.connect(otherAccount).getInsurance();

       // Aumentar tempo
       time.increaseTo(insuranceData[5]);

       // Pagar seguro
       await cUSD.connect(otherAccount).approve(await insurance.getAddress(), amount);
       await insurance.connect(otherAccount).pay(amount);
       const newInsuranceData = await insurance.connect(otherAccount).getInsurance();

       expect(newInsuranceData[0]).to.be.equal(ethers.parseEther("120"));
       expect(newInsuranceData[1]).to.be.equal(ethers.parseEther("2400"));
      expect(await cUSD.balanceOf(await insurance.getAddress())).to.be.equal(ethers.parseEther("120"));
      expect(await insurance.balanceOf(otherAccount.address)).to.be.equal(ethers.parseEther("2400"));
   })
})
Enter fullscreen mode Exit fullscreen mode

Este conjunto de testes verifica se o contrato segura corretamente um agricultor e permite que ele solicite o seguro. Substitua “0xcUSDCoinAddress” pelo endereço do contrato do token cUSD e “0xYourAddress” por um endereço real.

Você pode executar estes testes usando Hardhat:

$ npx hardhat test
Enter fullscreen mode Exit fullscreen mode

Implantando o Contrato Inteligente

Após o teste, implante o contrato na rede Celo. Você precisa configurar uma rede na configuração do Hardhat e depositar em sua conta com cUSD.

No arquivo hardhat.config.js, adicione:

require("@nomicfoundation/hardhat-toolbox");
require("dotenv").config({ path: ".env" });

/** @type import('hardhat/config').HardhatUserConfig */
module.exports = {
    solidity: {
        version: "0.8.15",
        settings: {
            optimizer: {
                enabled: true,
                runs: 200,
            },
        },
    },
    networks:{
        alfajores:{
            url: process.env.RPC_URL,
            chainId: 44787,
            accounts: {
                mnemonic: process.env.MNEMONIC,
                path: "m/44'/60'/0'/0",
            }
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

Por fim, crie um script de implantação em scripts/deploy.js:

const hre = require("hardhat")

async function main() {
  const CropInsurance = await hre.ethers.getContractFactory("CropInsurance");
  const cropInsurance = await CropInsurance.deploy();
  await cropInsurance.deployed("<CUSD_ADDRESS>");
  console.log("CropInsurance deployed to:", cropInsurance.address);
}

main()
  .then(() => process.exit(0))
  .catch(error => {
      console.error(error);
      process.exit(1);
  });
Enter fullscreen mode Exit fullscreen mode

Execute o comando abaixo para implantar o contrato:

npx hardhat run --network celo scripts/deploy.js
Enter fullscreen mode Exit fullscreen mode

Agora você tem uma plataforma de seguro agrícola na blockchain Celo. Essa plataforma permite que um funcionário designado assegure as colheitas dos agricultores em troca de um prémio pago em cUSD. Os agricultores podem reivindicar o seu seguro em caso de perda, com todas as transações registradas de forma transparente na blockchain.

Aprimoramentos futuros podem incluir links para um oráculo meteorológico para reinvindicações automáticas com base em condições climáticas adversas.

Sobre o autor

Aborode Olusegun Isaac é um profissional de marketing em crescimento e analista de dados. Ele tem um grande interesse na Web3 e na descentralização que ela oferece.

Referência

Código fonte 5

Este artigo foi escrito por Olusegun e traduzido por Isabela Curado Nehme. Seu original pode ser lido aqui.

Top comments (0)