The core protocol of WoopChain
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
woop/rosetta/services/tx_operation_components.go

235 lines
8.5 KiB

package services
import (
"fmt"
"math/big"
"github.com/coinbase/rosetta-sdk-go/types"
"github.com/harmony-one/harmony/rosetta/common"
"github.com/pkg/errors"
)
const (
// maxNumOfConstructionOps ..
maxNumOfConstructionOps = 2
// transferOperationCount ..
transferOperationCount = 2
)
// OperationComponents are components from a set of operations to construct a valid transaction
type OperationComponents struct {
Type string `json:"type"`
From *types.AccountIdentifier `json:"from"`
To *types.AccountIdentifier `json:"to"`
Amount *big.Int `json:"amount"`
StakingMessage interface{} `json:"staking_message,omitempty"`
}
// IsStaking ..
func (s *OperationComponents) IsStaking() bool {
return s.StakingMessage != nil
}
// GetOperationComponents ensures the provided operations creates a valid transaction and returns
// the OperationComponents of the resulting transaction.
//
// Providing a gas expenditure operation is INVALID.
// All staking & cross-shard operations require metadata matching the operation type to be a valid.
// All other operations do not require metadata.
// TODO (dm): implement staking transaction construction
func GetOperationComponents(
operations []*types.Operation,
) (*OperationComponents, *types.Error) {
if operations == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "nil operations",
})
}
if len(operations) > maxNumOfConstructionOps || len(operations) == 0 {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": fmt.Sprintf("invalid number of operations, must <= %v & > 0", maxNumOfConstructionOps),
})
}
if len(operations) == transferOperationCount {
return getTransferOperationComponents(operations)
}
switch operations[0].Type {
case common.CrossShardTransferNativeOperation:
return getCrossShardOperationComponents(operations[0])
case common.ContractCreationOperation:
return getContractCreationOperationComponents(operations[0])
default:
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": fmt.Sprintf("%v is unsupported or invalid operation type", operations[0].Type),
})
}
}
// getTransferOperationComponents ..
func getTransferOperationComponents(
operations []*types.Operation,
) (*OperationComponents, *types.Error) {
if len(operations) != transferOperationCount {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "require exactly 2 operations",
})
}
op0, op1 := operations[0], operations[1]
if op0.Type != common.TransferNativeOperation || op1.Type != common.TransferNativeOperation {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "invalid operation type(s) for same shard transfer",
})
}
val0, err := types.AmountValue(op0.Amount)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err.Error(),
})
}
val1, err := types.AmountValue(op1.Amount)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err.Error(),
})
}
if new(big.Int).Add(val0, val1).Cmp(big.NewInt(0)) != 0 {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "amount taken from sender is not exactly paid out to receiver for same shard transfer",
})
}
if types.Hash(op0.Amount.Currency) != common.NativeCurrencyHash ||
types.Hash(op1.Amount.Currency) != common.NativeCurrencyHash {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "invalid currency for provided amounts",
})
}
if len(op1.RelatedOperations) == 1 &&
op1.RelatedOperations[0].Index != op0.OperationIdentifier.Index {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "second operation is not related to the first operation for same shard transfer",
})
} else if len(op0.RelatedOperations) == 1 &&
op0.RelatedOperations[0].Index != op1.OperationIdentifier.Index {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "first operation is not related to the second operation for same shard transfer",
})
} else if len(op0.RelatedOperations) > 1 || len(op1.RelatedOperations) > 1 ||
len(op0.RelatedOperations)^len(op1.RelatedOperations) != 1 {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operations must only relate to one another in one direction for same shard transfers",
})
}
components := &OperationComponents{
Type: op0.Type,
Amount: new(big.Int).Abs(val0),
}
if val0.Sign() != 1 {
components.From = op0.Account
components.To = op1.Account
} else {
components.From = op1.Account
components.To = op0.Account
}
if components.From == nil || components.To == nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "both operations must have account identifiers for same shard transfer",
})
}
return components, nil
}
// getCrossShardOperationComponents ..
func getCrossShardOperationComponents(
operation *types.Operation,
) (*OperationComponents, *types.Error) {
if operation == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "nil operation",
})
}
metadata := common.CrossShardTransactionOperationMetadata{}
if err := metadata.UnmarshalFromInterface(operation.Metadata); err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": errors.WithMessage(err, "invalid metadata").Error(),
})
}
amount, err := types.AmountValue(operation.Amount)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err.Error(),
})
}
if amount.Sign() == 1 {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "sender amount must not be positive for cross shard transfer",
})
}
if types.Hash(operation.Amount.Currency) != common.NativeCurrencyHash {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "invalid currency for provided amounts",
})
}
components := &OperationComponents{
Type: operation.Type,
To: metadata.To,
From: metadata.From,
Amount: new(big.Int).Abs(amount),
}
if components.From == nil || components.To == nil || operation.Account == nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operation must have account sender/from & receiver/to identifiers for cross shard transfer",
})
}
if types.Hash(operation.Account) != types.Hash(components.From) {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operation account identifier does not match sender/from identifiers for cross shard transfer",
})
}
return components, nil
}
// getContractCreationOperationComponents ..
func getContractCreationOperationComponents(
operation *types.Operation,
) (*OperationComponents, *types.Error) {
if operation == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "nil operation",
})
}
amount, err := types.AmountValue(operation.Amount)
if err != nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": err.Error(),
})
}
if amount.Sign() == 1 {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "sender amount must not be positive for contract creation",
})
}
if types.Hash(operation.Amount.Currency) != common.NativeCurrencyHash {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "invalid currency for provided amounts",
})
}
components := &OperationComponents{
Type: operation.Type,
From: operation.Account,
Amount: new(big.Int).Abs(amount),
}
if components.From == nil {
return nil, common.NewError(common.InvalidTransactionConstructionError, map[string]interface{}{
"message": "operation must have account sender/from identifier for contract creation",
})
}
return components, nil
}