Skip to content

Commit

Permalink
Merge 23f6c4c into 599006b
Browse files Browse the repository at this point in the history
  • Loading branch information
Defi-Moses authored Sep 20, 2024
2 parents 599006b + 23f6c4c commit 739a35a
Show file tree
Hide file tree
Showing 111 changed files with 12,015 additions and 5,551 deletions.
1 change: 1 addition & 0 deletions lerna.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
"useWorkspaces": true,
"packages": [
"packages/*",
"packages/rfq-indexer/*",
"docs/*"
],
"version": "independent"
Expand Down
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
"workspaces": {
"packages": [
"packages/*",
"packages/rfq-indexer/*",
"docs/*"
],
"nohoist": [
Expand Down Expand Up @@ -73,7 +74,8 @@
"ts-mocha": "^10.0.0",
"typedoc": "^0.23.24",
"typedoc-plugin-markdown": "^3.14.0",
"typescript": "^5.0.4"
"typescript": "^5.0.4",
"wagmi": "^2.12.12"
},
"dependencies": {
"@changesets/cli": "2.22.0",
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-rfq/contracts/Admin.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
pragma solidity ^0.8.20;

import {AccessControlEnumerable} from "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";

Expand Down
287 changes: 287 additions & 0 deletions packages/contracts-rfq/contracts/FastBridgeV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "./libs/Errors.sol";
import {UniversalTokenLib} from "./libs/UniversalToken.sol";

import {Admin} from "./Admin.sol";
import {IFastBridge} from "./interfaces/IFastBridge.sol";
import {IFastBridgeV2} from "./interfaces/IFastBridgeV2.sol";

contract FastBridgeV2 is Admin, IFastBridgeV2 {
using SafeERC20 for IERC20;
using UniversalTokenLib for address;

/// @notice Dispute period for relayed transactions
uint256 public constant DISPUTE_PERIOD = 30 minutes;

/// @notice Delay for a transaction after which it could be permisionlessly refunded
uint256 public constant REFUND_DELAY = 7 days;

/// @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;
/// @notice Whether bridge has been relayed on destination chain
mapping(bytes32 => bool) public bridgeRelays;

/// @dev to prevent replays
uint256 public nonce;
// @dev the block the contract was deployed at
uint256 public immutable deployBlock;

constructor(address _owner) Admin(_owner) {
deployBlock = block.number;
}

/// @notice Pulls a requested token from the user to the requested recipient.
/// @dev Be careful of re-entrancy issues when msg.value > 0 and recipient != address(this)
function _pullToken(address recipient, address token, uint256 amount) internal returns (uint256 amountPulled) {
if (token != UniversalTokenLib.ETH_ADDRESS) {
token.assertIsContract();
// Record token balance before transfer
amountPulled = IERC20(token).balanceOf(recipient);
// Token needs to be pulled only if msg.value is zero
// This way user can specify WETH as the origin asset
IERC20(token).safeTransferFrom(msg.sender, recipient, amount);
// Use the difference between the recorded balance and the current balance as the amountPulled
amountPulled = IERC20(token).balanceOf(recipient) - amountPulled;
} else {
// Otherwise, we need to check that ETH amount matches msg.value
if (amount != msg.value) revert MsgValueIncorrect();
// Transfer value to recipient if not this address
if (recipient != address(this)) token.universalTransfer(recipient, amount);
// We will forward msg.value in the external call later, if recipient is not this contract
amountPulled = msg.value;
}
}

/// @inheritdoc IFastBridge
function getBridgeTransaction(bytes memory request) public pure returns (BridgeTransaction memory) {
return abi.decode(request, (BridgeTransaction));
}

/// @inheritdoc IFastBridge
function bridge(BridgeParams memory params) external payable {
// check bridge params
if (params.dstChainId == block.chainid) revert ChainIncorrect();
if (params.originAmount == 0 || params.destAmount == 0) revert AmountIncorrect();
if (params.originToken == address(0) || params.destToken == address(0)) revert ZeroAddress();
if (params.deadline < block.timestamp + MIN_DEADLINE_PERIOD) revert DeadlineTooShort();

// transfer tokens to bridge contract
// @dev use returned originAmount in request in case of transfer fees
uint256 originAmount = _pullToken(address(this), params.originToken, params.originAmount);

// track amount of origin token owed to protocol
uint256 originFeeAmount;
if (protocolFeeRate > 0) originFeeAmount = (originAmount * protocolFeeRate) / FEE_BPS;
originAmount -= originFeeAmount; // remove from amount used in request as not relevant for relayers

// set status to requested
bytes memory request = abi.encode(
BridgeTransaction({
originChainId: uint32(block.chainid),
destChainId: params.dstChainId,
originSender: params.sender,
destRecipient: params.to,
originToken: params.originToken,
destToken: params.destToken,
originAmount: originAmount,
destAmount: params.destAmount,
originFeeAmount: originFeeAmount,
sendChainGas: params.sendChainGas,
deadline: params.deadline,
nonce: nonce++ // increment nonce on every bridge
})
);
bytes32 transactionId = keccak256(request);
bridgeStatuses[transactionId] = BridgeStatus.REQUESTED;

emit BridgeRequested(
transactionId,
params.sender,
request,
params.dstChainId,
params.originToken,
params.destToken,
originAmount,
params.destAmount,
params.sendChainGas
);
}

/// @inheritdoc IFastBridge
function relay(bytes memory request) external payable {
relay(request, msg.sender);
}

/// @inheritdoc IFastBridgeV2
function relay(bytes memory request, address relayer) public payable {
bytes32 transactionId = keccak256(request);
BridgeTransaction memory transaction = getBridgeTransaction(request);
if (transaction.destChainId != uint32(block.chainid)) revert ChainIncorrect();

// check haven't exceeded deadline for relay to happen
if (block.timestamp > transaction.deadline) revert DeadlineExceeded();

// mark bridge transaction as relayed
if (bridgeRelays[transactionId]) revert TransactionRelayed();
bridgeRelays[transactionId] = true;

// transfer tokens to recipient on destination chain and gas rebate if requested
address to = transaction.destRecipient;
address token = transaction.destToken;
uint256 amount = transaction.destAmount;

uint256 rebate = chainGasAmount;
if (!transaction.sendChainGas) {
// forward erc20
rebate = 0;
_pullToken(to, token, amount);
} else if (token == UniversalTokenLib.ETH_ADDRESS) {
// lump in gas rebate into amount in native gas token
_pullToken(to, token, amount + rebate);
} else {
// forward erc20 then forward gas rebate in native gas token
_pullToken(to, token, amount);
_pullToken(to, UniversalTokenLib.ETH_ADDRESS, rebate);
}

emit BridgeRelayed(
transactionId,
relayer,
to,
transaction.originChainId,
transaction.originToken,
transaction.destToken,
transaction.originAmount,
transaction.destAmount,
rebate
);
}

/// @inheritdoc IFastBridge
function prove(bytes memory request, bytes32 destTxHash) external {
prove(request, destTxHash, msg.sender);
}

/// @inheritdoc IFastBridgeV2
function prove(bytes memory request, bytes32 destTxHash, address relayer) public onlyRole(RELAYER_ROLE) {
bytes32 transactionId = keccak256(request);
// update bridge tx status given proof provided
if (bridgeStatuses[transactionId] != BridgeStatus.REQUESTED) revert StatusIncorrect();
bridgeStatuses[transactionId] = BridgeStatus.RELAYER_PROVED;
bridgeProofs[transactionId] = BridgeProof({timestamp: uint96(block.timestamp), relayer: relayer}); // overflow ok

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
/// @return delta Time delta since proof submitted
function _timeSince(BridgeProof memory proof) internal view returns (uint256 delta) {
unchecked {
delta = uint96(block.timestamp) - proof.timestamp;
}
}

/// @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;
}

/// @inheritdoc IFastBridgeV2
function claim(bytes memory request) external {
claim(request, address(0));
}

/// @inheritdoc IFastBridge
function claim(bytes memory request, address to) public {
bytes32 transactionId = keccak256(request);
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 "to" is zero addr, permissionlessly send funds to proven relayer
if (to == address(0)) {
to = proof.relayer;
} else if (proof.relayer != msg.sender) {
revert SenderIncorrect();
}

if (_timeSince(proof) <= DISPUTE_PERIOD) revert DisputePeriodNotPassed();

bridgeStatuses[transactionId] = BridgeStatus.RELAYER_CLAIMED;

// update protocol fees if origin fee amount exists
if (transaction.originFeeAmount > 0) protocolFees[transaction.originToken] += transaction.originFeeAmount;

// transfer origin collateral less fee to specified address
address token = transaction.originToken;
uint256 amount = transaction.originAmount;
token.universalTransfer(to, amount);

emit BridgeDepositClaimed(transactionId, proof.relayer, 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();

// @dev relayer gets slashed effectively if dest relay has gone thru
bridgeStatuses[transactionId] = BridgeStatus.REQUESTED;
delete bridgeProofs[transactionId];

emit BridgeProofDisputed(transactionId, msg.sender);
}

/// @inheritdoc IFastBridge
function refund(bytes memory request) external {
bytes32 transactionId = keccak256(request);
BridgeTransaction memory transaction = getBridgeTransaction(request);

if (hasRole(REFUNDER_ROLE, msg.sender)) {
// Refunder can refund if deadline has passed
if (block.timestamp <= transaction.deadline) revert DeadlineNotExceeded();
} else {
// Permissionless refund is allowed after REFUND_DELAY
if (block.timestamp <= transaction.deadline + REFUND_DELAY) revert DeadlineNotExceeded();
}

// set status to refunded if still in requested state
if (bridgeStatuses[transactionId] != BridgeStatus.REQUESTED) revert StatusIncorrect();
bridgeStatuses[transactionId] = BridgeStatus.REFUNDED;

// transfer origin collateral back to original sender
address to = transaction.originSender;
address token = transaction.originToken;
uint256 amount = transaction.originAmount + transaction.originFeeAmount;
token.universalTransfer(to, amount);

emit BridgeDepositRefunded(transactionId, to, token, amount);
}
}
2 changes: 1 addition & 1 deletion packages/contracts-rfq/contracts/interfaces/IAdmin.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma solidity ^0.8.20;

interface IAdmin {
// ============ Events ============
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma solidity ^0.8.20;

interface IFastBridge {
struct BridgeTransaction {
Expand Down
23 changes: 23 additions & 0 deletions packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {IFastBridge} from "./IFastBridge.sol";

interface IFastBridgeV2 is IFastBridge {

/// @notice Relays destination side of bridge transaction by off-chain relayer
/// @param request The encoded bridge transaction to relay on destination chain
/// @param relayer The address of the relaying entity which should have control of the origin funds when claimed
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 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;

/// @notice Completes bridge transaction on origin chain by claiming originally deposited capital. Can only send funds to the relayer address on the proof.
/// @param request The encoded bridge transaction to claim on origin chain
function claim(bytes memory request) external;

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma solidity ^0.8.20;

/// @notice Interface for a contract that can be called multiple times by the same caller. Inspired by MulticallV3:
/// https://github.com/mds1/multicall/blob/master/src/Multicall3.sol
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-rfq/contracts/libs/Errors.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
pragma solidity ^0.8.20;

error DeadlineExceeded();
error DeadlineNotExceeded();
Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-rfq/contracts/libs/UniversalToken.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
pragma solidity ^0.8.20;

import {TokenNotContract} from "./Errors.sol";

Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-rfq/contracts/utils/MulticallTarget.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
pragma solidity ^0.8.20;

import {IMulticallTarget} from "../interfaces/IMulticallTarget.sol";

Expand Down
2 changes: 1 addition & 1 deletion packages/contracts-rfq/script/ConfigureFastBridge.s.sol
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
pragma solidity ^0.8.20;

import {FastBridge} from "../contracts/FastBridge.sol";

Expand Down
Loading

0 comments on commit 739a35a

Please sign in to comment.