Skip to content

Commit

Permalink
fix: add minted coins to balance in x/collection MsgMintFT (#1105)
Browse files Browse the repository at this point in the history
* Add coins

* Add the corresponding unit test

* Update CHANGELOG.md

* Add total-supply invariants

* Add migration logic

* Add unit tests on migration

* Do not verify nfts

* Add a unit test on the invariant

* Lint

* Update consensus version

* Revert "Add a unit test on the invariant"

This reverts commit a779379.

* Revert "Do not verify nfts"

This reverts commit e85305f.

* Revert "Add total-supply invariants"

This reverts commit 3a3e423.

* Apply suggestions from code review
  • Loading branch information
0Tech authored Sep 1, 2023
1 parent c95052c commit 0a27aef
Show file tree
Hide file tree
Showing 8 changed files with 450 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
* (x/bank) [#1093](https://github.com/Finschia/finschia-sdk/pull/1093) Remove message events including `sender` attribute whose information is already present in the relevant events (backport #1066)
* (ostracon) [\#1099](https://github.com/Finschia/finschia-sdk/pull/1099) feat!: remove libsodium vrf library.
* (x/collection) [\#1102](https://github.com/finschia/finschia-sdk/pull/1102) Reject modifying NFT class with token index filled in MsgModify
* (x/collection) [\#1105](https://github.com/Finschia/finschia-sdk/pull/1105) Add minted coins to balance in x/collection MsgMintFT

### Build, CI
* (build,ci) [\#1043](https://github.com/Finschia/finschia-sdk/pull/1043) Update golang version to 1.20
Expand Down
32 changes: 32 additions & 0 deletions x/collection/keeper/migrations.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package keeper

import (
sdk "github.com/Finschia/finschia-sdk/types"
"github.com/Finschia/finschia-sdk/types/module"
"github.com/Finschia/finschia-sdk/x/collection"
v2 "github.com/Finschia/finschia-sdk/x/collection/keeper/migrations/v2"
)

// Migrator is a struct for handling in-place store migrations.
type Migrator struct {
keeper Keeper
}

// NewMigrator returns a new Migrator.
func NewMigrator(keeper Keeper) Migrator {
return Migrator{keeper: keeper}
}

func (m Migrator) Register(register func(moduleName string, fromVersion uint64, handler module.MigrationHandler) error) error {
for fromVersion, handler := range map[uint64]module.MigrationHandler{
1: func(ctx sdk.Context) error {
return v2.MigrateStore(ctx, m.keeper.storeKey, m.keeper.cdc)
},
} {
if err := register(collection.ModuleName, fromVersion, handler); err != nil {
return err
}
}

return nil
}
115 changes: 115 additions & 0 deletions x/collection/keeper/migrations/v2/keys.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package v2

import (
sdk "github.com/Finschia/finschia-sdk/types"
)

var (
contractKeyPrefix = []byte{0x10}
nextClassIDKeyPrefix = []byte{0x12}

balanceKeyPrefix = []byte{0x20}

SupplyKeyPrefix = []byte{0x40}
MintedKeyPrefix = []byte{0x41}
BurntKeyPrefix = []byte{0x42}
)

func ContractKey(contractID string) []byte {
key := make([]byte, len(contractKeyPrefix)+len(contractID))

copy(key, contractKeyPrefix)
copy(key[len(contractKeyPrefix):], contractID)

return key
}

func BalanceKey(contractID string, address sdk.AccAddress, tokenID string) []byte {
prefix := balanceKeyPrefixByAddress(contractID, address)
key := make([]byte, len(prefix)+len(tokenID))

copy(key, prefix)
copy(key[len(prefix):], tokenID)

return key
}

func balanceKeyPrefixByAddress(contractID string, address sdk.AccAddress) []byte {
prefix := balanceKeyPrefixByContractID(contractID)
key := make([]byte, len(prefix)+1+len(address))

begin := 0
copy(key, prefix)

begin += len(prefix)
key[begin] = byte(len(address))

begin++
copy(key[begin:], address)

return key
}

func balanceKeyPrefixByContractID(contractID string) []byte {
key := make([]byte, len(balanceKeyPrefix)+1+len(contractID))

begin := 0
copy(key, balanceKeyPrefix)

begin += len(balanceKeyPrefix)
key[begin] = byte(len(contractID))

begin++
copy(key[begin:], contractID)

return key
}

func splitBalanceKey(key []byte) (contractID string, address sdk.AccAddress, tokenID string) {
begin := len(balanceKeyPrefix) + 1
end := begin + int(key[begin-1])
contractID = string(key[begin:end])

begin = end + 1
end = begin + int(key[begin-1])
address = sdk.AccAddress(key[begin:end])

begin = end
tokenID = string(key[begin:])

return
}

func StatisticKey(keyPrefix []byte, contractID string, classID string) []byte {
prefix := statisticKeyPrefixByContractID(keyPrefix, contractID)
key := make([]byte, len(prefix)+len(classID))

copy(key, prefix)
copy(key[len(prefix):], classID)

return key
}

func statisticKeyPrefixByContractID(keyPrefix []byte, contractID string) []byte {
key := make([]byte, len(keyPrefix)+1+len(contractID))

begin := 0
copy(key, keyPrefix)

begin += len(keyPrefix)
key[begin] = byte(len(contractID))

begin++
copy(key[begin:], contractID)

return key
}

func NextClassIDKey(contractID string) []byte {
key := make([]byte, len(nextClassIDKeyPrefix)+len(contractID))

copy(key, nextClassIDKeyPrefix)
copy(key[len(nextClassIDKeyPrefix):], contractID)

return key
}
134 changes: 134 additions & 0 deletions x/collection/keeper/migrations/v2/store.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package v2

import (
"fmt"

"github.com/Finschia/finschia-sdk/codec"
storetypes "github.com/Finschia/finschia-sdk/store/types"
sdk "github.com/Finschia/finschia-sdk/types"
"github.com/Finschia/finschia-sdk/x/collection"
)

// MigrateStore performs in-place store migrations from v1 to v2.
func MigrateStore(ctx sdk.Context, storeKey storetypes.StoreKey, cdc codec.BinaryCodec) error {
store := ctx.KVStore(storeKey)

// fix ft statistics
if err := fixFTStatistics(store, cdc); err != nil {
return err
}

return nil
}

func fixFTStatistics(store storetypes.KVStore, cdc codec.BinaryCodec) error {
iterator := sdk.KVStorePrefixIterator(store, contractKeyPrefix)
defer iterator.Close()

for ; iterator.Valid(); iterator.Next() {
var contract collection.Contract
if err := cdc.Unmarshal(iterator.Value(), &contract); err != nil {
return err
}

if err := fixContractFTStatistics(store, contract.Id); err != nil {
return err
}
}

return nil
}

func fixContractFTStatistics(store storetypes.KVStore, contractID string) error {
supplies, err := evalContractFTSupplies(store, contractID)
if err != nil {
return err
}

if err := updateContractFTStatistics(store, contractID, supplies); err != nil {
return err
}

return nil
}

func evalContractFTSupplies(store storetypes.KVStore, contractID string) (map[string]sdk.Int, error) {
prefix := balanceKeyPrefixByContractID(contractID)
iterator := sdk.KVStorePrefixIterator(store, prefix)
defer iterator.Close()

supplies := map[string]sdk.Int{}
for ; iterator.Valid(); iterator.Next() {
_, _, tokenID := splitBalanceKey(iterator.Key())
if err := collection.ValidateFTID(tokenID); err != nil {
continue
}

var amount sdk.Int
if err := amount.Unmarshal(iterator.Value()); err != nil {
return nil, err
}

classID := collection.SplitTokenID(tokenID)
if supply, ok := supplies[classID]; ok {
supplies[classID] = supply.Add(amount)
} else {
supplies[classID] = amount
}
}

return supplies, nil
}

func updateContractFTStatistics(store storetypes.KVStore, contractID string, supplies map[string]sdk.Int) error {
bz := store.Get(NextClassIDKey(contractID))
if bz == nil {
return fmt.Errorf("no next class ids of contract %s", contractID)
}

var nextClassIDs collection.NextClassIDs
if err := nextClassIDs.Unmarshal(bz); err != nil {
return err
}

for intClassID := uint64(1); intClassID < nextClassIDs.Fungible.Uint64(); intClassID++ {
classID := fmt.Sprintf("%08x", intClassID)

// update supply
supplyKey := StatisticKey(SupplyKeyPrefix, contractID, classID)
supply, ok := supplies[classID]
if ok {
bz, err := supply.Marshal()
if err != nil {
return err
}
store.Set(supplyKey, bz)
} else {
store.Delete(supplyKey)
}

// get burnt
burntKey := StatisticKey(BurntKeyPrefix, contractID, classID)
burnt := sdk.ZeroInt()
if bz := store.Get(burntKey); bz != nil {
if err := burnt.Unmarshal(bz); err != nil {
return err
}
}

// update minted
minted := supply.Add(burnt)
mintedKey := StatisticKey(MintedKeyPrefix, contractID, classID)
if !minted.IsZero() {
bz, err := minted.Marshal()
if err != nil {
return err
}
store.Set(mintedKey, bz)
} else {
store.Delete(mintedKey)
}
}

return nil
}
Loading

0 comments on commit 0a27aef

Please sign in to comment.