Skip to content

Commit

Permalink
status/proof mapping packing [SLT-186] (#3173)
Browse files Browse the repository at this point in the history
* status/proof mapping packing [SLT-186]

* fix BridgeProofs def to use struct

* WIP - evaluating ProofDetail struct approach

* fix: overflow in parity tests

* refactor BridgeTxDetails w/o addtl ProofDetail struct

* master merge conflicts

* restore bad lint warning

* switch to fully qualified bridgeTxDetails instead of storage refs

* rearrange for lint (120 char line)

* test fixes

* code cleanup

---------

Co-authored-by: ChiTimesChi <88190723+ChiTimesChi@users.noreply.github.com>
Co-authored-by: jw <jw@jw.com>
  • Loading branch information
3 people authored Sep 27, 2024
1 parent 5fd9bac commit 0240714
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 107 deletions.
85 changes: 44 additions & 41 deletions packages/contracts-rfq/contracts/FastBridgeV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,18 +24,8 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors {
/// @notice Minimum deadline period to relay a requested bridge transaction
uint256 public constant MIN_DEADLINE_PERIOD = 30 minutes;

enum BridgeStatus {
NULL, // doesn't exist yet
REQUESTED,
RELAYER_PROVED,
RELAYER_CLAIMED,
REFUNDED
}

/// @notice Status of the bridge tx on origin chain
mapping(bytes32 => BridgeStatus) public bridgeStatuses;
/// @notice Proof of relayed bridge tx on origin chain
mapping(bytes32 => BridgeProof) public bridgeProofs;
mapping(bytes32 => BridgeTxDetails) public bridgeTxDetails;
/// @notice Relay details on destination chain
mapping(bytes32 => BridgeRelay) public bridgeRelayDetails;

Expand All @@ -44,6 +34,15 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors {
// @dev the block the contract was deployed at
uint256 public immutable deployBlock;

function bridgeStatuses(bytes32 transactionId) public view returns (BridgeStatus status) {
return bridgeTxDetails[transactionId].status;
}

function bridgeProofs(bytes32 transactionId) public view returns (uint96 timestamp, address relayer) {
timestamp = bridgeTxDetails[transactionId].proofBlockTimestamp;
relayer = bridgeTxDetails[transactionId].proofRelayer;
}

constructor(address _owner) Admin(_owner) {
deployBlock = block.number;
}
Expand Down Expand Up @@ -111,7 +110,7 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors {
})
);
bytes32 transactionId = keccak256(request);
bridgeStatuses[transactionId] = BridgeStatus.REQUESTED;
bridgeTxDetails[transactionId].status = BridgeStatus.REQUESTED;

emit BridgeRequested(
transactionId,
Expand Down Expand Up @@ -194,32 +193,32 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors {
/// @inheritdoc IFastBridgeV2
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;
// overflow ok
bridgeProofs[transactionId] = BridgeProof({timestamp: uint96(block.timestamp), relayer: relayer});
if (bridgeTxDetails[transactionId].status != BridgeStatus.REQUESTED) revert StatusIncorrect();
bridgeTxDetails[transactionId].status = BridgeStatus.RELAYER_PROVED;
bridgeTxDetails[transactionId].proofBlockTimestamp = uint40(block.timestamp);
bridgeTxDetails[transactionId].proofBlockNumber = uint48(block.number);
bridgeTxDetails[transactionId].proofRelayer = relayer;

emit BridgeProofProvided(transactionId, relayer, destTxHash);
}

/// @notice Calculates time since proof submitted
/// @dev proof.timestamp stores casted uint96(block.timestamp) block timestamps for gas optimization
/// _timeSince(proof) can accomodate rollover case when block.timestamp > type(uint96).max but
/// proof.timestamp < type(uint96).max via unchecked statement
/// @param proof The bridge proof
/// @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
/// @param proofBlockTimestamp The bridge proof block timestamp
/// @return delta Time delta since proof submitted
function _timeSince(BridgeProof memory proof) internal view returns (uint256 delta) {
function _timeSince(uint40 proofBlockTimestamp) internal view returns (uint256 delta) {
unchecked {
delta = uint96(block.timestamp) - proof.timestamp;
delta = uint40(block.timestamp) - proofBlockTimestamp;
}
}

/// @inheritdoc IFastBridge
function canClaim(bytes32 transactionId, address relayer) external view returns (bool) {
if (bridgeStatuses[transactionId] != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect();
BridgeProof memory proof = bridgeProofs[transactionId];
if (proof.relayer != relayer) revert SenderIncorrect();
return _timeSince(proof) > DISPUTE_PERIOD;
if (bridgeTxDetails[transactionId].status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect();
if (bridgeTxDetails[transactionId].proofRelayer != relayer) revert SenderIncorrect();
return _timeSince(bridgeTxDetails[transactionId].proofBlockTimestamp) > DISPUTE_PERIOD;
}

/// @inheritdoc IFastBridgeV2
Expand All @@ -233,20 +232,20 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors {
BridgeTransaction memory transaction = getBridgeTransaction(request);

// update bridge tx status if able to claim origin collateral
if (bridgeStatuses[transactionId] != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect();

BridgeProof memory proof = bridgeProofs[transactionId];
if (bridgeTxDetails[transactionId].status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect();

// if "to" is zero addr, permissionlessly send funds to proven relayer
if (to == address(0)) {
to = proof.relayer;
} else if (proof.relayer != msg.sender) {
to = bridgeTxDetails[transactionId].proofRelayer;
} else if (bridgeTxDetails[transactionId].proofRelayer != msg.sender) {
revert SenderIncorrect();
}

if (_timeSince(proof) <= DISPUTE_PERIOD) revert DisputePeriodNotPassed();
if (_timeSince(bridgeTxDetails[transactionId].proofBlockTimestamp) <= DISPUTE_PERIOD) {
revert DisputePeriodNotPassed();
}

bridgeStatuses[transactionId] = BridgeStatus.RELAYER_CLAIMED;
bridgeTxDetails[transactionId].status = BridgeStatus.RELAYER_CLAIMED;

// update protocol fees if origin fee amount exists
if (transaction.originFeeAmount > 0) protocolFees[transaction.originToken] += transaction.originFeeAmount;
Expand All @@ -256,17 +255,21 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors {
uint256 amount = transaction.originAmount;
token.universalTransfer(to, amount);

emit BridgeDepositClaimed(transactionId, proof.relayer, to, token, amount);
emit BridgeDepositClaimed(transactionId, bridgeTxDetails[transactionId].proofRelayer, to, token, amount);
}

/// @inheritdoc IFastBridge
function dispute(bytes32 transactionId) external onlyRole(GUARD_ROLE) {
if (bridgeStatuses[transactionId] != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect();
if (_timeSince(bridgeProofs[transactionId]) > DISPUTE_PERIOD) revert DisputePeriodPassed();
if (bridgeTxDetails[transactionId].status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect();
if (_timeSince(bridgeTxDetails[transactionId].proofBlockTimestamp) > DISPUTE_PERIOD) {
revert DisputePeriodPassed();
}

// @dev relayer gets slashed effectively if dest relay has gone thru
bridgeStatuses[transactionId] = BridgeStatus.REQUESTED;
delete bridgeProofs[transactionId];
bridgeTxDetails[transactionId].status = BridgeStatus.REQUESTED;
bridgeTxDetails[transactionId].proofRelayer = address(0);
bridgeTxDetails[transactionId].proofBlockTimestamp = 0;
bridgeTxDetails[transactionId].proofBlockNumber = 0;

emit BridgeProofDisputed(transactionId, msg.sender);
}
Expand All @@ -275,10 +278,10 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors {
function refund(bytes memory request) external {
bytes32 transactionId = keccak256(request);

if (bridgeStatuses[transactionId] != BridgeStatus.REQUESTED) revert StatusIncorrect();

BridgeTransaction memory transaction = getBridgeTransaction(request);

if (bridgeTxDetails[transactionId].status != BridgeStatus.REQUESTED) revert StatusIncorrect();

if (hasRole(REFUNDER_ROLE, msg.sender)) {
// Refunder can refund if deadline has passed
if (block.timestamp <= transaction.deadline) revert DeadlineNotExceeded();
Expand All @@ -288,7 +291,7 @@ contract FastBridgeV2 is Admin, IFastBridgeV2, IFastBridgeV2Errors {
}

// if all checks passed, set to REFUNDED status
bridgeStatuses[transactionId] = BridgeStatus.REFUNDED;
bridgeTxDetails[transactionId].status = BridgeStatus.REFUNDED;

// transfer origin collateral back to original sender
address to = transaction.originSender;
Expand Down
26 changes: 26 additions & 0 deletions packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,21 @@ pragma solidity ^0.8.20;
import {IFastBridge} from "./IFastBridge.sol";

interface IFastBridgeV2 is IFastBridge {
enum BridgeStatus {
NULL, // doesn't exist yet
REQUESTED,
RELAYER_PROVED,
RELAYER_CLAIMED,
REFUNDED
}

struct BridgeTxDetails {
BridgeStatus status;
uint40 proofBlockTimestamp;
uint48 proofBlockNumber;
address proofRelayer;
}

struct BridgeRelay {
uint48 blockNumber;
uint48 blockTimestamp;
Expand All @@ -29,4 +44,15 @@ interface IFastBridgeV2 is IFastBridge {
/// @param transactionId The ID of the transaction to check
/// @return True if the transaction has been relayed, false otherwise
function bridgeRelays(bytes32 transactionId) external view returns (bool);

/// @notice Returns the status of a bridge transaction
/// @param transactionId The ID of the bridge transaction
/// @return BridgeStatus Status of the bridge transaction
function bridgeStatuses(bytes32 transactionId) external view returns (BridgeStatus);

/// @notice Returns the timestamp and relayer of a bridge proof
/// @param transactionId The ID of the bridge transaction
/// @return timestamp The timestamp of the bridge proof
/// @return relayer The relayer address of the bridge proof
function bridgeProofs(bytes32 transactionId) external view returns (uint96 timestamp, address relayer);
}
34 changes: 19 additions & 15 deletions packages/contracts-rfq/test/FastBridge.t.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;
// solhint-disable

import "forge-std/Test.sol";
import "forge-std/console2.sol";
Expand Down Expand Up @@ -51,6 +52,19 @@ contract FastBridgeTest is Test {
ethUSDC.mint(dstUser, 100 * 10 ** 6);
}

function assertCorrectProof(
bytes32 transactionId,
uint256 expectedTimestamp,
address expectedRelayer
)
internal
virtual
{
(uint96 timestamp, address relayer) = fastBridge.bridgeProofs(transactionId);
assertEq(timestamp, uint96(expectedTimestamp));
assertEq(relayer, expectedRelayer);
}

function _getBridgeRequestAndId(
uint256 chainId,
uint256 currentNonce,
Expand Down Expand Up @@ -1349,9 +1363,7 @@ contract FastBridgeTest is Test {
fastBridge.prove(request, fakeTxnHash);

// We check if the bridge transaction proof timestamp is set to the timestamp at which the proof was provided
(uint96 _timestamp, address _oldRelayer) = fastBridge.bridgeProofs(transactionId);
assertEq(_timestamp, uint96(block.timestamp));
assertEq(_oldRelayer, relayer);
assertCorrectProof(transactionId, block.timestamp, relayer);

// We check if the bridge status is RELAYER_PROVED
assertEq(uint256(fastBridge.bridgeStatuses(transactionId)), uint256(FastBridge.BridgeStatus.RELAYER_PROVED));
Expand Down Expand Up @@ -1383,9 +1395,7 @@ contract FastBridgeTest is Test {
fastBridge.prove(request, fakeTxnHash);

// We check if the bridge transaction proof timestamp is set to the timestamp at which the proof was provided
(uint96 _timestamp, address _oldRelayer) = fastBridge.bridgeProofs(transactionId);
assertEq(_timestamp, uint96(block.timestamp));
assertEq(_oldRelayer, relayer);
assertCorrectProof(transactionId, block.timestamp, relayer);

// We stop a prank to contain within test
vm.stopPrank();
Expand Down Expand Up @@ -1414,9 +1424,7 @@ contract FastBridgeTest is Test {
fastBridge.prove(request, fakeTxnHash);

// We check if the bridge transaction proof timestamp is set to the timestamp at which the proof was provided
(uint96 _timestamp, address _oldRelayer) = fastBridge.bridgeProofs(transactionId);
assertEq(_timestamp, uint96(block.timestamp));
assertEq(_oldRelayer, relayer);
assertCorrectProof(transactionId, block.timestamp, relayer);

// We stop a prank to contain within test
vm.stopPrank();
Expand Down Expand Up @@ -1713,10 +1721,8 @@ contract FastBridgeTest is Test {
fastBridge.dispute(transactionId);

// check status and proofs updated
(uint96 _timestamp, address _oldRelayer) = fastBridge.bridgeProofs(transactionId);
assertEq(uint256(fastBridge.bridgeStatuses(transactionId)), uint256(FastBridge.BridgeStatus.REQUESTED));
assertEq(_timestamp, 0);
assertEq(_oldRelayer, address(0));
assertCorrectProof(transactionId, 0, address(0));

// We stop a prank to contain within test
vm.stopPrank();
Expand All @@ -1739,10 +1745,8 @@ contract FastBridgeTest is Test {
fastBridge.dispute(transactionId);

// check status and proofs updated
(uint96 _timestamp, address _oldRelayer) = fastBridge.bridgeProofs(transactionId);
assertEq(uint256(fastBridge.bridgeStatuses(transactionId)), uint256(FastBridge.BridgeStatus.REQUESTED));
assertEq(_timestamp, 0);
assertEq(_oldRelayer, address(0));
assertCorrectProof(transactionId, 0, address(0));

// We stop a prank to contain within test
vm.stopPrank();
Expand Down
Loading

0 comments on commit 0240714

Please sign in to comment.