# Base Contract

The ChallengeBase contract serves as a core component for managing gaming challenges on the blockchain. It integrates role-based access control, game template management, and prize distribution, ensuring secure and flexible challenge creation and participation. Additionally, it manages valid ERC20 tokens for transactions and maintains a system to increment and track challenge IDs effectively.

{% code fullWidth="false" %}

```solidity
// SPDX-License-Identifier: MIT
pragma solidity 0.8.21;

import {AccessControl} from "@openzeppelin/contracts/access/AccessControl.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";

/// @title Base contract for all Challenge types, includes common structs and functions
/// @author Challenge.GG
/// @dev The ChallengeBase contract serves as a core component for managing gaming challenges on the blockchain. 
/// It integrates role-based access control, game template management, and prize distribution, ensuring secure and flexible challenge creation and participation. 
/// Additionally, it manages valid ERC20 tokens for transactions and maintains a system to increment and track challenge IDs effectively.
contract ChallengeBase is AccessControl  {
    
    bytes32 public constant CONTRACT_ROLE = keccak256("CONTRACT"); //CONTRACT role is required to increment ids
    bytes32 public constant MANAGER_ROLE = keccak256("MANAGER"); //MANAGER role is required to handle gametemplates, payout structures and more
    bytes32 public constant PAYER_ROLE = keccak256("PAYMENT"); //PAYER role is required to handle payments

    uint256 private _challengeId  = 0; // Current challenge id, incremented for each new challenge created
    mapping (uint16 => uint256) private  _lastestIds; //Latest ids for each game template 
    mapping (address => bool) public isBlocked; //Players blocked from participating in challenges
    mapping (uint16 => GameTemplate) public gameTemplates; //Map of ids to game templates
    mapping (bool => PrizeDistribution[]) public prizeDistributions; // Mapping for sponsored and unsponsored distributions of prizes, x number of players will have a certain distribution including commission for Challenge.gg if not sponsored
    mapping(address => ValidERC20Token) public validTokens;  //Valid tokens to play with and their commission threshold (When we send comission to split contract)

    //Struct definitions

    //Challenge status
    enum ChallengeStatus {
        Unvailable,  //0 is default, when checking if challenge exists
        Created,
        Running,
        Finished,
        Error,
        Canceled
    }

    //Player status
    enum PlayerStatus {
        NotParticipating,  //0 is default,  when checking if player exists
        Participating,     
        Completed,
        Failed
    }    

    //A template to base created challenges on
    struct GameTemplate {
        uint256 fee;
        IERC20 token;
        uint256 prizeFund;
        IERC20 prizeFundToken;
        uint16 maxPlayersCount; //Do not need min players since min players is always equal to max players
        bool paused;  //If paused, players can not create or join challenges of this type
        bool softPaused; //If soft paused, players can join existing challenges of this type but not create new ones
    }

    // Percentage distribution of prizes, [80,10] = 80% to 1:st place, 10% to 2:nd place, 10% to ChallengeGG as comission
    struct PrizeDistribution {
        uint16 minPlayers;
        uint16 maxPlayers;
        uint8[] distribution;

    }

    //Valid tokens to play with and their commission threshold (When we send comission to split contract)
    struct ValidERC20Token {
        bool valid;
        uint256 commission_threshold;       
    }    

    //Modifiers

    /// @dev Restricted to members of the admin role.
    modifier onlyAdmin()
    {
        require(isAdmin(msg.sender), "Restricted to admins");
        _;
    }  

    /// @dev Restricted to members of the Contract role.
    modifier onlyContract()
    {
        require(isContract(msg.sender), "Restricted to allowed contracts");
        _;
    }

    /// @dev Restricted to members of the Manager role.
    modifier onlyManager()
    {
        require(isManager(msg.sender), "Restricted to managers");
        _;
    }  

    /// @dev Constructor
    /// @param multisigAdmin Mutisig owner 
    constructor(address multisigAdmin) {
       _grantRole(DEFAULT_ADMIN_ROLE, multisigAdmin);
       _setRoleAdmin(CONTRACT_ROLE, DEFAULT_ADMIN_ROLE);
       _setRoleAdmin(MANAGER_ROLE, DEFAULT_ADMIN_ROLE);
       _setRoleAdmin(PAYER_ROLE, DEFAULT_ADMIN_ROLE);
    }

    //Update functions
   
    /// @dev Add an account to the Contract role. Restricted to admins.
    /// @param account Address to add
    function addContract(address account) external onlyAdmin
    {
        grantRole(CONTRACT_ROLE, account);
    }

    /// @dev Remove an account from the Contract role. Restricted to admins.
    /// @param account Address to remove
    function removeContract(address account) external onlyAdmin
    {
        revokeRole(CONTRACT_ROLE, account);
    }

    /// @dev Add an account to the Manager role. Restricted to admins.
    /// @param account Address to add
    function addManager(address account) external onlyAdmin
    {
        grantRole(MANAGER_ROLE, account);
    }

    /// @dev Remove an account from the Manager role. Restricted to admins.
    /// @param account Address to remove
    function removeManager(address account) external onlyAdmin
    {
        revokeRole(MANAGER_ROLE, account);
    }

    /// @dev Add an account to the Payer role. Restricted to admins.
    /// @param account Address to add
    function addPayer(address account) external onlyAdmin
    {
        grantRole(PAYER_ROLE, account);
    }

    /// @dev Remove an account from the Payer role. Restricted to admins.
    /// @param account Address to remove
    function removePayer(address account) external onlyAdmin
    {
        revokeRole(PAYER_ROLE, account);
    }

    /// @dev Set challenge id offset. If we replace contract we need to set the challenge id offset. Restricted to admins.
    /// @param challengeId Challenge id to set
    function setChallengeIdOffset(uint256 challengeId) external onlyAdmin {
        _challengeId=challengeId;
    } 

    /// @dev Set current id offset for a game template. Restricted to admins.
    /// @param gameTemplateId Game template id
    /// @param challengeId Challenge id to set
    function setCurrentIdOffset(uint16 gameTemplateId, uint256 challengeId) external onlyAdmin {
        _lastestIds[gameTemplateId] = challengeId;
    }

    /// @dev Increment challenge id by one and returns the id, restricted to contracts
    /// @return challengeId Current challenge id
    function incrementAndGetChallengeId() external onlyContract returns(uint256) {
        _challengeId++;
        return _challengeId;
    }   

    /// @dev Increment the challenge id and set it for a specific game template id
    /// @param gameTemplateId Game template id
    /// @return challengeId Current challenge id
    function incrementAndSetCurrentId(uint16 gameTemplateId) external onlyContract returns(uint256) {
        _challengeId++;
        _lastestIds[gameTemplateId] = _challengeId;
        return _challengeId;
    }
   
    /// @dev Resets the current id for a game template, restricted to contracts
    /// @param gameTemplateId Game template id
    function resetCurrentId(uint16 gameTemplateId) external onlyContract {
        _lastestIds[gameTemplateId] = 0;
    }      

    /// @dev Add a ERC20 token to the valid tokens list, restricted to managers
    /// @param tokenAddress Token address to add
    /// @param commission_threshold Commission threshold to set
    function addValidToken(address tokenAddress, uint256 commission_threshold) external onlyManager {
        validTokens[tokenAddress] = ValidERC20Token({
            valid: true,
            commission_threshold: commission_threshold
        });
    }

    /// @dev Remove a ERC20 token from the valid tokens list, restricted to managers
    function removeValidToken(address tokenAddress) external onlyManager {
        delete validTokens[tokenAddress];
    } 

    /// @dev Add a new game template, restricted to managers
    /// @param gameTemplateId Game template id 
    /// @param maxPlayersCount  Max players for this template
    /// @param fee Fee for this template 
    /// @param token  ERC20 Token to use for this template 
    function addGameTemplate(uint16 gameTemplateId, uint16 maxPlayersCount, uint256 fee, address token, uint256 prizeFund, address prizeFundToken) external onlyManager {
        require(gameTemplates[gameTemplateId].maxPlayersCount==0,"Template already exist");
        _setGameTemplate(gameTemplateId, maxPlayersCount, fee, token, prizeFund, prizeFundToken);
    }

    /// @dev Update a game template, restricted to managers
    /// @param gameTemplateId Game template id
    /// @param maxPlayersCount  Max players for this template
    /// @param fee Fee for this template
    /// @param token  ERC20 Token to use for this template
    function updateGameTemplate(uint16 gameTemplateId, uint16 maxPlayersCount, uint256 fee, address token, uint256 prizeFund, address prizeFundToken) external onlyManager {        
        require(gameTemplates[gameTemplateId].maxPlayersCount!=0,"Template does not exist");
        _setGameTemplate(gameTemplateId, maxPlayersCount, fee, token, prizeFund, prizeFundToken);
    }

    /// @dev Delete a game template, restricted to managers
    /// @param gameTemplateId Game template id
    function deleteGameTemplate(uint16 gameTemplateId) external onlyManager  {
        require(gameTemplates[gameTemplateId].maxPlayersCount!=0,"Template does not exist");
        delete  gameTemplates[gameTemplateId];
    }

    /// @dev Set if template is paused (players can not create or join challenges of this type). Restricted to managers
    /// @param gameTemplateId Game template id
    /// @param paused True if template should be paused
    function setTemplatePaused(uint16 gameTemplateId, bool paused) external onlyManager {
        GameTemplate storage gameTemplate = gameTemplates[gameTemplateId];
        gameTemplate.paused = paused;
    }   

    /// @dev Set if template is soft paused (players can join existing challenges but not create new ones). Restricted to managers
    /// @param gameTemplateId Game template id
    /// @param softPaused True if template should be soft paused
    function setTemplateSoftPaused(uint16 gameTemplateId, bool softPaused) external onlyManager {
        GameTemplate storage gameTemplate = gameTemplates[gameTemplateId];
        gameTemplate.softPaused = softPaused;
    }
   
    /// @dev Add or replace a payout distribution, restricted to managers
    /// @param minPlayers Minimum number of players for this distribution
    /// @param maxPlayers Maximum number of players for this distribution
    /// @param percentage Percentage distribution, [80,10] = 80% to 1:st place, 10% to 2:nd place, 10% to ChallengeGG as comission
    /// @param isSponsored True if sponsored distribution 
    function setPayoutDistribution(uint16 minPlayers, uint16 maxPlayers, uint8[] memory percentage, bool isSponsored) external onlyManager {        
        require(percentage.length>0, "Distribution is empty");
        require(minPlayers>0, "Min players must be greater than 0");
        require(maxPlayers>=minPlayers, "Max players must be greater than or equal to min players");

        // Ensure valid distribution
        uint256 totalPercentage;
        for (uint256 i = 0; i < percentage.length; i++) {
            totalPercentage += percentage[i];
        }
        require(totalPercentage <= 100, "Total percentage exceeds 100");
        
        
        bool _replaced;
        PrizeDistribution[] storage prizeDistribution = prizeDistributions[isSponsored];

        for (uint8 i = 0; i < prizeDistribution.length; i++) {
            if(prizeDistribution[i].minPlayers==minPlayers && prizeDistribution[i].maxPlayers==maxPlayers){
                prizeDistribution[i].distribution=percentage;
                _replaced=true;
            }
        }

        if(!_replaced){                
               //uint8[] memory base;
               prizeDistribution.push(PrizeDistribution(minPlayers, maxPlayers, percentage));
               //prizeDistribution[prizeDistribution.length-1].distribution=percentage;
        }
    }

    /// @dev Delete a payout distribution, restricted to managers
    /// @param minPlayers Minimum number of players for this distribution
    /// @param maxPlayers Maximum number of players for this distribution
    /// @param isSponsored True if sponsored distribution should be deleted
    function deletePayoutDistribution(uint16 minPlayers, uint16 maxPlayers, bool isSponsored) external onlyManager {

        PrizeDistribution[] storage prizeDistribution = prizeDistributions[isSponsored];

        for (uint8 i = 0; i < prizeDistribution.length; i++) {
            if(prizeDistribution[i].minPlayers==minPlayers && prizeDistribution[i].maxPlayers==maxPlayers){
                delete prizeDistribution[i];
            }
        }
    }

    /// @dev Set if player is blocked from participating in challenges
    /// @param player Address of player
    /// @param blocked True if player should be blocked
    function setBlocked(address player, bool blocked) external onlyManager {
        isBlocked[player]=blocked;
    }


    //View functions    

    /// @dev Checks if a ERC20 token is valid to use in Challenge.gg.
    /// @param tokenAddress Token address to check
    /// @return valid True if token is valid
    function isValidToken(address tokenAddress) external view returns(bool){
        return validTokens[tokenAddress].valid;
    }

    /// Returs the commission threshold for a token, as soon as commision for token is above theshold we send commission to split contract
    /// @param tokenAddress Token address to check
    /// @return commission_threshold Commission threshold
    function getValidTokenThreshold(address tokenAddress) external view returns(uint256){
        return validTokens[tokenAddress].commission_threshold;
    }

    /// @dev Returns the current challenge id
    /// @return challengeId Current challenge id
    function getChallengeId() external view returns(uint256) {
        return _challengeId;
    }

    /// @dev Returns the current challenge id for a game template
    /// @param gameTemplateId Game template id
    /// @return challengeId Current challenge id
    function getCurrentId(uint16 gameTemplateId) external view returns(uint256) {
        return _lastestIds[gameTemplateId];
    }

    /// Returns the game template for a id
    /// @param gameTemplateId Game template id
    /// @return gameTemplate Game template
    function getGameTemplate(uint16 gameTemplateId) external view returns(GameTemplate memory) {
        return gameTemplates[gameTemplateId];
    }

    /// Checks if a template is paused (players can not create or join challenges of this type)
    /// @param gameTemplateId Game template id
    /// @return paused True if template is paused
    function isTemplatePaused(uint16 gameTemplateId) external view returns (bool paused)  {
        GameTemplate storage gameTemplate = gameTemplates[gameTemplateId];
        return gameTemplate.paused;
    }

    /// Checks if template is soft paused (players can join existing challenges but not create new ones)
    /// @param gameTemplateId Game template id
    /// @return softPaused True if template is soft paused
    function isTemplateSoftPaused(uint16 gameTemplateId) external view returns (bool softPaused)  {
        GameTemplate storage gameTemplate = gameTemplates[gameTemplateId];
        return gameTemplate.softPaused;
    }

    /// Returns the payout distribution for a number of players
    /// @param players Number of players
    /// @param isSponsored True if sponsored distribution should be returned
    /// @return minPlayers Minimum number of players for this distribution
    /// @return maxPlayers Maximum number of players for this distribution
    /// @return percentage Percentage distribution, [80,10] = 80% to 1:st place, 10% to 2:nd place, 10% to ChallengeGG as comission
    /// @return commission Commission for this distribution
    function getPayoutDistribution(uint16 players, bool isSponsored) external view returns(uint16 minPlayers, uint16 maxPlayers, uint8[] memory percentage,uint8 commission) {

        PrizeDistribution[] storage prizeDistribution = prizeDistributions[isSponsored];
        for (uint8 i = 0; i < prizeDistribution.length; i++) {
            if(prizeDistribution[i].minPlayers<=players && prizeDistribution[i].maxPlayers>=players){

                uint8 sum=0;
                for(uint j=0; j< prizeDistribution[i].distribution.length; j++){
                        sum=sum+prizeDistribution[i].distribution[j];
                }
                uint8 _commission= 100-sum;

                return (prizeDistribution[i].minPlayers,prizeDistribution[i].maxPlayers, prizeDistribution[i].distribution,_commission);
            }
        }
      
    }

    /// @dev Checks if the account belongs to the admin role.
    /// @param account Address to check
    /// @return True if account belongs to 'ADMIN' role
    function isAdmin(address account) public view returns (bool)
    {
        return hasRole(DEFAULT_ADMIN_ROLE, account);
    }

    /// @dev Checks if the account belongs to the contract role.
    /// @param account Address to check
    /// @return True if account belongs to 'CONTRACT' role
    function isContract(address account) public view returns (bool)
    {
        return hasRole(CONTRACT_ROLE, account);
    }

    /// @dev Checks if the account belongs to the manager role.
    /// @param account Address to check
    /// @return True if account belongs to 'MANAGER' role
    function isManager(address account) public view returns (bool)
    {
        return hasRole(MANAGER_ROLE, account);
    }

    /// @dev Checks if the account belongs to the payer role.
    /// @param account Address to check
    /// @return True if account belongs to 'PAYER' role
    function isPayer(address account) public view returns (bool)
    {
        return hasRole(PAYER_ROLE, account);
    }

    function _setGameTemplate(uint16 gameTemplateId, uint16 maxPlayersCount, uint256 fee, address token, uint256 prizeFund, address prizeFundToken) private {
        require(validTokens[token].valid, "Invalid ERC20 token");

        if(prizeFund > 0) {
            require(validTokens[prizeFundToken].valid, "Invalid ERC20 prize fund token");
        }

        GameTemplate storage gameTemplate = gameTemplates[gameTemplateId];
        gameTemplate.maxPlayersCount = maxPlayersCount;
        gameTemplate.fee = fee;
        gameTemplate.token = IERC20(token);
        gameTemplate.prizeFund = prizeFund;
        gameTemplate.prizeFundToken = IERC20(prizeFundToken);

    }
}
       

```

{% endcode %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://whitepaper.challenge.gg/tokenomics/contracts/base-contract.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
