3

I'm implementing a fundraising contract where a user can create a fundraiser but when creating they must send a rewardingToken (ERC20 token) to the contract and the user also specifies a token which is accepted for the fundraiser, called acceptingToken, and the raise amount which is the number of acceptingToken that we are fundraising for.

acceptingToken can be any ERC20 token.

In this example, Lets assume the total supply of rewardingToken is 100,000 tokens. When creating a fundraiser the amount of rewardingToken deposited is 1000, the raise goal (i.e the number of acceptingToken we want to raise) is 100.

When the raise goal is met and an investor wants to withdraw the rewardToken then I calculate it by

                uint256 rewardTokens = ((rewardingTokenAmount) /
                (raise)) * amtInvestorInvested;

                IERC20(rewardingToken).transfer(
                    msg.sender,
                    rewardTokens
                )

The issue here is if there are decimal number which will end up not giving rewards in an accurate manner, which will cause users to get less than what they deserve.

I initially thought to multiply the rewardingTokenAmount by 1e18, to have 18 decimal points in accuracy. But then when sending the tokens, I will need to divide by 1e18 which would defeat the purpose of doing that.

Not sure what the best method for this

1 Answer 1

3

I'll set some other concerns aside to just focus on the question raised.

When working with integers that represent fixed decimal numbers you do indeed need to scale precision. In order to avoid loss of precision, perform multiplication before division.

Consider:

uint256 rewardTokens = rewardingTokenAmount * amtInvestorInvested / raise;

Precision/scale was raised by 1e18 (mul) and then reduced by 1e18 (div) so no further adjustment is necessary.

Also consider truncation of the last digit and what it means in your application. That is to say, account for "dust" and decide what to do with it whenever division is involved.

Hope it helps.

5
  • Thanks @Rob so should my reward reward tokens be calculated like uint256 rewardTokens = rewardingTokenAmount * amtInvestorInvested * 1e18 / raise * 1e18; ? Mar 7, 2023 at 9:38
  • You can remove 1e18 from both sides and still land on the same result. Mar 9, 2023 at 3:58
  • Thanks that totally makes sense, a final question - will this be the most accurate way to do such a division ? Mar 9, 2023 at 12:58
  • Multiplication before division. Account for "dust", a.k.a. the remainder (division truncates). If the remainder does not land naturally in the right place, then use a modulo to account for it and do something. As a rough heuristic, don't have money in the contract that isn't in someone's or something's "account". Mar 9, 2023 at 17:24
  • As an example of what you don't want, consider a contract that splits deposits into two accounts for Alice and Bob. The contract uses a simple mapping to record their balances/entitlements. Someone deposits an odd number, say 3 - 1 to Alice, 1 to Bob, and 1 unrecoverable from the contract unless care is taken to avoid overlooking the uneven division. Mar 9, 2023 at 17:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.