Why is my calculation broken?

I have a smart contract that holds a token balance, let's call it `Token A`, users may send this smart contract `Token B` to receive `Token A`, they do so by specifying the amount of `Token A` they would like to receive and the smart contract would automatically calculate the amount of `Token B` they will have to spend based on a pre-defined ratio.

The problem is, on the front-end, I would like the users to be able to specify the amount of `Token B` they would like to spend instead of the amount of `Token A` they would like to receive, so I create a function to convert the user input amount of `Token B` to `Token A`, it goes something like this:

``````function convertBtoA(uint amountB) returns (uint) {
return totalA.mul(amountB).div(totalB);
}
``````

The user submits the result of that function, then the smart contract converts the amount back to `Token B` to determine the payout using another function:

``````function convertAtoB(uint amountA) returns (uint) {
return totalB.mul(amountA).div(totalA);
}
``````

However, the result of this function would sometimes be slightly different from the original user input amount, for example:

``````# given totalA == 195738239776775570; totalB == 59744193591648150
convertBtoA(50000000000000000) # output == 163813609331349736
convertAtoB(163813609331349736) # output == 49999999999999999
``````

In the example above, the original user input is `50000000000000000` and the result of the conversion done by the smart contract is `49999999999999999`. My question is, is there a way to guarantee the conversion of `Token A` to `Token B` and back to `Token A` to result in the same number it starts with every time?

Note: I can't feed the user input amount of `Token B` directly to the contract without conversion to `Token A` because this contract is sometimes used to pay out `Token B` by receiving `Token A`, in which case, I would like the user input to be specified in `Token A`.

1 Answer

It is not posible for all cases unless totalA equals totalB in which case you will have amountB = amountA.

The division truncates the result discarding decimals digits, in your example the return value of convertBtoA should be 163813609331349736.27117520077928403.

For most use cases small rounding errors are accepted, for example ERC20 and Ether both have 18 decimals so an error of 1/10^18 is much less than the typical transaction fee.

• do you think a solution that detects the fraction and just + 1 to the final result would work? – Hieu Jul 21 at 10:06
• There's no way to detect that situation since the information is missing. Let's say you got 16381 how do you determine if it came from 49999 or 50000? – Ismael Jul 21 at 16:24