Building a Performance-Based Smart Contract Payment Systems on Celo Blockchain

Building a Performance-Based Smart Contract Payment Systems on Celo Blockchain https://celo.academy/uploads/default/optimized/2X/8/891cb3a6296ea3d542f3eaf8d0974b0216cb0cad_2_1024x576.jpeg
none 0.0 0

Introduction

In today’s dynamic world, businesses are constantly exploring innovative ways to optimize their operations, and performance-based payment models have emerged as an exciting approach in this endeavour. Emphasizing the value of results and quality, these models promise a more fair and accountable compensation structure. Now, with the advent of blockchain technology, we have the opportunity to take these models to the next level.

Welcome to our tutorial, “Building Performance-Based Payment Systems on Celo.” This comprehensive guide will walk you through the process of creating a performance-based payment system using smart contracts on the Celo blockchain. By leveraging Celo’s efficient and accessible blockchain platform, you’ll learn to establish a transparent, secure, and automated payment system that ensures every contributor in your project is compensated in line with their achievements.

You can find the repository for this tutorial Here

Prerequisites

To follow this tutorial, you will need the following:

  • Basic understanding of Solidity and smart contracts.
  • A Development Environment Like Remix.
  • The Celo Extension Wallet.

Contract Developement

The complete code contract look like this

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract AdvancedPerformanceBasedPayment is AccessControl, ReentrancyGuard {
    using SafeMath for uint256;

    bytes32 public constant EVALUATOR_ROLE = keccak256("EVALUATOR_ROLE");
    bytes32 public constant ARBITER_ROLE = keccak256("ARBITER_ROLE");

    IERC20 public token;
    uint256 public pointsPerToken;

    uint256 public constant MAX_POINTS = 10**30;

    enum PerformanceLevel {LEVEL1, LEVEL2, LEVEL3}

    struct Performance {
        uint256 points;
        PerformanceLevel level;
    }

    mapping(address => Performance) public performances;

    event PerformancePointsAdded(address indexed performer, uint256 points, PerformanceLevel level);
    event PaymentClaimed(address indexed performer, uint256 amount);
    event DisputeResolved(address indexed performer, uint256 points, PerformanceLevel level);

    constructor(IERC20 _token, uint256 _pointsPerToken) {
        _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _setupRole(EVALUATOR_ROLE, msg.sender);
        _setupRole(ARBITER_ROLE, msg.sender);
        token = _token;
        pointsPerToken = _pointsPerToken;
    }

    function addPerformancePoints(address performer, uint256 _points, PerformanceLevel level) external onlyRole(EVALUATOR_ROLE) {
        require(performances[performer].points.add(_points) <= MAX_POINTS, "Max points limit reached");
        performances[performer].points = performances[performer].points.add(_points);
        performances[performer].level = level;
        emit PerformancePointsAdded(performer, _points, level);
    }

    function claimPayment() external nonReentrant {
        require(performances[msg.sender].points >= pointsPerToken, "Not enough performance points");
        uint256 tokenAmount = performances[msg.sender].points.div(pointsPerToken);
        performances[msg.sender].points = performances[msg.sender].points.mod(pointsPerToken);
        token.transfer(msg.sender, tokenAmount);
        emit PaymentClaimed(msg.sender, tokenAmount);
    }

    function resolveDispute(address performer, uint256 _points, PerformanceLevel level) external onlyRole(ARBITER_ROLE) {
        performances[performer].points = _points;
        performances[performer].level = level;
        emit DisputeResolved(performer, _points, level);
    }
}

Code Breakdown

Let’s look at our smart contract in detail.

Version and License Declaration

The script starts with specifying the license under which the code is released and the version of the Solidity compiler it’s compatible with:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

The pragma solidity ^0.8.0; line means that this contract is written for the Solidity compiler version 0.8.0 or any newer compatible versions.

Imports

Next, the contract imports several libraries from the OpenZeppelin contracts library. OpenZeppelin provides reusable and secure smart contract components that are widely used in the Celo development community.

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/utils/math/SafeMath.sol";
  • AccessControl.sol: This module is used for role-based access control, allowing to restrict access to certain functions in the contract to certain Ethereum addresses.
  • ReentrancyGuard.sol: This is a security module that helps prevent reentrancy attacks. A reentrancy attack is when an attacker is able to repeatedly call a function and drain funds.
  • IERC20.sol: This is an interface for ERC20 tokens. ERC20 is a standard interface for tokens, meaning that this contract will be able to interact with any ERC20-compliant tokens.
  • SafeMath.sol: This library provides functions for safe mathematical operations that throw errors on overflow. This helps avoid common bugs in smart contracts.

Contract Declaration

The contract itself is declared with the line:

