What is the snapshot token and how does the snapshotting of token balances at a specific point in time work?

OpenDevICON
OpenDevICON
Published in
5 min readNov 9, 2020

What is snapshot?

The term snapshot refers to the ability to record the state of something at a specific point in time. In blockchain, a snapshot refers to the act of recording the state of a blockchain, at a particular block height. In our case, it is used to record the total supply and balance of token holders at specific point in time.

Double spending problem

Double spending is a potential flaw in digital cash scheme in which the same digital token can be spent more than once. Since IRC2 tokens is basically a digital file it’s easier to duplicate than actual money. This means some people can manipulate their way to paying more than once with the same token. Here, the cryptocurrency is essentially stolen.

So, it’s obvious that we need to solve this issue. To avoid this, we implement snapshot mechanism in our IRC2 contract. The implementation can be found here, in odi-contracts repo of OpenDevICON.

Snapshot is used to avoid the double spending problem. This system runs the risk that many copies of the same bitstring are spent at different merchants. In naive implementation, it is possible to perform a “double spend” attack by reusing the same balance from different accounts.

The snapshot contract extends an IRC2 token with a snapshot mechanism. When a snapshot is created, the balances and total supply at the time are recorded for later access.

Snapshot are created by the internal _snapshot function, which will emit a snapshot event and set a snapshot id in the blockchain. balanceOfAt external method is used to get the balance of a addresss at particular snapshot id. Similarly, totalSupplyAt method is used to get the total supply at a particular snapshot id.

Snapshots are needed during transfer operation, mint operation and burn operation. When transferred, the snapshot of sending and recieving account needs to be updated. Similarly, during mint operation, the snapshot of address where the token was minted and the total supply needs to be changed. During burn operation, the snapshot of address where the token was burned from and the total supply needs to be changed.

Before diving deeper, let’s learn what these variables mean…

_current_snapshot_id : The current snapshot id. (VarDB)

_account_balance_snapshot: It is used to keep track of snapshot_id, and the account balance at that id. length is used to connect between them. Length is like the index of an array in this case. It is equivalent to 2 level 1 dicts combined.

For reference:

ids = {0:0, 1:10, 2: 30, 3:50}vals = {0:100, 1:500, 2: 200, 3:1000}

Here, 0,1,2,3 i.e. the keys in both dicts are same, and they represent length. The values in ids represent snapshot id at that particular length, and values in vals represent balance at that particular length.

_total_supply_snapshot: It is used to keep track of total supply at a particular snapshot id. It works similar to account balance snapshot.

How does updating account snapshot work?

Here, first the current snapshot id is fetched and is set to current_id. Then, the balance of that account is set to current_value. The length, the key used to connect snapshot id with balance, is initialized to 1 when the contract is deployed. That value is set to length variable.

If length is 0, then, the snapshot balance of that account at length 0 is set to current_value, i.e. the balance of that account. Then, the length is increased. Generally, the length is not zero, and in this case, last_snapshot_id is set to the length-1.

Now, if the last_snapshot_id is less than current id, the snapshot id for that account at that length is set to current_id, value at that length is set to current_value and the length is updated by 1. Else, the value at length-1 is set to current_value.

How does updating total supply snapshot work?

This works similar to account snapshot update, except for balance of a particular address is replaced by total supply. Here, in DictDB, we don’t need additional depth for wallet address. The snapshot id is fetched, and length for total supply snapshot is fetched and set to local variables current_id and current_value respectively.

If length is 0, then, the snapshot total supply at length 0 is set to current_value, i.e. the total supply. Then, the length is increased. Generally, the length is not zero, and in this case, last_snapshot_id is set to the length -1.

Now, if the last_snapshot_id is less than current id, the snapshot id at that length is set to current_id, value at that length is set to current_value and the length is updated. Else, the value at length-1 is set to current_value.

Now, that updating account and total supply snapshot are covered, let’s see how we can get the balance of a account or total supply at a particular snapshot.

balanceOfAt

This method is used to find the balance of a particular account at a particular snapshot id. The snapshot id starts at 0 and is updated by 1. So, it cannot be less than 0.

When a valid snapshot id is queried, there are the following possibilties:

- The queried value was modified after the snapshot was taken.

- The value was not modified after the snapshot was taken.

- More snapshot ids were created after the requested one.

So, basically we need to find the balance at that id.

Here, we initialize low as 0 and high as the length.

While, the low value is less than high, we calculate mid by integer division of low and high. Then the id of that account when length = mid is fetched. If it is greater than snapshot id, high is set to mid. Else, low is set to mid + 1. This continues till the condition low is less than high is false.

Then, if the queried snapshot id is equal to the 0 length, then that value at 0 length is returned.

If low is equal to 0, 0 is returned, i.e. balance at that snapshot id is zero.

Else, the balance of that account at length `low-1` is returned.

This can be a bit complex to understand. It can be simplified by the following code snippet for understanding.

low = 0high = 3_snapshot_id = 50ids = {0:0, 1:10, 2: 30, 3:50}vals = {0:100, 1:500, 2: 200, 3:1000}while (low < high):mid = (low + high) // 2if ids[mid] > _snapshot_id:high = midelse:low = mid + 1if ids[0] == _snapshot_id:print(vals[0])elif low == 0:print(0)else:print(vals[low])

Here, the keys in the dictionaries, indicate length. The value in dict ids represent snapshot id, equivalent to

_account_balance_snapshot[_account][‘ids’][length] in the code. The value in vals represent balance at that length, equivalent to

account_balance_snapshot[_account][‘values’][length] in the code.

Note that, the initial length is set to 1 in on_install method. So, in the above snippet, we use vals[low] only, in the last line.

totalSupplyAt

This method is used to find the total supply at a particular snapshot id. The snapshot id cannot be less than 0.

This works exactly similar to the balanceOfAt method, except the total supply value is fetched instead of balance of particular account.

This is how snapshotting of token balances work at specific snapshot id.

--

--