Help wanted with tranche based bonus in a crowdsale

Does anybody know a good way of creating a tranche based bonus as opposed to a time based bonus for a crowdsale? I’m looking to create a crowdsale that starts at a certain price per token but the price increases by .05% depending on how many tokens have been sold so far.

2 Likes

Hi @Roodiger welcome to the community :wave:

You can take inspiration from the IncreasingPriceCrowdsale.sol

You could override the _getTokenAmount function of Crowdsale to use the current rate and then have a function getCurrentRate which has your rate calculation based on how many tokens have been sold.

I have created an example crowdsale contract below. The getCurrentRate function would need to be updated to include the rate calculation you want to use. Currently it just multiplies the _initialRate by one.

I recommend that any such contract be fully tested and audited.
For testing, I suggest using the following guide:

pragma solidity ^0.5.0;

import "openzeppelin-solidity/contracts/crowdsale/Crowdsale.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";

/**
 * @title MyCrowdsale
 * @dev This is a crowdsale.
 */
contract MyCrowdsale is Crowdsale {

    using SafeMath for uint256;

    uint256 private _initialRate;

    constructor (
        uint256 initialRate,
        address payable wallet,
        IERC20 token
    )
        public
        Crowdsale(initialRate, wallet, token) {
        _initialRate = initialRate;
    }

    /**
     * The base rate function is overridden to revert, since this crowdsale doesn't use it, and
     * all calls to it are a mistake.
     */
    function rate() public view returns (uint256) {
        revert("MyCrowdsale: rate() called");
    }

    /**
     * @return the initial rate of the crowdsale.
     */
    function initialRate() public view returns (uint256) {
        return _initialRate;
    }

    /**
     * @dev Returns the current rate of tokens per wei.
     * @return The number of tokens a buyer gets per wei
     */
    function getCurrentRate() public view returns (uint256) {
        // calculate the current rate
        return _initialRate.mul(1);
    }

    /**
     * @dev Overrides parent method taking into account variable rate.
     * @param weiAmount The value in wei to be converted into tokens
     * @return The number of tokens _weiAmount wei will buy at present time
     */
    function _getTokenAmount(uint256 weiAmount) internal view returns (uint256) {
        uint256 currentRate = getCurrentRate();
        return currentRate.mul(weiAmount);
    }
}
1 Like

Thank you so much for the reply! I already use Truffle for testing, but thanks for sending the guide over, that will be helpful.

I’m new to programming tokens. I come from Java development, and have taken a keen interest in Solidity, Ethereum, and tokens. I’m just having some troubles with getting all of the pieces I know to fit together.

For that contract you wrote, how would I specify how often the rate changes? I see where I can choose how to multiply the rate, but I’m not sure how to, say, change the rate for every 50,000 tokens that get sold. So the first rate would be .01 for a token and after 50,000 get sold it goes to 0.0105 per token etc.

1 Like

Hi @Roodiger

Welcome to developing token smart contracts.

Are you developing for a specific project or are you learning? Feel free to ask all the questions that you need.

The OpenZeppelin documentation is great for reading up on some of the different options for tokens and crowdsales

I think the testing guide is really useful, and it is great to get code coverage and continuous integration.


For the rate change algorithm it really depends on what you want to do.

You could change the rate at fixed intervals of tokens, so every X tokens sold (or wei raised) you change the rate or you could calculate based on the amount of tokens sold (or wei raised) as is done in IncreasingPriceCrowdsale.

    function getCurrentRate() public view returns (uint256) {
        if (!isOpen()) {
            return 0;
        }

        // solhint-disable-next-line not-rely-on-time
        uint256 elapsedTime = block.timestamp.sub(openingTime());
        uint256 timeRange = closingTime().sub(openingTime());
        uint256 rateRange = _initialRate.sub(_finalRate);
        return _initialRate.sub(elapsedTime.mul(rateRange).div(timeRange));
    }

How simple or complex you want to make it is up to you. :smile:

1 Like

Hey @abcoathup

Thank you!

A bit of both. It’s for a project, but it’s more so of a learning project for me.


Yeah more like the first one. I just want to make it so that every 50,000 tokens sold changes the rate by .05%.

Most of the places I looked showed bonuses by time instead of by token supply bought already.

Where do I call the function in order for it to work? Like, I think what I’m not connecting is where in the contract do I call getCurrentRate(). I understand that this is what multiplies the rate when it is called, but when do I call it? In the “MyCrowdsale.sol”? Or does it get called in migrations?

1 Like

A participant in your crowdsale either sends Ether to your contract or calls the buyTokens function sending Ether.

buyTokens calls the function _getTokenAmount in MyCrowdsale that overrides the function in Crowdsale.

_getTokenAmount then calls the getCurrentRate function which has your algorithm for calculating what the rate is.

When you deploy MyCrowdsale using Truffle migrations you specify the initial rate (or you could hard code the initial rate).

Back in buyTokens the rest of the sale functionality occurs now that the tokenAmount has been calculated.

Let me know if you need more information.

1 Like

Is there a simple way to make the algorithm so that the rate multiplication happens every time 50,000 more tokens gets sold? Instead of having to manually make the tiers? Like, is there a way to see how many tokens have been sold so far and only multiply the rate if it’s been 50,000 more than last time it raised the rate?

1 Like

Hi @Roodiger it depends on your chosen token emission. There is lots of flexibility. :smile:

For example, if you used default emission, then you could use the balance of tokens controlled by the contract subtracted from the total supply to calculate how many were sold, otherwise you might need a private state variable to track the tokens sold e.g. something like _tokensSold, similar to _weiRaised.

