Merge pull request #3255 from smallhive/3177-wallet-from-bytes
wallet: Add new wallet constructors
This commit is contained in:
commit
3766206f44
2 changed files with 113 additions and 43 deletions
|
@ -1,6 +1,7 @@
|
||||||
package wallet
|
package wallet
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
@ -18,6 +19,13 @@ const (
|
||||||
walletVersion = "1.0"
|
walletVersion = "1.0"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
// ErrPathIsEmpty appears if wallet was created without linking to file system path,
|
||||||
|
// for instance with [NewInMemoryWallet] or [NewWalletFromBytes].
|
||||||
|
// Despite this, there was an attempt to save it via [Wallet.Save] or [Wallet.SavePretty] without [Wallet.SetPath].
|
||||||
|
ErrPathIsEmpty = errors.New("path is empty")
|
||||||
|
)
|
||||||
|
|
||||||
// Wallet represents a NEO (NEP-2, NEP-6) compliant wallet.
|
// Wallet represents a NEO (NEP-2, NEP-6) compliant wallet.
|
||||||
type Wallet struct {
|
type Wallet struct {
|
||||||
// Version of the wallet, used for later upgrades.
|
// Version of the wallet, used for later upgrades.
|
||||||
|
@ -53,6 +61,12 @@ func NewWallet(location string) (*Wallet, error) {
|
||||||
return newWallet(file), nil
|
return newWallet(file), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewInMemoryWallet creates a new NEO wallet without linking to the read file on file system.
|
||||||
|
// If wallet required to be written to the file system, [Wallet.SetPath] should be used to set the path.
|
||||||
|
func NewInMemoryWallet() *Wallet {
|
||||||
|
return newWallet(nil)
|
||||||
|
}
|
||||||
|
|
||||||
// NewWalletFromFile creates a Wallet from the given wallet file path.
|
// NewWalletFromFile creates a Wallet from the given wallet file path.
|
||||||
func NewWalletFromFile(path string) (*Wallet, error) {
|
func NewWalletFromFile(path string) (*Wallet, error) {
|
||||||
file, err := os.Open(path)
|
file, err := os.Open(path)
|
||||||
|
@ -70,6 +84,20 @@ func NewWalletFromFile(path string) (*Wallet, error) {
|
||||||
return wall, nil
|
return wall, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NewWalletFromBytes creates a [Wallet] from the given byte slice.
|
||||||
|
// Parameter wallet contains JSON representation of wallet, see [Wallet.JSON] for details.
|
||||||
|
//
|
||||||
|
// NewWalletFromBytes constructor doesn't set wallet's path. If you want to save the wallet to file system,
|
||||||
|
// use [Wallet.SetPath].
|
||||||
|
func NewWalletFromBytes(wallet []byte) (*Wallet, error) {
|
||||||
|
wall := &Wallet{}
|
||||||
|
if err := json.NewDecoder(bytes.NewReader(wallet)).Decode(wall); err != nil {
|
||||||
|
return nil, fmt.Errorf("unmarshal wallet: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return wall, nil
|
||||||
|
}
|
||||||
|
|
||||||
func newWallet(rw io.ReadWriter) *Wallet {
|
func newWallet(rw io.ReadWriter) *Wallet {
|
||||||
var path string
|
var path string
|
||||||
if f, ok := rw.(*os.File); ok {
|
if f, ok := rw.(*os.File); ok {
|
||||||
|
@ -138,9 +166,15 @@ func (w *Wallet) Path() string {
|
||||||
return w.path
|
return w.path
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save saves the wallet data. It's the internal io.ReadWriter
|
// SetPath sets the location of the wallet on the filesystem.
|
||||||
// that is responsible for saving the data. This can
|
func (w *Wallet) SetPath(path string) {
|
||||||
// be a buffer, file, etc..
|
w.path = path
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save saves the wallet data to the file located at the path that was either provided
|
||||||
|
// via [NewWalletFromFile] constructor or via [Wallet.SetPath].
|
||||||
|
//
|
||||||
|
// Returns [ErrPathIsEmpty] if wallet path is not set. See [Wallet.SetPath].
|
||||||
func (w *Wallet) Save() error {
|
func (w *Wallet) Save() error {
|
||||||
data, err := json.Marshal(w)
|
data, err := json.Marshal(w)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -151,6 +185,8 @@ func (w *Wallet) Save() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// SavePretty saves the wallet in a beautiful JSON.
|
// SavePretty saves the wallet in a beautiful JSON.
|
||||||
|
//
|
||||||
|
// Returns [ErrPathIsEmpty] if wallet path is not set. See [Wallet.SetPath].
|
||||||
func (w *Wallet) SavePretty() error {
|
func (w *Wallet) SavePretty() error {
|
||||||
data, err := json.MarshalIndent(w, "", " ")
|
data, err := json.MarshalIndent(w, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -161,6 +197,10 @@ func (w *Wallet) SavePretty() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *Wallet) writeRaw(data []byte) error {
|
func (w *Wallet) writeRaw(data []byte) error {
|
||||||
|
if w.path == "" {
|
||||||
|
return ErrPathIsEmpty
|
||||||
|
}
|
||||||
|
|
||||||
return os.WriteFile(w.path, data, 0644)
|
return os.WriteFile(w.path, data, 0644)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -47,9 +47,13 @@ func TestCreateAccountAndClose(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestAddAccount(t *testing.T) {
|
func TestAddAccount(t *testing.T) {
|
||||||
wallet := checkWalletConstructor(t)
|
wallets := []*Wallet{
|
||||||
|
checkWalletConstructor(t),
|
||||||
|
NewInMemoryWallet(),
|
||||||
|
}
|
||||||
|
|
||||||
wallet.AddAccount(&Account{
|
for _, w := range wallets {
|
||||||
|
w.AddAccount(&Account{
|
||||||
privateKey: nil,
|
privateKey: nil,
|
||||||
Address: "real",
|
Address: "real",
|
||||||
EncryptedWIF: "",
|
EncryptedWIF: "",
|
||||||
|
@ -58,13 +62,14 @@ func TestAddAccount(t *testing.T) {
|
||||||
Locked: false,
|
Locked: false,
|
||||||
Default: false,
|
Default: false,
|
||||||
})
|
})
|
||||||
accounts := wallet.Accounts
|
accounts := w.Accounts
|
||||||
require.Len(t, accounts, 1)
|
require.Len(t, accounts, 1)
|
||||||
|
|
||||||
require.Error(t, wallet.RemoveAccount("abc"))
|
require.Error(t, w.RemoveAccount("abc"))
|
||||||
require.Len(t, wallet.Accounts, 1)
|
require.Len(t, w.Accounts, 1)
|
||||||
require.NoError(t, wallet.RemoveAccount("real"))
|
require.NoError(t, w.RemoveAccount("real"))
|
||||||
require.Len(t, wallet.Accounts, 0)
|
require.Len(t, w.Accounts, 0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPath(t *testing.T) {
|
func TestPath(t *testing.T) {
|
||||||
|
@ -75,9 +80,19 @@ func TestPath(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSave(t *testing.T) {
|
func TestSave(t *testing.T) {
|
||||||
wallet := checkWalletConstructor(t)
|
inMemWallet := NewInMemoryWallet()
|
||||||
|
|
||||||
wallet.AddAccount(&Account{
|
tmpDir := t.TempDir()
|
||||||
|
file := filepath.Join(tmpDir, walletTemplate)
|
||||||
|
inMemWallet.SetPath(file)
|
||||||
|
|
||||||
|
wallets := []*Wallet{
|
||||||
|
checkWalletConstructor(t),
|
||||||
|
inMemWallet,
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, w := range wallets {
|
||||||
|
w.AddAccount(&Account{
|
||||||
privateKey: nil,
|
privateKey: nil,
|
||||||
Address: "",
|
Address: "",
|
||||||
EncryptedWIF: "",
|
EncryptedWIF: "",
|
||||||
|
@ -87,12 +102,12 @@ func TestSave(t *testing.T) {
|
||||||
Default: false,
|
Default: false,
|
||||||
})
|
})
|
||||||
|
|
||||||
errForSave := wallet.Save()
|
errForSave := w.Save()
|
||||||
require.NoError(t, errForSave)
|
require.NoError(t, errForSave)
|
||||||
|
|
||||||
openedWallet, err := NewWalletFromFile(wallet.path)
|
openedWallet, err := NewWalletFromFile(w.path)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, wallet.Accounts, openedWallet.Accounts)
|
require.Equal(t, w.Accounts, openedWallet.Accounts)
|
||||||
|
|
||||||
t.Run("change and rewrite", func(t *testing.T) {
|
t.Run("change and rewrite", func(t *testing.T) {
|
||||||
err := openedWallet.CreateAccount("test", "pass")
|
err := openedWallet.CreateAccount("test", "pass")
|
||||||
|
@ -106,6 +121,7 @@ func TestSave(t *testing.T) {
|
||||||
require.Equal(t, openedWallet.Accounts, w2.Accounts)
|
require.Equal(t, openedWallet.Accounts, w2.Accounts)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestJSONMarshallUnmarshal(t *testing.T) {
|
func TestJSONMarshallUnmarshal(t *testing.T) {
|
||||||
wallet := checkWalletConstructor(t)
|
wallet := checkWalletConstructor(t)
|
||||||
|
@ -198,3 +214,17 @@ func TestWalletForExamples(t *testing.T) {
|
||||||
// we need to keep the owner of the example contracts the same as the wallet account
|
// we need to keep the owner of the example contracts the same as the wallet account
|
||||||
require.Equal(t, "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB", w.Accounts[0].Address, "need to change `owner` in the example contracts")
|
require.Equal(t, "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB", w.Accounts[0].Address, "need to change `owner` in the example contracts")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestFromBytes(t *testing.T) {
|
||||||
|
wallet := checkWalletConstructor(t)
|
||||||
|
bts, err := wallet.JSON()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
w, err := NewWalletFromBytes(bts)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Len(t, w.path, 0)
|
||||||
|
w.SetPath(wallet.path)
|
||||||
|
|
||||||
|
require.Equal(t, wallet, w)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue