Skip to content

Commit

Permalink
feat(txtar): create user from passed mnemonic, account and index (gno…
Browse files Browse the repository at this point in the history
…lang#1879)

<!-- please provide a detailed description of the changes made in this
pull request. -->

`adduser` in txtar creates user with bit of
[randomness](https://github.com/gnolang/gno/blob/831bb6f92e1a2217242169dab1f4fd1f87e5eaa0/gno.land/pkg/integration/testing_integration.go#L471-L474)
which results different address are being added for every time we run
txtar testing.
> To test SPECIFIC ADDRESS, we need to get rid of `randomness`

This PR implements `adduserfrom`, which creates a user from given
mnemonic (optionally account and index can be also passed to derive
different address from same mnemonic)


<details><summary>Contributors' checklist...</summary>

- [x] Added new tests, or not needed, or not feasible
- [ ] Provided an example (e.g. screenshot) to aid review or the PR is
self-explanatory
- [x] Updated the official documentation or not needed
- [x] No breaking changes were made, or a `BREAKING CHANGE: xxx` message
was included in the description
- [ ] Added references to related issues and PRs
- [ ] Provided any useful hints for running manual tests
- [ ] Added new benchmarks to [generated
graphs](https://gnoland.github.io/benchmarks), if any. More info
[here](https://github.com/gnolang/gno/blob/master/.benchmarks/README.md).
</details>

---------

Co-authored-by: Guilhem Fanton <8671905+gfanton@users.noreply.github.com>
  • Loading branch information
r3v4s and gfanton committed Apr 25, 2024
1 parent 688edf5 commit c1fd8d5
Show file tree
Hide file tree
Showing 3 changed files with 121 additions and 4 deletions.
6 changes: 5 additions & 1 deletion gno.land/pkg/integration/doc.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,11 @@
// - Must be run before `gnoland start`.
// - Creates a new user in the default keybase directory.
//
// 4. `loadpkg`:
// 4. `adduserfrom`:
// - Must be run before `gnoland start`.
// - Creates a new user in the default keybase directory from a given seed. ( Optionally, account and index can be provided )
//
// 5. `loadpkg`:
// - Must be run before `gnoland start`.
// - Loads a specific package from the 'examples' directory or from the working ($WORK) directory.
// - Can be used to load a single package or all packages within a directory.
Expand Down
34 changes: 34 additions & 0 deletions gno.land/pkg/integration/testdata/adduserfrom.txtar
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# adduserfrom just mnemonic
adduserfrom user1 'source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast'
stdout 'g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5'

# adduserfrom mnemonic and account
adduserfrom user2 'source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast' 1
stdout 'g18e22n23g462drp4pyszyl6e6mwxkaylthgeeq4'

# adduserfrom mnemonic, account and index
adduserfrom user3 'source bonus chronic canvas draft south burst lottery vacant surface solve popular case indicate oppose farm nothing bullet exhibit title speed wink action roast' 1 1
stdout 'g1mtmrdmqfu0aryqfl4aw65n35haw2wdjkh5p4cp'

## start a new node
gnoland start

## check users initial balance
gnokey query bank/balances/${USER_ADDR_user1}
stdout '10000000ugnot'

gnokey query bank/balances/g18e22n23g462drp4pyszyl6e6mwxkaylthgeeq4
stdout '10000000ugnot'

gnokey query auth/accounts/${USER_ADDR_user3}
stdout 'height: 0'
stdout 'data: {'
stdout ' "BaseAccount": {'
stdout ' "address": "g1mtmrdmqfu0aryqfl4aw65n35haw2wdjkh5p4cp",'
stdout ' "coins": "10000000ugnot",'
stdout ' "public_key": null,'
stdout ' "account_number": "58",'
stdout ' "sequence": "0"'
stdout ' }'
stdout '}'
! stderr '.+' # empty
85 changes: 82 additions & 3 deletions gno.land/pkg/integration/testing_integration.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
break
}

// get pacakges
// get packages
pkgs := ts.Value(envKeyPkgsLoader).(*pkgsLoader) // grab logger
creator := crypto.MustAddressFromString(DefaultAccount_Address) // test1
defaultFee := std.NewFee(50000, std.MustParseCoin("1000000ugnot"))
Expand All @@ -193,7 +193,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
// Register cleanup
nodes[sid] = &testNode{Node: n}

// Add default environements
// Add default environments
ts.Setenv("RPC_ADDR", remoteAddr)

fmt.Fprintln(ts.Stdout(), "node started successfully")
Expand All @@ -206,7 +206,7 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
if err = n.Stop(); err == nil {
delete(nodes, sid)

// Unset gnoland environements
// Unset gnoland environments
ts.Setenv("RPC_ADDR", "")
fmt.Fprintln(ts.Stdout(), "node stopped successfully")
}
Expand Down Expand Up @@ -284,6 +284,61 @@ func setupGnolandTestScript(t *testing.T, txtarDir string) testscript.Params {
genesis := ts.Value(envKeyGenesis).(*gnoland.GnoGenesisState)
genesis.Balances = append(genesis.Balances, balance)
},
// adduserfrom commands must be executed before starting the node; it errors out otherwise.
"adduserfrom": func(ts *testscript.TestScript, neg bool, args []string) {
if nodeIsRunning(nodes, getNodeSID(ts)) {
tsValidateError(ts, "adduserfrom", neg, errors.New("adduserfrom must be used before starting node"))
return
}

var account, index uint64
var err error

switch len(args) {
case 2:
// expected user input
// adduserfrom 'username 'menmonic'
// no need to do anything

case 4:
// expected user input
// adduserfrom 'username 'menmonic' 'account' 'index'

// parse 'index' first, then fallghrough to `case 3` to parse 'account'
index, err = strconv.ParseUint(args[3], 10, 32)
if err != nil {
ts.Fatalf("invalid index number %s", args[3])
}

fallthrough // parse 'account'
case 3:
// expected user input
// adduserfrom 'username 'menmonic' 'account'

account, err = strconv.ParseUint(args[2], 10, 32)
if err != nil {
ts.Fatalf("invalid account number %s", args[2])
}
default:
ts.Fatalf("to create account from metadatas, user name and mnemonic are required ( account and index are optional )")
}

kb, err := keys.NewKeyBaseFromDir(gnoHomeDir)
if err != nil {
ts.Fatalf("unable to get keybase")
}

balance, err := createAccountFrom(ts, kb, args[0], args[1], uint32(account), uint32(index))
if err != nil {
ts.Fatalf("error creating wallet %s", err)
}

// Add balance to genesis
genesis := ts.Value(envKeyGenesis).(*gnoland.GnoGenesisState)
genesis.Balances = append(genesis.Balances, balance)

fmt.Fprintf(ts.Stdout(), "Added %s(%s) to genesis", args[0], balance.Address)
},
// `loadpkg` load a specific package from the 'examples' or working directory
"loadpkg": func(ts *testscript.TestScript, neg bool, args []string) {
// special dirs
Expand Down Expand Up @@ -493,6 +548,30 @@ func createAccount(env envSetter, kb keys.Keybase, accountName string) (gnoland.
}, nil
}

// createAccountFrom creates a new account with the given metadata and adds it to the keybase.
func createAccountFrom(env envSetter, kb keys.Keybase, accountName, mnemonic string, account, index uint32) (gnoland.Balance, error) {
var balance gnoland.Balance

// check if mnemonic is valid
if !bip39.IsMnemonicValid(mnemonic) {
return balance, fmt.Errorf("invalid mnemonic")
}

keyInfo, err := kb.CreateAccount(accountName, mnemonic, "", "", account, index)
if err != nil {
return balance, fmt.Errorf("unable to create account: %w", err)
}

address := keyInfo.GetAddress()
env.Setenv("USER_SEED_"+accountName, mnemonic)
env.Setenv("USER_ADDR_"+accountName, address.String())

return gnoland.Balance{
Address: address,
Amount: std.Coins{std.NewCoin("ugnot", 10e6)},
}, nil
}

type pkgsLoader struct {
pkgs []gnomod.Pkg
visited map[string]struct{}
Expand Down

0 comments on commit c1fd8d5

Please sign in to comment.