Tokenomics: Conducting a Crowdsale using a Zero Decimal ERC20 token

Hi there. I am currently writing smart contracts for someone who wishes to conduct a crowdsale of ERC884 tokens.

ERC884 is a standard devised for Delaware Amendment Compliance for keeping company stock ledgers on a blockchain. You can technically perform an STO by offering a crowdsale of ERC884 tokens.

The main sticking point here is that the ERC884 token strictly has zero decimals.

The group performing their sale wishes to have their token sold at a price of $0.12 USD per token, which roughly amounts to 846753500847432 wei (0.000846753500847432 ETH). They also wish to have a cap of 3.15 billion tokens.

By these parameters, I simply am unable to figure out a way to properly calculate the token math, without scaling every number up by a constant. I believe this is related to fixed point arithmetics.

If tokenAmount(_weiAmount) = _weiAmount.mul(rate):

I want tokenAmount(846753500847432) = 1

which means rate must be 1/846753500847432.

But this cannot be represented in solidity without multiplying by a constant, thereby diluting the token supply.

My question here is, what is the best way to approach this in a smart contract, given Solidity’s constraints?

2 Likes

Hi,

I'm not sure if this is actually related, but I saw something familiar a few time ago.

2 Likes

Hello @sharad-s, welcome to the forum!

Your analysis is indeed correct (and interesting!). An exchange rate between tokens and ether can be expressed in two ways, such that you need to either multiply or divide by it (that is token = ether * rateMul or token = ether / rateDiv). It follows that rateMul = 1/rateDiv.

Now, when dealing with real numbers, there’s no distinction between these two approaches, but that isn’t the case for integer arithmetic. The main issue is that integer division performs truncation and is therefore usually avoided as much as possible, hence our usage of multiplying rates in Crowdsale. And, as you’ve noticed, the way of achieving a wide range of exchange rates is using multiple decimal places (similar to what fixed-point does).

For your particular scenario, where changing decimals is a non-option, you could do the following: inherit from Crowdsale as usual, but override the _getTokenAmount function, so that it uses a dividing rate (which in your case will be equal to 846753500847432):

contract ERC884Crowdsale is Crowdsale {
  function _getTokenAmount(uint256 weiAmount) internal view returns (uint256) {
    return weiAmount.div(_rate);
  }
}

This obviously has the problem of truncation, the remainder of dividing by _rate is lost, but you could send those back to the sender. The main issue here is that _processPurchase doesn’t receive the weiAmount, only the tokens, so you’d have to recalculate that from msg.value (which may, but shouldn’t, cause problems, depending on your inheritance tree and other overrides):

contract ERC884Crowdsale is Crowdsale {
  function _getTokenAmount(uint256 weiAmount) internal view returns (uint256) {
    return weiAmount.div(_rate);
  }

  function _processPurchase(address beneficiary, uint256 tokenAmount) internal {
    super._processPurchase(beneficiary, tokenAmount);

    remainder = (msg.value).mod(_rate);
    (msg.sender).transfer(remainder);
  }
}

An easier way would be to simply revert in _preValidatePurchase if weiAmount is not a multiple of _rate (i.e. require(weiAmount.mod(_rate) == 0)).

@frangio I’d like to hear your thoughts on this.

3 Likes

Thank you @nventuro for your insight. I did end up overriding function _getTokenAmount to return weiAmount.div(_rate) and that works. However, currently the remainder up to 12_CENTS_IN_WEI is simply consumed by the contract.

I reasoned that the truncation is not too much of an issue since you would never lose more than $0.12 in wei. Of course this is ethically grey but is a leap of assumption which makes the implementation clear and easy to use.

What are your thoughts on this approach? I do not think it would be difficult to implement the overriding of _processPurchase as you outlined if necessary.

Thank you !

I think this the right trade-off: when transferring back to the sender you open the door for re-entrancy, etc. (though buyTokens is nonReentrant), so only altering _getTokenAmount sounds perfect. I'd only be wary of the possibility of the price going up, and users maybe losing more than just those $0.12 per purchase.

in the context of this token sale, the price will be a constant weiAmount ($0.12 set at instantiation). therefore no price fluctuations will occur.

thank you very much your help here :slight_smile: I hope this thread will serve some benefit to others in the future

3 Likes

@sharad-s I'm curious, did you consider adding the following constraint that @nventuro suggested? I'm wondering if such a thing would be okay when the requirements are as strict as they seem to be in your case.


Alternatively, the remainder can be put aside for the buyer to retrieve later using PullPayment. But this might not be cost effective given the cap of $0.12.

@frangio I did end up changing the _getTokenAmount to use .div instead of .mul.

I had preconceived notions against using the .div operation before @nventuro validated that it is possible, and the Crowdsale now works as expected with the Zero Decimal token.

Also, I just let the Crowdsale consume the remainder in an effort to keep Crowdsale mechanics simple for investors, as no single investor would lose more than 0.00069383 ETH.

I wonder if this sort of situation has risen before in previous token sales?

1 Like