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

ERC20 Snapshot Impl #2 #1617

Merged
merged 14 commits into from
Feb 6, 2019
Merged

Conversation

mswezey23
Copy link
Contributor

Fixes #1209

Note: due to vast changes within OZ's repo, I wiped out my own and re-forked to start clean versus tackling many different rebase patches, etc...

Introduces a working ERC20Snapshot token that is modular, per OZ standard. ERC20Snapshot is compatible with mint, burn, and burnFrom.

This implementation will fail early. Meaning if the transfer fails normal ERC20 methods, no extra gas was wasted in attempts to write to storage to create the snapshot.

Another feature is that it tracks totalSupplyAt, which is very much needed for proper dividend calculations.

API wise, it adds the following two functions:

balanceOfAt(address owner, uint blockNumber)
totalSupplyAt(uint blockNumber) 

✅- reviewed the OpenZeppelin Contributor Guidelines
✅- added tests where applicable to test new functionality,
✅- made sure that your contracts are well-documented, and
✅- run the JS/Solidity linters and fixed any issues (npm run lint:fix).

@mswezey23 mswezey23 changed the title ✏️ Refactor code & Refork OZ Repo ERC20 Snapshot Impl #1398 Jan 21, 2019
@mswezey23 mswezey23 changed the title ERC20 Snapshot Impl #1398 ERC20 Snapshot Impl Jan 21, 2019
@mswezey23 mswezey23 changed the title ERC20 Snapshot Impl ERC20 Snapshot Impl #2 Jan 21, 2019
@nventuro nventuro added this to the v2.2 milestone Jan 22, 2019
@nventuro nventuro added feature New contracts, functions, or helpers. contracts Smart contract code. labels Jan 22, 2019
@nventuro nventuro self-assigned this Jan 22, 2019
@nventuro nventuro requested a review from frangio January 22, 2019 20:18
@nventuro
Copy link
Contributor

Hey there @mswezey23! Due to timing constraints I took the liberty of changing your code a bit so that it'd fit better with what we had in mind. I really like the addition of snapshotting the total supply, thanks for the idea! I also wanted to keep id and value inside a Snapshot struct, but sadly that would've prevented us from using Arrays.findUpperBound, which works on plain arrays.

Copy link
Contributor

@frangio frangio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM overall. But please remove .DS_Store files.

CHANGELOG.md Outdated Show resolved Hide resolved
contracts/token/ERC20/ERC20Snapshot.sol Outdated Show resolved Hide resolved
contracts/token/ERC20/ERC20Snapshot.sol Outdated Show resolved Hide resolved
test/token/ERC20/ERC20Snapshot.test.js Outdated Show resolved Hide resolved
@nventuro nventuro force-pushed the erc20snapshot-fix#1209 branch from da36463 to 5fb6fea Compare January 23, 2019 17:20
@nventuro
Copy link
Contributor

nventuro commented Jan 23, 2019

Force-pushed after a rebase to get the newly added Counters library.

@nventuro nventuro requested a review from frangio January 23, 2019 17:23
frangio
frangio previously approved these changes Jan 23, 2019
Copy link
Contributor

@frangio frangio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@mswezey23
Copy link
Contributor Author

mswezey23 commented Jan 24, 2019

@nventuro Hey thanks for the feedback! Glad you liked the snapshotting of the total supply.

I had a question why you prefer the snapshot be taken before the super._transfer() call?

I've noticed in other implementations (MinimeToken for one) that they, too, took the snapshot before the balance change occurred.

My logic is this:
When one queries block 100. Does one really want to get the balance that was the result of block 99? Or does one want the true balance of the result of block 100?

Granted you've changed to on demand ids now versus blocknumber/timestamp but the argument remains the same.

Thanks :)

@frangio
Copy link
Contributor

frangio commented Jan 25, 2019

@mswezey23 I agree that if snapshots are per-block then taking the snapshot after the changes feels more natural. However, I can see an argument for taking the snapshot before the changes: all queries in the same block can be made to return the same value (the value before the entire block was executed). Otherwise, a transaction early in a block might return value X and another one later in the same block might return value X+n if there is a transfer in between. I can see this causing bugs. I don't know if MiniMe works like this, though.

Since we changed it to on-demand snapshot ids, we now need to store the balances corresponding to the last snapshot before they change in a transfer, because that last snapshot was in fact requested before the transfer.

Does this explain?

@mswezey23
Copy link
Contributor Author

ll queries in the same block can be made to return the same value (the value before the entire block was executed). Otherwise, a transaction early in a block might return value X and another one later in the same block might return value X+n

Ah! Thank you. I've been looking for a good argument not to do it the way I implemented (minus the extra gas savings).

I will say, IIRC, the way I originally implemented the code the after-balance snapshot would update for every N transactions that occurred in the block since snapshots were done automatically. (Incurring extra gas costs per TX) So that was a none issue feature wise as the query would be accurate no matter how many transactions involved the same account occurring in that block.

        if (
            (checkpoints.length == 0) ||
            (checkpoints[checkpoints.length.sub(1)].fromBlock < block.number)
        ) {
            checkpoints.push(Snapshot(uint128(block.number), uint128(value)));
        } else {
            checkpoints[checkpoints.length.sub(1)].value = uint128(value);
        }

But since you're proceeding with an on-demand snapshot that only takes 1 snapshot for each account (much more gas friendly); you're forced to take the balance beforehand otherwise, as you already pointed out, any transactions afterwards involving the same account(s) included in the same block would cause the balance "snapped" to be inaccurate.

Thank you for your additional insight :)

frangio
frangio previously approved these changes Feb 6, 2019
Copy link
Contributor

@frangio frangio left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @mswezey23 and @nventuro!

@frangio frangio merged commit 40d1514 into OpenZeppelin:master Feb 6, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
contracts Smart contract code. feature New contracts, functions, or helpers.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

ERC20 token with snapshots
3 participants