Skip to content
Merged
66 changes: 58 additions & 8 deletions pkg/client/account.go
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,23 @@ func (g *GrpcClient) UpdateAccountPermission(from string, owner, witness map[str
}

// UpdateAccountPermissionCtx is the context-aware version of UpdateAccountPermission.
// permField reads one field from a caller-supplied permission map with a checked
// assertion. The public API takes map[string]interface{}, so a missing key — or a
// plain int where int64 was meant — would otherwise panic part-way through a
// multisig permission update rather than returning an error.
func permField[T any](m map[string]interface{}, scope, key string) (T, error) {
var zero T
v, ok := m[key]
if !ok {
return zero, fmt.Errorf("%s permission: missing %q", scope, key)
}
t, ok := v.(T)
if !ok {
return zero, fmt.Errorf("%s permission: %q must be %T, got %T", scope, key, zero, v)
}
return t, nil
}

func (g *GrpcClient) UpdateAccountPermissionCtx(ctx context.Context, from string, owner, witness map[string]interface{}, actives []map[string]interface{}) (*api.TransactionExtention, error) {
ctx = g.withAPIKey(ctx)

Expand All @@ -447,13 +464,21 @@ func (g *GrpcClient) UpdateAccountPermissionCtx(ctx context.Context, from string
if owner == nil {
return nil, fmt.Errorf("owner is mandatory")
}
ownerThreshold, err := permField[int64](owner, "owner", "threshold")
if err != nil {
return nil, err
}
ownerKeys, err := permField[map[string]int64](owner, "owner", "keys")
if err != nil {
return nil, err
}
ownerPermission, err := makePermission(
"owner",
core.Permission_Owner,
0,
owner["threshold"].(int64),
ownerThreshold,
nil,
owner["keys"].(map[string]int64),
ownerKeys,
)
if err != nil {
return nil, err
Expand All @@ -469,13 +494,30 @@ func (g *GrpcClient) UpdateAccountPermissionCtx(ctx context.Context, from string
if actives != nil {
activesPermission := make([]*core.Permission, 0)
for i, active := range actives {
scope := fmt.Sprintf("active[%d]", i)
activeName, err := permField[string](active, scope, "name")
if err != nil {
return nil, err
}
activeThreshold, err := permField[int64](active, scope, "threshold")
if err != nil {
return nil, err
}
activeOps, err := permField[map[string]bool](active, scope, "operations")
if err != nil {
return nil, err
}
activeKeys, err := permField[map[string]int64](active, scope, "keys")
if err != nil {
return nil, err
}
activeP, err := makePermission(
active["name"].(string),
activeName,
core.Permission_Active,
int32(2+i),
active["threshold"].(int64),
active["operations"].(map[string]bool),
active["keys"].(map[string]int64),
activeThreshold,
activeOps,
activeKeys,
)
if err != nil {
return nil, err
Expand All @@ -486,13 +528,21 @@ func (g *GrpcClient) UpdateAccountPermissionCtx(ctx context.Context, from string
}

if witness != nil {
witnessThreshold, err := permField[int64](witness, "witness", "threshold")
if err != nil {
return nil, err
}
witnessKeys, err := permField[map[string]int64](witness, "witness", "keys")
if err != nil {
return nil, err
}
witnessPermission, err := makePermission(
"witness",
core.Permission_Witness,
1,
witness["threshold"].(int64),
witnessThreshold,
nil,
witness["keys"].(map[string]int64),
witnessKeys,
)
if err != nil {
return nil, err
Expand Down
3 changes: 3 additions & 0 deletions pkg/client/bank.go
Original file line number Diff line number Diff line change
Expand Up @@ -249,5 +249,8 @@ func (g *GrpcClient) WithdrawExpireUnfreezeCtx(ctx context.Context, from string,
if proto.Size(tx) == 0 {
return nil, fmt.Errorf("bad transaction")
}
if tx.GetResult().GetCode() != 0 {
return nil, fmt.Errorf("%s", tx.GetResult().GetMessage())
}
return tx, nil
}
30 changes: 22 additions & 8 deletions pkg/client/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -94,8 +94,8 @@ func (g *GrpcClient) UpdateEnergyLimitContractCtx(ctx context.Context, from, con
return nil, err
}

if tx.Result.Code > 0 {
return nil, fmt.Errorf("%s", string(tx.Result.Message))
if tx.GetResult().GetCode() > 0 {
return nil, fmt.Errorf("%s", string(tx.GetResult().GetMessage()))
}

return tx, err
Expand Down Expand Up @@ -133,8 +133,8 @@ func (g *GrpcClient) UpdateSettingContractCtx(ctx context.Context, from, contrac
return nil, err
}

if tx.Result.Code > 0 {
return nil, fmt.Errorf("%s", string(tx.Result.Message))
if tx.GetResult().GetCode() > 0 {
return nil, fmt.Errorf("%s", string(tx.GetResult().GetMessage()))
}

return tx, err
Expand Down Expand Up @@ -248,8 +248,13 @@ func (g *GrpcClient) triggerContract(ctx context.Context, ct *core.TriggerSmartC
return nil, err
}

if tx.Result.Code > 0 {
return nil, fmt.Errorf("%s", string(tx.Result.Message))
if tx.GetResult().GetCode() > 0 {
return nil, fmt.Errorf("%s", string(tx.GetResult().GetMessage()))
}
// A success code does not guarantee a transaction: guard before assigning
// through it, as DeployContractCtx does.
if tx.GetTransaction().GetRawData() == nil {
return nil, fmt.Errorf("trigger contract: node returned no transaction")
}
if feeLimit > 0 {
tx.Transaction.RawData.FeeLimit = feeLimit
Expand Down Expand Up @@ -446,8 +451,8 @@ func (g *GrpcClient) estimateEnergy(ctx context.Context, ct *core.TriggerSmartCo
return nil, err
}

if tx.Result.Code > 0 {
return nil, fmt.Errorf("%s", string(tx.Result.Message))
if tx.GetResult().GetCode() > 0 {
return nil, fmt.Errorf("%s", string(tx.GetResult().GetMessage()))
}

return tx, err
Expand Down Expand Up @@ -505,6 +510,15 @@ func (g *GrpcClient) DeployContractCtx(ctx context.Context, from, contractName s
if err != nil {
return nil, err
}
// A rejected deployment comes back with a nil gRPC error, a non-zero result
// code and no Transaction, so the fee-limit assignment below would panic
// instead of surfacing the node's reason for the rejection.
if tx.GetResult().GetCode() != 0 {
return nil, fmt.Errorf("%s", tx.GetResult().GetMessage())
}
if tx.GetTransaction().GetRawData() == nil {
return nil, fmt.Errorf("deploy contract: node returned no transaction")
}
if feeLimit > 0 {
tx.Transaction.RawData.FeeLimit = feeLimit
// update hash
Expand Down
5 changes: 4 additions & 1 deletion pkg/client/network.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ func (g *GrpcClient) GetTransactionInfoByIDCtx(ctx context.Context, id string) (
if err != nil {
return nil, err
}
if bytes.Equal(txi.Id, transactionID.Value) {
if txi == nil {
return nil, fmt.Errorf("transaction info not found: empty response from node")
}
if bytes.Equal(txi.GetId(), transactionID.Value) {
return txi, nil
}
return nil, fmt.Errorf("transaction info not found")
Expand Down
14 changes: 14 additions & 0 deletions pkg/client/resources.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,12 @@ package client

import (
"context"
"fmt"

"github.com/fbsobreira/gotron-sdk/pkg/common"
"github.com/fbsobreira/gotron-sdk/pkg/proto/api"
"github.com/fbsobreira/gotron-sdk/pkg/proto/core"
"google.golang.org/protobuf/proto"
)

// GetAccountResource from BASE58 address
Expand Down Expand Up @@ -205,6 +207,12 @@ func (g *GrpcClient) DelegateResourceCtx(ctx context.Context, from, to string, r
return nil, err

}
if proto.Size(response) == 0 {
return nil, fmt.Errorf("bad transaction")
}
if response.GetResult().GetCode() != 0 {
return nil, fmt.Errorf("%s", response.GetResult().GetMessage())
}

return response, nil
}
Expand Down Expand Up @@ -242,6 +250,12 @@ func (g *GrpcClient) UnDelegateResourceCtx(ctx context.Context, owner, receiver
return nil, err

}
if proto.Size(response) == 0 {
return nil, fmt.Errorf("bad transaction")
}
if response.GetResult().GetCode() != 0 {
return nil, fmt.Errorf("%s", response.GetResult().GetMessage())
}

return response, nil
}
73 changes: 62 additions & 11 deletions pkg/client/trc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import (
"github.com/fbsobreira/gotron-sdk/pkg/common"
"github.com/fbsobreira/gotron-sdk/pkg/proto/api"
"github.com/fbsobreira/gotron-sdk/pkg/proto/core"
"github.com/fbsobreira/gotron-sdk/pkg/standards/trc20enc"
)

// TRC20Option configures optional behaviour on TRC20 write methods
Expand Down Expand Up @@ -94,13 +95,37 @@ func (g *GrpcClient) TRC20CallCtx(ctx context.Context, from, contractAddress, da
if err != nil {
return nil, err
}
if result.Result.Code > 0 {
return result, fmt.Errorf("%s", string(result.Result.Message))
// Return nil rather than the rejection payload, matching triggerContract and
// DeployContractCtx. TRC20Send, TRC20Approve and TRC20TransferFrom return this
// value straight through, so handing back a rejected TransactionExtention lets
// a caller that checks the result before the error sign and broadcast it.
if result.GetResult().GetCode() > 0 {
return nil, fmt.Errorf("%s", string(result.GetResult().GetMessage()))
}
return result, nil

}

// constantResultHex returns the first constant_result entry of a read-only call
// as a hex string.
//
// The node controls this slice, and a response can carry a success code while
// still containing no entries, so it is length-checked before indexing. The
// entry must also hold at least one full ABI word: every constant result is
// 32-byte aligned, so a shorter one is malformed, and rejecting it here gives a
// clear error instead of a confusing parse failure further down. This mirrors
// the guard in callForAddress (contracts.go).
func constantResultHex(result *api.TransactionExtention) (string, error) {
if len(result.GetConstantResult()) == 0 {
return "", fmt.Errorf("node returned no constant result")
}
if len(result.GetConstantResult()[0]) < 32 {
return "", fmt.Errorf("node returned a %d-byte constant result, expected at least 32",
len(result.GetConstantResult()[0]))
}
return common.BytesToHexString(result.GetConstantResult()[0]), nil
}

// TRC20GetName returns the name of a TRC20 token contract.
func (g *GrpcClient) TRC20GetName(contractAddress string) (string, error) {
ctx, cancel := g.newContext()
Expand All @@ -116,7 +141,10 @@ func (g *GrpcClient) TRC20GetNameCtx(ctx context.Context, contractAddress string
if err != nil {
return "", err
}
data := common.BytesToHexString(result.GetConstantResult()[0])
data, err := constantResultHex(result)
if err != nil {
return "", err
}
return g.ParseTRC20StringProperty(data)
}

Expand All @@ -135,7 +163,10 @@ func (g *GrpcClient) TRC20GetSymbolCtx(ctx context.Context, contractAddress stri
if err != nil {
return "", err
}
data := common.BytesToHexString(result.GetConstantResult()[0])
data, err := constantResultHex(result)
if err != nil {
return "", err
}
return g.ParseTRC20StringProperty(data)
}

Expand All @@ -154,7 +185,10 @@ func (g *GrpcClient) TRC20GetDecimalsCtx(ctx context.Context, contractAddress st
if err != nil {
return nil, err
}
data := common.BytesToHexString(result.GetConstantResult()[0])
data, err := constantResultHex(result)
if err != nil {
return nil, err
}
return g.ParseTRC20NumericProperty(data)
}

Expand Down Expand Up @@ -185,9 +219,14 @@ func (g *GrpcClient) ParseTRC20StringProperty(data string) (string, error) {
}
if len(data) > 128 {
n, _ := g.ParseTRC20NumericProperty(data[64:128])
if n != nil {
// l is the ABI string length, taken from contract-controlled return data.
// Reject anything that does not fit in a uint64 — Uint64 would silently
// return the low 64 bits — then bound it by division rather than by
// comparing 2*l. The original 2*int(l) overflowed to a negative value for
// l >= 2^62, passing the check and panicking on the slice below.
if n != nil && n.IsUint64() {
l := n.Uint64()
if 2*int(l) <= len(data)-128 {
if l <= uint64(len(data)-128)/2 {
b, err := hex.DecodeString(data[128 : 128+2*l])
if err == nil {
return string(b), nil
Expand Down Expand Up @@ -232,7 +271,10 @@ func (g *GrpcClient) TRC20ContractBalanceCtx(ctx context.Context, addr, contract
if err != nil {
return nil, err
}
data := common.BytesToHexString(result.GetConstantResult()[0])
data, err := constantResultHex(result)
if err != nil {
return nil, err
}
r, err := g.ParseTRC20NumericProperty(data)
if err != nil {
return nil, fmt.Errorf("contract address %s: %v", contractAddress, err)
Expand All @@ -259,7 +301,10 @@ func (g *GrpcClient) TRC20SendCtx(ctx context.Context, from, to, contract string
if err != nil {
return nil, err
}
ab := common.LeftPadBytes(amount.Bytes(), 32)
ab, err := trc20enc.PadUint256(amount)
if err != nil {
return nil, fmt.Errorf("invalid amount: %w", err)
}
req := trc20TransferMethodSignature + "0000000000000000000000000000000000000000000000000000000000000000"[len(addrB.Hex())-4:] + addrB.Hex()[4:]
req += common.Bytes2Hex(ab)
return g.TRC20CallCtx(ctx, from, contract, req, cfg.estimate, feeLimit)
Expand Down Expand Up @@ -288,7 +333,10 @@ func (g *GrpcClient) TRC20TransferFromCtx(ctx context.Context, owner, from, to,
if err != nil {
return nil, err
}
ab := common.LeftPadBytes(amount.Bytes(), 32)
ab, err := trc20enc.PadUint256(amount)
if err != nil {
return nil, fmt.Errorf("invalid amount: %w", err)
}
req := "0x23b872dd" +
"0000000000000000000000000000000000000000000000000000000000000000"[len(addrA.Hex())-4:] + addrA.Hex()[4:] +
"0000000000000000000000000000000000000000000000000000000000000000"[len(addrB.Hex())-4:] + addrB.Hex()[4:]
Expand All @@ -312,7 +360,10 @@ func (g *GrpcClient) TRC20ApproveCtx(ctx context.Context, from, to, contract str
if err != nil {
return nil, err
}
ab := common.LeftPadBytes(amount.Bytes(), 32)
ab, err := trc20enc.PadUint256(amount)
if err != nil {
return nil, fmt.Errorf("invalid amount: %w", err)
}
req := trc20ApproveMethodSignature + "0000000000000000000000000000000000000000000000000000000000000000"[len(addrB.Hex())-4:] + addrB.Hex()[4:]
req += common.Bytes2Hex(ab)
return g.TRC20CallCtx(ctx, from, contract, req, cfg.estimate, feeLimit)
Expand Down
Loading
Loading