Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

status/proof mapping packing [SLT-186] #3173

Merged
merged 13 commits into from
Sep 27, 2024
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;
parodime marked this conversation as resolved.
Show resolved Hide resolved
/// @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;
}
parodime marked this conversation as resolved.
Show resolved Hide resolved

function bridgeProofs(bytes32 transactionId) public view returns (uint96 timestamp, address relayer) {
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
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;
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved

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;
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved

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;
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved

// 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;
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved

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
}
parodime marked this conversation as resolved.
Show resolved Hide resolved

struct BridgeTxDetails {
BridgeStatus status;
uint40 proofBlockTimestamp;
uint48 proofBlockNumber;
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
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);
ChiTimesChi marked this conversation as resolved.
Show resolved Hide resolved
}
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
Loading