Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

subsequent calls of chai.dai() display that tokens lacking #12

Open
akolotov opened this issue Apr 1, 2020 · 6 comments
Open

subsequent calls of chai.dai() display that tokens lacking #12

akolotov opened this issue Apr 1, 2020 · 6 comments

Comments

@akolotov
Copy link

akolotov commented Apr 1, 2020

We are observing a strange situation with drawing tokens from the chai contract.

Here is the transaction: https://etherscan.io/tx/0xafde98025b80382855871045b81570dba5b833cfeb3c411d99617dee4d3ea627

The logs of the transactions represents that 100010000000000000000 Dai was minted to 0x4aa42145aa6ebf72e164c9bbc74fbd3788045016, so I would expect that the Dai balance invested in Chai will differ on this value.

But if you check the value returned by chai.dai() before this transaction and after it you will check that the difference is more than 100.01 Dai.

Firstly, let's get the value for the block 9780635:

curl https://url.to.rpc.node -X POST -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"eth_call","params": [{"to":"0x06AF07097C9Eeb7fD685c692751D5C66dB49c215","data":"0x6c25b3460000000000000000000000004aa42145aa6ebf72e164c9bbc74fbd3788045016"}, "0x953d9b"],"id":1}'

returns

{"jsonrpc":"2.0","result":"0x00000000000000000000000000000000000000000000088b0be7d408ff70d3f7","id":1}

Next, - for the block 9780637:

curl https://url.to.rpc.node -X POST -H "Content-Type: application/json" \
    -d '{"jsonrpc":"2.0","method":"eth_call","params": [{"to":"0x06AF07097C9Eeb7fD685c692751D5C66dB49c215","data":"0x6c25b3460000000000000000000000004aa42145aa6ebf72e164c9bbc74fbd3788045016"}, "0x953d9d"],"id":1}'

returns

{"jsonrpc":"2.0","result":"0x0000000000000000000000000000000000000000000008859ffceee92c9fd3f6","id":1}

The difference between 0x88b0be7d408ff70d3f7 and 0x8859ffceee92c9fd3f6 is 100010000000000000001 (0x56beae51fd2d10001) rather than 100010000000000000000 as expected.

The issue here is that with DSR=0 we would expect that the Dai balance invested to Chai is not reduced. We are building logic of our contracts relying on this statement and see that our transactions are being failed since it is not true: https://etherscan.io/tx/0x236ea5de2c8957c9706f78f2f0347feb9b5b2757a59c285bfcda9ff008aeb178.

@akolotov
Copy link
Author

akolotov commented Apr 1, 2020

The same is applicable for the transaction: https://etherscan.io/tx/0x3cf2c78ebcec110092422a7b2333f5ba962a0959d27a3edd0057e616e5219054

So, with these two transactions the balance leaks for 0.000000000000000002 Dai.

@livnev
Copy link
Member

livnev commented Apr 1, 2020

@akolotov Unfortunately, it's not possible to go in and out of the DSR without incurring a rounding error. This is because DAI balances and Pot balances are related by the equation:

dai = pie * chi

where dai and pie have are 18 decimal fixed point integers, and chi is a 27 decimal fixed point integer (which is changing in time). Since we are in a fixed-precision setting, whenever the DAI amount isn't perfectly divisible by chi, there will be no corresponding pie amount without a rounding error. In other words, if you have X DAI, and you want to put them into DSR, you will either have to put slightly less than X in, or you can put all of X in but lose the remainder dust X mod chi.

The join interface to CHAI opts to take all of the tokens specified as the second argument, losing the remainder to dust:

    // wad is denominated in dai
    function join(address dst, uint wad) external {
        uint chi = (now > pot.rho()) ? pot.drip() : pot.chi();
        uint pie = rdiv(wad, chi);
        balanceOf[dst] = add(balanceOf[dst], pie);
        totalSupply    = add(totalSupply, pie);

        daiToken.transferFrom(msg.sender, address(this), wad);
        daiJoin.join(address(this), wad);
        pot.join(pie);
        emit Transfer(address(0), dst, pie);
    }

The specific step where the down-rounding occurs is when we compute uint pie = rdiv(wad, chi); (rdiv is down-rounding fixed-point division). The dust that is shaved off will remain as a DAI balance in the CHAI contract, forever irretrievable. The maximum rounding error per join is 1 wei of DAI.

Therefore, you should not assume that after joining and exiting that you will have no fewer tokens than when you started. Let me know if you have any other questions.

@akolotov
Copy link
Author

akolotov commented Apr 1, 2020

thanks @livnev for explanation!

In general, this approach is OK since it is assumed that DSR>0 and (pie * chi) - invested dai will always be more than zero bearing in mind that chi is being increased from one block to another. Am I right?

But having DSR=0 exposes its weakness. At the moment, it was decided to set DSR to zero the way to calculate pie should be changed as so wad = (wad / chi) * chi. Don't you think so?

@k1rill-fedoseev
Copy link

k1rill-fedoseev commented Apr 1, 2020

Also, I would like to clarify one more moment.
You mentioned that the maximum rounding error is 1 wei of DAI, however, seems to me that this is not true.
Consider the following situation:

chi = 2 * 10^27 + 1
wad = 10^18

pie = rdiv(wad, chi) = floor(10^18 * 10^27 / (2 * 10^27+1))

chai.dai(address me) = rmul(chi, pie) = floor(pie * (2 * 10^27+1) / 10^27) = 10^18 - 2

So, the loss actually depends on parameter chi. It is not bounded by 1 wei.
While chi is less than 2 * 10^27, the loss will be at most 1, indeed. However, it will increase further in the future.

Please, correct me, if I am wrong.

@livnev
Copy link
Member

livnev commented Apr 1, 2020

@k1rill-fedoseev apologies, what I said about the error being bounded at 1 wei of DAI was false. I should have said that it is bounded at 1 wei of pie, which translates into a bound of chi / 10^27 wei of DAI (chi viewed as an integer). Your example calculation demonstrating this is correct.

@livnev
Copy link
Member

livnev commented Apr 1, 2020

thanks @livnev for explanation!

In general, this approach is OK since it is assumed that DSR>0 and (pie * chi) - invested dai will always be more than zero bearing in mind that chi is being increased from one block to another. Am I right?

Well, as I said, I'm not sure that it's a good idea to assume that if you lock tokens in CHAI, and then withdraw them, that you will end up with no fewer tokens than what you started with.

But having DSR=0 exposes its weakness. At the moment, it was decided to set DSR to zero the way to calculate pie should be changed as so wad = (wad / chi) * chi. Don't you think so?

To be clear, are you proposing here to pull less DAI than requested? As I mentioned above, that is another possible way of doing things, but then risks leaving DAI dust in the user's account (which could similarly create accounting problems).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants