Skip to content

Commit

Permalink
cmd: Add reserved addresses, migrate ResolveAddress
Browse files Browse the repository at this point in the history
  • Loading branch information
matevz committed Jan 25, 2024
1 parent 629d715 commit 11e24c6
Show file tree
Hide file tree
Showing 6 changed files with 223 additions and 20 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
128 changes: 122 additions & 6 deletions cmd/common/wallet.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ package common

import (
"fmt"
"strings"

"github.com/AlecAivazis/survey/v2"
ethCommon "github.com/ethereum/go-ethereum/common"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/accounts"
"github.com/oasisprotocol/oasis-sdk/client-sdk/go/modules/consensusaccounts"
"github.com/spf13/cobra"

staking "github.com/oasisprotocol/oasis-core/go/staking/api"
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 poolRewards:
return &rewards.RewardPoolAddress, nil, nil
case poolPendingWithdrawal:
return &consensusaccounts.PendingWithdrawalAddress, nil, nil

Check failure on line 188 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / lint

undefined: consensusaccounts.PendingWithdrawalAddress

Check failure on line 188 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / lint

undefined: consensusaccounts.PendingWithdrawalAddress

Check failure on line 188 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / lint

undefined: consensusaccounts.PendingWithdrawalAddress

Check failure on line 188 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / tests-ubuntu-latest

undefined: consensusaccounts.PendingWithdrawalAddress
case poolPendingDelegation:
return &consensusaccounts.PendingDelegationAddress, nil, nil

Check failure on line 190 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / lint

undefined: consensusaccounts.PendingDelegationAddress) (typecheck)

Check failure on line 190 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / lint

undefined: consensusaccounts.PendingDelegationAddress) (typecheck)

Check failure on line 190 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / lint

undefined: consensusaccounts.PendingDelegationAddress) (typecheck)

Check failure on line 190 in cmd/common/wallet.go

View workflow job for this annotation

GitHub Actions / tests-ubuntu-latest

undefined: consensusaccounts.PendingDelegationAddress
case poolCommon:
return &accounts.CommonPoolAddress, nil, nil
case poolFeeAccumulator:
return &accounts.FeeAccumulatorAddress, nil, nil
default:
return nil, nil, fmt.Errorf("unsupported ParaTime pool: %s", poolName)
}
} else if poolKind == addressExplicitConsensus {
var addr types.Address
switch poolName {
case poolCommon:
addr = types.NewAddressFromConsensus(staking.CommonPoolAddress)
case poolFeeAccumulator:
addr = types.NewAddressFromConsensus(staking.FeeAccumulatorAddress)
case poolGovernanceDeposits:
addr = types.NewAddressFromConsensus(staking.GovernanceDepositsAddress)
case poolBurn:
addr = types.NewAddressFromConsensus(staking.BurnAddress)
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,23 @@ 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 == 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
76 changes: 76 additions & 0 deletions cmd/common/wallet_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
package common

import (
"testing"

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

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:rewards", "oasis1qp7x0q9qahahhjas0xde8w0v04ctp4pqzu5mhjav", ""},
{"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
20 changes: 20 additions & 0 deletions docs/account.md
Original file line number Diff line number Diff line change
Expand Up @@ -648,3 +648,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.

0 comments on commit 11e24c6

Please sign in to comment.