diff --git a/packages/contracts-rfq/contracts/FastBridgeV2.sol b/packages/contracts-rfq/contracts/FastBridgeV2.sol index 710ca5352f..26a2a1b3d7 100644 --- a/packages/contracts-rfq/contracts/FastBridgeV2.sol +++ b/packages/contracts-rfq/contracts/FastBridgeV2.sol @@ -89,7 +89,7 @@ contract FastBridgeV2 is Admin, MulticallTarget, IFastBridgeV2, IFastBridgeV2Err // Aggregate the read operations from the same storage slot address disputedRelayer = $.proofRelayer; BridgeStatus status = $.status; - uint40 proofBlockTimestamp = $.proofBlockTimestamp; + uint56 proofBlockTimestamp = $.proofBlockTimestamp; // Can only dispute a RELAYER_PROVED transaction within the dispute period if (status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect(); if (_timeSince(proofBlockTimestamp) > DISPUTE_PERIOD) { @@ -100,7 +100,6 @@ contract FastBridgeV2 is Admin, MulticallTarget, IFastBridgeV2, IFastBridgeV2Err $.status = BridgeStatus.REQUESTED; $.proofRelayer = address(0); $.proofBlockTimestamp = 0; - $.proofBlockNumber = 0; emit BridgeProofDisputed(transactionId, disputedRelayer); } @@ -221,7 +220,9 @@ contract FastBridgeV2 is Admin, MulticallTarget, IFastBridgeV2, IFastBridgeV2Err }) ); bytes32 transactionId = keccak256(request); + // Note: the tx status will be updated throughout the tx lifecycle, while destChainId is set once here bridgeTxDetails[transactionId].status = BridgeStatus.REQUESTED; + bridgeTxDetails[transactionId].destChainId = params.dstChainId; emit BridgeRequested({ transactionId: transactionId, @@ -313,8 +314,7 @@ contract FastBridgeV2 is Admin, MulticallTarget, IFastBridgeV2, IFastBridgeV2Err // Update status to RELAYER_PROVED and store the proof details // Note: these are storage writes $.status = BridgeStatus.RELAYER_PROVED; - $.proofBlockTimestamp = uint40(block.timestamp); - $.proofBlockNumber = uint48(block.number); + $.proofBlockTimestamp = uint56(block.timestamp); $.proofRelayer = relayer; emit BridgeProofProvided(transactionId, relayer, destTxHash); @@ -328,7 +328,7 @@ contract FastBridgeV2 is Admin, MulticallTarget, IFastBridgeV2, IFastBridgeV2Err // Aggregate the read operations from the same storage slot address proofRelayer = $.proofRelayer; BridgeStatus status = $.status; - uint40 proofBlockTimestamp = $.proofBlockTimestamp; + uint56 proofBlockTimestamp = $.proofBlockTimestamp; // Can only claim a RELAYER_PROVED transaction after the dispute period if (status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect(); @@ -425,14 +425,14 @@ contract FastBridgeV2 is Admin, MulticallTarget, IFastBridgeV2, IFastBridgeV2Err } /// @notice Calculates time since proof submitted - /// @dev proof.timestamp stores casted uint40(block.timestamp) block timestamps for gas optimization - /// _timeSince(proof) can accomodate rollover case when block.timestamp > type(uint40).max but - /// proof.timestamp < type(uint40).max via unchecked statement + /// @dev proof.timestamp stores casted uint56(block.timestamp) block timestamps for gas optimization + /// _timeSince(proof) can accomodate rollover case when block.timestamp > type(uint56).max but + /// proof.timestamp < type(uint56).max via unchecked statement /// @param proofBlockTimestamp The bridge proof block timestamp /// @return delta Time delta since proof submitted - function _timeSince(uint40 proofBlockTimestamp) internal view returns (uint256 delta) { + function _timeSince(uint56 proofBlockTimestamp) internal view returns (uint256 delta) { unchecked { - delta = uint40(block.timestamp) - proofBlockTimestamp; + delta = uint56(block.timestamp) - proofBlockTimestamp; } } diff --git a/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol b/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol index 8615d750d2..2b3aa54d2b 100644 --- a/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol +++ b/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol @@ -14,8 +14,8 @@ interface IFastBridgeV2 is IFastBridge { struct BridgeTxDetails { BridgeStatus status; - uint40 proofBlockTimestamp; - uint48 proofBlockNumber; + uint32 destChainId; + uint56 proofBlockTimestamp; address proofRelayer; } diff --git a/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol index 060fbb8bab..15f0ac44c5 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol @@ -94,6 +94,19 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { // ══════════════════════════════════════════════════ BRIDGE ═══════════════════════════════════════════════════════ + function checkStatusAndProofAfterBridge(bytes32 txId) public view { + assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) = + fastBridge.bridgeTxDetails(txId); + assertEq(status, IFastBridgeV2.BridgeStatus.REQUESTED); + assertEq(destChainId, DST_CHAIN_ID); + assertEq(proofBlockTimestamp, 0); + assertEq(proofRelayer, address(0)); + (proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId); + assertEq(proofBlockTimestamp, 0); + assertEq(proofRelayer, address(0)); + } + function checkTokenBalancesAfterBridge(address caller) public view { assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(caller), LEFTOVER_BALANCE); @@ -107,7 +120,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: 0, params: tokenParams}); assertEq(fastBridge.senderNonces(userA), 1); assertEq(fastBridge.senderNonces(userB), 0); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterBridge(txId); checkTokenBalancesAfterBridge(userA); } @@ -118,7 +131,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userB, msgValue: 0, params: tokenParams}); assertEq(fastBridge.senderNonces(userA), 1); assertEq(fastBridge.senderNonces(userB), 0); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterBridge(txId); assertEq(srcToken.balanceOf(userA), LEFTOVER_BALANCE + tokenParams.originAmount); checkTokenBalancesAfterBridge(userB); } @@ -140,7 +153,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); assertEq(fastBridge.senderNonces(userA), 2); assertEq(fastBridge.senderNonces(userB), 0); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterBridge(txId); checkEthBalancesAfterBridge(userA); } @@ -156,7 +169,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userB, msgValue: ethParams.originAmount, params: ethParams}); assertEq(fastBridge.senderNonces(userA), 2); assertEq(fastBridge.senderNonces(userB), 0); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterBridge(txId); assertEq(userA.balance, LEFTOVER_BALANCE + ethParams.originAmount); checkEthBalancesAfterBridge(userB); } @@ -175,7 +188,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userB, msgValue: ethParams.originAmount, params: ethParams}); assertEq(fastBridge.senderNonces(userA), 1); assertEq(fastBridge.senderNonces(userB), 1); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterBridge(txId); checkEthBalancesAfterBridge(userB); } @@ -269,14 +282,25 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { // ═══════════════════════════════════════════════════ PROVE ═══════════════════════════════════════════════════════ + function checkStatusAndProofAfterProve(bytes32 txId, address relayer) public view { + assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_PROVED); + (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) = + fastBridge.bridgeTxDetails(txId); + assertEq(status, IFastBridgeV2.BridgeStatus.RELAYER_PROVED); + assertEq(destChainId, DST_CHAIN_ID); + assertEq(proofBlockTimestamp, block.timestamp); + assertEq(proofRelayer, relayer); + (proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId); + assertEq(proofBlockTimestamp, block.timestamp); + assertEq(proofRelayer, relayer); + } + function test_prove_token() public { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); - (uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId); - assertEq(timestamp, block.timestamp); - assertEq(relayer, relayerA); + checkStatusAndProofAfterProve(txId, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -288,9 +312,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"}); prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); - (uint96 timestamp, address relayer) = fastBridge.bridgeProofs(txId); - assertEq(timestamp, block.timestamp); - assertEq(relayer, relayerA); + checkStatusAndProofAfterProve(txId, relayerA); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); } @@ -338,9 +360,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { 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); + checkStatusAndProofAfterProve(txId, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -352,9 +372,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { 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); + checkStatusAndProofAfterProve(txId, relayerA); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); } @@ -365,9 +383,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { 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); + checkStatusAndProofAfterProve(txId, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -379,9 +395,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { 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)); + checkStatusAndProofAfterProve(txId, address(0x1234)); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -392,17 +406,17 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { 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}); + checkStatusAndProofAfterProve(txId, relayerA); expectBridgeProofDisputed(txId, relayerA); dispute(guard, txId); expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"02"}); prove({caller: relayerB, transactionId: txId, destTxHash: hex"02", relayer: relayerA}); + checkStatusAndProofAfterProve(txId, relayerA); expectBridgeProofDisputed(txId, relayerA); 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); + checkStatusAndProofAfterProve(txId, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -414,9 +428,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { 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); + checkStatusAndProofAfterProve(txId, relayerA); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); } @@ -458,6 +470,19 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { // ═══════════════════════════════════════════════════ CLAIM ═══════════════════════════════════════════════════════ + function checkStatusAndProofAfterClaim(bytes32 txId, address relayer, uint256 expectedProofTS) public view { + assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) = + fastBridge.bridgeTxDetails(txId); + assertEq(status, IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + assertEq(destChainId, DST_CHAIN_ID); + assertEq(proofBlockTimestamp, expectedProofTS); + assertEq(proofRelayer, relayer); + (proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId); + assertEq(proofBlockTimestamp, expectedProofTS); + assertEq(proofRelayer, relayer); + } + function checkTokenBalancesAfterClaim(address relayer) public view { uint256 expectedProtocolFees = INITIAL_PROTOCOL_FEES_TOKEN + tokenTx.originFeeAmount; assertEq(fastBridge.protocolFees(address(srcToken)), expectedProtocolFees); @@ -469,11 +494,12 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 1); assertTrue(fastBridge.canClaim(txId, relayerA)); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: relayerA, bridgeTx: tokenTx, to: relayerA}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkTokenBalancesAfterClaim(relayerA); } @@ -482,10 +508,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: caller, bridgeTx: tokenTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkTokenBalancesAfterClaim(relayerA); } @@ -494,10 +521,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: caller, bridgeTx: tokenTx, to: address(0)}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkTokenBalancesAfterClaim(relayerA); } @@ -505,10 +533,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: claimTo}); claim({caller: relayerA, bridgeTx: tokenTx, to: claimTo}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); assertEq(srcToken.balanceOf(relayerA), 0); checkTokenBalancesAfterClaim(claimTo); } @@ -517,10 +546,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 30 days); expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: relayerA, bridgeTx: tokenTx, to: relayerA}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkTokenBalancesAfterClaim(relayerA); } @@ -536,11 +566,12 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(ethTx); bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 1); assertTrue(fastBridge.canClaim(txId, relayerA)); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: relayerA, bridgeTx: ethTx, to: relayerA}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkEthBalancesAfterClaim(relayerA); } @@ -550,10 +581,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(ethTx); bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: caller, bridgeTx: ethTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkEthBalancesAfterClaim(relayerA); } @@ -563,10 +595,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(ethTx); bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: caller, bridgeTx: ethTx, to: address(0)}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkEthBalancesAfterClaim(relayerA); } @@ -575,10 +608,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(ethTx); bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 1); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: claimTo}); claim({caller: relayerA, bridgeTx: ethTx, to: claimTo}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkEthBalancesAfterClaim(claimTo); } @@ -587,10 +621,11 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { bytes32 txId = getTxId(ethTx); bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams}); prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); + uint256 expectedProofTS = block.timestamp; skip(CLAIM_DELAY + 30 days); expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA}); claim({caller: relayerA, bridgeTx: ethTx, to: relayerA}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED); + checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS); checkEthBalancesAfterClaim(relayerA); } @@ -664,13 +699,18 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { // ══════════════════════════════════════════════════ DISPUTE ══════════════════════════════════════════════════════ + function checkStatusAndProofAfterDispute(bytes32 txId) public view { + // Should be identical to a requested tx that was never proven + checkStatusAndProofAfterBridge(txId); + } + function test_dispute_token() public { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"}); expectBridgeProofDisputed({txId: txId, relayer: relayerA}); dispute({caller: guard, txId: txId}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterDispute(txId); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); } @@ -682,7 +722,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY); expectBridgeProofDisputed({txId: txId, relayer: relayerA}); dispute({caller: guard, txId: txId}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterDispute(txId); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount); } @@ -694,7 +734,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"}); expectBridgeProofDisputed({txId: txId, relayer: relayerA}); dispute({caller: guard, txId: txId}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterDispute(txId); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); } @@ -707,7 +747,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(CLAIM_DELAY); expectBridgeProofDisputed({txId: txId, relayer: relayerA}); dispute({caller: guard, txId: txId}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REQUESTED); + checkStatusAndProofAfterDispute(txId); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount); } @@ -764,13 +804,26 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { // ══════════════════════════════════════════════════ REFUND ═══════════════════════════════════════════════════════ + function checkStatusAndProofAfterRefund(bytes32 txId) public view { + assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) = + fastBridge.bridgeTxDetails(txId); + assertEq(status, IFastBridgeV2.BridgeStatus.REFUNDED); + assertEq(destChainId, DST_CHAIN_ID); + assertEq(proofBlockTimestamp, 0); + assertEq(proofRelayer, address(0)); + (proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId); + assertEq(proofBlockTimestamp, 0); + assertEq(proofRelayer, address(0)); + } + function test_refund_token() public { bytes32 txId = getTxId(tokenTx); bridge({caller: userA, msgValue: 0, params: tokenParams}); skip(DEADLINE + 1); expectBridgeDepositRefunded({bridgeParams: tokenParams, txId: txId}); refund({caller: refunder, bridgeTx: tokenTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + checkStatusAndProofAfterRefund(txId); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(userA), LEFTOVER_BALANCE + tokenParams.originAmount); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN); @@ -783,7 +836,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(DEADLINE + 1); expectBridgeDepositRefunded({bridgeParams: tokenParams, txId: txId}); refund({caller: refunder, bridgeTx: tokenTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + checkStatusAndProofAfterRefund(txId); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(userA), LEFTOVER_BALANCE + 2 * tokenParams.originAmount); assertEq(srcToken.balanceOf(userB), LEFTOVER_BALANCE); @@ -796,7 +849,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(DEADLINE + 30 days); expectBridgeDepositRefunded({bridgeParams: tokenParams, txId: txId}); refund({caller: refunder, bridgeTx: tokenTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + checkStatusAndProofAfterRefund(txId); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(userA), LEFTOVER_BALANCE + tokenParams.originAmount); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN); @@ -809,7 +862,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(DEADLINE + PERMISSIONLESS_REFUND_DELAY + 1); expectBridgeDepositRefunded({bridgeParams: tokenParams, txId: txId}); refund({caller: caller, bridgeTx: tokenTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + checkStatusAndProofAfterRefund(txId); assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN); assertEq(srcToken.balanceOf(userA), LEFTOVER_BALANCE + tokenParams.originAmount); assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN); @@ -822,7 +875,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(DEADLINE + 1); expectBridgeDepositRefunded({bridgeParams: ethParams, txId: txId}); refund({caller: refunder, bridgeTx: ethTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + checkStatusAndProofAfterRefund(txId); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); assertEq(userA.balance, LEFTOVER_BALANCE + ethParams.originAmount); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH); @@ -836,7 +889,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(DEADLINE + 1); expectBridgeDepositRefunded({bridgeParams: ethParams, txId: txId}); refund({caller: refunder, bridgeTx: ethTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + checkStatusAndProofAfterRefund(txId); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); assertEq(userA.balance, LEFTOVER_BALANCE + 2 * ethParams.originAmount); assertEq(userB.balance, LEFTOVER_BALANCE); @@ -850,7 +903,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(DEADLINE + 30 days); expectBridgeDepositRefunded({bridgeParams: ethParams, txId: txId}); refund({caller: refunder, bridgeTx: ethTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + checkStatusAndProofAfterRefund(txId); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); assertEq(userA.balance, LEFTOVER_BALANCE + ethParams.originAmount); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH); @@ -864,7 +917,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest { skip(DEADLINE + PERMISSIONLESS_REFUND_DELAY + 1); expectBridgeDepositRefunded({bridgeParams: ethParams, txId: txId}); refund({caller: caller, bridgeTx: ethTx}); - assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED); + checkStatusAndProofAfterRefund(txId); assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH); assertEq(userA.balance, LEFTOVER_BALANCE + ethParams.originAmount); assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH);