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

Improve metadata scope nav cli command documentation #2160

Merged
merged 12 commits into from
Oct 2, 2024
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
* Fixes the metadata nav cli command example to use the correct module name [#2058](https://github.com/provenance-io/provenance/issues/2058)
During this fix it was discovered that the volume parameter was not present but was required for proper price ratios. The volume
parameter has been added to the NAV entry and when not present a default value of 1 (which should be the most common case for a scope) is
used instead.
1 change: 1 addition & 0 deletions .changelog/unreleased/improvements/2030-explicit-navs.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Only set a NAV record when explicitly provided. [#2030](https://github.com/provenance-io/provenance/issues/2030).
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
* Address missing documentation on metadata/scope nav command [#2134](https://github.com/provenance-io/provenance/issues/2134).
2 changes: 1 addition & 1 deletion app/scope_navs_updater.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func ReadScopeNAVs(fileName string) ([]ScopeNAV, error) {

asset := ScopeNAV{
ScopeUUID: scopeUUID,
NetAssetValue: metadatatypes.NewNetAssetValue(price),
NetAssetValue: metadatatypes.NewNetAssetValue(price, 1),
Height: height,
}

Expand Down
8 changes: 4 additions & 4 deletions app/scope_navs_updater_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ func TestReadScopeNAVs(t *testing.T) {
expCount: 1101,
expFirst: ScopeNAV{
ScopeUUID: "2f389a9f-873d-4920-85a6-7734f27e1738",
NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 398820670)),
NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 398820670), 1),
iramiller marked this conversation as resolved.
Show resolved Hide resolved
Height: 23056719,
},
expLast: ScopeNAV{
ScopeUUID: "65939db0-6d7a-42ef-9443-378304d33225",
NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 93661920)),
NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 93661920), 1),
Height: 23056719,
},
},
Expand All @@ -37,12 +37,12 @@ func TestReadScopeNAVs(t *testing.T) {
expCount: 215558,
expFirst: ScopeNAV{
ScopeUUID: "b0b97639-5ecf-4808-b679-99c11a5cda47",
NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 47395000)),
NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 47395000), 1),
Height: 14871216,
},
expLast: ScopeNAV{
ScopeUUID: "98503480-12be-4142-bd9d-e80c6e017e22",
NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 43768160)),
NetAssetValue: metadatatypes.NewNetAssetValue(sdk.NewInt64Coin(metadatatypes.UsdDenom, 43768160), 1),
Height: 9787583,
},
},
Expand Down
1 change: 1 addition & 0 deletions proto/provenance/metadata/v1/events.proto
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,5 @@ message EventSetNetAssetValue {
string scope_id = 1;
string price = 2;
string source = 3;
string volume = 4;
}
4 changes: 4 additions & 0 deletions proto/provenance/metadata/v1/scope.proto
Original file line number Diff line number Diff line change
Expand Up @@ -229,4 +229,8 @@ message NetAssetValue {
cosmos.base.v1beta1.Coin price = 1 [(gogoproto.nullable) = false];
// updated_block_height is the block height of last update
uint64 updated_block_height = 2;
// volume is the number of scope instances that were purchased for the price
// Typically this will be null (equivalent to one) or one. The only reason this would be more than
// one is for cases where the precision of the price denom is insufficient to represent the actual price
uint64 volume = 3;
}
3 changes: 0 additions & 3 deletions x/marker/keeper/marker.go
Original file line number Diff line number Diff line change
Expand Up @@ -447,9 +447,6 @@ func (k Keeper) FinalizeMarker(ctx sdk.Context, caller sdk.Address, denom string
if err != nil {
return err
}
if len(navs) == 0 {
return fmt.Errorf("marker %v does not have any net asset values assigned", denom)
}

// transition to finalized state ... then to active once mint is complete
if err = m.SetStatus(types.StatusFinalized); err != nil {
Expand Down
27 changes: 18 additions & 9 deletions x/marker/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -114,12 +114,17 @@ func (k msgServer) AddMarker(goCtx context.Context, msg *types.MsgAddMarkerReque
return nil, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
}

usdMills := sdkmath.NewIntFromUint64(msg.UsdMills)
nav := types.NewNetAssetValue(sdk.NewCoin(types.UsdDenom, usdMills), msg.Volume)
err = k.AddSetNetAssetValues(ctx, ma, []types.NetAssetValue{nav}, types.ModuleName)
if err != nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
// Only create a NAV entry if an explicit value is given for a NAV. If a zero value is desired this can be set explicitly in a followup call.
// This check prevents a proliferation of incorrect NAV entries being recorded when setting up markers.
if msg.UsdMills > 0 {
usdMills := sdkmath.NewIntFromUint64(msg.UsdMills)
nav := types.NewNetAssetValue(sdk.NewCoin(types.UsdDenom, usdMills), msg.Volume)
err = k.AddSetNetAssetValues(ctx, ma, []types.NetAssetValue{nav}, types.ModuleName)
if err != nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
}
}

// Note: The status can only be Active if this is being done via gov prop.
if ma.Status == types.StatusActive {
// Active markers should have supply set.
Expand Down Expand Up @@ -517,10 +522,14 @@ func (k msgServer) AddFinalizeActivateMarker(goCtx context.Context, msg *types.M
normalizedReqAttrs,
)

usdMills := sdkmath.NewIntFromUint64(msg.UsdMills)
err = k.AddSetNetAssetValues(ctx, ma, []types.NetAssetValue{types.NewNetAssetValue(sdk.NewCoin(types.UsdDenom, usdMills), msg.Volume)}, types.ModuleName)
if err != nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
// Only create a NAV entry if an explicit value is given for a NAV. If a zero value is desired this can be set explicitly in a followup call.
// This check prevents a proliferation of incorrect NAV entries being recorded when setting up markers.
if msg.UsdMills > 0 {
usdMills := sdkmath.NewIntFromUint64(msg.UsdMills)
err = k.AddSetNetAssetValues(ctx, ma, []types.NetAssetValue{types.NewNetAssetValue(sdk.NewCoin(types.UsdDenom, usdMills), msg.Volume)}, types.ModuleName)
if err != nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
}
}

if err := k.Keeper.AddFinalizeAndActivateMarker(ctx, ma); err != nil {
Expand Down
23 changes: 0 additions & 23 deletions x/marker/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,12 +108,6 @@ func (s *MsgServerTestSuite) TestMsgAddMarkerRequest() {
Manager: s.owner1,
MarkerType: types.MarkerType_Coin.String(),
},
&types.EventSetNetAssetValue{
Denom: denom,
Price: "0usd",
Volume: "0",
Source: types.ModuleName,
},
},
},
{
Expand Down Expand Up @@ -212,12 +206,6 @@ func (s *MsgServerTestSuite) TestMsgAddMarkerRequest() {
Manager: s.owner1,
MarkerType: types.MarkerType_Coin.String(),
},
&types.EventSetNetAssetValue{
Denom: denomWithDashPeriod,
Price: "0usd",
Volume: "0",
Source: types.ModuleName,
},
},
},
{
Expand All @@ -242,12 +230,6 @@ func (s *MsgServerTestSuite) TestMsgAddMarkerRequest() {
Manager: s.owner1,
MarkerType: types.MarkerType_RestrictedCoin.String(),
},
&types.EventSetNetAssetValue{
Denom: rdenom,
Price: "0usd",
Volume: "0",
Source: types.ModuleName,
},
},
},
}
Expand Down Expand Up @@ -311,11 +293,6 @@ func (s *MsgServerTestSuite) TestMsgFinalizeMarkerRequest() {
msg types.MsgFinalizeRequest
expErr string
}{
{
name: "marker does not have net asset value",
msg: types.MsgFinalizeRequest{Denom: noNavMarker.Denom, Administrator: authUser.String()},
expErr: "marker nonav does not have any net asset values assigned: invalid request",
},
{
name: "successfully finalize",
msg: types.MsgFinalizeRequest{Denom: validMarker.Denom, Administrator: authUser.String()},
Expand Down
20 changes: 16 additions & 4 deletions x/metadata/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3860,7 +3860,7 @@ func (s *IntegrationCLITestSuite) TestGetCmdAddNetAssetValues() {
},
{
name: "successful with multi net asset values",
args: argsWStdFlags(scopeID, "1usd,2jackthecat"),
args: argsWStdFlags(scopeID, "1usd;2jackthecat"),
},
}

Expand Down Expand Up @@ -3924,17 +3924,29 @@ func (s *IntegrationCLITestSuite) TestParseNetAssertValueString() {
expErr: "invalid net asset value coin : notacoin",
expResult: []types.NetAssetValue{},
},
{
name: "invalid volume (previous format of comma separated coins is invalid)",
netAssetValues: "1hotdog,5cheesedog",
expErr: "invalid volume : 5cheesedog",
expResult: []types.NetAssetValue{},
},
{
name: "successfully parse single nav",
netAssetValues: "1hotdog,10",
expErr: "",
expResult: []types.NetAssetValue{{Price: sdk.NewInt64Coin("hotdog", 1), Volume: 10}},
},
{
name: "successfully parse single nav (no volume)",
netAssetValues: "1hotdog",
expErr: "",
expResult: []types.NetAssetValue{{Price: sdk.NewInt64Coin("hotdog", 1)}},
expResult: []types.NetAssetValue{{Price: sdk.NewInt64Coin("hotdog", 1), Volume: 1}},
},
{
name: "successfully parse multi nav",
netAssetValues: "1hotdog,20jackthecat",
netAssetValues: "1hotdog,20;20jackthecat",
expErr: "",
expResult: []types.NetAssetValue{{Price: sdk.NewInt64Coin("hotdog", 1)}, {Price: sdk.NewInt64Coin("jackthecat", 20)}},
expResult: []types.NetAssetValue{{Price: sdk.NewInt64Coin("hotdog", 1), Volume: 20}, {Price: sdk.NewInt64Coin("jackthecat", 20), Volume: 1}},
},
}
for _, tc := range testCases {
Expand Down
70 changes: 60 additions & 10 deletions x/metadata/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package cli

import (
"encoding/base64"
"errors"
"fmt"
"strconv"
"strings"

"github.com/google/uuid"
Expand Down Expand Up @@ -1190,11 +1192,47 @@ $ %[1]s tx metadata account-data %[2]s --%[5]s
// GetCmdAddNetAssetValues returns a CLI command for adding/updating scopes net asset values.
func GetCmdAddNetAssetValues() *cobra.Command {
cmd := &cobra.Command{
Use: "add-net-asset-values <scope-metadata-address> " + attrcli.AccountDataFlagsUse,
Aliases: []string{"add-navs", "anavs"},
Short: "Add/updates net asset values for a scope",
Example: fmt.Sprintf(`$ %[1]s tx marker add-net-asset-values scope1234... 1usd,1;2nhash,3`,
version.AppName),
Use: "add-net-asset-values <scope-metadata-address> <valuation[;valuation...]>",
Aliases: []string{"add-nav", "nav"},
Short: "Provide net asset value for a scope",
Long: `
Provide net asset valuation for a scope. Net asset values are used to establish
the relative value of the digital asset in relation to other assets or currencies.

Net asset values are expressed as a ratio between an amount of coin paid (price)
and the scope (generally one) which are considered equivalent in value.

The denomination of the amount paid (price) must either:

1) Exist on-chain as a marker, or
2) Be supplied as [1000usd], an integer valued in mils (1000 mils = $1 USD).

IMPORTANT: The net asset value contains a volume which should be set to one
in almost all cases. All values must be represented as whole integers. If a
decimal value is required, adjust the ratio between the price and volume as the
least common denominator to achieve the desired precision.
`,
iramiller marked this conversation as resolved.
Show resolved Hide resolved
Example: fmt.Sprintf(`
Set a value of $1 (Note USD is denominated in mils)
$ %[1]s tx %[2]s add-net-asset-values %[3]s 1000usd

Set a value of $1 (Note USD is denominated in mils) and include optional volume
$ %[1]s tx %[2]s add-net-asset-values %[3]s 1000usd,1

Provide more than one valuation in a single call
$ %[1]s tx %[2]s add-net-asset-values %[3]s 1000usd;5000000000nhash,1

Valuation for asset with volumes greater than 1 to adjust for high value price denom
$ %[1]s tx %[2]s add-net-asset-values %[3]s 1btc,60000

Note: When the valuations are recorded, each will indicate the address of the admin
who provided the value. This will be published in the associated event data and
captured in the NAV record. For NAVs set by other modules such as x/exchange the
protocol will indicate these sources. This separates established values from
the owner (self-attestation) from those set through blockchain transactions.
`,
version.AppName, types.ModuleName, "scope1qzhp...tsk0cn"),

Args: cobra.ExactArgs(2),
RunE: func(cmd *cobra.Command, args []string) error {
clientCtx, err := client.GetClientTxContext(cmd)
Expand Down Expand Up @@ -1264,19 +1302,31 @@ func validateAccAddress(addr, argName string) (string, error) {
return addr, nil
}

// ParseNetAssetValueString splits string (example 1hotdog,2jackthecat) to list of NetAssetValue's
// ParseNetAssetValueString splits string (example 1hotdog,1;2jackthecat100,...) to list of NetAssetValue's
func ParseNetAssetValueString(netAssetValuesString string) ([]types.NetAssetValue, error) {
navs := strings.Split(netAssetValuesString, ",")
navs := strings.Split(netAssetValuesString, ";")
if len(navs) == 1 && len(navs[0]) == 0 {
return []types.NetAssetValue{}, nil
}
netAssetValues := make([]types.NetAssetValue, len(navs))
for i, nav := range navs {
coin, err := sdk.ParseCoinNormalized(nav)
parts := strings.Split(nav, ",")
SpicyLemon marked this conversation as resolved.
Show resolved Hide resolved
if len(parts) != 1 && len(parts) != 2 {
return []types.NetAssetValue{}, errors.New("invalid net asset value, expected [coin,volume] or [coin]")
}
coin, err := sdk.ParseCoinNormalized(parts[0])
if err != nil {
return []types.NetAssetValue{}, fmt.Errorf("invalid net asset value coin : %s", nav)
return []types.NetAssetValue{}, fmt.Errorf("invalid net asset value coin : %s", parts[0])
}
if len(parts) == 2 {
volume, err := strconv.ParseUint(parts[1], 10, 64)
if err != nil {
return []types.NetAssetValue{}, fmt.Errorf("invalid volume : %s", parts[1])
}
netAssetValues[i] = types.NewNetAssetValue(coin, volume)
} else {
netAssetValues[i] = types.NewNetAssetValue(coin, 1)
}
netAssetValues[i] = types.NewNetAssetValue(coin)
}
return netAssetValues, nil
iramiller marked this conversation as resolved.
Show resolved Hide resolved
}
7 changes: 6 additions & 1 deletion x/metadata/keeper/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,12 @@ func (k Keeper) InitGenesis(ctx sdk.Context, data *types.GenesisState) {
if err != nil {
panic(err)
}
err = k.SetNetAssetValue(ctx, address, types.NewNetAssetValue(nav.Price), types.ModuleName)
// Extra guard here in case volume is null or invalid
volume := nav.GetVolume()
if volume < 1 {
volume = 1
}
err = k.SetNetAssetValue(ctx, address, types.NewNetAssetValue(nav.Price, volume), types.ModuleName)
if err != nil {
panic(err)
}
Expand Down
15 changes: 10 additions & 5 deletions x/metadata/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,11 +46,16 @@ func (k msgServer) WriteScope(
return nil, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
}

usdMills := sdkmath.NewIntFromUint64(msg.UsdMills)
nav := types.NewNetAssetValue(sdk.NewCoin(types.UsdDenom, usdMills))
err := k.AddSetNetAssetValues(ctx, msg.Scope.ScopeId, []types.NetAssetValue{nav}, types.ModuleName)
if err != nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
// Do not set a NAV entry at this time unless a value greater than zero is specified. This avoids the common case of
// not having a NAV entry value at hand during a scope write request. A zero value can still be set explicitly with
// an add NAV call made separately.
if msg.UsdMills > 0 {
usdMills := sdkmath.NewIntFromUint64(msg.UsdMills)
nav := types.NewNetAssetValue(sdk.NewCoin(types.UsdDenom, usdMills), 1)
err := k.AddSetNetAssetValues(ctx, msg.Scope.ScopeId, []types.NetAssetValue{nav}, types.ModuleName)
if err != nil {
return nil, sdkerrors.ErrInvalidRequest.Wrap(err.Error())
}
}

k.SetScope(ctx, msg.Scope)
Expand Down
Loading
Loading