diff --git a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go index f411bdc13a..cf88a2dc63 100644 --- a/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go +++ b/packages/evm/jsonrpc/jsonrpctest/jsonrpc_test.go @@ -93,6 +93,7 @@ func TestRPCGetBalance(t *testing.T) { ) // 18 decimals + initialBalance := env.Balance(nonEmptyAddress) toSend := new(big.Int).SetUint64(1_111_111_111_111_111_111) // use all 18 decimals tx, err := types.SignTx( types.NewTransaction(0, emptyAddress, toSend, uint64(100_000), env.MustGetGasPrice(), []byte{}), @@ -102,6 +103,10 @@ func TestRPCGetBalance(t *testing.T) { require.NoError(t, err) receipt := env.mustSendTransactionAndWait(tx) require.Equal(t, types.ReceiptStatusSuccessful, receipt.Status) + fee := new(big.Int).Mul(receipt.EffectiveGasPrice, new(big.Int).SetUint64(receipt.GasUsed)) + exptectedBalance := new(big.Int).Sub(initialBalance, toSend) + exptectedBalance = new(big.Int).Sub(exptectedBalance, fee) + require.Equal(t, exptectedBalance, env.Balance(nonEmptyAddress)) require.Equal(t, toSend, env.Balance(emptyAddress)) } diff --git a/packages/solo/checkledger.go b/packages/solo/checkledger.go new file mode 100644 index 0000000000..771c476653 --- /dev/null +++ b/packages/solo/checkledger.go @@ -0,0 +1,48 @@ +package solo + +import ( + "fmt" + "math/big" + + "github.com/iotaledger/wasp/packages/isc" + "github.com/iotaledger/wasp/packages/kv" + "github.com/iotaledger/wasp/packages/kv/subrealm" + "github.com/iotaledger/wasp/packages/parameters" + "github.com/iotaledger/wasp/packages/util" + "github.com/iotaledger/wasp/packages/vm/core/accounts" +) + +// only used in internal tests and solo +func CheckLedger(v isc.SchemaVersion, store kv.KVStoreReader, checkpoint string) { + state := subrealm.NewReadOnly(store, kv.Key(accounts.Contract.Hname().Bytes())) + t := accounts.GetTotalL2FungibleTokens(v, state) + c := calcL2TotalFungibleTokens(v, state) + if !t.Equals(c) { + panic(fmt.Sprintf("inconsistent on-chain account ledger @ checkpoint '%s'\n total assets: %s\ncalc total: %s\n", + checkpoint, t, c)) + } +} + +func calcL2TotalFungibleTokens(v isc.SchemaVersion, state kv.KVStoreReader) *isc.Assets { + ret := isc.NewEmptyAssets() + totalBaseTokens := big.NewInt(0) + + accounts.AllAccountsMapR(state).IterateKeys(func(accountKey []byte) bool { + // add all native tokens owned by each account + accounts.NativeTokensMapR(state, kv.Key(accountKey)).Iterate(func(idBytes []byte, val []byte) bool { + ret.AddNativeTokens( + isc.MustNativeTokenIDFromBytes(idBytes), + new(big.Int).SetBytes(val), + ) + return true + }) + // use the full decimals for each account, so no dust balance is lost in the calculation + baseTokensFullDecimals := accounts.GetBaseTokensFullDecimals(v)(state, kv.Key(accountKey)) + totalBaseTokens = new(big.Int).Add(totalBaseTokens, baseTokensFullDecimals) + return true + }) + + // convert from 18 decimals, remainder must be 0 + ret.BaseTokens = util.MustEthereumDecimalsToBaseTokenDecimalsExact(totalBaseTokens, parameters.L1().BaseToken.Decimals) + return ret +} diff --git a/packages/solo/utils.go b/packages/solo/utils.go index 438b2a9b63..58db1ad5c4 100644 --- a/packages/solo/utils.go +++ b/packages/solo/utils.go @@ -1,18 +1,8 @@ package solo import ( - "fmt" - "math/big" - - "github.com/samber/lo" - - iotago "github.com/iotaledger/iota.go/v3" "github.com/iotaledger/wasp/packages/cryptolib" "github.com/iotaledger/wasp/packages/isc" - "github.com/iotaledger/wasp/packages/kv" - "github.com/iotaledger/wasp/packages/parameters" - "github.com/iotaledger/wasp/packages/util" - "github.com/iotaledger/wasp/packages/vm/core/accounts" "github.com/iotaledger/wasp/packages/vm/core/root" ) @@ -54,63 +44,3 @@ func ISCRequestFromCallParams(ch *Chain, req *CallParams, keyPair *cryptolib.Key } return requestsFromSignedTx[ch.ChainID][0], nil } - -// only used in internal tests and solo -func CheckLedger(v isc.SchemaVersion, state kv.KVStoreReader, checkpoint string) { - t := accounts.GetTotalL2FungibleTokens(v, state) - c := calcL2TotalFungibleTokens(v, state) - if !t.Equals(c) { - panic(fmt.Sprintf("inconsistent on-chain account ledger @ checkpoint '%s'\n total assets: %s\ncalc total: %s\n", - checkpoint, t, c)) - } - - totalAccNFTs := accounts.GetTotalL2NFTs(state) - if len(lo.FindDuplicates(totalAccNFTs)) != 0 { - panic(fmt.Sprintf("inconsistent on-chain account ledger @ checkpoint '%s'\n duplicate NFTs\n", checkpoint)) - } - calculatedNFTs := calcL2TotalNFTs(state) - if len(lo.FindDuplicates(calculatedNFTs)) != 0 { - panic(fmt.Sprintf("inconsistent on-chain account ledger @ checkpoint '%s'\n duplicate NFTs\n", checkpoint)) - } - left, right := lo.Difference(calculatedNFTs, totalAccNFTs) - if len(left)+len(right) != 0 { - panic(fmt.Sprintf("inconsistent on-chain account ledger @ checkpoint '%s'\n NFTs don't match\n", checkpoint)) - } -} - -func calcL2TotalFungibleTokens(v isc.SchemaVersion, state kv.KVStoreReader) *isc.Assets { - ret := isc.NewEmptyAssets() - totalBaseTokens := big.NewInt(0) - - accounts.AllAccountsMapR(state).IterateKeys(func(accountKey []byte) bool { - // add all native tokens owned by each account - accounts.NativeTokensMapR(state, kv.Key(accountKey)).Iterate(func(idBytes []byte, val []byte) bool { - ret.AddNativeTokens( - isc.MustNativeTokenIDFromBytes(idBytes), - new(big.Int).SetBytes(val), - ) - return true - }) - // use the full decimals for each account, so no dust balance is lost in the calculation - baseTokensFullDecimals := accounts.GetBaseTokensFullDecimals(v)(state, kv.Key(accountKey)) - totalBaseTokens = new(big.Int).Add(totalBaseTokens, baseTokensFullDecimals) - return true - }) - - // convert from 18 decimals, remainder must be 0 - ret.BaseTokens = util.MustEthereumDecimalsToBaseTokenDecimalsExact(totalBaseTokens, parameters.L1().BaseToken.Decimals) - return ret -} - -func calcL2TotalNFTs(state kv.KVStoreReader) []iotago.NFTID { - var ret []iotago.NFTID - accounts.AllAccountsMapR(state).IterateKeys(func(key []byte) bool { - agentID, err := isc.AgentIDFromBytes(key) // obs: this can only be done because the key saves the entire bytes of agentID, unlike the BaseTokens/NativeTokens accounting - if err != nil { - panic(fmt.Errorf("calcL2TotalNFTs: %w", err)) - } - ret = append(ret, accounts.GetAccountNFTs(state, agentID)...) - return true - }) - return ret -} diff --git a/packages/vm/core/accounts/basetokens.go b/packages/vm/core/accounts/basetokens.go index 31eb955388..deb0ce6059 100644 --- a/packages/vm/core/accounts/basetokens.go +++ b/packages/vm/core/accounts/basetokens.go @@ -12,7 +12,6 @@ import ( type ( getBaseTokensFn func(state kv.KVStoreReader, accountKey kv.Key) uint64 - setBaseTokensFn func(state kv.KVStore, accountKey kv.Key, amount uint64) GetBaseTokensFullDecimalsFn func(state kv.KVStoreReader, accountKey kv.Key) *big.Int setBaseTokensFullDecimalsFn func(state kv.KVStore, accountKey kv.Key, amount *big.Int) ) @@ -26,15 +25,6 @@ func getBaseTokens(v isc.SchemaVersion) getBaseTokensFn { } } -func setBaseTokens(v isc.SchemaVersion) setBaseTokensFn { - switch v { - case 0: - return setBaseTokensDEPRECATED - default: - return setBaseTokensNEW - } -} - func GetBaseTokensFullDecimals(v isc.SchemaVersion) GetBaseTokensFullDecimalsFn { switch v { case 0: @@ -74,12 +64,6 @@ func getBaseTokensNEW(state kv.KVStoreReader, accountKey kv.Key) uint64 { return convertedAmount } -func setBaseTokensNEW(state kv.KVStore, accountKey kv.Key, amount uint64) { - // convert to 18 decimals - amountConverted := util.MustBaseTokensDecimalsToEthereumDecimalsExact(amount, parameters.L1().BaseToken.Decimals) - state.Set(BaseTokensKey(accountKey), codec.EncodeBigIntAbs(amountConverted)) -} - func AdjustAccountBaseTokens(v isc.SchemaVersion, state kv.KVStore, account isc.AgentID, adjustment int64, chainID isc.ChainID) { switch { case adjustment > 0: diff --git a/packages/vm/core/accounts/basetokens_deprecated.go b/packages/vm/core/accounts/basetokens_deprecated.go index 2e87adbe6e..9d8f31ca21 100644 --- a/packages/vm/core/accounts/basetokens_deprecated.go +++ b/packages/vm/core/accounts/basetokens_deprecated.go @@ -15,10 +15,6 @@ func getBaseTokensDEPRECATED(state kv.KVStoreReader, accountKey kv.Key) uint64 { return codec.MustDecodeUint64(state.Get(BaseTokensKey(accountKey)), 0) } -func setBaseTokensDEPRECATED(state kv.KVStore, accountKey kv.Key, amount uint64) { - state.Set(BaseTokensKey(accountKey), codec.EncodeUint64(amount)) -} - func getBaseTokensFullDecimalsDEPRECATED(state kv.KVStoreReader, accountKey kv.Key) *big.Int { amount := codec.MustDecodeUint64(state.Get(BaseTokensKey(accountKey)), 0) baseTokens, _ := util.BaseTokensDecimalsToEthereumDecimals(amount, parameters.L1().BaseToken.Decimals) diff --git a/packages/vm/core/accounts/fungibletokens.go b/packages/vm/core/accounts/fungibletokens.go index 4f8b16d7dc..a94d5a9f97 100644 --- a/packages/vm/core/accounts/fungibletokens.go +++ b/packages/vm/core/accounts/fungibletokens.go @@ -7,6 +7,7 @@ import ( "github.com/iotaledger/wasp/packages/isc" "github.com/iotaledger/wasp/packages/kv" "github.com/iotaledger/wasp/packages/kv/dict" + "github.com/iotaledger/wasp/packages/parameters" "github.com/iotaledger/wasp/packages/util" ) @@ -29,7 +30,8 @@ func creditToAccount(v isc.SchemaVersion, state kv.KVStore, accountKey kv.Key, a } if assets.BaseTokens > 0 { - setBaseTokens(v)(state, accountKey, getBaseTokens(v)(state, accountKey)+assets.BaseTokens) + incomingTokensFullDecimals := util.MustBaseTokensDecimalsToEthereumDecimalsExact(assets.BaseTokens, parameters.L1().BaseToken.Decimals) + creditToAccountFullDecimals(v, state, accountKey, incomingTokensFullDecimals) } for _, nt := range assets.NativeTokens { if nt.Amount.Sign() == 0 { @@ -85,16 +87,19 @@ func debitFromAccount(v isc.SchemaVersion, state kv.KVStore, accountKey kv.Key, // first check, then mutate mutateBaseTokens := false - mutations := isc.NewEmptyAssets() + baseTokensToDebit := util.MustBaseTokensDecimalsToEthereumDecimalsExact(assets.BaseTokens, parameters.L1().BaseToken.Decimals) + var baseTokensToSet *big.Int if assets.BaseTokens > 0 { - balance := getBaseTokens(v)(state, accountKey) - if assets.BaseTokens > balance { + balance := GetBaseTokensFullDecimals(v)(state, accountKey) + if baseTokensToDebit.Cmp(balance) > 0 { return false } mutateBaseTokens = true - mutations.BaseTokens = balance - assets.BaseTokens + baseTokensToSet = new(big.Int).Sub(balance, baseTokensToDebit) } + + nativeTokensMutations := isc.NewEmptyAssets() for _, nt := range assets.NativeTokens { if nt.Amount.Sign() == 0 { continue @@ -107,13 +112,13 @@ func debitFromAccount(v isc.SchemaVersion, state kv.KVStore, accountKey kv.Key, if balance.Sign() < 0 { return false } - mutations.AddNativeTokens(nt.ID, balance) + nativeTokensMutations.AddNativeTokens(nt.ID, balance) } if mutateBaseTokens { - setBaseTokens(v)(state, accountKey, mutations.BaseTokens) + setBaseTokensFullDecimals(v)(state, accountKey, baseTokensToSet) } - for _, nt := range mutations.NativeTokens { + for _, nt := range nativeTokensMutations.NativeTokens { setNativeTokenAmount(state, accountKey, nt.ID, nt.Amount) } return true diff --git a/packages/vm/core/accounts/nfts.go b/packages/vm/core/accounts/nfts.go index 3a6fe78ca4..4e4a1cc5c6 100644 --- a/packages/vm/core/accounts/nfts.go +++ b/packages/vm/core/accounts/nfts.go @@ -33,11 +33,11 @@ func accountToNFTsMap(state kv.KVStore, agentID isc.AgentID) *collections.Map { return collections.NewMap(state, nftsMapKey(agentID)) } -func NFTToOwnerMap(state kv.KVStore) *collections.Map { +func nftToOwnerMap(state kv.KVStore) *collections.Map { return collections.NewMap(state, keyNFTOwner) } -func NFTToOwnerMapR(state kv.KVStoreReader) *collections.ImmutableMap { +func nftToOwnerMapR(state kv.KVStoreReader) *collections.ImmutableMap { return collections.NewMapReadOnly(state, keyNFTOwner) } @@ -67,7 +67,7 @@ func hasNFT(state kv.KVStoreReader, agentID isc.AgentID, nftID iotago.NFTID) boo func removeNFTOwner(state kv.KVStore, nftID iotago.NFTID, agentID isc.AgentID) bool { // remove the mapping of NFTID => owner - nftMap := NFTToOwnerMap(state) + nftMap := nftToOwnerMap(state) if !nftMap.HasAt(nftID[:]) { return false } @@ -84,7 +84,7 @@ func removeNFTOwner(state kv.KVStore, nftID iotago.NFTID, agentID isc.AgentID) b func setNFTOwner(state kv.KVStore, nftID iotago.NFTID, agentID isc.AgentID) { // add to the mapping of NFTID => owner - nftMap := NFTToOwnerMap(state) + nftMap := nftToOwnerMap(state) nftMap.SetAt(nftID[:], agentID.Bytes()) // add to the mapping of agentID => []NFTIDs @@ -97,7 +97,7 @@ func GetNFTData(state kv.KVStoreReader, nftID iotago.NFTID) *isc.NFT { if o == nil { return nil } - owner, err := isc.AgentIDFromBytes(NFTToOwnerMapR(state).GetAt(nftID[:])) + owner, err := isc.AgentIDFromBytes(nftToOwnerMapR(state).GetAt(nftID[:])) if err != nil { panic("error parsing AgentID in NFTToOwnerMap") } @@ -184,7 +184,7 @@ func getAccountNFTsInCollection(state kv.KVStoreReader, agentID isc.AgentID, col } func getL2TotalNFTs(state kv.KVStoreReader) []iotago.NFTID { - return collectNFTIDs(NFTToOwnerMapR(state)) + return collectNFTIDs(nftToOwnerMapR(state)) } // GetAccountNFTs returns all NFTs belonging to the agentID on the state diff --git a/packages/vm/vmimpl/migrations.go b/packages/vm/vmimpl/migrations.go index 55b0269cf0..feff763cfb 100644 --- a/packages/vm/vmimpl/migrations.go +++ b/packages/vm/vmimpl/migrations.go @@ -11,14 +11,6 @@ import ( func (vmctx *vmContext) runMigrations(chainState kv.KVStore, migrationScheme *migrations.MigrationScheme) { latestSchemaVersion := migrationScheme.LatestSchemaVersion() - if vmctx.task.AnchorOutput.StateIndex == 0 { - // initializing new chain -- set the schema to latest version - withContractState(chainState, root.Contract, func(s kv.KVStore) { - root.SetSchemaVersion(s, latestSchemaVersion) - }) - return - } - currentVersion := root.NewStateAccess(chainState).SchemaVersion() if currentVersion < migrationScheme.BaseSchemaVersion { diff --git a/packages/vm/vmimpl/migrations_test.go b/packages/vm/vmimpl/migrations_test.go index ef369364c7..d81c875f0d 100644 --- a/packages/vm/vmimpl/migrations_test.go +++ b/packages/vm/vmimpl/migrations_test.go @@ -91,21 +91,6 @@ func newMigrationsTest(t *testing.T, stateIndex uint32) *migrationsTestEnv { return env } -func TestMigrationsStateIndex0(t *testing.T) { - env := newMigrationsTest(t, 0) - - require.EqualValues(t, 0, env.getSchemaVersion()) - - env.vmctx.withStateUpdate(func(chainState kv.KVStore) { - env.vmctx.runMigrations(chainState, &migrations.MigrationScheme{ - BaseSchemaVersion: 0, - Migrations: []migrations.Migration{env.panic, env.panic, env.panic}, - }) - }) - - require.EqualValues(t, 3, env.getSchemaVersion()) -} - func TestMigrationsStateIndex1(t *testing.T) { env := newMigrationsTest(t, 1)