In your crowdsale you could override _processPurchase to update this state.

    /**
     * @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send
     * tokens.
     * @param beneficiary Address receiving the tokens
     * @param tokenAmount Number of tokens to be purchased
     */
    function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
        // update state
        _tokensSold = _tokensSold.add(tokenAmount);

        super._processPurchase(beneficiary, tokenAmount);
    }
1 Like

So what I have so far is kind of a wreck. I can’t seem to get it right quite yet. Where exactly do I send the crowdsale the tokens? And how do I go about that? I know that you use the transfer function, but just can’t seem to get it right.

This is what I have so far. I have my crowdsale and my crowdsale test file.

EduchainCrowdsale.sol

pragma solidity ^0.5.0;



import "openzeppelin-solidity/contracts/crowdsale/Crowdsale.sol";

import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";



/**

 * @title EduChainCrowdsale

 * @dev This is a crowdsale.

 */

contract EduChainCrowdsale is Crowdsale {



    using SafeMath for uint256;



    uint256 private _initialRate;

    uint256 private _tokensSold;

    uint256 private _currentTier = 50000;



    constructor (

        uint256 initialRate,

        address payable wallet,

        IERC20 token

    )

        public

        Crowdsale(initialRate, wallet, token) {

        _initialRate = initialRate;

    }



    /**

     * The base rate function is overridden to revert, since this crowdsale doesn't use it, and

     * all calls to it are a mistake.

     */

    function rate() public view returns (uint256) {

        revert("EduChainCrowdsale: rate() called");

    }



    /**

     * @return the initial rate of the crowdsale.

     */

    function initialRate() public view returns (uint256) {

        return _initialRate;

    }



    /**

     * @dev Returns the current rate of tokens per wei.

     * @return The number of tokens a buyer gets per wei

     */

    function getCurrentRate() public view returns (uint256) {

        // calculate the current rate
        if (_tokensSold >= _currentTier){

        return _initialRate.mul(2);
      } else{
        return _initialRate;
      }

    }



    /**

     * @dev Overrides parent method taking into account variable rate.

     * @param weiAmount The value in wei to be converted into tokens

     * @return The number of tokens _weiAmount wei will buy at present time

     */

    function _getTokenAmount(uint256 weiAmount) internal view returns (uint256) {

        uint256 currentRate = getCurrentRate();

        return currentRate.mul(weiAmount);

    }

    /**
  * @dev Executed when a purchase has been validated and is ready to be executed. Doesn't necessarily emit/send
  * tokens.
  * @param beneficiary Address receiving the tokens
  * @param tokenAmount Number of tokens to be purchased
  */
 function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
     // update state
     _tokensSold = _tokensSold.add(tokenAmount);

     if(_tokensSold >= _currentTier){
       _currentTier = _currentTier + 50000;
     }

     super._processPurchase(beneficiary, tokenAmount);
 }

}

And then my testing file:

EduChainCrowdsale.test.js

function ether(n) {
  return web3.utils.toWei(web3.utils.toBN(n), 'ether');
}

const EduChainCrowdsale = artifacts.require('EduChainCrowdsale');
const EduChain = artifacts.require('EduChain');

require('chai').should();

contract('EduChainCrowdsale', function([_, wallet, investor1, investor2]){

  beforeEach(async function(){
    this.totalSupply = 105000000
    this.token = await EduChain.new(this.totalSupply);


  console.log(this.token);

  this.rate = 500;
  this.wallet = wallet;



  this.crowdsale = await EduChainCrowdsale.new(
    this.rate,
    this.wallet,
    this.token.address,

  );


});

describe('crowdsale', function(){

  it('tracks the token', async function(){
    const token = await this.crowdsale.token();
    token.should.equal(this.token.address);
  });

  it('tracks the rate', async function(){
    const rate = (await this.crowdsale.initialRate()).toNumber();
    rate.should.equal(this.rate);
  });

  it('tracks the wallet', async function(){
    const wallet = await this.crowdsale.wallet();
    wallet.should.equal(this.wallet);
  });





});

describe('accepting payments', function(){
  it('should accept payments', async function() {
    const value = '1';
    await this.crowdsale.sendTransaction({value: value, from: investor1})
  })
})



});

What am I doing wrong? If this is too much for you to deal with I understand. I just think I’m missing some links to make this work.

1 Like

Hi @Roodiger

In your test, the line you were missing is, this goes after you have created your crowdsale:

    await this.token.transfer(this.crowdsale.address, this.totalSupply);

Please ask all the questions that you need. Everyone was new once (see top three recommendations for a new developer in the space).

In your contract, I suggest you use SafeMath so that the following: _currentTier = _currentTier + 50000; becomes: _currentTier = _currentTier.add(50000);

A good place to see how to test and interact with the contracts is the OpenZeppelin tests. e.g. Crowdsale.test.js

There is also a sample crowdsale and test that you can look at for reference.

I recommend (if you haven’t already) reading up on decimals and crowdsale rate, as these can be trickier topics without an explanation.

I also recommend looking at https://github.com/OpenZeppelin/openzeppelin-test-helpers to help in your tests.

1 Like

A post was split to a new topic: Help with rate testing for crowdsale

A post was split to a new topic: How to calculate rate for a crowdsale

Hi @Roodiger
Some housekeeping: Assuming my reply answered your question, can you mark it as the solution please.

I moved your other questions into new topics. It is best to have a new topic per separate question as that is easier for the community to answer and hence get you an answer quicker :smile:. It also makes it easier for someone in the future having the same question to find the answer :rocket:.