contract AdvancedPerformanceBasedPayment is AccessControl, ReentrancyGuard {

This is declaring a new contract named AdvancedPerformanceBasedPayment . The is keyword is used for inheritance in Solidity, meaning that this contract will have all public/external functions and state variables from the AccessControl and ReentrancyGuard contracts.

Using Statements

SafeMath library is then declared for use on uint256 data types for safe mathematical operations.

using SafeMath for uint256;

Role Constants

The contract defines two constants for the roles it will be using:

bytes32 public constant EVALUATOR_ROLE = keccak256("EVALUATOR_ROLE");
bytes32 public constant ARBITER_ROLE = keccak256("ARBITER_ROLE");

These constants are hash values generated from the strings “EVALUATOR_ROLE” and “ARBITER_ROLE”. These roles will be used to restrict access to certain functions.

State Variables

The contract declares several state variables:

IERC20 public token;
uint256 public pointsPerToken;
  • token: This is the ERC20 token that will be used for payments.
  • pointsPerToken: This variable determines how many performance points are needed to earn one token.

There’s also a constant for the maximum number of points:

uint256 public constant MAX_POINTS = 10**30;

This constant defines the maximum number of performance points that can be earned.

Performance Level Enumeration

The contract defines an enumeration called PerformanceLevel:

enum PerformanceLevel {LEVEL1, LEVEL2, LEVEL3}

An enum is a way of creating a user-defined type in Solidity. The PerformanceLevel enum represents three possible levels of performance.

Performance Struct

Next, we have a struct definition for Performance:

   struct Performance {
       uint256 points;
       PerformanceLevel level;
   }

A struct is a way to define a new data type in Solidity. The Performance struct represents a performer’s total points and their performance level.

Performance Mapping

The contract maintains a mapping from addresses to their performance:

 mapping(address => Performance) public performances;

A mapping in Solidity is similar to a hash table in other programming languages. This line declares a public mapping named performances where keys are addresses and values are Performance structs. This mapping will be used to keep track of the performance points and levels of each performer.

Events

Events are emitted using the emit keyword and they cause the arguments to be stored in the transaction’s log, a special data structure in the blockchain. These logs are associated with the address of the contract and are incorporated into the blockchain.

    event PerformancePointsAdded(address indexed performer, uint256 points, PerformanceLevel level);
    event PaymentClaimed(address indexed performer, uint256 amount);
    event DisputeResolved(address indexed performer, uint256 points, PerformanceLevel level);
  • PerformancePointsAdded: This event is emitted when performance points are added to a performer.

  • PaymentClaimed: This event is emitted when a performer claims their payment.

  • DisputeResolved: This event is emitted when a dispute is resolved.

Constructor

The constructor is a special function that is executed during the creation of the contract:

constructor(IERC20 _token, uint256 _pointsPerToken) {
       _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
       _setupRole(EVALUATOR_ROLE, msg.sender);
       _setupRole(ARBITER_ROLE, msg.sender);
       token = _token;
       pointsPerToken = _pointsPerToken;
   }

The constructor takes two parameters:

_token and _pointsPerToken. It sets up the DEFAULT_ADMIN_ROLE, EVALUATOR_ROLE, and ARBITER_ROLE to be associated with the address that deploys the contract (msg.sender). It also initializes the token and pointsPerToken state variables.

Functions

 function addPerformancePoints(address performer, uint256 _points, PerformanceLevel level) external onlyRole(EVALUATOR_ROLE) {
        require(performances[performer].points.add(_points) <= MAX_POINTS, "Max points limit reached");
        performances[performer].points = performances[performer].points.add(_points);
        performances[performer].level = level;
        emit PerformancePointsAdded(performer, _points, level);
    }

    function claimPayment() external nonReentrant {
        require(performances[msg.sender].points >= pointsPerToken, "Not enough performance points");
        uint256 tokenAmount = performances[msg.sender].points.div(pointsPerToken);
        performances[msg.sender].points = performances[msg.sender].points.mod(pointsPerToken);
        token.transfer(msg.sender, tokenAmount);
        emit PaymentClaimed(msg.sender, tokenAmount);
    }

    function resolveDispute(address performer, uint256 _points, PerformanceLevel level) external onlyRole(ARBITER_ROLE) {
        performances[performer].points = _points;
        performances[performer].level = level;
        emit DisputeResolved(performer, _points, level);
    }
  • addPerformancePoints: This function allows an evaluator to add performance points to a performer. It emits a PerformancePointsAdded event.

  • claimPayment: Any performer can claim their payment based on the points they have accumulated. The function checks if the performer has enough points, calculates the amount of token to be paid, updates the performer’s points, transfers the token to the performer, and emits a PaymentClaimed event.

  • resolveDispute: An arbiter can resolve disputes by setting a performer’s points and performance level. It emits a DisputeResolved event.

Deployment

To deploy our smart contract successfully, we need the celo extention wallet which can be downloaded from here

  • Next, we need to fund our newly created wallet which can done using the celo alfojares faucet Here

  • Now, click on the plugin logo at the bottom left corner and search for celo plugin.

  • Install the plugin and click on the celo logo which will show in the side tab after the plugin is installed.

  • Finally, connect your Celo wallet, select the contract you want to deploy and finally click on deploy to deploy your contract.

Conclusion

This contract manages a system where performers are rewarded based on their performance, and disputes can be resolved by an arbiter. The contract makes use of role-based access control to restrict the ability to add points and resolve disputes to certain roles. It also includes safeguards against reentrancy attacks and arithmetic overflows, and it emits events that allow external observers to track the operations of the contract.

Next Steps

I hope you learned a lot from this tutorial. Here are some relevant links that would aid your learning further.

About the author

I’m Jonathan Iheme, A full stack block-chain Developer from Nigeria.

linkedIn
Twitter

4 Likes

@Celo_Academy i would like to get started with this. Thanks :smiley:

2 Likes

Approved as award via Announcing This Week’s Top Proposals and Celebrating this Week’s Leading Contributors | 06/01/23. Thanks for your contributions to the community this week @4undRaiser!

3 Likes

@danielogbuti alright

1 Like

@danielogbuti done!!

1 Like

Looks good! Great job. You can move to publish

5 Likes

This tutorial showcases a deep understanding of the topic and effectively communicates complex concepts. It serves as an excellent resource for developers looking to build performance-based payment systems on the Celo blockchain. The writer has done a remarkable job, and their efforts are commendable.

Kudos to the writer for creating such a valuable and well-crafted tutorial

4 Likes

With performance-based payment systems, smart contracts can be designed to automatically trigger payments based on predefined conditions, such as meeting specific milestones, achieving set targets, or delivering certain results. Amazing tutorial as always, welldone brother.

6 Likes