From 9742f8f8d3fae9551c011d7644c44be1b02f01d9 Mon Sep 17 00:00:00 2001 From: parodime Date: Thu, 26 Sep 2024 07:19:18 -0400 Subject: [PATCH] prove w/ tx id [SLT-181] (#3169) * prove w/ tx id SLT-181 * +proveOther tests, forge fmt * fmt * fmt --- .../contracts-rfq/contracts/FastBridgeV2.sol | 6 +- .../contracts/interfaces/IFastBridgeV2.sol | 4 +- .../contracts-rfq/test/FastBridgeV2.Src.t.sol | 130 ++++++++++++++++++ 3 files changed, 135 insertions(+), 5 deletions(-) diff --git a/packages/contracts-rfq/contracts/FastBridgeV2.sol b/packages/contracts-rfq/contracts/FastBridgeV2.sol index 4ea19df8d5..99c3568de1 100644 --- a/packages/contracts-rfq/contracts/FastBridgeV2.sol +++ b/packages/contracts-rfq/contracts/FastBridgeV2.sol @@ -177,12 +177,12 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors { /// @inheritdoc IFastBridge function prove(bytes memory request, bytes32 destTxHash) external { - prove(request, destTxHash, msg.sender); + bytes32 transactionId = keccak256(request); + prove(transactionId, destTxHash, msg.sender); } /// @inheritdoc IFastBridgeV2 - function prove(bytes memory request, bytes32 destTxHash, address relayer) public onlyRole(RELAYER_ROLE) { - bytes32 transactionId = keccak256(request); + function prove(bytes32 transactionId, bytes32 destTxHash, address relayer) public onlyRole(RELAYER_ROLE) { // update bridge tx status given proof provided if (bridgeStatuses[transactionId] != BridgeStatus.REQUESTED) revert StatusIncorrect(); bridgeStatuses[transactionId] = BridgeStatus.RELAYER_PROVED; diff --git a/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol b/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol index 63154b6255..0d9cd33fcb 100644 --- a/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol +++ b/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol @@ -10,10 +10,10 @@ interface IFastBridgeV2 is IFastBridge { function relay(bytes memory request, address relayer) external payable; /// @notice Provides proof on origin side that relayer provided funds on destination side of bridge transaction - /// @param request The encoded bridge transaction to prove on origin chain + /// @param transactionId The transaction id associated with the encoded bridge transaction to prove /// @param destTxHash The destination tx hash proving bridge transaction was relayed /// @param relayer The address of the relaying entity which should have control of the origin funds when claimed - function prove(bytes memory request, bytes32 destTxHash, address relayer) external; + function prove(bytes32 transactionId, bytes32 destTxHash, address relayer) external; /// @notice Completes bridge transaction on origin chain by claiming originally deposited capital. /// @notice Can only send funds to the relayer address on the proof. diff --git a/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol index b5177aae6a..6e433a1274 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol @@ -75,6 +75,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2Test { fastBridge.bridge{value: msgValue}(params); } + function prove(address caller, bytes32 transactionId, bytes32 destTxHash, address relayer) public { + vm.prank(caller); + fastBridge.prove(transactionId, destTxHash, relayer); + } + function prove(address caller, IFastBridge.BridgeTransaction memory bridgeTx, bytes32 destTxHash) public { vm.prank(caller); fastBridge.prove(abi.encode(bridgeTx), destTxHash); @@ -353,6 +358,131 @@ contract FastBridgeV2SrcTest is FastBridgeV2Test { prove({caller: caller, bridgeTx: tokenTx, destTxHash: hex"01"}); } + // ════════════════════════════════════════ PROVE OTHER RELAYER ════════════════════════════════════════════ + + function test_proveOther_token() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + (uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId); + assertEq(timestamp, block.timestamp); + assertEq(relayer, relayerA); + assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); + } + + function test_proveOther_eth() public { + // bridge token first to match the nonce + bridge({caller: userA, msgValue: 0, params: tokenParams}); + bytes32 txId = getTxId(ethTx); + bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); + expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + (uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId); + assertEq(timestamp, block.timestamp); + assertEq(relayer, relayerA); + assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); + assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); + } + + // relayer self-proving using tx id, which is capable of proving for another & most tests focus on that angle. + function test_proveOther_self() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); + prove({caller: relayerA, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + (uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId); + assertEq(timestamp, block.timestamp); + assertEq(relayer, relayerA); + assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); + } + + // arbitrary non-privileged address can be asserted as the relayer + function test_proveOther_permless() public { + bytes32 txId = getTxId(tokenTx); + + bridge({caller: userA, msgValue: 0, params: tokenParams}); + expectBridgeProofProvided({txId: txId, relayer: address(0x1234), destTxHash: hex"01"}); + prove({caller: relayerA, transactionId: txId, destTxHash: hex"01", relayer: address(0x1234)}); + (uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId); + assertEq(timestamp, block.timestamp); + assertEq(relayer, address(0x1234)); + assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); + } + + function test_proveOther_reProveAfterDispute() public { + bridge({caller: userA, msgValue: 0, params: tokenParams}); + bytes32 txId = getTxId(ethTx); + bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); + expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + expectBridgeProofDisputed(txId, guard); + dispute(guard, txId); + expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"02"}); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"02", relayer: relayerA}); + expectBridgeProofDisputed(txId, guard); + dispute(guard, txId); + expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"03"}); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"03", relayer: relayerA}); + (uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId); + assertEq(timestamp, block.timestamp); + assertEq(relayer, relayerA); + assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); + } + + // can prove long after relaying as long as status is still good + function test_proveOther_longDelay() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + skip(10 days); + expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); + prove({caller: relayerA, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + (uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId); + assertEq(timestamp, block.timestamp); + assertEq(relayer, relayerA); + assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); + assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); + } + + function test_proveOther_revert_statusProved() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + vm.expectRevert(StatusIncorrect.selector); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"02", relayer: relayerA}); + } + + function test_proveOther_revert_statusClaimed() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + skip(CLAIM_DELAY + 1); + claim({caller: relayerA, bridgeTx: tokenTx, to: relayerA}); + vm.expectRevert(StatusIncorrect.selector); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"02", relayer: relayerA}); + } + + function test_proveOther_revert_statusRefunded() public { + bytes32 txId = getTxId(tokenTx); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + skip(DEADLINE + 1); + refund({caller: refunder, bridgeTx: tokenTx}); + vm.expectRevert(StatusIncorrect.selector); + prove({caller: relayerB, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + } + + function test_proveOther_revert_callerNotAuthed(address caller) public { + bytes32 txId = getTxId(tokenTx); + vm.assume(caller != relayerA && caller != relayerB); + bridge({caller: userA, msgValue: 0, params: tokenParams}); + expectUnauthorized(caller, fastBridge.RELAYER_ROLE()); + prove({caller: caller, transactionId: txId, destTxHash: hex"01", relayer: relayerA}); + } + // ═══════════════════════════════════════════════════ CLAIM ═══════════════════════════════════════════════════════ function checkTokenBalancesAfterClaim(address relayer) public view {