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

cmd/wallet: More reserved addresses aliases and checks #134

Merged
merged 1 commit into from
Jan 26, 2024
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
2 changes: 1 addition & 1 deletion cmd/account/withdraw.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ var withdrawCmd = &cobra.Command{
}
} else {
// Destination address is implicit, but obtain it for safety check below nonetheless.
addr, _, err := helpers.ResolveAddress(npa.Network, npa.Account.Address)
addr, _, err := common.ResolveAddress(npa.Network, npa.Account.Address)
cobra.CheckErr(err)
addrToCheck = addr.String()
}
Expand Down
14 changes: 3 additions & 11 deletions cmd/common/selector.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import (
flag "github.com/spf13/pflag"

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

cliConfig "github.com/oasisprotocol/cli/config"
)
Expand Down Expand Up @@ -80,16 +79,9 @@ func GetNPASelection(cfg *cliConfig.Config) *NPASelection {
s.AccountName = selectedAccount
}
if s.AccountName != "" {
if testName := helpers.ParseTestAccountAddress(s.AccountName); testName != "" {
testAcc, err := LoadTestAccountConfig(testName)
cobra.CheckErr(err)
s.Account = testAcc
} else {
s.Account = cfg.Wallet.All[s.AccountName]
if s.Account == nil {
cobra.CheckErr(fmt.Errorf("account '%s' does not exist in the wallet", s.AccountName))
}
}
accCfg, err := LoadAccountConfig(cfg, s.AccountName)
cobra.CheckErr(err)
s.Account = accCfg
}

return &s
Expand Down
140 changes: 134 additions & 6 deletions cmd/common/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package common

