Skip to content
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
5 changes: 4 additions & 1 deletion cmd/account/show/allowances.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (

staking "github.com/oasisprotocol/oasis-core/go/staking/api"

"github.com/oasisprotocol/cli/cmd/common"

"github.com/oasisprotocol/oasis-sdk/client-sdk/go/config"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"
Expand Down Expand Up @@ -61,7 +63,8 @@ func prettyPrintAllowanceDescriptions(
lenLongest := lenLongestString(beneficiaryFieldName, amountFieldName)

for _, desc := range allowDescriptions {
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, beneficiaryFieldName, desc.beneficiary)
prettyAddr := common.PrettyAddress(desc.beneficiary.String())
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, beneficiaryFieldName, prettyAddr)
if desc.self {
fmt.Fprintf(w, " (self)")
}
Expand Down
11 changes: 10 additions & 1 deletion cmd/account/show/delegations.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import (
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/consensusaccounts"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

"github.com/oasisprotocol/cli/cmd/common"
)

const amountFieldName = "Amount:"
Expand Down Expand Up @@ -87,6 +89,12 @@ func prettyPrintDelegationDescriptions(

fmt.Fprintf(w, "%sDelegations:\n", prefix)

// Guard against empty slice to prevent panic when accessing delDescriptions[0].
if len(delDescriptions) == 0 {
fmt.Fprintf(w, "%s <none>\n", prefix)
return
}

sort.Sort(byEndTimeAmountAddress(delDescriptions))

// Get the length of name of the longest field to display for each
Expand All @@ -103,7 +111,8 @@ func prettyPrintDelegationDescriptions(
}

for _, desc := range delDescriptions {
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, addressFieldName, desc.address)
prettyAddr := common.PrettyAddress(desc.address.String())
fmt.Fprintf(w, "%s - %-*s %s", prefix, lenLongest, addressFieldName, prettyAddr)
if desc.self {
fmt.Fprintf(w, " (self)")
}
Expand Down
15 changes: 13 additions & 2 deletions cmd/account/show/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,9 +83,20 @@ var (

nativeAddr, ethAddr, err := common.ResolveLocalAccountOrAddress(npa.Network, targetAddress)
cobra.CheckErr(err)
out.EthereumAddress = ethAddr
out.NativeAddress = nativeAddr
out.Name = common.FindAccountName(nativeAddr.String())
addrCtx := common.GenAddressFormatContext()
out.Name = addrCtx.Names[nativeAddr.String()]

// If eth address is not available, try to get it from locally-known mappings
// (wallet/addressbook/test accounts). No unlock required.
if ethAddr == nil {
if ethHex := addrCtx.Eth[nativeAddr.String()]; ethHex != "" && ethCommon.IsHexAddress(ethHex) {
eth := ethCommon.HexToAddress(ethHex)
ethAddr = &eth
}
}

out.EthereumAddress = ethAddr

height, err := common.GetActualHeight(
ctx,
Expand Down
142 changes: 136 additions & 6 deletions cmd/common/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@ package common
import (
"fmt"
"os"
"sort"

"github.com/spf13/cobra"

staking "github.com/oasisprotocol/oasis-core/go/staking/api"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/testing"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

buildRoflProvider "github.com/oasisprotocol/cli/build/rofl/provider"
"github.com/oasisprotocol/cli/config"
)

Expand Down Expand Up @@ -41,19 +45,66 @@ func CheckForceErr(err interface{}) {
cobra.CheckErr(errMsg)
}

// GenAccountNames generates a map of all addresses -> account name for pretty printing.
// GenAccountNames generates a map of all known native addresses -> account name for pretty printing.
// It includes test accounts, configured networks (paratimes/ROFL defaults), addressbook and wallet.
//
// Priority order (later entries overwrite earlier):
// test accounts < network entries < addressbook < wallet.
func GenAccountNames() types.AccountNames {
an := types.AccountNames{}
for name, acc := range config.Global().Wallet.All {
an[acc.GetAddress().String()] = name

// Test accounts have lowest priority.
for name, acc := range testing.TestAccounts {
an[acc.Address.String()] = fmt.Sprintf("test:%s", name)
}

// Network-derived entries (paratimes, ROFL providers) have second-lowest priority.
cfg := config.Global()
netNames := make([]string, 0, len(cfg.Networks.All))
for name := range cfg.Networks.All {
netNames = append(netNames, name)
}
sort.Strings(netNames)
for _, netName := range netNames {
net := cfg.Networks.All[netName]
if net == nil {
continue
}

// Include ParaTime runtime addresses as paratime:<name>.
ptNames := make([]string, 0, len(net.ParaTimes.All))
for ptName := range net.ParaTimes.All {
ptNames = append(ptNames, ptName)
}
sort.Strings(ptNames)
for _, ptName := range ptNames {
pt := net.ParaTimes.All[ptName]
if pt == nil {
continue
}

rtAddr := types.NewAddressFromConsensus(staking.NewRuntimeAddress(pt.Namespace()))
an[rtAddr.String()] = fmt.Sprintf("paratime:%s", ptName)

// Include ROFL default provider addresses as rofl:provider:<paratime>.
if svc, ok := buildRoflProvider.DefaultRoflServices[pt.ID]; ok {
if svc.Provider != "" {
if a, _, err := helpers.ResolveEthOrOasisAddress(svc.Provider); err == nil && a != nil {
an[a.String()] = fmt.Sprintf("rofl:provider:%s", ptName)
}
}
}
}
}

for name, acc := range config.Global().AddressBook.All {
// Addressbook entries have medium priority.
for name, acc := range cfg.AddressBook.All {
an[acc.GetAddress().String()] = name
}

for name, acc := range testing.TestAccounts {
an[acc.Address.String()] = fmt.Sprintf("test:%s", name)
// Wallet entries have highest priority.
for name, acc := range cfg.Wallet.All {
an[acc.GetAddress().String()] = name
}

return an
Expand All @@ -64,3 +115,82 @@ func FindAccountName(address string) string {
an := GenAccountNames()
return an[address]
}

// AddressFormatContext contains precomputed maps for address formatting.
type AddressFormatContext struct {
// Names maps native address string to account name.
Names types.AccountNames
// Eth maps native address string to Ethereum hex address string, if known.
Eth map[string]string
}

// GenAccountEthMap generates a map of native address string -> eth hex address (if known).
// Priority order matches GenAccountNames: test accounts < addressbook < wallet.
func GenAccountEthMap() map[string]string {
eth := make(map[string]string)

for _, acc := range testing.TestAccounts {
if acc.EthAddress != nil {
eth[acc.Address.String()] = acc.EthAddress.Hex()
}
}

for _, acc := range config.Global().AddressBook.All {
if ethAddr := acc.GetEthAddress(); ethAddr != nil {
eth[acc.GetAddress().String()] = ethAddr.Hex()
}
}

for _, acc := range config.Global().Wallet.All {
if ethAddr := acc.GetEthAddress(); ethAddr != nil {
eth[acc.GetAddress().String()] = ethAddr.Hex()
}
}

return eth
}

// GenAddressFormatContext builds both name and eth address maps for formatting.
func GenAddressFormatContext() AddressFormatContext {
return AddressFormatContext{
Names: GenAccountNames(),
Eth: GenAccountEthMap(),
}
}

// PrettyAddressWith formats the address in native or eth string for display using a precomputed context.
// Known addresses return "name (preferred_addr)", unknown addresses return the input unchanged.
func PrettyAddressWith(ctx AddressFormatContext, addr string) string {
nativeAddr, ethFromInput, err := helpers.ResolveEthOrOasisAddress(addr)
if err != nil || nativeAddr == nil {
return addr
}

nativeStr := nativeAddr.String()

name := ctx.Names[nativeStr]
if name == "" {
return addr
}

// Prefer eth address in parentheses when available.
var parenAddr string
if ethFromInput != nil {
parenAddr = ethFromInput.Hex()
} else if ethHex := ctx.Eth[nativeStr]; ethHex != "" {
parenAddr = ethHex
} else {
parenAddr = nativeStr
}

if name == parenAddr {
return parenAddr
}

return fmt.Sprintf("%s (%s)", name, parenAddr)
}

// PrettyAddress is like PrettyAddressWith but builds a fresh context on each call.
func PrettyAddress(addr string) string {
return PrettyAddressWith(GenAddressFormatContext(), addr)
}
69 changes: 69 additions & 0 deletions cmd/common/helpers_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package common

import (
"testing"

"github.com/stretchr/testify/require"

"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"
)

func TestPrettyAddressWith(t *testing.T) {
require := require.New(t)

nativeAddr, ethAddr, err := helpers.ResolveEthOrOasisAddress("0x60a6321eA71d37102Dbf923AAe2E08d005C4e403")
require.NoError(err)
require.NotNil(nativeAddr)
require.NotNil(ethAddr)

t.Run("eth preferred when known", func(_ *testing.T) {
ctx := AddressFormatContext{
Names: types.AccountNames{
nativeAddr.String(): "my",
},
Eth: map[string]string{
nativeAddr.String(): ethAddr.Hex(),
},
}

require.Equal("my ("+ethAddr.Hex()+")", PrettyAddressWith(ctx, nativeAddr.String()))
require.Equal("my ("+ethAddr.Hex()+")", PrettyAddressWith(ctx, ethAddr.Hex()))
})

t.Run("native fallback when eth unknown", func(_ *testing.T) {
ctx := AddressFormatContext{
Names: types.AccountNames{
nativeAddr.String(): "my",
},
Eth: map[string]string{},
}

require.Equal("my ("+nativeAddr.String()+")", PrettyAddressWith(ctx, nativeAddr.String()))
// If the user explicitly provided an Ethereum address, prefer it even if not in ctx.Eth.
require.Equal("my ("+ethAddr.Hex()+")", PrettyAddressWith(ctx, ethAddr.Hex()))
})

t.Run("unknown returns unchanged", func(_ *testing.T) {
ctx := AddressFormatContext{
Names: types.AccountNames{},
Eth: map[string]string{},
}

require.Equal(nativeAddr.String(), PrettyAddressWith(ctx, nativeAddr.String()))
require.Equal(ethAddr.Hex(), PrettyAddressWith(ctx, ethAddr.Hex()))
})

t.Run("unparseable returns unchanged", func(_ *testing.T) {
ctx := AddressFormatContext{
Names: types.AccountNames{
nativeAddr.String(): "my",
},
Eth: map[string]string{
nativeAddr.String(): ethAddr.Hex(),
},
}

require.Equal("not-an-address", PrettyAddressWith(ctx, "not-an-address"))
})
}
20 changes: 19 additions & 1 deletion cmd/common/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,6 +152,11 @@ func JSONMarshalUniversalValue(v interface{}) []byte {
// For types implementing consensusPretty.PrettyPrinter, it uses the custom pretty printer.
// For other types, it does basic JSON indentation and cleanup of common delimiters.
func PrettyPrint(npa *NPASelection, prefix string, blob interface{}) string {
return PrettyPrintWithTxDetails(npa, prefix, blob, nil)
}

// PrettyPrintWithTxDetails is like PrettyPrint but passes txDetails to the signature context.
func PrettyPrintWithTxDetails(npa *NPASelection, prefix string, blob interface{}, txDetails *signature.TxDetails) string {
ret := ""
switch rtx := blob.(type) {
case consensusPretty.PrettyPrinter:
Expand All @@ -164,6 +169,7 @@ func PrettyPrint(npa *NPASelection, prefix string, blob interface{}) string {
RuntimeID: ns,
ChainContext: npa.Network.ChainContext,
Base: types.SignatureContextBase,
TxDetails: txDetails,
}
ctx := context.Background()
ctx = context.WithValue(ctx, consensusPretty.ContextKeyTokenSymbol, npa.Network.Denomination.Symbol)
Expand All @@ -172,7 +178,19 @@ func PrettyPrint(npa *NPASelection, prefix string, blob interface{}) string {
ctx = context.WithValue(ctx, config.ContextKeyParaTimeCfg, npa.ParaTime)
}
ctx = context.WithValue(ctx, signature.ContextKeySigContext, &sigCtx)
ctx = context.WithValue(ctx, types.ContextKeyAccountNames, GenAccountNames())

// Provide locally-known names and native->ETH mapping for address formatting.
addrCtx := GenAddressFormatContext()

// Inject the original Ethereum "To" address into the eth map so that
// FormatNamedAddressWith can prefer it over the native representation.
if txDetails != nil && txDetails.OrigTo != nil {
native := types.NewAddressFromEth(txDetails.OrigTo.Bytes()).String()
addrCtx.Eth[native] = txDetails.OrigTo.Hex()
}

ctx = context.WithValue(ctx, types.ContextKeyAccountNames, addrCtx.Names)
ctx = context.WithValue(ctx, types.ContextKeyAccountEthMap, addrCtx.Eth)

// Set up chain context for signature verification during pretty-printing.
coreSignature.UnsafeResetChainContext()
Expand Down
Loading
Loading