From 239d324c2a3f902cf3b2b993333748c3a681e27d Mon Sep 17 00:00:00 2001 From: Javier Alvarez Date: Wed, 3 Feb 2021 17:51:58 +0100 Subject: [PATCH] Added tests --- contracts/Bridge.sol | 15 -- contracts/BridgeMock.sol | 10 +- contracts/LiquidityBridgeContract.sol | 137 ++++++++---------- contracts/Mock.sol | 4 + test/liquidityBridgeContract.js | 201 +++++++++++++++++--------- 5 files changed, 198 insertions(+), 169 deletions(-) delete mode 100644 contracts/Bridge.sol diff --git a/contracts/Bridge.sol b/contracts/Bridge.sol deleted file mode 100644 index a5a9e8d..0000000 --- a/contracts/Bridge.sol +++ /dev/null @@ -1,15 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity ^0.7.4; - -interface Bridge { - function registerFastBridgeBtcTransaction( - bytes calldata btcTxSerialized, - uint256 height, - bytes calldata pmtSerialized, - bytes32 derivationArgumentsHash, - bytes calldata userRefundBtcAddress, - address payable liquidityBridgeContractAddress, - bytes calldata liquidityProviderBtcAddress, - bool shouldTransferToContract - ) external returns (int256); -} diff --git a/contracts/BridgeMock.sol b/contracts/BridgeMock.sol index b2460c6..e8546e7 100644 --- a/contracts/BridgeMock.sol +++ b/contracts/BridgeMock.sol @@ -1,9 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.7.4; -import "./Bridge.sol"; - -contract BridgeMock is Bridge { +contract BridgeMock { mapping(bytes32 => uint256) private amounts; @@ -16,7 +14,7 @@ contract BridgeMock is Bridge { address payable liquidityBridgeContractAddress, bytes calldata liquidityProviderBtcAddress, bool shouldTransferToContract - ) external override returns (int256) { + ) external returns (int256) { uint256 amount = amounts[derivationArgumentsHash]; amounts[derivationArgumentsHash] = 0; (bool success, ) = liquidityBridgeContractAddress.call{value: amount}(""); @@ -26,8 +24,4 @@ contract BridgeMock is Bridge { function setPegin(bytes32 derivationArgumentsHash) public payable { amounts[derivationArgumentsHash] = msg.value; } - - function getPegin(bytes32 hashe) external view returns (uint256) { - return amounts[hashe]; - } } diff --git a/contracts/LiquidityBridgeContract.sol b/contracts/LiquidityBridgeContract.sol index 061409a..ab9a6ec 100644 --- a/contracts/LiquidityBridgeContract.sol +++ b/contracts/LiquidityBridgeContract.sol @@ -1,18 +1,32 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.7.4; +pragma experimental ABIEncoderV2; -import './Bridge.sol'; +import './BridgeMock.sol'; contract LiquidityBridgeContract { - Bridge bridge; + struct DerivationParams { + bytes fedBtcAddress; + address liquidityProviderRskAddress; + address rskRefundAddress; + address contractAddress; + bytes data; + uint penaltyFee; + uint successFee; + uint gasLimit; + uint nonce; + uint value; + } + + BridgeMock bridge; mapping(address => uint256) private balances; mapping(address => uint256) private deposits; - mapping(bytes32 => address) private callRegistry; + mapping(bytes32 => bool) private callRegistry; mapping(bytes32 => bool) private callSuccess; constructor(address bridgeAddress) { - bridge = Bridge(bridgeAddress); + bridge = BridgeMock(bridgeAddress); } receive() external payable { } @@ -33,111 +47,74 @@ contract LiquidityBridgeContract { return balances[lp]; } - function callForUser( - bytes memory fedBtcAddress, - address liquidityProviderRskAddress, - address payable rskRefundAddress, - address contractAddress, - bytes memory data, - uint penaltyFee, - uint successFee, - uint gasLimit, - uint nonce, - uint value - ) external payable { - require(msg.sender == liquidityProviderRskAddress, "Unauthorized"); - require(deposits[liquidityProviderRskAddress] >= penaltyFee, "Insufficient collateral"); - - balances[liquidityProviderRskAddress] += msg.value; - - require(balances[liquidityProviderRskAddress] >= value, "Insufficient funds"); - - bytes32 derivationHash = hash( - fedBtcAddress, - liquidityProviderRskAddress, - rskRefundAddress, - contractAddress, - data, - penaltyFee, - successFee, - gasLimit, - nonce, - value); - - (bool success, bytes memory ret) = contractAddress.call{gas:gasLimit, value: value}(data); - - callRegistry[derivationHash] = liquidityProviderRskAddress; + function callForUser(DerivationParams memory params) external payable returns (bool) { + require(msg.sender == params.liquidityProviderRskAddress, "Unauthorized"); + require(deposits[params.liquidityProviderRskAddress] >= params.penaltyFee, "Insufficient collateral"); + require(balances[params.liquidityProviderRskAddress] + msg.value >= params.value, "Insufficient funds"); + + bytes32 derivationHash = hash(params); + + (bool success, bytes memory ret) = params.contractAddress.call{gas:params.gasLimit, value: params.value}(params.data); + + balances[params.liquidityProviderRskAddress] += msg.value; + callRegistry[derivationHash] = true; if (success) { - balances[liquidityProviderRskAddress] -= value; + balances[params.liquidityProviderRskAddress] -= params.value; callSuccess[derivationHash] = true; - } + } + return success; } function registerFastBridgeBtcTransaction( + DerivationParams memory params, bytes memory btcRawTransaction, bytes memory partialMerkleTree, uint256 height, bytes memory userBtcRefundAddress, - bytes memory liquidityProviderBtcAddress, - address payable rskRefundAddress, - bytes32 preHash, - uint256 successFee, - uint256 penaltyFee + bytes memory liquidityProviderBtcAddress ) public returns (int256) { - address liquidityProviderRskAddress = callRegistry[preHash]; + bytes32 derivationHash = hash(params); int256 transferredAmount = bridge.registerFastBridgeBtcTransaction( btcRawTransaction, height, partialMerkleTree, - preHash, + derivationHash, userBtcRefundAddress, address(this), liquidityProviderBtcAddress, - liquidityProviderRskAddress != address(0x0) + callRegistry[derivationHash] ); - if (transferredAmount > 0 && liquidityProviderRskAddress != address(0x0)) { - if (callSuccess[preHash]) { - balances[liquidityProviderRskAddress] += uint256(transferredAmount); - callSuccess[preHash] = false; + if (transferredAmount > 0 && callRegistry[derivationHash]) { + if (callSuccess[derivationHash]) { + balances[params.liquidityProviderRskAddress] += uint256(transferredAmount); + callSuccess[derivationHash] = false; } else { - balances[liquidityProviderRskAddress] += successFee; - (bool success, ) = rskRefundAddress.call{value : uint256(transferredAmount) - successFee}(""); + balances[params.liquidityProviderRskAddress] += params.successFee; + (bool success, ) = params.rskRefundAddress.call{value : uint256(transferredAmount) - params.successFee}(""); } - callRegistry[preHash] = address(0x0); + callRegistry[derivationHash] = false; } else if (transferredAmount > 0) { - deposits[liquidityProviderRskAddress] -= penaltyFee; - (bool success, ) = rskRefundAddress.call{value : uint256(transferredAmount) + penaltyFee}(""); + deposits[params.liquidityProviderRskAddress] -= params.penaltyFee; + (bool success, ) = params.rskRefundAddress.call{value : uint256(transferredAmount) + params.penaltyFee}(""); } return transferredAmount; } - function hash( - bytes memory fedBtcAddress, - address liquidityProviderRskAddress, - address rskRefundAddress, - address callContract, - bytes memory callContractArguments, - uint penaltyFee, - uint successFee, - uint gasLimit, - uint nonce , - uint valueToTransfer - ) public pure returns (bytes32) { - + function hash(DerivationParams memory params) public pure returns (bytes32) { return keccak256(abi.encode( - fedBtcAddress, - liquidityProviderRskAddress, - rskRefundAddress, - callContract, - callContractArguments, - penaltyFee, - successFee, - gasLimit, - nonce, - valueToTransfer + params.fedBtcAddress, + params.liquidityProviderRskAddress, + params.rskRefundAddress, + params.contractAddress, + params.data, + params.penaltyFee, + params.successFee, + params.gasLimit, + params.nonce, + params.value )); } } diff --git a/contracts/Mock.sol b/contracts/Mock.sol index 23f5657..629dafd 100644 --- a/contracts/Mock.sol +++ b/contracts/Mock.sol @@ -13,4 +13,8 @@ contract Mock { function check() external view returns (int){ return status; } + + function fail() external pure { + require(false, "error"); + } } diff --git a/test/liquidityBridgeContract.js b/test/liquidityBridgeContract.js index bfad2c6..4dd5a91 100644 --- a/test/liquidityBridgeContract.js +++ b/test/liquidityBridgeContract.js @@ -34,78 +34,64 @@ contract('LiquidityBridgeContract', async accounts => { let liquidityProviderBtcAddress = '0x004'; let rskRefundAddress = web3.eth.currentProvider.addresses[2]; let destAddr = web3.eth.currentProvider.addresses[1]; - let previousUserBalance = await web3.eth.getBalance(destAddr); - let fedBtcAddress = '0x001'; + let initialUserBalance = await web3.eth.getBalance(destAddr); + let fedBtcAddress = '0x01'; let liquidityProviderRskAddress = web3.eth.currentProvider.getAddress(); - let previousLPBalance = await instance.getBalance(liquidityProviderRskAddress); - let data = '0x0'; + let initialLPBalance = await instance.getBalance(liquidityProviderRskAddress); + let data = '0x00'; let penaltyFee = 0; let successFee = 1; let gasLimit = 3; let nonce = 0; let peginAmount = val + successFee; + let derivationParams = [fedBtcAddress, liquidityProviderRskAddress, rskRefundAddress, destAddr, data, penaltyFee, successFee, gasLimit, nonce, val]; + let encodedParams = await web3.eth.abi.encodeParameters( ['bytes','address','address','address','bytes','int','int','int','int','int'], - [fedBtcAddress, liquidityProviderRskAddress, rskRefundAddress, destAddr, data, penaltyFee, successFee, gasLimit, nonce, val] + derivationParams ); let preHash = await web3.utils.keccak256(encodedParams); await bridgeMockInstance.setPegin(preHash, {value : peginAmount}); await instance.callForUser( - fedBtcAddress, - liquidityProviderRskAddress, - rskRefundAddress, - destAddr, - data, - penaltyFee, - successFee, - gasLimit, - nonce, - val, + derivationParams, {value : val} ); currentLPBalance = await instance.getBalance(liquidityProviderRskAddress); - newUserBalance = await web3.eth.getBalance(destAddr); - previousLBCBalance = await web3.eth.getBalance(instance.address); + finalUserBalance = await web3.eth.getBalance(destAddr); + initialLBCBalance = await web3.eth.getBalance(instance.address); - assert.equal(currentLPBalance.toNumber(), previousLPBalance.toNumber()); + assert.equal(currentLPBalance.toNumber(), initialLPBalance.toNumber()); amount = await instance.registerFastBridgeBtcTransaction.call( + derivationParams, btcRawTransaction, partialMerkleTree, height, userBtcRefundAddress, - liquidityProviderBtcAddress, - rskRefundAddress, - preHash, - successFee, - penaltyFee + liquidityProviderBtcAddress ); await instance.registerFastBridgeBtcTransaction( + derivationParams, btcRawTransaction, partialMerkleTree, height, userBtcRefundAddress, - liquidityProviderBtcAddress, - rskRefundAddress, - preHash, - successFee, - penaltyFee + liquidityProviderBtcAddress ); - newLPBalance = await instance.getBalance(liquidityProviderRskAddress); - newLBCBalance = await web3.eth.getBalance(instance.address); + finalLPBalance = await instance.getBalance(liquidityProviderRskAddress); + finalLBCBalance = await web3.eth.getBalance(instance.address); assert.equal(peginAmount, amount.toNumber()); - assert.equal(val, parseInt(newUserBalance) - parseInt(previousUserBalance)); - assert.equal(peginAmount, parseInt(newLBCBalance) - parseInt(previousLBCBalance)); - assert.equal(peginAmount, newLPBalance.toNumber() - previousLPBalance.toNumber()); + assert.equal(val, parseInt(finalUserBalance) - parseInt(initialUserBalance)); + assert.equal(peginAmount, parseInt(finalLBCBalance) - parseInt(initialLBCBalance)); + assert.equal(peginAmount, finalLPBalance.toNumber() - initialLPBalance.toNumber()); }); - it ('should call contract for user', async () => { let val = 0; let btcRawTransaction = '0x101'; @@ -114,7 +100,7 @@ contract('LiquidityBridgeContract', async accounts => { let userBtcRefundAddress = '0x003'; let liquidityProviderBtcAddress = '0x004'; let destAddr = mock.address; - let fedBtcAddress = '0x001'; + let fedBtcAddress = '0x01'; let liquidityProviderRskAddress = web3.eth.currentProvider.getAddress(); let rskRefundAddress = web3.eth.currentProvider.addresses[2]; let penaltyFee = 0; @@ -122,11 +108,12 @@ contract('LiquidityBridgeContract', async accounts => { let gasLimit = 50000; let nonce = 0; let data = web3.eth.abi.encodeFunctionCall(mock.abi[0], ['12']); - let previousLPBalance = await instance.getBalance(liquidityProviderRskAddress); + let initialLPBalance = await instance.getBalance(liquidityProviderRskAddress); let peginAmount = val + successFee; + let derivationParams = [fedBtcAddress, liquidityProviderRskAddress, rskRefundAddress, destAddr, data, penaltyFee, successFee, gasLimit, nonce, val]; let encodedParams = await web3.eth.abi.encodeParameters( ['bytes','address','address','address','bytes','int','int','int','int','int'], - [fedBtcAddress, liquidityProviderRskAddress, rskRefundAddress, destAddr, data, penaltyFee, successFee, gasLimit, nonce, val] + derivationParams ); let preHash = await web3.utils.keccak256(encodedParams); @@ -134,57 +121,139 @@ contract('LiquidityBridgeContract', async accounts => { await mock.set(0); await instance.callForUser( - fedBtcAddress, - liquidityProviderRskAddress, - rskRefundAddress, - destAddr, - data, - penaltyFee, - successFee, - gasLimit, - nonce, - val + derivationParams, + {value : val} ); - previousLBCBalance = await web3.eth.getBalance(instance.address); + initialLBCBalance = await web3.eth.getBalance(instance.address); currentLPBalance = await instance.getBalance(liquidityProviderRskAddress); - assert.equal(currentLPBalance.toNumber(), previousLPBalance.toNumber()); + assert.equal(currentLPBalance.toNumber(), initialLPBalance.toNumber()); amount = await instance.registerFastBridgeBtcTransaction.call( + derivationParams, btcRawTransaction, partialMerkleTree, height, userBtcRefundAddress, - liquidityProviderBtcAddress, - rskRefundAddress, - preHash, - successFee, - penaltyFee + liquidityProviderBtcAddress ); await instance.registerFastBridgeBtcTransaction( + derivationParams, btcRawTransaction, partialMerkleTree, height, userBtcRefundAddress, - liquidityProviderBtcAddress, - rskRefundAddress, - preHash, - successFee, - penaltyFee + liquidityProviderBtcAddress ); - newLPBalance = await instance.getBalance(liquidityProviderRskAddress); - newLBCBalance = await web3.eth.getBalance(instance.address); + finalLPBalance = await instance.getBalance(liquidityProviderRskAddress); + finalLBCBalance = await web3.eth.getBalance(instance.address); - assert.equal(peginAmount, amount); assert.equal(peginAmount, amount.toNumber()); - assert.equal(peginAmount, newLPBalance.toNumber() - previousLPBalance.toNumber()); - assert.equal(peginAmount, parseInt(newLBCBalance) - parseInt(previousLBCBalance)); + assert.equal(peginAmount, finalLPBalance.toNumber() - initialLPBalance.toNumber()); + assert.equal(peginAmount, parseInt(finalLBCBalance) - parseInt(initialLBCBalance)); + + finalValue = await mock.check(); + + assert.equal(12, finalValue.toNumber()); + }); + + it ('should refund user on failed call', async () => { + let val = 2; + let btcRawTransaction = '0x101'; + let partialMerkleTree = '0x202'; + let height = 100; + let userBtcRefundAddress = '0x003'; + let liquidityProviderBtcAddress = '0x004'; + let destAddr = mock.address; + let fedBtcAddress = '0x01'; + let liquidityProviderRskAddress = web3.eth.currentProvider.getAddress(); + let rskRefundAddress = web3.eth.currentProvider.addresses[2]; + let penaltyFee = 1; + let successFee = 1; + let gasLimit = 50000; + let nonce = 0; + let data = web3.eth.abi.encodeFunctionCall(mock.abi[2], []); + let initialLPBalance = await instance.getBalance(liquidityProviderRskAddress); + let peginAmount = val + successFee; + let derivationParams = [fedBtcAddress, liquidityProviderRskAddress, rskRefundAddress, destAddr, data, penaltyFee, successFee, gasLimit, nonce, val]; + let encodedParams = await web3.eth.abi.encodeParameters( + ['bytes','address','address','address','bytes','int','int','int','int','int'], + derivationParams + ); + let preHash = await web3.utils.keccak256(encodedParams); + let initialUserBalance = await web3.eth.getBalance(rskRefundAddress); + + await bridgeMockInstance.setPegin(preHash, {value : peginAmount}); + + await instance.callForUser( + derivationParams, + {value : val} + ); + + currentLPBalance = await instance.getBalance(liquidityProviderRskAddress); + + assert.equal(val, parseInt(currentLPBalance) - parseInt(initialLPBalance)); + + await instance.registerFastBridgeBtcTransaction( + derivationParams, + btcRawTransaction, + partialMerkleTree, + height, + userBtcRefundAddress, + liquidityProviderBtcAddress + ); + + finalUserBalance = await web3.eth.getBalance(rskRefundAddress); + finalLPBalance = await instance.getBalance(liquidityProviderRskAddress); + + assert.equal(successFee + val, finalLPBalance.toNumber() - initialLPBalance.toNumber()); + assert.equal(peginAmount - successFee, parseInt(finalUserBalance) - parseInt(initialUserBalance)); + }); + + it ('should refund user on missed call', async () => { + let val = 2; + let btcRawTransaction = '0x101'; + let partialMerkleTree = '0x202'; + let height = 100; + let userBtcRefundAddress = '0x003'; + let liquidityProviderBtcAddress = '0x004'; + let destAddr = mock.address; + let fedBtcAddress = '0x01'; + let liquidityProviderRskAddress = web3.eth.currentProvider.getAddress(); + let rskRefundAddress = web3.eth.currentProvider.addresses[2]; + let penaltyFee = 1; + let successFee = 1; + let gasLimit = 50000; + let nonce = 0; + let data = web3.eth.abi.encodeFunctionCall(mock.abi[2], []); + let initialLPDeposit = await instance.getDeposit(liquidityProviderRskAddress); + let peginAmount = val + successFee; + let derivationParams = [fedBtcAddress, liquidityProviderRskAddress, rskRefundAddress, destAddr, data, penaltyFee, successFee, gasLimit, nonce, val]; + let encodedParams = await web3.eth.abi.encodeParameters( + ['bytes','address','address','address','bytes','int','int','int','int','int'], + derivationParams + ); + let preHash = await web3.utils.keccak256(encodedParams); + let initialUserBalance = await web3.eth.getBalance(rskRefundAddress); + + await bridgeMockInstance.setPegin(preHash, {value : peginAmount}); + + await instance.registerFastBridgeBtcTransaction( + derivationParams, + btcRawTransaction, + partialMerkleTree, + height, + userBtcRefundAddress, + liquidityProviderBtcAddress + ); - currentValue = await mock.check(); + finalUserBalance = await web3.eth.getBalance(rskRefundAddress); + finalLPDeposit = await instance.getDeposit(liquidityProviderRskAddress); - assert.equal(12, currentValue.toNumber()); + assert.equal(initialLPDeposit.toNumber() - penaltyFee, finalLPDeposit.toNumber()); + assert.equal(peginAmount + penaltyFee, parseInt(finalUserBalance) - parseInt(initialUserBalance)); }); });