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.go

851 lines
28 KiB

package services
import (
"github.com/harmony-one/harmony/hmy/tracers"
"math/big"
"github.com/harmony-one/harmony/internal/bech32"
internalCommon "github.com/harmony-one/harmony/internal/common"
"github.com/coinbase/rosetta-sdk-go/types"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/harmony-one/harmony/core"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/core/vm"
"github.com/harmony-one/harmony/hmy"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/rosetta/common"
rpcV2 "github.com/harmony-one/harmony/rpc/v2"
"github.com/harmony-one/harmony/staking"
stakingTypes "github.com/harmony-one/harmony/staking/types"
)
const (
SubAccountMetadataKey = "type"
Delegation = "delegation"
UnDelegation = "undelegation"
UndelegationPayout = "UndelegationPayout"
)
// GetNativeOperationsFromTransaction for one of the following transactions:
// contract creation, cross-shard sender, same-shard transfer with and without code execution.
// Native operations only include operations that affect the native currency balance of an account.
func GetNativeOperationsFromTransaction(
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, contractInfo *ContractInfo,
) ([]*types.Operation, *types.Error) {
senderAddress, err := tx.SenderAddress()
if err != nil {
senderAddress = FormatDefaultSenderAddress
}
accountID, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
}
// All operations excepts for cross-shard tx payout expend gas
gasExpended := new(big.Int).Mul(new(big.Int).SetUint64(receipt.GasUsed), tx.GasPrice())
gasOperations := newNativeOperationsWithGas(gasExpended, accountID)
startingOpIndex := gasOperations[0].OperationIdentifier.Index + 1
// Handle based on tx type & available data.
var txOperations []*types.Operation
if tx.To() == nil {
txOperations, rosettaError = getContractCreationNativeOperations(
tx, receipt, senderAddress, contractInfo, &startingOpIndex,
)
} else if tx.ShardID() != tx.ToShardID() {
txOperations, rosettaError = getCrossShardSenderTransferNativeOperations(
tx, receipt, senderAddress, &startingOpIndex,
)
} else if contractInfo != nil && contractInfo.ExecutionResult != nil {
txOperations, rosettaError = getContractTransferNativeOperations(
tx, receipt, senderAddress, tx.To(), contractInfo, &startingOpIndex,
)
} else {
txOperations, rosettaError = getBasicTransferNativeOperations(
tx, receipt, senderAddress, tx.To(), &startingOpIndex,
)
}
if rosettaError != nil {
return nil, rosettaError
}
return append(gasOperations, txOperations...), nil
}
// GetNativeOperationsFromStakingTransaction for all staking directives
// Note that only native token operations can come from staking transactions.
func GetNativeOperationsFromStakingTransaction(
tx *stakingTypes.StakingTransaction, receipt *hmytypes.Receipt, signed bool,
) ([]*types.Operation, *types.Error) {
senderAddress, err := tx.SenderAddress()
if err != nil {
senderAddress = FormatDefaultSenderAddress
}
accountID, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
}
var operations []*types.Operation
if signed {
// All operations excepts for cross-shard tx payout expend gas
gasExpended := new(big.Int).Mul(new(big.Int).SetUint64(receipt.GasUsed), tx.GasPrice())
operations = newNativeOperationsWithGasNoSubAccount(gasExpended, accountID)
}
// Format staking message for metadata using decimal numbers (hence usage of rpcV2)
rpcStakingTx, err := rpcV2.NewStakingTransaction(tx, ethcommon.Hash{}, 0, 0, 0, signed)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
metadata, err := types.MarshalMap(rpcStakingTx.Msg)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
// Set correct amount depending on staking message directive that apply balance changes INSTANTLY
var amount *types.Amount
switch tx.StakingType() {
case stakingTypes.DirectiveCreateValidator:
if amount, rosettaError = getAmountFromCreateValidatorMessage(tx.Data()); rosettaError != nil {
return nil, rosettaError
}
case stakingTypes.DirectiveDelegate:
if amount, rosettaError = getAmountFromDelegateMessage(receipt, tx.Data()); rosettaError != nil {
return nil, rosettaError
}
case stakingTypes.DirectiveUndelegate:
if amount, rosettaError = getAmountFromUnDelegateMessage(receipt, tx.Data()); rosettaError != nil {
return nil, rosettaError
}
case stakingTypes.DirectiveCollectRewards:
if amount, rosettaError = getAmountFromCollectRewards(receipt, senderAddress); rosettaError != nil {
return nil, rosettaError
}
default:
amount = &types.Amount{
Value: "0", // All other staking transactions do not apply balance changes instantly or at all
Currency: &common.NativeCurrency,
}
}
if len(operations) > 0 {
operations = append(operations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: operations[0].OperationIdentifier.Index + 1,
},
Type: tx.StakingType().String(),
Status: GetTransactionStatus(tx, receipt),
Account: accountID,
Amount: amount,
Metadata: metadata,
})
} else {
operations = []*types.Operation{{
OperationIdentifier: &types.OperationIdentifier{
Index: 0,
},
Type: tx.StakingType().String(),
Status: GetTransactionStatus(tx, receipt),
Account: accountID,
Amount: amount,
Metadata: metadata,
}}
}
if signed {
// expose delegated balance
if tx.StakingType() == stakingTypes.DirectiveDelegate {
ops := getDelegateOperationForSubAccount(tx, receipt, operations[1])
return append(operations, ops...), nil
}
if tx.StakingType() == stakingTypes.DirectiveUndelegate {
op2 := getUndelegateOperationForSubAccount(tx, operations[1], receipt)
return append(operations, op2), nil
}
}
return operations, nil
}
func getUndelegateOperationForSubAccount(
tx *stakingTypes.StakingTransaction, delegateOperation *types.Operation, receipt *hmytypes.Receipt,
) *types.Operation {
// set sub account
validatorAddress := delegateOperation.Metadata["validatorAddress"]
delegateOperation.Account.SubAccount = &types.SubAccountIdentifier{
Address: validatorAddress.(string),
Metadata: map[string]interface{}{
SubAccountMetadataKey: Delegation,
},
}
amount := &types.Amount{
Value: positiveStringValue(delegateOperation.Amount.Value),
Currency: delegateOperation.Amount.Currency,
Metadata: delegateOperation.Amount.Metadata,
}
undelegateion := &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: delegateOperation.OperationIdentifier.Index + 1,
},
Type: tx.StakingType().String(),
Status: GetTransactionStatus(tx, receipt),
Account: &types.AccountIdentifier{
Address: delegateOperation.Account.Address,
SubAccount: &types.SubAccountIdentifier{
Address: validatorAddress.(string),
Metadata: map[string]interface{}{
SubAccountMetadataKey: UnDelegation,
},
},
Metadata: delegateOperation.Account.Metadata,
},
Amount: amount,
Metadata: delegateOperation.Metadata,
}
return undelegateion
}
func getDelegateOperationForSubAccount(tx *stakingTypes.StakingTransaction, receipt *hmytypes.Receipt, delegateOperation *types.Operation) (ops []*types.Operation) {
msg, err := stakingTypes.RLPDecodeStakeMsg(tx.Data(), stakingTypes.DirectiveDelegate)
if err != nil {
return nil
}
stkMsg, ok := msg.(*stakingTypes.Delegate)
if !ok {
return nil
}
amt, _ := new(big.Int).SetString(delegateOperation.Amount.Value, 10)
delegateAmt := new(big.Int).Sub(new(big.Int).SetUint64(0), amt)
validatorAddress := delegateOperation.Metadata["validatorAddress"]
idx := int64(0)
logs := hmytypes.FindLogsWithTopic(receipt, staking.DelegateTopic)
for _, log := range logs {
if len(log.Data) > ethcommon.AddressLength && log.Address == stkMsg.DelegatorAddress {
// add undelegated transaction
subAccount := log.Data[:ethcommon.AddressLength]
address := internalCommon.BytesToAddress(subAccount)
b32Address, err := bech32.ConvertAndEncode(internalCommon.Bech32AddressHRP, address.Bytes())
if err != nil {
return nil
}
deductedAmt := new(big.Int).SetBytes(log.Data[ethcommon.AddressLength:])
delegateAmt = stkMsg.Amount
idx++
ops = append(ops, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: delegateOperation.OperationIdentifier.Index + idx,
},
RelatedOperations: []*types.OperationIdentifier{
{
Index: delegateOperation.OperationIdentifier.Index,
},
},
Type: tx.StakingType().String(),
Status: delegateOperation.Status,
Account: &types.AccountIdentifier{
Address: delegateOperation.Account.Address,
SubAccount: &types.SubAccountIdentifier{
Address: b32Address,
Metadata: map[string]interface{}{
SubAccountMetadataKey: UnDelegation,
},
},
Metadata: delegateOperation.Account.Metadata,
},
Amount: &types.Amount{
Value: negativeBigValue(deductedAmt),
Currency: delegateOperation.Amount.Currency,
Metadata: delegateOperation.Amount.Metadata,
},
Metadata: delegateOperation.Metadata,
})
}
}
idx++
return append(ops, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: delegateOperation.OperationIdentifier.Index + idx,
},
RelatedOperations: []*types.OperationIdentifier{
{
Index: delegateOperation.OperationIdentifier.Index,
},
},
Type: tx.StakingType().String(),
Status: delegateOperation.Status,
Account: &types.AccountIdentifier{
Address: delegateOperation.Account.Address,
SubAccount: &types.SubAccountIdentifier{
Address: validatorAddress.(string),
Metadata: map[string]interface{}{
SubAccountMetadataKey: Delegation,
},
},
Metadata: delegateOperation.Account.Metadata,
},
Amount: &types.Amount{
Value: delegateAmt.String(),
Currency: delegateOperation.Amount.Currency,
Metadata: delegateOperation.Amount.Metadata,
},
Metadata: delegateOperation.Metadata,
})
}
// getSideEffectOperationsFromUndelegationPayouts from the given payouts.
// If the startingOperationIndex is provided, all operations will be indexed starting from the given operation index.
func getSideEffectOperationsFromUndelegationPayouts(
payouts *hmy.UndelegationPayouts, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
return getSideEffectOperationsFromUndelegationPayoutsMap(
payouts, common.UndelegationPayoutOperation, startingOperationIndex,
)
}
// GetSideEffectOperationsFromPreStakingRewards from the given rewards.
// If the startingOperationIndex is provided, all operations will be indexed starting from the given operation index.
func GetSideEffectOperationsFromPreStakingRewards(
rewards hmy.PreStakingBlockRewards, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
return getSideEffectOperationsFromValueMap(
rewards, common.PreStakingBlockRewardOperation, startingOperationIndex,
)
}
// GetSideEffectOperationsFromGenesisSpec for the given spec.
// If the startingOperationIndex is provided, all operations will be indexed starting from the given operation index.
func GetSideEffectOperationsFromGenesisSpec(
spec *core.Genesis, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
valueMap := map[ethcommon.Address]*big.Int{}
for address, acc := range spec.Alloc {
valueMap[address] = acc.Balance
}
return getSideEffectOperationsFromValueMap(
valueMap, common.GenesisFundsOperation, startingOperationIndex,
)
}
// GetTransactionStatus for any valid harmony transaction given its receipt.
func GetTransactionStatus(tx hmytypes.PoolTransaction, receipt *hmytypes.Receipt) *string {
if _, ok := tx.(*hmytypes.Transaction); ok {
status := common.SuccessOperationStatus.Status
if common.GetDefaultFix().IsForceTxSuccess(tx.Hash().Hex()) {
return &status
} else if common.GetDefaultFix().IsForceTxFailed(tx.Hash().Hex()) {
status = common.FailureOperationStatus.Status
return &status
}
if receipt.Status == hmytypes.ReceiptStatusFailed {
if len(tx.Data()) == 0 && receipt.CumulativeGasUsed <= params.TxGas {
status = common.FailureOperationStatus.Status
} else {
status = common.ContractFailureOperationStatus.Status
}
}
return &status
} else if _, ok := tx.(*stakingTypes.StakingTransaction); ok {
return &common.SuccessOperationStatus.Status
}
// Type of tx unknown, so default to failure
return &common.FailureOperationStatus.Status
}
// getBasicTransferNativeOperations extracts & formats the basic native operations for non-staking transaction.
// Note that this does NOT include any contract related transfers (i.e: internal transactions).
func getBasicTransferNativeOperations(
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, senderAddress ethcommon.Address, toAddress *ethcommon.Address,
startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
if toAddress == nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "tx receiver not found",
})
}
// Common operation elements
status := GetTransactionStatus(tx, receipt)
from, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
}
to, rosettaError := newAccountIdentifier(*toAddress)
if rosettaError != nil {
return nil, rosettaError
}
return newSameShardTransferNativeOperations(from, to, tx.Value(), *status, startingOperationIndex), nil
}
// getContractTransferNativeOperations extracts & formats the native operations for any
// transaction involving a contract.
// Note that this will include any native tokens that were transferred from the contract (i.e: internal transactions).
func getContractTransferNativeOperations(
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, senderAddress ethcommon.Address, toAddress *ethcommon.Address,
contractInfo *ContractInfo, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
basicOps, rosettaError := getBasicTransferNativeOperations(
tx, receipt, senderAddress, toAddress, startingOperationIndex,
)
if rosettaError != nil {
return nil, rosettaError
}
status := GetTransactionStatus(tx, receipt)
startingIndex := basicOps[len(basicOps)-1].OperationIdentifier.Index + 1
internalTxOps, rosettaError := getContractInternalTransferNativeOperations(
contractInfo.ExecutionResult, *status, &startingIndex,
)
if rosettaError != nil {
return nil, rosettaError
}
return append(basicOps, internalTxOps...), nil
}
// getContractCreationNativeOperations extracts & formats the native operations for a contract creation tx
func getContractCreationNativeOperations(
tx *hmytypes.Transaction, receipt *hmytypes.Receipt, senderAddress ethcommon.Address, contractInfo *ContractInfo,
startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
basicOps, rosettaError := getBasicTransferNativeOperations(
tx, receipt, senderAddress, &receipt.ContractAddress, startingOperationIndex,
)
if rosettaError != nil {
return nil, rosettaError
}
for _, op := range basicOps {
op.Type = common.ContractCreationOperation
}
status := GetTransactionStatus(tx, receipt)
startingIndex := basicOps[len(basicOps)-1].OperationIdentifier.Index + 1
internalTxOps, rosettaError := getContractInternalTransferNativeOperations(
contractInfo.ExecutionResult, *status, &startingIndex,
)
if rosettaError != nil {
return nil, rosettaError
}
return append(basicOps, internalTxOps...), nil
}
var (
// internalNativeTransferEvmOps are the EVM operations that can execute a native transfer
// where the sender is a contract address. This is also known as ops for an 'internal' transaction.
// All operations have at least 7 elements on the stack when executed.
internalNativeTransferEvmOps = map[vm.OpCode]interface{}{
vm.CALL: struct{}{},
vm.CALLCODE: struct{}{},
vm.SELFDESTRUCT: struct{}{},
vm.CREATE: struct{}{},
vm.CREATE2: struct{}{},
}
)
// getContractInternalTransferNativeOperations extracts & formats the native operations for a contract's internal
// native token transfers (i.e: the sender of a transaction is the contract).
func getContractInternalTransferNativeOperations(
executionResult []*tracers.RosettaLogItem, status string,
startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
ops := []*types.Operation{}
if executionResult == nil {
// No error since nil execution result implies empty StructLogs, which is not an error.
return ops, nil
}
for _, log := range executionResult {
// skip meaningless information
if log.Value.Cmp(big.NewInt(0)) != 0 {
fromAccID, rosettaError := newAccountIdentifier(log.From)
if rosettaError != nil {
return nil, rosettaError
}
toAccID, rosettaError := newAccountIdentifier(log.To)
if rosettaError != nil {
return nil, rosettaError
}
txStatus := status
if log.Reverted {
txStatus = common.FailureOperationStatus.Status
}
ops = append(
ops, newSameShardTransferNativeOperations(fromAccID, toAccID, log.Value, txStatus, startingOperationIndex)...,
)
nextOpIndex := ops[len(ops)-1].OperationIdentifier.Index + 1
startingOperationIndex = &nextOpIndex
}
}
return ops, nil
}
// getCrossShardSenderTransferNativeOperations extracts & formats the native operation(s)
// for cross-shard-tx on the sender's shard.
func getCrossShardSenderTransferNativeOperations(tx *hmytypes.Transaction, receipt *hmytypes.Receipt, senderAddress ethcommon.Address, startingOperationIndex *int64) ([]*types.Operation, *types.Error) {
if tx.To() == nil {
return nil, common.NewError(common.CatchAllError, nil)
}
senderAccountID, rosettaError := newAccountIdentifier(senderAddress)
if rosettaError != nil {
return nil, rosettaError
}
receiverAccountID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
return nil, rosettaError
}
metadata, err := types.MarshalMap(common.CrossShardTransactionOperationMetadata{
From: senderAccountID,
To: receiverAccountID,
})
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
var opIndex int64
if startingOperationIndex != nil {
opIndex = *startingOperationIndex
}
status := GetTransactionStatus(tx, receipt)
return []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: opIndex,
},
Type: common.NativeCrossShardTransferOperation,
Status: status,
Account: senderAccountID,
Amount: &types.Amount{
Value: negativeBigValue(tx.Value()),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
},
}, nil
}
// delegator address => validator address => amount
func getSideEffectOperationsFromUndelegationPayoutsMap(
undelegationPayouts *hmy.UndelegationPayouts, opType string, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
var opIndex int64
operations := []*types.Operation{}
if startingOperationIndex != nil {
opIndex = *startingOperationIndex
}
for validator, undelegationMap := range undelegationPayouts.Data {
ops, err := getOperationAndTotalAmountFromUndelegationMap(validator, &opIndex, opType, undelegationMap)
if err != nil {
return nil, err
}
operations = append(operations, ops...)
}
return operations, nil
}
// getOperationAndTotalAmountFromUndelegationMap is a helper for getSideEffectOperationsFromUndelegationPayoutsMap which actually
// has some side effect(opIndex will be increased by this function) so be careful while using for other purpose
func getOperationAndTotalAmountFromUndelegationMap(validator ethcommon.Address, opIndex *int64,
opType string, undelegationMap map[ethcommon.Address]*big.Int) ([]*types.Operation, *types.Error) {
var operations []*types.Operation
for delegator, amount := range undelegationMap {
subAccId, rosettaError := newAccountIdentifierWithSubAccount(delegator, validator, map[string]interface{}{
SubAccountMetadataKey: UnDelegation,
})
if rosettaError != nil {
return nil, rosettaError
}
accID, rosettaError := newAccountIdentifier(delegator)
if rosettaError != nil {
return nil, rosettaError
}
receiverOp := &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: *opIndex,
},
Type: opType,
Status: &common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: amount.String(),
Currency: &common.NativeCurrency,
},
}
*opIndex++
payoutOp := &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: *opIndex,
},
RelatedOperations: []*types.OperationIdentifier{
receiverOp.OperationIdentifier,
},
Type: opType,
Status: &common.SuccessOperationStatus.Status,
Account: subAccId,
Amount: &types.Amount{
Value: negativeBigValue(amount),
Currency: &common.NativeCurrency,
},
}
operations = append(operations, receiverOp, payoutOp)
*opIndex++
}
return operations, nil
}
// getSideEffectOperationsFromValueMap is a helper for side effect operation construction from a address to value map.
func getSideEffectOperationsFromValueMap(
valueMap map[ethcommon.Address]*big.Int, opType string, startingOperationIndex *int64,
) ([]*types.Operation, *types.Error) {
var opIndex int64
operations := []*types.Operation{}
if startingOperationIndex != nil {
opIndex = *startingOperationIndex
} else {
opIndex = 0
}
for address, value := range valueMap {
accID, rosettaError := newAccountIdentifier(address)
if rosettaError != nil {
return nil, rosettaError
}
operations = append(operations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{
Index: opIndex,
},
Type: opType,
Status: &common.SuccessOperationStatus.Status,
Account: accID,
Amount: &types.Amount{
Value: value.String(),
Currency: &common.NativeCurrency,
},
})
opIndex++
}
return operations, nil
}
func getAmountFromCreateValidatorMessage(data []byte) (*types.Amount, *types.Error) {
msg, err := stakingTypes.RLPDecodeStakeMsg(data, stakingTypes.DirectiveCreateValidator)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
stkMsg, ok := msg.(*stakingTypes.CreateValidator)
if !ok {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "unable to parse staking message for create validator tx",
})
}
return &types.Amount{
Value: negativeBigValue(stkMsg.Amount),
Currency: &common.NativeCurrency,
}, nil
}
func getAmountFromDelegateMessage(receipt *hmytypes.Receipt, data []byte) (*types.Amount, *types.Error) {
msg, err := stakingTypes.RLPDecodeStakeMsg(data, stakingTypes.DirectiveDelegate)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
stkMsg, ok := msg.(*stakingTypes.Delegate)
if !ok {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "unable to parse staking message for delegate tx",
})
}
deductedAmt := stkMsg.Amount
logs := hmytypes.FindLogsWithTopic(receipt, staking.DelegateTopic)
for _, log := range logs {
if len(log.Data) > ethcommon.AddressLength && log.Address == stkMsg.DelegatorAddress {
// Remove re-delegation amount as funds were never credited to account's balance.
deductedAmt = new(big.Int).Sub(deductedAmt, new(big.Int).SetBytes(log.Data[ethcommon.AddressLength:]))
}
}
return &types.Amount{
Value: negativeBigValue(deductedAmt),
Currency: &common.NativeCurrency,
}, nil
}
func getAmountFromUnDelegateMessage(receipt *hmytypes.Receipt, data []byte) (*types.Amount, *types.Error) {
msg, err := stakingTypes.RLPDecodeStakeMsg(data, stakingTypes.DirectiveUndelegate)
if err != nil {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": err.Error(),
})
}
stkMsg, ok := msg.(*stakingTypes.Undelegate)
if !ok {
return nil, common.NewError(common.CatchAllError, map[string]interface{}{
"message": "unable to parse staking message for unDelegate tx",
})
}
deductedAmt := stkMsg.Amount
logs := hmytypes.FindLogsWithTopic(receipt, staking.UnDelegateTopic)
for _, log := range logs {
if len(log.Data) > ethcommon.AddressLength && log.Address == stkMsg.DelegatorAddress {
// Remove re-delegation amount as funds were never credited to account's balance.
deductedAmt = new(big.Int).Sub(deductedAmt, new(big.Int).SetBytes(log.Data[ethcommon.AddressLength:]))
}
}
return &types.Amount{
Value: negativeBigValue(deductedAmt),
Currency: &common.NativeCurrency,
}, nil
}
func getAmountFromCollectRewards(
receipt *hmytypes.Receipt, senderAddress ethcommon.Address,
) (*types.Amount, *types.Error) {
var amount *types.Amount
logs := hmytypes.FindLogsWithTopic(receipt, staking.CollectRewardsTopic)
for _, log := range logs {
if log.Address == senderAddress {
amount = &types.Amount{
Value: big.NewInt(0).SetBytes(log.Data).String(),
Currency: &common.NativeCurrency,
}
break
}
}
return amount, nil
}
// newSameShardTransferNativeOperations creates a new slice of operations for a native transfer on the same shard.
func newSameShardTransferNativeOperations(
from, to *types.AccountIdentifier, amount *big.Int, status string,
startingOperationIndex *int64,
) []*types.Operation {
// Subtraction operation elements
var opIndex int64
if startingOperationIndex != nil {
opIndex = *startingOperationIndex
} else {
opIndex = 0
}
subOperationID := &types.OperationIdentifier{
Index: opIndex,
}
subAmount := &types.Amount{
Value: negativeBigValue(amount),
Currency: &common.NativeCurrency,
}
// Addition operation elements
addOperationID := &types.OperationIdentifier{
Index: subOperationID.Index + 1,
}
addRelatedID := []*types.OperationIdentifier{
subOperationID,
}
addAmount := &types.Amount{
Value: amount.String(),
Currency: &common.NativeCurrency,
}
return []*types.Operation{
{
OperationIdentifier: subOperationID,
Type: common.NativeTransferOperation,
Status: &status,
Account: from,
Amount: subAmount,
},
{
OperationIdentifier: addOperationID,
RelatedOperations: addRelatedID,
Type: common.NativeTransferOperation,
Status: &status,
Account: to,
Amount: addAmount,
},
}
}
// newNativeOperationsWithGas creates a new operation with the gas fee as the first operation.
// Note: the gas fee is gasPrice * gasUsed.
func newNativeOperationsWithGas(
gasFeeInATTO *big.Int, accountID *types.AccountIdentifier,
) []*types.Operation {
return []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0, // gas operation is always first
},
Type: common.ExpendGasOperation,
Status: &common.SuccessOperationStatus.Status,
Account: &types.AccountIdentifier{
Address: accountID.Address,
Metadata: accountID.Metadata,
},
Amount: &types.Amount{
Value: negativeBigValue(gasFeeInATTO),
Currency: &common.NativeCurrency,
},
},
}
}
// newNativeOperationsWithGasNoSubAccount creates a new operation with the gas fee as the first operation.
// Note: the gas fee is gasPrice * gasUsed.
func newNativeOperationsWithGasNoSubAccount(
gasFeeInATTO *big.Int, accountID *types.AccountIdentifier,
) []*types.Operation {
return []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: 0, // gas operation is always first
},
Type: common.ExpendGasOperation,
Status: &common.SuccessOperationStatus.Status,
Account: &types.AccountIdentifier{
Address: accountID.Address,
Metadata: accountID.Metadata,
},
Amount: &types.Amount{
Value: negativeBigValue(gasFeeInATTO),
Currency: &common.NativeCurrency,
},
},
}
}