import (
"fmt"
"strings"

"github.com/AlecAivazis/survey/v2"
ethCommon "github.com/ethereum/go-ethereum/common"
Expand All @@ -10,6 +11,8 @@ import (
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
configSdk "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/modules/accounts"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/consensusaccounts"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/rewards"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/testing"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"
Expand All @@ -19,10 +22,31 @@ import (
"github.com/oasisprotocol/cli/wallet/test"
)

const (
addressExplicitSeparator = ":"
addressExplicitParaTime = "paratime"
addressExplicitConsensus = "consensus"
addressExplicitPool = "pool"
addressExplicitTest = "test"

// Shared address literals.
poolCommon = "common"
poolFeeAccumulator = "fee-accumulator"

// Consensus address literals.
poolGovernanceDeposits = "governance-deposits"
poolBurn = "burn"

// ParaTime address literals.
poolRewards = "rewards"
poolPendingWithdrawal = "pending-withdrawal"
poolPendingDelegation = "pending-delegation"
)

// LoadAccount loads the given named account.
func LoadAccount(cfg *config.Config, name string) wallet.Account {
// Check if the specified account is a test account.
if testName := helpers.ParseTestAccountAddress(name); testName != "" {
if testName := ParseTestAccountAddress(name); testName != "" {
acc, err := LoadTestAccount(testName)
cobra.CheckErr(err)
return acc
Expand All @@ -49,9 +73,22 @@ func LoadAccount(cfg *config.Config, name string) wallet.Account {
return acc
}

// ParseTestAccountAddress extracts test account name from "test:some_test_account" format or
// returns an empty string, if the format doesn't match.
func ParseTestAccountAddress(name string) string {
if strings.Contains(name, addressExplicitSeparator) {
subs := strings.SplitN(name, addressExplicitSeparator, 2)
if subs[0] == addressExplicitTest {
return subs[1]
}
}

return ""
}

// LoadAccountConfig loads the config instance of the given named account.
func LoadAccountConfig(cfg *config.Config, name string) (*config.Account, error) {
if testName := helpers.ParseTestAccountAddress(name); testName != "" {
if testName := ParseTestAccountAddress(name); testName != "" {
return LoadTestAccountConfig(testName)
}

Expand Down Expand Up @@ -113,7 +150,78 @@ func ResolveLocalAccountOrAddress(net *configSdk.Network, address string) (*type
return &addr, entry.GetEthAddress(), nil
}

return helpers.ResolveAddress(net, address)
return ResolveAddress(net, address)
}

// ResolveAddress resolves a string address into the corresponding account address.
func ResolveAddress(net *configSdk.Network, address string) (*types.Address, *ethCommon.Address, error) {
if addr, ethAddr, _ := helpers.ResolveEthOrOasisAddress(address); addr != nil {
return addr, ethAddr, nil
}

if !strings.Contains(address, addressExplicitSeparator) {
return nil, nil, fmt.Errorf("unsupported address format")
}

subs := strings.SplitN(address, addressExplicitSeparator, 3)
switch kind, data := subs[0], subs[1]; kind {
case addressExplicitParaTime:
// paratime:sapphire, paratime:emerald, paratime:cipher
pt := net.ParaTimes.All[data]
if pt == nil {
return nil, nil, fmt.Errorf("paratime '%s' does not exist", data)
}

addr := types.NewAddressFromConsensus(staking.NewRuntimeAddress(pt.Namespace()))
return &addr, nil, nil
case addressExplicitPool:
// pool:paratime:pending-withdrawal, pool:paratime:fee-accumulator, pool:consensus:fee-accumulator
poolKind, poolName := data, ""
if len(subs) > 2 {
poolName = subs[2]
}
if poolKind == addressExplicitParaTime {
switch poolName {
case poolCommon:
return &accounts.CommonPoolAddress, nil, nil
case poolFeeAccumulator:
return &accounts.FeeAccumulatorAddress, nil, nil
case poolPendingDelegation:
return &consensusaccounts.PendingDelegationAddress, nil, nil
case poolPendingWithdrawal:
return &consensusaccounts.PendingWithdrawalAddress, nil, nil
case poolRewards:
return &rewards.RewardPoolAddress, nil, nil
default:
return nil, nil, fmt.Errorf("unsupported ParaTime pool: %s", poolName)
}
} else if poolKind == addressExplicitConsensus {
var addr types.Address
switch poolName {
case poolBurn:
addr = types.NewAddressFromConsensus(staking.BurnAddress)
case poolCommon:
addr = types.NewAddressFromConsensus(staking.CommonPoolAddress)
case poolFeeAccumulator:
addr = types.NewAddressFromConsensus(staking.FeeAccumulatorAddress)
case poolGovernanceDeposits:
addr = types.NewAddressFromConsensus(staking.GovernanceDepositsAddress)
default:
return nil, nil, fmt.Errorf("unsupported consensus pool: %s", poolName)
}
return &addr, nil, nil
}
return nil, nil, fmt.Errorf("unsupported pool kind: %s. Please use pool:<poolKind>:<poolName>, for example pool:paratime:pending-withdrawal", poolKind)
case addressExplicitTest:
// test:alice, test:dave
if testKey, ok := testing.TestAccounts[data]; ok {
return &testKey.Address, testKey.EthAddress, nil
}
return nil, nil, fmt.Errorf("unsupported test account: %s", data)
default:
// Unsupported kind.
return nil, nil, fmt.Errorf("unsupported explicit address kind: %s", kind)
}
}

// CheckAddressIsConsensusCapable checks whether the given address is derived from any known
Expand Down Expand Up @@ -150,15 +258,35 @@ func CheckAddressIsConsensusCapable(cfg *config.Config, address string) error {
// fee accumulator or the native ParaTime addresses.
func CheckAddressNotReserved(cfg *config.Config, address string) error {
if address == rewards.RewardPoolAddress.String() {
return fmt.Errorf("address '%s' is rewards pool address", address)
return fmt.Errorf("address '%s' is ParaTime rewards pool address", address)
}

if address == accounts.CommonPoolAddress.String() {
return fmt.Errorf("address '%s' is ParaTime common pool address", address)
}

if address == accounts.FeeAccumulatorAddress.String() {
return fmt.Errorf("address '%s' is ParaTime fee accumulator address", address)
}

if address == consensusaccounts.PendingDelegationAddress.String() {
return fmt.Errorf("address '%s' is ParaTime pending delegations address", address)
}

if address == consensusaccounts.PendingWithdrawalAddress.String() {
return fmt.Errorf("address '%s' is ParaTime pending withdrawal address", address)
}

if address == staking.BurnAddress.String() {
return fmt.Errorf("address '%s' is consensus burn address", address)
}

if address == staking.CommonPoolAddress.String() {
return fmt.Errorf("address '%s' is common pool address", address)
return fmt.Errorf("address '%s' is consensus common pool address", address)
}

if address == staking.FeeAccumulatorAddress.String() {
return fmt.Errorf("address '%s' is fee accumulator address", address)
return fmt.Errorf("address '%s' is consensus fee accumulator address", address)
}

if address == staking.GovernanceDepositsAddress.String() {
Expand Down
85 changes: 85 additions & 0 deletions cmd/common/wallet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package common

import (
"testing"

"github.com/stretchr/testify/require"

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

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

net := config.Network{
ParaTimes: config.ParaTimes{
All: map[string]*config.ParaTime{
"pt1": {
ID: "0000000000000000000000000000000000000000000000000000000000000000",
},
},
},
}

for _, tc := range []struct {
address string
expectedAddr string
expectedEthAddr string
}{
{"", "", ""},
{"oasis1", "", ""},
{"oasis1blah", "", ""},
{"oasis1qqzh32kr72v7x55cjnjp2me0pdn579u6as38kacz", "oasis1qqzh32kr72v7x55cjnjp2me0pdn579u6as38kacz", ""},
{"0x", "", ""},
{"0xblah", "", ""},
{"0x60a6321eA71d37102Dbf923AAe2E08d005C4e403", "oasis1qpaqumrpewltmh9mr73hteycfzveus2rvvn8w5sp", "0x60a6321eA71d37102Dbf923AAe2E08d005C4e403"},
{"paratime:", "", ""},
{"paratime:invalid", "", ""},
{"paratime:pt1", "oasis1qqdn25n5a2jtet2s5amc7gmchsqqgs4j0qcg5k0t", ""},
{"pool:", "", ""},
{"pool:invalid", "", ""},
{"pool:paratime:common", "oasis1qz78phkdan64g040cvqvqpwkplfqf6tj6uwcsh30", ""},
{"pool:paratime:fee-accumulator", "oasis1qp3r8hgsnphajmfzfuaa8fhjag7e0yt35cjxq0u4", ""},
{"pool:paratime:rewards", "oasis1qp7x0q9qahahhjas0xde8w0v04ctp4pqzu5mhjav", ""},
{"pool:paratime:pending-delegation", "oasis1qzcdegtf7aunxr5n5pw7n5xs3u7cmzlz9gwmq49r", ""},
{"pool:paratime:pending-withdrawal", "oasis1qr677rv0dcnh7ys4yanlynysvnjtk9gnsyhvm6ln", ""},
{"pool:consensus:burn", "oasis1qzq8u7xs328puu2jy524w3fygzs63rv3u5967970", ""},
{"pool:consensus:common", "oasis1qrmufhkkyyf79s5za2r8yga9gnk4t446dcy3a5zm", ""},
{"pool:consensus:fee-accumulator", "oasis1qqnv3peudzvekhulf8v3ht29z4cthkhy7gkxmph5", ""},
{"pool:consensus:governance-deposits", "oasis1qp65laz8zsa9a305wxeslpnkh9x4dv2h2qhjz0ec", ""},
{"test:alice", "oasis1qrec770vrek0a9a5lcrv0zvt22504k68svq7kzve", ""},
{"test:dave", "oasis1qrk58a6j2qn065m6p06jgjyt032f7qucy5wqeqpt", "0xDce075E1C39b1ae0b75D554558b6451A226ffe00"},
{"test:frank", "oasis1qqnf0s9p8z79zfutszt0hwlh7w7jjrfqnq997mlw", ""},
{"test:invalid", "", ""},
{"invalid:", "", ""},
} {
addr, ethAddr, err := ResolveAddress(&net, tc.address)
if len(tc.expectedAddr) > 0 {
require.NoError(err, tc.address)
require.EqualValues(tc.expectedAddr, addr.String(), tc.address)
if len(tc.expectedEthAddr) > 0 {
require.EqualValues(tc.expectedEthAddr, ethAddr.String())
}
} else {
require.Error(err, tc.address)
}
}
}

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

for _, tc := range []struct {
address string
expected string
}{
{"test:abc", "abc"},
{"testabc", ""},
{"testing:abc", ""},
{"oasis1qqzh32kr72v7x55cjnjp2me0pdn579u6as38kacz", ""},
{"", ""},
} {
testName := ParseTestAccountAddress(tc.address)
require.EqualValues(tc.expected, testName, tc.address)
}
}
3 changes: 1 addition & 2 deletions cmd/network/show.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ import (
staking "github.com/oasisprotocol/oasis-core/go/staking/api"
"github.com/oasisprotocol/oasis-core/go/staking/api/token"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/connection"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/helpers"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/types"

"github.com/oasisprotocol/cli/cmd/common"
Expand Down Expand Up @@ -213,7 +212,7 @@ func parseIdentifier(
return sel, nil
}

addr, _, err := helpers.ResolveAddress(npa.Network, s)
addr, _, err := common.ResolveAddress(npa.Network, s)
if err == nil {
return addr, nil
}
Expand Down
28 changes: 26 additions & 2 deletions docs/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,17 @@ of your default account on the default network and ParaTime:

![code](../examples/account/show.out)

You can also pass the name of the account in your wallet or the name stored in
your address book:
You can also pass the name of the account in your wallet or address book, or one
of the [built-in named addresses](#reserved-addresses):

![code shell](../examples/account/show-named.in)

![code](../examples/account/show-named.out)

![code shell](../examples/account/show-named-pool.in)

![code](../examples/account/show-named-pool.out)

Or, you can check the balance of an arbitrary account address by passing the
native or Ethereum-compatible addresses.

Expand Down Expand Up @@ -648,3 +652,23 @@ assets anymore, but will also permanently remove the tokens from circulation.
command.

:::

### Pools and Reserved Addresses {#reserved-addresses}

The following literals are used in the Oasis CLI to denote special reserved
addresses which cannot be directly used in the ledger:

#### Consensus layer

- `pool:consensus:burn`: The token burn address.
- `pool:consensus:common`: The common pool address.
- `pool:consensus:fee-accumulator`: The per-block fee accumulator address.
- `pool:consensus:governance-deposits`: The governance deposits address.

#### ParaTime layer

- `pool:paratime:common`: The common pool address.
- `pool:paratime:fee-accumulator`: The per-block fee accumulator address.
- `pool:paratime:pending-withdrawal`: The internal pending withdrawal address.
- `pool:paratime:pending-delegation`: The internal pending delegation address.
- `pool:paratime:rewards`: The reward pool address.
1 change: 1 addition & 0 deletions examples/account/show-named-pool.in
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
oasis acc show pool:consensus:fee-accumulator
8 changes: 8 additions & 0 deletions examples/account/show-named-pool.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Address: oasis1qqnv3peudzvekhulf8v3ht29z4cthkhy7gkxmph5

=== CONSENSUS LAYER (testnet) ===
Nonce: 0

Total: 0.0 TEST
Available: 0.0 TEST

2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ require (
github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7
github.com/oasisprotocol/metadata-registry-tools v0.0.0-20220406100644-7e9a2b991920
github.com/oasisprotocol/oasis-core/go v0.2300.9
github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.7.1
github.com/oasisprotocol/oasis-sdk/client-sdk/go v0.7.2
github.com/olekukonko/tablewriter v0.0.5
github.com/spf13/cobra v1.8.0
github.com/spf13/pflag v1.0.5
Expand Down
Loading
Loading