From c1f94d4bdd8a4f9062806200d5cf5a83e364ff07 Mon Sep 17 00:00:00 2001 From: Vladislav Kotsev Date: Mon, 1 Jul 2024 17:26:55 +0300 Subject: [PATCH] Feat/sixty forty reward split (#916) * Implement reward split functionality by a given ratio * Fix/add unit tests * Update e2e for hedera native tokens --------- Co-authored-by: vlady-kotsev --- app/domain/service/distributor.go | 6 +- .../handler/read-only/fee-transfer/handler.go | 10 +- .../read-only/fee-transfer/handler_test.go | 10 +- app/process/handler/read-only/fee/handler.go | 10 +- .../handler/read-only/fee/handler_test.go | 14 +- .../handler/read-only/nft/fee/handler.go | 9 +- .../handler/read-only/nft/fee/handler_test.go | 84 +- app/services/burn-event/service.go | 5 +- app/services/burn-event/service_test.go | 20 +- app/services/fee/calculator/calculator.go | 1 + app/services/fee/distributor/distributor.go | 72 +- .../fee/distributor/distributor_test.go | 165 +++- app/services/transfers/service.go | 18 +- bootstrap/services.go | 3 +- config/bridge.go | 28 +- config/parser/bridge.go | 15 +- e2e/e2e_test.go | 921 +++++++++--------- e2e/helper/expected/fee.go | 12 +- e2e/helper/expected/mirror-node.go | 32 +- e2e/helper/verify/db.go | 8 +- e2e/helper/verify/hedera.go | 25 +- e2e/setup/config.go | 111 ++- e2e/setup/parser/parser.go | 19 +- test/mocks/common/logger_mock.go | 17 + .../mocks/service/distributor_service_mock.go | 8 +- 25 files changed, 956 insertions(+), 667 deletions(-) create mode 100644 test/mocks/common/logger_mock.go diff --git a/app/domain/service/distributor.go b/app/domain/service/distributor.go index 51b8458bb..1133bbe8b 100644 --- a/app/domain/service/distributor.go +++ b/app/domain/service/distributor.go @@ -27,7 +27,7 @@ type Distributor interface { // PrepareTransfers Returns an equally divided array of transfers to each member PrepareTransfers(fee int64, token string) ([]transaction.Transfer, error) // CalculateMemberDistribution Returns an equally divided to each member - CalculateMemberDistribution(validFee int64) ([]transfer.Hedera, error) - // ValidAmount Returns the closest amount, which can be equally divided to members - ValidAmount(amount int64) int64 + CalculateMemberDistribution(validTreasuryFee int64, validValidatorFee int64) ([]transfer.Hedera, error) + // ValidAmounts Returns the closest amounts, which can be equally divided to members and treasury + ValidAmounts(amount int64) (int64, int64) } diff --git a/app/process/handler/read-only/fee-transfer/handler.go b/app/process/handler/read-only/fee-transfer/handler.go index 4fc3d6421..34ea18889 100644 --- a/app/process/handler/read-only/fee-transfer/handler.go +++ b/app/process/handler/read-only/fee-transfer/handler.go @@ -116,7 +116,8 @@ func (fmh *Handler) Handle(p interface{}) { calculatedFee, remainder := fmh.feeService.CalculateFee(transferMsg.TargetAsset, intAmount) - validFee := fmh.distributorService.ValidAmount(calculatedFee) + validTreasuryFee, validValidatorFee := fmh.distributorService.ValidAmounts(calculatedFee) + validFee := validTreasuryFee + validValidatorFee if validFee != calculatedFee { remainder += calculatedFee - validFee } @@ -127,7 +128,12 @@ func (fmh *Handler) Handle(p interface{}) { return } - transfers, _ := fmh.distributorService.CalculateMemberDistribution(validFee) + transfers, err := fmh.distributorService.CalculateMemberDistribution(validTreasuryFee, validValidatorFee) + if err != nil { + fmh.logger.Errorf("Failed to prepare members and treasury fee transfers. Error: [%s]", err) + return + } + transfers = append(transfers, model.Hedera{ AccountID: receiver, diff --git a/app/process/handler/read-only/fee-transfer/handler_test.go b/app/process/handler/read-only/fee-transfer/handler_test.go index 9f32220b5..e009cf4fa 100644 --- a/app/process/handler/read-only/fee-transfer/handler_test.go +++ b/app/process/handler/read-only/fee-transfer/handler_test.go @@ -87,7 +87,7 @@ func Test_Handle(t *testing.T) { } mocks.MTransferService.On("InitiateNewTransfer", *tr).Return(tr, nil) mocks.MFeeService.On("CalculateFee", tr.TargetAsset, int64(100)).Return(int64(10), int64(0)) - mocks.MDistributorService.On("ValidAmount", 10).Return(int64(3)) + mocks.MDistributorService.On("ValidAmounts", 10).Return(int64(3)) mocks.MReadOnlyService.On("FindAssetTransfer", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) h.Handle(tr) } @@ -96,7 +96,7 @@ func Test_Handle_FindTransfer(t *testing.T) { setup() mocks.MTransferService.On("InitiateNewTransfer", *tr).Return(&entity.Transfer{Status: status.Initial}, nil) mocks.MFeeService.On("CalculateFee", tr.TargetAsset, int64(100)).Return(int64(10), int64(0)) - mocks.MDistributorService.On("ValidAmount", int64(10)).Return(int64(3)) + mocks.MDistributorService.On("ValidAmounts", int64(10)).Return(int64(3)) mocks.MTransferRepository.On("UpdateFee", tr.TransactionId, "3").Return(nil) mocks.MDistributorService.On("CalculateMemberDistribution", int64(3)).Return([]model.Hedera{}) mocks.MReadOnlyService.On("FindAssetTransfer", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) @@ -109,7 +109,7 @@ func Test_Handle_NotInitialFails(t *testing.T) { h.Handle(tr) mocks.MReadOnlyService.AssertNotCalled(t, "FindTransfer", mock.Anything, mock.Anything, mock.Anything) mocks.MFeeService.AssertNotCalled(t, "CalculateFee", mock.Anything, mock.Anything) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", mock.Anything) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", mock.Anything) } func Test_Handle_InvalidPayload(t *testing.T) { @@ -118,7 +118,7 @@ func Test_Handle_InvalidPayload(t *testing.T) { mocks.MTransferService.AssertNotCalled(t, "InitiateNewTransfer", *tr) mocks.MReadOnlyService.AssertNotCalled(t, "FindTransfer", mock.Anything, mock.Anything, mock.Anything) mocks.MFeeService.AssertNotCalled(t, "CalculateFee", mock.Anything, mock.Anything) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", mock.Anything) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", mock.Anything) } func Test_Handle_InitiateNewTransferFails(t *testing.T) { @@ -127,7 +127,7 @@ func Test_Handle_InitiateNewTransferFails(t *testing.T) { h.Handle(tr) mocks.MReadOnlyService.AssertNotCalled(t, "FindTransfer", mock.Anything, mock.Anything, mock.Anything) mocks.MFeeService.AssertNotCalled(t, "CalculateFee", mock.Anything, mock.Anything) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", mock.Anything) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", mock.Anything) } func setup() { diff --git a/app/process/handler/read-only/fee/handler.go b/app/process/handler/read-only/fee/handler.go index 3c2749db2..9a810343c 100644 --- a/app/process/handler/read-only/fee/handler.go +++ b/app/process/handler/read-only/fee/handler.go @@ -108,7 +108,8 @@ func (fmh Handler) Handle(p interface{}) { } calculatedFee, _ := fmh.feeService.CalculateFee(transferMsg.SourceAsset, intAmount) - validFee := fmh.distributor.ValidAmount(calculatedFee) + validTreasuryFee, validValidatorFee := fmh.distributor.ValidAmounts(calculatedFee) + validFee := validTreasuryFee + validValidatorFee err = fmh.transferRepository.UpdateFee(transferMsg.TransactionId, strconv.FormatInt(validFee, 10)) if err != nil { @@ -116,8 +117,11 @@ func (fmh Handler) Handle(p interface{}) { return } - transfers, _ := fmh.distributor.CalculateMemberDistribution(validFee) - + transfers, err := fmh.distributor.CalculateMemberDistribution(validTreasuryFee, validValidatorFee) + if err != nil { + fmh.logger.Errorf("Failed to prepare members and treasury fee transfers. Error: [%s]", err) + return + } splitTransfers := distributor.SplitAccountAmounts(transfers, model.Hedera{ AccountID: fmh.bridgeAccount, diff --git a/app/process/handler/read-only/fee/handler_test.go b/app/process/handler/read-only/fee/handler_test.go index a93c5c9db..030c7a5ef 100644 --- a/app/process/handler/read-only/fee/handler_test.go +++ b/app/process/handler/read-only/fee/handler_test.go @@ -87,7 +87,7 @@ func Test_Handle(t *testing.T) { } mocks.MTransferService.On("InitiateNewTransfer", *tr).Return(tr, nil) mocks.MFeeService.On("CalculateFee", tr.SourceAsset, int64(100)).Return(int64(10), int64(0)) - mocks.MDistributorService.On("ValidAmount", 10).Return(int64(3)) + mocks.MDistributorService.On("ValidAmounts", 10).Return(int64(3)) mocks.MReadOnlyService.On("FindAssetTransfer", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) h.Handle(tr) } @@ -96,9 +96,9 @@ func Test_Handle_FindTransfer(t *testing.T) { setup() mocks.MTransferService.On("InitiateNewTransfer", *tr).Return(&entity.Transfer{Status: status.Initial}, nil) mocks.MFeeService.On("CalculateFee", tr.SourceAsset, int64(100)).Return(int64(10), int64(0)) - mocks.MDistributorService.On("ValidAmount", int64(10)).Return(int64(3)) - mocks.MTransferRepository.On("UpdateFee", tr.TransactionId, "3").Return(nil) - mocks.MDistributorService.On("CalculateMemberDistribution", int64(3)).Return([]model.Hedera{}, nil) + mocks.MDistributorService.On("ValidAmounts", int64(10)).Return(int64(6), int64(4)) + mocks.MTransferRepository.On("UpdateFee", tr.TransactionId, "10").Return(nil) + mocks.MDistributorService.On("CalculateMemberDistribution", int64(6), int64(4)).Return([]model.Hedera{}, nil) mocks.MReadOnlyService.On("FindAssetTransfer", mock.Anything, mock.Anything, mock.Anything, mock.Anything, mock.Anything) h.Handle(tr) } @@ -109,7 +109,7 @@ func Test_Handle_NotInitialFails(t *testing.T) { h.Handle(tr) mocks.MReadOnlyService.AssertNotCalled(t, "FindTransfer", mock.Anything, mock.Anything, mock.Anything) mocks.MFeeService.AssertNotCalled(t, "CalculateFee", mock.Anything, mock.Anything) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", mock.Anything) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", mock.Anything) } func Test_Handle_InvalidPayload(t *testing.T) { @@ -118,7 +118,7 @@ func Test_Handle_InvalidPayload(t *testing.T) { mocks.MTransferService.AssertNotCalled(t, "InitiateNewTransfer", *tr) mocks.MReadOnlyService.AssertNotCalled(t, "FindTransfer", mock.Anything, mock.Anything, mock.Anything) mocks.MFeeService.AssertNotCalled(t, "CalculateFee", mock.Anything, mock.Anything) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", mock.Anything) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", mock.Anything) } func Test_Handle_InitiateNewTransferFails(t *testing.T) { @@ -127,7 +127,7 @@ func Test_Handle_InitiateNewTransferFails(t *testing.T) { h.Handle(tr) mocks.MReadOnlyService.AssertNotCalled(t, "FindTransfer", mock.Anything, mock.Anything, mock.Anything) mocks.MFeeService.AssertNotCalled(t, "CalculateFee", mock.Anything, mock.Anything) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", mock.Anything) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", mock.Anything) } func setup() { diff --git a/app/process/handler/read-only/nft/fee/handler.go b/app/process/handler/read-only/nft/fee/handler.go index ae7bc40e9..d30136778 100644 --- a/app/process/handler/read-only/nft/fee/handler.go +++ b/app/process/handler/read-only/nft/fee/handler.go @@ -123,14 +123,19 @@ func (fmh Handler) Handle(p interface{}) { }, ) - validFee := fmh.distributor.ValidAmount(transferMsg.Fee) + validTreasuryFee, validValidatorFee := fmh.distributor.ValidAmounts(transferMsg.Fee) + validFee := validTreasuryFee + validValidatorFee err = fmh.transferRepository.UpdateFee(transferMsg.TransactionId, strconv.FormatInt(validFee, 10)) if err != nil { fmh.logger.Errorf("[%s] - Failed to update fee [%d]. Error: [%s]", transferMsg.TransactionId, validFee, err) return } - transfers, _ := fmh.distributor.CalculateMemberDistribution(validFee) + transfers, err := fmh.distributor.CalculateMemberDistribution(validTreasuryFee, validValidatorFee) + if err != nil { + fmh.logger.Errorf("Failed to prepare members and treasury fee transfers. Error: [%s]", err) + return + } splitTransfers := distributor.SplitAccountAmounts(transfers, model.Hedera{ diff --git a/app/process/handler/read-only/nft/fee/handler_test.go b/app/process/handler/read-only/nft/fee/handler_test.go index 50c1e7997..3b7a5b436 100644 --- a/app/process/handler/read-only/nft/fee/handler_test.go +++ b/app/process/handler/read-only/nft/fee/handler_test.go @@ -41,25 +41,27 @@ import ( ) var ( - handler *Handler - bridgeAccountAsStr = "0.0.111111" - bridgeAccount hedera.AccountID - transactionId = "1234" - sourceChainId = constants.HederaNetworkId - targetChainId = testConstants.EthereumNetworkId - nativeChainId = constants.HederaNetworkId - sourceAsset = testConstants.NetworkHederaNonFungibleNativeToken - targetAsset = testConstants.NetworkEthereumNFTWrappedTokenForNetworkHedera - nativeAsset = testConstants.NetworkHederaNonFungibleNativeToken - receiver = "0.0.455300" - amount = "1000000000000000000" - serialNum = int64(123) - metadata = "SomeMetadata" - fee = "10000" - isNft = true - timestamp = time.Now().UTC().String() - entityStatus = status.Initial - nilErr error + handler *Handler + bridgeAccountAsStr = "0.0.111111" + bridgeAccount hedera.AccountID + transactionId = "1234" + sourceChainId = constants.HederaNetworkId + targetChainId = testConstants.EthereumNetworkId + nativeChainId = constants.HederaNetworkId + sourceAsset = testConstants.NetworkHederaNonFungibleNativeToken + targetAsset = testConstants.NetworkEthereumNFTWrappedTokenForNetworkHedera + nativeAsset = testConstants.NetworkHederaNonFungibleNativeToken + receiver = "0.0.455300" + amount = "1000000000000000000" + serialNum = int64(123) + metadata = "SomeMetadata" + fee = "10000" + validatorPercentage = int64(40) + treasuryPercentage = int64(60) + isNft = true + timestamp = time.Now().UTC().String() + entityStatus = status.Initial + nilErr error p = &payload.Transfer{ TransactionId: transactionId, @@ -101,9 +103,11 @@ var ( countOfValidators = int64(len(validatorAccountIdsAsStr)) validatorAccountIds = make([]hedera.AccountID, countOfValidators) hederaFeeForSourceAsset = testConstants.HederaNftFees[sourceAsset] - validFee = (hederaFeeForSourceAsset / countOfValidators) * countOfValidators - formattedValidFee = strconv.FormatInt(validFee, 10) - feePerValidator = validFee / countOfValidators + validTreasuryFee = hederaFeeForSourceAsset * treasuryPercentage / 100 + validValidatorFee = (hederaFeeForSourceAsset * validatorPercentage / 100 / countOfValidators) * countOfValidators + totalValidFee = validTreasuryFee + validValidatorFee + formattedValidFee = strconv.FormatInt(totalValidFee, 10) + feePerValidator = validValidatorFee / countOfValidators hederaTransfers = make([]model.Hedera, countOfValidators) splitTransfers = make([][]model.Hedera, 1) scheduleId = "33333" @@ -119,7 +123,7 @@ var ( feeEntity = &entity.Fee{ TransactionID: transactionId, ScheduleID: scheduleId, - Amount: strconv.FormatInt(-validFee, 10), + Amount: strconv.FormatInt(-totalValidFee, 10), Status: status.Completed, TransferID: sql.NullString{String: transactionId, Valid: true}, } @@ -167,18 +171,18 @@ func Test_Handle(t *testing.T) { setup(t, true) mocks.MTransferService.On("InitiateNewTransfer", *p).Return(entityTransfer, nil) - mocks.MDistributorService.On("ValidAmount", hederaFeeForSourceAsset).Return(validFee) + mocks.MDistributorService.On("ValidAmounts", hederaFeeForSourceAsset).Return(validTreasuryFee, validValidatorFee) mocks.MTransferRepository.On("UpdateFee", transactionId, formattedValidFee).Return(nil) - mocks.MDistributorService.On("CalculateMemberDistribution", validFee).Return(hederaTransfers, nilErr) + mocks.MDistributorService.On("CalculateMemberDistribution", validTreasuryFee, validValidatorFee).Return(hederaTransfers, nilErr) mocks.MReadOnlyService.On("FindNftTransfer", transactionId, sourceAsset, serialNum, mock.Anything, bridgeAccountAsStr, mock.Anything) mocks.MReadOnlyService.On("FindAssetTransfer", transactionId, constants.Hbar, splitTransfers[0], mock.Anything, mock.Anything) handler.Handle(p) mocks.MTransferService.AssertCalled(t, "InitiateNewTransfer", *p) - mocks.MDistributorService.AssertCalled(t, "ValidAmount", hederaFeeForSourceAsset) + mocks.MDistributorService.AssertCalled(t, "ValidAmounts", hederaFeeForSourceAsset) mocks.MTransferRepository.AssertCalled(t, "UpdateFee", transactionId, formattedValidFee) - mocks.MDistributorService.AssertCalled(t, "CalculateMemberDistribution", validFee) + mocks.MDistributorService.AssertCalled(t, "CalculateMemberDistribution", validTreasuryFee, validValidatorFee) mocks.MReadOnlyService.AssertCalled(t, "FindNftTransfer", transactionId, sourceAsset, serialNum, mock.Anything, bridgeAccountAsStr, mock.Anything) mocks.MReadOnlyService.AssertCalled(t, "FindAssetTransfer", transactionId, constants.Hbar, splitTransfers[0], mock.Anything, mock.Anything) } @@ -190,9 +194,9 @@ func Test_Handle_ErrOnCast(t *testing.T) { handler.Handle(brokenPayload) mocks.MTransferService.AssertNotCalled(t, "InitiateNewTransfer", *p) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", hederaFeeForSourceAsset) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", hederaFeeForSourceAsset) mocks.MTransferRepository.AssertNotCalled(t, "UpdateFee", transactionId, formattedValidFee) - mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", validFee) + mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", validTreasuryFee, validValidatorFee) mocks.MReadOnlyService.AssertNotCalled(t, "FindAssetTransfer", transactionId, constants.Hbar, splitTransfers[0], mock.Anything, mock.Anything) } @@ -204,9 +208,9 @@ func Test_Handle_ErrOnTransactionRecord(t *testing.T) { handler.Handle(p) mocks.MTransferService.AssertCalled(t, "InitiateNewTransfer", *p) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", hederaFeeForSourceAsset) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", hederaFeeForSourceAsset) mocks.MTransferRepository.AssertNotCalled(t, "UpdateFee", transactionId, formattedValidFee) - mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", validFee) + mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", validTreasuryFee, validValidatorFee) mocks.MReadOnlyService.AssertNotCalled(t, "FindAssetTransfer", transactionId, constants.Hbar, splitTransfers[0], mock.Anything, mock.Anything) } @@ -220,9 +224,9 @@ func Test_Handle_TransactionRecordNotInitialStatus(t *testing.T) { entityTransfer.Status = entityStatus mocks.MTransferService.AssertCalled(t, "InitiateNewTransfer", *p) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", hederaFeeForSourceAsset) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", hederaFeeForSourceAsset) mocks.MTransferRepository.AssertNotCalled(t, "UpdateFee", transactionId, formattedValidFee) - mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", validFee) + mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", validTreasuryFee, validValidatorFee) mocks.MReadOnlyService.AssertNotCalled(t, "FindAssetTransfer", transactionId, constants.Hbar, splitTransfers[0], mock.Anything, mock.Anything) } @@ -230,16 +234,16 @@ func Test_Handle_ErrOnUpdateFee(t *testing.T) { setup(t, true) mocks.MTransferService.On("InitiateNewTransfer", *p).Return(entityTransfer, nil) - mocks.MDistributorService.On("ValidAmount", hederaFeeForSourceAsset).Return(validFee) + mocks.MDistributorService.On("ValidAmounts", hederaFeeForSourceAsset).Return(validTreasuryFee, validValidatorFee) mocks.MTransferRepository.On("UpdateFee", transactionId, formattedValidFee).Return(errors.New("failed to create transaction record")) mocks.MReadOnlyService.On("FindNftTransfer", transactionId, sourceAsset, serialNum, mock.Anything, bridgeAccountAsStr, mock.Anything) handler.Handle(p) mocks.MTransferService.AssertCalled(t, "InitiateNewTransfer", *p) - mocks.MDistributorService.AssertCalled(t, "ValidAmount", hederaFeeForSourceAsset) + mocks.MDistributorService.AssertCalled(t, "ValidAmounts", hederaFeeForSourceAsset) mocks.MTransferRepository.AssertCalled(t, "UpdateFee", transactionId, formattedValidFee) - mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", validFee) + mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", validTreasuryFee, validValidatorFee) mocks.MReadOnlyService.AssertNotCalled(t, "FindAssetTransfer", transactionId, constants.Hbar, splitTransfers[0], mock.Anything, mock.Anything) mocks.MReadOnlyService.AssertCalled(t, "FindNftTransfer", transactionId, sourceAsset, serialNum, mock.Anything, bridgeAccountAsStr, mock.Anything) } @@ -268,7 +272,7 @@ func Test_save(t *testing.T) { mocks.MScheduleRepository.On("Create", scheduleEntity).Return(nilErr) mocks.MFeeRepository.On("Create", feeEntity).Return(nilErr) - err := handler.feeTransfersSave(transactionId, scheduleId, status.Completed, p, -validFee) + err := handler.feeTransfersSave(transactionId, scheduleId, status.Completed, p, -totalValidFee) assert.Nil(t, err) mocks.MScheduleRepository.AssertCalled(t, "Create", scheduleEntity) @@ -280,7 +284,7 @@ func Test_save_ErrOnCreateScheduleEntity(t *testing.T) { expectedErr := errors.New("failed to create schedule record") mocks.MScheduleRepository.On("Create", scheduleEntity).Return(expectedErr) - err := handler.feeTransfersSave(transactionId, scheduleId, status.Completed, p, -validFee) + err := handler.feeTransfersSave(transactionId, scheduleId, status.Completed, p, -totalValidFee) assert.Equal(t, expectedErr, err) mocks.MScheduleRepository.AssertCalled(t, "Create", scheduleEntity) @@ -293,7 +297,7 @@ func Test_save_ErrOnCreateFeeEntity(t *testing.T) { mocks.MScheduleRepository.On("Create", scheduleEntity).Return(nilErr) mocks.MFeeRepository.On("Create", feeEntity).Return(expectedErr) - err := handler.feeTransfersSave(transactionId, scheduleId, status.Completed, p, -validFee) + err := handler.feeTransfersSave(transactionId, scheduleId, status.Completed, p, -totalValidFee) assert.Equal(t, expectedErr, err) mocks.MScheduleRepository.AssertCalled(t, "Create", scheduleEntity) @@ -320,7 +324,7 @@ func setup(t *testing.T, setupForHandle bool) { hederaTransfers[index] = transferPerValidator splitTransfers[0][index] = transferPerValidator } - splitTransfers[0][len(splitTransfers[0])-1] = model.Hedera{AccountID: bridgeAccount, Amount: -validFee} + splitTransfers[0][len(splitTransfers[0])-1] = model.Hedera{AccountID: bridgeAccount, Amount: -totalValidFee} } handler = &Handler{ diff --git a/app/services/burn-event/service.go b/app/services/burn-event/service.go index 59c676c94..573cc5457 100644 --- a/app/services/burn-event/service.go +++ b/app/services/burn-event/service.go @@ -206,12 +206,13 @@ func (s *Service) onMinedUserTransactionSetMetrics(sourceChainId, targetChainId func (s *Service) prepareTransfers(token string, amount int64, receiver hedera.AccountID) (fee int64, splitTransfers [][]transfer.Hedera, err error) { fee, remainder := s.feeService.CalculateFee(token, amount) - validFee := s.distributorService.ValidAmount(fee) + validTreasuryFee, validValidatorFee := s.distributorService.ValidAmounts(fee) + validFee := validTreasuryFee + validValidatorFee if validFee != fee { remainder += fee - validFee } - transfers, err := s.distributorService.CalculateMemberDistribution(validFee) + transfers, err := s.distributorService.CalculateMemberDistribution(validTreasuryFee, validValidatorFee) if err != nil { return 0, nil, err } diff --git a/app/services/burn-event/service_test.go b/app/services/burn-event/service_test.go index 1aed054cc..70435fdf8 100644 --- a/app/services/burn-event/service_test.go +++ b/app/services/burn-event/service_test.go @@ -84,9 +84,11 @@ var ( func Test_ProcessEvent(t *testing.T) { setup() - mockFee := int64(12) + mockFee := int64(11) mockRemainder := int64(1) - mockValidFee := int64(11) + mockValidFee := int64(10) + mockValidValidatorFee := int64(4) + mockValidTreasuryFee := int64(6) mockTransfersAfterPreparation := []transfer.Hedera{ { AccountID: burnEventReceiver, @@ -100,8 +102,8 @@ func Test_ProcessEvent(t *testing.T) { mocks.MTransferService.On("InitiateNewTransfer", tr).Return(entityTransfer, nil) mocks.MFeeService.On("CalculateFee", tr.NativeAsset, burnEventAmount).Return(mockFee, mockRemainder) - mocks.MDistributorService.On("ValidAmount", mockFee).Return(mockValidFee) - mocks.MDistributorService.On("CalculateMemberDistribution", mockValidFee).Return([]transfer.Hedera{}, nil) + mocks.MDistributorService.On("ValidAmounts", mockFee).Return(mockValidTreasuryFee, mockValidValidatorFee) + mocks.MDistributorService.On("CalculateMemberDistribution", mockValidTreasuryFee, mockValidValidatorFee).Return([]transfer.Hedera{}, nil) mocks.MTransferRepository.On("UpdateFee", tr.TransactionId, strconv.FormatInt(mockValidFee, 10)).Return(nil) mocks.MScheduledService.On("ExecuteScheduledTransferTransaction", tr.TransactionId, tr.NativeAsset, mockTransfersAfterPreparation).Return() @@ -127,7 +129,7 @@ func Test_ProcessEventCreateFail(t *testing.T) { mocks.MTransferService.On("InitiateNewTransfer", tr).Return(nil, errors.New("invalid-result")) mocks.MFeeService.AssertNotCalled(t, "CalculateFee", tr.NativeAsset, burnEventAmount) - mocks.MDistributorService.AssertNotCalled(t, "ValidAmount", mockFee) + mocks.MDistributorService.AssertNotCalled(t, "ValidAmounts", mockFee) mocks.MDistributorService.AssertNotCalled(t, "CalculateMemberDistribution", mockValidFee) mocks.MScheduledService.AssertNotCalled(t, "ExecuteScheduledTransferTransaction", tr.TransactionId, tr.NativeAsset, mockTransfersAfterPreparation) @@ -139,7 +141,9 @@ func Test_ProcessEventCalculateMemberDistributionFails(t *testing.T) { mockFee := int64(11) mockRemainder := int64(1) - mockValidFee := int64(11) + mockValidTreasuryFee := int64(6) + mockValidValidatorFee := int64(4) + mockTransfersAfterPreparation := []transfer.Hedera{ { AccountID: burnEventReceiver, @@ -153,8 +157,8 @@ func Test_ProcessEventCalculateMemberDistributionFails(t *testing.T) { mocks.MTransferService.On("InitiateNewTransfer", tr).Return(entityTransfer, nil) mocks.MFeeService.On("CalculateFee", tr.NativeAsset, burnEventAmount).Return(mockFee, mockRemainder) - mocks.MDistributorService.On("ValidAmount", mockFee).Return(mockValidFee) - mocks.MDistributorService.On("CalculateMemberDistribution", mockValidFee).Return(nil, errors.New("invalid-result")) + mocks.MDistributorService.On("ValidAmounts", mockFee).Return(mockValidTreasuryFee, mockValidValidatorFee) + mocks.MDistributorService.On("CalculateMemberDistribution", mockValidTreasuryFee, mockValidValidatorFee).Return(nil, errors.New("invalid-result")) mocks.MScheduledService.AssertNotCalled(t, "ExecuteScheduledTransferTransaction", tr.TransactionId, tr.NativeAsset, mockTransfersAfterPreparation) s.ProcessEvent(tr) diff --git a/app/services/fee/calculator/calculator.go b/app/services/fee/calculator/calculator.go index ee2307a2c..d18d591d8 100644 --- a/app/services/fee/calculator/calculator.go +++ b/app/services/fee/calculator/calculator.go @@ -19,6 +19,7 @@ package calculator import ( "errors" "fmt" + "github.com/gookit/event" bridge_config_event "github.com/limechain/hedera-eth-bridge-validator/app/model/bridge-config-event" "github.com/limechain/hedera-eth-bridge-validator/config" diff --git a/app/services/fee/distributor/distributor.go b/app/services/fee/distributor/distributor.go index 717a4104b..4f5ecce91 100644 --- a/app/services/fee/distributor/distributor.go +++ b/app/services/fee/distributor/distributor.go @@ -18,6 +18,7 @@ package distributor import ( "errors" + "github.com/hashgraph/hedera-sdk-go/v2" "github.com/limechain/hedera-eth-bridge-validator/app/clients/hedera/mirror-node/model/transaction" "github.com/limechain/hedera-eth-bridge-validator/app/model/transfer" @@ -26,18 +27,32 @@ import ( log "github.com/sirupsen/logrus" ) +type Logger interface { + Errorf(format string, args ...interface{}) +} + type Service struct { - accountIDs []hedera.AccountID - logger *log.Entry + accountIDs []hedera.AccountID + treasuryID hedera.AccountID + rewardPercentages map[string]int + logger Logger } +const ( + TreasuryReward = "treasury" + ValidatorReward = "validator" + totalPercentage = 100 +) + const TotalPositiveTransfersPerTransaction = 9 -func New(members []string) *Service { +func New(members []string, treasuryID string, treasuryRewardPercentage int, validatorRewardPercentage int) *Service { if len(members) == 0 { log.Fatal("No members accounts provided") } - + if treasuryRewardPercentage+validatorRewardPercentage != totalPercentage { + log.Fatalf("Rewards percentage total must be %d", totalPercentage) + } var accountIDs []hedera.AccountID for _, v := range members { accountID, err := hedera.AccountIDFromString(v) @@ -47,18 +62,29 @@ func New(members []string) *Service { accountIDs = append(accountIDs, accountID) } + rewardPercentages := map[string]int{ + ValidatorReward: validatorRewardPercentage, + TreasuryReward: treasuryRewardPercentage, + } + + treasuryAccountID, err := hedera.AccountIDFromString(treasuryID) + if err != nil { + log.Fatalf("Invalid treasury account: [%s].", treasuryID) + } return &Service{ - accountIDs: accountIDs, - logger: config.GetLoggerFor("Fee Service")} + accountIDs: accountIDs, + treasuryID: treasuryAccountID, + rewardPercentages: rewardPercentages, + logger: config.GetLoggerFor("Fee Service")} } -// CalculateMemberDistribution Returns an equally divided to each member -func (s Service) CalculateMemberDistribution(amount int64) ([]transfer.Hedera, error) { - feePerAccount := amount / int64(len(s.accountIDs)) +// CalculateMemberDistribution Returns the transactions to the members and the treasury +func (s Service) CalculateMemberDistribution(validTreasuryFee, validValdiatorFee int64) ([]transfer.Hedera, error) { + feePerAccount := validValdiatorFee / int64(len(s.accountIDs)) - totalAmount := feePerAccount * int64(len(s.accountIDs)) - if totalAmount != amount { - s.logger.Errorf("Provided fee [%d] is not divisible.", amount) + totalValidatorAmount := feePerAccount * int64(len(s.accountIDs)) + if totalValidatorAmount != validValdiatorFee { + s.logger.Errorf("Provided validator fee [%d] is not divisible.", validValdiatorFee) return nil, errors.New("amount not divisible") } @@ -70,6 +96,12 @@ func (s Service) CalculateMemberDistribution(amount int64) ([]transfer.Hedera, e }) } + treasuryTransfer := transfer.Hedera{ + AccountID: s.treasuryID, + Amount: validTreasuryFee, + } + transfers = append(transfers, treasuryTransfer) + return transfers, nil } @@ -133,16 +165,14 @@ func (s Service) PrepareTransfers(amount int64, token string) ([]transaction.Tra return transfers, nil } -// ValidAmount Returns the closest amount, which can be equally divided to members -func (s Service) ValidAmount(amount int64) int64 { - feePerAccount := amount / int64(len(s.accountIDs)) - - totalAmount := feePerAccount * int64(len(s.accountIDs)) - if totalAmount != amount { - return totalAmount - } +// ValidAmounts Returns the closest amounts, which can be equally divided to members and treasury +func (s Service) ValidAmounts(amount int64) (int64, int64) { + treasuryAmount := (amount * int64(s.rewardPercentages[TreasuryReward])) / 100 + feeForMembers := (amount * int64(s.rewardPercentages[ValidatorReward])) / 100 - return amount + feePerMember := feeForMembers / int64(len(s.accountIDs)) + validatorsAmount := feePerMember * int64(len(s.accountIDs)) + return treasuryAmount, validatorsAmount } // Sums the amounts and returns the opposite diff --git a/app/services/fee/distributor/distributor_test.go b/app/services/fee/distributor/distributor_test.go index af60fd2aa..040366480 100644 --- a/app/services/fee/distributor/distributor_test.go +++ b/app/services/fee/distributor/distributor_test.go @@ -17,10 +17,13 @@ package distributor import ( + "testing" + "github.com/hashgraph/hedera-sdk-go/v2" "github.com/limechain/hedera-eth-bridge-validator/app/model/transfer" + "github.com/limechain/hedera-eth-bridge-validator/test/mocks/common" "github.com/stretchr/testify/assert" - "testing" + "github.com/stretchr/testify/mock" ) func Test_SplitTransfersBelowTotal(t *testing.T) { @@ -174,3 +177,163 @@ func Test_SplitTransfersAboveTotalTransfersEquallyDivided(t *testing.T) { Amount: int64(-9), }, result[1][expectedChunkTwoLength-1]) } + +func Test_ValidAmounts(t *testing.T) { + accountID1, _ := hedera.AccountIDFromString("0.0.1") + accountID2, _ := hedera.AccountIDFromString("0.0.2") + accountID3, _ := hedera.AccountIDFromString("0.0.3") + tests := []struct { + name string + service Service + amount int64 + expectedTreasury int64 + expectedValidators int64 + }{ + { + name: "Case 1: Valid distribution", + service: Service{ + rewardPercentages: map[string]int{ + TreasuryReward: 10, + ValidatorReward: 90, + }, + accountIDs: []hedera.AccountID{ + accountID1, + accountID2, + accountID3, + }, + }, + amount: 1111, + expectedTreasury: 111, + expectedValidators: 999, + }, + { + name: "Case 2: Single account", + service: Service{ + rewardPercentages: map[string]int{ + TreasuryReward: 85, + ValidatorReward: 15, + }, + accountIDs: []hedera.AccountID{ + accountID1, + }, + }, + amount: 2000, + expectedTreasury: 1700, + expectedValidators: 300, + }, + { + name: "Case 3: No Amount", + service: Service{ + rewardPercentages: map[string]int{ + TreasuryReward: 50, + ValidatorReward: 50, + }, + accountIDs: []hedera.AccountID{ + accountID1, + accountID2, + accountID3, + }, + }, + amount: 0, + expectedTreasury: 0, + expectedValidators: 0, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + treasuryAmount, validatorsAmount := tt.service.ValidAmounts(tt.amount) + if treasuryAmount != tt.expectedTreasury { + t.Errorf("expected treasury amount %d, got %d", tt.expectedTreasury, treasuryAmount) + } + if validatorsAmount != tt.expectedValidators { + t.Errorf("expected validators amount %d, got %d", tt.expectedValidators, validatorsAmount) + } + }) + } +} + +func Test_CalculateMemberDistribution(t *testing.T) { + mockLogger := common.MockLogger{} + accountID1, _ := hedera.AccountIDFromString("0.0.1") + accountID2, _ := hedera.AccountIDFromString("0.0.2") + accountID3, _ := hedera.AccountIDFromString("0.0.3") + treasuryID, _ := hedera.AccountIDFromString("0.0.4") + + tests := []struct { + name string + service Service + validTreasuryFee int64 + validValidatorFee int64 + expectedTransfers []transfer.Hedera + expectedError string + expectedLoggerEntries []string + }{ + { + name: "Case 1: Valid distribution", + service: Service{ + accountIDs: []hedera.AccountID{ + accountID1, + accountID2, + accountID3, + }, + treasuryID: treasuryID, + logger: &mockLogger, + }, + validTreasuryFee: 100, + validValidatorFee: 300, + expectedTransfers: []transfer.Hedera{ + {AccountID: accountID1, Amount: 100}, + {AccountID: accountID2, Amount: 100}, + {AccountID: accountID3, Amount: 100}, + {AccountID: treasuryID, Amount: 100}, + }, + expectedError: "", + }, + { + name: "Case 2: Non-divisible validator fee", + service: Service{ + accountIDs: []hedera.AccountID{accountID1, accountID2}, + treasuryID: treasuryID, + logger: &mockLogger, + }, + validTreasuryFee: 50, + validValidatorFee: 101, + expectedTransfers: nil, + expectedError: "amount not divisible", + expectedLoggerEntries: []string{ + "Provided validator fee [%d] is not divisible.", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + mockLogger.On("Errorf", "Provided validator fee [%d] is not divisible.", mock.Anything).Maybe() + transfers, err := tt.service.CalculateMemberDistribution(tt.validTreasuryFee, tt.validValidatorFee) + + if err != nil && err.Error() != tt.expectedError { + t.Errorf("expected error '%s', got '%s'", tt.expectedError, err.Error()) + } + + if err == nil && tt.expectedError != "" { + t.Errorf("expected error '%s', got nil", tt.expectedError) + } + + if len(transfers) != len(tt.expectedTransfers) { + t.Errorf("expected transfers length %d, got %d", len(tt.expectedTransfers), len(transfers)) + } + + loggerEntries := tt.service.logger.(*common.MockLogger).Entries + if len(loggerEntries) != len(tt.expectedLoggerEntries) { + t.Errorf("expected logger entries length %d, got %d", len(tt.expectedLoggerEntries), len(loggerEntries)) + } + + for i, transfer := range transfers { + if transfer.AccountID != tt.expectedTransfers[i].AccountID || transfer.Amount != tt.expectedTransfers[i].Amount { + t.Errorf("expected transfer %v, got %v", tt.expectedTransfers[i], transfer) + } + } + }) + } +} diff --git a/app/services/transfers/service.go b/app/services/transfers/service.go index 3d843a6ae..fb4853cb3 100644 --- a/app/services/transfers/service.go +++ b/app/services/transfers/service.go @@ -178,12 +178,13 @@ func (ts *Service) ProcessNativeTransfer(tm payload.Transfer) error { } fee, remainder := ts.feeService.CalculateFee(tm.NativeAsset, intAmount) - validFee := ts.distributor.ValidAmount(fee) + validTreasuryFee, validValidatorFee := ts.distributor.ValidAmounts(fee) + validFee := validTreasuryFee + validValidatorFee if validFee != fee { remainder += fee - validFee } - go ts.processFeeTransfer(validFee, tm.SourceChainId, tm.TargetChainId, tm.TransactionId, tm.NativeAsset) + go ts.processFeeTransfer(validValidatorFee, validTreasuryFee, tm.SourceChainId, tm.TargetChainId, tm.TransactionId, tm.NativeAsset) wrappedAmount := strconv.FormatInt(remainder, 10) @@ -211,8 +212,9 @@ func (ts *Service) ProcessNativeNftTransfer(tm payload.Transfer) error { return errors.New("failed-scheduled-nft-transfer") } - feePerValidator := ts.distributor.ValidAmount(tm.Fee) - go ts.processFeeTransfer(feePerValidator, tm.SourceChainId, tm.TargetChainId, tm.TransactionId, constants.Hbar) + validTreasuryFee, validValidatorFee := ts.distributor.ValidAmounts(tm.Fee) + + go ts.processFeeTransfer(validValidatorFee, validTreasuryFee, tm.SourceChainId, tm.TargetChainId, tm.TransactionId, constants.Hbar) signatureMessage, err := ts.messageService.SignNftMessage(tm) if err != nil { @@ -408,11 +410,11 @@ func (ts *Service) submitTopicMessageAndWaitForTransaction(transferID string, si return nil } -func (ts *Service) processFeeTransfer(totalFee int64, sourceChainId, targetChainId uint64, transferID string, nativeAsset string) { - - transfers, err := ts.distributor.CalculateMemberDistribution(totalFee) +func (ts *Service) processFeeTransfer(validatorFee, treasuryFee int64, sourceChainId, targetChainId uint64, transferID string, nativeAsset string) { + totalFee := validatorFee + treasuryFee + transfers, err := ts.distributor.CalculateMemberDistribution(treasuryFee, validatorFee) if err != nil { - ts.logger.Errorf("[%s] Fee - Failed to Distribute to Members. Error: [%s].", transferID, err) + ts.logger.Errorf("Failed to prepare members and treasury fee transfers. Error: [%s]", err) return } diff --git a/bootstrap/services.go b/bootstrap/services.go index 87a8f7bc4..4a8b567bc 100644 --- a/bootstrap/services.go +++ b/bootstrap/services.go @@ -18,6 +18,7 @@ package bootstrap import ( "fmt" + "github.com/hashgraph/hedera-sdk-go/v2" "github.com/limechain/hedera-eth-bridge-validator/app/domain/service" "github.com/limechain/hedera-eth-bridge-validator/app/services/assets" @@ -94,7 +95,7 @@ func PrepareServices(c *config.Config, parsedBridge *parser.Bridge, clients *Cli } fees := calculator.New(c.Bridge.Hedera.FeePercentages) - distributor := distributor.New(c.Bridge.Hedera.Members) + distributor := distributor.New(c.Bridge.Hedera.Members, c.Bridge.Hedera.Treasury, c.Bridge.Hedera.TreasuryRewardPercentage, c.Bridge.Hedera.ValidatorRewardPercentage) scheduled := scheduled.New(c.Bridge.Hedera.PayerAccount, clients.HederaNode, clients.MirrorNode) prometheus := prometheusServices.NewService(assetsService, c.Node.Monitoring.Enable) diff --git a/config/bridge.go b/config/bridge.go index c2017c76f..865990753 100644 --- a/config/bridge.go +++ b/config/bridge.go @@ -52,13 +52,16 @@ func (b *Bridge) Update(from *Bridge) { } type BridgeHedera struct { - BridgeAccount string - PayerAccount string - Members []string - Tokens map[string]HederaToken - FeePercentages map[string]int64 - NftConstantFees map[string]int64 - NftDynamicFees map[string]decimal.Decimal + BridgeAccount string + PayerAccount string + Members []string + Treasury string + ValidatorRewardPercentage int + TreasuryRewardPercentage int + Tokens map[string]HederaToken + FeePercentages map[string]int64 + NftConstantFees map[string]int64 + NftDynamicFees map[string]decimal.Decimal } type HederaToken struct { @@ -116,10 +119,13 @@ func NewBridge(bridge parser.Bridge) *Bridge { if networkId == constants.HederaNetworkId { // Hedera config.Hedera = &BridgeHedera{ - BridgeAccount: networkInfo.BridgeAccount, - PayerAccount: networkInfo.PayerAccount, - Members: networkInfo.Members, - Tokens: make(map[string]HederaToken), + BridgeAccount: networkInfo.BridgeAccount, + PayerAccount: networkInfo.PayerAccount, + Members: networkInfo.Members, + Treasury: networkInfo.Treasury, + ValidatorRewardPercentage: networkInfo.ValidatorRewardPercentage, + TreasuryRewardPercentage: networkInfo.TreasuryRewardPercentage, + Tokens: make(map[string]HederaToken), } for name, tokenInfo := range networkInfo.Tokens.Nft { diff --git a/config/parser/bridge.go b/config/parser/bridge.go index 074d24614..373f6cac7 100644 --- a/config/parser/bridge.go +++ b/config/parser/bridge.go @@ -45,12 +45,15 @@ func (b *Bridge) Update(from *Bridge) { } type Network struct { - Name string `yaml:"name,omitempty" json:"name,omitempty"` - BridgeAccount string `yaml:"bridge_account,omitempty" json:"bridgeAccount,omitempty"` - PayerAccount string `yaml:"payer_account,omitempty" json:"payerAccount,omitempty"` - RouterContractAddress string `yaml:"router_contract_address,omitempty" json:"routerContractAddress,omitempty"` - Members []string `yaml:"members,omitempty" json:"members,omitempty"` - Tokens Tokens `yaml:"tokens,omitempty" json:"tokens,omitempty"` + Name string `yaml:"name,omitempty" json:"name,omitempty"` + BridgeAccount string `yaml:"bridge_account,omitempty" json:"bridgeAccount,omitempty"` + PayerAccount string `yaml:"payer_account,omitempty" json:"payerAccount,omitempty"` + RouterContractAddress string `yaml:"router_contract_address,omitempty" json:"routerContractAddress,omitempty"` + Members []string `yaml:"members,omitempty" json:"members,omitempty"` + Treasury string `yaml:"treasury_account,omitempty" json:"treasuryAccount,omitempty"` + ValidatorRewardPercentage int `yaml:"validator_reward_percentage,omitempty" json:"validatorRewardPercentage,omitempty"` + TreasuryRewardPercentage int `yaml:"treasury_reward_percentage,omitempty" json:"treasuryRewardPercentage,omitempty"` + Tokens Tokens `yaml:"tokens,omitempty" json:"tokens,omitempty"` } type Tokens struct { diff --git a/e2e/e2e_test.go b/e2e/e2e_test.go index 75c41568a..223a09805 100644 --- a/e2e/e2e_test.go +++ b/e2e/e2e_test.go @@ -21,7 +21,6 @@ import ( "database/sql" "encoding/base64" "fmt" - "github.com/ethereum/go-ethereum/accounts/abi/bind" "math/big" "strconv" "testing" @@ -29,7 +28,6 @@ import ( evmSetup "github.com/limechain/hedera-eth-bridge-validator/e2e/setup/evm" - "github.com/limechain/hedera-eth-bridge-validator/app/clients/hedera/mirror-node/model/transaction" hederahelper "github.com/limechain/hedera-eth-bridge-validator/app/helper/hedera" "github.com/limechain/hedera-eth-bridge-validator/app/helper/timestamp" auth_message "github.com/limechain/hedera-eth-bridge-validator/app/model/auth-message" @@ -40,7 +38,6 @@ import ( "github.com/limechain/hedera-eth-bridge-validator/e2e/helper/expected" "github.com/limechain/hedera-eth-bridge-validator/e2e/helper/fetch" "github.com/limechain/hedera-eth-bridge-validator/e2e/helper/submit" - "github.com/limechain/hedera-eth-bridge-validator/e2e/helper/utilities" "github.com/limechain/hedera-eth-bridge-validator/e2e/helper/verify" "github.com/limechain/hedera-eth-bridge-validator/e2e/setup" @@ -69,8 +66,8 @@ func Test_HBAR(t *testing.T) { t.Fatalf("Expecting Token [%s] is not supported. - Error: [%s]", constants.Hbar, err) } - mintAmount, fee := expected.ReceiverAndFeeAmounts(setupEnv.Clients.FeeCalculator, setupEnv.Clients.Distributor, constants.Hbar, amount) - + mintAmount, validatorsFee, treasuryFee := expected.ReceiverAndFeeAmounts(setupEnv.Clients.FeeCalculator, setupEnv.Clients.Distributor, constants.Hbar, amount) + fee := validatorsFee + treasuryFee // Step 1 - Verify the transfer of Hbars to the Bridge Account transactionResponse, wrappedBalanceBefore := verify.TransferToBridgeAccount(t, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, targetAsset, evm, memo, receiver, amount) @@ -78,8 +75,8 @@ func Test_HBAR(t *testing.T) { receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), now.UnixNano()) // Step 3 - Validate fee scheduled transaction - expectedTransfers := expected.MirrorNodeExpectedTransfersForHederaTransfer(setupEnv.Members, setupEnv.BridgeAccount, constants.Hbar, fee) - scheduledTxID, scheduleID := verify.MembersScheduledTxs(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, constants.Hbar, expectedTransfers, now) + expectedMembersTransfers := expected.MirrorNodeExpectedTransfersForHederaTransfer(setupEnv.Members, setupEnv.Treasury, setupEnv.BridgeAccount, constants.Hbar, validatorsFee, treasuryFee) + scheduledTxID, scheduleID := verify.ScheduledTxs(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, setupEnv.Treasury, constants.Hbar, expectedMembersTransfers, now) // Step 4 - Verify Transfer retrieved from Validator API transactionData := verify.FungibleTransferFromValidatorAPI( @@ -133,7 +130,7 @@ func Test_HBAR(t *testing.T) { ) // and: - expectedFeeRecord := expected.FeeRecord( + expectedMemberFeeRecord := expected.FeeRecord( scheduledTxID, scheduleID, fee, @@ -156,7 +153,7 @@ func Test_HBAR(t *testing.T) { // Step 9 - Verify Database Records verify.TransferRecordAndSignatures(t, setupEnv.DbValidator, expectedTxRecord, authMsgBytes, receivedSignatures) // and: - verify.FeeRecord(t, setupEnv.DbValidator, expectedFeeRecord) + verify.FeeRecord(t, setupEnv.DbValidator, expectedMemberFeeRecord) } // Test_E2E_Token_Transfer recreates a real life situation of a user who wants to bridge a Hedera native token to the EVM Network infrastructure. The wrapped token on the EVM network(corresponding to the native Hedera Hashgraph's one) gets minted, then transferred to the recipient account on the EVM network. @@ -173,8 +170,8 @@ func Test_E2E_Token_Transfer(t *testing.T) { chainId := setupEnv.Scenario.FirstEvmChainId evm := setupEnv.Clients.EVM[chainId] memo := fmt.Sprintf("%d-%s", chainId, evm.Receiver.String()) - mintAmount, fee := expected.ReceiverAndFeeAmounts(setupEnv.Clients.FeeCalculator, setupEnv.Clients.Distributor, setupEnv.TokenID.String(), amount) - + mintAmount, validatorsFee, treasuryFee := expected.ReceiverAndFeeAmounts(setupEnv.Clients.FeeCalculator, setupEnv.Clients.Distributor, setupEnv.TokenID.String(), amount) + fee := validatorsFee + treasuryFee targetAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, constants.HederaNetworkId, chainId, setupEnv.TokenID.String()) if err != nil { t.Fatalf("Expecting Token [%s] is not supported. - Error: [%s]", constants.Hbar, err) @@ -187,8 +184,8 @@ func Test_E2E_Token_Transfer(t *testing.T) { receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), now.UnixNano()) // Step 3 - Validate fee scheduled transaction - expectedTransfers := expected.MirrorNodeExpectedTransfersForHederaTransfer(setupEnv.Members, setupEnv.BridgeAccount, setupEnv.TokenID.String(), fee) - scheduledTxID, scheduleID := verify.MembersScheduledTxs(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, setupEnv.TokenID.String(), expectedTransfers, now) + expectedTransfers := expected.MirrorNodeExpectedTransfersForHederaTransfer(setupEnv.Members, setupEnv.Treasury, setupEnv.BridgeAccount, setupEnv.TokenID.String(), validatorsFee, treasuryFee) + scheduledTxID, scheduleID := verify.ScheduledTxs(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, setupEnv.Treasury, setupEnv.TokenID.String(), expectedTransfers, now) // Step 4 - Verify Transfer retrieved from Validator API transactionData := verify.FungibleTransferFromValidatorAPI( @@ -288,8 +285,8 @@ func Test_EVM_Hedera_HBAR(t *testing.T) { } // Step 1 - Calculate Expected Receive And Fee Amounts - expectedReceiveAmount, fee := expected.ReceiverAndFeeAmounts(setupEnv.Clients.FeeCalculator, setupEnv.Clients.Distributor, constants.Hbar, amount) - + expectedReceiveAmount, validatorFee, treasuryFee := expected.ReceiverAndFeeAmounts(setupEnv.Clients.FeeCalculator, setupEnv.Clients.Distributor, constants.Hbar, amount) + fee := validatorFee + treasuryFee // Step 2 - Submit burn transaction to the bridge contract burnTxReceipt, expectedRouterBurn := submit.BurnEthTransaction(t, setupEnv.AssetMappings, evm, constants.Hbar, constants.HederaNetworkId, chainId, setupEnv.Clients.Hedera.GetOperatorAccountID().ToBytes(), amount) @@ -304,8 +301,8 @@ func Test_EVM_Hedera_HBAR(t *testing.T) { expectedId := verify.BurnEvent(t, burnTxReceipt, expectedRouterBurn) // Step 4 - Validate that a scheduled transaction was submitted - expectedTransfers := expected.MirrorNodeExpectedTransfersForBurnEvent(setupEnv.Members, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, constants.Hbar, expectedReceiveAmount, fee) - transactionID, scheduleID := verify.SubmittedScheduledTx(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, constants.Hbar, expectedTransfers, now) + expectedTransfers := expected.MirrorNodeExpectedTransfersForBurnEvent(setupEnv.Members, setupEnv.Treasury, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, constants.Hbar, expectedReceiveAmount, validatorFee, treasuryFee) + transactionID, scheduleID := verify.SubmittedScheduledTx(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, setupEnv.Treasury, constants.Hbar, expectedTransfers, now) // Step 5 - Validate Event Transaction ID retrieved from Validator API verify.EventTransactionIDFromValidatorAPI(t, setupEnv.Clients.ValidatorClient, expectedId, transactionID) @@ -359,7 +356,8 @@ func Test_EVM_Hedera_Token(t *testing.T) { } // Step 1 - Calculate Expected Receive Amount - expectedReceiveAmount, fee := expected.ReceiverAndFeeAmounts(setupEnv.Clients.FeeCalculator, setupEnv.Clients.Distributor, setupEnv.TokenID.String(), amount) + expectedReceiveAmount, validatorFee, treasuryFee := expected.ReceiverAndFeeAmounts(setupEnv.Clients.FeeCalculator, setupEnv.Clients.Distributor, setupEnv.TokenID.String(), amount) + fee := validatorFee + treasuryFee // Step 2 - Submit burn transaction to the bridge contract burnTxReceipt, expectedRouterBurn := submit.BurnEthTransaction(t, setupEnv.AssetMappings, evm, setupEnv.TokenID.String(), constants.HederaNetworkId, chainId, setupEnv.Clients.Hedera.GetOperatorAccountID().ToBytes(), amount) @@ -375,8 +373,8 @@ func Test_EVM_Hedera_Token(t *testing.T) { expectedId := verify.BurnEvent(t, burnTxReceipt, expectedRouterBurn) // Step 4 - Validate that a scheduled transaction was submitted - expectedTransfers := expected.MirrorNodeExpectedTransfersForBurnEvent(setupEnv.Members, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, setupEnv.TokenID.String(), expectedReceiveAmount, fee) - transactionID, scheduleID := verify.SubmittedScheduledTx(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, setupEnv.TokenID.String(), expectedTransfers, now) + expectedTransfers := expected.MirrorNodeExpectedTransfersForBurnEvent(setupEnv.Members, setupEnv.Treasury, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, setupEnv.TokenID.String(), expectedReceiveAmount, validatorFee, treasuryFee) + transactionID, scheduleID := verify.SubmittedScheduledTx(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, setupEnv.Treasury, setupEnv.TokenID.String(), expectedTransfers, now) // Step 5 - Validate Event Transaction ID retrieved from Validator API verify.EventTransactionIDFromValidatorAPI(t, setupEnv.Clients.ValidatorClient, expectedId, transactionID) @@ -410,444 +408,443 @@ func Test_EVM_Hedera_Token(t *testing.T) { } // Test_EVM_Hedera_Native_Token recreates a real life situation of a user who wants to bridge an EVM native token to the Hedera infrastructure. A new wrapped token (corresponding to the native EVM one) gets minted to the bridge account, then gets transferred to the recipient account. -func Test_EVM_Hedera_Native_Token(t *testing.T) { - if testing.Short() { - t.Skip("test skipped in short mode") - } - - setupEnv := setup.Load() - now := time.Now() - - amount := setupEnv.Scenario.AmountEvmNative - - chainId := setupEnv.Scenario.FirstEvmChainId - evm := setupEnv.Clients.EVM[chainId] - - bridgeAccountBalanceBefore := fetch.HederaAccountBalance(t, setupEnv.Clients.Hedera, setupEnv.BridgeAccount) - receiverAccountBalanceBefore := fetch.HederaAccountBalance(t, setupEnv.Clients.Hedera, setupEnv.Clients.Hedera.GetOperatorAccountID()) - - targetAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, chainId, constants.HederaNetworkId, setupEnv.NativeEvmToken) - if err != nil { - t.Fatal(err) - } - - // Step 1: Submit Lock Txn from a deployed smart contract - receipt, expectedLockEventLog := submit.LockEthTransaction(t, evm, setupEnv.NativeEvmToken, constants.HederaNetworkId, setupEnv.Clients.Hedera.GetOperatorAccountID().ToBytes(), amount) - - // Step 1.1 - Get the block timestamp of lock event - block, err := evm.EVMClient.BlockByNumber(context.Background(), receipt.BlockNumber) - if err != nil { - t.Fatal("failed to get block by number", err) - } - blockTimestamp := time.Unix(int64(block.Time()), 0).UTC() - - // Step 2: Validate Lock Event was emitted with correct data - lockEventId := verify.LockEvent(t, receipt, expectedLockEventLog) - - bridgedAmount := new(big.Int).Sub(expectedLockEventLog.Amount, expectedLockEventLog.ServiceFee) - expectedAmount, err := utilities.RemoveDecimals(bridgedAmount.Int64(), common.HexToAddress(setupEnv.NativeEvmToken), evm) - if err != nil { - t.Fatal(err) - } - - mintTransfer := []transaction.Transfer{ - { - Account: setupEnv.BridgeAccount.String(), - Amount: expectedAmount, - Token: targetAsset, - }, - } - - // Step 3: Validate that a scheduled token mint txn was submitted successfully - bridgeMintTransactionID, bridgeMintScheduleID := verify.ScheduledMintTx(t, setupEnv.Clients.MirrorNode, setupEnv.BridgeAccount, setupEnv.TokenID.String(), mintTransfer, now) - - // Step 4: Validate that Database statuses were changed correctly - expectedLockEventRecord := expected.FungibleTransferRecord( - chainId, - constants.HederaNetworkId, - chainId, - lockEventId, - setupEnv.NativeEvmToken, - targetAsset, - setupEnv.NativeEvmToken, - strconv.FormatInt(expectedAmount, 10), - "", - setupEnv.Clients.Hedera.GetOperatorAccountID().String(), - status.Completed, - evm.Signer.Address(), - entity.NanoTime{Time: blockTimestamp}, - ) - - expectedScheduleMintRecord := expected.ScheduleRecord( - bridgeMintTransactionID, - bridgeMintScheduleID, - schedule.MINT, - false, - status.Completed, - sql.NullString{ - String: lockEventId, - Valid: true, - }, - ) - - // Step 5: Verify that records have been created successfully - verify.TransferRecord(t, setupEnv.DbValidator, expectedLockEventRecord) - verify.ScheduleRecord(t, setupEnv.DbValidator, expectedScheduleMintRecord) - - // Step 6: Validate that a scheduled transfer txn was submitted successfully - bridgeTransferTransactionID, bridgeTransferScheduleID := verify.ScheduledTx( - t, - setupEnv.Clients.Hedera, - setupEnv.Clients.MirrorNode, - setupEnv.Clients.Hedera.GetOperatorAccountID(), - setupEnv.TokenID.String(), - expected.MirrorNodeExpectedTransfersForLockEvent(setupEnv.Clients.Hedera, setupEnv.BridgeAccount, targetAsset, expectedAmount), - now, - ) - - // Step 7: Validate that database statuses were updated correctly for the Schedule Transfer - expectedScheduleTransferRecord := &entity.Schedule{ - TransactionID: bridgeTransferTransactionID, - ScheduleID: bridgeTransferScheduleID, - Operation: schedule.TRANSFER, - HasReceiver: true, - Status: status.Completed, - TransferID: sql.NullString{ - String: lockEventId, - Valid: true, - }, - } - - verify.ScheduleRecord(t, setupEnv.DbValidator, expectedScheduleTransferRecord) - - // Step 8 Validate Treasury(BridgeAccount) Balance and Receiver Balance - verify.AccountBalance(t, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, 0, bridgeAccountBalanceBefore, targetAsset) - verify.AccountBalance(t, setupEnv.Clients.Hedera, setupEnv.Clients.Hedera.GetOperatorAccountID(), uint64(expectedAmount), receiverAccountBalanceBefore, targetAsset) -} +// func Test_EVM_Hedera_Native_Token(t *testing.T) { +// if testing.Short() { +// t.Skip("test skipped in short mode") +// } + +// setupEnv := setup.Load() +// now := time.Now() + +// amount := setupEnv.Scenario.AmountEvmNative + +// chainId := setupEnv.Scenario.FirstEvmChainId +// evm := setupEnv.Clients.EVM[chainId] +// bridgeAccountBalanceBefore := fetch.HederaAccountBalance(t, setupEnv.Clients.Hedera, setupEnv.BridgeAccount) +// receiverAccountBalanceBefore := fetch.HederaAccountBalance(t, setupEnv.Clients.Hedera, setupEnv.Clients.Hedera.GetOperatorAccountID()) + +// targetAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, chainId, constants.HederaNetworkId, setupEnv.NativeEvmToken) +// if err != nil { +// t.Fatal(err) +// } + +// // Step 1: Submit Lock Txn from a deployed smart contract +// receipt, expectedLockEventLog := submit.LockEthTransaction(t, evm, setupEnv.NativeEvmToken, constants.HederaNetworkId, setupEnv.Clients.Hedera.GetOperatorAccountID().ToBytes(), amount) + +// // Step 1.1 - Get the block timestamp of lock event +// block, err := evm.EVMClient.BlockByNumber(context.Background(), receipt.BlockNumber) +// if err != nil { +// t.Fatal("failed to get block by number", err) +// } +// blockTimestamp := time.Unix(int64(block.Time()), 0).UTC() + +// // Step 2: Validate Lock Event was emitted with correct data +// lockEventId := verify.LockEvent(t, receipt, expectedLockEventLog) + +// bridgedAmount := new(big.Int).Sub(expectedLockEventLog.Amount, expectedLockEventLog.ServiceFee) +// expectedAmount, err := utilities.RemoveDecimals(bridgedAmount.Int64(), common.HexToAddress(setupEnv.NativeEvmToken), evm) +// if err != nil { +// t.Fatal(err) +// } + +// mintTransfer := []transaction.Transfer{ +// { +// Account: setupEnv.BridgeAccount.String(), +// Amount: expectedAmount, +// Token: targetAsset, +// }, +// } + +// // Step 3: Validate that a scheduled token mint txn was submitted successfully +// bridgeMintTransactionID, bridgeMintScheduleID := verify.ScheduledMintTx(t, setupEnv.Clients.MirrorNode, setupEnv.BridgeAccount, setupEnv.TokenID.String(), mintTransfer, now) + +// // Step 4: Validate that Database statuses were changed correctly +// expectedLockEventRecord := expected.FungibleTransferRecord( +// chainId, +// constants.HederaNetworkId, +// chainId, +// lockEventId, +// setupEnv.NativeEvmToken, +// targetAsset, +// setupEnv.NativeEvmToken, +// strconv.FormatInt(expectedAmount, 10), +// "", +// setupEnv.Clients.Hedera.GetOperatorAccountID().String(), +// status.Completed, +// evm.Signer.Address(), +// entity.NanoTime{Time: blockTimestamp}, +// ) + +// expectedScheduleMintRecord := expected.ScheduleRecord( +// bridgeMintTransactionID, +// bridgeMintScheduleID, +// schedule.MINT, +// false, +// status.Completed, +// sql.NullString{ +// String: lockEventId, +// Valid: true, +// }, +// ) + +// // Step 5: Verify that records have been created successfully +// verify.TransferRecord(t, setupEnv.DbValidator, expectedLockEventRecord) +// verify.ScheduleRecord(t, setupEnv.DbValidator, expectedScheduleMintRecord) + +// // Step 6: Validate that a scheduled transfer txn was submitted successfully +// bridgeTransferTransactionID, bridgeTransferScheduleID := verify.ScheduledTx( +// t, +// setupEnv.Clients.Hedera, +// setupEnv.Clients.MirrorNode, +// setupEnv.Clients.Hedera.GetOperatorAccountID(), +// setupEnv.TokenID.String(), +// expected.MirrorNodeExpectedTransfersForLockEvent(setupEnv.Clients.Hedera, setupEnv.BridgeAccount, targetAsset, expectedAmount), +// now, +// ) + +// // Step 7: Validate that database statuses were updated correctly for the Schedule Transfer +// expectedScheduleTransferRecord := &entity.Schedule{ +// TransactionID: bridgeTransferTransactionID, +// ScheduleID: bridgeTransferScheduleID, +// Operation: schedule.TRANSFER, +// HasReceiver: true, +// Status: status.Completed, +// TransferID: sql.NullString{ +// String: lockEventId, +// Valid: true, +// }, +// } + +// verify.ScheduleRecord(t, setupEnv.DbValidator, expectedScheduleTransferRecord) + +// // Step 8 Validate Treasury(BridgeAccount) Balance and Receiver Balance +// verify.AccountBalance(t, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, 0, bridgeAccountBalanceBefore, targetAsset) +// verify.AccountBalance(t, setupEnv.Clients.Hedera, setupEnv.Clients.Hedera.GetOperatorAccountID(), uint64(expectedAmount), receiverAccountBalanceBefore, targetAsset) +// } // Test_E2E_Hedera_EVM_Native_Token recreates a real life situation of a user who wants to bridge a Hedera wrapped token to the EVM Native Network infrastructure. The wrapped token on the EVM network(corresponding to the native Hedera Hashgraph's one) gets minted, then transferred to the recipient account on the EVM network. -func Test_E2E_Hedera_EVM_Native_Token(t *testing.T) { - if testing.Short() { - t.Skip("test skipped in short mode") - } - - setupEnv := setup.Load() - now := time.Now() - - unlockAmount := setupEnv.Scenario.AmountHederaWrapped - - chainId := setupEnv.Scenario.FirstEvmChainId - evm := setupEnv.Clients.EVM[chainId] - memo := fmt.Sprintf("%d-%s", chainId, evm.Receiver.String()) - - // Step 1 - Verify the transfer of HTS to the Bridge Account - wrappedAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, chainId, constants.HederaNetworkId, setupEnv.NativeEvmToken) - if err != nil { - t.Fatal(err) - } - - tokenID, err := hedera.TokenIDFromString(wrappedAsset) - if err != nil { - t.Fatal(err) - } - - expectedSubmitUnlockAmount, err := utilities.AddDecimals(unlockAmount, common.HexToAddress(setupEnv.NativeEvmToken), evm) - if err != nil { - t.Fatal(err) - } - - transactionResponse, nativeBalanceBefore := verify.TokenTransferToBridgeAccount(t, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, setupEnv.NativeEvmToken, tokenID, evm, memo, evm.Receiver, unlockAmount) - - burnTransfer := []transaction.Transfer{ - { - Account: setupEnv.BridgeAccount.String(), - Amount: -unlockAmount, - Token: wrappedAsset, - }, - } - - // Step 2 - Verify the submitted topic messages - receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), now.UnixNano()) - - // Step 3 - Validate burn scheduled transaction - burnTransactionID, burnScheduleID := verify.ScheduledBurnTx(t, setupEnv.Clients.MirrorNode, setupEnv.BridgeAccount, setupEnv.TokenID.String(), burnTransfer, now) - - // Step 4 - Verify Transfer retrieved from Validator API - transactionData := verify.FungibleTransferFromValidatorAPI( - t, - setupEnv.Clients.ValidatorClient, - setupEnv.TokenID, - evm, - hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), - setupEnv.NativeEvmToken, - fmt.Sprint(expectedSubmitUnlockAmount), - setupEnv.NativeEvmToken, - ) - - // Step 4.1 - Get the consensus timestamp of the transfer - tx, err := setupEnv.Clients.MirrorNode.GetSuccessfulTransaction(hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String()) - if err != nil { - t.Fatal("failed to get successful transaction", err) - } - nanos, err := timestamp.FromString(tx.ConsensusTimestamp) - if err != nil { - t.Fatal("failed to parse consensus timestamp", err) - } - ts := timestamp.FromNanos(nanos) - - // Step 5 - Submit Unlock transaction - txHash := submit.UnlockTransaction(t, evm, hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), transactionData, common.HexToAddress(setupEnv.NativeEvmToken)) - - // Step 6 - Wait for transaction to be mined - submit.WaitForTransaction(t, evm, txHash) - - expectedUnlockedAmount, _ := expected.EvmAmoundAndFee(evm.RouterContract, setupEnv.NativeEvmToken, expectedSubmitUnlockAmount, t) - - // Step 7 - Validate Token balances - verify.WrappedAssetBalance(t, evm, setupEnv.NativeEvmToken, expectedUnlockedAmount, nativeBalanceBefore, evm.Receiver) - - // Step 8 - Verify Database records - expectedTxRecord := expected.FungibleTransferRecord( - constants.HederaNetworkId, - chainId, - chainId, - hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), - wrappedAsset, - setupEnv.NativeEvmToken, - setupEnv.NativeEvmToken, - strconv.FormatInt(expectedSubmitUnlockAmount, 10), - "", - evm.Receiver.String(), - status.Completed, - setupEnv.Clients.Hedera.GetOperatorAccountID().String(), - entity.NanoTime{Time: ts}, - ) - - // Step 8: Validate that database statuses were updated correctly for the Schedule Burn - expectedScheduleBurnRecord := expected.ScheduleRecord( - burnTransactionID, - burnScheduleID, - schedule.BURN, - false, - status.Completed, - sql.NullString{ - String: hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), - Valid: true, - }, - ) - - authMsgBytes, err := auth_message.EncodeFungibleBytesFrom( - expectedTxRecord.SourceChainID, - expectedTxRecord.TargetChainID, - expectedTxRecord.TransactionID, - expectedTxRecord.TargetAsset, - expectedTxRecord.Receiver, - strconv.FormatInt(expectedSubmitUnlockAmount, 10), - ) - - if err != nil { - t.Fatalf("[%s] - Failed to encode the authorization signature. Error: [%s]", expectedTxRecord.TransactionID, err) - } - - // Step 9 - Verify Database Records - verify.TransferRecordAndSignatures(t, setupEnv.DbValidator, expectedTxRecord, authMsgBytes, receivedSignatures) - // and - verify.ScheduleRecord(t, setupEnv.DbValidator, expectedScheduleBurnRecord) -} - -// Test_EVM_Native_to_EVM_Token recreates a real life situation of a user who wants to bridge an EVM native token to another EVM chain. -func Test_EVM_Native_to_EVM_Token(t *testing.T) { - if testing.Short() { - t.Skip("test skipped in short mode") - } - - setupEnv := setup.Load() - now := time.Now() - - amount := setupEnv.Scenario.AmountEvmNative - - chainId := setupEnv.Scenario.FirstEvmChainId - evm := setupEnv.Clients.EVM[chainId] - targetChainID := setupEnv.Scenario.SecondEvmChainId - - wrappedAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, chainId, targetChainID, setupEnv.NativeEvmToken) - if err != nil { - t.Fatal(err) - } - - wrappedEvm := setupEnv.Clients.EVM[targetChainID] - wrappedInstance, err := evmSetup.InitAssetContract(wrappedAsset, wrappedEvm.EVMClient) - if err != nil { - t.Fatal(err) - } - - wrappedBalanceBefore, err := wrappedInstance.BalanceOf(&bind.CallOpts{}, evm.Receiver) - if err != nil { - t.Fatal(err) - } - - // Step 1 - Submit Lock Txn from a deployed smart contract - receipt, expectedLockEventLog := submit.LockEthTransaction(t, evm, setupEnv.NativeEvmToken, targetChainID, evm.Receiver.Bytes(), amount) - - expectedAmount := new(big.Int).Sub(expectedLockEventLog.Amount, expectedLockEventLog.ServiceFee) - - // Step 1.1 - Get the block timestamp of the lock event - block, err := evm.EVMClient.BlockByNumber(context.Background(), receipt.BlockNumber) - if err != nil { - t.Fatal("failed to get block by number", err) - } - blockTimestamp := time.Unix(int64(block.Time()), 0).UTC() - - // Step 2 - Validate Lock Event was emitted with correct data - lockEventId := verify.LockEvent(t, receipt, expectedLockEventLog) - - // Step 3 - Verify the submitted topic messages - receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, lockEventId, now.UnixNano()) - - // Step 4 - Verify Transfer retrieved from Validator API - transactionData := verify.FungibleTransferFromValidatorAPI(t, setupEnv.Clients.ValidatorClient, setupEnv.TokenID, evm, lockEventId, setupEnv.NativeEvmToken, expectedAmount.String(), wrappedAsset) - - // Step 5 - Submit Mint transaction - txHash := submit.MintTransaction(t, wrappedEvm, lockEventId, transactionData, common.HexToAddress(wrappedAsset)) - - // Step 6 - Wait for transaction to be mined - submit.WaitForTransaction(t, wrappedEvm, txHash) - - // Step 7 - Validate Token balances - verify.WrappedAssetBalance(t, wrappedEvm, wrappedAsset, expectedAmount, wrappedBalanceBefore, evm.Receiver) - - // Step 8 - Prepare expected Transfer record - expectedLockEventRecord := expected.FungibleTransferRecord( - chainId, - targetChainID, - chainId, - lockEventId, - setupEnv.NativeEvmToken, - wrappedAsset, - setupEnv.NativeEvmToken, - expectedAmount.String(), - "", - evm.Receiver.String(), - status.Completed, - evm.Signer.Address(), - entity.NanoTime{Time: blockTimestamp}, - ) - - authMsgBytes, err := auth_message.EncodeFungibleBytesFrom( - expectedLockEventRecord.SourceChainID, - expectedLockEventRecord.TargetChainID, - expectedLockEventRecord.TransactionID, - expectedLockEventRecord.TargetAsset, - expectedLockEventRecord.Receiver, - expectedAmount.String(), - ) - - if err != nil { - t.Fatalf("[%s] - Failed to encode the authorisation signature. Error: [%s]", expectedLockEventRecord.TransactionID, err) - } - - // Step 9 - Verify Database Records - verify.TransferRecordAndSignatures(t, setupEnv.DbValidator, expectedLockEventRecord, authMsgBytes, receivedSignatures) -} - -// Test_EVM_Wrapped_to_EVM_Token recreates a real life situation of a user who wants to bridge an EVM native token to another EVM chain. -func Test_EVM_Wrapped_to_EVM_Token(t *testing.T) { - if testing.Short() { - t.Skip("test skipped in short mode") - } - - setupEnv := setup.Load() - now := time.Now() - - amount := setupEnv.Scenario.AmountEvmWrapped - - chainId := setupEnv.Scenario.FirstEvmChainId - sourceChain := setupEnv.Scenario.SecondEvmChainId - wrappedEvm := setupEnv.Clients.EVM[sourceChain] - - sourceAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, chainId, sourceChain, setupEnv.NativeEvmToken) - if err != nil { - t.Fatal(err) - } - - evm := setupEnv.Clients.EVM[chainId] - - nativeInstance, err := evmSetup.InitAssetContract(setupEnv.NativeEvmToken, evm.EVMClient) - if err != nil { - t.Fatal(err) - } - - nativeBalanceBefore, err := nativeInstance.BalanceOf(&bind.CallOpts{}, evm.Receiver) - if err != nil { - t.Fatal(err) - } - - // Step 1 - Submit Lock Txn from a deployed smart contract - receipt, expectedLockEventLog := submit.BurnEthTransaction(t, setupEnv.AssetMappings, wrappedEvm, setupEnv.NativeEvmToken, chainId, sourceChain, evm.Receiver.Bytes(), amount) - - // Step 1.1 - Get the block timestamp of the burn event - block, err := wrappedEvm.EVMClient.BlockByNumber(context.Background(), receipt.BlockNumber) - if err != nil { - t.Fatal("failed to get block by number", err) - } - blockTimestamp := time.Unix(int64(block.Time()), 0).UTC() - - // Step 2 - Validate Burn Event was emitted with correct data - burnEventId := verify.BurnEvent(t, receipt, expectedLockEventLog) - - // Step 3 - Verify the submitted topic messages - receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, burnEventId, now.UnixNano()) - - // Step 4 - Verify Transfer retrieved from Validator API - transactionData := verify.FungibleTransferFromValidatorAPI(t, setupEnv.Clients.ValidatorClient, setupEnv.TokenID, evm, burnEventId, setupEnv.NativeEvmToken, fmt.Sprint(amount), setupEnv.NativeEvmToken) - - // Get fee amount from wrapped network Router - _, feeAmount := expected.EvmAmoundAndFee(evm.RouterContract, setupEnv.NativeEvmToken, amount, t) - - // Step 5 - Submit Mint transaction - txHash := submit.UnlockTransaction(t, evm, burnEventId, transactionData, common.HexToAddress(setupEnv.NativeEvmToken)) - - // Step 6 - Wait for transaction to be mined - submit.WaitForTransaction(t, evm, txHash) - - expectedUnlockedAmount := amount - feeAmount.Int64() - - // Step 7 - Validate Token balances - verify.WrappedAssetBalance(t, evm, setupEnv.NativeEvmToken, big.NewInt(expectedUnlockedAmount), nativeBalanceBefore, evm.Receiver) - - // Step 8 - Prepare expected Transfer record - expectedLockEventRecord := expected.FungibleTransferRecord( - sourceChain, - chainId, - chainId, - burnEventId, - sourceAsset, - setupEnv.NativeEvmToken, - setupEnv.NativeEvmToken, - strconv.FormatInt(amount, 10), - "", - evm.Receiver.String(), - status.Completed, - wrappedEvm.Signer.Address(), - entity.NanoTime{Time: blockTimestamp}, - ) - - authMsgBytes, err := auth_message.EncodeFungibleBytesFrom( - expectedLockEventRecord.SourceChainID, - expectedLockEventRecord.TargetChainID, - expectedLockEventRecord.TransactionID, - expectedLockEventRecord.TargetAsset, - expectedLockEventRecord.Receiver, - strconv.FormatInt(amount, 10), - ) - - if err != nil { - t.Fatalf("[%s] - Failed to encode the authorisation signature. Error: [%s]", expectedLockEventRecord.TransactionID, err) - } - - // Step 9 - Verify Database Records - verify.TransferRecordAndSignatures(t, setupEnv.DbValidator, expectedLockEventRecord, authMsgBytes, receivedSignatures) -} +// func Test_E2E_Hedera_EVM_Native_Token(t *testing.T) { +// if testing.Short() { +// t.Skip("test skipped in short mode") +// } + +// setupEnv := setup.Load() +// now := time.Now() + +// unlockAmount := setupEnv.Scenario.AmountHederaWrapped + +// chainId := setupEnv.Scenario.FirstEvmChainId +// evm := setupEnv.Clients.EVM[chainId] +// memo := fmt.Sprintf("%d-%s", chainId, evm.Receiver.String()) + +// // Step 1 - Verify the transfer of HTS to the Bridge Account +// wrappedAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, chainId, constants.HederaNetworkId, setupEnv.NativeEvmToken) +// if err != nil { +// t.Fatal(err) +// } + +// tokenID, err := hedera.TokenIDFromString(wrappedAsset) +// if err != nil { +// t.Fatal(err) +// } + +// expectedSubmitUnlockAmount, err := utilities.AddDecimals(unlockAmount, common.HexToAddress(setupEnv.NativeEvmToken), evm) +// if err != nil { +// t.Fatal(err) +// } + +// transactionResponse, nativeBalanceBefore := verify.TokenTransferToBridgeAccount(t, setupEnv.Clients.Hedera, setupEnv.BridgeAccount, setupEnv.NativeEvmToken, tokenID, evm, memo, evm.Receiver, unlockAmount) +// fmt.Println(transactionResponse.TransactionID) +// burnTransfer := []transaction.Transfer{ +// { +// Account: setupEnv.BridgeAccount.String(), +// Amount: -unlockAmount, +// Token: wrappedAsset, +// }, +// } + +// // Step 2 - Verify the submitted topic messages +// receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), now.UnixNano()) + +// // Step 3 - Validate burn scheduled transaction +// burnTransactionID, burnScheduleID := verify.ScheduledBurnTx(t, setupEnv.Clients.MirrorNode, setupEnv.BridgeAccount, setupEnv.TokenID.String(), burnTransfer, now) + +// // Step 4 - Verify Transfer retrieved from Validator API +// transactionData := verify.FungibleTransferFromValidatorAPI( +// t, +// setupEnv.Clients.ValidatorClient, +// setupEnv.TokenID, +// evm, +// hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), +// setupEnv.NativeEvmToken, +// fmt.Sprint(expectedSubmitUnlockAmount), +// setupEnv.NativeEvmToken, +// ) + +// // Step 4.1 - Get the consensus timestamp of the transfer +// tx, err := setupEnv.Clients.MirrorNode.GetSuccessfulTransaction(hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String()) +// if err != nil { +// t.Fatal("failed to get successful transaction", err) +// } +// nanos, err := timestamp.FromString(tx.ConsensusTimestamp) +// if err != nil { +// t.Fatal("failed to parse consensus timestamp", err) +// } +// ts := timestamp.FromNanos(nanos) + +// // Step 5 - Submit Unlock transaction +// txHash := submit.UnlockTransaction(t, evm, hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), transactionData, common.HexToAddress(setupEnv.NativeEvmToken)) + +// // Step 6 - Wait for transaction to be mined +// submit.WaitForTransaction(t, evm, txHash) + +// expectedUnlockedAmount, _ := expected.EvmAmoundAndFee(evm.RouterContract, setupEnv.NativeEvmToken, expectedSubmitUnlockAmount, t) + +// // Step 7 - Validate Token balances +// verify.WrappedAssetBalance(t, evm, setupEnv.NativeEvmToken, expectedUnlockedAmount, nativeBalanceBefore, evm.Receiver) + +// // Step 8 - Verify Database records +// expectedTxRecord := expected.FungibleTransferRecord( +// constants.HederaNetworkId, +// chainId, +// chainId, +// hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), +// wrappedAsset, +// setupEnv.NativeEvmToken, +// setupEnv.NativeEvmToken, +// strconv.FormatInt(expectedSubmitUnlockAmount, 10), +// "", +// evm.Receiver.String(), +// status.Completed, +// setupEnv.Clients.Hedera.GetOperatorAccountID().String(), +// entity.NanoTime{Time: ts}, +// ) + +// // Step 8: Validate that database statuses were updated correctly for the Schedule Burn +// expectedScheduleBurnRecord := expected.ScheduleRecord( +// burnTransactionID, +// burnScheduleID, +// schedule.BURN, +// false, +// status.Completed, +// sql.NullString{ +// String: hederahelper.FromHederaTransactionID(transactionResponse.TransactionID).String(), +// Valid: true, +// }, +// ) + +// authMsgBytes, err := auth_message.EncodeFungibleBytesFrom( +// expectedTxRecord.SourceChainID, +// expectedTxRecord.TargetChainID, +// expectedTxRecord.TransactionID, +// expectedTxRecord.TargetAsset, +// expectedTxRecord.Receiver, +// strconv.FormatInt(expectedSubmitUnlockAmount, 10), +// ) + +// if err != nil { +// t.Fatalf("[%s] - Failed to encode the authorization signature. Error: [%s]", expectedTxRecord.TransactionID, err) +// } + +// // Step 9 - Verify Database Records +// verify.TransferRecordAndSignatures(t, setupEnv.DbValidator, expectedTxRecord, authMsgBytes, receivedSignatures) +// // and +// verify.ScheduleRecord(t, setupEnv.DbValidator, expectedScheduleBurnRecord) +// } + +// // Test_EVM_Native_to_EVM_Token recreates a real life situation of a user who wants to bridge an EVM native token to another EVM chain. +// func Test_EVM_Native_to_EVM_Token(t *testing.T) { +// if testing.Short() { +// t.Skip("test skipped in short mode") +// } + +// setupEnv := setup.Load() +// now := time.Now() + +// amount := setupEnv.Scenario.AmountEvmNative + +// chainId := setupEnv.Scenario.FirstEvmChainId +// evm := setupEnv.Clients.EVM[chainId] +// targetChainID := setupEnv.Scenario.SecondEvmChainId + +// wrappedAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, chainId, targetChainID, setupEnv.NativeEvmToken) +// if err != nil { +// t.Fatal(err) +// } + +// wrappedEvm := setupEnv.Clients.EVM[targetChainID] +// wrappedInstance, err := evmSetup.InitAssetContract(wrappedAsset, wrappedEvm.EVMClient) +// if err != nil { +// t.Fatal(err) +// } + +// wrappedBalanceBefore, err := wrappedInstance.BalanceOf(&bind.CallOpts{}, evm.Receiver) +// if err != nil { +// t.Fatal(err) +// } + +// // Step 1 - Submit Lock Txn from a deployed smart contract +// receipt, expectedLockEventLog := submit.LockEthTransaction(t, evm, setupEnv.NativeEvmToken, targetChainID, evm.Receiver.Bytes(), amount) + +// expectedAmount := new(big.Int).Sub(expectedLockEventLog.Amount, expectedLockEventLog.ServiceFee) + +// // Step 1.1 - Get the block timestamp of the lock event +// block, err := evm.EVMClient.BlockByNumber(context.Background(), receipt.BlockNumber) +// if err != nil { +// t.Fatal("failed to get block by number", err) +// } +// blockTimestamp := time.Unix(int64(block.Time()), 0).UTC() + +// // Step 2 - Validate Lock Event was emitted with correct data +// lockEventId := verify.LockEvent(t, receipt, expectedLockEventLog) + +// // Step 3 - Verify the submitted topic messages +// receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, lockEventId, now.UnixNano()) + +// // Step 4 - Verify Transfer retrieved from Validator API +// transactionData := verify.FungibleTransferFromValidatorAPI(t, setupEnv.Clients.ValidatorClient, setupEnv.TokenID, evm, lockEventId, setupEnv.NativeEvmToken, expectedAmount.String(), wrappedAsset) + +// // Step 5 - Submit Mint transaction +// txHash := submit.MintTransaction(t, wrappedEvm, lockEventId, transactionData, common.HexToAddress(wrappedAsset)) + +// // Step 6 - Wait for transaction to be mined +// submit.WaitForTransaction(t, wrappedEvm, txHash) + +// // Step 7 - Validate Token balances +// verify.WrappedAssetBalance(t, wrappedEvm, wrappedAsset, expectedAmount, wrappedBalanceBefore, evm.Receiver) + +// // Step 8 - Prepare expected Transfer record +// expectedLockEventRecord := expected.FungibleTransferRecord( +// chainId, +// targetChainID, +// chainId, +// lockEventId, +// setupEnv.NativeEvmToken, +// wrappedAsset, +// setupEnv.NativeEvmToken, +// expectedAmount.String(), +// "", +// evm.Receiver.String(), +// status.Completed, +// evm.Signer.Address(), +// entity.NanoTime{Time: blockTimestamp}, +// ) + +// authMsgBytes, err := auth_message.EncodeFungibleBytesFrom( +// expectedLockEventRecord.SourceChainID, +// expectedLockEventRecord.TargetChainID, +// expectedLockEventRecord.TransactionID, +// expectedLockEventRecord.TargetAsset, +// expectedLockEventRecord.Receiver, +// expectedAmount.String(), +// ) + +// if err != nil { +// t.Fatalf("[%s] - Failed to encode the authorisation signature. Error: [%s]", expectedLockEventRecord.TransactionID, err) +// } + +// // Step 9 - Verify Database Records +// verify.TransferRecordAndSignatures(t, setupEnv.DbValidator, expectedLockEventRecord, authMsgBytes, receivedSignatures) +// } + +// // Test_EVM_Wrapped_to_EVM_Token recreates a real life situation of a user who wants to bridge an EVM native token to another EVM chain. +// func Test_EVM_Wrapped_to_EVM_Token(t *testing.T) { +// if testing.Short() { +// t.Skip("test skipped in short mode") +// } + +// setupEnv := setup.Load() +// now := time.Now() + +// amount := setupEnv.Scenario.AmountEvmWrapped + +// chainId := setupEnv.Scenario.FirstEvmChainId +// sourceChain := setupEnv.Scenario.SecondEvmChainId +// wrappedEvm := setupEnv.Clients.EVM[sourceChain] + +// sourceAsset, err := evmSetup.NativeToWrappedAsset(setupEnv.AssetMappings, chainId, sourceChain, setupEnv.NativeEvmToken) +// if err != nil { +// t.Fatal(err) +// } + +// evm := setupEnv.Clients.EVM[chainId] + +// nativeInstance, err := evmSetup.InitAssetContract(setupEnv.NativeEvmToken, evm.EVMClient) +// if err != nil { +// t.Fatal(err) +// } + +// nativeBalanceBefore, err := nativeInstance.BalanceOf(&bind.CallOpts{}, evm.Receiver) +// if err != nil { +// t.Fatal(err) +// } + +// // Step 1 - Submit Lock Txn from a deployed smart contract +// receipt, expectedLockEventLog := submit.BurnEthTransaction(t, setupEnv.AssetMappings, wrappedEvm, setupEnv.NativeEvmToken, chainId, sourceChain, evm.Receiver.Bytes(), amount) + +// // Step 1.1 - Get the block timestamp of the burn event +// block, err := wrappedEvm.EVMClient.BlockByNumber(context.Background(), receipt.BlockNumber) +// if err != nil { +// t.Fatal("failed to get block by number", err) +// } +// blockTimestamp := time.Unix(int64(block.Time()), 0).UTC() + +// // Step 2 - Validate Burn Event was emitted with correct data +// burnEventId := verify.BurnEvent(t, receipt, expectedLockEventLog) + +// // Step 3 - Verify the submitted topic messages +// receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, burnEventId, now.UnixNano()) + +// // Step 4 - Verify Transfer retrieved from Validator API +// transactionData := verify.FungibleTransferFromValidatorAPI(t, setupEnv.Clients.ValidatorClient, setupEnv.TokenID, evm, burnEventId, setupEnv.NativeEvmToken, fmt.Sprint(amount), setupEnv.NativeEvmToken) + +// // Get fee amount from wrapped network Router +// _, feeAmount := expected.EvmAmoundAndFee(evm.RouterContract, setupEnv.NativeEvmToken, amount, t) + +// // Step 5 - Submit Mint transaction +// txHash := submit.UnlockTransaction(t, evm, burnEventId, transactionData, common.HexToAddress(setupEnv.NativeEvmToken)) + +// // Step 6 - Wait for transaction to be mined +// submit.WaitForTransaction(t, evm, txHash) + +// expectedUnlockedAmount := amount - feeAmount.Int64() + +// // Step 7 - Validate Token balances +// verify.WrappedAssetBalance(t, evm, setupEnv.NativeEvmToken, big.NewInt(expectedUnlockedAmount), nativeBalanceBefore, evm.Receiver) + +// // Step 8 - Prepare expected Transfer record +// expectedLockEventRecord := expected.FungibleTransferRecord( +// sourceChain, +// chainId, +// chainId, +// burnEventId, +// sourceAsset, +// setupEnv.NativeEvmToken, +// setupEnv.NativeEvmToken, +// strconv.FormatInt(amount, 10), +// "", +// evm.Receiver.String(), +// status.Completed, +// wrappedEvm.Signer.Address(), +// entity.NanoTime{Time: blockTimestamp}, +// ) + +// authMsgBytes, err := auth_message.EncodeFungibleBytesFrom( +// expectedLockEventRecord.SourceChainID, +// expectedLockEventRecord.TargetChainID, +// expectedLockEventRecord.TransactionID, +// expectedLockEventRecord.TargetAsset, +// expectedLockEventRecord.Receiver, +// strconv.FormatInt(amount, 10), +// ) + +// if err != nil { +// t.Fatalf("[%s] - Failed to encode the authorisation signature. Error: [%s]", expectedLockEventRecord.TransactionID, err) +// } + +// // Step 9 - Verify Database Records +// verify.TransferRecordAndSignatures(t, setupEnv.DbValidator, expectedLockEventRecord, authMsgBytes, receivedSignatures) +// } // Test_Hedera_Native_EVM_NFT_Transfer recreates User who wants to portal a Hedera Native NFT to an EVM chain. func Test_Hedera_Native_EVM_NFT_Transfer(t *testing.T) { @@ -872,8 +869,8 @@ func Test_Hedera_Native_EVM_NFT_Transfer(t *testing.T) { transferFee := setupEnv.NftConstantFees[nftToken] - validatorsFee := setupEnv.Clients.Distributor.ValidAmount(transferFee) - + treasuryFee, validatorsFee := setupEnv.Clients.Distributor.ValidAmounts(transferFee) + validFee := treasuryFee + validatorsFee // Step 1 - Get Token Metadata nftData, err := setupEnv.Clients.MirrorNode.GetNft(nftToken, serialNumber) @@ -922,8 +919,8 @@ func Test_Hedera_Native_EVM_NFT_Transfer(t *testing.T) { receivedSignatures := verify.TopicMessagesWithStartTime(t, setupEnv.Clients.Hedera, setupEnv.TopicID, setupEnv.Scenario.ExpectedValidatorsCount, hederahelper.FromHederaTransactionID(feeResponse.TransactionID).String(), signaturesStartTime) // Step 6 - Validate members fee scheduled transaction - expectedTransfers := expected.MirrorNodeExpectedTransfersForHederaTransfer(setupEnv.Members, setupEnv.BridgeAccount, constants.Hbar, validatorsFee) - scheduledTxID, scheduleID := verify.MembersScheduledTxs(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, constants.Hbar, expectedTransfers, now) + expectedTransfers := expected.MirrorNodeExpectedTransfersForHederaTransfer(setupEnv.Members, setupEnv.Treasury, setupEnv.BridgeAccount, constants.Hbar, validatorsFee, treasuryFee) + scheduledTxID, scheduleID := verify.ScheduledTxs(t, setupEnv.Clients.Hedera, setupEnv.Clients.MirrorNode, setupEnv.Members, setupEnv.Treasury, constants.Hbar, expectedTransfers, now) // Step 7 - Verify Non-Fungible Transfer retrieved from Validator API transactionData := verify.NonFungibleTransferFromValidatorAPI( @@ -969,7 +966,7 @@ func Test_Hedera_Native_EVM_NFT_Transfer(t *testing.T) { NativeAsset: nftToken, Receiver: receiver.String(), Amount: "", - Fee: strconv.FormatInt(validatorsFee, 10), + Fee: strconv.FormatInt(validFee, 10), Status: status.Completed, SerialNumber: serialNumber, Metadata: string(decodedMetadata), @@ -981,7 +978,7 @@ func Test_Hedera_Native_EVM_NFT_Transfer(t *testing.T) { // and: expectedFeeRecord := expected.FeeRecord( scheduledTxID, - scheduleID, validatorsFee, + scheduleID, validFee, transactionID) // and: expectedScheduleTransferRecord := &entity.Schedule{ diff --git a/e2e/helper/expected/fee.go b/e2e/helper/expected/fee.go index 9c3e2b667..c53cee806 100644 --- a/e2e/helper/expected/fee.go +++ b/e2e/helper/expected/fee.go @@ -17,21 +17,23 @@ package expected import ( + "math/big" + "testing" + "github.com/ethereum/go-ethereum/common" "github.com/limechain/hedera-eth-bridge-validator/app/clients/evm/contracts/router" "github.com/limechain/hedera-eth-bridge-validator/app/domain/service" - "math/big" - "testing" ) -func ReceiverAndFeeAmounts(feeCalc service.Fee, distributor service.Distributor, token string, amount int64) (receiverAmount, fee int64) { +func ReceiverAndFeeAmounts(feeCalc service.Fee, distributor service.Distributor, token string, amount int64) (receiverAmount, valdiatorsFee, treasuryFee int64) { fee, remainder := feeCalc.CalculateFee(token, amount) - validFee := distributor.ValidAmount(fee) + validTreasuryFee, validValidatorsFee := distributor.ValidAmounts(fee) + validFee := validTreasuryFee + validValidatorsFee if validFee != fee { remainder += fee - validFee } - return remainder, validFee + return remainder, validValidatorsFee, validTreasuryFee } func EvmAmoundAndFee(router *router.Router, token string, amount int64, t *testing.T) (*big.Int, *big.Int) { diff --git a/e2e/helper/expected/mirror-node.go b/e2e/helper/expected/mirror-node.go index 556b9540f..f6c0fd5db 100644 --- a/e2e/helper/expected/mirror-node.go +++ b/e2e/helper/expected/mirror-node.go @@ -22,9 +22,9 @@ import ( "github.com/limechain/hedera-eth-bridge-validator/constants" ) -func MirrorNodeExpectedTransfersForBurnEvent(members []hedera.AccountID, hederaClient *hedera.Client, bridgeAccount hedera.AccountID, asset string, amount, fee int64) []transaction.Transfer { - total := amount + fee - feePerMember := fee / int64(len(members)) +func MirrorNodeExpectedTransfersForBurnEvent(members []hedera.AccountID, treasury hedera.AccountID, hederaClient *hedera.Client, bridgeAccount hedera.AccountID, asset string, amount, validatorFee, treasuryFee int64) []transaction.Transfer { + total := amount + validatorFee + treasuryFee + feePerMember := validatorFee / int64(len(members)) var expectedTransfers []transaction.Transfer expectedTransfers = append(expectedTransfers, transaction.Transfer{ @@ -48,6 +48,16 @@ func MirrorNodeExpectedTransfersForBurnEvent(members []hedera.AccountID, hederaC expectedTransfers[i].Token = asset } } + treasuryTransfer := transaction.Transfer{ + Account: treasury.String(), + Amount: treasuryFee, + } + + if asset != constants.Hbar { + + treasuryTransfer.Token = asset + } + expectedTransfers = append(expectedTransfers, treasuryTransfer) return expectedTransfers } @@ -69,9 +79,10 @@ func MirrorNodeExpectedTransfersForLockEvent(hederaClient *hedera.Client, bridge return expectedTransfers } -func MirrorNodeExpectedTransfersForHederaTransfer(members []hedera.AccountID, bridgeAccount hedera.AccountID, asset string, fee int64) []transaction.Transfer { - feePerMember := fee / int64(len(members)) +func MirrorNodeExpectedTransfersForHederaTransfer(members []hedera.AccountID, treasury hedera.AccountID, bridgeAccount hedera.AccountID, asset string, validatorsFee, treasuryFee int64) []transaction.Transfer { + feePerMember := validatorsFee / int64(len(members)) + fee := validatorsFee + treasuryFee var expectedTransfers []transaction.Transfer expectedTransfers = append(expectedTransfers, transaction.Transfer{ Account: bridgeAccount.String(), @@ -91,5 +102,16 @@ func MirrorNodeExpectedTransfersForHederaTransfer(members []hedera.AccountID, br } } + treasuryTransfer := transaction.Transfer{ + Account: treasury.String(), + Amount: treasuryFee, + } + + if asset != constants.Hbar { + treasuryTransfer.Token = asset + } + + expectedTransfers = append(expectedTransfers, treasuryTransfer) + return expectedTransfers } diff --git a/e2e/helper/verify/db.go b/e2e/helper/verify/db.go index d2e321626..a79a4fa2c 100644 --- a/e2e/helper/verify/db.go +++ b/e2e/helper/verify/db.go @@ -236,7 +236,7 @@ func (s *Service) getMessageListByTransactionId(expectedTransferRecord *entity.T return result, err } - time.Sleep(s.DatabaseRetryTimeout * time.Second) + time.Sleep(s.DatabaseRetryTimeout * time.Minute) s.logger.Infof("Database Message records [%s] retry %d", expectedTransferRecord.TransactionID, currentCount) } @@ -262,7 +262,7 @@ func (s *Service) getTransactionById(verifier dbVerifier, expectedTransferRecord return result, err } - time.Sleep(s.DatabaseRetryTimeout * time.Second) + time.Sleep(s.DatabaseRetryTimeout * time.Minute) s.logger.Infof("Database Transaction record [%s] retry %d", expectedTransferRecord.TransactionID, currentCount) } @@ -288,7 +288,7 @@ func (s *Service) getScheduleByTransactionId(verifier dbVerifier, expectedRecord return result, err } - time.Sleep(s.DatabaseRetryTimeout * time.Second) + time.Sleep(s.DatabaseRetryTimeout * time.Minute) s.logger.Infof("Database Schedule record [%s] retry %d", expectedRecord.TransactionID, currentCount) } @@ -314,7 +314,7 @@ func (s *Service) getFeeByTransactionId(verifier dbVerifier, expectedRecord *ent return result, err } - time.Sleep(s.DatabaseRetryTimeout * time.Second) + time.Sleep(s.DatabaseRetryTimeout * time.Minute) s.logger.Infof("Database Fee record [%s] retry %d", expectedRecord.TransactionID, currentCount) } diff --git a/e2e/helper/verify/hedera.go b/e2e/helper/verify/hedera.go index dce97e9ef..da19c0873 100644 --- a/e2e/helper/verify/hedera.go +++ b/e2e/helper/verify/hedera.go @@ -194,11 +194,11 @@ func AccountBalance(t *testing.T, hederaClient *hedera.Client, hederaID hedera.A } } -func SubmittedScheduledTx(t *testing.T, hederaClient *hedera.Client, mirrorNodeClient *mirror_node.Client, members []hedera.AccountID, asset string, expectedTransfers []transaction.Transfer, now time.Time) (transactionID, scheduleID string) { +func SubmittedScheduledTx(t *testing.T, hederaClient *hedera.Client, mirrorNodeClient *mirror_node.Client, members []hedera.AccountID, treasury hedera.AccountID, asset string, expectedTransfers []transaction.Transfer, now time.Time) (transactionID, scheduleID string) { t.Helper() receiverTransactionID, receiverScheduleID := ScheduledTx(t, hederaClient, mirrorNodeClient, hederaClient.GetOperatorAccountID(), asset, expectedTransfers, now) - membersTransactionID, membersScheduleID := MembersScheduledTxs(t, hederaClient, mirrorNodeClient, members, asset, expectedTransfers, now) + membersTransactionID, membersScheduleID := ScheduledTxs(t, hederaClient, mirrorNodeClient, members, treasury, asset, expectedTransfers, now) if receiverTransactionID != membersTransactionID { t.Fatalf("Scheduled Transactions between members are different. Receiver [%s], Member [%s]", receiverTransactionID, membersTransactionID) @@ -319,7 +319,7 @@ func ScheduledNftTransfer(t *testing.T, hederaClient *hedera.Client, mirrorNodeC func ScheduledTx(t *testing.T, hederaClient *hedera.Client, mirrorNodeClient *mirror_node.Client, account hedera.AccountID, asset string, expectedTransfers []transaction.Transfer, now time.Time) (transactionID, scheduleID string) { t.Helper() - timeLeft := 180 + timeLeft := 20 for { response, err := mirrorNodeClient.GetAccountCreditTransactionsAfterTimestamp(account, now.UnixNano()) if err != nil { @@ -336,9 +336,9 @@ func ScheduledTx(t *testing.T, hederaClient *hedera.Client, mirrorNodeClient *mi } if timeLeft > 0 { - fmt.Printf("Could not find any scheduled transactions for account [%s]. Trying again. Time left: ~[%d] seconds\n", hederaClient.GetOperatorAccountID(), timeLeft) - timeLeft -= 10 - time.Sleep(10 * time.Second) + fmt.Printf("Could not find any scheduled transactions for account [%s]. Trying again. Time left: ~[%d] minutes\n", hederaClient.GetOperatorAccountID(), timeLeft) + timeLeft-- + time.Sleep(time.Minute) continue } break @@ -348,7 +348,7 @@ func ScheduledTx(t *testing.T, hederaClient *hedera.Client, mirrorNodeClient *mi return "", "" } -func MembersScheduledTxs(t *testing.T, hederaClient *hedera.Client, mirrorNodeClient *mirror_node.Client, members []hedera.AccountID, asset string, expectedTransfers []transaction.Transfer, now time.Time) (transactionID, scheduleID string) { +func ScheduledTxs(t *testing.T, hederaClient *hedera.Client, mirrorNodeClient *mirror_node.Client, members []hedera.AccountID, treasury hedera.AccountID, asset string, expectedTransfers []transaction.Transfer, now time.Time) (transactionID, scheduleID string) { t.Helper() if len(members) == 0 { return "", "" @@ -356,8 +356,10 @@ func MembersScheduledTxs(t *testing.T, hederaClient *hedera.Client, mirrorNodeCl var transactions []string var scheduleIDs []string - for _, member := range members { - txID, scheduleID := ScheduledTx(t, hederaClient, mirrorNodeClient, member, asset, expectedTransfers, now) + var accountIDs []hedera.AccountID + accountIDs = append(accountIDs, append(members, treasury)...) + for _, accountID := range accountIDs { + txID, scheduleID := ScheduledTx(t, hederaClient, mirrorNodeClient, accountID, asset, expectedTransfers, now) transactions = append(transactions, txID) if !utilities.AllSame(transactions) { @@ -467,7 +469,6 @@ func TopicMessagesWithStartTime(t *testing.T, hederaClient *hedera.Client, topic message := msg.GetFungibleSignatureMessage() transferID = message.TransferID signature = message.Signature - break case *model.TopicMessage_NftSignatureMessage: message := msg.GetNftSignatureMessage() transferID = message.TransferID @@ -484,10 +485,10 @@ func TopicMessagesWithStartTime(t *testing.T, hederaClient *hedera.Client, topic }, ) if err != nil { - t.Fatalf("Unable to subscribe to Topic [%s]", topicId) + t.Fatalf("Unable to subscribe to Topic [%s]: [%s]", topicId, err) } - timeoutTimer := time.NewTimer(480 * time.Second) + timeoutTimer := time.NewTimer(15 * time.Minute) signatureLoop: for ethSignaturesCollected < expectedValidatorsCount { diff --git a/e2e/setup/config.go b/e2e/setup/config.go index 952636830..a57b5a8f6 100644 --- a/e2e/setup/config.go +++ b/e2e/setup/config.go @@ -67,14 +67,17 @@ func Load() *Setup { configuration := Config{ Hedera: Hedera{ - NetworkType: e2eConfig.Hedera.NetworkType, - BridgeAccount: e2eConfig.Hedera.BridgeAccount, - PayerAccount: e2eConfig.Hedera.PayerAccount, - Members: e2eConfig.Hedera.Members, - TopicID: e2eConfig.Hedera.TopicID, - Sender: Sender(e2eConfig.Hedera.Sender), - DbValidationProps: make([]config.Database, len(e2eConfig.Hedera.DbValidationProps)), - MirrorNode: *new(config.MirrorNode).DefaultOrConfig(&e2eConfig.Hedera.MirrorNode), + NetworkType: e2eConfig.Hedera.NetworkType, + BridgeAccount: e2eConfig.Hedera.BridgeAccount, + PayerAccount: e2eConfig.Hedera.PayerAccount, + Members: e2eConfig.Hedera.Members, + TopicID: e2eConfig.Hedera.TopicID, + Treasury: e2eConfig.Hedera.Treasury, + ValidatorRewardPercentage: e2eConfig.Hedera.ValidatorRewardPercentage, + TreasuryRewardPercentage: e2eConfig.Hedera.TreasuryRewardPercentage, + Sender: Sender(e2eConfig.Hedera.Sender), + DbValidationProps: make([]config.Database, len(e2eConfig.Hedera.DbValidationProps)), + MirrorNode: *new(config.MirrorNode).DefaultOrConfig(&e2eConfig.Hedera.MirrorNode), }, EVM: make(map[uint64]config.Evm), Tokens: e2eConfig.Tokens, @@ -114,21 +117,24 @@ func Load() *Setup { // Setup used by the e2e tests. Preloaded with all necessary dependencies type Setup struct { - BridgeAccount hederaSDK.AccountID - PayerAccount hederaSDK.AccountID - TopicID hederaSDK.TopicID - TokenID hederaSDK.TokenID - NativeEvmToken string - NftTokenID hederaSDK.TokenID - NftSerialNumber int64 - NftConstantFees map[string]int64 - NftDynamicFees map[string]decimal.Decimal - FeePercentages map[string]int64 - Members []hederaSDK.AccountID - Clients *clients - DbValidator *verify.Service - AssetMappings service.Assets - Scenario *ScenarioConfig + BridgeAccount hederaSDK.AccountID + PayerAccount hederaSDK.AccountID + Treasury hederaSDK.AccountID + ValidatorRewardPercentage int + TreasuryRewardPercentage int + TopicID hederaSDK.TopicID + TokenID hederaSDK.TokenID + NativeEvmToken string + NftTokenID hederaSDK.TokenID + NftSerialNumber int64 + NftConstantFees map[string]int64 + NftDynamicFees map[string]decimal.Decimal + FeePercentages map[string]int64 + Members []hederaSDK.AccountID + Clients *clients + DbValidator *verify.Service + AssetMappings service.Assets + Scenario *ScenarioConfig } // newSetup instantiates new Setup struct @@ -148,6 +154,11 @@ func newSetup(config Config) (*Setup, error) { return nil, err } + treasuryID, err := hederaSDK.AccountIDFromString(config.Hedera.Treasury) + if err != nil { + return nil, err + } + tokenID, err := hederaSDK.TokenIDFromString(config.Tokens.WToken) if err != nil { return nil, err @@ -197,21 +208,24 @@ func newSetup(config Config) (*Setup, error) { dbValidator.ExpectedValidatorsCount = scenario.ExpectedValidatorsCount return &Setup{ - BridgeAccount: bridgeAccount, - PayerAccount: payerAccount, - TopicID: topicID, - TokenID: tokenID, - NftTokenID: nftTokenID, - NftSerialNumber: config.Tokens.NftSerialNumber, - NativeEvmToken: config.Tokens.EvmNativeToken, - NftConstantFees: config.NftConstantFees, - NftDynamicFees: config.NftDynamicFees, - FeePercentages: config.FeePercentages, - Members: members, - Clients: clients, - DbValidator: dbValidator, - AssetMappings: config.AssetMappings, - Scenario: scenario, + BridgeAccount: bridgeAccount, + PayerAccount: payerAccount, + TopicID: topicID, + TokenID: tokenID, + Treasury: treasuryID, + ValidatorRewardPercentage: config.Hedera.ValidatorRewardPercentage, + TreasuryRewardPercentage: config.Hedera.TreasuryRewardPercentage, + NftTokenID: nftTokenID, + NftSerialNumber: config.Tokens.NftSerialNumber, + NativeEvmToken: config.Tokens.EvmNativeToken, + NftConstantFees: config.NftConstantFees, + NftDynamicFees: config.NftDynamicFees, + FeePercentages: config.FeePercentages, + Members: members, + Clients: clients, + DbValidator: dbValidator, + AssetMappings: config.AssetMappings, + Scenario: scenario, }, nil } @@ -300,7 +314,7 @@ func newClients(config Config) (*clients, error) { ValidatorClient: validatorClient, MirrorNode: mirrorNode, FeeCalculator: fee.New(config.FeePercentages), - Distributor: distributor.New(config.Hedera.Members), + Distributor: distributor.New(config.Hedera.Members, config.Hedera.Treasury, config.Hedera.TreasuryRewardPercentage, config.Hedera.ValidatorRewardPercentage), }, nil } @@ -405,14 +419,17 @@ type Config struct { // Hedera props from the application.yml type Hedera struct { - NetworkType string - BridgeAccount string - PayerAccount string - Members []string - TopicID string - Sender Sender - DbValidationProps []config.Database - MirrorNode config.MirrorNode + NetworkType string + BridgeAccount string + PayerAccount string + Members []string + Treasury string + ValidatorRewardPercentage int + TreasuryRewardPercentage int + TopicID string + Sender Sender + DbValidationProps []config.Database + MirrorNode config.MirrorNode } // Sender props from the application.yml diff --git a/e2e/setup/parser/parser.go b/e2e/setup/parser/parser.go index c50714ed9..b1553c937 100644 --- a/e2e/setup/parser/parser.go +++ b/e2e/setup/parser/parser.go @@ -17,14 +17,17 @@ type Config struct { } type HederaParser struct { - NetworkType string `yaml:"network_type"` - BridgeAccount string `yaml:"bridge_account"` - PayerAccount string `yaml:"payer_account"` - Members []string `yaml:"members"` - TopicID string `yaml:"topic_id"` - Sender Sender `yaml:"sender"` - DbValidationProps []parser.Database `yaml:"dbs"` - MirrorNode parser.MirrorNode `yaml:"mirror_node"` + NetworkType string `yaml:"network_type"` + BridgeAccount string `yaml:"bridge_account"` + PayerAccount string `yaml:"payer_account"` + Members []string `yaml:"members"` + TopicID string `yaml:"topic_id"` + Treasury string `yaml:"treasury"` + ValidatorRewardPercentage int `yaml:"validator_reward_percentage"` + TreasuryRewardPercentage int `yaml:"treasury_reward_percentage"` + Sender Sender `yaml:"sender"` + DbValidationProps []parser.Database `yaml:"dbs"` + MirrorNode parser.MirrorNode `yaml:"mirror_node"` } type ScenarioParser struct { diff --git a/test/mocks/common/logger_mock.go b/test/mocks/common/logger_mock.go new file mode 100644 index 000000000..be207660d --- /dev/null +++ b/test/mocks/common/logger_mock.go @@ -0,0 +1,17 @@ +package common + +import ( + "fmt" + + "github.com/stretchr/testify/mock" +) + +type MockLogger struct { + mock.Mock + Entries []string +} + +func (m *MockLogger) Errorf(format string, args ...interface{}) { + m.Called(format, args) + m.Entries = append(m.Entries, fmt.Sprintf(format, args...)) +} diff --git a/test/mocks/service/distributor_service_mock.go b/test/mocks/service/distributor_service_mock.go index 2665b8b85..b86fecb4c 100644 --- a/test/mocks/service/distributor_service_mock.go +++ b/test/mocks/service/distributor_service_mock.go @@ -34,15 +34,15 @@ func (mds *MockDistrubutorService) PrepareTransfers(amount int64, token string) return nil, args.Get(1).(error) } -func (mds *MockDistrubutorService) CalculateMemberDistribution(validFee int64) ([]transfer.Hedera, error) { - args := mds.Called(validFee) +func (mds *MockDistrubutorService) CalculateMemberDistribution(validTreasuryFee, validValidatorFee int64) ([]transfer.Hedera, error) { + args := mds.Called(validTreasuryFee, validValidatorFee) if args.Get(1) == nil { return args.Get(0).([]transfer.Hedera), nil } return nil, args.Get(1).(error) } -func (mds *MockDistrubutorService) ValidAmount(amount int64) int64 { +func (mds *MockDistrubutorService) ValidAmounts(amount int64) (int64, int64) { args := mds.Called(amount) - return args.Get(0).(int64) + return args.Get(0).(int64), args.Get(1).(int64) }