Skip to content

Commit b342237

Browse files
committed
fix: Format wasp-cli message in json
1 parent 7484724 commit b342237

File tree

8 files changed

+218
-81
lines changed

8 files changed

+218
-81
lines changed

tools/cluster/cluster.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ func Retry(fn func() error, retries int) error {
8383
return err
8484
}
8585

86-
func New(name string, config *ClusterConfig, dataPath string, t *testing.T, log log.Logger, l1PacakgeID *iotago.PackageID) *Cluster {
86+
func New(name string, config *ClusterConfig, dataPath string, t *testing.T, log log.Logger, l1PackageID *iotago.PackageID) *Cluster {
8787
if log == nil {
8888
if t == nil {
8989
panic("one of t or log must be set")
@@ -94,7 +94,7 @@ func New(name string, config *ClusterConfig, dataPath string, t *testing.T, log
9494

9595
config.setValidatorAddressIfNotSet() // privtangle prefix
9696
for i := range config.Wasp {
97-
config.Wasp[i].PackageID = l1PacakgeID
97+
config.Wasp[i].PackageID = l1PackageID
9898
}
9999
client := config.L1Client()
100100
return &Cluster{

tools/cluster/tests/wasp-cli_test.go

Lines changed: 52 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"crypto/ecdsa"
66
"encoding/json"
7+
"errors"
78
"fmt"
89
"io"
910
"math/big"
@@ -23,6 +24,7 @@ import (
2324
"github.com/iotaledger/wasp/v2/packages/testutil/testkey"
2425
"github.com/iotaledger/wasp/v2/packages/vm/gas"
2526
"github.com/iotaledger/wasp/v2/tools/cluster"
27+
"github.com/iotaledger/wasp/v2/tools/wasp-cli/format"
2628
)
2729

2830
func TestWaspAuth(t *testing.T) {
@@ -137,10 +139,10 @@ func TestZeroGasFee(t *testing.T) {
137139
t.Run("deposit directly to EVM", func(t *testing.T) {
138140
alternativeAddress := getAddressFromJSON(w.MustRun("wallet", "address", "--address-index=1", "--json"))
139141
w.MustRun("wallet", "send-funds", alternativeAddress, "base|1000000")
140-
outs := w.MustRun("wallet", "balance", "--address-index=1")
142+
w.MustRun("wallet", "balance", "--address-index=1")
141143
_, eth := newEthereumAccount()
142144
w.MustRun("chain", "deposit", eth.String(), "base|1000000", "--node=0")
143-
outs = w.MustRun("chain", "balance", eth.String(), "--node=0")
145+
outs = w.MustRun("chain", "balance", eth.String(), "--node=0", "--json")
144146
checkL2Balance(t, outs, 1000000)
145147
})
146148
}
@@ -199,13 +201,27 @@ func checkL1BalanceJSON(t *testing.T, out []string, expected int) {
199201

200202
func checkL2Balance(t *testing.T, out []string, expected int) {
201203
t.Helper()
202-
r := regexp.MustCompile(`.*(?i:base)\s*(?i:tokens)?:*\s*(\d+).*`).FindStringSubmatch(strings.Join(out, ""))
203-
if r == nil {
204-
panic("couldn't check balance")
204+
205+
rawOutput := strings.Join(out, "\n")
206+
207+
var balanceOutput format.ChainBalanceOutput
208+
err := json.Unmarshal([]byte(rawOutput), &balanceOutput)
209+
require.NoError(t, err, "Expected valid JSON output, got: %v", rawOutput)
210+
211+
// Find the base token in the coins array
212+
var baseAmount int64
213+
found := false
214+
for _, coin := range balanceOutput.Coins {
215+
if coin.Token == "base" {
216+
baseAmount, err = strconv.ParseInt(coin.Amount, 10, 64)
217+
require.NoError(t, err)
218+
found = true
219+
break
220+
}
205221
}
206-
amount, err := strconv.Atoi(r[1])
207-
require.NoError(t, err)
208-
require.EqualValues(t, expected, amount)
222+
223+
require.True(t, found, "base token not found in balance output")
224+
require.EqualValues(t, expected, baseAmount, "Expected base token balance to be %d, got %d", expected, baseAmount)
209225
}
210226

211227
// getAddressFromJSON extracts the address from JSON output
@@ -283,25 +299,25 @@ func TestWaspCLIDeposit(t *testing.T) {
283299
t.Run("deposit directly to EVM", func(t *testing.T) {
284300
_, eth := newEthereumAccount()
285301
w.MustRun("chain", "deposit", "base|1000000", "--node=0")
286-
outs := w.MustRun("chain", "deposit", eth.String(), "base|10000", "--node=0", "--print-receipt")
287-
outs = w.MustRun("chain", "balance", eth.String(), "--node=0")
302+
w.MustRun("chain", "deposit", eth.String(), "base|10000", "--node=0", "--print-receipt")
303+
outs = w.MustRun("chain", "balance", eth.String(), "--node=0", "--json")
288304
checkL2Balance(t, outs, 10000)
289305
})
290306

291307
t.Run("deposit to own account, then to EVM", func(t *testing.T) {
292308
const depositAmount = int64(1_000_000)
293309
w.MustRun("wallet", "request-funds", "--address-index=2")
294-
outs = w.MustRun("chain", "deposit", "base|1000000", "--address-index=2", "--node=0", "--print-receipt")
310+
outs = w.MustRun("chain", "deposit", "base|1000000", "--address-index=2", "--node=0", "--print-receipt", "--json")
295311
l2GasFee := getL2GasFee(t, outs)
296-
outs = w.MustRun("chain", "balance", "--address-index=2", "--node=0")
312+
outs = w.MustRun("chain", "balance", "--address-index=2", "--node=0", "--json")
297313
checkL2Balance(t, outs, int(depositAmount-l2GasFee))
298-
outs := w.MustRun("wallet", "balance", "--address-index=2", "--json")
314+
w.MustRun("wallet", "balance", "--address-index=2", "--json")
299315
_, eth := newEthereumAccount()
300-
outs = w.MustRun("chain", "deposit", eth.String(), "base|1000000", "--address-index=2", "--node=0", "--print-receipt")
316+
outs = w.MustRun("chain", "deposit", eth.String(), "base|1000000", "--address-index=2", "--node=0", "--print-receipt", "--json")
301317
l2GasFee = getL2GasFee(t, outs)
302-
outs = w.MustRun("chain", "balance", eth.String(), "--node=0")
318+
outs = w.MustRun("chain", "balance", eth.String(), "--node=0", "--json")
303319
checkL2Balance(t, outs, 1000000) // receiver gets full amount
304-
outs = w.MustRun("chain", "balance", "--address-index=2", "--node=0")
320+
outs = w.MustRun("chain", "balance", "--address-index=2", "--node=0", "--json")
305321
expectedL2Balance := int(depositAmount - l2GasFee - int64(minFee))
306322
checkL2Balance(t, outs, expectedL2Balance)
307323
})
@@ -383,18 +399,28 @@ func TestWaspCLIDeposit(t *testing.T) {
383399
}
384400

385401
func getL2GasFee(t *testing.T, outs []string) int64 {
386-
var err error
387-
var l2GasFee int64
388-
re := regexp.MustCompile(`Gas fee charged:\s*(\d+)`)
389-
390-
for _, line := range outs {
391-
matches := re.FindStringSubmatch(line)
392-
if len(matches) > 1 {
393-
l2GasFee, err = strconv.ParseInt(matches[1], 10, 64)
394-
require.NoError(t, err)
402+
t.Helper()
403+
404+
rawOutput := strings.Join(outs, "\n")
405+
decoder := json.NewDecoder(strings.NewReader(rawOutput))
406+
407+
for {
408+
var receipt format.ChainReceiptOutput
409+
if err := decoder.Decode(&receipt); err != nil {
410+
if errors.Is(err, io.EOF) {
411+
break
412+
}
413+
require.FailNowf(t, "failed to decode CLI output", "error: %v\noutput:\n%s", err, rawOutput)
414+
}
415+
if receipt.Type != "chain_receipt" {
416+
continue
395417
}
418+
l2GasFee, err := strconv.ParseInt(receipt.GasFeeCharged, 10, 64)
419+
require.NoError(t, err)
420+
return l2GasFee
396421
}
397-
return l2GasFee
422+
423+
panic("gas fee not found")
398424
}
399425

400426
func findRequestIDInOutput(out []string) string {

tools/wasp-cli/chain/accounts.go

Lines changed: 20 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"github.com/iotaledger/wasp/v2/packages/vm/core/governance"
2121
"github.com/iotaledger/wasp/v2/tools/wasp-cli/cli/cliclients"
2222
"github.com/iotaledger/wasp/v2/tools/wasp-cli/cli/config"
23+
"github.com/iotaledger/wasp/v2/tools/wasp-cli/format"
2324
"github.com/iotaledger/wasp/v2/tools/wasp-cli/log"
2425
"github.com/iotaledger/wasp/v2/tools/wasp-cli/util"
2526
"github.com/iotaledger/wasp/v2/tools/wasp-cli/waspcmd"
@@ -50,20 +51,25 @@ func initBalanceCmd() *cobra.Command {
5051
return err
5152
}
5253

53-
header := []string{"token", "amount"}
54-
rows := make([][]string, len(balance.Coins)+1)
55-
56-
rows[0] = []string{"base", balance.BaseTokens}
57-
for k, v := range balance.Coins {
54+
coins := make([]format.ChainBalanceCoin, 0, len(balance.Coins)+1)
55+
coins = append(coins, format.ChainBalanceCoin{
56+
Token: "base",
57+
Amount: balance.BaseTokens,
58+
})
59+
for _, v := range balance.Coins {
5860
if lo.Must(coin.IsBaseToken(v.CoinType)) {
5961
continue
6062
}
61-
62-
rows[k+1] = []string{v.CoinType, v.Balance}
63+
coins = append(coins, format.ChainBalanceCoin{
64+
Token: v.CoinType,
65+
Amount: v.Balance,
66+
})
6367
}
6468

65-
log.PrintTable(header, rows)
66-
return nil
69+
output := format.ChainBalanceOutput{
70+
Coins: coins,
71+
}
72+
return format.FormatSuccess("chain_balance", output.ToMap())
6773
},
6874
}
6975

@@ -226,7 +232,11 @@ func initDepositCmd() *cobra.Command {
226232
}
227233

228234
if printReceipt {
229-
log.Printf("L1 Gas Fee: %d\n", res.Effects.Data.GasFee())
235+
if err := format.FormatSuccess("l1_gas_fee", map[string]interface{}{
236+
"amount": res.Effects.Data.GasFee(),
237+
}); err != nil {
238+
return err
239+
}
230240
ref, err := res.GetCreatedObjectByName("request", "Request")
231241
if err != nil {
232242
return err

tools/wasp-cli/chain/blocklog.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func logRequestsInBlock(ctx context.Context, client *apiclient.APIClient, index
105105

106106
for i, receipt := range receipts {
107107
r := receipt
108-
util.LogReceipt(r, i)
108+
util.LogReceipt(r, i) //nolint:contextcheck
109109
}
110110
return nil
111111
}

tools/wasp-cli/format/glazed_formatter.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ func wrapText(text string, width int) []string {
140140
}
141141

142142
// formatTable outputs data as a table using glazed with proper output handling
143+
// context.Background() is acceptable here for table formatting
143144
func (gf *GlazedFormatter) formatTable(data map[string]interface{}) error {
144145
ctx := context.Background()
145146

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
package format
2+
3+
import "github.com/iotaledger/wasp/v2/tools/wasp-cli/log"
4+
5+
// ChainReceiptAsset mirrors the asset snippet included in chain receipt logs.
6+
type ChainReceiptAsset struct {
7+
CoinType string `json:"coin_type"`
8+
Balance string `json:"balance"`
9+
}
10+
11+
// ChainReceiptOutput documents the JSON payload emitted when a chain receipt is logged.
12+
//
13+
// It contains both the formatter metadata (type/status/timestamp) and the receipt-specific
14+
// fields so other packages (tests, tooling) can unmarshal CLI output without relying on
15+
// map[string]interface{}.
16+
type ChainReceiptOutput struct {
17+
Type string `json:"type,omitempty"`
18+
Status string `json:"status,omitempty"`
19+
Timestamp string `json:"timestamp,omitempty"`
20+
RequestID string `json:"request_id"`
21+
Kind string `json:"kind"`
22+
Sender string `json:"sender"`
23+
ContractHName string `json:"contract_hname"`
24+
FunctionHName string `json:"function_hname"`
25+
ParamsHex []string `json:"params_hex"`
26+
ArgumentsRaw interface{} `json:"arguments_raw"`
27+
DecodedKnownParams []log.TreeItem `json:"decoded_known_params"`
28+
Error string `json:"error"`
29+
GasBudget string `json:"gas_budget"`
30+
GasBurned string `json:"gas_burned"`
31+
GasFeeCharged string `json:"gas_fee_charged"`
32+
StorageDeposit string `json:"storage_deposit"`
33+
Assets []ChainReceiptAsset `json:"assets"`
34+
Index *int `json:"index,omitempty"`
35+
}
36+
37+
// ToMap converts the struct into the map representation expected by FormatSuccess.
38+
func (c ChainReceiptOutput) ToMap() map[string]interface{} {
39+
data := map[string]interface{}{
40+
"request_id": c.RequestID,
41+
"kind": c.Kind,
42+
"sender": c.Sender,
43+
"contract_hname": c.ContractHName,
44+
"function_hname": c.FunctionHName,
45+
"params_hex": c.ParamsHex,
46+
"arguments_raw": c.ArgumentsRaw,
47+
"decoded_known_params": c.DecodedKnownParams,
48+
"error": c.Error,
49+
"gas_budget": c.GasBudget,
50+
"gas_burned": c.GasBurned,
51+
"gas_fee_charged": c.GasFeeCharged,
52+
"storage_deposit": c.StorageDeposit,
53+
"assets": c.Assets,
54+
}
55+
if c.Index != nil {
56+
data["index"] = *c.Index
57+
}
58+
return data
59+
}
60+
61+
// ChainBalanceCoin represents a single coin balance entry.
62+
type ChainBalanceCoin struct {
63+
Token string `json:"token"`
64+
Amount string `json:"amount"`
65+
}
66+
67+
// ChainBalanceOutput documents the JSON payload emitted when a chain balance is logged.
68+
//
69+
// It contains both the formatter metadata (type/status/timestamp) and the balance-specific
70+
// fields so other packages (tests, tooling) can unmarshal CLI output without relying on
71+
// map[string]interface{}.
72+
type ChainBalanceOutput struct {
73+
Type string `json:"type,omitempty"`
74+
Status string `json:"status,omitempty"`
75+
Timestamp string `json:"timestamp,omitempty"`
76+
Coins []ChainBalanceCoin `json:"coins"`
77+
}
78+
79+
// ToMap converts the struct into the map representation expected by FormatSuccess.
80+
func (c ChainBalanceOutput) ToMap() map[string]interface{} {
81+
return map[string]interface{}{
82+
"coins": c.Coins,
83+
}
84+
}

0 commit comments

Comments
 (0)