ERC20 Snapshot not usable for smart contracts

The current implementation of ERC20Snapshot exposes a function balanceOfAt that requires a snapshotId instead of _blockNumber as in the original minime token. As a consequence, these snapshots are not accessible by smart contracts which have no idea about the snapshot ID. Furthermore these external contracts also don’t have a way of looking for them as the _accountBalanceSnapshots are private.

Since this would enable a larger amount of on-chain governance projects to leverage this feature I would suggest to add a function which implements the binary search and takes a block number similar to minime.

1 Like

Hi @SCBuergel1,

Welcome to the community :wave:

Thanks for the feedback. How are you doing snapshots currently? Are you using ERC20Snapshot currently and extending it to include the functionality?

Hi @SCBuergel1,

I have created an Issue for this in the OpenZeppelin Contracts repository: https://github.com/OpenZeppelin/openzeppelin-contracts/issues/2460

@SCBuergel1 This is an interesting request. There's a couple of issues with using block numbers to identify snapshots for our contract.

First of all token transfers can happen many times in a block, so you need to define what a snapshot at a block number means: is it at the beginning of the block, the end, or some arbitrary point in the middle?

Secondly, our implementation of ERC20Snapshot does not have snapshots for every block, snapshots are instead taken on demand by a function call. This is a big difference with the minime token, but it makes for a vastly more efficient implementation. As a consequence, there may not be a snapshot for every block. An on-chain governance contract would need to know that there is a snapshot for a given block number, otherwise the proposal would fail when it tries to read the snapshot balances. If you will let the governance contract know that there is a snapshot for a given block, you may as well just share a snapshot id with it.

Also keep in mind that there can be multiple snapshots within the same block so we would have to disambiguate what snapshot is referred to by a block number.

I have an intuition that this ambiguity can be an avenue for attacks agains the contract.

Note that the snapshot() function returns the integer snapshot id. This means that a contract can take the snapshot itself when it is required, and store the id for later use.


In summary, there are several issues due to which I don't think block numbers are feasible or a good idea for this contract. I would love to know more about the governance use case you have in mind so we can figure out whether it can be implemented using snapshot ids, or we can figure out other improvements to the contract that would make it possible.

One thing that I realize is not possible is to get the block number that corresponds to a snapshot id. This is something that we can easily add if it helps your use case.

1 Like

First of all token transfers can happen many times in a block, so you need to define what a snapshot at a block number means: is it at the beginning of the block, the end, or some arbitrary point in the middle?

The context that I have in mind is for these snapshot tokens to be used with snapshot.page governance. Currently most tokens there are using archive node as default strategy. That is costly and not very transparent for retail users (as few of us run a costly archive node). Therefore, I'm looking for a clean reference implementation of a token with snapshots that can be used by a to-be-developed snapshot.page strategy relying on that token. Thus, the snapshot should be taken right after every transaction and be applicable from that point onward until another transaction was made to/from that account. In that way you can vote on proposals such as "I want every TKN holder to vote on this proposal with a voting power of their balance at block 12'345'678".

Secondly, our implementation of ERC20Snapshot does not have snapshots for every block, snapshots are instead taken on demand by a function call. This is a big difference with the minime token, but it makes for a vastly more efficient implementation. As a consequence, there may not be a snapshot for every block. An on-chain governance contract would need to know that there is a snapshot for a given block number, otherwise the proposal would fail when it tries to read the snapshot balances. If you will let the governance contract know that there is a snapshot for a given block, you may as well just share a snapshot id with it.

As stated above, I'd be interested in a token that does take snapshots for every transaction despite the higher costs.

Also keep in mind that there can be multiple snapshots within the same block so we would have to disambiguate what snapshot is referred to by a block number.

The balance at the end of that block.

these snapshots are not accessible by smart contracts which have no idea about the snapshot ID

Note that the snapshot() function returns the integer snapshot id. This means that a contract can take the snapshot itself when it is required, and store the id for later use.

Good idea!

2 Likes

Here is a specific use case for having balanceOfAt(account, blocknumber) be available for each blocknumber. Compound’s governance contract requires that a user owns a treshold of tokens to create a proposal. This check is done in this line:
https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/GovernorAlpha.sol#L137 (read “getPriorVotes” as “balanceOfAt”).

The contract checks the balance at the block before the proposal was created, to protect against flashloan attacks.

I do not see how, with the ERC20Snapshot implementation, one could implement that behavior…

1 Like

Sorry about that, I see this is already addressed here: ERC20Snapshot and flash loans/swaps/mints - #7 by frangio :slight_smile:

1 Like

Hi @Jelle_Gerbrandy,

Welcome to the community :wave:

You may also want to join the discussion on: Contracts for Governance.

1 Like