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

609 lines
19 KiB

package services
import (
"fmt"
"math/big"
"reflect"
"testing"
"github.com/coinbase/rosetta-sdk-go/types"
ethcommon "github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
hmytypes "github.com/harmony-one/harmony/core/types"
"github.com/harmony-one/harmony/internal/params"
"github.com/harmony-one/harmony/rosetta/common"
"github.com/harmony-one/harmony/staking"
stakingTypes "github.com/harmony-one/harmony/staking/types"
"github.com/harmony-one/harmony/test/helpers"
)
func TestGetStakingOperationsFromCreateValidator(t *testing.T) {
gasLimit := uint64(1e18)
createValidatorTxDescription := stakingTypes.Description{
Name: "SuperHero",
Identity: "YouWouldNotKnow",
Website: "Secret Website",
SecurityContact: "LicenseToKill",
Details: "blah blah blah",
}
tx, err := helpers.CreateTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
fromKey, _ := crypto.GenerateKey()
return stakingTypes.DirectiveCreateValidator, stakingTypes.CreateValidator{
Description: createValidatorTxDescription,
MinSelfDelegation: tenOnes,
MaxTotalDelegation: twelveOnes,
ValidatorAddress: crypto.PubkeyToAddress(fromKey.PublicKey),
Amount: tenOnes,
}
}, nil, 0, gasLimit, gasPrice)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := helpers.GetMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: negativeBigValue(tenOnes),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromDelegate(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
validatorKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
validatorAddr := crypto.PubkeyToAddress(validatorKey.PublicKey)
tx, err := helpers.CreateTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveDelegate, stakingTypes.Delegate{
DelegatorAddress: senderAddr,
ValidatorAddress: validatorAddr,
Amount: tenOnes,
}
}, senderKey, 0, gasLimit, gasPrice)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := helpers.GetMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: negativeBigValue(tenOnes),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromUndelegate(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
validatorKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
validatorAddr := crypto.PubkeyToAddress(validatorKey.PublicKey)
tx, err := helpers.CreateTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveUndelegate, stakingTypes.Undelegate{
DelegatorAddress: senderAddr,
ValidatorAddress: validatorAddr,
Amount: tenOnes,
}
}, senderKey, 0, gasLimit, gasPrice)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := helpers.GetMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("0"),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromCollectRewards(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
tx, err := helpers.CreateTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveCollectRewards, stakingTypes.CollectRewards{
DelegatorAddress: senderAddr,
}
}, senderKey, 0, gasLimit, gasPrice)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := helpers.GetMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
Logs: []*hmytypes.Log{
{
Address: senderAddr,
Topics: []ethcommon.Hash{staking.CollectRewardsTopic},
Data: tenOnes.Bytes(),
},
},
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("%v", tenOnes.Uint64()),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestGetStakingOperationsFromEditValidator(t *testing.T) {
gasLimit := uint64(1e18)
senderKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
senderAddr := crypto.PubkeyToAddress(senderKey.PublicKey)
tx, err := helpers.CreateTestStakingTransaction(func() (stakingTypes.Directive, interface{}) {
return stakingTypes.DirectiveEditValidator, stakingTypes.EditValidator{
ValidatorAddress: senderAddr,
}
}, senderKey, 0, gasLimit, gasPrice)
if err != nil {
t.Fatal(err.Error())
}
metadata, err := helpers.GetMessageFromStakingTx(tx)
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
gasUsed := uint64(1e5)
gasFee := new(big.Int).Mul(gasPrice, big.NewInt(int64(gasUsed)))
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusSuccessful, // Failed staking transaction are never saved on-chain
GasUsed: gasUsed,
}
refOperations := newNativeOperations(gasFee, senderAccID)
refOperations = append(refOperations, &types.Operation{
OperationIdentifier: &types.OperationIdentifier{Index: 1},
RelatedOperations: []*types.OperationIdentifier{
{Index: 0},
},
Type: tx.StakingType().String(),
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("0"),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
})
operations, rosettaError := GetOperationsFromStakingTransaction(tx, receipt)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestNewTransferNativeOperations(t *testing.T) {
signer := hmytypes.NewEIP155Signer(params.TestChainConfig.ChainID)
tx, err := helpers.CreateTestTransaction(
signer, 0, 0, 0, 1e18, gasPrice, big.NewInt(1), []byte("test"),
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
receiverAccID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
t.Fatal(rosettaError)
}
startingOpID := &types.OperationIdentifier{}
// Test failed 'contract' transaction
refOperations := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
{
Index: startingOpID.Index,
},
},
Type: common.TransferNativeOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: negativeBigValue(tx.Value()),
Currency: &common.NativeCurrency,
},
},
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 2,
},
RelatedOperations: []*types.OperationIdentifier{
{
Index: startingOpID.Index + 1,
},
},
Type: common.TransferNativeOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: receiverAccID,
Amount: &types.Amount{
Value: fmt.Sprintf("%v", tx.Value().Uint64()),
Currency: &common.NativeCurrency,
},
},
}
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusFailed,
}
operations, rosettaError := newTransferNativeOperations(startingOpID, tx, receipt, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
// Test successful plain / contract transaction
refOperations[0].Status = common.SuccessOperationStatus.Status
refOperations[1].Status = common.SuccessOperationStatus.Status
receipt.Status = hmytypes.ReceiptStatusSuccessful
operations, rosettaError = newTransferNativeOperations(startingOpID, tx, receipt, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestNewCrossShardSenderTransferNativeOperations(t *testing.T) {
signer := hmytypes.NewEIP155Signer(params.TestChainConfig.ChainID)
tx, err := helpers.CreateTestTransaction(
signer, 0, 1, 0, 1e18, gasPrice, big.NewInt(1), []byte("data-does-nothing"),
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
startingOpID := &types.OperationIdentifier{}
receiverAccID, rosettaError := newAccountIdentifier(*tx.To())
if rosettaError != nil {
t.Error(rosettaError)
}
metadata, err := types.MarshalMap(common.CrossShardTransactionOperationMetadata{
From: senderAccID,
To: receiverAccID,
})
if err != nil {
t.Fatal(err)
}
refOperations := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
startingOpID,
},
Type: common.CrossShardTransferNativeOperation,
Status: common.SuccessOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: negativeBigValue(tx.Value()),
Currency: &common.NativeCurrency,
},
Metadata: metadata,
},
}
operations, rosettaError := newCrossShardSenderTransferNativeOperations(startingOpID, tx, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestNewContractCreationNativeOperations(t *testing.T) {
dummyContractKey, err := crypto.GenerateKey()
if err != nil {
t.Fatalf(err.Error())
}
chainID := params.TestChainConfig.ChainID
signer := hmytypes.NewEIP155Signer(chainID)
tx, err := helpers.CreateTestContractCreationTransaction(
signer, 0, 0, 1e18, gasPrice, big.NewInt(0), []byte("test"),
)
if err != nil {
t.Fatal(err.Error())
}
senderAddr, err := tx.SenderAddress()
if err != nil {
t.Fatal(err.Error())
}
senderAccID, rosettaError := newAccountIdentifier(senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
startingOpID := &types.OperationIdentifier{}
// Test failed contract creation
contractAddr := crypto.PubkeyToAddress(dummyContractKey.PublicKey)
contractAddressID, rosettaError := newAccountIdentifier(contractAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
refOperations := []*types.Operation{
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 1,
},
RelatedOperations: []*types.OperationIdentifier{
startingOpID,
},
Type: common.ContractCreationOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: senderAccID,
Amount: &types.Amount{
Value: negativeBigValue(tx.Value()),
Currency: &common.NativeCurrency,
},
},
{
OperationIdentifier: &types.OperationIdentifier{
Index: startingOpID.Index + 2,
},
RelatedOperations: []*types.OperationIdentifier{
{
Index: startingOpID.Index + 1,
},
},
Type: common.ContractCreationOperation,
Status: common.ContractFailureOperationStatus.Status,
Account: contractAddressID,
Amount: &types.Amount{
Value: tx.Value().String(),
Currency: &common.NativeCurrency,
},
},
}
receipt := &hmytypes.Receipt{
Status: hmytypes.ReceiptStatusFailed,
ContractAddress: contractAddr,
}
operations, rosettaError := newContractCreationNativeOperations(startingOpID, tx, receipt, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
// Test successful contract creation
refOperations[0].Status = common.SuccessOperationStatus.Status
refOperations[1].Status = common.SuccessOperationStatus.Status
receipt.Status = hmytypes.ReceiptStatusSuccessful // Indicate successful tx
operations, rosettaError = newContractCreationNativeOperations(startingOpID, tx, receipt, senderAddr)
if rosettaError != nil {
t.Fatal(rosettaError)
}
if !reflect.DeepEqual(operations, refOperations) {
t.Errorf("Expected operations to be %v not %v", refOperations, operations)
}
if err := assertNativeOperationTypeUniquenessInvariant(operations); err != nil {
t.Error(err)
}
}
func TestNewNativeOperations(t *testing.T) {
accountID := &types.AccountIdentifier{
Address: "test-address",
}
gasFee := big.NewInt(int64(1e18))
amount := &types.Amount{
Value: negativeBigValue(gasFee),
Currency: &common.NativeCurrency,
}
ops := newNativeOperations(gasFee, accountID)
if len(ops) != 1 {
t.Fatalf("Expected new operations to be of length 1")
}
if !reflect.DeepEqual(ops[0].Account, accountID) {
t.Errorf("Expected account ID to be %v not %v", accountID, ops[0].OperationIdentifier)
}
if !reflect.DeepEqual(ops[0].Amount, amount) {
t.Errorf("Expected amount to be %v not %v", amount, ops[0].Amount)
}
if ops[0].Type != common.ExpendGasOperation {
t.Errorf("Expected operation to be %v not %v", common.ExpendGasOperation, ops[0].Type)
}
if ops[0].OperationIdentifier.Index != 0 {
t.Errorf("Expected operational ID to be of index 0")
}
if ops[0].Status != common.SuccessOperationStatus.Status {
t.Errorf("Expected operation status to be %v", common.SuccessOperationStatus.Status)
}
}