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

✨ Add CurrencyAccount to handle all Currency related logic #3779

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGES.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ To be released.

### Added APIs

- (Libplanet.Action) Added `CurrencyAccount` class. [[#3779]]

### Behavioral changes

- (Libplanet.Mocks) `MockWorldState.SetBalance()` now automatically updates
Expand All @@ -40,6 +42,7 @@ To be released.
[#3774]: https://github.com/planetarium/libplanet/pull/3774
[#3775]: https://github.com/planetarium/libplanet/pull/3775
[#3778]: https://github.com/planetarium/libplanet/pull/3778
[#3779]: https://github.com/planetarium/libplanet/pull/3779


Version 4.4.2
Expand Down
400 changes: 400 additions & 0 deletions Libplanet.Action/State/CurrencyAccount.cs
s2quake marked this conversation as resolved.
Show resolved Hide resolved

Large diffs are not rendered by default.

102 changes: 102 additions & 0 deletions Libplanet.Action/State/IStateStoreExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using System.Security.Cryptography;
using System.Text;
using Bencodex.Types;
using Libplanet.Common;
using Libplanet.Crypto;
using Libplanet.Store;
using Libplanet.Store.Trie;
using Libplanet.Types.Blocks;
Expand Down Expand Up @@ -152,6 +157,103 @@ internal static IWorld MigrateWorld(
}
}

// Migrate up to BlockMetadata.ValidatorSetAccountProtocolVersion
// if conditions are met.
if (targetVersion >= BlockMetadata.CurrencyAccountProtocolVersion &&
world.Version < BlockMetadata.CurrencyAccountProtocolVersion)
{
var worldTrie = world.Trie;
worldTrie = worldTrie.SetMetadata(
new TrieMetadata(BlockMetadata.CurrencyAccountProtocolVersion));
worldTrie = stateStore.Commit(worldTrie);
world = new World(new WorldBaseState(worldTrie, stateStore));

// Remove all total supply tracking.
const int totalSupplyKeyLength = 42;
var subRootPath = new KeyBytes(Encoding.ASCII.GetBytes("__"));
var legacyAccountTrie =
world.GetAccount(ReservedAddresses.LegacyAccount).Trie;
var tempTrie = (MerkleTrie)legacyAccountTrie.Set(subRootPath, Null.Value);
foreach (var pair in tempTrie.IterateSubTrieValues(subRootPath))
{
if (pair.Path.Length == totalSupplyKeyLength)
{
legacyAccountTrie = legacyAccountTrie.Remove(pair.Path);
}
}

legacyAccountTrie = stateStore.Commit(legacyAccountTrie);
worldTrie = worldTrie.Set(
KeyConverters.ToStateKey(ReservedAddresses.LegacyAccount),
new Binary(legacyAccountTrie.Hash.ByteArray));
worldTrie = stateStore.Commit(worldTrie);
world = new World(new WorldBaseState(worldTrie, stateStore));

// Remove all fungible assets
const int fungibleAssetKeyLength = 82;
subRootPath = new KeyBytes(Encoding.ASCII.GetBytes("_"));
tempTrie = (MerkleTrie)legacyAccountTrie.Set(subRootPath, Null.Value);
byte[] addressBytesBuffer = new byte[40];
byte[] currencyBytesBuffer = new byte[40];
List<(KeyBytes Address, KeyBytes Currency, Integer Amount)> favs =
new List<(KeyBytes Address, KeyBytes Currency, Integer Amount)>();
foreach (var pair in tempTrie.IterateSubTrieValues(subRootPath))
{
if (pair.Path.Length == fungibleAssetKeyLength)
{
legacyAccountTrie = legacyAccountTrie.Remove(pair.Path);
pair.Path.ByteArray.CopyTo(1, addressBytesBuffer, 0, 40);
pair.Path.ByteArray.CopyTo(42, currencyBytesBuffer, 0, 40);
favs.Add((
new KeyBytes(addressBytesBuffer),
new KeyBytes(currencyBytesBuffer),
(Integer)pair.Value));
}
}

legacyAccountTrie = stateStore.Commit(legacyAccountTrie);
worldTrie = worldTrie.Set(
KeyConverters.ToStateKey(ReservedAddresses.LegacyAccount),
new Binary(legacyAccountTrie.Hash.ByteArray));
worldTrie = stateStore.Commit(worldTrie);
world = new World(new WorldBaseState(worldTrie, stateStore));

// Add in fungible assets to new accounts.
KeyBytes totalSupplyKeyBytes =
KeyConverters.ToStateKey(CurrencyAccount.TotalSupplyAddress);
var grouped = favs.GroupBy(fav => fav.Currency);
foreach (var group in grouped)
{
var currencyAccountTrie = world.Trie.Get(group.Key) is Binary hash
? stateStore.GetStateRoot(new HashDigest<SHA256>(hash))
: stateStore.GetStateRoot(null);
foreach (var fav in group)
{
Integer balance = fav.Amount;
Integer prevTotalSupply =
currencyAccountTrie.Get(totalSupplyKeyBytes) is Integer i
? i
: new Integer(0);
Integer newTotalSupply = new Integer(prevTotalSupply.Value + balance.Value);
currencyAccountTrie =
currencyAccountTrie.Set(
fav.Address,
balance);
currencyAccountTrie =
currencyAccountTrie.Set(
totalSupplyKeyBytes,
newTotalSupply);
}

currencyAccountTrie = stateStore.Commit(currencyAccountTrie);
worldTrie = worldTrie.Set(
group.Key,
new Binary(currencyAccountTrie.Hash.ByteArray));
worldTrie = stateStore.Commit(worldTrie);
world = new World(new WorldBaseState(worldTrie, stateStore));
}
}

// Migrate up to target version if conditions are met.
if (targetVersion >= BlockMetadata.WorldStateProtocolVersion &&
world.Version < targetVersion)
Expand Down
Loading
Loading