diff --git a/.gitattributes b/.gitattributes
index 19f0650d7e..c17ad86ad0 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -16,7 +16,7 @@
# ignore ifacemaker files
*_generated.go linguist-generated
contrib/opbot/generated/* linguist-generated
-*.contractinfo.json linguist-generated=true
+*.contractinfo.json linguist-generated
# svg should be treated as a binary https://git.io/JE2VK
@@ -24,3 +24,6 @@ contrib/opbot/generated/* linguist-generated
*.sol linguist-language=Solidity
.vscode/*.json linguist-language=jsonc
+
+# foundry deploy data
+packages/**/deployments/*.json linguist-generated
diff --git a/docs/bridge/CHANGELOG.md b/docs/bridge/CHANGELOG.md
index b99c54635a..ccf73ae349 100644
--- a/docs/bridge/CHANGELOG.md
+++ b/docs/bridge/CHANGELOG.md
@@ -3,6 +3,22 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.5.13](https://github.com/synapsecns/sanguine/compare/@synapsecns/bridge-docs@0.5.12...@synapsecns/bridge-docs@0.5.13) (2024-12-08)
+
+**Note:** Version bump only for package @synapsecns/bridge-docs
+
+
+
+
+
+## [0.5.12](https://github.com/synapsecns/sanguine/compare/@synapsecns/bridge-docs@0.5.11...@synapsecns/bridge-docs@0.5.12) (2024-12-06)
+
+**Note:** Version bump only for package @synapsecns/bridge-docs
+
+
+
+
+
## [0.5.11](https://github.com/synapsecns/sanguine/compare/@synapsecns/bridge-docs@0.5.10...@synapsecns/bridge-docs@0.5.11) (2024-12-02)
**Note:** Version bump only for package @synapsecns/bridge-docs
diff --git a/docs/bridge/docs/01-About/04-SYN.md b/docs/bridge/docs/01-About/04-SYN.md
new file mode 100644
index 0000000000..09bbd9239c
--- /dev/null
+++ b/docs/bridge/docs/01-About/04-SYN.md
@@ -0,0 +1,24 @@
+---
+title: $SYN Token
+---
+
+# $SYN Token
+
+$SYN is the governance token for the Synapse Protocol. There are no unlocks, all future $SYN emissions are goverened by the [DAO](/docs/About/DAO).
+
+Liquidity for the [$SYN](https://coinmarketcap.com/currencies/synapse-2/) token can be found here:
+
+| Venue | Link |
+| -------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
+| Coinbase | `https://www.coinbase.com/price/synapse` [↗](https://www.coinbase.com/price/synapse) |
+| Sushi | `https://www.sushi.com/ethereum/pool/v2/0x4a86c01d67965f8cb3d0aaa2c655705e64097c31` [↗](https://www.sushi.com/ethereum/pool/v2/0x4a86c01d67965f8cb3d0aaa2c655705e64097c31) |
+| Revolut | `https://www.revolut.com/crypto/price/syn` [↗](https://www.revolut.com/crypto/price/syn) |
+| Binance (Spot) | `https://www.binance.com/en/trade/SYN_USDT?type=spot` [↗](https://www.binance.com/en/trade/SYN_USDT?type=spot) |
+| Binance (Perpetuals) | `https://www.binance.com/en/futures/SYNUSDT` [↗](https://www.binance.com/en/futures/SYNUSDT) |
+| Bybit (SYN/USDT) | `https://www.bybit.com/trade/usdt/SYNUSDT` [↗](https://www.bybit.com/trade/usdt/SYNUSDT) |
+| HTX | `https://www.htx.com/price/syn/` [↗](https://www.htx.com/price/syn/) |
+| Kraken | `https://www.kraken.com/prices/synapse` [↗](https://www.kraken.com/prices/synapse) |
+| KuCoin | `https://www.kucoin.com/price/SYN` [↗](https://www.kucoin.com/price/SYN) |
+
+
+All $SYN token addresses can be found [here](/docs/Contracts/SYN).
diff --git a/docs/bridge/docs/05-Contracts/09-SYN.md b/docs/bridge/docs/05-Contracts/09-SYN.md
new file mode 100644
index 0000000000..e5a8d3e78c
--- /dev/null
+++ b/docs/bridge/docs/05-Contracts/09-SYN.md
@@ -0,0 +1,32 @@
+---
+title: $SYN Token
+---
+
+:::note This list may be incomplete
+
+The canonical list is hosted within the SynapseCNS on [Github](https://github.com/synapsecns/synapse-contracts).
+
+:::
+
+# $SYN
+
+| Chain | Address |
+| --------- | ---------------------------------------------------------------------------------------------------------------------------------------- |
+| Arbitrum | `0x080f6aed32fc474dd5717105dba5ea57268f46eb` [↗](https://arbiscan.io/address/0x080f6aed32fc474dd5717105dba5ea57268f46eb) |
+| Aurora | `0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445` [↗](https://explorer.mainnet.aurora.dev/address/0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445) |
+| Avalanche | `0x1f1E7c893855525b303f99bDF5c3c05Be09ca251` [↗](https://snowscan.xyz/address/0x1f1E7c893855525b303f99bDF5c3c05Be09ca251) |
+| Base | `0x432036208d2717394d2614d6697c46DF3Ed69540` [↗](https://basescan.org/address/0x432036208d2717394d2614d6697c46DF3Ed69540) |
+| Blast | `0x9592f08387134e218327E6E8423400eb845EdE0E` [↗](https://blastscan.io/address/0x9592f08387134e218327E6E8423400eb845EdE0E) |
+| Boba | `0xb554A55358fF0382Fb21F0a478C3546d1106Be8c` [↗](https://blockexplorer.boba.network/address/0xb554A55358fF0382Fb21F0a478C3546d1106Be8c) |
+| BSC | `0xa4080f1778e69467e905b8d6f72f6e441f9e9484` [↗](https://bscscan.com/address/0xa4080f1778e69467e905b8d6f72f6e441f9e9484) |
+| Canto | `0x555982d2E211745b96736665e19D9308B615F78e` [↗](https://canto.dex.guru/address/0x555982d2E211745b96736665e19D9308B615F78e) |
+| Cronos | `0xFD0F80899983b8D46152aa1717D76cba71a31616` [↗](https://cronos.org/explorer/address/0xFD0F80899983b8D46152aa1717D76cba71a31616) |
+| DFK Chain | `0xB6b5C854a8f71939556d4f3a2e5829F7FcC1bf2A` [↗](https://dfkchain.com/address/0xB6b5C854a8f71939556d4f3a2e5829F7FcC1bf2A) |
+| Ethereum | `0x0f2D719407FdBeFF09D87557AbB7232601FD9F29` [↗](https://etherscan.io/address/0x0f2D719407FdBeFF09D87557AbB7232601FD9F29) |
+| Fantom | `0xE55e19Fb4F2D85af758950957714292DAC1e25B2` [↗](https://ftmscan.com/address/0xE55e19Fb4F2D85af758950957714292DAC1e25B2) |
+| Harmony | `0xE55e19Fb4F2D85af758950957714292DAC1e25B2` [↗](https://explorer.harmony.one/address/0xE55e19Fb4F2D85af758950957714292DAC1e25B2) |
+| Metis | `0x67c10c397dd0ba417329543c1a40eb48aaa7cd00` [↗](https://andromeda-explorer.metis.io/address/0x67c10c397dd0ba417329543c1a40eb48aaa7cd00) |
+| Moonbeam | `0xF44938b0125A6662f9536281aD2CD6c499F22004` [↗](https://moonbeam.moonscan.io/address/0xF44938b0125A6662f9536281aD2CD6c499F22004) |
+| Moonriver | `0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445` [↗](https://moonriver.moonscan.io/address/0xd80d8688b02B3FD3afb81cDb124F188BB5aD0445) |
+| Optimism | `0x5A5fFf6F753d7C11A56A52FE47a177a87e431655` [↗](https://optimistic.etherscan.io/address/0x5A5fFf6F753d7C11A56A52FE47a177a87e431655) |
+| Polygon | `0xf8f9efc0db77d8881500bb06ff5d6abc3070e695` [↗](https://polygonscan.com/address/0xf8f9efc0db77d8881500bb06ff5d6abc3070e695) |
diff --git a/docs/bridge/package.json b/docs/bridge/package.json
index e908b9b17a..de0c5ebf15 100644
--- a/docs/bridge/package.json
+++ b/docs/bridge/package.json
@@ -1,6 +1,6 @@
{
"name": "@synapsecns/bridge-docs",
- "version": "0.5.11",
+ "version": "0.5.13",
"private": true,
"scripts": {
"docusaurus": "docusaurus",
diff --git a/packages/contracts-rfq/.solhintignore b/packages/contracts-rfq/.solhintignore
index f2e4fd2a54..30d665167e 100644
--- a/packages/contracts-rfq/.solhintignore
+++ b/packages/contracts-rfq/.solhintignore
@@ -3,4 +3,4 @@ contracts/interfaces/IFastBridge.sol
contracts/legacy/**/*.sol
script/FastBridge.s.sol
test/FastBridge.t.sol
-test/FastBridgeMock.sol
+test/mocks/FastBridgeMock.sol
diff --git a/packages/contracts-rfq/CHANGELOG.md b/packages/contracts-rfq/CHANGELOG.md
index 1f89070995..3c258f337c 100644
--- a/packages/contracts-rfq/CHANGELOG.md
+++ b/packages/contracts-rfq/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.14.7](https://github.com/synapsecns/sanguine/compare/@synapsecns/contracts-rfq@0.14.6...@synapsecns/contracts-rfq@0.14.7) (2024-12-05)
+
+**Note:** Version bump only for package @synapsecns/contracts-rfq
+
+
+
+
+
## [0.14.6](https://github.com/synapsecns/sanguine/compare/@synapsecns/contracts-rfq@0.14.5...@synapsecns/contracts-rfq@0.14.6) (2024-11-28)
diff --git a/packages/contracts-rfq/contracts/AdminV2.sol b/packages/contracts-rfq/contracts/AdminV2.sol
index 14ad93a3bc..0ec8a6eed0 100644
--- a/packages/contracts-rfq/contracts/AdminV2.sol
+++ b/packages/contracts-rfq/contracts/AdminV2.sol
@@ -18,6 +18,16 @@ import {Address} from "@openzeppelin/contracts/utils/Address.sol";
contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors {
using SafeERC20 for IERC20;
+ /// @notice Struct for storing information about a prover.
+ /// @param id The ID of the prover: its position in `_allProvers` plus one,
+ /// or zero if the prover has never been added.
+ /// @param activeFromTimestamp The timestamp at which the prover becomes active,
+ /// or zero if the prover has never been added or is no longer active.
+ struct ProverInfo {
+ uint16 id;
+ uint240 activeFromTimestamp;
+ }
+
/// @notice The address reserved for the native gas token (ETH on Ethereum and most L2s, AVAX on Avalanche, etc.).
address public constant NATIVE_GAS_TOKEN = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE;
@@ -25,10 +35,6 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors {
/// @dev Only addresses with this role can post FastBridge quotes to the API.
bytes32 public constant QUOTER_ROLE = keccak256("QUOTER_ROLE");
- /// @notice The role identifier for the Prover's on-chain authentication in FastBridge.
- /// @dev Only addresses with this role can provide proofs that a FastBridge request has been relayed.
- bytes32 public constant PROVER_ROLE = keccak256("PROVER_ROLE");
-
/// @notice The role identifier for the Guard's on-chain authentication in FastBridge.
/// @dev Only addresses with this role can dispute submitted relay proofs during the dispute period.
bytes32 public constant GUARD_ROLE = keccak256("GUARD_ROLE");
@@ -51,6 +57,11 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors {
/// @notice The default cancel delay set during contract deployment.
uint256 public constant DEFAULT_CANCEL_DELAY = 1 days;
+ /// @notice The minimum dispute penalty time that can be set by the governor.
+ uint256 public constant MIN_DISPUTE_PENALTY_TIME = 1 minutes;
+ /// @notice The default dispute penalty time set during contract deployment.
+ uint256 public constant DEFAULT_DISPUTE_PENALTY_TIME = 30 minutes;
+
/// @notice The protocol fee rate taken on the origin amount deposited in the origin chain.
uint256 public protocolFeeRate;
@@ -66,6 +77,14 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors {
/// This is exposed for conveniece for off-chain indexers that need to know the deployment block.
uint256 public deployBlock;
+ /// @notice The timeout period that is used to temporarily disactivate a disputed prover.
+ uint256 public disputePenaltyTime;
+
+ /// @notice A list of all provers ever added to the contract. Can hold up to 2^16-1 provers.
+ address[] private _allProvers;
+ /// @notice A mapping of provers to their information: id and activeFromTimestamp.
+ mapping(address => ProverInfo) private _proverInfos;
+
/// @notice This variable is deprecated and should not be used.
/// @dev Use ZapNative V2 requests instead.
uint256 public immutable chainGasAmount = 0;
@@ -74,6 +93,35 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors {
_grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);
_setCancelDelay(DEFAULT_CANCEL_DELAY);
_setDeployBlock(block.number);
+ _setDisputePenaltyTime(DEFAULT_DISPUTE_PENALTY_TIME);
+ }
+
+ /// @inheritdoc IAdminV2
+ function addProver(address prover) external onlyRole(DEFAULT_ADMIN_ROLE) {
+ if (getActiveProverID(prover) != 0) revert ProverAlreadyActive();
+ ProverInfo storage $ = _proverInfos[prover];
+ // Add the prover to the list of all provers and record its id (its position + 1),
+ // if this has not already been done.
+ if ($.id == 0) {
+ _allProvers.push(prover);
+ uint256 id = _allProvers.length;
+ if (id > type(uint16).max) revert ProverCapacityExceeded();
+ // Note: this is a storage write.
+ $.id = uint16(id);
+ }
+ // Update the activeFrom timestamp.
+ // Note: this is a storage write.
+ $.activeFromTimestamp = uint240(block.timestamp);
+ emit ProverAdded(prover);
+ }
+
+ /// @inheritdoc IAdminV2
+ function removeProver(address prover) external onlyRole(DEFAULT_ADMIN_ROLE) {
+ if (getActiveProverID(prover) == 0) revert ProverNotActive();
+ // We never remove provers from the list of all provers to preserve their IDs,
+ // so we just need to reset the activeFrom timestamp.
+ _proverInfos[prover].activeFromTimestamp = 0;
+ emit ProverRemoved(prover);
}
/// @inheritdoc IAdminV2
@@ -86,6 +134,11 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors {
_setDeployBlock(blockNumber);
}
+ /// @inheritdoc IAdminV2
+ function setDisputePenaltyTime(uint256 newDisputePenaltyTime) external onlyRole(GOVERNOR_ROLE) {
+ _setDisputePenaltyTime(newDisputePenaltyTime);
+ }
+
/// @inheritdoc IAdminV2
function setProtocolFeeRate(uint256 newFeeRate) external onlyRole(GOVERNOR_ROLE) {
if (newFeeRate > FEE_RATE_MAX) revert FeeRateAboveMax();
@@ -110,6 +163,66 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors {
}
}
+ /// @inheritdoc IAdminV2
+ function getProvers() external view returns (address[] memory provers) {
+ uint256 length = _allProvers.length;
+ // Calculate the number of active provers.
+ uint256 activeProversCount = 0;
+ for (uint256 i = 0; i < length; i++) {
+ if (getActiveProverID(_allProvers[i]) != 0) {
+ activeProversCount++;
+ }
+ }
+ // Do the second pass to populate the provers array.
+ provers = new address[](activeProversCount);
+ uint256 activeProversIndex = 0;
+ for (uint256 i = 0; i < length; i++) {
+ address prover = _allProvers[i];
+ if (getActiveProverID(prover) != 0) {
+ provers[activeProversIndex++] = prover;
+ }
+ }
+ }
+
+ /// @inheritdoc IAdminV2
+ function getProverInfo(address prover) external view returns (uint16 proverID, uint256 activeFromTimestamp) {
+ proverID = _proverInfos[prover].id;
+ activeFromTimestamp = _proverInfos[prover].activeFromTimestamp;
+ }
+
+ /// @inheritdoc IAdminV2
+ function getProverInfoByID(uint16 proverID) external view returns (address prover, uint256 activeFromTimestamp) {
+ if (proverID == 0 || proverID > _allProvers.length) return (address(0), 0);
+ prover = _allProvers[proverID - 1];
+ activeFromTimestamp = _proverInfos[prover].activeFromTimestamp;
+ }
+
+ /// @inheritdoc IAdminV2
+ function getActiveProverID(address prover) public view returns (uint16) {
+ // Aggregate the read operations from the same storage slot.
+ uint16 id = _proverInfos[prover].id;
+ uint256 activeFromTimestamp = _proverInfos[prover].activeFromTimestamp;
+ // Return zero if the prover has never been added or is no longer active.
+ if (activeFromTimestamp == 0 || activeFromTimestamp > block.timestamp) return 0;
+ return id;
+ }
+
+ /// @notice Internal logic to apply the dispute penalty time to a given prover. Will make the prover inactive
+ /// for `disputePenaltyTime` seconds. No-op if the prover ID does not exist or prover is already inactive.
+ function _applyDisputePenaltyTime(uint16 proverID) internal {
+ // Check that the prover exists.
+ if (proverID == 0 || proverID > _allProvers.length) return;
+ address prover = _allProvers[proverID - 1];
+ ProverInfo storage $ = _proverInfos[prover];
+ // No-op if the prover is already inactive.
+ if ($.activeFromTimestamp == 0) return;
+ uint256 newActiveFromTimestamp = block.timestamp + disputePenaltyTime;
+ // Update the activeFrom timestamp.
+ // Note: this is a storage write.
+ $.activeFromTimestamp = uint240(newActiveFromTimestamp);
+ emit DisputePenaltyTimeApplied(prover, newActiveFromTimestamp);
+ }
+
/// @notice Internal logic to set the cancel delay. Security checks are performed outside of this function.
/// @dev This function is marked as private to prevent child contracts from calling it directly.
function _setCancelDelay(uint256 newCancelDelay) private {
@@ -125,4 +238,13 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors {
deployBlock = blockNumber;
emit DeployBlockSet(blockNumber);
}
+
+ /// @notice Internal logic to set the dispute penalty time. Security checks are performed outside of this function.
+ /// @dev This function is marked as private to prevent child contracts from calling it directly.
+ function _setDisputePenaltyTime(uint256 newDisputePenaltyTime) private {
+ if (newDisputePenaltyTime < MIN_DISPUTE_PENALTY_TIME) revert DisputePenaltyTimeBelowMin();
+ uint256 oldDisputePenaltyTime = disputePenaltyTime;
+ disputePenaltyTime = newDisputePenaltyTime;
+ emit DisputePenaltyTimeUpdated(oldDisputePenaltyTime, newDisputePenaltyTime);
+ }
}
diff --git a/packages/contracts-rfq/contracts/FastBridgeV2.sol b/packages/contracts-rfq/contracts/FastBridgeV2.sol
index 836c26262a..706859c113 100644
--- a/packages/contracts-rfq/contracts/FastBridgeV2.sol
+++ b/packages/contracts-rfq/contracts/FastBridgeV2.sol
@@ -104,9 +104,10 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E
function dispute(bytes32 transactionId) external onlyRole(GUARD_ROLE) {
// Aggregate the read operations from the same storage slot.
BridgeTxDetails storage $ = bridgeTxDetails[transactionId];
+ uint16 proverID = $.proverID;
address disputedRelayer = $.proofRelayer;
BridgeStatus status = $.status;
- uint56 proofBlockTimestamp = $.proofBlockTimestamp;
+ uint40 proofBlockTimestamp = $.proofBlockTimestamp;
// Can only dispute a RELAYER_PROVED transaction within the dispute period.
if (status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect();
@@ -114,9 +115,14 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E
revert DisputePeriodPassed();
}
+ // Apply the timeout penalty to the prover that submitted the proof.
+ // Note: this is a no-op if the prover has already been removed.
+ _applyDisputePenaltyTime(proverID);
+
// Update status to REQUESTED and delete the disputed proof details.
// Note: these are storage writes.
$.status = BridgeStatus.REQUESTED;
+ $.proverID = 0;
$.proofRelayer = address(0);
$.proofBlockTimestamp = 0;
@@ -343,7 +349,9 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E
}
/// @inheritdoc IFastBridgeV2
- function proveV2(bytes32 transactionId, bytes32 destTxHash, address relayer) public onlyRole(PROVER_ROLE) {
+ function proveV2(bytes32 transactionId, bytes32 destTxHash, address relayer) public {
+ uint16 proverID = getActiveProverID(msg.sender);
+ if (proverID == 0) revert ProverNotActive();
// Can only prove a REQUESTED transaction.
BridgeTxDetails storage $ = bridgeTxDetails[transactionId];
if ($.status != BridgeStatus.REQUESTED) revert StatusIncorrect();
@@ -351,7 +359,8 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E
// Update status to RELAYER_PROVED and store the proof details.
// Note: these are storage writes.
$.status = BridgeStatus.RELAYER_PROVED;
- $.proofBlockTimestamp = uint56(block.timestamp);
+ $.proverID = proverID;
+ $.proofBlockTimestamp = uint40(block.timestamp);
$.proofRelayer = relayer;
emit BridgeProofProvided(transactionId, relayer, destTxHash);
@@ -367,7 +376,7 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E
BridgeTxDetails storage $ = bridgeTxDetails[transactionId];
address proofRelayer = $.proofRelayer;
BridgeStatus status = $.status;
- uint56 proofBlockTimestamp = $.proofBlockTimestamp;
+ uint40 proofBlockTimestamp = $.proofBlockTimestamp;
// Can only claim a RELAYER_PROVED transaction after the dispute period.
if (status != BridgeStatus.RELAYER_PROVED) revert StatusIncorrect();
@@ -468,14 +477,14 @@ contract FastBridgeV2 is AdminV2, MulticallTarget, IFastBridgeV2, IFastBridgeV2E
}
/// @notice Calculates the time elapsed since a proof was submitted.
- /// @dev The proof.timestamp stores block timestamps as uint56 for gas optimization.
- /// _timeSince(proof) handles timestamp rollover when block.timestamp > type(uint56).max but
- /// proof.timestamp < type(uint56).max via an unchecked statement.
+ /// @dev The proof.timestamp stores block timestamps as uint40 for gas optimization.
+ /// _timeSince(proof) handles timestamp rollover when block.timestamp > type(uint40).max but
+ /// proof.timestamp < type(uint40).max via an unchecked statement.
/// @param proofBlockTimestamp The block timestamp when the proof was submitted.
/// @return delta The time elapsed since proof submission.
- function _timeSince(uint56 proofBlockTimestamp) internal view returns (uint256 delta) {
+ function _timeSince(uint40 proofBlockTimestamp) internal view returns (uint256 delta) {
unchecked {
- delta = uint56(block.timestamp) - proofBlockTimestamp;
+ delta = uint40(block.timestamp) - proofBlockTimestamp;
}
}
diff --git a/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol b/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol
index 0720cce4ac..728193110a 100644
--- a/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol
+++ b/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol
@@ -4,9 +4,20 @@ pragma solidity ^0.8.4;
interface IAdminV2 {
event CancelDelayUpdated(uint256 oldCancelDelay, uint256 newCancelDelay);
event DeployBlockSet(uint256 blockNumber);
+ event DisputePenaltyTimeUpdated(uint256 oldDisputePenaltyTime, uint256 newDisputePenaltyTime);
event FeeRateUpdated(uint256 oldFeeRate, uint256 newFeeRate);
event FeesSwept(address token, address recipient, uint256 amount);
+ event ProverAdded(address prover);
+ event ProverRemoved(address prover);
+ event DisputePenaltyTimeApplied(address prover, uint256 inactiveUntilTimestamp);
+
+ /// @notice Allows the role admin to add a new prover to the contract.
+ function addProver(address prover) external;
+
+ /// @notice Allows the role admin to remove a prover from the contract.
+ function removeProver(address prover) external;
+
/// @notice Allows the governor to set the cancel delay. The cancel delay is the time period after the transaction
/// deadline during which a transaction can be permissionlessly cancelled if it hasn't been proven by any Relayer.
function setCancelDelay(uint256 newCancelDelay) external;
@@ -16,6 +27,11 @@ interface IAdminV2 {
/// block number rather than the chain's native block number.
function setDeployBlock(uint256 blockNumber) external;
+ /// @notice Allows the governor to set the dispute penalty time. The dispute penalty time is the time period used to
+ /// temporarily deactivate a prover if its proof is disputed: prover will be inactive for the dispute penalty time
+ /// after the dispute is submitted.
+ function setDisputePenaltyTime(uint256 newDisputePenaltyTime) external;
+
/// @notice Allows the governor to set the protocol fee rate. The protocol fee is taken from the origin
/// amount and is only applied to completed and claimed transactions.
/// @dev The protocol fee is abstracted away from the relayers; they always operate using the amounts after fees.
@@ -24,4 +40,20 @@ interface IAdminV2 {
/// @notice Allows the governor to withdraw the accumulated protocol fees from the contract.
function sweepProtocolFees(address token, address recipient) external;
+
+ /// @notice Returns the ID of the active prover, or zero if the prover is not currently active.
+ function getActiveProverID(address prover) external view returns (uint16);
+
+ /// @notice Returns the information about the prover with the provided address.
+ /// @return proverID The ID of the prover if it has been added before, or zero otherwise.
+ /// @return activeFromTimestamp The timestamp when the prover becomes active, or zero if the prover isn't active.
+ function getProverInfo(address prover) external view returns (uint16 proverID, uint256 activeFromTimestamp);
+
+ /// @notice Returns the information about the prover with the provided ID.
+ /// @return prover The address of the prover with the provided ID, or zero the ID does not exist.
+ /// @return activeFromTimestamp The timestamp when the prover becomes active, or zero if the prover isn't active.
+ function getProverInfoByID(uint16 proverID) external view returns (address prover, uint256 activeFromTimestamp);
+
+ /// @notice Returns the list of the active provers.
+ function getProvers() external view returns (address[] memory);
}
diff --git a/packages/contracts-rfq/contracts/interfaces/IAdminV2Errors.sol b/packages/contracts-rfq/contracts/interfaces/IAdminV2Errors.sol
index 445087c87c..7fede46ec3 100644
--- a/packages/contracts-rfq/contracts/interfaces/IAdminV2Errors.sol
+++ b/packages/contracts-rfq/contracts/interfaces/IAdminV2Errors.sol
@@ -4,4 +4,8 @@ pragma solidity ^0.8.4;
interface IAdminV2Errors {
error CancelDelayBelowMin();
error FeeRateAboveMax();
+ error ProverAlreadyActive();
+ error ProverCapacityExceeded();
+ error ProverNotActive();
+ error DisputePenaltyTimeBelowMin();
}
diff --git a/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol b/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol
index 141b3808a7..e42a923960 100644
--- a/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol
+++ b/packages/contracts-rfq/contracts/interfaces/IFastBridgeV2.sol
@@ -15,7 +15,8 @@ interface IFastBridgeV2 is IFastBridge {
struct BridgeTxDetails {
BridgeStatus status;
uint32 destChainId;
- uint56 proofBlockTimestamp;
+ uint16 proverID;
+ uint40 proofBlockTimestamp;
address proofRelayer;
}
diff --git a/packages/contracts-rfq/foundry.toml b/packages/contracts-rfq/foundry.toml
index 48f7927e13..e44a7f6a0a 100644
--- a/packages/contracts-rfq/foundry.toml
+++ b/packages/contracts-rfq/foundry.toml
@@ -7,6 +7,7 @@ src = 'contracts'
out = 'out'
libs = ["lib", "node_modules"]
ffi = true
+gas_limit = 9223372036854775807
fs_permissions = [
{ access = "read", path = "./" },
{ access = "read-write", path = "./.deployments" }
diff --git a/packages/contracts-rfq/package.json b/packages/contracts-rfq/package.json
index 29c224390d..17fe284464 100644
--- a/packages/contracts-rfq/package.json
+++ b/packages/contracts-rfq/package.json
@@ -1,7 +1,7 @@
{
"name": "@synapsecns/contracts-rfq",
"license": "MIT",
- "version": "0.14.6",
+ "version": "0.14.7",
"description": "FastBridge contracts.",
"private": true,
"files": [
@@ -26,7 +26,7 @@
"lint": "forge fmt && npm run solhint",
"lint:check": "forge fmt --check && npm run solhint:check",
"ci:lint": "npm run lint:check",
- "build:go": "./flatten.sh contracts/*.sol test/*.sol",
+ "build:go": "./flatten.sh contracts/*.sol test/harnesses/*.sol test/mocks/*.sol",
"solhint": "solhint '{contracts,script,test}/**/*.sol' --fix --noPrompt --max-warnings 3",
"solhint:check": "solhint '{contracts,script,test}/**/*.sol' --max-warnings 3"
}
diff --git a/packages/contracts-rfq/test/FastBridge.t.sol b/packages/contracts-rfq/test/FastBridge.t.sol
index 0e7804e49a..7fad47dc27 100644
--- a/packages/contracts-rfq/test/FastBridge.t.sol
+++ b/packages/contracts-rfq/test/FastBridge.t.sol
@@ -10,7 +10,7 @@ import "../contracts/interfaces/IFastBridge.sol";
import "../contracts/libs/Errors.sol";
import "../contracts/libs/UniversalToken.sol";
-import "./MockERC20.sol";
+import "./mocks/MockERC20.sol";
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
diff --git a/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol
index 4c61883aa1..51610b1029 100644
--- a/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol
+++ b/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol
@@ -2,23 +2,32 @@
pragma solidity ^0.8.20;
import {IAdmin} from "../contracts/interfaces/IAdmin.sol";
-import {IAdminV2Errors} from "../contracts/interfaces/IAdminV2Errors.sol";
import {FastBridgeV2, FastBridgeV2Test} from "./FastBridgeV2.t.sol";
// solhint-disable func-name-mixedcase, ordering
-contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors {
+contract FastBridgeV2ManagementTest is FastBridgeV2Test {
uint256 public constant FEE_RATE_MAX = 1e4; // 1%
bytes32 public constant GOVERNOR_ROLE = keccak256("GOVERNOR_ROLE");
uint256 public constant MIN_CANCEL_DELAY = 1 hours;
uint256 public constant DEFAULT_CANCEL_DELAY = 1 days;
+ uint256 public constant MIN_DISPUTE_PENALTY_TIME = 1 minutes;
+ uint256 public constant DEFAULT_DISPUTE_PENALTY_TIME = 30 minutes;
+
address public admin = makeAddr("Admin");
address public governorA = makeAddr("Governor A");
+ address public proverA = makeAddr("Prover A");
+ address public proverB = makeAddr("Prover B");
+
+ event ProverAdded(address prover);
+ event ProverRemoved(address prover);
+
event CancelDelayUpdated(uint256 oldCancelDelay, uint256 newCancelDelay);
event DeployBlockSet(uint256 blockNumber);
+ event DisputePenaltyTimeUpdated(uint256 oldDisputePenaltyTime, uint256 newDisputePenaltyTime);
event FeeRateUpdated(uint256 oldFeeRate, uint256 newFeeRate);
event FeesSwept(address token, address recipient, uint256 amount);
@@ -37,6 +46,16 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors {
cheatCollectedProtocolFees(ETH_ADDRESS, 200);
}
+ function addProver(address caller, address prover) public {
+ vm.prank(caller);
+ fastBridge.addProver(prover);
+ }
+
+ function removeProver(address caller, address prover) public {
+ vm.prank(caller);
+ fastBridge.removeProver(prover);
+ }
+
function setGovernor(address caller, address newGovernor) public {
vm.prank(caller);
fastBridge.grantRole(GOVERNOR_ROLE, newGovernor);
@@ -52,6 +71,11 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors {
fastBridge.setDeployBlock(blockNumber);
}
+ function setDisputePenaltyTime(address caller, uint256 newDisputePenaltyTime) public {
+ vm.prank(caller);
+ fastBridge.setDisputePenaltyTime(newDisputePenaltyTime);
+ }
+
function setProtocolFeeRate(address caller, uint256 newFeeRate) public {
vm.prank(caller);
fastBridge.setProtocolFeeRate(newFeeRate);
@@ -77,9 +101,157 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors {
function test_defaultValues() public view {
assertEq(fastBridge.cancelDelay(), DEFAULT_CANCEL_DELAY);
assertEq(fastBridge.deployBlock(), block.number);
+ assertEq(fastBridge.disputePenaltyTime(), DEFAULT_DISPUTE_PENALTY_TIME);
assertEq(fastBridge.protocolFeeRate(), 0);
}
+ // ════════════════════════════════════════════════ ADD PROVER ═════════════════════════════════════════════════════
+
+ function checkProverInfo(address prover, uint16 proverID, uint256 activeFromTimestamp) public view {
+ (uint16 id, uint256 ts) = fastBridge.getProverInfo(prover);
+ assertEq(id, proverID);
+ assertEq(ts, activeFromTimestamp);
+ address p;
+ (p, ts) = fastBridge.getProverInfoByID(proverID);
+ if (proverID != 0) {
+ assertEq(p, prover);
+ assertEq(ts, activeFromTimestamp);
+ } else {
+ assertEq(p, address(0));
+ assertEq(ts, 0);
+ }
+ }
+
+ function test_addProver() public {
+ uint256 proverAtime = block.timestamp;
+ vm.expectEmit(address(fastBridge));
+ emit ProverAdded(proverA);
+ addProver(admin, proverA);
+ assertEq(fastBridge.getActiveProverID(proverA), 1);
+ assertEq(fastBridge.getActiveProverID(proverB), 0);
+ address[] memory provers = fastBridge.getProvers();
+ assertEq(provers.length, 1);
+ assertEq(provers[0], proverA);
+ checkProverInfo(proverA, 1, proverAtime);
+ checkProverInfo(proverB, 0, 0);
+ }
+
+ function test_addProver_twice() public {
+ uint256 proverAtime = block.timestamp;
+ test_addProver();
+ skip(1 hours);
+ uint256 proverBtime = block.timestamp;
+ vm.expectEmit(address(fastBridge));
+ emit ProverAdded(proverB);
+ addProver(admin, proverB);
+ assertEq(fastBridge.getActiveProverID(proverA), 1);
+ assertEq(fastBridge.getActiveProverID(proverB), 2);
+ address[] memory provers = fastBridge.getProvers();
+ assertEq(provers.length, 2);
+ assertEq(provers[0], proverA);
+ assertEq(provers[1], proverB);
+ checkProverInfo(proverA, 1, proverAtime);
+ checkProverInfo(proverB, 2, proverBtime);
+ }
+
+ function test_addProver_twice_afterRemoval() public {
+ test_removeProver_twice();
+ // Add B back
+ skip(1 hours);
+ uint256 proverBtime = block.timestamp;
+ vm.expectEmit(address(fastBridge));
+ emit ProverAdded(proverB);
+ addProver(admin, proverB);
+ assertEq(fastBridge.getActiveProverID(proverA), 0);
+ assertEq(fastBridge.getActiveProverID(proverB), 2);
+ address[] memory provers = fastBridge.getProvers();
+ assertEq(provers.length, 1);
+ assertEq(provers[0], proverB);
+ checkProverInfo(proverA, 1, 0);
+ checkProverInfo(proverB, 2, proverBtime);
+ // Add A back
+ skip(1 hours);
+ uint256 proverAtime = block.timestamp;
+ vm.expectEmit(address(fastBridge));
+ emit ProverAdded(proverA);
+ addProver(admin, proverA);
+ assertEq(fastBridge.getActiveProverID(proverA), 1);
+ assertEq(fastBridge.getActiveProverID(proverB), 2);
+ provers = fastBridge.getProvers();
+ assertEq(provers.length, 2);
+ assertEq(provers[0], proverA);
+ assertEq(provers[1], proverB);
+ checkProverInfo(proverA, 1, proverAtime);
+ checkProverInfo(proverB, 2, proverBtime);
+ }
+
+ function test_addProver_revertNotAdmin(address caller) public {
+ vm.assume(caller != admin);
+ expectUnauthorized(caller, fastBridge.DEFAULT_ADMIN_ROLE());
+ addProver(caller, proverA);
+ }
+
+ function test_addProver_revertAlreadyActive() public {
+ test_addProver();
+ vm.expectRevert(ProverAlreadyActive.selector);
+ addProver(admin, proverA);
+ }
+
+ function test_addProver_revertTooManyProvers() public {
+ for (uint256 i = 0; i < type(uint16).max; i++) {
+ addProver(admin, address(uint160(i)));
+ }
+ vm.expectRevert(ProverCapacityExceeded.selector);
+ addProver(admin, proverA);
+ }
+
+ // ═══════════════════════════════════════════════ REMOVE PROVER ═══════════════════════════════════════════════════
+
+ function test_removeProver() public {
+ test_addProver_twice();
+ uint256 proverBtime = block.timestamp;
+ vm.expectEmit(address(fastBridge));
+ emit ProverRemoved(proverA);
+ removeProver(admin, proverA);
+ assertEq(fastBridge.getActiveProverID(proverA), 0);
+ assertEq(fastBridge.getActiveProverID(proverB), 2);
+ address[] memory provers = fastBridge.getProvers();
+ assertEq(provers.length, 1);
+ assertEq(provers[0], proverB);
+ checkProverInfo(proverA, 1, 0);
+ checkProverInfo(proverB, 2, proverBtime);
+ }
+
+ function test_removeProver_twice() public {
+ test_removeProver();
+ vm.expectEmit(address(fastBridge));
+ emit ProverRemoved(proverB);
+ removeProver(admin, proverB);
+ assertEq(fastBridge.getActiveProverID(proverA), 0);
+ assertEq(fastBridge.getActiveProverID(proverB), 0);
+ address[] memory provers = fastBridge.getProvers();
+ assertEq(provers.length, 0);
+ checkProverInfo(proverA, 1, 0);
+ checkProverInfo(proverB, 2, 0);
+ }
+
+ function test_removeProver_revertNotAdmin(address caller) public {
+ vm.assume(caller != admin);
+ expectUnauthorized(caller, fastBridge.DEFAULT_ADMIN_ROLE());
+ removeProver(caller, proverA);
+ }
+
+ function test_removeProver_revertNeverBeenActive() public {
+ vm.expectRevert(ProverNotActive.selector);
+ removeProver(admin, proverA);
+ }
+
+ function test_removeProver_revertNotActive() public {
+ test_removeProver();
+ vm.expectRevert(ProverNotActive.selector);
+ removeProver(admin, proverA);
+ }
+
// ═════════════════════════════════════════════ SET CANCEL DELAY ══════════════════════════════════════════════════
function test_setCancelDelay() public {
@@ -98,7 +270,7 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors {
}
function test_setCancelDelay_revertBelowMin() public {
- vm.expectRevert(IAdminV2Errors.CancelDelayBelowMin.selector);
+ vm.expectRevert(CancelDelayBelowMin.selector);
setCancelDelay(governor, MIN_CANCEL_DELAY - 1);
}
@@ -123,6 +295,34 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors {
setDeployBlock(caller, 123_456);
}
+ // ═════════════════════════════════════════ SET DISPUTE PENALTY TIME ══════════════════════════════════════════════
+
+ function test_setDisputePenaltyTime() public {
+ vm.expectEmit(address(fastBridge));
+ emit DisputePenaltyTimeUpdated(DEFAULT_DISPUTE_PENALTY_TIME, 1 days);
+ setDisputePenaltyTime(governor, 1 days);
+ assertEq(fastBridge.disputePenaltyTime(), 1 days);
+ }
+
+ function test_setDisputePenaltyTime_twice() public {
+ test_setDisputePenaltyTime();
+ vm.expectEmit(address(fastBridge));
+ emit DisputePenaltyTimeUpdated(1 days, 2 days);
+ setDisputePenaltyTime(governor, 2 days);
+ assertEq(fastBridge.disputePenaltyTime(), 2 days);
+ }
+
+ function test_setDisputePenaltyTime_revertBelowMin() public {
+ vm.expectRevert(DisputePenaltyTimeBelowMin.selector);
+ setDisputePenaltyTime(governor, MIN_DISPUTE_PENALTY_TIME - 1);
+ }
+
+ function test_setDisputePenaltyTime_revertNotGovernor(address caller) public {
+ vm.assume(caller != governor);
+ expectUnauthorized(caller, fastBridge.GOVERNOR_ROLE());
+ setDisputePenaltyTime(caller, 1 days);
+ }
+
// ═══════════════════════════════════════════ SET PROTOCOL FEE RATE ═══════════════════════════════════════════════
function test_setProtocolFeeRate() public {
@@ -141,7 +341,7 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors {
}
function test_setProtocolFeeRate_revert_tooHigh() public {
- vm.expectRevert(IAdminV2Errors.FeeRateAboveMax.selector);
+ vm.expectRevert(FeeRateAboveMax.selector);
setProtocolFeeRate(governor, FEE_RATE_MAX + 1);
}
diff --git a/packages/contracts-rfq/test/FastBridgeV2.Src.Base.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Src.Base.t.sol
index 54853731ef..969fbc3220 100644
--- a/packages/contracts-rfq/test/FastBridgeV2.Src.Base.t.sol
+++ b/packages/contracts-rfq/test/FastBridgeV2.Src.Base.t.sol
@@ -9,8 +9,9 @@ import {FastBridgeV2, FastBridgeV2Test, IFastBridge, IFastBridgeV2} from "./Fast
abstract contract FastBridgeV2SrcBaseTest is FastBridgeV2Test {
uint256 public constant MIN_DEADLINE = 30 minutes;
uint256 public constant CLAIM_DELAY = 30 minutes;
- // Use a value different from the default to ensure it's being set correctly.
+ // Use values different from the default to ensure it's being set correctly.
uint256 public constant PERMISSIONLESS_CANCEL_DELAY = 13.37 hours;
+ uint256 public constant DISPUTE_PENALTY_TIME = 4.2 minutes;
uint256 public constant LEFTOVER_BALANCE = 10 ether;
uint256 public constant INITIAL_PROTOCOL_FEES_TOKEN = 456_789;
@@ -26,13 +27,15 @@ abstract contract FastBridgeV2SrcBaseTest is FastBridgeV2Test {
}
function configureFastBridge() public virtual override {
- fastBridge.grantRole(fastBridge.PROVER_ROLE(), relayerA);
- fastBridge.grantRole(fastBridge.PROVER_ROLE(), relayerB);
+ fastBridge.addProver(relayerA);
+ fastBridge.addProver(relayerB);
+
fastBridge.grantRole(fastBridge.GUARD_ROLE(), guard);
fastBridge.grantRole(fastBridge.CANCELER_ROLE(), canceler);
fastBridge.grantRole(fastBridge.GOVERNOR_ROLE(), address(this));
fastBridge.setCancelDelay(PERMISSIONLESS_CANCEL_DELAY);
+ fastBridge.setDisputePenaltyTime(DISPUTE_PENALTY_TIME);
}
function mintTokens() public virtual override {
diff --git a/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol
index 5b3db7e3a9..14f7cb184c 100644
--- a/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol
+++ b/packages/contracts-rfq/test/FastBridgeV2.Src.t.sol
@@ -32,6 +32,8 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
event BridgeQuoteDetails(bytes32 indexed transactionId, bytes quoteId);
+ event DisputePenaltyTimeApplied(address prover, uint256 inactiveUntilTimestamp);
+
address public claimTo = makeAddr("Claim To");
function expectBridgeRequested(IFastBridgeV2.BridgeTransactionV2 memory bridgeTx, bytes32 txId) public {
@@ -92,14 +94,26 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
});
}
+ function expectDisputePenaltyTimeApplied(address prover) public {
+ uint256 inactiveUntilTimestamp = block.timestamp + DISPUTE_PENALTY_TIME;
+ vm.expectEmit(address(fastBridge));
+ emit DisputePenaltyTimeApplied(prover, inactiveUntilTimestamp);
+ }
+
// ══════════════════════════════════════════════════ 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);
+ (
+ IFastBridgeV2.BridgeStatus status,
+ uint32 destChainId,
+ uint16 proverID,
+ uint256 proofBlockTimestamp,
+ address proofRelayer
+ ) = fastBridge.bridgeTxDetails(txId);
assertEq(status, IFastBridgeV2.BridgeStatus.REQUESTED);
assertEq(destChainId, DST_CHAIN_ID);
+ assertEq(proverID, 0);
assertEq(proofBlockTimestamp, 0);
assertEq(proofRelayer, address(0));
(proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId);
@@ -282,12 +296,18 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
// ═══════════════════════════════════════════════════ PROVE ═══════════════════════════════════════════════════════
- function checkStatusAndProofAfterProve(bytes32 txId, address relayer) public view {
+ function checkStatusAndProofAfterProve(bytes32 txId, uint16 expectedProverID, address relayer) public view {
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.RELAYER_PROVED);
- (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) =
- fastBridge.bridgeTxDetails(txId);
+ (
+ IFastBridgeV2.BridgeStatus status,
+ uint32 destChainId,
+ uint16 proverID,
+ uint256 proofBlockTimestamp,
+ address proofRelayer
+ ) = fastBridge.bridgeTxDetails(txId);
assertEq(status, IFastBridgeV2.BridgeStatus.RELAYER_PROVED);
assertEq(destChainId, DST_CHAIN_ID);
+ assertEq(proverID, expectedProverID);
assertEq(proofBlockTimestamp, block.timestamp);
assertEq(proofRelayer, relayer);
(proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId);
@@ -300,7 +320,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
bridge({caller: userA, msgValue: 0, params: tokenParams});
expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"01"});
prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"});
- checkStatusAndProofAfterProve(txId, relayerA);
+ checkStatusAndProofAfterProve(txId, 1, relayerA);
assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
}
@@ -312,7 +332,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"});
- checkStatusAndProofAfterProve(txId, relayerA);
+ checkStatusAndProofAfterProve(txId, 1, relayerA);
assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount);
assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH);
}
@@ -346,13 +366,25 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"});
}
- function test_prove_revert_callerNotRelayer(address caller) public {
+ function test_prove_revert_callerNotProver(address caller) public {
vm.assume(caller != relayerA && caller != relayerB);
bridge({caller: userA, msgValue: 0, params: tokenParams});
- expectUnauthorized(caller, fastBridge.PROVER_ROLE());
+ vm.expectRevert(ProverNotActive.selector);
prove({caller: caller, bridgeTx: tokenTx, destTxHash: hex"01"});
}
+ function test_prove_revert_disputePenaltyTime() public {
+ bytes32 txId = getTxId(tokenTx);
+ bridge({caller: userA, msgValue: 0, params: tokenParams});
+ prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"});
+ dispute({caller: guard, txId: txId});
+ vm.expectRevert(ProverNotActive.selector);
+ prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"02"});
+ skip(DISPUTE_PENALTY_TIME - 1);
+ vm.expectRevert(ProverNotActive.selector);
+ prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"02"});
+ }
+
// ════════════════════════════════════════ PROVE OTHER RELAYER ════════════════════════════════════════════
function test_proveOther_token() public {
@@ -360,7 +392,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});
- checkStatusAndProofAfterProve(txId, relayerA);
+ checkStatusAndProofAfterProve(txId, 2, relayerA);
assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
}
@@ -372,7 +404,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});
- checkStatusAndProofAfterProve(txId, relayerA);
+ checkStatusAndProofAfterProve(txId, 2, relayerA);
assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount);
assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH);
}
@@ -383,7 +415,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});
- checkStatusAndProofAfterProve(txId, relayerA);
+ checkStatusAndProofAfterProve(txId, 1, relayerA);
assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
}
@@ -395,7 +427,19 @@ 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)});
- checkStatusAndProofAfterProve(txId, address(0x1234));
+ checkStatusAndProofAfterProve(txId, 1, 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_afterDispute() public {
+ bytes32 txId = getTxId(tokenTx);
+ bridge({caller: userA, msgValue: 0, params: tokenParams});
+ prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"01"});
+ dispute({caller: guard, txId: txId});
+ expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"02"});
+ prove({caller: relayerB, relayer: relayerA, transactionId: txId, destTxHash: hex"02"});
+ checkStatusAndProofAfterProve(txId, 2, relayerA);
assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
}
@@ -406,17 +450,19 @@ 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);
+ checkStatusAndProofAfterProve(txId, 2, relayerA);
expectBridgeProofDisputed(txId, relayerA);
dispute(guard, txId);
+ skip(DISPUTE_PENALTY_TIME);
expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"02"});
prove({caller: relayerB, transactionId: txId, destTxHash: hex"02", relayer: relayerA});
- checkStatusAndProofAfterProve(txId, relayerA);
+ checkStatusAndProofAfterProve(txId, 2, relayerA);
expectBridgeProofDisputed(txId, relayerA);
dispute(guard, txId);
+ skip(DISPUTE_PENALTY_TIME);
expectBridgeProofProvided({txId: txId, relayer: relayerA, destTxHash: hex"03"});
prove({caller: relayerB, transactionId: txId, destTxHash: hex"03", relayer: relayerA});
- checkStatusAndProofAfterProve(txId, relayerA);
+ checkStatusAndProofAfterProve(txId, 2, relayerA);
assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
}
@@ -428,7 +474,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});
- checkStatusAndProofAfterProve(txId, relayerA);
+ checkStatusAndProofAfterProve(txId, 1, relayerA);
assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
}
@@ -464,18 +510,44 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
bytes32 txId = getTxId(tokenTx);
vm.assume(caller != relayerA && caller != relayerB);
bridge({caller: userA, msgValue: 0, params: tokenParams});
- expectUnauthorized(caller, fastBridge.PROVER_ROLE());
+ vm.expectRevert(ProverNotActive.selector);
prove({caller: caller, transactionId: txId, destTxHash: hex"01", relayer: relayerA});
}
+ function test_proveOther_revert_disputePenaltyTime() public {
+ bytes32 txId = getTxId(tokenTx);
+ bridge({caller: userA, msgValue: 0, params: tokenParams});
+ prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"01"});
+ dispute({caller: guard, txId: txId});
+ vm.expectRevert(ProverNotActive.selector);
+ prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"02"});
+ skip(DISPUTE_PENALTY_TIME - 1);
+ vm.expectRevert(ProverNotActive.selector);
+ prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"02"});
+ }
+
// ═══════════════════════════════════════════════════ CLAIM ═══════════════════════════════════════════════════════
- function checkStatusAndProofAfterClaim(bytes32 txId, address relayer, uint256 expectedProofTS) public view {
+ function checkStatusAndProofAfterClaim(
+ bytes32 txId,
+ uint16 expectedProverID,
+ 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);
+ (
+ IFastBridgeV2.BridgeStatus status,
+ uint32 destChainId,
+ uint16 proverID,
+ uint256 proofBlockTimestamp,
+ address proofRelayer
+ ) = fastBridge.bridgeTxDetails(txId);
assertEq(status, IFastBridgeV2.BridgeStatus.RELAYER_CLAIMED);
assertEq(destChainId, DST_CHAIN_ID);
+ assertEq(proverID, expectedProverID);
assertEq(proofBlockTimestamp, expectedProofTS);
assertEq(proofRelayer, relayer);
(proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId);
@@ -499,7 +571,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
assertTrue(fastBridge.canClaim(txId, relayerA));
expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA});
claim({caller: relayerA, bridgeTx: tokenTx, to: relayerA});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkTokenBalancesAfterClaim(relayerA);
}
@@ -512,7 +584,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
skip(CLAIM_DELAY + 1);
expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA});
claim({caller: caller, bridgeTx: tokenTx});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkTokenBalancesAfterClaim(relayerA);
}
@@ -525,7 +597,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
skip(CLAIM_DELAY + 1);
expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA});
claim({caller: caller, bridgeTx: tokenTx, to: address(0)});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkTokenBalancesAfterClaim(relayerA);
}
@@ -537,7 +609,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
skip(CLAIM_DELAY + 1);
expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: claimTo});
claim({caller: relayerA, bridgeTx: tokenTx, to: claimTo});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
assertEq(srcToken.balanceOf(relayerA), 0);
checkTokenBalancesAfterClaim(claimTo);
}
@@ -550,7 +622,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
skip(CLAIM_DELAY + 30 days);
expectBridgeDepositClaimed({bridgeTx: tokenTx, txId: txId, relayer: relayerA, to: relayerA});
claim({caller: relayerA, bridgeTx: tokenTx, to: relayerA});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkTokenBalancesAfterClaim(relayerA);
}
@@ -571,7 +643,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
assertTrue(fastBridge.canClaim(txId, relayerA));
expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA});
claim({caller: relayerA, bridgeTx: ethTx, to: relayerA});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkEthBalancesAfterClaim(relayerA);
}
@@ -585,7 +657,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
skip(CLAIM_DELAY + 1);
expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA});
claim({caller: caller, bridgeTx: ethTx});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkEthBalancesAfterClaim(relayerA);
}
@@ -599,7 +671,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
skip(CLAIM_DELAY + 1);
expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA});
claim({caller: caller, bridgeTx: ethTx, to: address(0)});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkEthBalancesAfterClaim(relayerA);
}
@@ -612,7 +684,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
skip(CLAIM_DELAY + 1);
expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: claimTo});
claim({caller: relayerA, bridgeTx: ethTx, to: claimTo});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkEthBalancesAfterClaim(claimTo);
}
@@ -625,7 +697,7 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
skip(CLAIM_DELAY + 30 days);
expectBridgeDepositClaimed({bridgeTx: ethTx, txId: txId, relayer: relayerA, to: relayerA});
claim({caller: relayerA, bridgeTx: ethTx, to: relayerA});
- checkStatusAndProofAfterClaim(txId, relayerA, expectedProofTS);
+ checkStatusAndProofAfterClaim(txId, 1, relayerA, expectedProofTS);
checkEthBalancesAfterClaim(relayerA);
}
@@ -704,15 +776,59 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
checkStatusAndProofAfterBridge(txId);
}
+ function checkProver(address prover, uint16 expectedProverID, uint256 expectedActiveFromTimestamp) public view {
+ (uint16 proverID, uint256 activeFromTimestamp) = fastBridge.getProverInfo(prover);
+ assertEq(proverID, expectedProverID);
+ assertEq(activeFromTimestamp, expectedActiveFromTimestamp);
+ address p;
+ (p, activeFromTimestamp) = fastBridge.getProverInfoByID(expectedProverID);
+ assertEq(p, prover);
+ assertEq(activeFromTimestamp, expectedActiveFromTimestamp);
+ }
+
function test_dispute_token() public {
bytes32 txId = getTxId(tokenTx);
bridge({caller: userA, msgValue: 0, params: tokenParams});
prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"});
+ expectDisputePenaltyTimeApplied({prover: relayerA});
expectBridgeProofDisputed({txId: txId, relayer: relayerA});
dispute({caller: guard, txId: txId});
checkStatusAndProofAfterDispute(txId);
assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
+ // Check disputed prover
+ assertEq(fastBridge.getActiveProverID(relayerA), 0);
+ checkProver(relayerA, 1, block.timestamp + DISPUTE_PENALTY_TIME);
+ }
+
+ function test_dispute_token_provedOther() public {
+ bytes32 txId = getTxId(tokenTx);
+ bridge({caller: userA, msgValue: 0, params: tokenParams});
+ prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"01"});
+ expectDisputePenaltyTimeApplied({prover: relayerA});
+ expectBridgeProofDisputed({txId: txId, relayer: relayerB});
+ dispute({caller: guard, txId: txId});
+ checkStatusAndProofAfterDispute(txId);
+ assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
+ assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
+ // Check disputed prover
+ assertEq(fastBridge.getActiveProverID(relayerA), 0);
+ checkProver(relayerA, 1, block.timestamp + DISPUTE_PENALTY_TIME);
+ }
+
+ function test_dispute_token_proverAlreadyRemoved() public {
+ bytes32 txId = getTxId(tokenTx);
+ bridge({caller: userA, msgValue: 0, params: tokenParams});
+ prove({caller: relayerA, relayer: relayerB, transactionId: txId, destTxHash: hex"01"});
+ fastBridge.removeProver(relayerA);
+ expectBridgeProofDisputed({txId: txId, relayer: relayerB});
+ dispute({caller: guard, txId: txId});
+ checkStatusAndProofAfterDispute(txId);
+ assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
+ assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
+ // Check disputed prover
+ assertEq(fastBridge.getActiveProverID(relayerA), 0);
+ checkProver(relayerA, 1, 0);
}
function test_dispute_token_justBeforeDeadline() public {
@@ -720,36 +836,79 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
bridge({caller: userA, msgValue: 0, params: tokenParams});
prove({caller: relayerA, bridgeTx: tokenTx, destTxHash: hex"01"});
skip(CLAIM_DELAY);
+ expectDisputePenaltyTimeApplied({prover: relayerA});
expectBridgeProofDisputed({txId: txId, relayer: relayerA});
dispute({caller: guard, txId: txId});
checkStatusAndProofAfterDispute(txId);
assertEq(fastBridge.protocolFees(address(srcToken)), INITIAL_PROTOCOL_FEES_TOKEN);
assertEq(srcToken.balanceOf(address(fastBridge)), INITIAL_PROTOCOL_FEES_TOKEN + tokenParams.originAmount);
+ // Check disputed prover
+ assertEq(fastBridge.getActiveProverID(relayerA), 0);
+ checkProver(relayerA, 1, block.timestamp + DISPUTE_PENALTY_TIME);
}
function test_dispute_eth() public {
bridge({caller: userA, msgValue: 0, params: tokenParams});
bytes32 txId = getTxId(ethTx);
bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams});
- prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"});
+ prove({caller: relayerB, bridgeTx: ethTx, destTxHash: hex"01"});
+ expectDisputePenaltyTimeApplied({prover: relayerB});
+ expectBridgeProofDisputed({txId: txId, relayer: relayerB});
+ dispute({caller: guard, txId: txId});
+ checkStatusAndProofAfterDispute(txId);
+ assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH);
+ assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount);
+ // Check disputed prover
+ assertEq(fastBridge.getActiveProverID(relayerB), 0);
+ checkProver(relayerB, 2, block.timestamp + DISPUTE_PENALTY_TIME);
+ }
+
+ function test_dispute_eth_provedOther() public {
+ bridge({caller: userA, msgValue: 0, params: tokenParams});
+ bytes32 txId = getTxId(ethTx);
+ bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams});
+ prove({caller: relayerB, relayer: relayerA, transactionId: txId, destTxHash: hex"01"});
+ expectDisputePenaltyTimeApplied({prover: relayerB});
expectBridgeProofDisputed({txId: txId, relayer: relayerA});
dispute({caller: guard, txId: txId});
checkStatusAndProofAfterDispute(txId);
assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH);
assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount);
+ // Check disputed prover
+ assertEq(fastBridge.getActiveProverID(relayerB), 0);
+ checkProver(relayerB, 2, block.timestamp + DISPUTE_PENALTY_TIME);
+ }
+
+ function test_dispute_eth_proverAlreadyRemoved() public {
+ bridge({caller: userA, msgValue: 0, params: tokenParams});
+ bytes32 txId = getTxId(ethTx);
+ bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams});
+ prove({caller: relayerB, relayer: relayerA, transactionId: txId, destTxHash: hex"01"});
+ fastBridge.removeProver(relayerB);
+ dispute({caller: guard, txId: txId});
+ checkStatusAndProofAfterDispute(txId);
+ assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH);
+ assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount);
+ // Check disputed prover
+ assertEq(fastBridge.getActiveProverID(relayerB), 0);
+ checkProver(relayerB, 2, 0);
}
function test_dispute_eth_justBeforeDeadline() public {
bridge({caller: userA, msgValue: 0, params: tokenParams});
bytes32 txId = getTxId(ethTx);
bridge({caller: userA, msgValue: ethParams.originAmount, params: ethParams});
- prove({caller: relayerA, bridgeTx: ethTx, destTxHash: hex"01"});
+ prove({caller: relayerB, bridgeTx: ethTx, destTxHash: hex"01"});
skip(CLAIM_DELAY);
- expectBridgeProofDisputed({txId: txId, relayer: relayerA});
+ expectDisputePenaltyTimeApplied({prover: relayerB});
+ expectBridgeProofDisputed({txId: txId, relayer: relayerB});
dispute({caller: guard, txId: txId});
checkStatusAndProofAfterDispute(txId);
assertEq(fastBridge.protocolFees(ETH_ADDRESS), INITIAL_PROTOCOL_FEES_ETH);
assertEq(address(fastBridge).balance, INITIAL_PROTOCOL_FEES_ETH + ethParams.originAmount);
+ // Check disputed prover
+ assertEq(fastBridge.getActiveProverID(relayerB), 0);
+ checkProver(relayerB, 2, block.timestamp + DISPUTE_PENALTY_TIME);
}
function test_dispute_revert_afterDeadline() public {
@@ -806,10 +965,16 @@ contract FastBridgeV2SrcTest is FastBridgeV2SrcBaseTest {
function checkStatusAndProofAfterCancel(bytes32 txId) public view {
assertEq(fastBridge.bridgeStatuses(txId), IFastBridgeV2.BridgeStatus.REFUNDED);
- (IFastBridgeV2.BridgeStatus status, uint32 destChainId, uint256 proofBlockTimestamp, address proofRelayer) =
- fastBridge.bridgeTxDetails(txId);
+ (
+ IFastBridgeV2.BridgeStatus status,
+ uint32 destChainId,
+ uint16 proverID,
+ uint256 proofBlockTimestamp,
+ address proofRelayer
+ ) = fastBridge.bridgeTxDetails(txId);
assertEq(status, IFastBridgeV2.BridgeStatus.REFUNDED);
assertEq(destChainId, DST_CHAIN_ID);
+ assertEq(proverID, 0);
assertEq(proofBlockTimestamp, 0);
assertEq(proofRelayer, address(0));
(proofBlockTimestamp, proofRelayer) = fastBridge.bridgeProofs(txId);
diff --git a/packages/contracts-rfq/test/FastBridgeV2.t.sol b/packages/contracts-rfq/test/FastBridgeV2.t.sol
index 5a99259ad2..d4fab0a1ae 100644
--- a/packages/contracts-rfq/test/FastBridgeV2.t.sol
+++ b/packages/contracts-rfq/test/FastBridgeV2.t.sol
@@ -9,16 +9,17 @@ import {IFastBridge} from "../contracts/interfaces/IFastBridge.sol";
import {IFastBridgeV2} from "../contracts/interfaces/IFastBridgeV2.sol";
import {FastBridgeV2} from "../contracts/FastBridgeV2.sol";
+import {IAdminV2Errors} from "../contracts/interfaces/IAdminV2Errors.sol";
import {IFastBridgeV2Errors} from "../contracts/interfaces/IFastBridgeV2Errors.sol";
-import {MockERC20} from "./MockERC20.sol";
+import {MockERC20} from "./mocks/MockERC20.sol";
import {IAccessControl} from "@openzeppelin/contracts/access/IAccessControl.sol";
import {Test} from "forge-std/Test.sol";
import {StdStorage, stdStorage} from "forge-std/Test.sol";
// solhint-disable no-empty-blocks, max-states-count, ordering
-abstract contract FastBridgeV2Test is Test, IFastBridgeV2Errors {
+abstract contract FastBridgeV2Test is Test, IAdminV2Errors, IFastBridgeV2Errors {
using stdStorage for StdStorage;
uint32 public constant SRC_CHAIN_ID = 1337;
diff --git a/packages/contracts-rfq/test/UniversalTokenLibHarness.sol b/packages/contracts-rfq/test/harnesses/UniversalTokenLibHarness.sol
similarity index 93%
rename from packages/contracts-rfq/test/UniversalTokenLibHarness.sol
rename to packages/contracts-rfq/test/harnesses/UniversalTokenLibHarness.sol
index 5e628b5fa2..0b2d2bc234 100644
--- a/packages/contracts-rfq/test/UniversalTokenLibHarness.sol
+++ b/packages/contracts-rfq/test/harnesses/UniversalTokenLibHarness.sol
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
-import {UniversalTokenLib} from "../contracts/libs/UniversalToken.sol";
+import {UniversalTokenLib} from "../../contracts/libs/UniversalToken.sol";
// solhint-disable no-empty-blocks, ordering
contract UniversalTokenLibHarness {
diff --git a/packages/contracts-rfq/test/integration/FastBridgeV2.MulticallTarget.t.sol b/packages/contracts-rfq/test/integration/FastBridgeV2.MulticallTarget.t.sol
index 4a23e98169..794d0c9210 100644
--- a/packages/contracts-rfq/test/integration/FastBridgeV2.MulticallTarget.t.sol
+++ b/packages/contracts-rfq/test/integration/FastBridgeV2.MulticallTarget.t.sol
@@ -8,9 +8,9 @@ import {IFastBridge, MulticallTargetIntegrationTest} from "./MulticallTarget.t.s
contract FastBridgeV2MulticallTargetTest is MulticallTargetIntegrationTest {
function deployAndConfigureFastBridge() public override returns (address) {
- FastBridgeV2 fastBridge = new FastBridgeV2(address(this));
- fastBridge.grantRole(fastBridge.PROVER_ROLE(), relayer);
- return address(fastBridge);
+ FastBridgeV2 fb = new FastBridgeV2(address(this));
+ fb.addProver(relayer);
+ return address(fb);
}
function getEncodedBridgeTx(IFastBridge.BridgeTransaction memory bridgeTx)
diff --git a/packages/contracts-rfq/test/integration/MulticallTarget.t.sol b/packages/contracts-rfq/test/integration/MulticallTarget.t.sol
index c844086c46..0e43522346 100644
--- a/packages/contracts-rfq/test/integration/MulticallTarget.t.sol
+++ b/packages/contracts-rfq/test/integration/MulticallTarget.t.sol
@@ -6,7 +6,7 @@ import {IFastBridgeV2} from "../../contracts/interfaces/IFastBridgeV2.sol";
import {IMulticallTarget} from "../../contracts/interfaces/IMulticallTarget.sol";
import {DisputePeriodNotPassed} from "../../contracts/libs/Errors.sol";
-import {MockERC20} from "../MockERC20.sol";
+import {MockERC20} from "../mocks/MockERC20.sol";
import {Test} from "forge-std/Test.sol";
diff --git a/packages/contracts-rfq/test/integration/TokenZapV1.t.sol b/packages/contracts-rfq/test/integration/TokenZapV1.t.sol
index fec2ee9042..12b5f9f49a 100644
--- a/packages/contracts-rfq/test/integration/TokenZapV1.t.sol
+++ b/packages/contracts-rfq/test/integration/TokenZapV1.t.sol
@@ -6,7 +6,7 @@ import {BridgeTransactionV2Lib} from "../../contracts/libs/BridgeTransactionV2.s
import {ZapDataV1} from "../../contracts/libs/ZapDataV1.sol";
import {TokenZapV1} from "../../contracts/zaps/TokenZapV1.sol";
-import {MockERC20} from "../MockERC20.sol";
+import {MockERC20} from "../mocks/MockERC20.sol";
import {VaultManyArguments} from "../mocks/VaultManyArguments.sol";
import {Test} from "forge-std/Test.sol";
@@ -45,7 +45,7 @@ abstract contract TokenZapV1IntegrationTest is Test {
function setUp() public virtual {
fastBridge = new FastBridgeV2(address(this));
- fastBridge.grantRole(fastBridge.PROVER_ROLE(), relayer);
+ fastBridge.addProver(relayer);
srcToken = new MockERC20("SRC", 18);
dstToken = new MockERC20("DST", 18);
diff --git a/packages/contracts-rfq/test/UniversalTokenLib.t.sol b/packages/contracts-rfq/test/libs/UniversalTokenLib.t.sol
similarity index 96%
rename from packages/contracts-rfq/test/UniversalTokenLib.t.sol
rename to packages/contracts-rfq/test/libs/UniversalTokenLib.t.sol
index ce46c08b19..8e900e9918 100644
--- a/packages/contracts-rfq/test/UniversalTokenLib.t.sol
+++ b/packages/contracts-rfq/test/libs/UniversalTokenLib.t.sol
@@ -1,11 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
-import {TokenNotContract} from "../contracts/libs/Errors.sol";
+import {TokenNotContract} from "../../contracts/libs/Errors.sol";
-import {MockERC20} from "./MockERC20.sol";
-import {MockRevertingRecipient} from "./MockRevertingRecipient.sol";
-import {UniversalTokenLibHarness} from "./UniversalTokenLibHarness.sol";
+import {UniversalTokenLibHarness} from "../harnesses/UniversalTokenLibHarness.sol";
+import {MockERC20} from "../mocks/MockERC20.sol";
+import {MockRevertingRecipient} from "../mocks/MockRevertingRecipient.sol";
import {Test} from "forge-std/Test.sol";
diff --git a/packages/contracts-rfq/test/FastBridgeMock.sol b/packages/contracts-rfq/test/mocks/FastBridgeMock.sol
similarity index 96%
rename from packages/contracts-rfq/test/FastBridgeMock.sol
rename to packages/contracts-rfq/test/mocks/FastBridgeMock.sol
index 68026550f5..d2f7bcb3c1 100644
--- a/packages/contracts-rfq/test/FastBridgeMock.sol
+++ b/packages/contracts-rfq/test/mocks/FastBridgeMock.sol
@@ -1,10 +1,10 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
-import {Admin} from "../contracts/Admin.sol";
+import {Admin} from "../../contracts/Admin.sol";
-import {FastBridge} from "../contracts/FastBridge.sol";
-import {IFastBridge} from "../contracts/interfaces/IFastBridge.sol";
+import {FastBridge} from "../../contracts/FastBridge.sol";
+import {IFastBridge} from "../../contracts/interfaces/IFastBridge.sol";
contract FastBridgeMock is IFastBridge, Admin {
// @dev the block the contract was deployed at
diff --git a/packages/contracts-rfq/test/MockERC20.sol b/packages/contracts-rfq/test/mocks/MockERC20.sol
similarity index 100%
rename from packages/contracts-rfq/test/MockERC20.sol
rename to packages/contracts-rfq/test/mocks/MockERC20.sol
diff --git a/packages/contracts-rfq/test/MockRevertingRecipient.sol b/packages/contracts-rfq/test/mocks/MockRevertingRecipient.sol
similarity index 100%
rename from packages/contracts-rfq/test/MockRevertingRecipient.sol
rename to packages/contracts-rfq/test/mocks/MockRevertingRecipient.sol
diff --git a/packages/contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol b/packages/contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol
index 3726b7d7b0..5352a5e4fb 100644
--- a/packages/contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol
+++ b/packages/contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol
@@ -3,7 +3,7 @@ pragma solidity 0.8.24;
import {TokenZapV1} from "../../contracts/zaps/TokenZapV1.sol";
-import {MockERC20} from "../MockERC20.sol";
+import {MockERC20} from "../mocks/MockERC20.sol";
import {SimpleVaultMock} from "../mocks/SimpleVaultMock.sol";
import {Test} from "forge-std/Test.sol";
diff --git a/packages/contracts-rfq/test/zaps/TokenZapV1.t.sol b/packages/contracts-rfq/test/zaps/TokenZapV1.t.sol
index 68afd42331..e081831372 100644
--- a/packages/contracts-rfq/test/zaps/TokenZapV1.t.sol
+++ b/packages/contracts-rfq/test/zaps/TokenZapV1.t.sol
@@ -4,7 +4,7 @@ pragma solidity 0.8.24;
import {ZapDataV1} from "../../contracts/libs/ZapDataV1.sol";
import {TokenZapV1} from "../../contracts/zaps/TokenZapV1.sol";
-import {MockERC20} from "../MockERC20.sol";
+import {MockERC20} from "../mocks/MockERC20.sol";
import {NonPayableRecipient} from "../mocks/NonPayableRecipient.sol";
import {RecipientMock} from "../mocks/RecipientMock.sol";
import {VaultManyArguments} from "../mocks/VaultManyArguments.sol";
diff --git a/packages/synapse-interface/CHANGELOG.md b/packages/synapse-interface/CHANGELOG.md
index 12d15839cd..d68d4a38b3 100644
--- a/packages/synapse-interface/CHANGELOG.md
+++ b/packages/synapse-interface/CHANGELOG.md
@@ -3,6 +3,14 @@
All notable changes to this project will be documented in this file.
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
+## [0.40.23](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.22...@synapsecns/synapse-interface@0.40.23) (2024-12-06)
+
+**Note:** Version bump only for package @synapsecns/synapse-interface
+
+
+
+
+
## [0.40.22](https://github.com/synapsecns/sanguine/compare/@synapsecns/synapse-interface@0.40.21...@synapsecns/synapse-interface@0.40.22) (2024-12-02)
diff --git a/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx b/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx
index b8b2ec294c..aef82543a5 100644
--- a/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx
+++ b/packages/synapse-interface/components/layouts/LandingPageWrapper/index.tsx
@@ -211,9 +211,9 @@ function MoreInfoButtons() {
description="See preliminary analytics of the bridge"
/>
>
)
diff --git a/packages/synapse-interface/components/toast/ToastContent.tsx b/packages/synapse-interface/components/toast/ToastContent.tsx
index 361cfee462..3d138fddd1 100644
--- a/packages/synapse-interface/components/toast/ToastContent.tsx
+++ b/packages/synapse-interface/components/toast/ToastContent.tsx
@@ -1,3 +1,4 @@
+import React from 'react'
import toast from 'react-hot-toast'
import {
XIcon,
diff --git a/packages/synapse-interface/components/toast/index.tsx b/packages/synapse-interface/components/toast/index.tsx
index 0ef7675d2c..83d4bde824 100644
--- a/packages/synapse-interface/components/toast/index.tsx
+++ b/packages/synapse-interface/components/toast/index.tsx
@@ -1,7 +1,9 @@
+// @ts-nocheck
+import React from 'react'
import toast, { Toaster, ToastBar } from 'react-hot-toast'
import ToastContent from './ToastContent'
-export default function CustomToaster() {
+const CustomToaster: React.FC = () => {
return (
)
}
+
+export default CustomToaster
diff --git a/packages/synapse-interface/constants/routes.ts b/packages/synapse-interface/constants/routes.ts
index b33db9f19e..1381e2a87c 100644
--- a/packages/synapse-interface/constants/routes.ts
+++ b/packages/synapse-interface/constants/routes.ts
@@ -6,8 +6,8 @@ import {
POOL_PATH,
LANDING_PATH,
BRIDGE_PATH,
- INTERCHAIN_LINK,
SOLANA_BRIDGE_LINK,
+ SYN_TOKEN_LINK,
} from './urls'
export interface RouteObject {
@@ -53,9 +53,9 @@ export const NAVIGATION: RouteObject = {
text: 'Explorer',
match: null,
},
- Contracts: {
- path: INTERCHAIN_LINK,
- text: 'Interchain Network',
+ SYN: {
+ path: SYN_TOKEN_LINK,
+ text: '$SYN',
match: null,
},
Solana: {
diff --git a/packages/synapse-interface/constants/urls/index.tsx b/packages/synapse-interface/constants/urls/index.tsx
index a3cac309ad..5c1c5df241 100644
--- a/packages/synapse-interface/constants/urls/index.tsx
+++ b/packages/synapse-interface/constants/urls/index.tsx
@@ -25,6 +25,7 @@ export const LANDING_PATH = '/landing'
export const EXPLORER_KAPPA = 'https://explorer.synapseprotocol.com/tx/'
export const EXPLORER_PATH = 'https://explorer.synapseprotocol.com/'
export const INTERCHAIN_LINK = 'https://interchain.synapseprotocol.com/'
+export const SYN_TOKEN_LINK = 'https://docs.synapseprotocol.com/docs/About/SYN'
export const SOLANA_BRIDGE_LINK = 'https://solana.synapseprotocol.com/'
export const TERMS_OF_SERVICE_PATH =
'https://explorer.synapseprotocol.com/terms'
diff --git a/packages/synapse-interface/messages/ar.json b/packages/synapse-interface/messages/ar.json
index 33849d6878..09f07631d4 100644
--- a/packages/synapse-interface/messages/ar.json
+++ b/packages/synapse-interface/messages/ar.json
@@ -344,6 +344,7 @@
"Telegram": "تليجرام",
"Functions": "الوظائف",
"Developers": "المطورون",
+ "$SYN": "$SYN",
"Support": "الدعم"
},
"ReturnToMonke": {
diff --git a/packages/synapse-interface/messages/en-US.json b/packages/synapse-interface/messages/en-US.json
index ce150f5e79..8452406b49 100644
--- a/packages/synapse-interface/messages/en-US.json
+++ b/packages/synapse-interface/messages/en-US.json
@@ -344,6 +344,7 @@
"Telegram": "Telegram",
"Functions": "Functions",
"Developers": "Developers",
+ "$SYN": "$SYN",
"Support": "Support"
},
"ReturnToMonke": {
diff --git a/packages/synapse-interface/messages/es.json b/packages/synapse-interface/messages/es.json
index d35b19a8e8..5c121dfbe6 100644
--- a/packages/synapse-interface/messages/es.json
+++ b/packages/synapse-interface/messages/es.json
@@ -344,6 +344,7 @@
"Telegram": "Telegram",
"Functions": "Funciones",
"Developers": "Desarrolladores",
+ "$SYN": "$SYN",
"Support": "Soporte"
},
"ReturnToMonke": {
diff --git a/packages/synapse-interface/messages/fr.json b/packages/synapse-interface/messages/fr.json
index 3298973c04..5f4608d38e 100644
--- a/packages/synapse-interface/messages/fr.json
+++ b/packages/synapse-interface/messages/fr.json
@@ -344,6 +344,7 @@
"Telegram": "Telegram",
"Functions": "Fonctions",
"Developers": "Développeurs",
+ "$SYN": "$SYN",
"Support": "Support"
},
"ReturnToMonke": {
diff --git a/packages/synapse-interface/messages/jp.json b/packages/synapse-interface/messages/jp.json
index 8dff093e1d..ee05d2a385 100644
--- a/packages/synapse-interface/messages/jp.json
+++ b/packages/synapse-interface/messages/jp.json
@@ -344,6 +344,7 @@
"Telegram": "Telegram",
"Functions": "機能",
"Developers": "開発者",
+ "$SYN": "$SYN",
"Support": "サポート"
},
"ReturnToMonke": {
diff --git a/packages/synapse-interface/messages/tr.json b/packages/synapse-interface/messages/tr.json
index a6ac858168..d51a91667a 100644
--- a/packages/synapse-interface/messages/tr.json
+++ b/packages/synapse-interface/messages/tr.json
@@ -344,6 +344,7 @@
"Telegram": "Telegram",
"Functions": "Fonksiyonlar",
"Developers": "Geliştiriciler",
+ "$SYN": "$SYN",
"Support": "Destek"
},
"ReturnToMonke": {
diff --git a/packages/synapse-interface/messages/zh-CN.json b/packages/synapse-interface/messages/zh-CN.json
index fc4d1e5d93..d6a864354b 100644
--- a/packages/synapse-interface/messages/zh-CN.json
+++ b/packages/synapse-interface/messages/zh-CN.json
@@ -344,6 +344,7 @@
"Telegram": "Telegram",
"Functions": "功能",
"Developers": "开发者",
+ "$SYN": "$SYN",
"Support": "支持"
},
"ReturnToMonke": {
diff --git a/packages/synapse-interface/package.json b/packages/synapse-interface/package.json
index 27a5660b73..d2e2ddb88e 100644
--- a/packages/synapse-interface/package.json
+++ b/packages/synapse-interface/package.json
@@ -1,6 +1,6 @@
{
"name": "@synapsecns/synapse-interface",
- "version": "0.40.22",
+ "version": "0.40.23",
"private": true,
"engines": {
"node": ">=18.18.0"
diff --git a/packages/synapse-interface/pages/_app.tsx b/packages/synapse-interface/pages/_app.tsx
index 74e50e8d27..83acb1d8e5 100644
--- a/packages/synapse-interface/pages/_app.tsx
+++ b/packages/synapse-interface/pages/_app.tsx
@@ -52,6 +52,7 @@ function App({ Component, pageProps }: AppProps) {
timeZone="UTC"
messages={pageProps.messages}
>
+ {/* @ts-ignore */}