Skip to content

Commit 696c6d0

Browse files
authored
feat: TRC20 contract integration test and CLI improvements (#288)
* feat: TRC20 contract integration test and CLI improvements - Add --params flag to contract deploy for constructor arguments - Fix TRC20ContractBalance: strip 0x41 prefix in ABI encoding (Hex()[2:]→Hex()[4:]) - Remove signer requirement from contract constant (read-only calls) - Trim whitespace from --abiFile/--bcFile content (trailing newline fix) - Error when --params provided but ABI has no constructor - Add pre-compiled TestToken TRC20 contract (testdata/contracts/) - Extend integration test: deploy, constant calls, balance, transfer, approve * style: align var block formatting
1 parent d549ea7 commit 696c6d0

6 files changed

Lines changed: 436 additions & 21 deletions

File tree

cmd/subcommands/contract.go

Lines changed: 51 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -5,29 +5,33 @@ import (
55
"fmt"
66
"math"
77
"os"
8+
"strings"
89

10+
"github.com/fbsobreira/gotron-sdk/pkg/abi"
911
"github.com/fbsobreira/gotron-sdk/pkg/address"
1012
"github.com/fbsobreira/gotron-sdk/pkg/client/transaction"
1113
"github.com/fbsobreira/gotron-sdk/pkg/common"
1214
"github.com/fbsobreira/gotron-sdk/pkg/contract"
1315
"github.com/fbsobreira/gotron-sdk/pkg/keystore"
16+
"github.com/fbsobreira/gotron-sdk/pkg/proto/core"
1417
"github.com/fbsobreira/gotron-sdk/pkg/store"
1518

1619
"github.com/spf13/cobra"
1720
)
1821

1922
var (
20-
abiSTR string
21-
abiFile string
22-
bcSTR string
23-
bcFile string
24-
feeLimit int64
25-
curPercent int64
26-
oeLimit int64
27-
tAmount float64
28-
tTokenID string
29-
tTokenAmount float64
30-
estimate bool
23+
abiSTR string
24+
abiFile string
25+
bcSTR string
26+
bcFile string
27+
feeLimit int64
28+
curPercent int64
29+
oeLimit int64
30+
tAmount float64
31+
tTokenID string
32+
tTokenAmount float64
33+
estimate bool
34+
constructorParams string
3135
)
3236

3337
func contractDeployCmd() *cobra.Command {
@@ -43,7 +47,7 @@ func contractDeployCmd() *cobra.Command {
4347
if err != nil {
4448
return fmt.Errorf("cannot read ABI file: %s %v", abiFile, err)
4549
}
46-
abiSTR = string(abiBytes)
50+
abiSTR = strings.TrimSpace(string(abiBytes))
4751
} else {
4852
return fmt.Errorf("no ABI string or ABI file specified")
4953
}
@@ -59,7 +63,7 @@ func contractDeployCmd() *cobra.Command {
5963
if err != nil {
6064
return fmt.Errorf("cannot read Bytecode file: %s %v", bcFile, err)
6165
}
62-
bcSTR = string(bcBytes)
66+
bcSTR = strings.TrimSpace(string(bcBytes))
6367
} else {
6468
return fmt.Errorf("no Bytecode string or Bytecode file specified")
6569
}
@@ -69,7 +73,36 @@ func contractDeployCmd() *cobra.Command {
6973
return fmt.Errorf("no signer specified")
7074
}
7175

72-
// TODO: add constructor arguments
76+
// Encode constructor arguments if provided
77+
if constructorParams != "" {
78+
found := false
79+
for _, entry := range ABI.Entrys {
80+
if entry.Type != core.SmartContract_ABI_Entry_Constructor {
81+
continue
82+
}
83+
found = true
84+
// Build constructor signature: constructor(type1,type2,...)
85+
types := make([]string, len(entry.Inputs))
86+
for i, input := range entry.Inputs {
87+
types[i] = input.Type
88+
}
89+
sig := fmt.Sprintf("constructor(%s)", strings.Join(types, ","))
90+
params, err := abi.LoadFromJSONWithMethod(sig, constructorParams)
91+
if err != nil {
92+
return fmt.Errorf("parse constructor params: %w", err)
93+
}
94+
encoded, err := abi.GetPaddedParam(params)
95+
if err != nil {
96+
return fmt.Errorf("encode constructor args: %w", err)
97+
}
98+
bcSTR += fmt.Sprintf("%x", encoded)
99+
break
100+
}
101+
if !found {
102+
return fmt.Errorf("--params provided but ABI has no constructor")
103+
}
104+
}
105+
73106
tx, err := conn.DeployContract(signerAddress.String(), args[0],
74107
ABI, bcSTR, feeLimit, curPercent, oeLimit)
75108
if err != nil {
@@ -127,6 +160,7 @@ func contractDeployCmd() *cobra.Command {
127160
cmd.Flags().StringVar(&abiFile, "abiFile", "", "abi file location")
128161
cmd.Flags().StringVar(&bcSTR, "bc", "", "bytecode HEX string")
129162
cmd.Flags().StringVar(&bcFile, "bcFile", "", "bytecode file location")
163+
cmd.Flags().StringVar(&constructorParams, "params", "", "constructor parameters as JSON (e.g. '[1000000]')")
130164
cmd.Flags().Int64Var(&feeLimit, "feeLimit", 1000000000, "fee limit")
131165
cmd.Flags().Int64Var(&curPercent, "curPercent", 100, "consume user resource percentage")
132166
cmd.Flags().Int64Var(&oeLimit, "oeLimit", 1000000, "origin energy limit")
@@ -137,21 +171,19 @@ func contractDeployCmd() *cobra.Command {
137171
func contractConstantCmd() *cobra.Command {
138172
cmd := &cobra.Command{
139173
Use: "constant <CONTRACT_ADDRESS> <METHOD> [PARAMETER]",
140-
Short: "constantTrigger contract",
174+
Short: "constant (read-only) contract call",
141175
Args: cobra.RangeArgs(2, 3),
142176
PreRunE: validateAddress,
143177
RunE: func(cmd *cobra.Command, args []string) error {
144-
if signerAddress.String() == "" {
145-
return fmt.Errorf("no signer specified")
146-
}
178+
from := signerAddress.String()
147179

148180
param := ""
149181
if len(args) == 3 {
150182
param = args[2]
151183
}
152184

153185
tx, err := conn.TriggerConstantContract(
154-
signerAddress.String(),
186+
from,
155187
addr.String(),
156188
args[1],
157189
param,

pkg/client/trc20.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,9 @@ func (g *GrpcClient) TRC20ContractBalanceCtx(ctx context.Context, addr, contract
225225
if err != nil {
226226
return nil, fmt.Errorf("invalid address %s: %v", addr, err)
227227
}
228-
req := trc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(addrB.Hex())-2:] + addrB.Hex()[2:]
228+
// ABI-encode: use 20-byte EVM address (strip 0x41 TRON prefix), left-pad to 32 bytes
229+
evmHex := addrB.Hex()[4:] // strip "0x41"
230+
req := trc20BalanceOf + "0000000000000000000000000000000000000000000000000000000000000000"[len(evmHex):] + evmHex
229231
result, err := g.TRC20CallCtx(ctx, "", contractAddress, req, true, 0)
230232
if err != nil {
231233
return nil, err

scripts/integration-test.sh

Lines changed: 101 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717

1818
set -euo pipefail
1919

20+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
21+
REPO_ROOT="$(dirname "$SCRIPT_DIR")"
2022
TRONCTL="./bin/tronctl"
2123
PASS="testpass1234"
2224
SEED_NAME="seed-account"
@@ -26,6 +28,8 @@ ACCOUNTS_JSON="accounts-data/accounts.json"
2628
FUND_AMOUNT="1500"
2729
TRANSFER_AMOUNT="1"
2830
FREEZE_AMOUNT="10"
31+
CONTRACT_ABI="$REPO_ROOT/testdata/contracts/TestToken.abi"
32+
CONTRACT_BIN="$REPO_ROOT/testdata/contracts/TestToken.bin"
2933
PASS_FILE=$(mktemp)
3034
trap 'rm -f "$PASS_FILE"' EXIT
3135
echo -n "$PASS" > "$PASS_FILE"
@@ -432,7 +436,103 @@ else
432436
log_fail "Random private key" "expected 64 hex chars, got ${#PK}"
433437
fi
434438

435-
# ── 17. TRC10 token operations ─────────────────────────────────────────────
439+
# ── 17. TRC20 contract deploy + interact ──────────────────────────────────
440+
log_section "TRC20 Contract (Deploy + Interact)"
441+
442+
CONTRACT_ADDR=""
443+
if has_balance "$ADDR1" && [[ -f "$CONTRACT_ABI" ]] && [[ -f "$CONTRACT_BIN" ]]; then
444+
log_info "Deploying TestToken TRC20 contract..."
445+
DEPLOY_OUTPUT=$($TRONCTL contract deploy "TestToken" \
446+
--abiFile "$CONTRACT_ABI" \
447+
--bcFile "$CONTRACT_BIN" \
448+
--params '[1000000]' \
449+
--signer "$ADDR1" --passphrase-file "$PASS_FILE" \
450+
--feeLimit 1000000000 --oeLimit 10000000 2>&1 || true)
451+
452+
CONTRACT_ADDR=$(echo "$DEPLOY_OUTPUT" | python3 -c "import sys,json; print(json.load(sys.stdin)['contractAddress'])" 2>/dev/null || \
453+
echo "$DEPLOY_OUTPUT" | jq -r '.contractAddress' 2>/dev/null || true)
454+
455+
if [[ -n "$CONTRACT_ADDR" && "$CONTRACT_ADDR" != "null" && "$CONTRACT_ADDR" != "" ]]; then
456+
log_pass "Deploy TRC20 contract: $CONTRACT_ADDR"
457+
sleep 3 # wait for confirmation
458+
459+
# Read token name via constant call
460+
NAME_OUT=$($TRONCTL contract constant "$CONTRACT_ADDR" "name()" 2>&1 || true)
461+
if echo "$NAME_OUT" | grep -qi "result\|0x"; then
462+
log_pass "Constant call: name()"
463+
else
464+
log_skip "Constant call: name()" "may need more time"
465+
fi
466+
467+
# Read token symbol
468+
SYM_OUT=$($TRONCTL contract constant "$CONTRACT_ADDR" "symbol()" 2>&1 || true)
469+
if echo "$SYM_OUT" | grep -qi "result\|0x"; then
470+
log_pass "Constant call: symbol()"
471+
else
472+
log_skip "Constant call: symbol()" "may need more time"
473+
fi
474+
475+
# Read decimals
476+
DEC_OUT=$($TRONCTL contract constant "$CONTRACT_ADDR" "decimals()" 2>&1 || true)
477+
if echo "$DEC_OUT" | grep -qi "result\|0x"; then
478+
log_pass "Constant call: decimals()"
479+
else
480+
log_skip "Constant call: decimals()" "may need more time"
481+
fi
482+
483+
# TRC20 balance check via tronctl trc20
484+
BAL_OUT=$($TRONCTL trc20 balance "$ADDR1" "$CONTRACT_ADDR" 2>&1 || true)
485+
if echo "$BAL_OUT" | grep -qiE "balance|[0-9]"; then
486+
log_pass "TRC20 balance check"
487+
else
488+
log_skip "TRC20 balance" "contract may not be indexed yet"
489+
fi
490+
491+
# TRC20 transfer: send tokens from ACC1 to ACC2
492+
log_info "Sending 100 TST tokens from ACC1 to ACC2..."
493+
TRC20_SEND=$($TRONCTL trc20 send "$ADDR2" 100 "$CONTRACT_ADDR" \
494+
--signer "$ADDR1" --passphrase-file "$PASS_FILE" \
495+
--feeLimit 100000000 --no-wait 2>&1 || true)
496+
if echo "$TRC20_SEND" | grep -qiE "txID|txid|0x[a-f0-9]"; then
497+
log_pass "TRC20 transfer ACC1 -> ACC2"
498+
sleep 5
499+
500+
# Verify ACC2 received tokens
501+
BAL2_OUT=$($TRONCTL trc20 balance "$ADDR2" "$CONTRACT_ADDR" 2>&1 || true)
502+
if echo "$BAL2_OUT" | grep -qE '"balance":.*[1-9]'; then
503+
log_pass "TRC20 balance ACC2 > 0 after transfer"
504+
else
505+
log_info "Balance output: $BAL2_OUT"
506+
log_skip "TRC20 balance verify" "balance not yet reflected"
507+
fi
508+
else
509+
log_fail "TRC20 transfer" "$TRC20_SEND"
510+
fi
511+
512+
# Trigger approve via contract trigger
513+
log_info "Approving ACC2 to spend 50 TST..."
514+
APPROVE_OUT=$($TRONCTL contract trigger "$CONTRACT_ADDR" \
515+
"approve(address,uint256)" "[\"$ADDR2\",50000000]" \
516+
--signer "$ADDR1" --passphrase-file "$PASS_FILE" \
517+
--feeLimit 100000000 --no-wait 2>&1 || true)
518+
if echo "$APPROVE_OUT" | grep -qiE "txID|txid|0x[a-f0-9]"; then
519+
log_pass "Contract trigger: approve()"
520+
else
521+
log_fail "Contract trigger: approve()" "$APPROVE_OUT"
522+
fi
523+
else
524+
log_fail "Deploy TRC20 contract" "no contract address in output"
525+
log_info "Output: $DEPLOY_OUTPUT"
526+
fi
527+
else
528+
if ! has_balance "$ADDR1"; then
529+
log_skip "TRC20 Contract" "ACC1 has no funds"
530+
else
531+
log_skip "TRC20 Contract" "Contract files not found at $CONTRACT_ABI"
532+
fi
533+
fi
534+
535+
# ── 18. TRC10 token operations ─────────────────────────────────────────────
436536
log_section "TRC10 Token"
437537

438538
if has_balance "$ADDR1"; then

0 commit comments

Comments
 (0)