Skip to content

Commit

Permalink
Feat/sixty forty reward split (#916)
Browse files Browse the repository at this point in the history
* Implement reward split functionality by a given ratio

* Fix/add unit tests

* Update e2e for hedera native tokens

---------

Co-authored-by: vlady-kotsev <[email protected]>
  • Loading branch information
vlady-kotsev and vlady-kotsev authored Jul 1, 2024
1 parent 1307e34 commit c1f94d4
Show file tree
Hide file tree
Showing 25 changed files with 956 additions and 667 deletions.
6 changes: 3 additions & 3 deletions app/domain/service/distributor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
10 changes: 8 additions & 2 deletions app/process/handler/read-only/fee-transfer/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Expand All @@ -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,
Expand Down
10 changes: 5 additions & 5 deletions app/process/handler/read-only/fee-transfer/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
10 changes: 7 additions & 3 deletions app/process/handler/read-only/fee/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,16 +108,20 @@ 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 {
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{
AccountID: fmh.bridgeAccount,
Expand Down
14 changes: 7 additions & 7 deletions app/process/handler/read-only/fee/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
Expand All @@ -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)
}
Expand All @@ -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) {
Expand All @@ -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) {
Expand All @@ -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() {
Expand Down
9 changes: 7 additions & 2 deletions app/process/handler/read-only/nft/fee/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -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{
Expand Down
84 changes: 44 additions & 40 deletions app/process/handler/read-only/nft/fee/handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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"
Expand All @@ -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},
}
Expand Down Expand Up @@ -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)
}
Expand All @@ -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)
}

Expand All @@ -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)
}

Expand All @@ -220,26 +224,26 @@ 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)
}

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)
}
Expand Down Expand Up @@ -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)
Expand All @@ -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)
Expand All @@ -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)
Expand All @@ -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{
Expand Down
Loading

0 comments on commit c1f94d4

Please sign in to comment.