diff --git a/CHANGELOG.md b/CHANGELOG.md index 1f30423b8..bbc30e91e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -48,7 +48,7 @@ Ref: https://keepachangelog.com/en/1.0.0/ * Update stargate queries for Attribute, Exchange, Marker, IBCRateLimit, Metadata, Msgfees, and Oracle modules [#1760](https://github.com/provenance-io/provenance/issues/1760). * Update stargate queries for Quarantine and Sanction modules [#2016](https://github.com/provenance-io/provenance/pull/2016). * Add the circuit breaker module [#2031](https://github.com/provenance-io/provenance/pull/2031). -* Add upgrade handler to set scope net asset values and update block height for pio-testnet-1 [#2046](https://github.com/provenance-io/provenance/pull/2046) +* Add upgrade handler to set scope net asset values and update block height for pio-testnet-1 [#2046](https://github.com/provenance-io/provenance/pull/2046), [#2050](https://github.com/provenance-io/provenance/pull/2050). ### Improvements @@ -184,7 +184,6 @@ Ref: https://keepachangelog.com/en/1.0.0/ * /cosmos.staking.v1beta1.Query/HistoricalInfo * /cosmos.staking.v1beta1.Query/Pool - ### Client Breaking * The `provenanced query account` command has been removed. It is still available as `provenanced query auth account` [#1971](https://github.com/provenance-io/provenance/pull/1971). @@ -235,8 +234,8 @@ Ref: https://keepachangelog.com/en/1.0.0/ - Bump `cosmossdk.io/client/v2` from 2.0.0-beta.1 to 2.0.0-beta.2 ([#2042](https://github.com/provenance-io/provenance/pull/2042)) - Bump `docker/build-push-action` from 5 to 6 ([#2039](https://github.com/provenance-io/provenance/pull/2039)) - Bump `github.com/cosmos/ibc-go/v8` from 8.2.1 to 8.3.2 ([#2043](https://github.com/provenance-io/provenance/pull/2043)) -- Bump wasmd to `v0.51.0` [#2045](https://github.com/provenance-io/provenance/pull/2045) -- Bump wasmvm to `v2.0.1` [#2045](https://github.com/provenance-io/provenance/pull/2045) +- Bump wasmd to `v0.51.0` ([#2045](https://github.com/provenance-io/provenance/pull/2045)) +- Bump wasmvm to `v2.0.1` ([#2045](https://github.com/provenance-io/provenance/pull/2045)) --- diff --git a/app/embed.go b/app/embed.go new file mode 100644 index 000000000..072d8be63 --- /dev/null +++ b/app/embed.go @@ -0,0 +1,6 @@ +package app + +import "embed" + +//go:embed upgrade_files +var UpgradeFiles embed.FS diff --git a/app/scope_navs_updater.go b/app/scope_navs_updater.go index 604512928..b6a082383 100644 --- a/app/scope_navs_updater.go +++ b/app/scope_navs_updater.go @@ -2,7 +2,6 @@ package app import ( "encoding/csv" - "os" "strconv" sdk "github.com/cosmos/cosmos-sdk/types" @@ -10,7 +9,9 @@ import ( metadatatypes "github.com/provenance-io/provenance/x/metadata/types" ) -type NetAssetValueWithHeight struct { +const umberTestnetScopeNAVsFN = "upgrade_files/umber/testnet_scope_navs.csv" + +type ScopeNAV struct { ScopeUUID string NetAssetValue metadatatypes.NetAssetValue Height uint64 @@ -25,9 +26,9 @@ func parseValueToUsdMills(navStr string) (int64, error) { return int64(navValue * 1000), nil } -// ReadNetAssetValues reads a CSV file and parses its contents into a slice of NetAssetValueWithHeight -func ReadNetAssetValues(fileName string) ([]NetAssetValueWithHeight, error) { - file, err := os.Open(fileName) +// ReadScopeNAVs reads a CSV file from the upgrade_files dir, and parses its contents into a slice of ScopeNAV +func ReadScopeNAVs(fileName string) ([]ScopeNAV, error) { + file, err := UpgradeFiles.Open(fileName) if err != nil { return nil, err } @@ -45,7 +46,7 @@ func ReadNetAssetValues(fileName string) ([]NetAssetValueWithHeight, error) { return nil, err } - assets := make([]NetAssetValueWithHeight, 0, len(records)) + assets := make([]ScopeNAV, 0, len(records)) for _, record := range records { if len(record) < 3 { continue @@ -66,7 +67,7 @@ func ReadNetAssetValues(fileName string) ([]NetAssetValueWithHeight, error) { price := sdk.NewInt64Coin(metadatatypes.UsdDenom, navInt64) - asset := NetAssetValueWithHeight{ + asset := ScopeNAV{ ScopeUUID: scopeUUID, NetAssetValue: metadatatypes.NewNetAssetValue(price), Height: height, diff --git a/app/scope_navs_updater_test.go b/app/scope_navs_updater_test.go index f9b8d70d7..57255419d 100644 --- a/app/scope_navs_updater_test.go +++ b/app/scope_navs_updater_test.go @@ -3,35 +3,63 @@ package app import ( "testing" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + sdk "github.com/cosmos/cosmos-sdk/types" + metadatatypes "github.com/provenance-io/provenance/x/metadata/types" - "github.com/stretchr/testify/assert" ) -func TestReadNetAssetValues(t *testing.T) { - fileName := "upgrade_files/testnet_scope_navs.csv" +func TestReadScopeNAVs(t *testing.T) { + tests := []struct { + fileName string + expCount int + expFirst ScopeNAV + expLast ScopeNAV + }{ + { + fileName: umberTestnetScopeNAVsFN, + expCount: 1101, + expFirst: ScopeNAV{ + ScopeUUID: "2f389a9f-873d-4920-85a6-7734f27e1738", + NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 398820670)), + Height: 23056719, + }, + expLast: ScopeNAV{ + ScopeUUID: "65939db0-6d7a-42ef-9443-378304d33225", + NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 93661920)), + Height: 23056719, + }, + }, + } + + assertEqualEntry := func(t *testing.T, expected, actual ScopeNAV, msg string, args ...interface{}) bool { + t.Helper() + if assert.Equalf(t, expected, actual, msg, args...) { + return true + } - assets, err := ReadNetAssetValues(fileName) - assert.NoError(t, err, "Failed to read net asset values") - assert.Len(t, assets, 1101, "The number of assets should be 1101") + assert.Equalf(t, expected.ScopeUUID, actual.ScopeUUID, msg+" ScopeUUID", args...) + if !assert.Equalf(t, expected.NetAssetValue, actual.NetAssetValue, msg+" NetAssetValue", args...) { + assert.Equalf(t, expected.NetAssetValue.Price.String(), actual.NetAssetValue.Price.String(), msg+" NetAssetValue.Price", args...) + assert.Equalf(t, int(expected.NetAssetValue.UpdatedBlockHeight), int(actual.NetAssetValue.UpdatedBlockHeight), msg+" NetAssetValue.UpdatedBlockHeight", args...) + } + assert.Equalf(t, expected.Height, actual.Height, msg+" Height", args...) - expectedFirst := NetAssetValueWithHeight{ - ScopeUUID: "2f389a9f-873d-4920-85a6-7734f27e1738", - NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 398820670)), - Height: 23056719, + return false } - assert.Equal(t, expectedFirst.ScopeUUID, assets[0].ScopeUUID, "The first ScopeUUID should match") - assert.True(t, assets[0].NetAssetValue.Price.Equal(expectedFirst.NetAssetValue.Price), "The first NetAssetValue should match") - assert.Equal(t, expectedFirst.Height, assets[0].Height, "The first Height should match") - - expectedLast := NetAssetValueWithHeight{ - ScopeUUID: "65939db0-6d7a-42ef-9443-378304d33225", - NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 93661920)), - Height: 23056719, + + for _, tc := range tests { + t.Run(tc.fileName, func(t *testing.T) { + assets, err := ReadScopeNAVs(tc.fileName) + require.NoError(t, err, "Failed to read net asset values") + assert.Len(t, assets, tc.expCount, "The number of assets should be 1101") + + assertEqualEntry(t, tc.expFirst, assets[0], "First entry") + assertEqualEntry(t, tc.expLast, assets[len(assets)-1], "Last entry") + }) } - assert.Equal(t, expectedLast.ScopeUUID, assets[len(assets)-1].ScopeUUID, "The last ScopeUUID should match") - assert.True(t, assets[len(assets)-1].NetAssetValue.Price.Equal(expectedLast.NetAssetValue.Price), "The last NetAssetValue should match") - assert.Equal(t, expectedLast.Height, assets[len(assets)-1].Height, "The last Height should match") } func TestParseValueToUsdMills(t *testing.T) { @@ -44,17 +72,24 @@ func TestParseValueToUsdMills(t *testing.T) { {"0.99", 990, false}, {"100.5", 100500, false}, {"100.3456", 100345, false}, + {"172755", 172755000, false}, {"abc", 0, true}, {"", 0, true}, } - for _, test := range tests { - output, err := parseValueToUsdMills(test.input) - if test.expectError { - assert.Error(t, err, "Expected an error for input: %s", test.input) - } else { - assert.NoError(t, err, "Did not expect an error for input: %s", test.input) - assert.Equal(t, test.expectedOutput, output, "Expected output to be %d for input: %s", test.expectedOutput, test.input) + for _, tc := range tests { + name := tc.input + if len(name) == 0 { + name = "empty" } + t.Run(name, func(t *testing.T) { + output, err := parseValueToUsdMills(tc.input) + if tc.expectError { + assert.Error(t, err, "parseValueToUsdMills error") + } else { + assert.NoError(t, err, "parseValueToUsdMills error") + } + assert.Equal(t, tc.expectedOutput, output, "parseValueToUsdMills output") + }) } } diff --git a/app/upgrade_files/testnet_scope_navs.csv b/app/upgrade_files/umber/testnet_scope_navs.csv similarity index 100% rename from app/upgrade_files/testnet_scope_navs.csv rename to app/upgrade_files/umber/testnet_scope_navs.csv diff --git a/app/upgrades.go b/app/upgrades.go index e95cceb4f..ce7e207ec 100644 --- a/app/upgrades.go +++ b/app/upgrades.go @@ -95,11 +95,10 @@ var upgrades = map[string]appUpgrade{ updateIBCClients(ctx, app) - scopeNavs, err := ReadNetAssetValues("upgrade_files/testnet_scope_navs.csv") + err = addScopeNAVs(ctx, app, umberTestnetScopeNAVsFN) if err != nil { return nil, err } - addScopeNavsWithHeight(ctx, app, scopeNavs) removeInactiveValidatorDelegations(ctx, app) @@ -571,30 +570,48 @@ func setNewGovParams(ctx sdk.Context, app *App, newParams govv1.Params, chain st return nil } -// addScopeNavsWithHeight sets net asset values with heights for markers +// addScopeNAVs reads a navs csv file and sets net asset values with heights for markers. // TODO: Remove with the umber handlers. -func addScopeNavsWithHeight(ctx sdk.Context, app *App, navsWithHeight []NetAssetValueWithHeight) { - ctx.Logger().Info("Adding scope net asset values with heights.") +func addScopeNAVs(ctx sdk.Context, app *App, fileName string) error { + ctx.Logger().Info("Adding scope net asset values.") + navs, err := ReadScopeNAVs(fileName) + if err != nil { + return fmt.Errorf("could not read navs: %w", err) + } + addScopeNAVsWithHeight(ctx, app, navs) + ctx.Logger().Info("Done adding scope net asset values.") + return nil +} + +// addScopeNAVsWithHeight sets net asset values with heights for markers. +// TODO: Remove with the umber handlers. +func addScopeNAVsWithHeight(ctx sdk.Context, app *App, scopeNAVs []ScopeNAV) { + count := len(scopeNAVs) + ctx.Logger().Info(fmt.Sprintf("Adding %d scope net asset value entries.", count)) totalAdded := 0 - for _, navWithHeight := range navsWithHeight { - uid, err := uuid.Parse(navWithHeight.ScopeUUID) + for i, scopeNAV := range scopeNAVs { + uid, err := uuid.Parse(scopeNAV.ScopeUUID) if err != nil { - ctx.Logger().Error(fmt.Sprintf("invalid uuid %v : %v", navWithHeight.ScopeUUID, err)) + ctx.Logger().Error(fmt.Sprintf("[%d/%d]: Invalid UUID %q: %v.", i+1, count, scopeNAV.ScopeUUID, err)) continue } scopeAddr := metadatatypes.ScopeMetadataAddress(uid) _, found := app.MetadataKeeper.GetScope(ctx, scopeAddr) if !found { - ctx.Logger().Error(fmt.Sprintf("unable to find scope %v", navWithHeight.ScopeUUID)) + ctx.Logger().Error(fmt.Sprintf("[%d/%d]: Unable to find scope with UUID %q.", i+1, count, scopeNAV.ScopeUUID)) continue } - if err := app.MetadataKeeper.SetNetAssetValueWithBlockHeight(ctx, scopeAddr, navWithHeight.NetAssetValue, "owner", navWithHeight.Height); err != nil { - ctx.Logger().Error(fmt.Sprintf("unable to set net asset value with height %v at height %d: %v", navWithHeight.NetAssetValue, navWithHeight.Height, err)) + err = app.MetadataKeeper.SetNetAssetValueWithBlockHeight(ctx, scopeAddr, scopeNAV.NetAssetValue, "owner", scopeNAV.Height) + if err != nil { + ctx.Logger().Error(fmt.Sprintf("[%d/%d]: Unable to set scope %s (%q) net asset value %s at height %d: %v.", + i+1, count, scopeAddr, scopeNAV.ScopeUUID, scopeNAV.NetAssetValue.Price, scopeNAV.Height, err)) + continue } + totalAdded++ } - ctx.Logger().Info(fmt.Sprintf("Done adding a total of %v scope net asset values with heights.", totalAdded)) + ctx.Logger().Info(fmt.Sprintf("Successfully added %d of %d scope net asset value entries.", totalAdded, count)) } diff --git a/app/upgrades_test.go b/app/upgrades_test.go index 3726d8aef..4b0775577 100644 --- a/app/upgrades_test.go +++ b/app/upgrades_test.go @@ -390,8 +390,10 @@ func (s *UpgradeTestSuite) TestUmberRC1() { "INF Done setting new gov params for testnet.", "INF Updating IBC AllowedClients.", "INF Done updating IBC AllowedClients.", - "INF Adding scope net asset values with heights.", - "INF Done adding a total of 0 scope net asset values with heights.", + "INF Adding scope net asset values.", + "INF Adding 1101 scope net asset value entries.", + "INF Successfully added 0 of 1101 scope net asset value entries.", + "INF Done adding scope net asset values.", "INF Removing inactive validator delegations.", "INF Threshold: 21 days", "INF A total of 0 inactive (unbonded) validators have had all their delegators removed.", @@ -757,18 +759,25 @@ func (s *UpgradeTestSuite) TestSetNewGovParamsMainnet() { s.Assert().Equal(expParams, actParams, "resulting gov params") } -func (s *UpgradeTestSuite) TestAddScopeNavsWithHeight() { +func (s *UpgradeTestSuite) TestAddScopeNAVsWithHeight() { + usdNAV := func(amount int) *metadatatypes.NetAssetValue { + return &metadatatypes.NetAssetValue{Price: sdk.NewInt64Coin(metadatatypes.UsdDenom, int64(amount))} + } + + // Define some scopes and initial NAVs, and store them. address1 := sdk.AccAddress("address1") testScopes := []struct { - name string scopeUUID string + initNAV *metadatatypes.NetAssetValue }{ - {"scope1", "11111111-1111-1111-1111-111111111111"}, - {"scope2", "22222222-2222-2222-2222-222222222222"}, - {"scope3", "33333333-3333-3333-3333-333333333333"}, + {scopeUUID: "11111111-1111-1111-1111-111111111111", initNAV: usdNAV(55)}, + {scopeUUID: "22222222-2222-2222-2222-222222222222"}, + {scopeUUID: "33333333-3333-3333-3333-333333333333"}, + {scopeUUID: "44444444-4444-4444-4444-444444444444"}, + {scopeUUID: "55555555-5555-5555-5555-555555555555", initNAV: usdNAV(73)}, } - for _, ts := range testScopes { + for i, ts := range testScopes { uid, err := uuid.Parse(ts.scopeUUID) s.Require().NoError(err) scopeAddr := metadatatypes.ScopeMetadataAddress(uid) @@ -777,19 +786,38 @@ func (s *UpgradeTestSuite) TestAddScopeNavsWithHeight() { Owners: []metadatatypes.Party{{Address: address1.String(), Role: metadatatypes.PartyType_PARTY_TYPE_OWNER}}, } s.app.MetadataKeeper.SetScope(s.ctx, scope) - } - presentNav := metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 55)) - s.Require().NoError(s.app.MetadataKeeper.SetNetAssetValueWithBlockHeight(s.ctx, metadatatypes.ScopeMetadataAddress(uuid.MustParse("11111111-1111-1111-1111-111111111111")), presentNav, "test", 100)) + if ts.initNAV != nil { + err = s.app.MetadataKeeper.SetNetAssetValueWithBlockHeight(s.ctx, scopeAddr, *ts.initNAV, "test", 100) + s.Require().NoError(err, "[%d/%d]: SetNetAssetValueWithBlockHeight", i+1, len(testScopes)) + } + } - addScopeNavsWithHeight(s.ctx, s.app, []NetAssetValueWithHeight{ + // Define the scope NAVs that we'll be giving to addScopeNAVsWithHeight. + scopeNAVs := []ScopeNAV{ + {ScopeUUID: "22222222-2222-2222-2222-222222222222", NetAssetValue: *usdNAV(12345), Height: 406}, + {ScopeUUID: "12345678-90ab-cdef-123x-567890abcdef", NetAssetValue: *usdNAV(54321), Height: 407}, + {ScopeUUID: "12345678-90ab-cdef-1234-567890abcdef", NetAssetValue: *usdNAV(66666), Height: 408}, { - ScopeUUID: "22222222-2222-2222-2222-222222222222", - NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 12345)), - Height: 406, + ScopeUUID: "44444444-4444-4444-4444-444444444444", + NetAssetValue: metadatatypes.NetAssetValue{ + Price: sdk.Coin{Denom: metadatatypes.UsdDenom, Amount: sdkmath.NewInt(-9001)}, + }, + Height: 409, }, - }) + {ScopeUUID: "55555555-5555-5555-5555-555555555555", NetAssetValue: *usdNAV(987654), Height: 410}, + } + + // Define the expected log output from addScopeNAVsWithHeight when given those NAVs. + expLogOutput := []string{ + "INF Adding 5 scope net asset value entries.", + "ERR [2/5]: Invalid UUID \"12345678-90ab-cdef-123x-567890abcdef\": invalid UUID format.", + "ERR [3/5]: Unable to find scope with UUID \"12345678-90ab-cdef-1234-567890abcdef\".", + "ERR [4/5]: Unable to set scope scope1qpzyg3zyg3zyg3zyg3zyg3zyg3zqyvqcrf (\"44444444-4444-4444-4444-444444444444\") net asset value -9001usd at height 409: negative coin amount: -9001.", + "INF Successfully added 2 of 5 scope net asset value entries.", + } + // Define what the expected resulting NAV entries should be for each scope. tests := []struct { name string scopeUUID string @@ -797,25 +825,51 @@ func (s *UpgradeTestSuite) TestAddScopeNavsWithHeight() { expHeight uint64 }{ { - name: "already has nav", + name: "previously set nav that was not provided", scopeUUID: "11111111-1111-1111-1111-111111111111", - expNav: &metadatatypes.NetAssetValue{Price: sdk.NewInt64Coin(metadatatypes.UsdDenom, 55)}, + expNav: usdNAV(55), expHeight: 100, }, { - name: "nav set from custom config with height", + name: "new nav set", scopeUUID: "22222222-2222-2222-2222-222222222222", - expNav: &metadatatypes.NetAssetValue{Price: sdk.NewInt64Coin(metadatatypes.UsdDenom, 12345)}, + expNav: usdNAV(12345), expHeight: 406, }, { - name: "nav add fails for non-existent scope", + name: "no nav set, was not provided", scopeUUID: "33333333-3333-3333-3333-333333333333", expNav: nil, - expHeight: 0, + }, + { + name: "nav add fails for being invalid", + scopeUUID: "44444444-4444-4444-4444-444444444444", + expNav: nil, + }, + { + name: "nav set over existing entry", + scopeUUID: "55555555-5555-5555-5555-555555555555", + expNav: usdNAV(987654), + expHeight: 410, + }, + { + name: "nav add fails for non-existent scope", + scopeUUID: "12345678-90ab-cdef-1234-567890abcdef", + expNav: nil, }, } + // Call addScopeNAVsWithHeight. + s.logBuffer.Reset() + addScopeNAVsWithHeight(s.ctx, s.app, scopeNAVs) + + // Check the logs. + s.Run("addScopeNAVsWithHeight logs", func() { + actLogOutput := splitLogOutput(s.GetLogOutput("addScopeNAVsWithHeight")) + s.Assert().Equal(expLogOutput, actLogOutput, "addScopeNAVsWithHeight") + }) + + // Check the NAVs. for _, tc := range tests { s.Run(tc.name, func() { uid, err := uuid.Parse(tc.scopeUUID)