From bdd6feab1bf8722497bea04d6e76d8d37fd79eeb Mon Sep 17 00:00:00 2001 From: failfmi Date: Wed, 28 Apr 2021 09:27:02 +0300 Subject: [PATCH] e2e: fees validation (#230) * Validate db fees * Validate transaction transfers * Update e2e docs & config Signed-off-by: failfmi --- app/domain/repository/fee.go | 2 + app/persistence/fee/fee.go | 19 +++ docs/testing.md | 46 +++--- e2e/e2e_test.go | 239 +++++++++++++++++++++++++++----- e2e/service/database/compare.go | 28 +++- e2e/service/database/service.go | 32 +++++ e2e/service/database/status.go | 16 +++ e2e/setup/application.yml | 16 +-- e2e/util/compare.go | 27 ++++ e2e/util/expectation.go | 50 ++++++- e2e/util/hedera.go | 16 +++ 11 files changed, 424 insertions(+), 67 deletions(-) create mode 100644 e2e/util/compare.go diff --git a/app/domain/repository/fee.go b/app/domain/repository/fee.go index 0f68a45a1..1e0a649f0 100644 --- a/app/domain/repository/fee.go +++ b/app/domain/repository/fee.go @@ -19,6 +19,8 @@ package repository import "github.com/limechain/hedera-eth-bridge-validator/app/persistence/entity" type Fee interface { + // Returns Fee. Returns nil if not found + Get(txId string) (*entity.Fee, error) Create(entity *entity.Fee) error UpdateStatusCompleted(txId string) error UpdateStatusFailed(txId string) error diff --git a/app/persistence/fee/fee.go b/app/persistence/fee/fee.go index cf51a3387..965f6ff7e 100644 --- a/app/persistence/fee/fee.go +++ b/app/persistence/fee/fee.go @@ -17,6 +17,7 @@ package fee import ( + "errors" "github.com/limechain/hedera-eth-bridge-validator/app/persistence/entity" "github.com/limechain/hedera-eth-bridge-validator/app/persistence/entity/fee" "github.com/limechain/hedera-eth-bridge-validator/config" @@ -36,6 +37,24 @@ func NewRepository(dbClient *gorm.DB) *Repository { } } +// Returns Fee. Returns nil if not found +func (r Repository) Get(id string) (*entity.Fee, error) { + record := &entity.Fee{} + + result := r.dbClient. + Model(entity.Fee{}). + Where("transaction_id = ?", id). + First(record) + + if result.Error != nil { + if errors.Is(result.Error, gorm.ErrRecordNotFound) { + return nil, nil + } + return nil, result.Error + } + return record, nil +} + func (r Repository) Create(entity *entity.Fee) error { return r.dbClient.Create(entity).Error } diff --git a/docs/testing.md b/docs/testing.md index feff49eee..06ab85190 100644 --- a/docs/testing.md +++ b/docs/testing.md @@ -6,29 +6,35 @@ Before you run E2E tests, you need to have a running application. ### Configuration -Configure properties, needed to run e2e tests in `e2e/config/application.yml`. +Configure properties, needed to run e2e tests in `e2e/setup/application.yml`. **Keep in mind that most of the configuration needs to be the same as the application's**. It supports the following configurations: -| Name | Description | -| ----------------------------------------- | ------------------------------------------------------------------------------------------ | -| `hedera.bridge_account` | The configured Bridge account. Validators listen for CryptoTranfers crediting that account | -| `hedera.topic_id` | The configured Bridge Topic. Validators watch & submit signatures to that Topic | -| `hedera.sender.account` | The account that will be sending the Hbars through the bridge | -| `hedera.sender.private_key` | The private key for the account that will be sending Hbars through the bridge | -| `hedera.ethereum.node_url` | The Ethereum Node that will be used for querying data | -| `hedera.ethereum.whbar_contract_address` | The ERC20 WHBAR contract on Ethereum | -| `hedera.ethereum.bridge_contract_address` | The Bridge contract on Ethereum | -| `hedera.validator_url` | The URL of the Validator node. Used for querying Metadata | -| `hedera.eth_signer` | The private key for the account that will execute mint transactions | -| `hedera.network_type` | Which Hedera network to use. Can be either `mainnet`, `previewnet`, `testnet`. | -| `hedera.tokens.whbar` | The whbar ID which will be used for the tests. | -| `hedera.tokens.wtoken` | The token ID which will be used for the token related tests. | -| `hedera.db_validation.host` | The IP or hostname used to connect to the database. | -| `hedera.db_validation.name` | The name of the database. | -| `hedera.db_validation.password` | The database password the processor uses to connect. | -| `hedera.db_validation.port` | The port used to connect to the database. | -| `hedera.db_validation.username` | The username the processor uses to connect to the database. | +Name | Description | +----------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +`hedera.bridge_account` | The configured Bridge account. Validators listen for CRYPTOTRANSFER transactions, crediting that account. +`hedera.dbs[].host` | The IP or hostname used to connect to the database. +`hedera.dbs[].name` | The name of the database. +`hedera.dbs[].password` | The database password the processor uses to connect. +`hedera.dbs[].port` | The port used to connect to the database. +`hedera.dbs[].username` | The username the processor uses to connect to the database. +`hedera.fee_percentage` | The percentage which validators take for every bridge transfer. Range is from 0 to 100.000 (multiplied by 1 000). Examples: 1% is 1 000, 1.234% = 1234, 0.15% = 150. Default 10% = 10 000. +`hedera.members` | The Hedera account ids of the validators, to which their bridge fees will be sent (if Bridge accepts Hedera Tokens, associations with these tokens will be required). Used to assert balances after transactions. +`hedera.mirror_node.api_address` | The Hedera Rest API root endpoint. Depending on the Hedera network type, this will need to be changed. +`hedera.mirror_node.client_address` | The HCS Mirror node endpoint. Depending on the Hedera network type, this will need to be changed. +`hedera.mirror_node.max_retries` | The maximum number of retries that the mirror node has to continue monitoring after a failure, before stopping completely. +`hedera.mirror_node.polling_interval` | How often (in seconds) the application will poll the mirror node for new transactions. +`hedera.network_type` | Which Hedera network to use. Can be either `mainnet`, `previewnet`, `testnet`. +`hedera.sender.account` | The account that will be sending assets through the bridge. +`hedera.sender.private_key` | The private key for the account that will be sending assets through the bridge. +`hedera.topic_id` | The configured Bridge Topic. Validators watch & submit signatures to that Topic. +`ethereum.block_confirmations` | The number of block confirmations to wait for before processing an ethereum event. +`ethereum.node_url` | The Ethereum Node that will be used for querying data. +`ethereum.private_key` | The private key for the account which executes mint/burn operations. The derived address of the key serves as an `HBAR->Ethereum` memo (receiver). +`ethereum.router_contract_address` | The address of the Router Contract. +`tokens.whbar` | The native asset, which represents HBAR. Used as a bridged asset between the two networks. +`tokens.wtoken` | The native asset, which represents a Token on Hedera. Used as a bridged asset between the two networks. +`validator_url` | The URL of the Validator node. Used for querying Metadata. ### Run E2E Tests diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 3ba800a88..e801bc0fb 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -22,6 +22,7 @@ import ( "github.com/ethereum/go-ethereum/accounts/abi" "github.com/ethereum/go-ethereum/core/types" "github.com/ethereum/go-ethereum/crypto" + mirror_node "github.com/limechain/hedera-eth-bridge-validator/app/clients/hedera/mirror-node" hederahelper "github.com/limechain/hedera-eth-bridge-validator/app/helper/hedera" "github.com/limechain/hedera-eth-bridge-validator/constants" "github.com/limechain/hedera-eth-bridge-validator/e2e/util" @@ -51,7 +52,7 @@ var ( tinyBarAmount int64 = 1000000000 hBarSendAmount = hedera.HbarFromTinybar(tinyBarAmount) hbarRemovalAmount = hedera.HbarFromTinybar(-tinyBarAmount) - now = time.Now() + now time.Time ) const ( @@ -60,8 +61,10 @@ const ( func Test_HBAR(t *testing.T) { setupEnv := setup.Load() + now = time.Now() memo := setupEnv.EthReceiver.String() + mintAmount, fee := calculateReceiverAndFeeAmounts(setupEnv, hBarSendAmount.AsTinybar()) // Step 1 - Verify the transfer of Hbars to the Bridge Account transactionResponse, wrappedBalanceBefore := verifyTransferToBridgeAccount(setupEnv, memo, setupEnv.EthReceiver, t) @@ -69,21 +72,22 @@ func Test_HBAR(t *testing.T) { // Step 2 - Verify the submitted topic messages receivedSignatures := verifyTopicMessages(setupEnv, transactionResponse, t) - mintAmount := calculateValidAmount(setupEnv, hBarSendAmount.AsTinybar()) + // Step 3 - Validate fee scheduled transaction + scheduledTxID, scheduleID := validateMembersScheduledTxs(setupEnv, constants.Hbar, generateMirrorNodeExpectedTransfersForHederaTransfer(setupEnv, constants.Hbar, fee), t) - // Step 3 - Verify Transfer retrieved from Validator API + // Step 4 - Verify Transfer retrieved from Validator API transactionData, tokenAddress := verifyTransferFromValidatorAPI(setupEnv, transactionResponse, constants.Hbar, mintAmount, t) - // Step 4 - Submit Mint transaction + // Step 5 - Submit Mint transaction txHash := submitMintTransaction(setupEnv, transactionResponse, transactionData, tokenAddress, t) - // Step 5 - Wait for transaction to be mined + // Step 6 - Wait for transaction to be mined waitForTransaction(setupEnv, txHash, t) - // Step 6 - Validate Token balances + // Step 7 - Validate Token balances verifyWrappedAssetBalance(setupEnv, constants.Hbar, big.NewInt(mintAmount), wrappedBalanceBefore, setupEnv.EthReceiver, t) - // Step 7 - Prepare Comparable Expected Transfer Record + // Step 8 - Prepare Comparable Expected Transfer Record expectedTxRecord := util.PrepareExpectedTransfer( setupEnv.Clients.RouterContract, transactionResponse.TransactionID, @@ -94,15 +98,25 @@ func Test_HBAR(t *testing.T) { Status: entity_transfer.StatusCompleted, StatusSignature: entity_transfer.StatusSignatureMined, }, t) - - // Step 8 - Verify Database Records + // and: + expectedFeeRecord := util.PrepareExpectedFeeRecord( + scheduledTxID, + scheduleID, fee, + hederahelper.ToMirrorNodeTransactionID(transactionResponse.TransactionID.String()), + "") + + // Step 9 - Verify Database Records verifyTransferRecordAndSignatures(setupEnv.DbValidator, expectedTxRecord, strconv.FormatInt(mintAmount, 10), receivedSignatures, t) + // and: + verifyFeeRecord(setupEnv.DbValidator, expectedFeeRecord, t) } func Test_E2E_Token_Transfer(t *testing.T) { setupEnv := setup.Load() + now = time.Now() memo := setupEnv.EthReceiver.String() + mintAmount, fee := calculateReceiverAndFeeAmounts(setupEnv, tinyBarAmount) // Step 1 - Verify the transfer of HTS to the Bridge Account transactionResponse, wrappedBalanceBefore := verifyTokenTransferToBridgeAccount(setupEnv, memo, setupEnv.EthReceiver, t) @@ -110,21 +124,22 @@ func Test_E2E_Token_Transfer(t *testing.T) { // Step 2 - Verify the submitted topic messages receivedSignatures := verifyTopicMessages(setupEnv, transactionResponse, t) - mintAmount := calculateValidAmount(setupEnv, tinyBarAmount) + // Step 3 - Validate fee scheduled transaction + scheduledTxID, scheduleID := validateMembersScheduledTxs(setupEnv, setupEnv.TokenID.String(), generateMirrorNodeExpectedTransfersForHederaTransfer(setupEnv, setupEnv.TokenID.String(), fee), t) - // Step 3 - Verify Transfer retrieved from Validator API + // Step 4 - Verify Transfer retrieved from Validator API transactionData, tokenAddress := verifyTransferFromValidatorAPI(setupEnv, transactionResponse, setupEnv.TokenID.String(), mintAmount, t) - // Step 4 - Submit Mint transaction + // Step 5 - Submit Mint transaction txHash := submitMintTransaction(setupEnv, transactionResponse, transactionData, tokenAddress, t) - // Step 5 - Wait for transaction to be mined + // Step 6 - Wait for transaction to be mined waitForTransaction(setupEnv, txHash, t) - // Step 6 - Validate Token balances + // Step 7 - Validate Token balances verifyWrappedAssetBalance(setupEnv, setupEnv.TokenID.String(), big.NewInt(mintAmount), wrappedBalanceBefore, setupEnv.EthReceiver, t) - // Step 7 - Verify Database records + // Step 8 - Verify Database records expectedTxRecord := util.PrepareExpectedTransfer( setupEnv.Clients.RouterContract, transactionResponse.TransactionID, @@ -135,9 +150,17 @@ func Test_E2E_Token_Transfer(t *testing.T) { Status: entity_transfer.StatusCompleted, StatusSignature: entity_transfer.StatusSignatureMined, }, t) - - // Step 8 - Verify Database Records + // and: + expectedFeeRecord := util.PrepareExpectedFeeRecord( + scheduledTxID, + scheduleID, fee, + hederahelper.ToMirrorNodeTransactionID(transactionResponse.TransactionID.String()), + "") + + // Step 9 - Verify Database Records verifyTransferRecordAndSignatures(setupEnv.DbValidator, expectedTxRecord, strconv.FormatInt(mintAmount, 10), receivedSignatures, t) + // and: + verifyFeeRecord(setupEnv.DbValidator, expectedFeeRecord, t) } func Test_Ethereum_Hedera_HBAR(t *testing.T) { @@ -145,8 +168,8 @@ func Test_Ethereum_Hedera_HBAR(t *testing.T) { now = time.Now() accountBalanceBefore := util.GetHederaAccountBalance(setupEnv.Clients.Hedera, setupEnv.Clients.Hedera.GetOperatorAccountID(), t) - // 1. Calculate Expected Receive Amount - expectedReceiveAmount := calculateValidAmount(setupEnv, receiveAmount) + // 1. Calculate Expected Receive And Fee Amounts + expectedReceiveAmount, fee := calculateReceiverAndFeeAmounts(setupEnv, receiveAmount) // 2. Submit burn transaction to the bridge contract burnTxReceipt, expectedRouterBurn := sendEthTransaction(setupEnv, constants.Hbar, t) @@ -155,23 +178,28 @@ func Test_Ethereum_Hedera_HBAR(t *testing.T) { expectedId := validateBurnEvent(burnTxReceipt, expectedRouterBurn, t) // 4. Validate that a scheduled transaction was submitted - scheduleId := validateScheduledTx(setupEnv, t) + transactionID, scheduleID := validateSubmittedScheduledTx(setupEnv, constants.Hbar, generateMirrorNodeExpectedTransfersForBurnEvent(setupEnv, constants.Hbar, expectedReceiveAmount, fee), t) // 5. Validate that the balance of the receiver account (hedera) was changed with the correct amount validateReceiverAccountBalance(setupEnv, uint64(expectedReceiveAmount), accountBalanceBefore, constants.Hbar, t) - // 6. Prepare Expected Database Record + // 6. Prepare Expected Database Records expectedBurnEventRecord := util.PrepareExpectedBurnEventRecord( - scheduleId, + scheduleID, receiveAmount, setupEnv.Clients.Hedera.GetOperatorAccountID(), - expectedId) + expectedId, + transactionID) + // and: + expectedFeeRecord := util.PrepareExpectedFeeRecord(transactionID, scheduleID, fee, "", expectedId) // 7. Wait for validators to update DB state after Scheduled TX is mined time.Sleep(10 * time.Second) - // 8. Validate Database Record + // 8. Validate Database Records verifyBurnEventRecord(setupEnv.DbValidator, expectedBurnEventRecord, t) + // and: + verifyFeeRecord(setupEnv.DbValidator, expectedFeeRecord, t) } func Test_Ethereum_Hedera_Token(t *testing.T) { @@ -180,7 +208,7 @@ func Test_Ethereum_Hedera_Token(t *testing.T) { accountBalanceBefore := util.GetHederaAccountBalance(setupEnv.Clients.Hedera, setupEnv.Clients.Hedera.GetOperatorAccountID(), t) // 1. Calculate Expected Receive Amount - expectedReceiveAmount := calculateValidAmount(setupEnv, receiveAmount) + expectedReceiveAmount, fee := calculateReceiverAndFeeAmounts(setupEnv, receiveAmount) // 2. Submit burn transaction to the bridge contract burnTxReceipt, expectedRouterBurn := sendEthTransaction(setupEnv, setupEnv.TokenID.String(), t) @@ -189,23 +217,28 @@ func Test_Ethereum_Hedera_Token(t *testing.T) { expectedId := validateBurnEvent(burnTxReceipt, expectedRouterBurn, t) // 4. Validate that a scheduled transaction was submitted - scheduleId := validateScheduledTx(setupEnv, t) + transactionID, scheduleID := validateSubmittedScheduledTx(setupEnv, setupEnv.TokenID.String(), generateMirrorNodeExpectedTransfersForBurnEvent(setupEnv, setupEnv.TokenID.String(), expectedReceiveAmount, fee), t) // 5. Validate that the balance of the receiver account (hedera) was changed with the correct amount validateReceiverAccountBalance(setupEnv, uint64(expectedReceiveAmount), accountBalanceBefore, setupEnv.TokenID.String(), t) - // 6. Prepare Expected Database Record + // 6. Prepare Expected Database Records expectedBurnEventRecord := util.PrepareExpectedBurnEventRecord( - scheduleId, + scheduleID, receiveAmount, setupEnv.Clients.Hedera.GetOperatorAccountID(), - expectedId) + expectedId, + transactionID) + // and: + expectedFeeRecord := util.PrepareExpectedFeeRecord(transactionID, scheduleID, fee, "", expectedId) // 7. Wait for validators to update DB state after Scheduled TX is mined time.Sleep(10 * time.Second) - // 8. Validate Database Record + // 8. Validate Database Records verifyBurnEventRecord(setupEnv.DbValidator, expectedBurnEventRecord, t) + // and: + verifyFeeRecord(setupEnv.DbValidator, expectedFeeRecord, t) } func validateReceiverAccountBalance(setup *setup.Setup, expectedReceiveAmount uint64, beforeHbarBalance hedera.AccountBalance, asset string, t *testing.T) { @@ -272,24 +305,67 @@ func validateBurnEvent(txReceipt *types.Receipt, expectedRouterBurn *routerContr return "" } -func validateScheduledTx(setupEnv *setup.Setup, t *testing.T) string { +func validateSubmittedScheduledTx(setupEnv *setup.Setup, asset string, expectedTransfers []mirror_node.Transfer, t *testing.T) (transactionID, scheduleID string) { + receiverTransactionID, receiverScheduleID := validateScheduledTx(setupEnv, setupEnv.Clients.Hedera.GetOperatorAccountID(), asset, expectedTransfers, t) + + membersTransactionID, membersScheduleID := validateMembersScheduledTxs(setupEnv, asset, expectedTransfers, t) + + if receiverTransactionID != membersTransactionID { + t.Fatalf("Scheduled Transactions between members are different. Receiver [%s], Member [%s]", receiverTransactionID, membersTransactionID) + } + + if receiverScheduleID != membersScheduleID { + t.Fatalf("Scheduled IDs between members are different. Receiver [%s], Member [%s]", receiverScheduleID, membersScheduleID) + } + + return receiverTransactionID, receiverScheduleID +} + +func validateScheduledTx(setupEnv *setup.Setup, account hedera.AccountID, asset string, expectedTransfers []mirror_node.Transfer, t *testing.T) (transactionID, scheduleID string) { timeLeft := 180 for { - transactions, err := setupEnv.Clients.MirrorNode.GetAccountCreditTransactionsAfterTimestamp(setupEnv.Clients.Hedera.GetOperatorAccountID(), now.UnixNano()) + response, err := setupEnv.Clients.MirrorNode.GetAccountCreditTransactionsAfterTimestamp(account, now.UnixNano()) if err != nil { t.Fatal(err) } - for _, transaction := range transactions.Transactions { + if len(response.Transactions) > 1 { + t.Fatalf("[%s] - Found [%d] new transactions, must be 1.", account, len(response.Transactions)) + } + + for _, transaction := range response.Transactions { if transaction.Scheduled == true { scheduleCreateTx, err := setupEnv.Clients.MirrorNode.GetTransaction(transaction.TransactionID) if err != nil { t.Fatal(err) } + for _, expectedTransfer := range expectedTransfers { + found := false + if asset == constants.Hbar { + for _, transfer := range transaction.Transfers { + if expectedTransfer == transfer { + found = true + break + } + } + } else { + for _, transfer := range transaction.TokenTransfers { + if expectedTransfer == transfer { + found = true + break + } + } + } + + if !found { + t.Fatalf("[%s] - Expected transfer [%v] not found.", transaction.TransactionID, expectedTransfer) + } + } + for _, tx := range scheduleCreateTx.Transactions { if tx.EntityId != "" { - return tx.EntityId + return tx.TransactionID, tx.EntityId } } } @@ -305,17 +381,41 @@ func validateScheduledTx(setupEnv *setup.Setup, t *testing.T) string { } t.Fatalf("Could not find any scheduled transactions for account [%s]", setupEnv.Clients.Hedera.GetOperatorAccountID()) - return "" + return "", "" } -func calculateValidAmount(setup *setup.Setup, amount int64) int64 { +func validateMembersScheduledTxs(setupEnv *setup.Setup, asset string, expectedTransfers []mirror_node.Transfer, t *testing.T) (transactionID, scheduleID string) { + if len(setupEnv.Members) == 0 { + return "", "" + } + + var transactions []string + var scheduleIDs []string + for _, member := range setupEnv.Members { + txID, scheduleID := validateScheduledTx(setupEnv, member, asset, expectedTransfers, t) + transactions = append(transactions, txID) + + if !util.AllSame(transactions) { + t.Fatalf("Transaction [%s] does not match with previously added transactions.", txID) + } + scheduleIDs = append(scheduleIDs, scheduleID) + + if !util.AllSame(scheduleIDs) { + t.Fatalf("ScheduleID [%s] does not match with previously added ids", scheduleID) + } + } + + return transactions[0], scheduleIDs[0] +} + +func calculateReceiverAndFeeAmounts(setup *setup.Setup, amount int64) (receiverAmount, fee int64) { fee, remainder := setup.Clients.FeeCalculator.CalculateFee(amount) validFee := setup.Clients.Distributor.ValidAmount(fee) if validFee != fee { remainder += fee - validFee } - return remainder + return remainder, validFee } func submitMintTransaction(setupEnv *setup.Setup, transactionResponse hedera.TransactionResponse, transactionData *service.TransferData, tokenAddress *common.Address, t *testing.T) common.Hash { @@ -347,6 +447,61 @@ func submitMintTransaction(setupEnv *setup.Setup, transactionResponse hedera.Tra return res.Hash() } +func generateMirrorNodeExpectedTransfersForBurnEvent(setupEnv *setup.Setup, asset string, amount, fee int64) []mirror_node.Transfer { + total := amount + fee + feePerMember := fee / int64(len(setupEnv.Members)) + + var expectedTransfers []mirror_node.Transfer + expectedTransfers = append(expectedTransfers, mirror_node.Transfer{ + Account: setupEnv.BridgeAccount.String(), + Amount: -total, + }, + mirror_node.Transfer{ + Account: setupEnv.Clients.Hedera.GetOperatorAccountID().String(), + Amount: amount, + }) + + for _, member := range setupEnv.Members { + expectedTransfers = append(expectedTransfers, mirror_node.Transfer{ + Account: member.String(), + Amount: feePerMember, + }) + } + + if asset != constants.Hbar { + for i := range expectedTransfers { + expectedTransfers[i].Token = asset + } + } + + return expectedTransfers +} + +func generateMirrorNodeExpectedTransfersForHederaTransfer(setupEnv *setup.Setup, asset string, fee int64) []mirror_node.Transfer { + feePerMember := fee / int64(len(setupEnv.Members)) + + var expectedTransfers []mirror_node.Transfer + expectedTransfers = append(expectedTransfers, mirror_node.Transfer{ + Account: setupEnv.BridgeAccount.String(), + Amount: -fee, + }) + + for _, member := range setupEnv.Members { + expectedTransfers = append(expectedTransfers, mirror_node.Transfer{ + Account: member.String(), + Amount: feePerMember, + }) + } + + if asset != constants.Hbar { + for i := range expectedTransfers { + expectedTransfers[i].Token = asset + } + } + + return expectedTransfers +} + func sendEthTransaction(setupEnv *setup.Setup, asset string, t *testing.T) (*types.Receipt, *routerContract.RouterBurn) { wrappedAsset, err := setup.WrappedAsset(setupEnv.Clients.RouterContract, asset) if err != nil { @@ -469,6 +624,16 @@ func verifyBurnEventRecord(dbValidation *database.Service, expectedRecord *entit } } +func verifyFeeRecord(dbValidation *database.Service, expectedRecord *entity.Fee, t *testing.T) { + ok, err := dbValidation.VerifyFeeRecord(expectedRecord) + if err != nil { + t.Fatalf("[%s] - Verification of database records failed - Error: [%s].", expectedRecord.TransactionID, err) + } + if !ok { + t.Fatalf("[%s] - Database does not contain expected records", expectedRecord.TransactionID) + } +} + func verifyTransferRecordAndSignatures(dbValidation *database.Service, expectedRecord *entity.Transfer, mintAmount string, signatures []string, t *testing.T) { exist, err := dbValidation.VerifyTransferAndSignatureRecords(expectedRecord, mintAmount, signatures) if err != nil { diff --git a/e2e/service/database/compare.go b/e2e/service/database/compare.go index d71591764..9e830d5e3 100644 --- a/e2e/service/database/compare.go +++ b/e2e/service/database/compare.go @@ -1,3 +1,19 @@ +/* + * Copyright 2021 LimeChain Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package database import ( @@ -22,10 +38,20 @@ func messagesFieldsMatch(comparing, comparable entity.Message) bool { comparable.Signer == comparing.Signer } +func feeFieldsMatch(comparing, comparable *entity.Fee) bool { + return comparing.Status == comparable.Status && + comparing.TransactionID == comparable.TransactionID && + comparing.Amount == comparable.Amount && + comparable.ScheduleID == comparable.ScheduleID && + comparing.TransferID == comparable.TransferID && + comparing.BurnEventID == comparable.BurnEventID +} + func burnEventsFieldsMatch(comparing, comparable *entity.BurnEvent) bool { return comparing.Status == comparable.Status && comparing.ScheduleID == comparable.ScheduleID && comparing.Recipient == comparable.Recipient && comparing.Amount == comparable.Amount && - comparing.Id == comparable.Id + comparing.Id == comparable.Id && + comparing.TransactionId == comparable.TransactionId } diff --git a/e2e/service/database/service.go b/e2e/service/database/service.go index 72d6d7af1..ddf975c13 100644 --- a/e2e/service/database/service.go +++ b/e2e/service/database/service.go @@ -1,3 +1,19 @@ +/* + * Copyright 2021 LimeChain Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package database import ( @@ -8,6 +24,7 @@ import ( "github.com/limechain/hedera-eth-bridge-validator/app/persistence" burn_event "github.com/limechain/hedera-eth-bridge-validator/app/persistence/burn-event" "github.com/limechain/hedera-eth-bridge-validator/app/persistence/entity" + "github.com/limechain/hedera-eth-bridge-validator/app/persistence/fee" "github.com/limechain/hedera-eth-bridge-validator/app/persistence/message" "github.com/limechain/hedera-eth-bridge-validator/app/persistence/transfer" "github.com/limechain/hedera-eth-bridge-validator/config" @@ -18,6 +35,7 @@ type dbVerifier struct { transactions repository.Transfer messages repository.Message burnEvents repository.BurnEvent + fee repository.Fee } type Service struct { @@ -33,6 +51,7 @@ func NewService(dbConfigs []config.Database) *Service { transactions: transfer.NewRepository(connection), messages: message.NewRepository(connection), burnEvents: burn_event.NewRepository(connection), + fee: fee.NewRepository(connection), } verifiers = append(verifiers, newVerifier) } @@ -133,6 +152,19 @@ func (s *Service) VerifyBurnRecord(expectedBurnRecord *entity.BurnEvent) (bool, return true, nil } +func (s *Service) VerifyFeeRecord(expectedRecord *entity.Fee) (bool, error) { + for _, verifier := range s.verifiers { + actual, err := verifier.fee.Get(expectedRecord.TransactionID) + if err != nil { + return false, err + } + if !feeFieldsMatch(actual, expectedRecord) { + return false, nil + } + } + return true, nil +} + func contains(m entity.Message, array []entity.Message) bool { for _, a := range array { if messagesFieldsMatch(a, m) { diff --git a/e2e/service/database/status.go b/e2e/service/database/status.go index 256b0bd76..e7d23a781 100644 --- a/e2e/service/database/status.go +++ b/e2e/service/database/status.go @@ -1,3 +1,19 @@ +/* + * Copyright 2021 LimeChain Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package database type ExpectedStatuses struct { diff --git a/e2e/setup/application.yml b/e2e/setup/application.yml index d5438dc82..5841e4bba 100644 --- a/e2e/setup/application.yml +++ b/e2e/setup/application.yml @@ -1,6 +1,6 @@ #E2E ETH-Hedera hedera: - network_type: + bridge_account: dbs: - host: name: @@ -17,23 +17,23 @@ hedera: password: port: username: - bridge_account: - topic_id: fee_percentage: 10000 # 10% members: - sender: - account: - private_key: mirror_node: api_address: client_address: max_retries: polling_interval: + network_type: + sender: + account: + private_key: + topic_id: ethereum: - node_url: - router_contract_address: block_confirmations: + node_url: private_key: + router_contract_address: tokens: whbar: "HBAR" wtoken: diff --git a/e2e/util/compare.go b/e2e/util/compare.go new file mode 100644 index 000000000..d8476c655 --- /dev/null +++ b/e2e/util/compare.go @@ -0,0 +1,27 @@ +/* + * Copyright 2021 LimeChain Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package util + +func AllSame(arr []string) bool { + for i := 1; i < len(arr); i++ { + if arr[i] != arr[0] { + return false + } + } + + return true +} diff --git a/e2e/util/expectation.go b/e2e/util/expectation.go index e93eb8de5..2228ea080 100644 --- a/e2e/util/expectation.go +++ b/e2e/util/expectation.go @@ -1,26 +1,74 @@ +/* + * Copyright 2021 LimeChain Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package util import ( + "database/sql" "github.com/hashgraph/hedera-sdk-go/v2" routerContract "github.com/limechain/hedera-eth-bridge-validator/app/clients/ethereum/contracts/router" hederahelper "github.com/limechain/hedera-eth-bridge-validator/app/helper/hedera" "github.com/limechain/hedera-eth-bridge-validator/app/persistence/entity" burn_event "github.com/limechain/hedera-eth-bridge-validator/app/persistence/entity/burn-event" + "github.com/limechain/hedera-eth-bridge-validator/app/persistence/entity/fee" "github.com/limechain/hedera-eth-bridge-validator/e2e/service/database" "github.com/limechain/hedera-eth-bridge-validator/e2e/setup" + "strconv" "testing" ) -func PrepareExpectedBurnEventRecord(scheduleID string, amount int64, recipient hedera.AccountID, burnEventId string) *entity.BurnEvent { +func PrepareExpectedBurnEventRecord(scheduleID string, amount int64, recipient hedera.AccountID, burnEventId string, transactionID string) *entity.BurnEvent { return &entity.BurnEvent{ Id: burnEventId, ScheduleID: scheduleID, Amount: amount, Recipient: recipient.String(), Status: burn_event.StatusCompleted, + TransactionId: sql.NullString{ + String: transactionID, + Valid: true, + }, } } +func PrepareExpectedFeeRecord(transactionID, scheduleID string, amount int64, transferID, burnEventID string) *entity.Fee { + fee := &entity.Fee{ + TransactionID: transactionID, + ScheduleID: scheduleID, + Amount: strconv.FormatInt(amount, 10), + Status: fee.StatusCompleted, + } + + if transferID != "" { + fee.TransferID = sql.NullString{ + String: transferID, + Valid: true, + } + } + + if burnEventID != "" { + fee.BurnEventID = sql.NullString{ + String: burnEventID, + Valid: true, + } + } + + return fee +} + func PrepareExpectedTransfer(routerContract *routerContract.Router, transactionID hedera.TransactionID, nativeAsset, amount, receiver string, statuses database.ExpectedStatuses, t *testing.T) *entity.Transfer { expectedTxId := hederahelper.FromHederaTransactionID(&transactionID) diff --git a/e2e/util/hedera.go b/e2e/util/hedera.go index b8fa0ecab..da8884fcb 100644 --- a/e2e/util/hedera.go +++ b/e2e/util/hedera.go @@ -1,3 +1,19 @@ +/* + * Copyright 2021 LimeChain Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package util import (