Skip to content

Commit b4fc6b9

Browse files
feat: format known addresses for consensus txes
test: cover consensusaccounts.Undelegate PrettyPrint feat: preserve user-provided eth addresses
1 parent e137daf commit b4fc6b9

5 files changed

Lines changed: 183 additions & 10 deletions

File tree

client-sdk/go/modules/consensusaccounts/types.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,8 @@ type Undelegate struct {
6565
}
6666

6767
// PrettyPrint writes a pretty-printed representation of the transaction to the given writer.
68-
func (ud *Undelegate) PrettyPrint(_ context.Context, prefix string, w io.Writer) {
69-
_, _ = fmt.Fprintf(w, "%sFrom: %s\n", prefix, ud.From)
68+
func (ud *Undelegate) PrettyPrint(ctx context.Context, prefix string, w io.Writer) {
69+
_, _ = fmt.Fprintf(w, "%sFrom: %s\n", prefix, types.FormatNamedAddress(ctx, ud.From))
7070
_, _ = fmt.Fprintf(w, "%sShares: %s\n", prefix, ud.Shares)
7171
}
7272

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package consensusaccounts
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/hex"
7+
"testing"
8+
9+
"github.com/stretchr/testify/require"
10+
11+
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"
12+
13+
"github.com/oasisprotocol/oasis-core/go/common/quantity"
14+
)
15+
16+
func TestUndelegatePrettyPrintFromNamedAddress(t *testing.T) {
17+
require := require.New(t)
18+
19+
ethHex := "0x60a6321ea71d37102dbf923aae2e08d005c4e403"
20+
ethBytes, err := hex.DecodeString(ethHex[2:])
21+
require.NoError(err)
22+
23+
addr := types.NewAddressFromEth(ethBytes)
24+
native := addr.String()
25+
26+
shares := *quantity.NewFromUint64(1234)
27+
28+
ctx := context.Background()
29+
ctx = context.WithValue(ctx, types.ContextKeyAccountNames, types.AccountNames{native: "my"})
30+
ctx = context.WithValue(ctx, types.ContextKeyAccountEthMap, map[string]string{native: ethHex})
31+
32+
ud := Undelegate{
33+
From: addr,
34+
Shares: shares,
35+
}
36+
37+
var buf bytes.Buffer
38+
ud.PrettyPrint(ctx, "", &buf)
39+
40+
require.Equal("From: my ("+ethHex+")\nShares: "+shares.String()+"\n", buf.String())
41+
}

client-sdk/go/types/address.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,13 @@ var (
4141

4242
type contextKey string
4343

44-
// ContextKeyAccountNames is the key to retrieve the public key to account name map from context.
44+
// ContextKeyAccountNames is the key to retrieve the native (Bech32) address string to account name map from context.
4545
const ContextKeyAccountNames = contextKey("runtime/account-names")
4646

47+
// ContextKeyAccountEthMap is the key to retrieve a mapping from native (Bech32)
48+
// Oasis addresses to their corresponding Ethereum hex addresses.
49+
const ContextKeyAccountEthMap = contextKey("runtime/account-eth-map")
50+
4751
// AccountNames maps public key or address to user-defined account name for pretty printing.
4852
type AccountNames map[string]string
4953

client-sdk/go/types/token.go

Lines changed: 80 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"github.com/oasisprotocol/oasis-core/go/common/quantity"
1010

1111
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/config"
12+
sdkSignature "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature"
1213
)
1314

1415
// Quantity is a arbitrary precision unsigned integer that never underflows.
@@ -87,17 +88,89 @@ func NewBaseUnits(amount quantity.Quantity, denomination Denomination) BaseUnits
8788
}
8889
}
8990

