Skip to content

Commit

Permalink
Merge pull request #134 from oasisprotocol/matevz/feature/more-reserv…
Browse files Browse the repository at this point in the history
…ed-addresses

cmd/wallet: More reserved addresses aliases and checks
  • Loading branch information
matevz authored Jan 26, 2024
2 parents 629d715 + 6763d65 commit 1b97709
Show file tree
Hide file tree
Showing 10 changed files with 262 additions and 25 deletions.
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

0 comments on commit 1b97709

Please sign in to comment.