From ecb9f0fb45e7978875bf0c598a258ea4304a5b4d Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Wed, 15 May 2024 16:38:41 +0300 Subject: [PATCH 01/17] Added `upgradeToWithSigAndCall` function to the UUPSSignableUpgradeable --- contracts/utils/UUPSSignableUpgradeable.sol | 17 +++++++++++++++++ test/bridge/Upgradeable.test.ts | 8 ++++++++ 2 files changed, 25 insertions(+) diff --git a/contracts/utils/UUPSSignableUpgradeable.sol b/contracts/utils/UUPSSignableUpgradeable.sol index 71bf894..c315b2b 100644 --- a/contracts/utils/UUPSSignableUpgradeable.sol +++ b/contracts/utils/UUPSSignableUpgradeable.sol @@ -30,4 +30,21 @@ abstract contract UUPSSignableUpgradeable is UUPSUpgradeable { _authorizeUpgrade(newImplementation_, signatures_); _upgradeToAndCallUUPS(newImplementation_, new bytes(0), false); } + + /** + * @notice Upgrade the implementation of the proxy to `newImplementation`, and subsequently execute the function call + * encoded in `data`. + * + * Calls {_authorizeUpgrade} with the provided `newImplementation` and `signatures`. + * + * Emits an {Upgraded} event. + */ + function upgradeToWithSigAndCall( + address newImplementation_, + bytes[] calldata signatures_, + bytes calldata data_ + ) external virtual onlyProxy { + _authorizeUpgrade(newImplementation_, signatures_); + _upgradeToAndCallUUPS(newImplementation_, data_, true); + } } diff --git a/test/bridge/Upgradeable.test.ts b/test/bridge/Upgradeable.test.ts index d20228a..e066ab5 100644 --- a/test/bridge/Upgradeable.test.ts +++ b/test/bridge/Upgradeable.test.ts @@ -50,6 +50,10 @@ describe("Upgradeable", () => { await expect(newBridge.upgradeToWithSig(await newBridge.getAddress(), [])).to.be.rejectedWith( "Function must be called through delegatecall", ); + + await expect(newBridge.upgradeToWithSigAndCall(await newBridge.getAddress(), [], "0x")).to.be.rejectedWith( + "Function must be called through delegatecall", + ); }); it("should upgrade implementation", async () => { @@ -85,6 +89,10 @@ describe("Upgradeable", () => { await expect(proxyBridge.connect(SECOND).upgradeToWithSig(await newBridge.getAddress(), [])).to.be.rejectedWith( "Ownable: caller is not the owner", ); + + await expect( + proxyBridge.connect(SECOND).upgradeToWithSigAndCall(await newBridge.getAddress(), [], "0x"), + ).to.be.rejectedWith("Ownable: caller is not the owner"); }); it("should receive ether through proxy", async () => { From 790f065bc31b2445678658cc98093abfc29c4d77 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Wed, 15 May 2024 16:40:25 +0300 Subject: [PATCH 02/17] Added base implementation of USDCManager contract --- contracts/bridge/BridgeV2.sol | 8 ++ contracts/mocks/bridge/BridgeV3.sol | 8 ++ contracts/mocks/utils/USDCManagerV2.sol | 17 +++++ contracts/utils/USDCManager.sol | 36 +++++++++ test/bridge/USDCHandOver.test.ts | 99 +++++++++++++++++++++++++ 5 files changed, 168 insertions(+) create mode 100644 contracts/bridge/BridgeV2.sol create mode 100644 contracts/mocks/bridge/BridgeV3.sol create mode 100644 contracts/mocks/utils/USDCManagerV2.sol create mode 100644 contracts/utils/USDCManager.sol create mode 100644 test/bridge/USDCHandOver.test.ts diff --git a/contracts/bridge/BridgeV2.sol b/contracts/bridge/BridgeV2.sol new file mode 100644 index 0000000..c7d75ee --- /dev/null +++ b/contracts/bridge/BridgeV2.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {Bridge} from "./Bridge.sol"; + +import {USDCManager} from "../utils/USDCManager.sol"; + +contract BridgeV2 is Bridge, USDCManager {} diff --git a/contracts/mocks/bridge/BridgeV3.sol b/contracts/mocks/bridge/BridgeV3.sol new file mode 100644 index 0000000..c15c06d --- /dev/null +++ b/contracts/mocks/bridge/BridgeV3.sol @@ -0,0 +1,8 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {USDCManagerV2} from "../utils/USDCManagerV2.sol"; + +import {Bridge} from "../../bridge/Bridge.sol"; + +contract BridgeV3 is Bridge, USDCManagerV2 {} diff --git a/contracts/mocks/utils/USDCManagerV2.sol b/contracts/mocks/utils/USDCManagerV2.sol new file mode 100644 index 0000000..24c323e --- /dev/null +++ b/contracts/mocks/utils/USDCManagerV2.sol @@ -0,0 +1,17 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {USDCManager, IUSDCType} from "../../utils/USDCManager.sol"; + +contract USDCManagerV2 is USDCManager { + function __USDCManager_init( + IUSDCType lockedUSDCAddress_, + address managerAddress_ + ) external override { + require(initializedVersion < 2, "USDCManager: already initialized"); + initializedVersion = 2; + + managerAddress = managerAddress_; + lockedUSDCAddress = lockedUSDCAddress_; + } +} diff --git a/contracts/utils/USDCManager.sol b/contracts/utils/USDCManager.sol new file mode 100644 index 0000000..9f6347f --- /dev/null +++ b/contracts/utils/USDCManager.sol @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.9; + +import {IUSDCType} from "../interfaces/tokens/IUSDCType.sol"; + +contract USDCManager { + IUSDCType public lockedUSDCAddress; + + address public managerAddress; + + uint256 public initializedVersion; + + modifier onlyManager() { + _requireOnlyManager(); + _; + } + + function __USDCManager_init( + IUSDCType lockedUSDCAddress_, + address managerAddress_ + ) external virtual { + require(initializedVersion < 1, "USDCManager: already initialized"); + initializedVersion = 1; + + managerAddress = managerAddress_; + lockedUSDCAddress = lockedUSDCAddress_; + } + + function burnLockedUSDC() external onlyManager { + lockedUSDCAddress.burn(lockedUSDCAddress.balanceOf(address(this))); + } + + function _requireOnlyManager() internal view { + require(msg.sender == managerAddress, "USDCManager: only manager can call this method"); + } +} diff --git a/test/bridge/USDCHandOver.test.ts b/test/bridge/USDCHandOver.test.ts new file mode 100644 index 0000000..8aea820 --- /dev/null +++ b/test/bridge/USDCHandOver.test.ts @@ -0,0 +1,99 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; + +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + +import { wei } from "@scripts"; +import { Reverter } from "@test-helpers"; + +import { BridgeV2, Bridge, BridgeV3, USDCTokenType } from "@ethers-v6"; + +describe("USDCHandOver", () => { + const reverter = new Reverter(); + + const baseBalance = wei("1000"); + + let OWNER: SignerWithAddress; + let FIRST: SignerWithAddress; + let SECOND: SignerWithAddress; + + let bridge: Bridge | BridgeV2 | BridgeV3; + + let erc20: USDCTokenType; + + before("setup", async () => { + [OWNER, FIRST, SECOND] = await ethers.getSigners(); + + const Bridge = await ethers.getContractFactory("Bridge"); + + bridge = await Bridge.deploy(); + + const Proxy = await ethers.getContractFactory("ERC1967Proxy"); + const proxy = await Proxy.deploy(await bridge.getAddress(), "0x"); + + bridge = Bridge.attach(await proxy.getAddress()) as Bridge; + + await bridge.__Bridge_init([OWNER.address], ethers.ZeroAddress, "1", false); + + const ERC20MB = await ethers.getContractFactory("USDCTokenType"); + + erc20 = await ERC20MB.deploy("Mock", "MK"); + await erc20.mint(await bridge.getAddress(), baseBalance); + + await reverter.snapshot(); + }); + + afterEach(reverter.revert); + + it("should upgrade the Bridge and burn USDC only by the specified account", async () => { + const NewBridge = await ethers.getContractFactory("BridgeV2"); + const newBridge = await NewBridge.deploy(); + + const initData = newBridge.interface.encodeFunctionData("__USDCManager_init", [ + await erc20.getAddress(), + SECOND.address, + ]); + + await bridge.upgradeToWithSigAndCall(await newBridge.getAddress(), [], initData); + + bridge = NewBridge.attach(await bridge.getAddress()) as BridgeV2; + + expect(await bridge.managerAddress()).to.be.equal(SECOND.address); + expect(await bridge.lockedUSDCAddress()).to.be.equal(await erc20.getAddress()); + expect(await bridge.initializedVersion()).to.be.equal("1"); + + await expect(bridge.__USDCManager_init(await erc20.getAddress(), SECOND.address)).to.be.revertedWith( + "USDCManager: already initialized", + ); + await expect(bridge.burnLockedUSDC()).to.be.revertedWith("USDCManager: only manager can call this method"); + + expect(await erc20.balanceOf(await bridge.getAddress())).to.be.equal(baseBalance); + + await bridge.connect(SECOND).burnLockedUSDC(); + + expect(await erc20.balanceOf(await bridge.getAddress())).to.be.equal(0); + }); + + it("should re-upgrade the Bridge", async () => { + const BridgeV2 = await ethers.getContractFactory("BridgeV2"); + const bridgeV2 = await BridgeV2.deploy(); + + let initData = bridgeV2.interface.encodeFunctionData("__USDCManager_init", [ + await erc20.getAddress(), + SECOND.address, + ]); + await bridge.upgradeToWithSigAndCall(await bridgeV2.getAddress(), [], initData); + + bridge = BridgeV2.attach(await bridge.getAddress()) as BridgeV2; + + expect(await bridge.managerAddress()).to.be.equal(SECOND.address); + + const BridgeV3 = await ethers.getContractFactory("BridgeV3"); + const bridgeV3 = await BridgeV3.deploy(); + + initData = bridgeV3.interface.encodeFunctionData("__USDCManager_init", [await erc20.getAddress(), FIRST.address]); + await bridge.upgradeToWithSigAndCall(await bridgeV3.getAddress(), [], initData); + + expect(await bridge.managerAddress()).to.be.equal(FIRST.address); + }); +}); From 9f16d090791c90e5bc22879f9dae8930b95280c4 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Mon, 20 May 2024 16:15:57 +0300 Subject: [PATCH 03/17] DRAFT: Started Documentation --- contracts/bridge/BridgeV2.sol | 6 ++++++ contracts/utils/USDCManager.sol | 20 ++++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/contracts/bridge/BridgeV2.sol b/contracts/bridge/BridgeV2.sol index c7d75ee..c668674 100644 --- a/contracts/bridge/BridgeV2.sol +++ b/contracts/bridge/BridgeV2.sol @@ -5,4 +5,10 @@ import {Bridge} from "./Bridge.sol"; import {USDCManager} from "../utils/USDCManager.sol"; +/** + * @title BridgeV2 Contract + * + * Inherits USDCManager to simplify USDC hand over process according to: + * https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#2-ability-to-burn-locked-usdc + */ contract BridgeV2 is Bridge, USDCManager {} diff --git a/contracts/utils/USDCManager.sol b/contracts/utils/USDCManager.sol index 9f6347f..d8aa96f 100644 --- a/contracts/utils/USDCManager.sol +++ b/contracts/utils/USDCManager.sol @@ -3,11 +3,30 @@ pragma solidity ^0.8.9; import {IUSDCType} from "../interfaces/tokens/IUSDCType.sol"; +/** + * @title USDCManager Contract + * + * This is a utility contract, that provides enough functionality mentioned in the document to hand over control of bridging USDC contracts to Circle: + * https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#2-ability-to-burn-locked-usdc + * + * This contracts provides minimal functionality needed to upgrade existing Brdige contract to the version of the Bridge contracts, that also inherits from + * this contract. Also makes it possible to upgrade a few times if needed. + */ contract USDCManager { + /** + * @notice The address of the USDC token contract on the chain. + */ IUSDCType public lockedUSDCAddress; + /** + * @notice The EOA that will be allowed to call the `burnLockedUSDC` method. + */ address public managerAddress; + /** + * @notice The initialized version of the contract. + * Could be bigger than 1 if the contract is upgraded a few times. + */ uint256 public initializedVersion; modifier onlyManager() { @@ -15,6 +34,7 @@ contract USDCManager { _; } + /****/ function __USDCManager_init( IUSDCType lockedUSDCAddress_, address managerAddress_ From d49bc69513db7f5726db228e64c5e961328533ef Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 21 May 2024 09:57:36 +0300 Subject: [PATCH 04/17] Completed NatSpec documentation for the USDCManager contract --- contracts/bridge/BridgeV2.sol | 2 +- contracts/utils/USDCManager.sol | 75 +++++++++++++++++++++++---------- 2 files changed, 53 insertions(+), 24 deletions(-) diff --git a/contracts/bridge/BridgeV2.sol b/contracts/bridge/BridgeV2.sol index c668674..f1668d3 100644 --- a/contracts/bridge/BridgeV2.sol +++ b/contracts/bridge/BridgeV2.sol @@ -8,7 +8,7 @@ import {USDCManager} from "../utils/USDCManager.sol"; /** * @title BridgeV2 Contract * - * Inherits USDCManager to simplify USDC hand over process according to: + * This contract inherits from USDCManager to simplify the USDC handover process according to: * https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#2-ability-to-burn-locked-usdc */ contract BridgeV2 is Bridge, USDCManager {} diff --git a/contracts/utils/USDCManager.sol b/contracts/utils/USDCManager.sol index d8aa96f..f6db984 100644 --- a/contracts/utils/USDCManager.sol +++ b/contracts/utils/USDCManager.sol @@ -6,51 +6,80 @@ import {IUSDCType} from "../interfaces/tokens/IUSDCType.sol"; /** * @title USDCManager Contract * - * This is a utility contract, that provides enough functionality mentioned in the document to hand over control of bridging USDC contracts to Circle: + * This utility contract provides the functionality mentioned in the document to hand over control of bridging USDC contracts to Circle: * https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#2-ability-to-burn-locked-usdc * - * This contracts provides minimal functionality needed to upgrade existing Brdige contract to the version of the Bridge contracts, that also inherits from - * this contract. Also makes it possible to upgrade a few times if needed. + * The contract offers the minimal functionality needed to upgrade the existing Bridge contract to a version that inherits from this contract. + * It also allows for multiple upgrades if necessary. + * + * To prevent any upgradability issues, this contract uses [EIP7201](https://eips.ethereum.org/EIPS/eip-7201). + * + * The `USDCManagerStorage` structure represents all of the storage variables inside the contract, where: + * - `lockedUSDCAddress` is the address of the USDC token contract on the chain. + * - `managerAddress` is the EOA that will be allowed to call the `burnLockedUSDC` method. + * - `initializedVersion` is the initialized version of the contract. It could be greater than 1 if the contract is upgraded multiple times. */ contract USDCManager { - /** - * @notice The address of the USDC token contract on the chain. - */ - IUSDCType public lockedUSDCAddress; + /// @custom:storage-location erc7201:qtum.storage.USDCManager + struct USDCManagerStorage { + IUSDCType lockedUSDCAddress; + address managerAddress; + uint256 initializedVersion; + } - /** - * @notice The EOA that will be allowed to call the `burnLockedUSDC` method. - */ - address public managerAddress; + // keccak256(abi.encode(uint256(keccak256("qtum.storage.USDCManager")) - 1)) & ~bytes32(uint256(0xff)) + bytes32 private constant USDCManagerStorageLocation = + 0x95d68dd162e0430e18daee917edf8bcd51a878f4fbf5d7005f16074d5f952e00; - /** - * @notice The initialized version of the contract. - * Could be bigger than 1 if the contract is upgraded a few times. - */ - uint256 public initializedVersion; + function _getUSDCManagerStorage() private pure returns (USDCManagerStorage storage $) { + assembly { + $.slot := USDCManagerStorageLocation + } + } modifier onlyManager() { _requireOnlyManager(); _; } - /****/ + /** + * @notice Initializer function that should be called in the same transaction as the proxy upgrade (`upgradeToWithSigAndCall` function should be used). + * It is expected that `initializedVersion` could be greater than 1 in case the Bridge needs to be reinitialized, for example, due to an incorrect first initialization. + */ function __USDCManager_init( IUSDCType lockedUSDCAddress_, address managerAddress_ ) external virtual { - require(initializedVersion < 1, "USDCManager: already initialized"); - initializedVersion = 1; + USDCManagerStorage storage $ = _getUSDCManagerStorage(); + + require($.initializedVersion < 1, "USDCManager: already initialized"); + $.initializedVersion = 1; - managerAddress = managerAddress_; - lockedUSDCAddress = lockedUSDCAddress_; + $.managerAddress = managerAddress_; + $.lockedUSDCAddress = lockedUSDCAddress_; } + /** + * As mentioned in the [Ability to burn locked USDC](https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#2-ability-to-burn-locked-usdc), + * the `burnLockedUSDC` function must, at a minimum: + * 1) Be only callable by an address that Circle specifies closer to the time of the upgrade. + * 2) Burn the amount of USDC held by the bridge that corresponds precisely to the circulating total supply of bridged USDC established by the supply lock. + * + * Point 1) is implemented in the `onlyManager` modifier. + * Point 2) is implemented by burning all the USDC tokens held by the contract. + */ function burnLockedUSDC() external onlyManager { - lockedUSDCAddress.burn(lockedUSDCAddress.balanceOf(address(this))); + USDCManagerStorage storage $ = _getUSDCManagerStorage(); + + $.lockedUSDCAddress.burn($.lockedUSDCAddress.balanceOf(address(this))); } + /** + * @notice This function enforces that only the manager can call it. + */ function _requireOnlyManager() internal view { - require(msg.sender == managerAddress, "USDCManager: only manager can call this method"); + USDCManagerStorage storage $ = _getUSDCManagerStorage(); + + require(msg.sender == $.managerAddress, "USDCManager: only manager can call this method"); } } From 7217d87448fe966b32ab1e42431dcab92456810f Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 21 May 2024 13:43:02 +0300 Subject: [PATCH 05/17] Updated @solarity/hardhat-migrate version to 2.1.8 --- package-lock.json | 8 ++++---- package.json | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index e99980d..c9009fc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -18,7 +18,7 @@ "@nomicfoundation/hardhat-ethers": "^3.0.5", "@solarity/hardhat-gobind": "v1.2.1", "@solarity/hardhat-habits": "^1.0.0", - "@solarity/hardhat-migrate": "^2.1.3", + "@solarity/hardhat-migrate": "^2.1.8", "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", "@types/chai": "^4.3.12", @@ -2013,9 +2013,9 @@ } }, "node_modules/@solarity/hardhat-migrate": { - "version": "2.1.5", - "resolved": "https://registry.npmjs.org/@solarity/hardhat-migrate/-/hardhat-migrate-2.1.5.tgz", - "integrity": "sha512-iQxGFURYvfFWyeE5XAFHYZgNVKQ8FQca985LLOGXKfadsTwmC76xAxlRwngBAqCsnzSV2aMuxpf0BOGBfaJ89Q==", + "version": "2.1.8", + "resolved": "https://registry.npmjs.org/@solarity/hardhat-migrate/-/hardhat-migrate-2.1.8.tgz", + "integrity": "sha512-HWQ660heuHWT1I3aAFTknJ2+OFZ9cVOKMeBjMR3G05/td7mxGhCYSYZXl0fZeNNOjauNjgw/L4hIqImFfJ8tmA==", "dev": true, "dependencies": { "@nomicfoundation/hardhat-ethers": "3.0.5", diff --git a/package.json b/package.json index 6737aef..11fbd9d 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,10 @@ "private-network": "npx hardhat node", "generate-types": "TYPECHAIN_FORCE=true npx hardhat typechain", "generate-go-bindings": "npx hardhat gobind --outdir ./go-bindings", - "deploy-ethereum": "npx hardhat migrate --network eth_mainnet", - "deploy-sepolia": "npx hardhat migrate --network sepolia", - "deploy-qtum-mainnet": "npx hardhat migrate --network qtumMainnet", - "deploy-qtum-testnet": "npx hardhat migrate --network qtumTestnet", + "deploy-ethereum": "npx hardhat migrate --network eth_mainnet --only 1", + "deploy-sepolia": "npx hardhat migrate --network sepolia --only 1", + "deploy-qtum-mainnet": "npx hardhat migrate --network qtumMainnet --only 1", + "deploy-qtum-testnet": "npx hardhat migrate --network qtumTestnet --only 1", "lint-fix": "npm run lint-sol-fix && npm run lint-ts-fix && npm run lint-json-fix", "lint-json-fix": "prettier --write \"./[a-zA-Z0-9.]+(?!-lock).json\"", "lint-ts-fix": "prettier --write \"./**/*.ts\"", @@ -30,7 +30,7 @@ "@nomicfoundation/hardhat-ethers": "^3.0.5", "@solarity/hardhat-gobind": "v1.2.1", "@solarity/hardhat-habits": "^1.0.0", - "@solarity/hardhat-migrate": "^2.1.3", + "@solarity/hardhat-migrate": "^2.1.8", "@typechain/ethers-v6": "^0.5.1", "@typechain/hardhat": "^9.1.0", "@types/chai": "^4.3.12", From 53e189f16a5b67eff60ed92b5c30c75d7a21348f Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 21 May 2024 13:43:23 +0300 Subject: [PATCH 06/17] Fixed tests failing --- contracts/mocks/utils/USDCManagerV2.sol | 10 ++++++---- contracts/utils/USDCManager.sol | 23 ++++++++++++++++++++++- test/bridge/USDCHandOver.test.ts | 10 +++++----- 3 files changed, 33 insertions(+), 10 deletions(-) diff --git a/contracts/mocks/utils/USDCManagerV2.sol b/contracts/mocks/utils/USDCManagerV2.sol index 24c323e..4a0c6ba 100644 --- a/contracts/mocks/utils/USDCManagerV2.sol +++ b/contracts/mocks/utils/USDCManagerV2.sol @@ -8,10 +8,12 @@ contract USDCManagerV2 is USDCManager { IUSDCType lockedUSDCAddress_, address managerAddress_ ) external override { - require(initializedVersion < 2, "USDCManager: already initialized"); - initializedVersion = 2; + USDCManagerStorage storage $ = _getUSDCManagerStorage(); - managerAddress = managerAddress_; - lockedUSDCAddress = lockedUSDCAddress_; + require($.initializedVersion < 2, "USDCManager: already initialized"); + $.initializedVersion = 2; + + $.managerAddress = managerAddress_; + $.lockedUSDCAddress = lockedUSDCAddress_; } } diff --git a/contracts/utils/USDCManager.sol b/contracts/utils/USDCManager.sol index f6db984..c0b45de 100644 --- a/contracts/utils/USDCManager.sol +++ b/contracts/utils/USDCManager.sol @@ -31,7 +31,7 @@ contract USDCManager { bytes32 private constant USDCManagerStorageLocation = 0x95d68dd162e0430e18daee917edf8bcd51a878f4fbf5d7005f16074d5f952e00; - function _getUSDCManagerStorage() private pure returns (USDCManagerStorage storage $) { + function _getUSDCManagerStorage() internal pure returns (USDCManagerStorage storage $) { assembly { $.slot := USDCManagerStorageLocation } @@ -74,6 +74,27 @@ contract USDCManager { $.lockedUSDCAddress.burn($.lockedUSDCAddress.balanceOf(address(this))); } + /** + * @notice Returns the address of the manager. + */ + function getManagerAddress() external view returns (address) { + return _getUSDCManagerStorage().managerAddress; + } + + /** + * @notice Returns the address of the locked USDC token. + */ + function getLockedUSDCAddress() external view returns (IUSDCType) { + return _getUSDCManagerStorage().lockedUSDCAddress; + } + + /** + * @notice Returns the initialized version of the contract. + */ + function getInitializedVersion() external view returns (uint256) { + return _getUSDCManagerStorage().initializedVersion; + } + /** * @notice This function enforces that only the manager can call it. */ diff --git a/test/bridge/USDCHandOver.test.ts b/test/bridge/USDCHandOver.test.ts index 8aea820..debf979 100644 --- a/test/bridge/USDCHandOver.test.ts +++ b/test/bridge/USDCHandOver.test.ts @@ -58,9 +58,9 @@ describe("USDCHandOver", () => { bridge = NewBridge.attach(await bridge.getAddress()) as BridgeV2; - expect(await bridge.managerAddress()).to.be.equal(SECOND.address); - expect(await bridge.lockedUSDCAddress()).to.be.equal(await erc20.getAddress()); - expect(await bridge.initializedVersion()).to.be.equal("1"); + expect(await bridge.getManagerAddress()).to.be.equal(SECOND.address); + expect(await bridge.getLockedUSDCAddress()).to.be.equal(await erc20.getAddress()); + expect(await bridge.getInitializedVersion()).to.be.equal("1"); await expect(bridge.__USDCManager_init(await erc20.getAddress(), SECOND.address)).to.be.revertedWith( "USDCManager: already initialized", @@ -86,7 +86,7 @@ describe("USDCHandOver", () => { bridge = BridgeV2.attach(await bridge.getAddress()) as BridgeV2; - expect(await bridge.managerAddress()).to.be.equal(SECOND.address); + expect(await bridge.getManagerAddress()).to.be.equal(SECOND.address); const BridgeV3 = await ethers.getContractFactory("BridgeV3"); const bridgeV3 = await BridgeV3.deploy(); @@ -94,6 +94,6 @@ describe("USDCHandOver", () => { initData = bridgeV3.interface.encodeFunctionData("__USDCManager_init", [await erc20.getAddress(), FIRST.address]); await bridge.upgradeToWithSigAndCall(await bridgeV3.getAddress(), [], initData); - expect(await bridge.managerAddress()).to.be.equal(FIRST.address); + expect(await bridge.getManagerAddress()).to.be.equal(FIRST.address); }); }); From aded6807d7339a1f9db9d1694f622393ba0a1843 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 21 May 2024 13:43:52 +0300 Subject: [PATCH 07/17] Added script to calculate data for `upgradeToWithSigAndCall` function --- .../calculate-bridgeV2-upgrade-data.ts | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 scripts/hardhat/calculate-bridgeV2-upgrade-data.ts diff --git a/scripts/hardhat/calculate-bridgeV2-upgrade-data.ts b/scripts/hardhat/calculate-bridgeV2-upgrade-data.ts new file mode 100644 index 0000000..2b0c74a --- /dev/null +++ b/scripts/hardhat/calculate-bridgeV2-upgrade-data.ts @@ -0,0 +1,21 @@ +import { ethers } from "hardhat"; + +import { BridgeV2__factory } from "@ethers-v6"; + +const usdcTokenAddress = process.env.USDC_TOKEN_ADDRESS!; +const circleTrustedAccount = process.env.CIRCLE_TRUSTED_ACCOUNT!; + +if(!ethers.isAddress(usdcTokenAddress!)) { + throw new Error("Invalid USDC_TOKEN_ADDRESS"); +} + +if (!ethers.isAddress(circleTrustedAccount)) { + throw new Error("Invalid CIRCLE_TRUSTED_ACCOUNT"); +} + +const initData = BridgeV2__factory.createInterface().encodeFunctionData("__USDCManager_init", [ + usdcTokenAddress, + circleTrustedAccount, +]); + +console.log("Data to be passed to upgradeToWithSigAndCall as data parameter: ", initData); From f3b46c23358bbb85477d8c370d2dd33a5281bfb2 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 21 May 2024 13:44:20 +0300 Subject: [PATCH 08/17] Added deployment script to deploy only BridgeV2 contract --- deploy/10_bridge.v2.migration.ts | 38 ++++++++++++++++++++++++++++++++ hardhat.config.ts | 2 -- 2 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 deploy/10_bridge.v2.migration.ts diff --git a/deploy/10_bridge.v2.migration.ts b/deploy/10_bridge.v2.migration.ts new file mode 100644 index 0000000..401d634 --- /dev/null +++ b/deploy/10_bridge.v2.migration.ts @@ -0,0 +1,38 @@ +import { Deployer, Reporter } from "@solarity/hardhat-migrate"; + +import { BridgeV2__factory } from "@ethers-v6"; + +import { deployQTumContract } from "@/scripts/qtumDeploy"; + +const networkMap: Record = { + "81": qtumDeployment, + "8889": qtumDeployment, + "1": ethereumDeployment, + "11155111": ethereumDeployment, +}; + +export = async (deployer: Deployer) => { + const chainId = await deployer.getChainId(); + + let bridgeImplementationAddress: string; + + if (!networkMap[chainId.toString()]) { + throw new Error(`ChainId ${chainId} not supported`); + } + + [bridgeImplementationAddress] = await networkMap[chainId.toString()](deployer); + + Reporter.reportContracts(["Bridge V2 Implementation", bridgeImplementationAddress]); +}; + +async function qtumDeployment(_deployer: Deployer): Promise<[string]> { + const bridgeImplementation = await deployQTumContract(BridgeV2__factory, "Bridge V2 Implementation"); + + return [bridgeImplementation.address as string]; +} + +async function ethereumDeployment(deployer: Deployer): Promise<[string]> { + const bridgeImplementation = await deployer.deploy(BridgeV2__factory); + + return [await bridgeImplementation.getAddress()]; +} diff --git a/hardhat.config.ts b/hardhat.config.ts index b0696a9..781c697 100644 --- a/hardhat.config.ts +++ b/hardhat.config.ts @@ -62,8 +62,6 @@ const config: HardhatUserConfig = { apiKey: { mainnet: `${process.env.ETHERSCAN_KEY}`, sepolia: `${process.env.ETHERSCAN_KEY}`, - bsc: `${process.env.BSCSCAN_KEY}`, - bscTestnet: `${process.env.BSCSCAN_KEY}`, }, }, migrate: { From cfdd5ba1e8f8a420e70631d1ae232a615d042b9a Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 21 May 2024 13:44:53 +0300 Subject: [PATCH 09/17] Linting --- contracts/utils/USDCManager.sol | 12 ++++++------ scripts/hardhat/calculate-bridgeV2-upgrade-data.ts | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/contracts/utils/USDCManager.sol b/contracts/utils/USDCManager.sol index c0b45de..7d0301f 100644 --- a/contracts/utils/USDCManager.sol +++ b/contracts/utils/USDCManager.sol @@ -75,22 +75,22 @@ contract USDCManager { } /** - * @notice Returns the address of the manager. - */ + * @notice Returns the address of the manager. + */ function getManagerAddress() external view returns (address) { return _getUSDCManagerStorage().managerAddress; } /** - * @notice Returns the address of the locked USDC token. - */ + * @notice Returns the address of the locked USDC token. + */ function getLockedUSDCAddress() external view returns (IUSDCType) { return _getUSDCManagerStorage().lockedUSDCAddress; } /** - * @notice Returns the initialized version of the contract. - */ + * @notice Returns the initialized version of the contract. + */ function getInitializedVersion() external view returns (uint256) { return _getUSDCManagerStorage().initializedVersion; } diff --git a/scripts/hardhat/calculate-bridgeV2-upgrade-data.ts b/scripts/hardhat/calculate-bridgeV2-upgrade-data.ts index 2b0c74a..99db8e5 100644 --- a/scripts/hardhat/calculate-bridgeV2-upgrade-data.ts +++ b/scripts/hardhat/calculate-bridgeV2-upgrade-data.ts @@ -5,7 +5,7 @@ import { BridgeV2__factory } from "@ethers-v6"; const usdcTokenAddress = process.env.USDC_TOKEN_ADDRESS!; const circleTrustedAccount = process.env.CIRCLE_TRUSTED_ACCOUNT!; -if(!ethers.isAddress(usdcTokenAddress!)) { +if (!ethers.isAddress(usdcTokenAddress!)) { throw new Error("Invalid USDC_TOKEN_ADDRESS"); } From 45c70fd586daf0fa6bffbf9503008321750cbc83 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 21 May 2024 13:45:08 +0300 Subject: [PATCH 10/17] Added `USDC Hand Over Procedure` part to README --- README.md | 90 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 90 insertions(+) diff --git a/README.md b/README.md index e5fb7cc..5a59368 100644 --- a/README.md +++ b/README.md @@ -63,3 +63,93 @@ Or, you can verify the contracts on Etherscan **immediately** after the deployme npx hardhat migrate:verify --network sepolia ``` +# USDC Hand Over Procedure + +The steps below outline the procedure for transferring ownership of a bridged USDC token contract to Circle to facilitate an upgrade to native USDC. + +As outlined in the [Bridged USDC Standard](https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#bridged-usdc-standard) document: + +1. The third-party team follows the standard to deploy their bridge contracts or retains the ability to upgrade their bridge contracts in the future to incorporate the required functionality. + +This functionality is supported by the Upgradability Nature of the [Bridge Contract](https://github.com/qtumproject/bridge-evm-contracts/blob/3c50da4b2a753659de158fb8a1fb975ff3f97bdb/contracts/bridge/Bridge.sol#L18). + +It follows the [Universal Upgradeable Proxy Standard](https://eips.ethereum.org/EIPS/eip-1822); therefore, the implementation can be upgraded either by the Bridge Owner or by the Bridge Validators (the second option is possible only if it was configured as such). + +2. The third-party team follows the standard to deploy their bridged USDC token contract. + +This option is also fully fulfilled by using specific deployment scripts implemented in the [USDC Deployment Scripts](https://github.com/qtumproject/usdc-deployment-script) repository. + +Check out its [README](https://github.com/qtumproject/usdc-deployment-script?tab=readme-ov-file#usdc-deployment-scripts) for more details. + +3. If and when a joint decision is made by the third-party team and Circle to securely transfer ownership of the bridged USDC token contract to Circle and perform an upgrade to native USDC, the following will take place: +- The third-party team will pause bridging activity and reconcile in-flight bridging activity to harmonize the total supply of native USDC locked on + the origin chain with the total supply of bridged USDC on the destination chain. +- The third-party team will securely re-assign the contract roles of the bridged USDC token contract to Circle. +- Circle and the third-party team will jointly coordinate to burn the supply of native USDC locked in the bridge contract on the origin chain and upgrade the bridged USDC token contract on the destination chain to native USDC. + +Option 3.1 can be achieved by using the [Pause Manager](https://github.com/qtumproject/bridge-evm-contracts/blob/main/contracts/utils/PauseManager.sol) +functionality. It exposes the `pause` function to stop any deposits and withdrawals to harmonize the total supply of native USDC. + +Option 3.2 is natively supported by the [stablecoin-evm](https://github.com/circlefin/stablecoin-evm/tree/c582e58f691cc0cc7df1c85b6ac07267f8861520) contracts. It can be done by the Token Owner. + +The first part of Option 3.3, `Circle and the third-party team will jointly coordinate to burn the supply of native USDC locked in the bridge contract on the origin chain`, is achieved by deploying the [BridgeV2]() contract and upgrading the already deployed Bridge contract using the BridgeV2 implementation. + +During the upgrade, the `upgradeToWithSigAndCall` function MUST be used to prevent any security risks during the upgrade process. + +### Commands to deploy the BridgeV2 contract + +To deploy the BridgeV2 contract on the Ethereum Sepolia, you can run the following command: + +```bash +npx hardhat migrate --network sepolia --only 10 --verify +``` + +A list of all available networks can be found in the [hardhat.config.js](https://github.com/qtumproject/bridge-evm-contracts/blob/3c50da4b2a753659de158fb8a1fb975ff3f97bdb/hardhat.config.ts) file. + +### Commands to upgrade the Bridge contract to BridgeV2 + +To correctly upgrade the implementation of the Bridge contract to BridgeV2, you first must be the owner of the Bridge contract. + +Alternatively, reach a consensus among validators to upgrade the Bridge contract (if Signers, aka Validators, are "working" as bridge owners). + +You will need to call the `upgradeToWithSigAndCall` function of the Bridge contract with the following parameters: + +- `newImplementation_`: The address of the new implementation contract. In this case, it is the newly deployed `BridgeV2` contract address. +- `signatures_`: If the [isSignersMode](https://github.com/qtumproject/bridge-evm-contracts/blob/3c50da4b2a753659de158fb8a1fb975ff3f97bdb/contracts/utils/Signers.sol#L42) is set to `true`, meaning that the validators act as bridge owners, it should be an array of signatures from the validators approving the upgrade. __Otherwise, pass an empty array.__ +- `data_`: The initialization calldata that will be used to perform a call to immediately initialize the proxy contract. + In our case, it will be the `__USDCManager_init` function call. To calculate the calldata, you can use the following command: + +```bash +USDC_TOKEN_ADDRESS="0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF" CIRCLE_TRUSTED_ACCOUNT="0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" npx hardhat run ./scripts/hardhat/calculate-bridgeV2-upgrade-data.ts +``` + +Replace `USDC_TOKEN_ADDRESS` and `CIRCLE_TRUSTED_ACCOUNT` with the actual addresses of the USDC token and Circle Trusted Account. + +Example output: + +```bash +Data to be passed to upgradeToWithSigAndCall as data parameter: 0x7778cd29000000000000000000000000ffffffffffffffffffffffffffffffffffffffff000000000000000000000000eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +``` + +Again, ensure that you replace `0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF` and `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` with the actual addresses! + +### Actual Bridge contract upgrade + +After you have the `data` parameter, you can call the `upgradeToWithSigAndCall` function of the Bridge contract on Etherscan. + +You need to go to [Etherscan](https://etherscan.io/) and find the **Proxy** of the Bridge contract. Then go to the `Write as Proxy` tab and find the `upgradeToWithSigAndCall` function. + +If you are unable to see the `Write as Proxy` button, click the `Code` tab, find the `More Options` dropdown, click the `Is this a proxy?` button, +then click `Verify`, and finally, click `Save`. + +After this sequence of steps, the `Write as Proxy` button should appear to the right of the `Write Contract` button. + +At this point, you have all the details and can successfully upgrade the Bridge contract to BridgeV2. + +> Ensure that you are the Bridge Owner or have a consensus among validators to perform this action. + +--- + +After the steps above (pausing and implementation upgrade), the Circle team can proceed with their part of burning the locked USDC tokens. + +This step concludes the USDC Hand Over Procedure. From 41f385b20c484c5ceef1f693693488e3b9b8181f Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 11 Jun 2024 19:08:25 +0300 Subject: [PATCH 11/17] Added simple doc for ownable methods --- README.md | 46 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/README.md b/README.md index 5a59368..e715cf4 100644 --- a/README.md +++ b/README.md @@ -153,3 +153,49 @@ At this point, you have all the details and can successfully upgrade the Bridge After the steps above (pausing and implementation upgrade), the Circle team can proceed with their part of burning the locked USDC tokens. This step concludes the USDC Hand Over Procedure. + +## Contract Management Methods + +All of the functions below share the common argument `bytes[] calldata signatures_`, which is an array of signatures from +the signers if required. If the `isSignersMode` flag is set to `true`, the signatures are required. +Otherwise, the signatures are not required, and this argument should be an empty array (i.e., `[]`). + +For the `pause` and `unpause` methods, if the `pauseManager` address is NOT the zero address, the `pauseManager` address can call these methods. +These methods will be restricted only to the `pauseManager` account. + +- `pause(bytes[] calldata signatures_)`: Pauses the contract. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + - Requires either the owner, the pause manager, or sufficient signatures depending on the `isSignersMode` flag and the `pauseManager` address. + +- `unpause(bytes[] calldata signatures_)`: Unpauses the contract. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + - Requires either the owner, the pause manager, or sufficient signatures depending on the `isSignersMode` flag and the `pauseManager` address. + +- `setPauseManager(address newManager_, bytes[] calldata signatures_)`: Transfers pause management to a new address. + - `address newManager_`: The address of the new pause manager. May be set to the zero address. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + +- `setSignaturesThreshold(uint256 signaturesThreshold_, bytes[] calldata signatures_)`: Sets the threshold of signatures required to authorize a transaction. + - `uint256 signaturesThreshold_`: The new signature threshold. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + +- `addSigners(address[] calldata signers_, bytes[] calldata signatures_)`: Adds new signers. + - `address[] calldata signers_`: The new signers to be added. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + +- `removeSigners(address[] calldata signers_, bytes[] calldata signatures_)`: Removes signers. + - `address[] calldata signers_`: The signers to remove. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + +- `toggleSignersMode(bool isSignersMode_, bytes[] calldata signatures_)`: Toggles the signers mode. + - `bool isSignersMode_`: The new signers mode. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + +- `upgradeToWithSig(address newImplementation_, bytes[] calldata signatures_)`: Upgrades the implementation of the proxy to `newImplementation`. + - `address newImplementation_`: The address of the new implementation. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + +- `upgradeToWithSigAndCall(address newImplementation_, bytes[] calldata signatures_, bytes calldata data_)`: Upgrades the implementation of the proxy to `newImplementation`, and subsequently executes the function call encoded in `data_`. + - `address newImplementation_`: The address of the new implementation. + - `bytes[] calldata signatures_`: The signatures from the signers if required. + - `bytes calldata data_`: The data for the function call to be executed. From d99d17eddf3bbda169dbadd26f265937bfd1c9f7 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 11 Jun 2024 19:21:02 +0300 Subject: [PATCH 12/17] Updated README --- README.md | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index e715cf4..1e580ab 100644 --- a/README.md +++ b/README.md @@ -156,12 +156,19 @@ This step concludes the USDC Hand Over Procedure. ## Contract Management Methods -All of the functions below share the common argument `bytes[] calldata signatures_`, which is an array of signatures from -the signers if required. If the `isSignersMode` flag is set to `true`, the signatures are required. -Otherwise, the signatures are not required, and this argument should be an empty array (i.e., `[]`). +All the functions below should be called directly on the Bridge contract. -For the `pause` and `unpause` methods, if the `pauseManager` address is NOT the zero address, the `pauseManager` address can call these methods. -These methods will be restricted only to the `pauseManager` account. +> To call these function on the Ethereum network the [Remix](https://remix.ethereum.org/) or [Etherscan](https://etherscan.io/) can be used. + +> To call these functions on the QTum network the [QTum Web Wallet](https://wallet.bridge.qtum.net/send-to-contract) can be used. + +To check the current owner of the Bridge contract, you can call the `owner` method. +To verify if the `signersMode` is enabled, use the `isSignersMode` method. +Lastly, to check the current `pauseManager` address, call the `pauseManager` method. If the `pauseManager` address is the zero address, the `pauseManager` functionality can only be called by the owner of the Bridge contract. + +All the functions below share the common argument `bytes[] calldata signatures_`, which is an array of signatures from the signers if required. If the `isSignersMode` flag is set to `true`, the signatures are required. Otherwise, the signatures are not required, and this argument should be an empty array (i.e., `[]`). + +For the `pause` and `unpause` methods, if the `pauseManager` address is NOT the zero address, the `pauseManager` address can call these methods. These methods will be restricted only to the `pauseManager` account. - `pause(bytes[] calldata signatures_)`: Pauses the contract. - `bytes[] calldata signatures_`: The signatures from the signers if required. From c30f921e1f6cad4fd655a4fed66af4b3fcca2c23 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 11 Jun 2024 19:21:35 +0300 Subject: [PATCH 13/17] Updated README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e580ab..7d4b614 100644 --- a/README.md +++ b/README.md @@ -158,7 +158,7 @@ This step concludes the USDC Hand Over Procedure. All the functions below should be called directly on the Bridge contract. -> To call these function on the Ethereum network the [Remix](https://remix.ethereum.org/) or [Etherscan](https://etherscan.io/) can be used. +> To call these functions on the Ethereum network the [Remix](https://remix.ethereum.org/) or [Etherscan](https://etherscan.io/) can be used. > To call these functions on the QTum network the [QTum Web Wallet](https://wallet.bridge.qtum.net/send-to-contract) can be used. From db8288800fab385135af837cd757456b520bb414 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 11 Jun 2024 19:22:41 +0300 Subject: [PATCH 14/17] Add Bridge ABI --- abi/Bridge.json | 1369 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1369 insertions(+) create mode 100644 abi/Bridge.json diff --git a/abi/Bridge.json b/abi/Bridge.json new file mode 100644 index 0000000..4106122 --- /dev/null +++ b/abi/Bridge.json @@ -0,0 +1,1369 @@ +[ + { + "inputs": [], + "stateMutability": "nonpayable", + "type": "constructor" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "previousAdmin", + "type": "address" + }, + { + "indexed": false, + "internalType": "address", + "name": "newAdmin", + "type": "address" + } + ], + "name": "AdminChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "beacon", + "type": "address" + } + ], + "name": "BeaconUpgraded", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "receiver", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "network", + "type": "string" + }, + { + "indexed": false, + "internalType": "enum IERC1155Handler.ERC1155BridgingType", + "name": "operationType", + "type": "uint8" + } + ], + "name": "DepositedERC1155", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "receiver", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "network", + "type": "string" + }, + { + "indexed": false, + "internalType": "enum IERC20Handler.ERC20BridgingType", + "name": "operationType", + "type": "uint8" + } + ], + "name": "DepositedERC20", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "token", + "type": "address" + }, + { + "indexed": false, + "internalType": "uint256", + "name": "tokenId", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "receiver", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "network", + "type": "string" + }, + { + "indexed": false, + "internalType": "enum IERC721Handler.ERC721BridgingType", + "name": "operationType", + "type": "uint8" + } + ], + "name": "DepositedERC721", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint256", + "name": "amount", + "type": "uint256" + }, + { + "indexed": false, + "internalType": "string", + "name": "receiver", + "type": "string" + }, + { + "indexed": false, + "internalType": "string", + "name": "network", + "type": "string" + } + ], + "name": "DepositedNative", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "uint8", + "name": "version", + "type": "uint8" + } + ], + "name": "Initialized", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "previousOwner", + "type": "address" + }, + { + "indexed": true, + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "OwnershipTransferred", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "newManager", + "type": "address" + } + ], + "name": "PauseManagerChanged", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Paused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": false, + "internalType": "address", + "name": "account", + "type": "address" + } + ], + "name": "Unpaused", + "type": "event" + }, + { + "anonymous": false, + "inputs": [ + { + "indexed": true, + "internalType": "address", + "name": "implementation", + "type": "address" + } + ], + "name": "Upgraded", + "type": "event" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "signers_", + "type": "address[]" + }, + { + "internalType": "address", + "name": "pauseManager_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "signaturesThreshold_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isSignersMode_", + "type": "bool" + } + ], + "name": "__Bridge_init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "signers_", + "type": "address[]" + }, + { + "internalType": "uint256", + "name": "signaturesThreshold_", + "type": "uint256" + }, + { + "internalType": "bool", + "name": "isSignersMode_", + "type": "bool" + } + ], + "name": "__Signers_init", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "addHash", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "signers_", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "addSigners", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + } + ], + "name": "containsHash", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "receiver_", + "type": "string" + }, + { + "internalType": "string", + "name": "network_", + "type": "string" + }, + { + "internalType": "enum IERC1155Handler.ERC1155BridgingType", + "name": "operationType_", + "type": "uint8" + } + ], + "name": "depositERC1155", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "receiver_", + "type": "string" + }, + { + "internalType": "string", + "name": "network_", + "type": "string" + }, + { + "internalType": "enum IERC20Handler.ERC20BridgingType", + "name": "operationType_", + "type": "uint8" + } + ], + "name": "depositERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "receiver_", + "type": "string" + }, + { + "internalType": "string", + "name": "network_", + "type": "string" + }, + { + "internalType": "enum IERC721Handler.ERC721BridgingType", + "name": "operationType_", + "type": "uint8" + } + ], + "name": "depositERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "string", + "name": "receiver_", + "type": "string" + }, + { + "internalType": "string", + "name": "network_", + "type": "string" + } + ], + "name": "depositNative", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver_", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chainId_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "tokenURI_", + "type": "string" + }, + { + "internalType": "enum IERC1155Handler.ERC1155BridgingType", + "name": "operationType_", + "type": "uint8" + } + ], + "name": "getERC1155SignHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver_", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chainId_", + "type": "uint256" + }, + { + "internalType": "enum IERC20Handler.ERC20BridgingType", + "name": "operationType_", + "type": "uint8" + } + ], + "name": "getERC20SignHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver_", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chainId_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "tokenURI_", + "type": "string" + }, + { + "internalType": "enum IERC721Handler.ERC721BridgingType", + "name": "operationType_", + "type": "uint8" + } + ], + "name": "getERC721SignHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "functionData_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "nonce_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "contract_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "chainId_", + "type": "uint256" + } + ], + "name": "getFunctionSignHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver_", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "chainId_", + "type": "uint256" + } + ], + "name": "getNativeSignHash", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "pure", + "type": "function" + }, + { + "inputs": [], + "name": "getSigners", + "outputs": [ + { + "internalType": "address[]", + "name": "", + "type": "address[]" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "isSignersMode", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "functionData_", + "type": "bytes32" + } + ], + "name": "nonces", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "uint256[]", + "name": "", + "type": "uint256[]" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155BatchReceived", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC1155Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "address", + "name": "", + "type": "address" + }, + { + "internalType": "uint256", + "name": "", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "", + "type": "bytes" + } + ], + "name": "onERC721Received", + "outputs": [ + { + "internalType": "bytes4", + "name": "", + "type": "bytes4" + } + ], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "owner", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "pause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "pauseManager", + "outputs": [ + { + "internalType": "address", + "name": "", + "type": "address" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "paused", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [], + "name": "proxiableUUID", + "outputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address[]", + "name": "signers_", + "type": "address[]" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "removeSigners", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "renounceOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newManager_", + "type": "address" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "setPauseManager", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "signaturesThreshold_", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "setSignaturesThreshold", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [], + "name": "signaturesThreshold", + "outputs": [ + { + "internalType": "uint256", + "name": "", + "type": "uint256" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes4", + "name": "interfaceId", + "type": "bytes4" + } + ], + "name": "supportsInterface", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bool", + "name": "isSignersMode_", + "type": "bool" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "toggleSignersMode", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newOwner", + "type": "address" + } + ], + "name": "transferOwnership", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "unpause", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + } + ], + "name": "upgradeTo", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation", + "type": "address" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "name": "upgradeToAndCall", + "outputs": [], + "stateMutability": "payable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation_", + "type": "address" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "upgradeToWithSig", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "newImplementation_", + "type": "address" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + }, + { + "internalType": "bytes", + "name": "data_", + "type": "bytes" + } + ], + "name": "upgradeToWithSigAndCall", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "", + "type": "bytes32" + } + ], + "name": "usedHashes", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId_", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver_", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "tokenURI_", + "type": "string" + }, + { + "internalType": "enum IERC1155Handler.ERC1155BridgingType", + "name": "operationType_", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "withdrawERC1155", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver_", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "enum IERC20Handler.ERC20BridgingType", + "name": "operationType_", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "withdrawERC20", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "address", + "name": "token_", + "type": "address" + }, + { + "internalType": "uint256", + "name": "tokenId_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver_", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "string", + "name": "tokenURI_", + "type": "string" + }, + { + "internalType": "enum IERC721Handler.ERC721BridgingType", + "name": "operationType_", + "type": "uint8" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "withdrawERC721", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "internalType": "uint256", + "name": "amount_", + "type": "uint256" + }, + { + "internalType": "address", + "name": "receiver_", + "type": "address" + }, + { + "internalType": "bytes32", + "name": "txHash_", + "type": "bytes32" + }, + { + "internalType": "uint256", + "name": "txNonce_", + "type": "uint256" + }, + { + "internalType": "bytes[]", + "name": "signatures_", + "type": "bytes[]" + } + ], + "name": "withdrawNative", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "stateMutability": "payable", + "type": "receive" + } + ] From c60641c135a946d53f8e1fb343a107b121acb599 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Tue, 11 Jun 2024 19:24:38 +0300 Subject: [PATCH 15/17] Updated README --- README.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 7d4b614..560e60e 100644 --- a/README.md +++ b/README.md @@ -154,7 +154,9 @@ After the steps above (pausing and implementation upgrade), the Circle team can This step concludes the USDC Hand Over Procedure. -## Contract Management Methods +## Bridge Management Methods + +### Usage All the functions below should be called directly on the Bridge contract. @@ -162,6 +164,10 @@ All the functions below should be called directly on the Bridge contract. > To call these functions on the QTum network the [QTum Web Wallet](https://wallet.bridge.qtum.net/send-to-contract) can be used. +Bridge ABI can be found [here](https://github.com/qtumproject/bridge-evm-contracts/blob/db8288800fab385135af837cd757456b520bb414/abi/Bridge.json) + +### Methods + To check the current owner of the Bridge contract, you can call the `owner` method. To verify if the `signersMode` is enabled, use the `isSignersMode` method. Lastly, to check the current `pauseManager` address, call the `pauseManager` method. If the `pauseManager` address is the zero address, the `pauseManager` functionality can only be called by the owner of the Bridge contract. From 8592143c48e776bba98f87cda34ec8541cbe7374 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Fri, 14 Jun 2024 12:17:33 +0300 Subject: [PATCH 16/17] Added bridge upgrade migration. Updated README --- README.md | 83 +++++++++++++++++++++++++-- deploy/10_bridge.v2.migration.ts | 33 +---------- deploy/11_bridge.upgrade.migration.ts | 58 +++++++++++++++++++ deploy/1_bridge.migration.ts | 23 +++++--- 4 files changed, 151 insertions(+), 46 deletions(-) create mode 100644 deploy/11_bridge.upgrade.migration.ts diff --git a/README.md b/README.md index 560e60e..781a955 100644 --- a/README.md +++ b/README.md @@ -96,7 +96,18 @@ The first part of Option 3.3, `Circle and the third-party team will jointly coor During the upgrade, the `upgradeToWithSigAndCall` function MUST be used to prevent any security risks during the upgrade process. -### Commands to deploy the BridgeV2 contract +## Commands to upgrade the Bridge contract to BridgeV2 + +Below, you will find two different ways to upgrade the Bridge contract to BridgeV2: + +- [Process of manually upgrading the Bridge contract to BridgeV2](#process-of-manually-upgrading-the-bridge-contract-to-bridgev2) +- [Process of automatically upgrading the Bridge contract to BridgeV2](#process-of-automatic-upgrade-of-the-bridge-contract-to-bridgev2) + +### Process of manually upgrading the Bridge contract to BridgeV2 + +The first step is to deploy the BridgeV2 contract using the process described below. + +#### Commands to deploy the BridgeV2 contract To deploy the BridgeV2 contract on the Ethereum Sepolia, you can run the following command: @@ -106,7 +117,7 @@ npx hardhat migrate --network sepolia --only 10 --verify A list of all available networks can be found in the [hardhat.config.js](https://github.com/qtumproject/bridge-evm-contracts/blob/3c50da4b2a753659de158fb8a1fb975ff3f97bdb/hardhat.config.ts) file. -### Commands to upgrade the Bridge contract to BridgeV2 +#### Upgrade the Bridge contract to BridgeV2 via Etherscan or Gnosis Safe To correctly upgrade the implementation of the Bridge contract to BridgeV2, you first must be the owner of the Bridge contract. @@ -133,7 +144,7 @@ Data to be passed to upgradeToWithSigAndCall as data parameter: 0x7778cd2900000 Again, ensure that you replace `0xFFfFfFffFFfffFFfFFfFFFFFffFFFffffFfFFFfF` and `0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE` with the actual addresses! -### Actual Bridge contract upgrade +#### Actual Bridge contract upgrade via Etherscan After you have the `data` parameter, you can call the `upgradeToWithSigAndCall` function of the Bridge contract on Etherscan. @@ -148,15 +159,75 @@ At this point, you have all the details and can successfully upgrade the Bridge > Ensure that you are the Bridge Owner or have a consensus among validators to perform this action. +#### Bridge contracts upgrade via Gnosis Safe + +The process the same as with Etherscan, but instead of sending the transaction directly, you need to use the [Gnosis Safe Wallet](https://app.safe.global/welcome) + +All you need to do, is to create a transaction to interact with the Bridge contract (should be a Bridge Proxy Address) on the Ethereum Network. + +Bridge ABI can be found [here](https://github.com/qtumproject/bridge-evm-contracts/blob/db8288800fab385135af837cd757456b520bb414/abi/Bridge.json) + +After that, you can pass there an ABI, select `upgradeToWithSigAndCall` function, and fill the parameters with the data you calculated before. + +There, you will need to provide three parameters: +- `newImplementation_`: The address of the new implementation contract. In this case, it is the newly deployed `BridgeV2` contract address. +- `signatures_`: As soon as a Gnosis Safe account is employed, the signatures are not needed, so you need to pass an empty array. +- `data_`: The initialization calldata that will be used to perform a call to immediately initialize the proxy contract. + +After, you could create a transaction and sign it with the Gnosis Safe account owners, reach a threshold and eventually execute it. + +### Process of Automatic Upgrade of the Bridge Contract to BridgeV2 + +For this method to work, you MUST meet one of the following criteria: +- Be the owner of the Bridge contract and have access to the private key of the owner account. +- Gather a consensus among validators to upgrade the Bridge contract and receive the required number of signatures. + +#### Commands to Upgrade the Bridge Contract to BridgeV2 + +To understand why `USDC_TOKEN_ADDRESS` and `CIRCLE_TRUSTED_ACCOUNT` are needed, refer to the [Ability to burn locked USDC](https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#2-ability-to-burn-locked-usdc) section of the [Bridged USDC Standard](https://github.com/circlefin/stablecoin-evm/blob/c582e58f691cc0cc7df1c85b6ac07267f8861520/doc/bridged_USDC_standard.md#bridged-usdc-standard) document. + +In the `.env` file, you need to add the following parameters: +- `PRIVATE_KEY` - The private key of the owner account that has enough balance to deploy the BridgeV2 contract and call the `upgradeToWithSigAndCall` function. + - In case you are not the owner but have gathered enough signatures, you only need enough balance to cover the transaction fees. +- `USDC_TOKEN_ADDRESS` - The address of the USDC token contract. +- `CIRCLE_TRUSTED_ACCOUNT` - The address of the Circle Trusted Account. +- `BRIDGE_ADDRESS` - The address of the Bridge contract that you want to upgrade to BridgeV2. +- `SIGNATURES` - The number of signatures required to upgrade the Bridge contract to BridgeV2. + - This option is needed only if the `isSignersMode` flag is set to `true` in the Bridge contract. + +After that, you can run the following command to upgrade the Bridge contract to BridgeV2 on the Ethereum Sepolia network: + +```bash +npx hardhat migrate --network sepolia --from 10 +``` + +If you made a mistake in the variables configuration and migration 11 failed, you can fix those and then only run migration 11. + +But, before that, you need to set the `BRIDGE_V2_ADDRESS` variable in the `.env` file to the address of the newly deployed BridgeV2 contract. + +After that, you can run the following command to upgrade the Bridge contract to BridgeV2 on the Ethereum Sepolia network: + +```bash +npx hardhat migrate --network sepolia --only 11 +``` + --- After the steps above (pausing and implementation upgrade), the Circle team can proceed with their part of burning the locked USDC tokens. This step concludes the USDC Hand Over Procedure. -## Bridge Management Methods +## Afterward BridgeV2 Contract Upgrade + +After the Bridge is upgraded to V2, the Circle team can proceed with their part of burning the locked USDC tokens. + +Make sure to upgrade Validator Nodes to stop supporting the USDC token. + +After the Validators are upgraded, the Bridge can be unpaused on both sides and continue working with other supported tokens, if any. + +# Bridge Management Methods -### Usage +## Usage All the functions below should be called directly on the Bridge contract. @@ -166,7 +237,7 @@ All the functions below should be called directly on the Bridge contract. Bridge ABI can be found [here](https://github.com/qtumproject/bridge-evm-contracts/blob/db8288800fab385135af837cd757456b520bb414/abi/Bridge.json) -### Methods +## Methods To check the current owner of the Bridge contract, you can call the `owner` method. To verify if the `signersMode` is enabled, use the `isSignersMode` method. diff --git a/deploy/10_bridge.v2.migration.ts b/deploy/10_bridge.v2.migration.ts index 401d634..3a6143f 100644 --- a/deploy/10_bridge.v2.migration.ts +++ b/deploy/10_bridge.v2.migration.ts @@ -2,37 +2,8 @@ import { Deployer, Reporter } from "@solarity/hardhat-migrate"; import { BridgeV2__factory } from "@ethers-v6"; -import { deployQTumContract } from "@/scripts/qtumDeploy"; - -const networkMap: Record = { - "81": qtumDeployment, - "8889": qtumDeployment, - "1": ethereumDeployment, - "11155111": ethereumDeployment, -}; - export = async (deployer: Deployer) => { - const chainId = await deployer.getChainId(); - - let bridgeImplementationAddress: string; - - if (!networkMap[chainId.toString()]) { - throw new Error(`ChainId ${chainId} not supported`); - } - - [bridgeImplementationAddress] = await networkMap[chainId.toString()](deployer); - - Reporter.reportContracts(["Bridge V2 Implementation", bridgeImplementationAddress]); -}; - -async function qtumDeployment(_deployer: Deployer): Promise<[string]> { - const bridgeImplementation = await deployQTumContract(BridgeV2__factory, "Bridge V2 Implementation"); - - return [bridgeImplementation.address as string]; -} - -async function ethereumDeployment(deployer: Deployer): Promise<[string]> { const bridgeImplementation = await deployer.deploy(BridgeV2__factory); - return [await bridgeImplementation.getAddress()]; -} + Reporter.reportContracts(["Bridge V2 Implementation", await bridgeImplementation.getAddress()]); +}; diff --git a/deploy/11_bridge.upgrade.migration.ts b/deploy/11_bridge.upgrade.migration.ts new file mode 100644 index 0000000..670f9e1 --- /dev/null +++ b/deploy/11_bridge.upgrade.migration.ts @@ -0,0 +1,58 @@ +import { Deployer } from "@solarity/hardhat-migrate"; + +import { Bridge, Bridge__factory, BridgeV2, BridgeV2__factory } from "@ethers-v6"; + +/** + * This part of the migration script can be run independently of the previous migrations, as well as immediately after the migration 10. + * + * If you intend to run the migration after the migration 10, you MUST set the BRIDGE_ADDRESS environment variable to the address of the Bridge contract (its Proxy). + * + * If you want to run the migration exclusively, you MUST set both the BRIDGE_ADDRESS and BRIDGE_V2_ADDRESS environment variables to the addresses of the Bridge contract (its Proxy) and the BridgeV2 contract, respectively. + */ +export = async (deployer: Deployer) => { + const bridge: Bridge = await deployer.deployed(Bridge__factory, process.env.BRIDGE_ADDRESS || "Bridge Proxy"); + const bridgeImplementation: BridgeV2 = await deployer.deployed(BridgeV2__factory, process.env.BRIDGE_V2_ADDRESS); + + await checkIfUpgradeIsPossible(deployer, bridge); + + const initCallData = BridgeV2__factory.createInterface().encodeFunctionData("__USDCManager_init(address,address)", [ + process.env.USDC_TOKEN_ADDRESS!, + process.env.CIRCLE_TRUSTED_ACCOUNT!, + ]); + + const isSignerModeEnabled = await bridge.isSignersMode(); + + if (isSignerModeEnabled) { + const signatures = process.env.SIGNATURES!.split(","); + + await bridge.upgradeToWithSigAndCall(await bridgeImplementation.getAddress(), signatures, initCallData); + } else { + await bridge.upgradeToWithSigAndCall(await bridgeImplementation.getAddress(), [], initCallData); + } +}; + +async function checkIfUpgradeIsPossible(deployer: Deployer, bridge: Bridge) { + const signer = await deployer.getSigner(); + const currentBridgeOwner = await bridge.owner(); + const isSignerModeEnabled = await bridge.isSignersMode(); + + if (isSignerModeEnabled && process.env.SIGNATURES === undefined) { + throw new Error( + "The Bridge contract is in Signers mode and SIGNATURES environment variable is not set. Upgrade is not possible by the EOA.", + ); + } + + if (currentBridgeOwner !== (await signer.getAddress()) && !isSignerModeEnabled) { + throw new Error( + `The Bridge contract is not owned by the deployer: ${await signer.getAddress()}. Current owner: ${currentBridgeOwner}`, + ); + } + + if (process.env.USDC_TOKEN_ADDRESS === undefined) { + throw new Error("USDC_TOKEN_ADDRESS environment variable is not set"); + } + + if (process.env.CIRCLE_TRUSTED_ACCOUNT === undefined) { + throw new Error("CIRCLE_TRUSTED_ACCOUNT environment variable is not set"); + } +} diff --git a/deploy/1_bridge.migration.ts b/deploy/1_bridge.migration.ts index 5ece4ad..67b5eb5 100644 --- a/deploy/1_bridge.migration.ts +++ b/deploy/1_bridge.migration.ts @@ -14,6 +14,7 @@ const networkMap: Record = { "81": qtumDeployment, "8889": qtumDeployment, "1": ethereumDeployment, + "31337": ethereumDeployment, "11155111": ethereumDeployment, }; @@ -54,15 +55,19 @@ async function qtumDeployment(_deployer: Deployer): Promise<[string, string]> { async function ethereumDeployment(deployer: Deployer): Promise<[string, string]> { const bridgeImplementation = await deployer.deploy(Bridge__factory); - const proxy = await deployer.deploy(ERC1967Proxy__factory, [ - await bridgeImplementation.getAddress(), - bridgeImplementation.interface.encodeFunctionData("__Bridge_init", [ - validators, - ethers.ZeroAddress, - threshold, - false, - ]), - ]); + const proxy = await deployer.deploy( + ERC1967Proxy__factory, + [ + await bridgeImplementation.getAddress(), + bridgeImplementation.interface.encodeFunctionData("__Bridge_init", [ + validators, + ethers.ZeroAddress, + threshold, + false, + ]), + ], + { name: "Bridge Proxy" }, + ); const bridge = await deployer.deployed(Bridge__factory, await proxy.getAddress()); From 8b8df2b0114fd14253c4c080b2758bc2039a81f5 Mon Sep 17 00:00:00 2001 From: Kyryl Riabov Date: Fri, 21 Jun 2024 16:07:49 +0300 Subject: [PATCH 17/17] Added `--verify` where needed. --- README.md | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 781a955..5b8d747 100644 --- a/README.md +++ b/README.md @@ -198,7 +198,7 @@ In the `.env` file, you need to add the following parameters: After that, you can run the following command to upgrade the Bridge contract to BridgeV2 on the Ethereum Sepolia network: ```bash -npx hardhat migrate --network sepolia --from 10 +npx hardhat migrate --network sepolia --from 10 --verify ``` If you made a mistake in the variables configuration and migration 11 failed, you can fix those and then only run migration 11. diff --git a/package.json b/package.json index 11fbd9d..3a01ce5 100644 --- a/package.json +++ b/package.json @@ -12,8 +12,8 @@ "private-network": "npx hardhat node", "generate-types": "TYPECHAIN_FORCE=true npx hardhat typechain", "generate-go-bindings": "npx hardhat gobind --outdir ./go-bindings", - "deploy-ethereum": "npx hardhat migrate --network eth_mainnet --only 1", - "deploy-sepolia": "npx hardhat migrate --network sepolia --only 1", + "deploy-ethereum": "npx hardhat migrate --network eth_mainnet --only 1 --verify", + "deploy-sepolia": "npx hardhat migrate --network sepolia --only 1 --verify", "deploy-qtum-mainnet": "npx hardhat migrate --network qtumMainnet --only 1", "deploy-qtum-testnet": "npx hardhat migrate --network qtumTestnet --only 1", "lint-fix": "npm run lint-sol-fix && npm run lint-ts-fix && npm run lint-json-fix",