diff --git a/lib/constants.js b/lib/constants.js index 87c17232..06f25c63 100644 --- a/lib/constants.js +++ b/lib/constants.js @@ -116,6 +116,9 @@ const PEGIN_V1_RSKT_PREFIX_HEX = '52534b54'; const MINIMUM_PEGOUT_AMOUNT_IN_RBTC = 0.0025; +const FEE_PER_KB_CHANGE_PK = '6a4b49312b91e203ddfb9bc2d900ebbd46fbede46a7462e770bedcb11ad405e9'; +const FEE_PER_KB_CHANGE_ADDR = '53f8f6dabd612b6137215ddd7758bb5cdd638922'; + module.exports = { KEY_TYPE_BTC, KEY_TYPE_RSK, @@ -141,4 +144,6 @@ module.exports = { PEGIN_V1_RSKT_PREFIX_HEX, PEGOUT_REJECTION_REASONS, MINIMUM_PEGOUT_AMOUNT_IN_RBTC, + FEE_PER_KB_CHANGE_PK, + FEE_PER_KB_CHANGE_ADDR, }; diff --git a/lib/rsk-utils.js b/lib/rsk-utils.js index 3f72143d..49833fc2 100644 --- a/lib/rsk-utils.js +++ b/lib/rsk-utils.js @@ -6,7 +6,11 @@ const { getRskTransactionHelpers } = require('../lib/rsk-tx-helper-provider'); const { wait, retryWithCheck, removePrefix0x } = require('./utils'); const { waitForBitcoinMempoolToGetTxs } = require('./btc-utils'); const { getLogger } = require('../logger'); -const { PEGOUT_EVENTS } = require('./constants'); +const { + PEGOUT_EVENTS, + FEE_PER_KB_CHANGE_PK, + FEE_PER_KB_CHANGE_ADDR, +} = require('./constants'); const BtcTransactionHelper = require('btc-transaction-helper/btc-transaction-helper'); const BTC_TO_RSK_MINIMUM_ACCEPTABLE_CONFIRMATIONS = 3; @@ -502,7 +506,25 @@ const getFedsPubKeys = async (bridge) => { FEDS_PUBKEYS_LIST.push(removePrefix0x(fedPubKey)); } return FEDS_PUBKEYS_LIST; -} +}; + +/** + * Calls `Bridge::voteFeePerKbChange` to set a new fee per kb. + * @param {RskTransactionHelper} rskTxHelper + * @param {number} feePerKbInSatoshis the value to be set as the new fee per kb + */ +const setFeePerKb = async (rskTxHelper, feePerKbInSatoshis) => { + + await rskTxHelper.getClient().eth.personal.importRawKey(FEE_PER_KB_CHANGE_PK, ''); + await rskTxHelper.getClient().eth.personal.unlockAccount(FEE_PER_KB_CHANGE_ADDR, ''); + const bridge = getBridge(rskTxHelper.getClient()); + + await sendTransaction(rskTxHelper, bridge.methods.voteFeePerKbChange(feePerKbInSatoshis), FEE_PER_KB_CHANGE_ADDR); + + const finalFeePerKb = await bridge.methods.getFeePerKb().call(); + expect(Number(finalFeePerKb)).to.equal(Number(feePerKbInSatoshis)); + +}; module.exports = { mineAndSync, @@ -522,4 +544,5 @@ module.exports = { waitAndUpdateBridge, sendTransaction, getPegoutEventsInBlockRange, + setFeePerKb, }; diff --git a/lib/tests/2wp.js b/lib/tests/2wp.js index 04bc7938..cfb8a9b6 100644 --- a/lib/tests/2wp.js +++ b/lib/tests/2wp.js @@ -5,7 +5,7 @@ const { getBridge } = require('../precompiled-abi-forks-util'); const { getBtcClient } = require('../btc-client-provider'); const { getRskTransactionHelper, getRskTransactionHelpers } = require('../rsk-tx-helper-provider'); const { satoshisToBtc, btcToSatoshis, satoshisToWeis, btcToWeis } = require('@rsksmart/btc-eth-unit-converter'); -const { findEventInBlock, triggerRelease, getPegoutEventsInBlockRange } = require('../rsk-utils'); +const { findEventInBlock, triggerRelease, getPegoutEventsInBlockRange, setFeePerKb } = require('../rsk-utils'); const { PEGIN_REJECTION_REASONS, PEGIN_UNREFUNDABLE_REASONS, @@ -777,6 +777,47 @@ const execute = (description, getRskHost) => { }); + it('should reject and refund a pegout when fee per kb is above value', async () => { + + // Arrange + + // Create a pegin for the sender to ensure there is enough funds to pegout and because this is the natural process + const senderRecipientInfo = await createSenderRecipientInfo(rskTxHelper, btcTxHelper); + const peginValueInSatoshis = btcToSatoshis(0.5); + const btcPeginTxHash = await sendPegin(rskTxHelper, btcTxHelper, senderRecipientInfo.btcSenderAddressInfo, satoshisToBtc(peginValueInSatoshis)); + await ensurePeginIsRegistered(rskTxHelper, btcPeginTxHash); + + const initial2wpBalances = await get2wpBalances(rskTxHelper, btcTxHelper); + const initialBtcRecipientAddressBalanceInSatoshis = await getBtcAddressBalanceInSatoshis(btcTxHelper, senderRecipientInfo.btcSenderAddressInfo.address); + const initialRskSenderBalanceInWeisBN = await rskTxHelper.getBalance(senderRecipientInfo.rskRecipientRskAddressInfo.address); + const pegoutValueInRbtc = MINIMUM_PEGOUT_AMOUNT_IN_RBTC; + + // The minimum pegout value plus 1 satoshi to make the fee per kb exactly above the pegout value. + const newFeePerKbInSatoshis = btcToSatoshis(MINIMUM_PEGOUT_AMOUNT_IN_RBTC) + 1; + await setFeePerKb(rskTxHelper, newFeePerKbInSatoshis); + + // Act + + const pegoutTransaction = await sendTxToBridge(rskTxHelper, pegoutValueInRbtc, senderRecipientInfo.rskRecipientRskAddressInfo.address); + + // Assert + + await assertExpectedReleaseRequestRejectedEventIsEmitted(senderRecipientInfo.rskRecipientRskAddressInfo.address, btcToSatoshis(pegoutValueInRbtc), PEGOUT_REJECTION_REASONS.FEE_ABOVE_VALUE); + + await assert2wpBalanceIsUnchanged(initial2wpBalances); + + // The rsk sender balance is the same as the initial balance minus the gas fee, because the pegout amount was refunded. + const finalRskSenderBalanceInWeisBN = await rskTxHelper.getBalance(senderRecipientInfo.rskRecipientRskAddressInfo.address); + const gasFee = pegoutTransaction.gasUsed * pegoutTransaction.effectiveGasPrice; + const expectedRskSenderBalanceInWeisBN = initialRskSenderBalanceInWeisBN.sub(new BN(`${gasFee}`)); + expect(finalRskSenderBalanceInWeisBN.eq(expectedRskSenderBalanceInWeisBN)).to.be.true; + + // The btc recipient address balance is the same as the initial balance, because the pegout didn't go though. + const finalBtcRecipientBalanceInSatoshis = await getBtcAddressBalanceInSatoshis(btcTxHelper, senderRecipientInfo.btcSenderAddressInfo.address); + expect(finalBtcRecipientBalanceInSatoshis).to.be.equal(initialBtcRecipientAddressBalanceInSatoshis); + + }); + }); };