245 lines
6.0 KiB
Go
245 lines
6.0 KiB
Go
package tron
|
|
|
|
import (
|
|
"crypto/sha256"
|
|
"encoding/hex"
|
|
)
|
|
|
|
// This file implements the minimal protobuf encoding required to compute TRON
|
|
// transaction ids (sha256 of the serialized Transaction.raw message) and to
|
|
// mirror tronpy's JSON transaction representation. Only the fields the wallet
|
|
// populates are handled; proto3 default (zero) values are omitted.
|
|
|
|
// --- protobuf wire primitives ---
|
|
|
|
const (
|
|
wireVarint = 0
|
|
wireBytes = 2
|
|
)
|
|
|
|
type pbWriter struct {
|
|
buf []byte
|
|
}
|
|
|
|
func (w *pbWriter) rawVarint(v uint64) {
|
|
for v >= 0x80 {
|
|
w.buf = append(w.buf, byte(v)|0x80)
|
|
v >>= 7
|
|
}
|
|
w.buf = append(w.buf, byte(v))
|
|
}
|
|
|
|
func (w *pbWriter) tag(field, wire int) {
|
|
w.rawVarint(uint64(field)<<3 | uint64(wire))
|
|
}
|
|
|
|
func (w *pbWriter) varintField(field int, v uint64) {
|
|
if v == 0 {
|
|
return
|
|
}
|
|
w.tag(field, wireVarint)
|
|
w.rawVarint(v)
|
|
}
|
|
|
|
func (w *pbWriter) int64Field(field int, v int64) {
|
|
w.varintField(field, uint64(v))
|
|
}
|
|
|
|
func (w *pbWriter) bytesField(field int, b []byte) {
|
|
if len(b) == 0 {
|
|
return
|
|
}
|
|
w.tag(field, wireBytes)
|
|
w.rawVarint(uint64(len(b)))
|
|
w.buf = append(w.buf, b...)
|
|
}
|
|
|
|
func (w *pbWriter) stringField(field int, s string) {
|
|
w.bytesField(field, []byte(s))
|
|
}
|
|
|
|
func (w *pbWriter) messageField(field int, fn func(*pbWriter)) {
|
|
sub := &pbWriter{}
|
|
fn(sub)
|
|
w.bytesField(field, sub.buf)
|
|
}
|
|
|
|
// --- contract value types (JSON shape matches tronpy dicts) ---
|
|
|
|
// TransferValue is the value payload of a TransferContract.
|
|
type TransferValue struct {
|
|
OwnerAddress string `json:"owner_address"`
|
|
ToAddress string `json:"to_address"`
|
|
Amount int64 `json:"amount"`
|
|
}
|
|
|
|
// TriggerValue is the value payload of a TriggerSmartContract.
|
|
type TriggerValue struct {
|
|
OwnerAddress string `json:"owner_address"`
|
|
ContractAddress string `json:"contract_address"`
|
|
Data string `json:"data"`
|
|
CallTokenValue int64 `json:"call_token_value"`
|
|
CallValue int64 `json:"call_value"`
|
|
TokenID int64 `json:"token_id"`
|
|
}
|
|
|
|
// FreezeValue is the value payload of a FreezeBalanceV2Contract.
|
|
type FreezeValue struct {
|
|
OwnerAddress string `json:"owner_address"`
|
|
FrozenBalance int64 `json:"frozen_balance"`
|
|
Resource string `json:"resource"`
|
|
}
|
|
|
|
// UnfreezeValue is the value payload of an UnfreezeBalanceV2Contract.
|
|
type UnfreezeValue struct {
|
|
OwnerAddress string `json:"owner_address"`
|
|
UnfreezeBalance int64 `json:"unfreeze_balance"`
|
|
Resource string `json:"resource"`
|
|
}
|
|
|
|
// ParameterJSON mirrors tronpy's {value, type_url} contract parameter.
|
|
type ParameterJSON struct {
|
|
Value any `json:"value"`
|
|
TypeURL string `json:"type_url"`
|
|
}
|
|
|
|
// ContractJSON mirrors tronpy's contract entry inside raw_data.
|
|
type ContractJSON struct {
|
|
Parameter ParameterJSON `json:"parameter"`
|
|
Type string `json:"type"`
|
|
}
|
|
|
|
// RawData mirrors tronpy's raw_data dict; addresses are hex strings.
|
|
type RawData struct {
|
|
Contract []ContractJSON `json:"contract"`
|
|
Timestamp int64 `json:"timestamp"`
|
|
Expiration int64 `json:"expiration"`
|
|
RefBlockBytes string `json:"ref_block_bytes"`
|
|
RefBlockHash string `json:"ref_block_hash"`
|
|
FeeLimit int64 `json:"fee_limit,omitempty"`
|
|
}
|
|
|
|
// --- encoding ---
|
|
|
|
func contractTypeCode(t string) uint64 {
|
|
switch t {
|
|
case "TransferContract":
|
|
return 1
|
|
case "TriggerSmartContract":
|
|
return 31
|
|
case "FreezeBalanceV2Contract":
|
|
return 54
|
|
case "UnfreezeBalanceV2Contract":
|
|
return 55
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func resourceCode(r string) uint64 {
|
|
if r == "ENERGY" {
|
|
return 1
|
|
}
|
|
return 0
|
|
}
|
|
|
|
func (c *ContractJSON) serializeValue() ([]byte, error) {
|
|
w := &pbWriter{}
|
|
switch v := c.Parameter.Value.(type) {
|
|
case TransferValue:
|
|
owner, err := ToRawAddress(v.OwnerAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
to, err := ToRawAddress(v.ToAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w.bytesField(1, owner)
|
|
w.bytesField(2, to)
|
|
w.int64Field(3, v.Amount)
|
|
case TriggerValue:
|
|
owner, err := ToRawAddress(v.OwnerAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
contractAddr, err := ToRawAddress(v.ContractAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
data, err := hex.DecodeString(v.Data)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w.bytesField(1, owner)
|
|
w.bytesField(2, contractAddr)
|
|
w.int64Field(3, v.CallValue)
|
|
w.bytesField(4, data)
|
|
w.int64Field(5, v.CallTokenValue)
|
|
w.int64Field(6, v.TokenID)
|
|
case FreezeValue:
|
|
owner, err := ToRawAddress(v.OwnerAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w.bytesField(1, owner)
|
|
w.int64Field(2, v.FrozenBalance)
|
|
w.varintField(3, resourceCode(v.Resource))
|
|
case UnfreezeValue:
|
|
owner, err := ToRawAddress(v.OwnerAddress)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w.bytesField(1, owner)
|
|
w.int64Field(2, v.UnfreezeBalance)
|
|
w.varintField(3, resourceCode(v.Resource))
|
|
default:
|
|
return nil, ErrValidation
|
|
}
|
|
return w.buf, nil
|
|
}
|
|
|
|
// serialize encodes the raw data into the protobuf Transaction.raw message.
|
|
func (rd *RawData) serialize() ([]byte, error) {
|
|
w := &pbWriter{}
|
|
rbb, err := hex.DecodeString(rd.RefBlockBytes)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w.bytesField(1, rbb)
|
|
rbh, err := hex.DecodeString(rd.RefBlockHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
w.bytesField(4, rbh)
|
|
w.int64Field(8, rd.Expiration)
|
|
for i := range rd.Contract {
|
|
c := &rd.Contract[i]
|
|
val, err := c.serializeValue()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
typeURL := c.Parameter.TypeURL
|
|
code := contractTypeCode(c.Type)
|
|
w.messageField(11, func(sub *pbWriter) {
|
|
sub.varintField(1, code)
|
|
sub.messageField(2, func(anyW *pbWriter) {
|
|
anyW.stringField(1, typeURL)
|
|
anyW.bytesField(2, val)
|
|
})
|
|
})
|
|
}
|
|
w.int64Field(14, rd.Timestamp)
|
|
w.int64Field(18, rd.FeeLimit)
|
|
return w.buf, nil
|
|
}
|
|
|
|
// TxID computes the transaction id (hex) from the raw data.
|
|
func (rd *RawData) TxID() (string, error) {
|
|
b, err := rd.serialize()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
h := sha256.Sum256(b)
|
|
return hex.EncodeToString(h[:]), nil
|
|
}
|