How to Set Default Cap on IndividuallyCappedCrowdsale?

I am trying to implement a default cap on every beneficiary that sends ETH to the sale contract. By default i have to set cap manually on each address, which is not usable.
Also i am using CappedCrowdsale to set a cap on whole crowdsale.

Sale Contract code:

pragma solidity ^0.5.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/validation/PausableCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/validation/CappedCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/validation/IndividuallyCappedCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/emission/MintedCrowdsale.sol";


contract BOTSale is PausableCrowdsale, CappedCrowdsale, IndividuallyCappedCrowdsale, MintedCrowdsale {

constructor (
    uint256 rate,
    address payable wallet,
    IERC20 token,
    uint256 cap,
    ) 
    public 
    Crowdsale(_rate, _wallet, _token)
    CappedCrowdsale(cap)
     {
        // solhint-disable-previous-line no-empty-blocks
    }
}

I am assuming it has something to do with PreValidatePurchase function? Also how would i add “cap” for individuals in the constructor. Please simplify it a bit as i am still a novice in all this.

1 Like

Hi @Farhan_W,

If you want to add a default individual cap as well as being able to set a cap on specific individuals then you could extend IndividuallyCappedCrowdsale to:

  • add a default individual cap state variable and a function to set it
  • override getCap(address beneficiary) to return the default individual cap if an individual cap isn’t set
  • override _preValidatePurchase to check if an individaul cap is set, otherwise to use the default individual cap.

OK i have done something like this:

pragma solidity ^0.5.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/validation/PausableCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/validation/CappedCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/validation/IndividuallyCappedCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/emission/MintedCrowdsale.sol";


contract BOTSale is PausableCrowdsale, CappedCrowdsale, IndividuallyCappedCrowdsale, MintedCrowdsale {

uint256 public individualCap;

event personalCapRaised(address _beneficiary, uint256 newCap);
constructor (
    uint256 rate,
    address payable wallet,
    IERC20 token,
    uint256 cap,
    uint256 individualCap,
    ) 
    public 
    Crowdsale(_rate, _wallet, _token)
    CappedCrowdsale(cap)
     {
         individualCap = _individualCap
    }
    function approvePersonalCap(address _beneficiary) internal returns (uint256){
        if (getCap(_beneficiary) <= 0) {
            this.setCap(_beneficiary, individualCap);
            emit personalCapRaised(_beneficiary, individualCap);
        }
        return getCap(_beneficiary);
    }
    // A function that sets user cap, if never contributed
    function _preValidatePurchase(
        address _beneficiary,
        uint256 _weiAmount
    )
        internal view
    {
        require(approvePersonalCap(_beneficiary) > 0);
        super._preValidatePurchase(_beneficiary, _weiAmount);
    }
}

But i am getting compile error on remix on “require(approvePersonalCap(_beneficiary) > 0);”

Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable.
1 Like

Hi @Farhan_W,

The compiler error is because you are changing state in a view function which you cannot do.
When you override _preValidatePurchase you should only check that the contributions are under the cap and not change any state.

It may be easier to duplicate the functionality in IndividuallyCappedCrowdsale due to the logic in _preValidatePurchase as overriding and calling super would call this logic.

You could do something like the following for example.

:warning: The following code has not been tested and not been audited. Code used in production should be appropriately tested and audited.

pragma solidity ^0.5.0;

import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/validation/PausableCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/validation/CappedCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/crowdsale/emission/MintedCrowdsale.sol";
import "https://github.com/OpenZeppelin/openzeppelin-contracts/blob/v2.5.1/contracts/access/roles/CapperRole.sol";


contract BOTSale is PausableCrowdsale, CappedCrowdsale, MintedCrowdsale, CapperRole {

    using SafeMath for uint256;

    mapping(address => uint256) private _contributions;
    mapping(address => uint256) private _caps;
    
    uint256 private _individualDefaultCap;

constructor (
    uint256 rate,
    address payable wallet,
    IERC20 token,
    uint256 cap,
    uint256 individualCap
    ) 
    public 
    Crowdsale(rate, wallet, token)
    CappedCrowdsale(cap)
    {
         _individualDefaultCap = individualCap;
    }

    /**
     * @dev Sets a specific beneficiary's maximum contribution.
     * @param beneficiary Address to be capped
     * @param cap Wei limit for individual contribution
     */
    function setCap(address beneficiary, uint256 cap) external onlyCapper {
        _caps[beneficiary] = cap;
    }

    /**
     * @dev Returns the cap of a specific beneficiary.
     * @param beneficiary Address whose cap is to be checked
     * @return Current cap for individual beneficiary
     */
    function getCap(address beneficiary) public view returns (uint256) {
        uint256 cap = _caps[beneficiary];
        if (cap == 0) {
            cap = _individualDefaultCap;
        }
        return cap;
    }

    /**
     * @dev Returns the amount contributed so far by a specific beneficiary.
     * @param beneficiary Address of contributor
     * @return Beneficiary contribution so far
     */
    function getContribution(address beneficiary) public view returns (uint256) {
        return _contributions[beneficiary];
    }

    /**
     * @dev Extend parent behavior requiring purchase to respect the beneficiary's funding cap.
     * @param beneficiary Token purchaser
     * @param weiAmount Amount of wei contributed
     */
    function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal view {
        super._preValidatePurchase(beneficiary, weiAmount);
        // solhint-disable-next-line max-line-length
        require(_contributions[beneficiary].add(weiAmount) <= getCap(beneficiary), "BOTSale: beneficiary's cap exceeded");
    }

    /**
     * @dev Extend parent behavior to update beneficiary contributions.
     * @param beneficiary Token purchaser
     * @param weiAmount Amount of wei contributed
     */
    function _updatePurchasingState(address beneficiary, uint256 weiAmount) internal {
        super._updatePurchasingState(beneficiary, weiAmount);
        _contributions[beneficiary] = _contributions[beneficiary].add(weiAmount);
    }
}

Wonderful. It works perfectly. Thank You

1 Like

Hi @Farhan_W,

Glad to help.

Please remember that the code above has not been tested and not been audited.
Code used in production should be appropriately tested and audited.