diff --git a/contracts/CallReleaseBtcContract.sol b/contracts/CallReleaseBtcContract.sol new file mode 100644 index 00000000..00b2dd6c --- /dev/null +++ b/contracts/CallReleaseBtcContract.sol @@ -0,0 +1,15 @@ +pragma solidity ^0.8.0; + +interface BridgeInterface { + function releaseBtc() external payable; +} + +contract CallReleaseBtcContract { + + BridgeInterface public bridgeContract = BridgeInterface(0x0000000000000000000000000000000001000006); + + function callBridgeReleaseBtc() external payable { + bridgeContract.releaseBtc{value:msg.value}(); + } + +} diff --git a/lib/contractDeployer.js b/lib/contractDeployer.js new file mode 100644 index 00000000..00bac6ab --- /dev/null +++ b/lib/contractDeployer.js @@ -0,0 +1,40 @@ + +const fs = require('fs'); +const path = require('path'); +const { btcToWeis } = require('@rsksmart/btc-eth-unit-converter'); +const solUtils = require('./sol-utils'); +const TEST_RELEASE_BTC_CONTRACT = '../contracts/CallReleaseBtcContract.sol'; +const TEST_RELEASE_BTC_CONTRACT_NAME = 'CallReleaseBtcContract'; +const SOLIDITY_COMPILER_VERSION = 'v0.8.26+commit.8a97fa7a'; +const { sendFromCow } = require('./rsk-utils'); + +const deployCallReleaseBtcContract = async (rskTxHelper) => { + + const address = await rskTxHelper.getClient().eth.personal.newAccount(''); + await sendFromCow(rskTxHelper, address, Number(btcToWeis(0.5))); + await rskTxHelper.getClient().eth.personal.unlockAccount(address, ''); + + const fullPath = path.resolve(__dirname, TEST_RELEASE_BTC_CONTRACT); + const source = fs.readFileSync(fullPath).toString(); + + const callReleaseBtcContract = await solUtils.compileAndDeploy( + SOLIDITY_COMPILER_VERSION, + source, + TEST_RELEASE_BTC_CONTRACT_NAME, + [], + rskTxHelper, + { + from: address + } + ); + + return { + creatorAddress: address, + callReleaseBtcContract, + }; + +}; + +module.exports = { + deployCallReleaseBtcContract, +}; diff --git a/lib/rsk-utils.js b/lib/rsk-utils.js index 155d77be..029d4f21 100644 --- a/lib/rsk-utils.js +++ b/lib/rsk-utils.js @@ -337,13 +337,13 @@ const triggerRelease = async (rskTransactionHelpers, btcClient, callbacks = {}) * @param {RskTransactionHelper} rskTxHelper to make transactions to the rsk network * @param {web3.eth.Contract.ContractSendMethod} method contract method to be invoked * @param {string} from rsk address to send the transaction from + * @param {number} valueInWeis amount in weis to be sent with the transaction + * @param {number} gas gas to be used in the transaction. Defaults to 100000 * @returns {web3.eth.TransactionReceipt} txReceipt */ -const sendTransaction = async (rskTxHelper, method, from) => { +const sendTransaction = async (rskTxHelper, method, from, valueInWeis = 0, gas = 100000) => { - const estimatedGas = await method.estimateGas({ from }); - - const txReceiptPromise = method.send({ from, value: 0, gasPrice: 0, gas: estimatedGas }); + const txReceiptPromise = method.send({ from, value: valueInWeis, gas }); await waitForRskMempoolToGetNewTxs(rskTxHelper); await mineAndSync(getRskTransactionHelpers()); diff --git a/lib/tests/2wp.js b/lib/tests/2wp.js index c0f43831..6a1e85d7 100644 --- a/lib/tests/2wp.js +++ b/lib/tests/2wp.js @@ -5,7 +5,9 @@ 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 } = require('@rsksmart/btc-eth-unit-converter'); -const { findEventInBlock, triggerRelease, getPegoutEventsInBlockRange, setFeePerKb } = require('../rsk-utils'); +const { findEventInBlock, triggerRelease, getPegoutEventsInBlockRange, setFeePerKb, sendTransaction } = require('../rsk-utils'); +const { satoshisToBtc, btcToSatoshis, satoshisToWeis, btcToWeis } = require('@rsksmart/btc-eth-unit-converter'); +const BridgeTransactionParser = require('@rsksmart/bridge-transaction-parser'); const { PEGIN_REJECTION_REASONS, PEGIN_UNREFUNDABLE_REASONS, @@ -30,6 +32,7 @@ const { getBtcAddressBalanceInSatoshis } = require('../btc-utils'); const { ensure0x, removePrefix0x } = require('../utils'); const { getBridgeState } = require('@rsksmart/bridge-state-data-parser'); const bitcoinJsLib = require('bitcoinjs-lib'); +const { deployCallReleaseBtcContract } = require('../contractDeployer'); let btcTxHelper; let rskTxHelpers; @@ -819,6 +822,37 @@ const execute = (description, getRskHost) => { }); + it('should reject and not refund a pegout when a contract is trying to execute it', async () => { + + // Arrange + + const initial2wpBalances = await get2wpBalances(rskTxHelper, btcTxHelper); + const pegoutValueInRbtc = MINIMUM_PEGOUT_AMOUNT_IN_RBTC; + + const { callReleaseBtcContract, creatorAddress } = await deployCallReleaseBtcContract(rskTxHelper); + const initialRskSenderBalanceInWeisBN = await rskTxHelper.getBalance(creatorAddress); + + // Act + + const callBridgeReleaseBtcMethod = callReleaseBtcContract.methods.callBridgeReleaseBtc(); + const contractCallTxReceipt = await sendTransaction(rskTxHelper, callBridgeReleaseBtcMethod, creatorAddress, btcToWeis(pegoutValueInRbtc)); + + // Assert + + const rskSenderAddressChecksummed = rskTxHelper.getClient().utils.toChecksumAddress(ensure0x(callReleaseBtcContract.options.address)); + const expectedEvent = createExpectedReleaseRequestRejectedEvent(rskSenderAddressChecksummed, btcToSatoshis(pegoutValueInRbtc), PEGOUT_REJECTION_REASONS.CALLER_CONTRACT); + const actualReleaseRequestRejectedEvent = getReleaseRequestRejectedEventFromContractCallTxReceipt(contractCallTxReceipt); + expect(actualReleaseRequestRejectedEvent).to.be.deep.equal(expectedEvent); + + await assert2wpBalancesAfterPegoutFromContract(initial2wpBalances, pegoutValueInRbtc); + + // The rsk sender should lose the funds since there's no refund when a smart contract is trying to do a pegout + const expectedRskSenderBalanceInWeisBN = initialRskSenderBalanceInWeisBN.sub(new BN(`${btcToWeis(pegoutValueInRbtc)}`)); + const finalRskSenderBalanceInWeisBN = await rskTxHelper.getBalance(creatorAddress); + expect(finalRskSenderBalanceInWeisBN.eq(expectedRskSenderBalanceInWeisBN)).to.be.true; + + }); + }); }; @@ -907,6 +941,19 @@ const assert2wpBalanceIsUnchanged = async (initial2wpBalances) => { expect(final2wpBalances).to.be.deep.equal(initial2wpBalances); }; +const assert2wpBalancesAfterPegoutFromContract = async (initial2wpBalances, pegoutValueInRbtc) => { + const final2wpBalances = await get2wpBalances(rskTxHelper, btcTxHelper); + + expect(final2wpBalances.federationAddressBalanceInSatoshis).to.be.equal(initial2wpBalances.federationAddressBalanceInSatoshis); + + expect(final2wpBalances.bridgeUtxosBalanceInSatoshis).to.be.equal(initial2wpBalances.bridgeUtxosBalanceInSatoshis); + // When a contract sends funds to the Bridge to try to do a pegout, the pegout is rejected and the Bridge rsk balance is increased by the pegout value + // because there's no refund when a contract is trying to do a pegout. + const expectedBridgeBalanceInWeisBN = initial2wpBalances.bridgeBalanceInWeisBN.add(new BN(btcToWeis(pegoutValueInRbtc))); + + expect(final2wpBalances.bridgeBalanceInWeisBN.eq(expectedBridgeBalanceInWeisBN)).to.be.true; +}; + const assertBtcPeginTxHashProcessed = async (btcPeginTxHash) => { const isBtcTxHashAlreadyProcessed = await bridge.methods.isBtcTxHashAlreadyProcessed(btcPeginTxHash).call(); expect(isBtcTxHashAlreadyProcessed).to.be.true; @@ -982,6 +1029,14 @@ const assertExpectedReleaseRequestRejectedEventIsEmitted = async (rskSenderAddre expect(releaseRequestRejectedEvent).to.be.deep.equal(expectedEvent); }; +const getReleaseRequestRejectedEventFromContractCallTxReceipt = (contractCallTxReceipt) => { + const bridgeTxParser = new BridgeTransactionParser(rskTxHelper.getClient()); + const logData = contractCallTxReceipt.events['0'].raw; + const releaseRequestRejectedAbiElement = bridgeTxParser.jsonInterfaceMap[PEGOUT_EVENTS.RELEASE_REQUEST_REJECTED.signature]; + const releaseRequestRejectedEvent = bridgeTxParser.decodeLog(logData, releaseRequestRejectedAbiElement); + return releaseRequestRejectedEvent; +}; + module.exports = { execute, }; diff --git a/package-lock.json b/package-lock.json index 91186cc0..c93cf16f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,7 @@ "license": "GPL3", "dependencies": { "@rsksmart/bridge-state-data-parser": "^1.2.0", - "@rsksmart/bridge-transaction-parser": "^1.1.3", + "@rsksmart/bridge-transaction-parser": "git+https://git@github.com/rsksmart/bridge-transaction-parser#c7cc7f5077eabf2e818bad3bb13fac9b9aabf477", "@rsksmart/btc-eth-unit-converter": "^1.0.0", "@rsksmart/btc-rsk-derivation": "^0.0.2", "@rsksmart/pmt-builder": "^3.0.0", diff --git a/package.json b/package.json index 10c4ea18..14b0d064 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,7 @@ "license": "GPL3", "dependencies": { "@rsksmart/bridge-state-data-parser": "^1.2.0", - "@rsksmart/bridge-transaction-parser": "^1.1.3", + "@rsksmart/bridge-transaction-parser": "git+https://git@github.com/rsksmart/bridge-transaction-parser#c7cc7f5077eabf2e818bad3bb13fac9b9aabf477", "@rsksmart/btc-eth-unit-converter": "^1.0.0", "@rsksmart/btc-rsk-derivation": "^0.0.2", "@rsksmart/pmt-builder": "^3.0.0",