From 16a4da979528219c2287b58d89f1fd04be994d22 Mon Sep 17 00:00:00 2001 From: trajan0x <83933037+trajan0x@users.noreply.github.com> Date: Thu, 5 Dec 2024 13:25:38 -0400 Subject: [PATCH 01/14] gitattributes foundry deployments (#3439) * gitattribute foundry * vendored->generated --------- Co-authored-by: Trajan0x --- .gitattributes | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) 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 From a9463760a1a0a098548b115e29acda416c5f9ffc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CF=87=C2=B2?= <88190723+ChiTimesChi@users.noreply.github.com> Date: Thu, 5 Dec 2024 19:30:54 +0000 Subject: [PATCH 02/14] chore(contracts-rfq): reorganise mocks, fix build script (#3434) * chore: move mocks to their respecitve directory * chore: update imports * chore: also move UniversalToken tests * build: include harnesses and mocks in the flatten command --- packages/contracts-rfq/.solhintignore | 2 +- packages/contracts-rfq/package.json | 2 +- packages/contracts-rfq/test/FastBridge.t.sol | 2 +- packages/contracts-rfq/test/FastBridgeV2.t.sol | 2 +- .../test/{ => harnesses}/UniversalTokenLibHarness.sol | 2 +- .../contracts-rfq/test/integration/MulticallTarget.t.sol | 2 +- packages/contracts-rfq/test/integration/TokenZapV1.t.sol | 2 +- .../contracts-rfq/test/{ => libs}/UniversalTokenLib.t.sol | 8 ++++---- .../contracts-rfq/test/{ => mocks}/FastBridgeMock.sol | 6 +++--- packages/contracts-rfq/test/{ => mocks}/MockERC20.sol | 0 .../test/{ => mocks}/MockRevertingRecipient.sol | 0 .../contracts-rfq/test/zaps/TokenZapV1.GasBench.t.sol | 2 +- packages/contracts-rfq/test/zaps/TokenZapV1.t.sol | 2 +- 13 files changed, 16 insertions(+), 16 deletions(-) rename packages/contracts-rfq/test/{ => harnesses}/UniversalTokenLibHarness.sol (93%) rename packages/contracts-rfq/test/{ => libs}/UniversalTokenLib.t.sol (96%) rename packages/contracts-rfq/test/{ => mocks}/FastBridgeMock.sol (96%) rename packages/contracts-rfq/test/{ => mocks}/MockERC20.sol (100%) rename packages/contracts-rfq/test/{ => mocks}/MockRevertingRecipient.sol (100%) 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/package.json b/packages/contracts-rfq/package.json index 29c224390d..93c1d15d69 100644 --- a/packages/contracts-rfq/package.json +++ b/packages/contracts-rfq/package.json @@ -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.t.sol b/packages/contracts-rfq/test/FastBridgeV2.t.sol index 5a99259ad2..04bfeb7a36 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.t.sol @@ -11,7 +11,7 @@ import {IFastBridgeV2} from "../contracts/interfaces/IFastBridgeV2.sol"; import {FastBridgeV2} from "../contracts/FastBridgeV2.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"; 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/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..92d3874970 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"; 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"; From 288dc7e0c143327234845122564ff6790386cb13 Mon Sep 17 00:00:00 2001 From: ChiTimesChi Date: Thu, 5 Dec 2024 19:35:10 +0000 Subject: [PATCH 03/14] Publish - @synapsecns/contracts-rfq@0.14.7 --- packages/contracts-rfq/CHANGELOG.md | 8 ++++++++ packages/contracts-rfq/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) 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/package.json b/packages/contracts-rfq/package.json index 93c1d15d69..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": [ From 55b1d081f6a17d6146f42769c56179e579d807f8 Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Fri, 6 Dec 2024 13:18:22 -0500 Subject: [PATCH 04/14] Add SYN to docs --- docs/bridge/docs/01-About/04-SYN.md | 22 +++++++++++++++++ docs/bridge/docs/05-Contracts/09-SYN.md | 32 +++++++++++++++++++++++++ 2 files changed, 54 insertions(+) create mode 100644 docs/bridge/docs/01-About/04-SYN.md create mode 100644 docs/bridge/docs/05-Contracts/09-SYN.md 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..33114ce99d --- /dev/null +++ b/docs/bridge/docs/01-About/04-SYN.md @@ -0,0 +1,22 @@ +--- +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) | + +
+ +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) | From 1131845460f90c037d8982b660d2b79d5bdfd12a Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Fri, 6 Dec 2024 18:29:59 +0000 Subject: [PATCH 05/14] Publish - @synapsecns/bridge-docs@0.5.12 --- docs/bridge/CHANGELOG.md | 8 ++++++++ docs/bridge/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/bridge/CHANGELOG.md b/docs/bridge/CHANGELOG.md index b99c54635a..9242eb3ab2 100644 --- a/docs/bridge/CHANGELOG.md +++ b/docs/bridge/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.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/package.json b/docs/bridge/package.json index e908b9b17a..f84b4caa24 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.12", "private": true, "scripts": { "docusaurus": "docusaurus", From 49cc5efc5996bc999c61932b159b4da5a53c2b66 Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Fri, 6 Dec 2024 13:44:57 -0500 Subject: [PATCH 06/14] Adds link to docs --- .../components/layouts/LandingPageWrapper/index.tsx | 6 +++--- packages/synapse-interface/constants/routes.ts | 8 ++++---- packages/synapse-interface/constants/urls/index.tsx | 1 + packages/synapse-interface/messages/ar.json | 1 + packages/synapse-interface/messages/en-US.json | 1 + packages/synapse-interface/messages/es.json | 1 + packages/synapse-interface/messages/fr.json | 1 + packages/synapse-interface/messages/jp.json | 1 + packages/synapse-interface/messages/tr.json | 1 + packages/synapse-interface/messages/zh-CN.json | 1 + 10 files changed, 15 insertions(+), 7 deletions(-) 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/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": { From 4d14f543cecb73e97bce53134df190201173d0b3 Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Fri, 6 Dec 2024 14:02:47 -0500 Subject: [PATCH 07/14] React imports --- packages/synapse-interface/components/toast/ToastContent.tsx | 1 + packages/synapse-interface/components/toast/index.tsx | 1 + 2 files changed, 2 insertions(+) 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..1b6112ad9b 100644 --- a/packages/synapse-interface/components/toast/index.tsx +++ b/packages/synapse-interface/components/toast/index.tsx @@ -1,3 +1,4 @@ +import React from 'react' import toast, { Toaster, ToastBar } from 'react-hot-toast' import ToastContent from './ToastContent' From fab9226bc7eeb71948b82f590d8bb349f6fb7996 Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Fri, 6 Dec 2024 14:12:59 -0500 Subject: [PATCH 08/14] Type custom toaster --- packages/synapse-interface/components/toast/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/synapse-interface/components/toast/index.tsx b/packages/synapse-interface/components/toast/index.tsx index 1b6112ad9b..4659dc802b 100644 --- a/packages/synapse-interface/components/toast/index.tsx +++ b/packages/synapse-interface/components/toast/index.tsx @@ -2,7 +2,7 @@ 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 From 2067b1d1e60d30967faa7128156b221998e016ed Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Fri, 6 Dec 2024 14:30:59 -0500 Subject: [PATCH 09/14] Nocheck --- packages/synapse-interface/components/toast/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/synapse-interface/components/toast/index.tsx b/packages/synapse-interface/components/toast/index.tsx index 4659dc802b..83d4bde824 100644 --- a/packages/synapse-interface/components/toast/index.tsx +++ b/packages/synapse-interface/components/toast/index.tsx @@ -1,3 +1,4 @@ +// @ts-nocheck import React from 'react' import toast, { Toaster, ToastBar } from 'react-hot-toast' import ToastContent from './ToastContent' From 9a375d065ff1eab4aa396911ec988e7cf2c6c756 Mon Sep 17 00:00:00 2001 From: abtestingalpha Date: Fri, 6 Dec 2024 14:38:50 -0500 Subject: [PATCH 10/14] ignore --- packages/synapse-interface/pages/_app.tsx | 1 + 1 file changed, 1 insertion(+) 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 */} Date: Fri, 6 Dec 2024 19:55:57 +0000 Subject: [PATCH 11/14] Publish - @synapsecns/synapse-interface@0.40.23 --- packages/synapse-interface/CHANGELOG.md | 8 ++++++++ packages/synapse-interface/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) 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/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" From c3d8cfb692722c47009e9dd6e5b6724be88a78e3 Mon Sep 17 00:00:00 2001 From: Moses <103143573+Defi-Moses@users.noreply.github.com> Date: Sun, 8 Dec 2024 02:32:27 +0000 Subject: [PATCH 12/14] new venues (#3444) --- docs/bridge/docs/01-About/04-SYN.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/bridge/docs/01-About/04-SYN.md b/docs/bridge/docs/01-About/04-SYN.md index 33114ce99d..09bbd9239c 100644 --- a/docs/bridge/docs/01-About/04-SYN.md +++ b/docs/bridge/docs/01-About/04-SYN.md @@ -16,7 +16,9 @@ Liquidity for the [$SYN](https://coinmarketcap.com/currencies/synapse-2/) token | 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). From bf6b5bfc5360aa3b435151fd679a5c512a315245 Mon Sep 17 00:00:00 2001 From: Defi-Moses Date: Sun, 8 Dec 2024 02:36:44 +0000 Subject: [PATCH 13/14] Publish - @synapsecns/bridge-docs@0.5.13 --- docs/bridge/CHANGELOG.md | 8 ++++++++ docs/bridge/package.json | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/docs/bridge/CHANGELOG.md b/docs/bridge/CHANGELOG.md index 9242eb3ab2..ccf73ae349 100644 --- a/docs/bridge/CHANGELOG.md +++ b/docs/bridge/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.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 diff --git a/docs/bridge/package.json b/docs/bridge/package.json index f84b4caa24..de0c5ebf15 100644 --- a/docs/bridge/package.json +++ b/docs/bridge/package.json @@ -1,6 +1,6 @@ { "name": "@synapsecns/bridge-docs", - "version": "0.5.12", + "version": "0.5.13", "private": true, "scripts": { "docusaurus": "docusaurus", From 3d3530de4867d66458399ee146e29a6cffdc44e5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=CF=87=C2=B2?= <88190723+ChiTimesChi@users.noreply.github.com> Date: Tue, 10 Dec 2024 22:22:09 +0000 Subject: [PATCH 14/14] fix(contracts-rfq): FastBridge V2 prove race condition (#3435) * feat: scaffold new prover management * test: define unit tests for prover management * feat: implement prover management * feat: deprecate prover role constant * test: update to constant deprecation * feat: additional prover getters * feat: prover timeout management * test: coverage for timeout management * test: should save proverID for origin txs * feat: save proverID, pack BridgeTxDetails * test: define expected behavior for prover timeouts * feat: implement timeout penalty in dispute * fix: Role Admin should be handling provers, as before * refactor: "prover timeout" -> "dispute penalty time" --- packages/contracts-rfq/contracts/AdminV2.sol | 130 +++++++++- .../contracts-rfq/contracts/FastBridgeV2.sol | 27 +- .../contracts/interfaces/IAdminV2.sol | 32 +++ .../contracts/interfaces/IAdminV2Errors.sol | 4 + .../contracts/interfaces/IFastBridgeV2.sol | 3 +- packages/contracts-rfq/foundry.toml | 1 + .../test/FastBridgeV2.Management.t.sol | 211 +++++++++++++++- .../test/FastBridgeV2.Src.Base.t.sol | 9 +- .../contracts-rfq/test/FastBridgeV2.Src.t.sol | 237 +++++++++++++++--- .../contracts-rfq/test/FastBridgeV2.t.sol | 3 +- .../FastBridgeV2.MulticallTarget.t.sol | 6 +- .../test/integration/TokenZapV1.t.sol | 2 +- 12 files changed, 602 insertions(+), 63 deletions(-) diff --git a/packages/contracts-rfq/contracts/AdminV2.sol b/packages/contracts-rfq/contracts/AdminV2.sol index 64806426c3..0ae47d9fc0 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; @@ -60,6 +71,14 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { /// @notice The delay period after which a transaction can be permissionlessly cancelled. uint256 public cancelDelay; + /// @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; @@ -67,6 +86,35 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { constructor(address defaultAdmin) { _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin); _setCancelDelay(DEFAULT_CANCEL_DELAY); + _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 @@ -74,6 +122,11 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { _setCancelDelay(newCancelDelay); } + /// @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(); @@ -98,6 +151,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 { @@ -106,4 +219,13 @@ contract AdminV2 is AccessControlEnumerable, IAdminV2, IAdminV2Errors { cancelDelay = newCancelDelay; emit CancelDelayUpdated(oldCancelDelay, newCancelDelay); } + + /// @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 f5e9cbd363..a03e4ddb2e 100644 --- a/packages/contracts-rfq/contracts/FastBridgeV2.sol +++ b/packages/contracts-rfq/contracts/FastBridgeV2.sol @@ -108,9 +108,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(); @@ -118,9 +119,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; @@ -347,7 +353,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(); @@ -355,7 +363,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); @@ -371,7 +380,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(); @@ -472,14 +481,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 90115d2f49..44c4009041 100644 --- a/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol +++ b/packages/contracts-rfq/contracts/interfaces/IAdminV2.sol @@ -3,13 +3,29 @@ pragma solidity ^0.8.4; interface IAdminV2 { event CancelDelayUpdated(uint256 oldCancelDelay, uint256 newCancelDelay); + 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; + /// @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. @@ -18,4 +34,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/test/FastBridgeV2.Management.t.sol b/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol index ab9ea59341..814e6d049c 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.Management.t.sol @@ -2,22 +2,31 @@ 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 DisputePenaltyTimeUpdated(uint256 oldDisputePenaltyTime, uint256 newDisputePenaltyTime); event FeeRateUpdated(uint256 oldFeeRate, uint256 newFeeRate); event FeesSwept(address token, address recipient, uint256 amount); @@ -36,6 +45,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); @@ -46,6 +65,11 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { fastBridge.setCancelDelay(newCancelDelay); } + 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); @@ -68,8 +92,157 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { setGovernor(caller, governorA); } - function test_defaultCancelDelay() public view { + function test_defaultValues() public view { assertEq(fastBridge.cancelDelay(), DEFAULT_CANCEL_DELAY); + 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 ══════════════════════════════════════════════════ @@ -90,7 +263,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); } @@ -100,6 +273,34 @@ contract FastBridgeV2ManagementTest is FastBridgeV2Test, IAdminV2Errors { setCancelDelay(caller, 4 days); } + // ═════════════════════════════════════════ 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 { @@ -118,7 +319,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 04bfeb7a36..d4fab0a1ae 100644 --- a/packages/contracts-rfq/test/FastBridgeV2.t.sol +++ b/packages/contracts-rfq/test/FastBridgeV2.t.sol @@ -9,6 +9,7 @@ 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 "./mocks/MockERC20.sol"; @@ -18,7 +19,7 @@ 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/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/TokenZapV1.t.sol b/packages/contracts-rfq/test/integration/TokenZapV1.t.sol index 92d3874970..12b5f9f49a 100644 --- a/packages/contracts-rfq/test/integration/TokenZapV1.t.sol +++ b/packages/contracts-rfq/test/integration/TokenZapV1.t.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);