91+
// FormatNamedAddress returns a human-friendly representation of an address.
92+
//
93+
// It prints the name (if known) followed by the preferred form of the address
94+
// in parentheses. If an Ethereum hex address mapping is provided for the native
95+
// address, it is used; otherwise the native Bech32 address is used.
96+
func FormatNamedAddress(ctx context.Context, addr Address) string {
97+
var (
98+
names AccountNames
99+
ethMap map[string]string
100+
)
101+
if v, ok := ctx.Value(ContextKeyAccountNames).(AccountNames); ok {
102+
names = v
103+
}
104+
if v, ok := ctx.Value(ContextKeyAccountEthMap).(map[string]string); ok {
105+
ethMap = v
106+
}
107+
108+
// Preserve the user-provided Ethereum address even when the account is unnamed.
109+
if origToHex, ok := origToHexForAddress(ctx, addr); ok {
110+
native := addr.String()
111+
name := ""
112+
if names != nil {
113+
name = names[native]
114+
}
115+
116+
switch name {
117+
case "", origToHex:
118+
return origToHex
119+
default:
120+
return fmt.Sprintf("%s (%s)", name, origToHex)
121+
}
122+
}
123+
124+
return FormatNamedAddressWith(names, ethMap, addr)
125+
}
126+
127+
// FormatNamedAddressWith is a pure helper for address formatting to make testing easier.
128+
func FormatNamedAddressWith(names AccountNames, ethMap map[string]string, addr Address) string {
129+
native := addr.String()
130+
131+
name := ""
132+
if names != nil {
133+
name = names[native]
134+
}
135+
if name == "" {
136+
return native
137+
}
138+
139+
preferred := native
140+
if ethMap != nil {
141+
if hex := ethMap[native]; hex != "" {
142+
preferred = hex
143+
}
144+
}
145+
146+
// Guard against redundant "name (name)" output.
147+
if name == preferred {
148+
return preferred
149+
}
150+
151+
return fmt.Sprintf("%s (%s)", name, preferred)
152+
}
153+
154+
func origToHexForAddress(ctx context.Context, addr Address) (string, bool) {
155+
sc, ok := ctx.Value(sdkSignature.ContextKeySigContext).(*sdkSignature.RichContext)
156+
if !ok || sc == nil || sc.TxDetails == nil || sc.TxDetails.OrigTo == nil {
157+
return "", false
158+
}
159+
160+
// Only apply OrigTo if it matches the address being printed.
161+
derived := NewAddressFromEth(sc.TxDetails.OrigTo.Bytes())
162+
if !derived.Equal(addr) {
163+
return "", false
164+
}
165+
166+
return sc.TxDetails.OrigTo.Hex(), true
167+
}
168+
90169
// PrettyPrintToAmount is a helper for printing To-Amount transaction bodies (e.g. transfer, deposit, withdraw).
91170
func PrettyPrintToAmount(ctx context.Context, prefix string, w io.Writer, to *Address, amount BaseUnits) {
92171
toStr := "Self"
93172
if to != nil {
94-
toStr = to.String()
95-
an, ok := ctx.Value(ContextKeyAccountNames).(AccountNames)
96-
if ok {
97-
if name, ok := an[to.String()]; ok {
98-
toStr = fmt.Sprintf("%s (%s)", name, to)
99-
}
100-
}
173+
toStr = FormatNamedAddress(ctx, *to)
101174
}
102175
_, _ = fmt.Fprintf(w, "%sTo: %s\n", prefix, toStr)
103176
_, _ = fmt.Fprintf(w, "%sAmount: ", prefix)

client-sdk/go/types/token_test.go

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@ import (
66
"encoding/hex"
77
"testing"
88

9+
ethCommon "github.com/ethereum/go-ethereum/common"
910
"github.com/stretchr/testify/require"
1011

1112
"github.com/oasisprotocol/oasis-core/go/common/cbor"
1213
"github.com/oasisprotocol/oasis-core/go/common/quantity"
1314

1415
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/config"
16+
sdkSignature "github.com/oasisprotocol/oasis-sdk/client-sdk/go/crypto/signature"
1517
)
1618

1719
func TestToken(t *testing.T) {
@@ -76,9 +78,62 @@ func TestPrettyPrintToAmount(t *testing.T) {
7678
PrettyPrintToAmount(ctx, "", &buf, &to, amt)
7779
require.Equal("To: test:dave (oasis1qrk58a6j2qn065m6p06jgjyt032f7qucy5wqeqpt)\nAmount: 50.0 TEST\n", buf.String())
7880

81+
// Preserve the user-provided Ethereum address even when the account is unnamed.
82+
buf.Reset()
83+
ethAddr := ethCommon.HexToAddress("0x60a6321ea71d37102dbf923aae2e08d005c4e403")
84+
ethTo := NewAddressFromEth(ethAddr.Bytes())
85+
ctx2 := context.Background()
86+
ctx2 = context.WithValue(ctx2, config.ContextKeyParaTimeCfg, ptCfg)
87+
ctx2 = context.WithValue(ctx2, sdkSignature.ContextKeySigContext, &sdkSignature.RichContext{
88+
TxDetails: &sdkSignature.TxDetails{OrigTo: &ethAddr},
89+
})
90+
PrettyPrintToAmount(ctx2, "", &buf, &ethTo, amt)
91+
require.Equal("To: "+ethAddr.Hex()+"\nAmount: 50.0 TEST\n", buf.String())
92+
7993
// No ParaTime set. Amount cannot be correctly determined.
8094
buf.Reset()
8195
ctx = context.WithValue(ctx, config.ContextKeyParaTimeCfg, nil)
8296
PrettyPrintToAmount(ctx, "", &buf, &to, amt)
8397
require.Equal("To: test:dave (oasis1qrk58a6j2qn065m6p06jgjyt032f7qucy5wqeqpt)\nAmount: <error: ParaTime information not available>\n", buf.String())
8498
}
99+
100+
func TestFormatNamedAddressWith(t *testing.T) {
101+
require := require.New(t)
102+
103+
ethHex := "0x60a6321ea71d37102dbf923aae2e08d005c4e403"
104+
ethBytes, err := hex.DecodeString(ethHex[2:])
105+
require.NoError(err)
106+
107+
addr := NewAddressFromEth(ethBytes)
108+
native := addr.String()
109+
110+
t.Run("unknown returns native", func(_ *testing.T) {
111+
require.Equal(native, FormatNamedAddressWith(nil, nil, addr))
112+
require.Equal(native, FormatNamedAddressWith(AccountNames{}, map[string]string{}, addr))
113+
})
114+
115+
t.Run("native fallback when eth unknown", func(_ *testing.T) {
116+
names := AccountNames{native: "my"}
117+
require.Equal("my ("+native+")", FormatNamedAddressWith(names, nil, addr))
118+
require.Equal("my ("+native+")", FormatNamedAddressWith(names, map[string]string{}, addr))
119+
require.Equal("my ("+native+")", FormatNamedAddressWith(names, map[string]string{native: ""}, addr))
120+
})
121+
122+
t.Run("eth preferred when known", func(_ *testing.T) {
123+
names := AccountNames{native: "my"}
124+
ethMap := map[string]string{native: ethHex}
125+
require.Equal("my ("+ethHex+")", FormatNamedAddressWith(names, ethMap, addr))
126+
})
127+
128+
t.Run("name equals preferred yields preferred", func(_ *testing.T) {
129+
names := AccountNames{native: native}
130+
require.Equal(native, FormatNamedAddressWith(names, nil, addr))
131+
})
132+
133+
t.Run("ctx wrapper reads maps", func(_ *testing.T) {
134+
ctx := context.Background()
135+
ctx = context.WithValue(ctx, ContextKeyAccountNames, AccountNames{native: "my"})
136+
ctx = context.WithValue(ctx, ContextKeyAccountEthMap, map[string]string{native: ethHex})
137+
require.Equal("my ("+ethHex+")", FormatNamedAddress(ctx, addr))
138+
})
139+
}

0 commit comments

Comments
 (0)