From 7d00674c1c343f1f7cdd733a3bdd516df6352785 Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 26 Jan 2026 14:51:34 -0500 Subject: [PATCH 01/41] chore: update hardhat configuration and dependencies for bridge contracts, including enabling hardhat-deploy and adding new network configurations for CELO and XDC --- package.json | 2 +- packages/bridge-contracts/hardhat.config.ts | 53 +- packages/bridge-contracts/layerzero.config.ts | 14 +- packages/bridge-contracts/package.json | 6 +- yarn.lock | 534 +++++++----------- 5 files changed, 268 insertions(+), 341 deletions(-) diff --git a/package.json b/package.json index 4d41894..9a9847d 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,6 @@ ], "packageManager": "yarn@4.6.0", "resolutions": { - "hardhat": "2.12.3" + "hardhat": "2.28.3" } } diff --git a/packages/bridge-contracts/hardhat.config.ts b/packages/bridge-contracts/hardhat.config.ts index 4ad5754..125f430 100644 --- a/packages/bridge-contracts/hardhat.config.ts +++ b/packages/bridge-contracts/hardhat.config.ts @@ -7,8 +7,7 @@ import 'hardhat-contract-sizer'; import '@openzeppelin/hardhat-upgrades'; import '@nomicfoundation/hardhat-chai-matchers'; import '@nomicfoundation/hardhat-verify'; -// Temporarily disabled due to incompatibility with Hardhat 2.x -// import 'hardhat-deploy'; +import 'hardhat-deploy'; import { HttpNetworkAccountsConfig } from 'hardhat/types'; import { configDotenv } from 'dotenv'; import * as envEnc from '@chainlink/env-enc'; @@ -150,6 +149,44 @@ const config: HardhatUserConfig = { gasPrice: 2e9, chainId: 5, }, + "development-celo": { + accounts: accounts as HttpNetworkAccountsConfig, + url: "https://forno.celo.org", + gas: 3000000, + gasPrice: 26e9, + chainId: 42220, + eid: EndpointId.CELO_V2_MAINNET, + } as any, + "production-celo": { + accounts: accounts as HttpNetworkAccountsConfig, + url: "https://forno.celo.org", + gas: 8000000, + gasPrice: 26e9, + chainId: 42220 + }, + "production-xdc": { + accounts: accounts as HttpNetworkAccountsConfig, + chainId: 50, + url: 'https://rpc.xdc.network', + verify: { + etherscan: { + apiUrl: 'https://api.etherscan.io/v2/api?chainid=50', + apiKey: process.env.ETHERSCAN_KEY || '', + }, + }, + }, + "development-xdc": { + accounts: accounts as HttpNetworkAccountsConfig, + chainId: 50, + url: 'https://rpc.xdc.network', + eid: EndpointId.XDC_V2_MAINNET, + verify: { + etherscan: { + apiUrl: 'https://api.etherscan.io/v2/api?chainid=50', + apiKey: process.env.ETHERSCAN_KEY || '', + }, + }, + } as any }, sourcify: { enabled: true, @@ -189,7 +226,17 @@ const config: HardhatUserConfig = { ], }, contractSizer: { - runOnCompile: true, + runOnCompile: false, + }, + namedAccounts: { + deployer: { + default: 0, // Use the first account as deployer + }, + }, + // hardhat-deploy configuration + // This ensures LayerZero DevTools can detect hardhat-deploy usage + paths: { + deployments: 'deployments', }, }; diff --git a/packages/bridge-contracts/layerzero.config.ts b/packages/bridge-contracts/layerzero.config.ts index 7570da1..b310c3d 100644 --- a/packages/bridge-contracts/layerzero.config.ts +++ b/packages/bridge-contracts/layerzero.config.ts @@ -14,7 +14,7 @@ import type { OmniPointHardhat } from "@layerzerolabs/toolbox-hardhat"; import { OAppEnforcedOption } from "@layerzerolabs/toolbox-hardhat"; import { ExecutorOptionType } from "@layerzerolabs/lz-v2-utilities"; import { TwoWayConfig, generateConnectionsConfig } from "@layerzerolabs/metadata-tools"; -import dao from "./releases/deployment.json"; +import dao from "./release/deployment.json"; // Network names - adjust these based on your deployment const XDC_NETWORK = "development-xdc"; @@ -24,26 +24,18 @@ const CELO_NETWORK = "development-celo"; const xdcOftAdapterAddress = (dao[XDC_NETWORK] as any)?.GoodDollarOFTAdapter; const celoOftAdapterAddress = (dao[CELO_NETWORK] as any)?.GoodDollarOFTAdapter; -if (!xdcOftAdapterAddress || !celoOftAdapterAddress) { - throw new Error( - `OFT Adapter addresses not found in deployment.json. ` + - `XDC: ${xdcOftAdapterAddress || "missing"}, CELO: ${celoOftAdapterAddress || "missing"}. ` + - `Please deploy them first or adjust XDC_NETWORK and CELO_NETWORK constants.` - ); -} - // XDC Network contract const xdcContract: OmniPointHardhat = { eid: EndpointId.XDC_V2_MAINNET, // XDC endpoint ID contractName: "GoodDollarOFTAdapter", - address: xdcOftAdapterAddress, + address: xdcOftAdapterAddress, // Will be updated after deployment }; // CELO Network contract const celoContract: OmniPointHardhat = { eid: EndpointId.CELO_V2_MAINNET, // CELO endpoint ID contractName: "GoodDollarOFTAdapter", - address: celoOftAdapterAddress, + address: celoOftAdapterAddress, // Will be updated after deployment }; // Enforced execution options for EVM chains diff --git a/packages/bridge-contracts/package.json b/packages/bridge-contracts/package.json index 1310c18..829e8e7 100644 --- a/packages/bridge-contracts/package.json +++ b/packages/bridge-contracts/package.json @@ -26,8 +26,11 @@ "@axelar-network/axelarjs-sdk": "^0.13.6", "@chainlink/env-enc": "^1.0.5", "@gooddollar/goodprotocol": "2.1.0", + "@layerzerolabs/devtools-evm-hardhat": "^4.0.4", "@layerzerolabs/lz-definitions": "^3.0.151", "@layerzerolabs/lz-evm-protocol-v2": "^3.0.151", + "@layerzerolabs/lz-v2-utilities": "^3.0.156", + "@layerzerolabs/metadata-tools": "^3.0.3", "@layerzerolabs/oapp-evm": "^0.4.1", "@layerzerolabs/oapp-evm-upgradeable": "^0.1.3", "@layerzerolabs/oft-evm": "^4.0.0", @@ -35,6 +38,7 @@ "@layerzerolabs/scan-client": "^0.0.6", "@layerzerolabs/solidity-examples": "^0.0.13", "@layerzerolabs/toolbox-hardhat": "~0.6.13", + "@layerzerolabs/ua-devtools": "^5.0.2", "@nomicfoundation/hardhat-chai-matchers": "^1.0.5", "@nomicfoundation/hardhat-network-helpers": "^1.0.6", "@nomicfoundation/hardhat-verify": "^2.1.0", @@ -57,7 +61,7 @@ "ethers": "^5.*", "hardhat": "2.*", "hardhat-contract-sizer": "^2.6.1", - "hardhat-deploy": "^1.0.4", + "hardhat-deploy": "^0.12.4", "hardhat-gas-reporter": "^1.0.9", "merkle-patricia-tree": "^2.*", "prettier": "^2.5.1", diff --git a/yarn.lock b/yarn.lock index 8bcee0c..2760b7a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1150,6 +1150,15 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/rlp@npm:^5.0.2": + version: 5.0.2 + resolution: "@ethereumjs/rlp@npm:5.0.2" + bin: + rlp: bin/rlp.cjs + checksum: 10/2af80d98faf7f64dfb6d739c2df7da7350ff5ad52426c3219897e843ee441215db0ffa346873200a6be6d11142edb9536e66acd62436b5005fa935baaf7eb6bd + languageName: node + linkType: hard + "@ethereumjs/tx@npm:3.3.2": version: 3.3.2 resolution: "@ethereumjs/tx@npm:3.3.2" @@ -1192,6 +1201,16 @@ __metadata: languageName: node linkType: hard +"@ethereumjs/util@npm:^9.1.0": + version: 9.1.0 + resolution: "@ethereumjs/util@npm:9.1.0" + dependencies: + "@ethereumjs/rlp": "npm:^5.0.2" + ethereum-cryptography: "npm:^2.2.1" + checksum: 10/4e22c4081c63eebb808eccd54f7f91cd3407f4cac192da5f30a0d6983fe07d51f25e6a9d08624f1376e604bb7dce574aafcf0fbf0becf42f62687c11e710ac41 + languageName: node + linkType: hard + "@ethersproject/abi@npm:5.0.0-beta.153": version: 5.0.0-beta.153 resolution: "@ethersproject/abi@npm:5.0.0-beta.153" @@ -2088,8 +2107,11 @@ __metadata: "@axelar-network/axelarjs-sdk": "npm:^0.13.6" "@chainlink/env-enc": "npm:^1.0.5" "@gooddollar/goodprotocol": "npm:2.1.0" + "@layerzerolabs/devtools-evm-hardhat": "npm:^4.0.4" "@layerzerolabs/lz-definitions": "npm:^3.0.151" "@layerzerolabs/lz-evm-protocol-v2": "npm:^3.0.151" + "@layerzerolabs/lz-v2-utilities": "npm:^3.0.156" + "@layerzerolabs/metadata-tools": "npm:^3.0.3" "@layerzerolabs/oapp-evm": "npm:^0.4.1" "@layerzerolabs/oapp-evm-upgradeable": "npm:^0.1.3" "@layerzerolabs/oft-evm": "npm:^4.0.0" @@ -2097,6 +2119,7 @@ __metadata: "@layerzerolabs/scan-client": "npm:^0.0.6" "@layerzerolabs/solidity-examples": "npm:^0.0.13" "@layerzerolabs/toolbox-hardhat": "npm:~0.6.13" + "@layerzerolabs/ua-devtools": "npm:^5.0.2" "@nomicfoundation/hardhat-chai-matchers": "npm:^1.0.5" "@nomicfoundation/hardhat-network-helpers": "npm:^1.0.6" "@nomicfoundation/hardhat-verify": "npm:^2.1.0" @@ -2119,7 +2142,7 @@ __metadata: ethers: "npm:^5.*" hardhat: "npm:2.*" hardhat-contract-sizer: "npm:^2.6.1" - hardhat-deploy: "npm:^1.0.4" + hardhat-deploy: "npm:^0.12.4" hardhat-gas-reporter: "npm:^1.0.9" merkle-patricia-tree: "npm:^2.*" prettier: "npm:^2.5.1" @@ -2624,7 +2647,7 @@ __metadata: languageName: node linkType: hard -"@layerzerolabs/devtools-evm-hardhat@npm:~4.0.4": +"@layerzerolabs/devtools-evm-hardhat@npm:^4.0.4, @layerzerolabs/devtools-evm-hardhat@npm:~4.0.4": version: 4.0.4 resolution: "@layerzerolabs/devtools-evm-hardhat@npm:4.0.4" dependencies: @@ -2796,7 +2819,7 @@ __metadata: languageName: node linkType: hard -"@layerzerolabs/lz-v2-utilities@npm:^3.0.148": +"@layerzerolabs/lz-v2-utilities@npm:^3.0.148, @layerzerolabs/lz-v2-utilities@npm:^3.0.156": version: 3.0.156 resolution: "@layerzerolabs/lz-v2-utilities@npm:3.0.156" dependencies: @@ -2812,6 +2835,16 @@ __metadata: languageName: node linkType: hard +"@layerzerolabs/metadata-tools@npm:^3.0.3": + version: 3.0.3 + resolution: "@layerzerolabs/metadata-tools@npm:3.0.3" + peerDependencies: + "@layerzerolabs/devtools-evm-hardhat": ~4.0.0 + "@layerzerolabs/ua-devtools": ~5.0.1 + checksum: 10/82e75265a744bdd5ada327b9d1cc6414b5e9f1fa7736662bc393704665e8527fe7e2d665246310d31f970d774c5671f5b2d53dc68d43d505681882ffaef59a0c + languageName: node + linkType: hard + "@layerzerolabs/oapp-evm-upgradeable@npm:^0.1.3": version: 0.1.3 resolution: "@layerzerolabs/oapp-evm-upgradeable@npm:0.1.3" @@ -3032,7 +3065,7 @@ __metadata: languageName: node linkType: hard -"@layerzerolabs/ua-devtools@npm:~5.0.2": +"@layerzerolabs/ua-devtools@npm:^5.0.2, @layerzerolabs/ua-devtools@npm:~5.0.2": version: 5.0.2 resolution: "@layerzerolabs/ua-devtools@npm:5.0.2" peerDependencies: @@ -3064,19 +3097,6 @@ __metadata: languageName: node linkType: hard -"@metamask/eth-sig-util@npm:^4.0.0": - version: 4.0.1 - resolution: "@metamask/eth-sig-util@npm:4.0.1" - dependencies: - ethereumjs-abi: "npm:^0.6.8" - ethereumjs-util: "npm:^6.2.1" - ethjs-util: "npm:^0.1.6" - tweetnacl: "npm:^1.0.3" - tweetnacl-util: "npm:^0.15.1" - checksum: 10/a41a986abd14675badeb02041466e30e1c3ef529c1d131f47c27fd48d73144fcf590f45d8ee8b7cd357725ebf75ece93f4484adf1baf6311cc996f7ef82c4ae1 - languageName: node - linkType: hard - "@noble/ciphers@npm:^1.3.0": version: 1.3.0 resolution: "@noble/ciphers@npm:1.3.0" @@ -3111,6 +3131,15 @@ __metadata: languageName: node linkType: hard +"@noble/curves@npm:~1.8.1": + version: 1.8.2 + resolution: "@noble/curves@npm:1.8.2" + dependencies: + "@noble/hashes": "npm:1.7.2" + checksum: 10/540e7b7a8fe92ecd5cef846f84d07180662eb7fd7d8e9172b8960c31827e74f148fe4630da962138a6be093ae9f8992d14ab23d3682a2cc32be839aa57c03a46 + languageName: node + linkType: hard + "@noble/hashes@npm:1.2.0, @noble/hashes@npm:~1.2.0": version: 1.2.0 resolution: "@noble/hashes@npm:1.2.0" @@ -3125,6 +3154,13 @@ __metadata: languageName: node linkType: hard +"@noble/hashes@npm:1.7.2, @noble/hashes@npm:~1.7.1": + version: 1.7.2 + resolution: "@noble/hashes@npm:1.7.2" + checksum: 10/b5af9e4b91543dcc46a811b5b2c57bfdeb41728361979a19d6110a743e2cb0459872553f68d3a46326d21959964db2776b8c8b4db85ac1d9f63ebcaddf7d59b6 + languageName: node + linkType: hard + "@noble/hashes@npm:1.8.0, @noble/hashes@npm:^1, @noble/hashes@npm:^1.0.0, @noble/hashes@npm:^1.4.0, @noble/hashes@npm:^1.8.0, @noble/hashes@npm:~1.8.0": version: 1.8.0 resolution: "@noble/hashes@npm:1.8.0" @@ -3173,159 +3209,67 @@ __metadata: languageName: node linkType: hard -"@nomicfoundation/ethereumjs-block@npm:4.2.2, @nomicfoundation/ethereumjs-block@npm:^4.0.0": - version: 4.2.2 - resolution: "@nomicfoundation/ethereumjs-block@npm:4.2.2" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:3.1.2" - "@nomicfoundation/ethereumjs-rlp": "npm:4.0.3" - "@nomicfoundation/ethereumjs-trie": "npm:5.0.5" - "@nomicfoundation/ethereumjs-tx": "npm:4.1.2" - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - ethereum-cryptography: "npm:0.1.3" - checksum: 10/9868b3b421b648feaaa0c07d2c533a7c67032eb2b1e2136517c822b7e31bc17049409b5226f70d72d8d2a6d2205b720d743b4abcf8782bb998a149d0c45d947c +"@nomicfoundation/edr-darwin-arm64@npm:0.12.0-next.22": + version: 0.12.0-next.22 + resolution: "@nomicfoundation/edr-darwin-arm64@npm:0.12.0-next.22" + checksum: 10/cc63789c5a496f5fa81cad2454113644d3404b715c2909d4a718ed42ff68eecedf698b09fa7d818b2c9d214bb22e10671fe5c03fff7208be33f7094a179c7fdb languageName: node linkType: hard -"@nomicfoundation/ethereumjs-blockchain@npm:6.2.2, @nomicfoundation/ethereumjs-blockchain@npm:^6.0.0": - version: 6.2.2 - resolution: "@nomicfoundation/ethereumjs-blockchain@npm:6.2.2" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:4.2.2" - "@nomicfoundation/ethereumjs-common": "npm:3.1.2" - "@nomicfoundation/ethereumjs-ethash": "npm:2.0.5" - "@nomicfoundation/ethereumjs-rlp": "npm:4.0.3" - "@nomicfoundation/ethereumjs-trie": "npm:5.0.5" - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - abstract-level: "npm:^1.0.3" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - level: "npm:^8.0.0" - lru-cache: "npm:^5.1.1" - memory-level: "npm:^1.0.0" - checksum: 10/d93025fe35778c97b4e3c17c8dae2f045f3bd51301c3ff79e470c395e8747b467a7522b2acc86e2c439b6226265f95d070009978604b8beb62d426428ab35dfe +"@nomicfoundation/edr-darwin-x64@npm:0.12.0-next.22": + version: 0.12.0-next.22 + resolution: "@nomicfoundation/edr-darwin-x64@npm:0.12.0-next.22" + checksum: 10/5497062c12560a80222026365b831005dd6341957393ab18fbfb0816f5139a9f50bd6679058cd987155ef8d055c0f78a8a1c107d1d3bbbe570dd48ad458fc9f3 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-common@npm:3.1.2, @nomicfoundation/ethereumjs-common@npm:^3.0.0": - version: 3.1.2 - resolution: "@nomicfoundation/ethereumjs-common@npm:3.1.2" - dependencies: - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - crc-32: "npm:^1.2.0" - checksum: 10/cdbcde8169a7e80610448107a8cf2ee27ffd86219eda93530c0dd919e7f5ffa9f16d73ee3e79466191fba7971186aeb5e9aa9cb6d61c3388d1f6e31494e19900 +"@nomicfoundation/edr-linux-arm64-gnu@npm:0.12.0-next.22": + version: 0.12.0-next.22 + resolution: "@nomicfoundation/edr-linux-arm64-gnu@npm:0.12.0-next.22" + checksum: 10/840580f689211d64296176bbe1aa9a915bc0861fcc986a3aff63ef06f13319528c00c23b35a8501de0c40db4215fb5d9da43660f1fcf93c078a70a19be94c177 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-ethash@npm:2.0.5": - version: 2.0.5 - resolution: "@nomicfoundation/ethereumjs-ethash@npm:2.0.5" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:4.2.2" - "@nomicfoundation/ethereumjs-rlp": "npm:4.0.3" - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - abstract-level: "npm:^1.0.3" - bigint-crypto-utils: "npm:^3.0.23" - ethereum-cryptography: "npm:0.1.3" - checksum: 10/f309d1350b3862ac571ef5683be2c91f17c73229f6bddb4b8d8d3a7e44f8b256c070bd632c1725f322dfc43930fb853ad6e0385dbfa0c754205bb5a18436d5c5 +"@nomicfoundation/edr-linux-arm64-musl@npm:0.12.0-next.22": + version: 0.12.0-next.22 + resolution: "@nomicfoundation/edr-linux-arm64-musl@npm:0.12.0-next.22" + checksum: 10/3906d6bb396f52567370c981c2570538ab178af471644c900c59e2911d2a65695d72a1ee12e50817ed4faad022650d78ccf2928b580d42bdce9887cb924a15c6 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-evm@npm:1.3.2, @nomicfoundation/ethereumjs-evm@npm:^1.0.0": - version: 1.3.2 - resolution: "@nomicfoundation/ethereumjs-evm@npm:1.3.2" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:3.1.2" - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - "@types/async-eventemitter": "npm:^0.2.1" - async-eventemitter: "npm:^0.2.4" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - mcl-wasm: "npm:^0.7.1" - rustbn.js: "npm:~0.2.0" - checksum: 10/aa25aa4854d52dcf64a456b4137e99aa4047b3b68a57fe3b9cdaaeedd22fd830375904bdfcf4a64ee2e7cdbb5933aea0478af3758723fa682d0ee5fc3a2c8590 +"@nomicfoundation/edr-linux-x64-gnu@npm:0.12.0-next.22": + version: 0.12.0-next.22 + resolution: "@nomicfoundation/edr-linux-x64-gnu@npm:0.12.0-next.22" + checksum: 10/643b58910f43d609b75edd09ced48a42f2f69e79b14ab287393fbbc94b40ac9f7c1b10b129f5ea5e8cb1e6e2f9c7ef831c5634fff32b74aa77e92bffa80f6a98 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-rlp@npm:4.0.3, @nomicfoundation/ethereumjs-rlp@npm:^4.0.0": - version: 4.0.3 - resolution: "@nomicfoundation/ethereumjs-rlp@npm:4.0.3" - bin: - rlp: bin/rlp - checksum: 10/707001cc306759477bad63204051734f0a1f167647494b689799e926adce67c5ed4ce4e5a6942b094c49c807c93f9bb0650c2b0c47259ebc99341fbb73e016f4 +"@nomicfoundation/edr-linux-x64-musl@npm:0.12.0-next.22": + version: 0.12.0-next.22 + resolution: "@nomicfoundation/edr-linux-x64-musl@npm:0.12.0-next.22" + checksum: 10/fe764a03bf81e9430d61a54bcbd7fbd14ad787bd175c2d4c518e730fd55294f3c4c2b5df16e30d8e88b5657927570450e5b913b2682f7c9174d85865a616aa2b languageName: node linkType: hard -"@nomicfoundation/ethereumjs-statemanager@npm:1.0.5, @nomicfoundation/ethereumjs-statemanager@npm:^1.0.0": - version: 1.0.5 - resolution: "@nomicfoundation/ethereumjs-statemanager@npm:1.0.5" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:3.1.2" - "@nomicfoundation/ethereumjs-rlp": "npm:4.0.3" - "@nomicfoundation/ethereumjs-trie": "npm:5.0.5" - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - functional-red-black-tree: "npm:^1.0.1" - checksum: 10/d6119d505785b5797cf89e6b68297c541c54d2a703acef975c9c4a55b850c245421ad19423a218ef1a7a5a90a9c9d867b4700dedc3f689c440801afeb3ebbeb7 +"@nomicfoundation/edr-win32-x64-msvc@npm:0.12.0-next.22": + version: 0.12.0-next.22 + resolution: "@nomicfoundation/edr-win32-x64-msvc@npm:0.12.0-next.22" + checksum: 10/8a04621a153369b0f8654be8af3d9ea64c6d78daaf7b11cfc99aebb53c05ad7e06876f9d729ce7edb7889648e6a3416e5515441e1fa9df066bc9b16c247be432 languageName: node linkType: hard -"@nomicfoundation/ethereumjs-trie@npm:5.0.5, @nomicfoundation/ethereumjs-trie@npm:^5.0.0": - version: 5.0.5 - resolution: "@nomicfoundation/ethereumjs-trie@npm:5.0.5" +"@nomicfoundation/edr@npm:0.12.0-next.22": + version: 0.12.0-next.22 + resolution: "@nomicfoundation/edr@npm:0.12.0-next.22" dependencies: - "@nomicfoundation/ethereumjs-rlp": "npm:4.0.3" - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - ethereum-cryptography: "npm:0.1.3" - readable-stream: "npm:^3.6.0" - checksum: 10/bcae77e5256a0bcb3484fbdf814e25a828300bdbfc719d63b62d833edf163337ed975e3d9efa161f45befd61e6d48ca06821cc24aba1e3f70071d7af37e9ffd2 - languageName: node - linkType: hard - -"@nomicfoundation/ethereumjs-tx@npm:4.1.2, @nomicfoundation/ethereumjs-tx@npm:^4.0.0": - version: 4.1.2 - resolution: "@nomicfoundation/ethereumjs-tx@npm:4.1.2" - dependencies: - "@nomicfoundation/ethereumjs-common": "npm:3.1.2" - "@nomicfoundation/ethereumjs-rlp": "npm:4.0.3" - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - ethereum-cryptography: "npm:0.1.3" - checksum: 10/015b78fa3f39e84b49f8d641fc8f8a6dbbc826128511a412d1177a58e6eb65d0279ed79aa1962d3c4553e6e8c77cc86baa2b37c944acb702654980d954031305 - languageName: node - linkType: hard - -"@nomicfoundation/ethereumjs-util@npm:8.0.6, @nomicfoundation/ethereumjs-util@npm:^8.0.0": - version: 8.0.6 - resolution: "@nomicfoundation/ethereumjs-util@npm:8.0.6" - dependencies: - "@nomicfoundation/ethereumjs-rlp": "npm:4.0.3" - ethereum-cryptography: "npm:0.1.3" - checksum: 10/1aa498a052cab2444e009f4364620d10fa94bbf0792fbe3662d286abd6a4d9f4e5b1eb22dcb991dcc34351891ab00a575ba843c198f490269525cc08de8b6819 - languageName: node - linkType: hard - -"@nomicfoundation/ethereumjs-vm@npm:^6.0.0": - version: 6.4.2 - resolution: "@nomicfoundation/ethereumjs-vm@npm:6.4.2" - dependencies: - "@nomicfoundation/ethereumjs-block": "npm:4.2.2" - "@nomicfoundation/ethereumjs-blockchain": "npm:6.2.2" - "@nomicfoundation/ethereumjs-common": "npm:3.1.2" - "@nomicfoundation/ethereumjs-evm": "npm:1.3.2" - "@nomicfoundation/ethereumjs-rlp": "npm:4.0.3" - "@nomicfoundation/ethereumjs-statemanager": "npm:1.0.5" - "@nomicfoundation/ethereumjs-trie": "npm:5.0.5" - "@nomicfoundation/ethereumjs-tx": "npm:4.1.2" - "@nomicfoundation/ethereumjs-util": "npm:8.0.6" - "@types/async-eventemitter": "npm:^0.2.1" - async-eventemitter: "npm:^0.2.4" - debug: "npm:^4.3.3" - ethereum-cryptography: "npm:0.1.3" - functional-red-black-tree: "npm:^1.0.1" - mcl-wasm: "npm:^0.7.1" - rustbn.js: "npm:~0.2.0" - checksum: 10/ce2a333fcd8d12fbc03896d99865e7d586b0f61fcce5846c48eec91d0454f4662438c6cbcacc5122574749234ffc7b84ebedde2f0685cabf38ee6790a10b8213 + "@nomicfoundation/edr-darwin-arm64": "npm:0.12.0-next.22" + "@nomicfoundation/edr-darwin-x64": "npm:0.12.0-next.22" + "@nomicfoundation/edr-linux-arm64-gnu": "npm:0.12.0-next.22" + "@nomicfoundation/edr-linux-arm64-musl": "npm:0.12.0-next.22" + "@nomicfoundation/edr-linux-x64-gnu": "npm:0.12.0-next.22" + "@nomicfoundation/edr-linux-x64-musl": "npm:0.12.0-next.22" + "@nomicfoundation/edr-win32-x64-msvc": "npm:0.12.0-next.22" + checksum: 10/a009a8c1e7af76ad68093fa845f8238be785ac6084da7c08df070cc1eb84e50ee86f7d69d37c1ecb3b9a64229314324f81a4719299430e181e390283cfea9fc4 languageName: node linkType: hard @@ -4708,15 +4652,6 @@ __metadata: languageName: node linkType: hard -"@types/async-eventemitter@npm:^0.2.1": - version: 0.2.4 - resolution: "@types/async-eventemitter@npm:0.2.4" - dependencies: - "@types/events": "npm:*" - checksum: 10/bc57952ce6a290187402aaca1638137a0fb5434bab13ef00a8c168e9f6139040d1df84e174963d56521960c1b6b61ac836fad01d065755e44a3d3939b722d405 - languageName: node - linkType: hard - "@types/babel__core@npm:^7.1.14": version: 7.20.5 resolution: "@types/babel__core@npm:7.20.5" @@ -4866,13 +4801,6 @@ __metadata: languageName: node linkType: hard -"@types/events@npm:*": - version: 3.0.3 - resolution: "@types/events@npm:3.0.3" - checksum: 10/50af9312fab001fd6bd4bb3ff65830f940877e6778de140a92481a0d9bf5f4853d44ec758a8800ef60e0598ac43ed1b5688116a3c65906ae54e989278d6c7c82 - languageName: node - linkType: hard - "@types/express-serve-static-core@npm:4.17.31": version: 4.17.31 resolution: "@types/express-serve-static-core@npm:4.17.31" @@ -5033,7 +4961,7 @@ __metadata: languageName: node linkType: hard -"@types/lru-cache@npm:5.1.1, @types/lru-cache@npm:^5.1.0": +"@types/lru-cache@npm:5.1.1": version: 5.1.1 resolution: "@types/lru-cache@npm:5.1.1" checksum: 10/0afadefc983306684a8ef95b6337a0d9e3f687e7e89e1f1f3f2e1ce3fbab5b018bb84cf277d781f871175a2c8f0176762b69e58b6f4296ee1b816cea94d5ef06 @@ -5587,21 +5515,6 @@ __metadata: languageName: node linkType: hard -"abstract-level@npm:^1.0.0, abstract-level@npm:^1.0.2, abstract-level@npm:^1.0.3, abstract-level@npm:^1.0.4": - version: 1.0.4 - resolution: "abstract-level@npm:1.0.4" - dependencies: - buffer: "npm:^6.0.3" - catering: "npm:^2.1.0" - is-buffer: "npm:^2.0.5" - level-supports: "npm:^4.0.0" - level-transcoder: "npm:^1.0.1" - module-error: "npm:^1.0.1" - queue-microtask: "npm:^1.2.3" - checksum: 10/8edf4cf55b7b66b653296f53a643bcf1501074be099d8c44351595cd33f769b7b2aed216d5fffe1c99ebea4acf14f5ae093e98baa60ea1d236ea8a3387350ebb - languageName: node - linkType: hard - "abstract-leveldown@npm:3.0.0": version: 3.0.0 resolution: "abstract-leveldown@npm:3.0.0" @@ -5820,6 +5733,15 @@ __metadata: languageName: node linkType: hard +"ansi-align@npm:^3.0.0": + version: 3.0.1 + resolution: "ansi-align@npm:3.0.1" + dependencies: + string-width: "npm:^4.1.0" + checksum: 10/4c7e8b6a10eaf18874ecee964b5db62ac86d0b9266ad4987b3a1efcb5d11a9e12c881ee40d14951833135a8966f10a3efe43f9c78286a6e632f53d85ad28b9c0 + languageName: node + linkType: hard + "ansi-colors@npm:4.1.1": version: 4.1.1 resolution: "ansi-colors@npm:4.1.1" @@ -6278,7 +6200,7 @@ __metadata: languageName: node linkType: hard -"async-eventemitter@npm:0.2.4, async-eventemitter@npm:^0.2.2, async-eventemitter@npm:^0.2.4": +"async-eventemitter@npm:0.2.4, async-eventemitter@npm:^0.2.2": version: 0.2.4 resolution: "async-eventemitter@npm:0.2.4" dependencies: @@ -7305,13 +7227,6 @@ __metadata: languageName: node linkType: hard -"bigint-crypto-utils@npm:^3.0.23": - version: 3.3.0 - resolution: "bigint-crypto-utils@npm:3.3.0" - checksum: 10/94d10ac9db66b093c7c2beace833ac167b57188c8ac784a7e207ea4f585cf9c2066e5d1f5a1b26cb6ccb7f7be8e38687c79f049b87df07cfdc7bd484aee2390d - languageName: node - linkType: hard - "bignumber.js@npm:^7.2.1": version: 7.2.1 resolution: "bignumber.js@npm:7.2.1" @@ -7419,6 +7334,22 @@ __metadata: languageName: node linkType: hard +"boxen@npm:^5.1.2": + version: 5.1.2 + resolution: "boxen@npm:5.1.2" + dependencies: + ansi-align: "npm:^3.0.0" + camelcase: "npm:^6.2.0" + chalk: "npm:^4.1.0" + cli-boxes: "npm:^2.2.1" + string-width: "npm:^4.2.2" + type-fest: "npm:^0.20.2" + widest-line: "npm:^3.1.0" + wrap-ansi: "npm:^7.0.0" + checksum: 10/bc3d3d88d77dc8cabb0811844acdbd4805e8ca8011222345330817737042bf6f86d93eb74a3f7e0cab634e64ef69db03cf52b480761ed90a965de0c8ff1bea8c + languageName: node + linkType: hard + "brace-expansion@npm:^1.1.7": version: 1.1.12 resolution: "brace-expansion@npm:1.1.12" @@ -7472,18 +7403,6 @@ __metadata: languageName: node linkType: hard -"browser-level@npm:^1.0.1": - version: 1.0.1 - resolution: "browser-level@npm:1.0.1" - dependencies: - abstract-level: "npm:^1.0.2" - catering: "npm:^2.1.1" - module-error: "npm:^1.0.2" - run-parallel-limit: "npm:^1.1.0" - checksum: 10/e712569111782da76853fecf648b43ff878ff2301c2830a9e7399685b646824a85f304dea5f023e02ee41a63a972f9aad734bd411069095adc9c79784fc649a5 - languageName: node - linkType: hard - "browser-stdout@npm:1.3.1, browser-stdout@npm:^1.3.1": version: 1.3.1 resolution: "browser-stdout@npm:1.3.1" @@ -7958,7 +7877,7 @@ __metadata: languageName: node linkType: hard -"catering@npm:^2.0.0, catering@npm:^2.1.0, catering@npm:^2.1.1": +"catering@npm:^2.0.0, catering@npm:^2.1.0": version: 2.1.1 resolution: "catering@npm:2.1.1" checksum: 10/4669c9fa5f3a73273535fb458a964d8aba12dc5102d8487049cf03623bef3cdff4b5d9f92ff04c00f1001057a7cc7df6e700752ac622c2a7baf7bcff34166683 @@ -8183,7 +8102,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.4.0, chokidar@npm:^3.5.2, chokidar@npm:^3.5.3": +"chokidar@npm:^3.5.2, chokidar@npm:^3.5.3": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -8202,6 +8121,15 @@ __metadata: languageName: node linkType: hard +"chokidar@npm:^4.0.0": + version: 4.0.3 + resolution: "chokidar@npm:4.0.3" + dependencies: + readdirp: "npm:^4.0.1" + checksum: 10/bf2a575ea5596000e88f5db95461a9d59ad2047e939d5a4aac59dd472d126be8f1c1ff3c7654b477cf532d18f42a97279ef80ee847972fd2a25410bf00b80b59 + languageName: node + linkType: hard + "chownr@npm:^1.1.4": version: 1.1.4 resolution: "chownr@npm:1.1.4" @@ -8287,20 +8215,6 @@ __metadata: languageName: node linkType: hard -"classic-level@npm:^1.2.0": - version: 1.4.1 - resolution: "classic-level@npm:1.4.1" - dependencies: - abstract-level: "npm:^1.0.2" - catering: "npm:^2.1.0" - module-error: "npm:^1.0.1" - napi-macros: "npm:^2.2.2" - node-gyp: "npm:latest" - node-gyp-build: "npm:^4.3.0" - checksum: 10/11f9362301477cb5cf3b147e5846754e0e4296231e265145101403f4a5cb797a685b6a9b6b4c880a42b05772f846a222a5a7a563262ca15b5ca03e25e9a805db - languageName: node - linkType: hard - "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -8308,7 +8222,7 @@ __metadata: languageName: node linkType: hard -"cli-boxes@npm:^2.2.0": +"cli-boxes@npm:^2.2.0, cli-boxes@npm:^2.2.1": version: 2.2.1 resolution: "cli-boxes@npm:2.2.1" checksum: 10/be79f8ec23a558b49e01311b39a1ea01243ecee30539c880cf14bf518a12e223ef40c57ead0cb44f509bffdffc5c129c746cd50d863ab879385370112af4f585 @@ -9254,7 +9168,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.3, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:~4.4.1": +"debug@npm:4, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:~4.4.1": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -10661,7 +10575,7 @@ __metadata: languageName: node linkType: hard -"ethereum-cryptography@npm:0.1.3, ethereum-cryptography@npm:^0.1.3": +"ethereum-cryptography@npm:^0.1.3": version: 0.1.3 resolution: "ethereum-cryptography@npm:0.1.3" dependencies: @@ -10696,7 +10610,7 @@ __metadata: languageName: node linkType: hard -"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2": +"ethereum-cryptography@npm:^2.0.0, ethereum-cryptography@npm:^2.1.2, ethereum-cryptography@npm:^2.2.1": version: 2.2.1 resolution: "ethereum-cryptography@npm:2.2.1" dependencies: @@ -10743,7 +10657,7 @@ __metadata: languageName: node linkType: hard -"ethereumjs-abi@npm:0.6.8, ethereumjs-abi@npm:^0.6.8": +"ethereumjs-abi@npm:0.6.8": version: 0.6.8 resolution: "ethereumjs-abi@npm:0.6.8" dependencies: @@ -10853,7 +10767,7 @@ __metadata: languageName: node linkType: hard -"ethereumjs-util@npm:6.2.1, ethereumjs-util@npm:^6.0.0, ethereumjs-util@npm:^6.1.0, ethereumjs-util@npm:^6.2.0, ethereumjs-util@npm:^6.2.1": +"ethereumjs-util@npm:6.2.1, ethereumjs-util@npm:^6.0.0, ethereumjs-util@npm:^6.1.0, ethereumjs-util@npm:^6.2.0": version: 6.2.1 resolution: "ethereumjs-util@npm:6.2.1" dependencies: @@ -11082,7 +10996,7 @@ __metadata: languageName: node linkType: hard -"ethjs-util@npm:0.1.6, ethjs-util@npm:^0.1.3, ethjs-util@npm:^0.1.6": +"ethjs-util@npm:0.1.6, ethjs-util@npm:^0.1.3": version: 0.1.6 resolution: "ethjs-util@npm:0.1.6" dependencies: @@ -12574,9 +12488,9 @@ __metadata: languageName: node linkType: hard -"hardhat-deploy@npm:^1.0.4": - version: 1.0.4 - resolution: "hardhat-deploy@npm:1.0.4" +"hardhat-deploy@npm:^0.12.4": + version: 0.12.4 + resolution: "hardhat-deploy@npm:0.12.4" dependencies: "@ethersproject/abi": "npm:^5.7.0" "@ethersproject/abstract-signer": "npm:^5.7.0" @@ -12589,6 +12503,7 @@ __metadata: "@ethersproject/solidity": "npm:^5.7.0" "@ethersproject/transactions": "npm:^5.7.0" "@ethersproject/wallet": "npm:^5.7.0" + "@types/qs": "npm:^6.9.7" axios: "npm:^0.21.1" chalk: "npm:^4.1.2" chokidar: "npm:^3.5.2" @@ -12599,9 +12514,9 @@ __metadata: fs-extra: "npm:^10.0.0" match-all: "npm:^1.2.6" murmur-128: "npm:^0.2.1" - neoqs: "npm:^6.13.0" + qs: "npm:^6.9.4" zksync-ethers: "npm:^5.0.0" - checksum: 10/41b4784703a29a1690578a34fd8bb75c463347e7050c55ef39ca1d183fef5463bc8a1f679d57e5f7228524fc6abf8e499f8edbe1bcbc801abd10e3ac3299f9f3 + checksum: 10/127feddc4f95eaa530e7fe77021f7634e23f7f182a8a2e6d51ef5c7254037b05862c54aaa79dd7e07cac627dbae6bff68f8439851ca048ccc571110704998f9d languageName: node linkType: hard @@ -12618,58 +12533,47 @@ __metadata: languageName: node linkType: hard -"hardhat@npm:2.12.3": - version: 2.12.3 - resolution: "hardhat@npm:2.12.3" +"hardhat@npm:2.28.3": + version: 2.28.3 + resolution: "hardhat@npm:2.28.3" dependencies: + "@ethereumjs/util": "npm:^9.1.0" "@ethersproject/abi": "npm:^5.1.2" - "@metamask/eth-sig-util": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-block": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-blockchain": "npm:^6.0.0" - "@nomicfoundation/ethereumjs-common": "npm:^3.0.0" - "@nomicfoundation/ethereumjs-evm": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-rlp": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-statemanager": "npm:^1.0.0" - "@nomicfoundation/ethereumjs-trie": "npm:^5.0.0" - "@nomicfoundation/ethereumjs-tx": "npm:^4.0.0" - "@nomicfoundation/ethereumjs-util": "npm:^8.0.0" - "@nomicfoundation/ethereumjs-vm": "npm:^6.0.0" + "@nomicfoundation/edr": "npm:0.12.0-next.22" "@nomicfoundation/solidity-analyzer": "npm:^0.1.0" "@sentry/node": "npm:^5.18.1" - "@types/bn.js": "npm:^5.1.0" - "@types/lru-cache": "npm:^5.1.0" - abort-controller: "npm:^3.0.0" adm-zip: "npm:^0.4.16" aggregate-error: "npm:^3.0.0" ansi-escapes: "npm:^4.3.0" - chalk: "npm:^2.4.2" - chokidar: "npm:^3.4.0" + boxen: "npm:^5.1.2" + chokidar: "npm:^4.0.0" ci-info: "npm:^2.0.0" debug: "npm:^4.1.1" enquirer: "npm:^2.3.0" env-paths: "npm:^2.2.0" ethereum-cryptography: "npm:^1.0.3" - ethereumjs-abi: "npm:^0.6.8" - find-up: "npm:^2.1.0" + find-up: "npm:^5.0.0" fp-ts: "npm:1.19.3" fs-extra: "npm:^7.0.1" - glob: "npm:7.2.0" immutable: "npm:^4.0.0-rc.12" io-ts: "npm:1.10.4" + json-stream-stringify: "npm:^3.1.4" keccak: "npm:^3.0.2" lodash: "npm:^4.17.11" + micro-eth-signer: "npm:^0.14.0" mnemonist: "npm:^0.38.0" mocha: "npm:^10.0.0" p-map: "npm:^4.0.0" - qs: "npm:^6.7.0" + picocolors: "npm:^1.1.0" raw-body: "npm:^2.4.1" resolve: "npm:1.17.0" semver: "npm:^6.3.0" - solc: "npm:0.7.3" + solc: "npm:0.8.26" source-map-support: "npm:^0.5.13" stacktrace-parser: "npm:^0.1.10" + tinyglobby: "npm:^0.2.6" tsort: "npm:0.0.1" - undici: "npm:^5.4.0" + undici: "npm:^5.14.0" uuid: "npm:^8.3.2" ws: "npm:^7.4.6" peerDependencies: @@ -12681,8 +12585,8 @@ __metadata: typescript: optional: true bin: - hardhat: internal/cli/cli.js - checksum: 10/5797d504f1a868847a738039f48ca5c981a3a1e33b4c35ed2aaa17d5310d141cdcfc695df9d1faf50871c571ed93a4310635e64ef7588735ce1ee55d3e322ac2 + hardhat: internal/cli/bootstrap.js + checksum: 10/588c385bb51d04000bd6c54c1c6dc058fc2911e147e50f1c1b98c8d921d0c81cf5489f8efcb82e2fd89f1ce847aec66220b2866de78f2db896f76ebfe704368f languageName: node linkType: hard @@ -14730,6 +14634,13 @@ __metadata: languageName: node linkType: hard +"json-stream-stringify@npm:^3.1.4": + version: 3.1.6 + resolution: "json-stream-stringify@npm:3.1.6" + checksum: 10/d52919465b4a31d7a0b5720ca0e6268f757fc1515486d5c77cfb75f7a9e4b58e13a73a2f811d6d322b9a101750d3961b48a68ee9d9b299ac3846ef2921a62a81 + languageName: node + linkType: hard + "json-stringify-safe@npm:~5.0.1": version: 5.0.1 resolution: "json-stringify-safe@npm:5.0.1" @@ -15232,17 +15143,6 @@ __metadata: languageName: node linkType: hard -"level@npm:^8.0.0": - version: 8.0.1 - resolution: "level@npm:8.0.1" - dependencies: - abstract-level: "npm:^1.0.4" - browser-level: "npm:^1.0.1" - classic-level: "npm:^1.2.0" - checksum: 10/a9c6d1fc50e30b2cc80b3c975b34de0eb12daab7fb4f8a546a28303705a45685340a904544fcd32e9a380fae7c62474ebd9cdb0108021ddbc7b88dd9c913f126 - languageName: node - linkType: hard - "leveldown@npm:5.6.0, leveldown@npm:^5.4.0": version: 5.6.0 resolution: "leveldown@npm:5.6.0" @@ -15755,13 +15655,6 @@ __metadata: languageName: node linkType: hard -"mcl-wasm@npm:^0.7.1": - version: 0.7.9 - resolution: "mcl-wasm@npm:0.7.9" - checksum: 10/eb689cf0e2422ef7b98e8b040ed601821aea839718c876cd734e9148ca7013adf1c869bbc9495aac351e645d314ec3bd3d3612c91f60c499c5aea8d3dd2a7e38 - languageName: node - linkType: hard - "md5.js@npm:^1.3.4": version: 1.3.5 resolution: "md5.js@npm:1.3.5" @@ -15822,17 +15715,6 @@ __metadata: languageName: node linkType: hard -"memory-level@npm:^1.0.0": - version: 1.0.0 - resolution: "memory-level@npm:1.0.0" - dependencies: - abstract-level: "npm:^1.0.0" - functional-red-black-tree: "npm:^1.0.1" - module-error: "npm:^1.0.1" - checksum: 10/e3293d8c67ebc0aa4b29982c5f8e3d139c5b1b04b97fa3ae98f940f91c7bdfefec9ff189742943734ebb6c7efa85fed6a4d559407b2d5751106b24cac17a23a6 - languageName: node - linkType: hard - "memorystream@npm:^0.3.1": version: 0.3.1 resolution: "memorystream@npm:0.3.1" @@ -15928,6 +15810,17 @@ __metadata: languageName: node linkType: hard +"micro-eth-signer@npm:^0.14.0": + version: 0.14.0 + resolution: "micro-eth-signer@npm:0.14.0" + dependencies: + "@noble/curves": "npm:~1.8.1" + "@noble/hashes": "npm:~1.7.1" + micro-packed: "npm:~0.7.2" + checksum: 10/de9fb0262253c22f280dc6fae18b61950ac2bf0e086d9ca60e3dd150f64b922ca9073e7566ebfc71be773507f3979ebdccee8bc9bb1162697b7e0eeec1dbd691 + languageName: node + linkType: hard + "micro-ftch@npm:^0.3.1": version: 0.3.1 resolution: "micro-ftch@npm:0.3.1" @@ -15942,6 +15835,15 @@ __metadata: languageName: node linkType: hard +"micro-packed@npm:~0.7.2": + version: 0.7.3 + resolution: "micro-packed@npm:0.7.3" + dependencies: + "@scure/base": "npm:~1.2.5" + checksum: 10/956c89cd0753e82566e13f67406e5983ae9cb7bcbe539238c5e0dcc605974f91d454b819dd3cf63acec7d67e63ef17afde45b451eaa00a38de31c6024a75cee5 + languageName: node + linkType: hard + "micromatch@npm:^3.1.4": version: 3.1.10 resolution: "micromatch@npm:3.1.10" @@ -16358,7 +16260,7 @@ __metadata: languageName: node linkType: hard -"module-error@npm:^1.0.1, module-error@npm:^1.0.2": +"module-error@npm:^1.0.1": version: 1.0.2 resolution: "module-error@npm:1.0.2" checksum: 10/5d653e35bd55b3e95f8aee2cdac108082ea892e71b8f651be92cde43e4ee86abee4fa8bd7fc3fe5e68b63926d42f63c54cd17b87a560c31f18739295575a3962 @@ -16526,13 +16428,6 @@ __metadata: languageName: node linkType: hard -"napi-macros@npm:^2.2.2": - version: 2.2.2 - resolution: "napi-macros@npm:2.2.2" - checksum: 10/2cdb9c40ad4b424b14fbe5e13c5329559e2b511665acf41cdcda172fd2270202dc747a2d288b687c72bc70f654c797bc24a93adb67631128d62461588d7cc070 - languageName: node - linkType: hard - "napi-macros@npm:~2.0.0": version: 2.0.0 resolution: "napi-macros@npm:2.0.0" @@ -16577,13 +16472,6 @@ __metadata: languageName: node linkType: hard -"neoqs@npm:^6.13.0": - version: 6.13.0 - resolution: "neoqs@npm:6.13.0" - checksum: 10/222ac1cc370a3906c24cbc3e384dea47f993d593200b35d5f97ccb03e7248b405889d480a668ddb131898ec3d5049dffc4ddc1910d75f96772ded9c6a33f103a - languageName: node - linkType: hard - "next-tick@npm:^1.1.0": version: 1.1.0 resolution: "next-tick@npm:1.1.0" @@ -18486,7 +18374,7 @@ __metadata: languageName: node linkType: hard -"qs@npm:^6.12.3, qs@npm:^6.4.0, qs@npm:^6.7.0, qs@npm:^6.9.4, qs@npm:~6.14.0": +"qs@npm:^6.12.3, qs@npm:^6.4.0, qs@npm:^6.9.4, qs@npm:~6.14.0": version: 6.14.1 resolution: "qs@npm:6.14.1" dependencies: @@ -18733,6 +18621,13 @@ __metadata: languageName: node linkType: hard +"readdirp@npm:^4.0.1": + version: 4.1.2 + resolution: "readdirp@npm:4.1.2" + checksum: 10/7b817c265940dba90bb9c94d82920d76c3a35ea2d67f9f9d8bd936adcfe02d50c802b14be3dd2e725e002dddbe2cc1c7a0edfb1bc3a365c9dfd5a61e612eea1e + languageName: node + linkType: hard + "readdirp@npm:~3.6.0": version: 3.6.0 resolution: "readdirp@npm:3.6.0" @@ -19257,15 +19152,6 @@ __metadata: languageName: node linkType: hard -"run-parallel-limit@npm:^1.1.0": - version: 1.1.0 - resolution: "run-parallel-limit@npm:1.1.0" - dependencies: - queue-microtask: "npm:^1.2.2" - checksum: 10/672c3b87e7f939c684b9965222b361421db0930223ed1e43ebf0e7e48ccc1a022ea4de080bef4d5468434e2577c33b7681e3f03b7593fdc49ad250a55381123c - languageName: node - linkType: hard - "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -20011,28 +19897,26 @@ __metadata: languageName: node linkType: hard -"solc@npm:0.7.3": - version: 0.7.3 - resolution: "solc@npm:0.7.3" +"solc@npm:0.8.21": + version: 0.8.21 + resolution: "solc@npm:0.8.21" dependencies: command-exists: "npm:^1.2.8" - commander: "npm:3.0.2" + commander: "npm:^8.1.0" follow-redirects: "npm:^1.12.1" - fs-extra: "npm:^0.30.0" js-sha3: "npm:0.8.0" memorystream: "npm:^0.3.1" - require-from-string: "npm:^2.0.0" semver: "npm:^5.5.0" tmp: "npm:0.0.33" bin: - solcjs: solcjs - checksum: 10/68bb783765d1aacf6ebe151ddbffff4c17f679046f2f83a2abae99c57cc0e7dbbcebd62b31861892df18fde272697c37c7a7518f1a9b1219de80217f0c780f0b + solcjs: solc.js + checksum: 10/e050323d171e38969b159567b0635ee9f58285f89e477a1650008e91b7c4ea176c3ad6d984e568ec4046fed0ff63d93b148ea53fa8177fe294278fea41af55a9 languageName: node linkType: hard -"solc@npm:0.8.21": - version: 0.8.21 - resolution: "solc@npm:0.8.21" +"solc@npm:0.8.26": + version: 0.8.26 + resolution: "solc@npm:0.8.26" dependencies: command-exists: "npm:^1.2.8" commander: "npm:^8.1.0" @@ -20043,7 +19927,7 @@ __metadata: tmp: "npm:0.0.33" bin: solcjs: solc.js - checksum: 10/e050323d171e38969b159567b0635ee9f58285f89e477a1650008e91b7c4ea176c3ad6d984e568ec4046fed0ff63d93b148ea53fa8177fe294278fea41af55a9 + checksum: 10/30ef9c2687f727eb5bdd685c77b1a0b354e7d6ba7a080cfcdce5a89f25a1399ff7949fecef47768088d825588da230da0044b46f056fc36f3959c0e3d3c9a82b languageName: node linkType: hard @@ -20922,7 +20806,7 @@ __metadata: languageName: node linkType: hard -"tinyglobby@npm:^0.2.12": +"tinyglobby@npm:^0.2.12, tinyglobby@npm:^0.2.6": version: 0.2.15 resolution: "tinyglobby@npm:0.2.15" dependencies: @@ -21327,7 +21211,7 @@ __metadata: languageName: node linkType: hard -"tweetnacl-util@npm:^0.15.0, tweetnacl-util@npm:^0.15.1": +"tweetnacl-util@npm:^0.15.0": version: 0.15.1 resolution: "tweetnacl-util@npm:0.15.1" checksum: 10/ae6aa8a52cdd21a95103a4cc10657d6a2040b36c7a6da7b9d3ab811c6750a2d5db77e8c36969e75fdee11f511aa2b91c552496c6e8e989b6e490e54aca2864fc @@ -21341,7 +21225,7 @@ __metadata: languageName: node linkType: hard -"tweetnacl@npm:^1.0.0, tweetnacl@npm:^1.0.3": +"tweetnacl@npm:^1.0.0": version: 1.0.3 resolution: "tweetnacl@npm:1.0.3" checksum: 10/ca122c2f86631f3c0f6d28efb44af2a301d4a557a62a3e2460286b08e97567b258c2212e4ad1cfa22bd6a57edcdc54ba76ebe946847450ab0999e6d48ccae332 @@ -21732,7 +21616,7 @@ __metadata: languageName: node linkType: hard -"undici@npm:^5.14.0, undici@npm:^5.4.0": +"undici@npm:^5.14.0": version: 5.29.0 resolution: "undici@npm:5.29.0" dependencies: From 6a11b7925a03aa9cf33f736985b98b7ff54b29e6 Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 27 Jan 2026 10:21:11 -0500 Subject: [PATCH 02/41] feat: enhance GoodDollarOFTAdapter with new bridge limits and account management structures, and add deployment and configuration scripts for cross-chain functionality --- .../contracts/oft/GoodDollarOFTAdapter.sol | 123 +++-- .../bridge-contracts/release/deployment.json | 40 +- .../scripts/oft/bridge-oft-token.ts | 420 ++++++++++++++++++ .../scripts/oft/check-layerzero-config.ts | 170 +++++++ .../scripts/oft/configure-oft-xdc-celo.sh | 239 ++++++++++ .../scripts/oft/grant-minter-role.ts | 139 ++++++ .../scripts/oft/oft-deploy.ts | 257 +++++++++++ .../scripts/oft/set-enforced-options.ts | 174 ++++++++ .../scripts/oft/set-minter-burner-limits.ts | 193 ++++++++ ...nsfer-oft-adapter-ownership-from-avatar.ts | 168 +++++++ .../oft/transfer-oft-adapter-ownership.ts | 252 +++++++++++ .../scripts/oft/verify-oft-contracts.sh | 234 ++++++++++ .../typechain-types/hardhat.d.ts | 9 + 13 files changed, 2365 insertions(+), 53 deletions(-) create mode 100644 packages/bridge-contracts/scripts/oft/bridge-oft-token.ts create mode 100644 packages/bridge-contracts/scripts/oft/check-layerzero-config.ts create mode 100755 packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh create mode 100644 packages/bridge-contracts/scripts/oft/grant-minter-role.ts create mode 100644 packages/bridge-contracts/scripts/oft/oft-deploy.ts create mode 100644 packages/bridge-contracts/scripts/oft/set-enforced-options.ts create mode 100644 packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts create mode 100644 packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts create mode 100644 packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts create mode 100755 packages/bridge-contracts/scripts/oft/verify-oft-contracts.sh diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index f26f3c8..42d6c7e 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { OFTCoreUpgradeable } from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTCoreUpgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol"; -import { BridgeHelperLibrary } from "../messagePassingBridge/BridgeHelperLibrary.sol"; -import { IMessagePassingBridge } from "../messagePassingBridge/IMessagePassingBridge.sol"; import { INameService } from "@gooddollar/goodprotocol/contracts/utils/DAOUpgradeableContract.sol"; interface IIdentity { @@ -17,7 +17,7 @@ interface IIdentity { * @notice Upgradeable OFT adapter that uses mint/burn mechanisms for cross-chain transfers * @dev Inherits from OFTCoreUpgradeable and implements mint/burn logic similar to MintBurnOFTAdapter */ -contract GoodDollarOFTAdapter is OFTCoreUpgradeable { +contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { /// @dev Struct for storing bridge fees struct BridgeFees { uint256 minFee; @@ -25,6 +25,27 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { uint256 fee; // Fee in basis points (0-10000, where 10000 = 100%) } + /// @dev Struct for storing account limits + struct AccountLimit { + uint256 lastTransferReset; + uint256 bridged24Hours; + } + + /// @dev Struct for storing bridge daily limits + struct BridgeDailyLimit { + uint256 lastTransferReset; + uint256 bridged24Hours; + } + + /// @dev Struct for storing bridge limits + struct BridgeLimits { + uint256 dailyLimit; + uint256 txLimit; + uint256 accountDailyLimit; + uint256 minAmount; + bool onlyWhitelisted; + } + /// @dev The underlying ERC20 token IERC20 internal innerToken; @@ -38,13 +59,13 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { address public feeRecipient; /// @dev Bridge limits structure - IMessagePassingBridge.BridgeLimits public bridgeLimits; + BridgeLimits public bridgeLimits; /// @dev Bridge daily limit tracking - IMessagePassingBridge.BridgeDailyLimit public bridgeDailyLimit; + BridgeDailyLimit public bridgeDailyLimit; /// @dev Account-specific daily limit tracking - mapping(address => IMessagePassingBridge.AccountLimit) public accountsDailyLimit; + mapping(address => AccountLimit) public accountsDailyLimit; /// @dev A mapping for approved requests above limits mapping(uint256 => bool) public approvedRequests; @@ -104,17 +125,32 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { * @param _owner The contract owner * @param _feeRecipient The address to receive bridge fees (can be address(0) to disable fees) * @param _nameService The NameService contract for identity checks (can be address(0)) + * @dev We call all parent initializers explicitly to satisfy the upgrade plugin's static analysis: + * 1. __Ownable_init() - Initialize OwnableUpgradeable + * 2. __OAppSender_init(_owner) - Initialize OAppSenderUpgradeable (calls __OAppCore_init) + * 3. __OAppReceiver_init(_owner) - Initialize OAppReceiverUpgradeable (calls __OAppCore_init again) + * 4. __OFTCore_init(_owner) - Initialize OFTCoreUpgradeable (calls __OApp_init which calls __OAppCore_init again) + * + * Note: __OAppCore_init calls endpoint.setDelegate(_owner), which is idempotent, so multiple calls are safe. + * The upgrade plugin may flag duplicate __OAppCore_init calls, but this is intentional and safe. */ + /// @custom:oz-upgrades-unsafe-allow constructor state-variable-immutable missing-initializer function initialize( address _token, IMintableBurnable _minterBurner, - address _lzEndpoint, + address /* _lzEndpoint */, address _owner, address _feeRecipient, INameService _nameService ) public initializer { - // Initialize parent contracts - __OFTCore_init(_owner); + // Initialize OwnableUpgradeable first + __Ownable_init(); + __OAppSender_init(_owner); // This calls __OAppCore_init internally + __OAppReceiver_init(_owner); // This also calls __OAppCore_init internally + __OFTCore_init(_owner); // This calls __OApp_init which calls both sender/receiver again + + // Transfer ownership to the specified owner (since __Ownable_init sets it to msg.sender) + _transferOwnership(_owner); // Set state variables innerToken = IERC20(_token); @@ -171,7 +207,7 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { * @notice Sets the bridge limits configuration * @param _limits The bridge limits struct */ - function setBridgeLimits(IMessagePassingBridge.BridgeLimits memory _limits) external onlyOwner { + function setBridgeLimits(BridgeLimits memory _limits) external onlyOwner { bridgeLimits = _limits; emit BridgeLimitsSet( _limits.dailyLimit, @@ -216,33 +252,35 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { * @return error The error message, if any */ function canBridge(address _from, uint256 _amount) public view returns (bool isWithinLimit, string memory error) { - IMessagePassingBridge.BridgeLimits memory limits = IMessagePassingBridge.BridgeLimits({ - dailyLimit: bridgeLimits.dailyLimit, - txLimit: bridgeLimits.txLimit, - accountDailyLimit: bridgeLimits.accountDailyLimit, - minAmount: bridgeLimits.minAmount, - onlyWhitelisted: bridgeLimits.onlyWhitelisted - }); - - IMessagePassingBridge.AccountLimit memory accountLimit = IMessagePassingBridge.AccountLimit({ - lastTransferReset: accountsDailyLimit[_from].lastTransferReset, - bridged24Hours: accountsDailyLimit[_from].bridged24Hours - }); - - IMessagePassingBridge.BridgeDailyLimit memory dailyLimit = IMessagePassingBridge.BridgeDailyLimit({ - lastTransferReset: bridgeDailyLimit.lastTransferReset, - bridged24Hours: bridgeDailyLimit.bridged24Hours - }); - - return BridgeHelperLibrary.canBridge( - limits, - accountLimit, - dailyLimit, - nameService, - isClosed, - _from, - _amount - ); + if (isClosed) return (false, 'closed'); + + if (_amount < bridgeLimits.minAmount) return (false, 'minAmount'); + + uint256 account24hours = accountsDailyLimit[_from].bridged24Hours; + if (accountsDailyLimit[_from].lastTransferReset < block.timestamp - 1 days) { + account24hours = _amount; + } else { + account24hours += _amount; + } + + if (bridgeLimits.onlyWhitelisted && address(nameService) != address(0)) { + IIdentity id = IIdentity(nameService.getAddress("IDENTITY")); + if (address(id) != address(0)) { + if (!id.isWhitelisted(_from)) return (false, 'not whitelisted'); + } + } + + if (account24hours > bridgeLimits.accountDailyLimit) return (false, 'accountDailyLimit'); + + if (_amount > bridgeLimits.txLimit) return (false, 'txLimit'); + + if (bridgeDailyLimit.lastTransferReset < block.timestamp - 1 days) { + if (_amount > bridgeLimits.dailyLimit) return (false, 'dailyLimit'); + } else { + if (bridgeDailyLimit.bridged24Hours + _amount > bridgeLimits.dailyLimit) return (false, 'dailyLimit'); + } + + return (true, ''); } /** @@ -349,4 +387,15 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { // Return the actual amount received by the recipient (amount minus fee) return amountToRecipient; } + + /** + * @dev Authorizes the upgrade of the contract to a new implementation + * @param newImplementation The address of the new implementation contract + * @dev Only the owner can authorize upgrades (enforced by onlyOwner modifier) + * @dev This function is required for UUPS upgradeable pattern + */ + function _authorizeUpgrade(address newImplementation) internal override onlyOwner { + // Authorization is handled by onlyOwner modifier + // Additional checks can be added here if needed + } } \ No newline at end of file diff --git a/packages/bridge-contracts/release/deployment.json b/packages/bridge-contracts/release/deployment.json index 24f06f7..4edf8c3 100644 --- a/packages/bridge-contracts/release/deployment.json +++ b/packages/bridge-contracts/release/deployment.json @@ -1,17 +1,25 @@ { - "fuse": { - "fuseBridge": "0x5B7cEfD0e7d952F7E400416F9c98fE36F1043822", - "celoBridge": "0x165aEb4184A0cc4eFb96Cb6035341Ba2265bA564", - "registry": "0x44a1E0A83821E239F9Cef248CECc3AC5b910aeD2" - }, - "staging": { - "fuseBridge": "0x1CD7a472FF2c6826252932CC8aC40473898d90E8", - "celoBridge": "0x0A6538C9DAc037f5313CaAEb42b19081993e3183", - "registry": "0x44a1E0A83821E239F9Cef248CECc3AC5b910aeD2" - }, - "production": { - "fuseBridge": "0x08fdf766694C353401350c225cAEB9C631dC3288", - "celoBridge": "0xfb152Fc469A3E9154f8AA60bbD6700EcBC357A54", - "registry": "0x44a1E0A83821E239F9Cef248CECc3AC5b910aeD2" - } -} \ No newline at end of file + "fuse": { + "fuseBridge": "0x5B7cEfD0e7d952F7E400416F9c98fE36F1043822", + "celoBridge": "0x165aEb4184A0cc4eFb96Cb6035341Ba2265bA564", + "registry": "0x44a1E0A83821E239F9Cef248CECc3AC5b910aeD2" + }, + "staging": { + "fuseBridge": "0x1CD7a472FF2c6826252932CC8aC40473898d90E8", + "celoBridge": "0x0A6538C9DAc037f5313CaAEb42b19081993e3183", + "registry": "0x44a1E0A83821E239F9Cef248CECc3AC5b910aeD2" + }, + "production": { + "fuseBridge": "0x08fdf766694C353401350c225cAEB9C631dC3288", + "celoBridge": "0xfb152Fc469A3E9154f8AA60bbD6700EcBC357A54", + "registry": "0x44a1E0A83821E239F9Cef248CECc3AC5b910aeD2" + }, + "development-celo": { + "GoodDollarMinterBurner": "0x3850786C7627Ce276EF6F715a0eF31d5FeCf1fc7", + "GoodDollarOFTAdapter": "0x28aE7CE2F3BBb0bf58B24F1961885aeE1e456f46" + }, + "development-xdc": { + "GoodDollarMinterBurner": "0xb20eaE658437b793257C326751a695ED5e94dD3E", + "GoodDollarOFTAdapter": "0x3a568CFD4e3F6ef9f5e341FAbAD84B6Cc41C1A75" + } +} diff --git a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts b/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts new file mode 100644 index 0000000..02eed1a --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts @@ -0,0 +1,420 @@ +/*** + * Script to bridge 1 G$ token between XDC and CELO using LayerZero OFT adapter + * + * Usage: + * # Bridge from XDC to CELO: + * npx hardhat run scripts/multichain-deploy/oft/bridge-oft-token.ts --network production-xdc + * # or + * npx hardhat run scripts/multichain-deploy/oft/bridge-oft-token.ts --network development-xdc + * + * # Bridge from CELO to XDC: + * npx hardhat run scripts/multichain-deploy/oft/bridge-oft-token.ts --network production-celo + * # or + * npx hardhat run scripts/multichain-deploy/oft/bridge-oft-token.ts --network development-celo + * + * Note: Make sure you have: + * - GoodDollarOFTAdapter deployed on both XDC and CELO + * - Sufficient G$ balance on the source chain + * - Sufficient native token (XDC or CELO) for gas and LayerZero fees + */ + +import { network, ethers } from "hardhat"; +import { Contract } from "ethers"; +import { EndpointId } from "@layerzerolabs/lz-definitions"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment.json"; + +// IERC20 interface for token operations +const IERC20_ABI = [ + "function balanceOf(address owner) view returns (uint256)", + "function allowance(address owner, address spender) view returns (uint256)", + "function approve(address spender, uint256 amount) returns (bool)", + "function transfer(address to, uint256 amount) returns (bool)", +]; + +// LayerZero Endpoint IDs (eid) +// These are LayerZero v2 endpoint IDs, not chain IDs +const XDC_ENDPOINT_ID = EndpointId.XDC_V2_MAINNET; +const CELO_ENDPOINT_ID = process.env.CELO_LZ_ENDPOINT_ID + ? parseInt(process.env.CELO_LZ_ENDPOINT_ID) + : EndpointId.CELO_V2_MAINNET; // Default CELO LayerZero endpoint ID + +const main = async () => { + const networkName = network.name; + const [sender] = await ethers.getSigners(); + + // Detect source and destination networks + const isXDC = networkName.includes("xdc"); + const isCELO = networkName.includes("celo"); + + if (!isXDC && !isCELO) { + throw new Error( + `Network must be XDC or CELO. Current network: ${networkName}\n` + + `Supported networks: production-xdc, development-xdc, production-celo, development-celo` + ); + } + + const sourceNetwork = isXDC ? "XDC" : "CELO"; + const destNetwork = isXDC ? "CELO" : "XDC"; + const sourceEndpointId = isXDC ? XDC_ENDPOINT_ID : CELO_ENDPOINT_ID; + const destEndpointId = isXDC ? CELO_ENDPOINT_ID : XDC_ENDPOINT_ID; + const nativeTokenName = isXDC ? "XDC" : "CELO"; + + console.log("=== Bridge G$ ==="); + console.log(`Bridging from ${sourceNetwork} to ${destNetwork}`); + console.log("Source Network:", networkName); + console.log("Sender:", sender.address); + console.log(`Sender balance: ${ethers.utils.formatEther(await ethers.provider.getBalance(sender.address))} ${nativeTokenName}`); + + // Get deployment info for source network + const currentRelease = release[networkName] || {}; + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + + if (!goodProtocolContracts) { + throw new Error(`No GoodProtocol contracts found for network: ${networkName}`); + } + + const oftAdapterAddress = currentRelease.GoodDollarOFTAdapter; + const tokenAddress = goodProtocolContracts.GoodDollar || goodProtocolContracts.SuperGoodDollar; + const minterBurnerAddress = currentRelease.GoodDollarMinterBurner; + + if (!oftAdapterAddress) { + throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + } + + if (!tokenAddress) { + throw new Error(`GoodDollar token not found in deployment.json for ${networkName}`); + } + + if (!minterBurnerAddress) { + throw new Error(`GoodDollarMinterBurner not found in deployment.json for ${networkName}`); + } + + console.log("\nSource chain contract addresses:"); + console.log("OFT Adapter:", oftAdapterAddress); + console.log("Token:", tokenAddress); + console.log("MinterBurner:", minterBurnerAddress); + + // Get contracts + const token = new ethers.Contract(tokenAddress, IERC20_ABI, sender); + const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); + + // Amount to bridge: 1 G$ = 1e18 + const amount = ethers.utils.parseEther("1"); + console.log("\nAmount to bridge:", ethers.utils.formatEther(amount), "G$"); + + // Check token balance + const balance = await token.balanceOf(sender.address); + console.log("Current G$ balance:", ethers.utils.formatEther(balance), "G$"); + + if (balance.lt(amount)) { + throw new Error(`Insufficient balance. Need ${ethers.utils.formatEther(amount)} G$, have ${ethers.utils.formatEther(balance)} G$`); + } + + // Check and approve MinterBurner if needed (required for burning tokens) + // The OFT adapter calls minterBurner.burn(), which calls token.burnFrom() requiring approval + // Note: OFT adapter itself doesn't need approval since approvalRequired() returns false + const minterBurnerAllowance = await token.allowance(sender.address, minterBurnerAddress); + console.log("\nChecking MinterBurner allowance..."); + console.log("Current MinterBurner allowance:", ethers.utils.formatEther(minterBurnerAllowance), "G$"); + + if (minterBurnerAllowance.lt(amount)) { + console.log("\nApproving MinterBurner to burn tokens..."); + const approveMinterBurnerTx = await token.approve(minterBurnerAddress, amount); + await approveMinterBurnerTx.wait(); + console.log("MinterBurner approval confirmed:", approveMinterBurnerTx.hash); + } else { + console.log("Sufficient MinterBurner allowance already set"); + } + + // Recipient address (same address on destination chain) + const recipient = sender.address; + console.log(`\nRecipient on ${destNetwork}:`, recipient); + + // Get destination network OFT adapter address + let destNetworkName: string; + if (isXDC) { + // Bridging to CELO - try production-celo first, then development-celo + destNetworkName = "development-celo"; + } else { + // Bridging to XDC - try production-xdc first, then development-xdc + destNetworkName = "development-xdc"; + } + + const destRelease = release[destNetworkName] || {}; + if (!destRelease.GoodDollarOFTAdapter) { + throw new Error(`No deployment found for destination network: ${destNetworkName}`); + } + + const destOFTAdapter = destRelease.GoodDollarOFTAdapter; + + if (!destOFTAdapter) { + throw new Error( + `${destNetwork} OFT adapter address not found in deployment.json.\n` + + `Please either:\n` + + ` 1. Deploy OFT adapter on ${destNetwork} and add it to deployment.json, or\n` + + ` 2. Manually set the peer using: scripts/set-oft-peer.ts` + ); + } + + console.log(`\nDestination chain (${destNetwork}):`); + console.log(`OFT Adapter: ${destOFTAdapter}`); + console.log(`Network name: ${destNetworkName}`); + + // Check if peer is set for destination chain + console.log(`\nChecking if ${destNetwork} peer is configured...`); + const destPeer = await oftAdapter.peers(destEndpointId); + console.log(`Current ${destNetwork} peer:`, destPeer); + + const expectedPeer = ethers.utils.hexZeroPad(destOFTAdapter, 32); + console.log(`Expected ${destNetwork} peer (OFT adapter on ${destNetwork}):`, destOFTAdapter); + console.log("Expected peer (bytes32):", expectedPeer); + + // Compare case-insensitively (addresses can have different case) + const destPeerLower = destPeer.toLowerCase(); + const expectedPeerLower = expectedPeer.toLowerCase(); + + if (destPeerLower === ethers.constants.HashZero.toLowerCase() || destPeerLower !== expectedPeerLower) { + console.log(`\n⚠️ WARNING: ${destNetwork} peer is not configured correctly!`); + console.log("You need to set the peer before bridging. Run this command:"); + console.log(` oftAdapter.setPeer(${destEndpointId}, "${expectedPeer}")`); + console.log("\nOr use the LayerZero wire command:"); + console.log(` npx hardhat lz:oapp:wire --oapp-config layerzero.config.ts --network ${networkName}`); + throw new Error(`NoPeer: ${destNetwork} peer (endpoint ${destEndpointId}) is not set. Expected: ${destOFTAdapter}`); + } + + console.log(`✅ ${destNetwork} peer is configured correctly`); + + // Double-check MinterBurner approval before calling quoteSend + console.log("\nVerifying MinterBurner approval before quoteSend..."); + const finalMinterBurnerAllowance = await token.allowance(sender.address, minterBurnerAddress); + console.log("Final MinterBurner allowance:", ethers.utils.formatEther(finalMinterBurnerAllowance), "G$"); + + if (finalMinterBurnerAllowance.lt(amount)) { + throw new Error( + `MinterBurner allowance insufficient. Need ${ethers.utils.formatEther(amount)} G$, have ${ethers.utils.formatEther(finalMinterBurnerAllowance)} G$` + ); + } + + // Check send library configuration before attempting quoteSend + console.log("\nChecking send library configuration..."); + try { + // Get the endpoint address from the OFT adapter (OApp has endpoint() function) + let endpointAddress: string; + try { + endpointAddress = await oftAdapter.endpoint(); + } catch { + // Fallback: try to get from the deployment config + const lzEndpoints: { [key: string]: string } = { + "development-celo": "0x1a44076050125825900e736c501f859c50fE728c", + "production-celo": "0x1a44076050125825900e736c501f859c50fE728c", + "development-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", + "production-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", + }; + endpointAddress = lzEndpoints[networkName] || ""; + if (!endpointAddress) { + throw new Error(`Could not determine endpoint address for ${networkName}`); + } + } + console.log("LayerZero Endpoint:", endpointAddress); + + // Check if send library is configured + // The endpoint's MessageLibManager has getSendLibrary function + const endpointABI = [ + "function getSendLibrary(address _sender, uint32 _dstEid) external view returns (address lib)", + "function defaultSendLibrary(uint32 _dstEid) external view returns (address lib)", + "function defaultReceiveLibrary(uint32 _srcEid) external view returns (address lib)", + "function getReceiveLibrary(address _receiver, uint32 _srcEid) external view returns (address lib)", + ]; + const endpoint = new ethers.Contract(endpointAddress, endpointABI, sender); + + try { + const sendLib = await endpoint.getSendLibrary(oftAdapterAddress, destEndpointId); + console.log(`Send library for ${destNetwork} (eid ${destEndpointId}):`, sendLib); + + if (sendLib === ethers.constants.AddressZero) { + console.log("⚠️ WARNING: No send library configured!"); + try { + const defaultSendLib = await endpoint.defaultSendLibrary(destEndpointId); + console.log(`Default send library for ${destNetwork}:`, defaultSendLib); + if (defaultSendLib === ethers.constants.AddressZero) { + throw new Error( + `No send library configured for ${destNetwork} (eid ${destEndpointId}). ` + + `You need to run the LayerZero wiring command: ` + + `yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network ${networkName}` + ); + } else { + console.log("ℹ️ Using default send library. Consider configuring a specific send library for better control."); + } + } catch (e: any) { + throw new Error( + `Send library not configured for ${destNetwork} (eid ${destEndpointId}). ` + + `Error: ${e.message}. ` + + `You need to run the LayerZero wiring command: ` + + `yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network ${networkName}` + ); + } + } else { + console.log("✅ Send library is configured"); + + // Note: We can't check receive library on destination chain from here (cross-chain calls not supported) + // The error 0x6592671c likely indicates missing DVN/executor configuration from wiring + console.log(`\nℹ️ Note: Cannot check receive library on ${destNetwork} from ${sourceNetwork} network.`); + console.log(` If quoteSend fails, ensure wiring is completed on BOTH networks.`); + } + } catch (e: any) { + console.log("⚠️ Could not check send library configuration:", e.message); + console.log("Proceeding with quoteSend - will fail if send library is not configured..."); + } + } catch (e: any) { + console.log("⚠️ Could not check endpoint configuration:", e.message); + console.log("Proceeding with quoteSend..."); + } + + // Estimate LayerZero fee using quoteSend + console.log("\nEstimating LayerZero fee..."); + try { + // LayerZero v2 OFT uses quoteSend with SendParam struct + // SendParam: { dstEid, to, amountLD, minAmountLD, extraOptions, composeMsg, oftCmd } + // + // For extraOptions: Use combineOptions to build proper options, or use empty bytes + // Since wiring failed, we'll try using the OApp's combineOptions if available, + // otherwise use empty options (but this might fail if enforced options are required) + let extraOptions = "0x"; + + try { + // Try to use combineOptions to build proper options + // combineOptions(msgType, extraOptions) - msgType 1 = SEND + const combineOptionsResult = await oftAdapter.combineOptions(1, "0x"); + if (combineOptionsResult && combineOptionsResult !== "0x") { + extraOptions = combineOptionsResult; + console.log("Using combined options from OApp"); + } + } catch (e: any) { + console.log("Note: Could not use combineOptions, using empty options"); + console.log("If quoteSend fails, enforced options may need to be configured via wiring"); + } + + const sendParam = { + dstEid: destEndpointId, // destination endpoint ID + to: ethers.utils.hexZeroPad(recipient, 32), // recipient address (bytes32 encoded) + amountLD: amount, // amount to send in local decimals + minAmountLD: amount, // minimum amount to receive (slippage protection) + extraOptions: extraOptions, // extra options (may need to be properly encoded) + composeMsg: "0x", // compose message (empty for simple send) + oftCmd: "0x" // OFT command (unused in default) + }; + + // Quote the fee (payInLzToken = false means pay in native token) + const msgFee = await oftAdapter.quoteSend(sendParam, false); + + console.log(`Estimated native fee: ${ethers.utils.formatEther(msgFee.nativeFee)} ${nativeTokenName}`); + console.log("Estimated LZ token fee:", ethers.utils.formatEther(msgFee.lzTokenFee), "LZ"); + + // Check if sender has enough native token for fee + const senderBalance = await ethers.provider.getBalance(sender.address); + if (senderBalance.lt(msgFee.nativeFee)) { + throw new Error( + `Insufficient native token for fee. Need ${ethers.utils.formatEther(msgFee.nativeFee)} ${nativeTokenName}, have ${ethers.utils.formatEther(senderBalance)} ${nativeTokenName}` + ); + } + + // Send tokens + console.log("\nSending tokens via LayerZero OFT..."); + console.log("This may take a few minutes..."); + + const sendTx = await oftAdapter.send( + sendParam, // SendParam struct + msgFee, // MessagingFee struct + sender.address, // refund address + { value: msgFee.nativeFee } // send native fee + ); + + console.log("Transaction sent:", sendTx.hash); + console.log("Waiting for confirmation..."); + + const receipt = await sendTx.wait(); + console.log("\n✅ Transaction confirmed!"); + console.log("Block number:", receipt.blockNumber); + console.log("Gas used:", receipt.gasUsed.toString()); + + // Look for Send event + const sendEvent = receipt.events?.find((e: any) => e.event === "Send"); + if (sendEvent) { + console.log("\nSend event found:"); + console.log(" Amount:", ethers.utils.formatEther(sendEvent.args?.amountLD || 0), "G$"); + console.log(" Recipient:", sendEvent.args?.to); + } + + console.log("\n=== Bridge Initiated Successfully ==="); + console.log(`Bridging from ${sourceNetwork} to ${destNetwork}`); + console.log("Transaction hash:", sendTx.hash); + console.log(`Recipient on ${destNetwork}:`, recipient); + console.log("Amount:", ethers.utils.formatEther(amount), "G$"); + console.log("\nYou can track the cross-chain message at:"); + console.log(`https://layerzeroscan.com/tx/${sendTx.hash}`); + console.log(`\nNote: The tokens will arrive on ${destNetwork} after the LayerZero message is delivered.`); + console.log("This typically takes a few minutes."); + + } catch (error: any) { + console.error("\n❌ Error during bridge:"); + + // Provide helpful error messages for common issues + if (error.code === 'CALL_EXCEPTION' || error.reason || error.data) { + const errorData = error.data || error.error?.data || ''; + + // Check for invalid worker options error (error code 0x6592671c = LZ_ULN_InvalidWorkerOptions) + if (errorData.includes('6592671c')) { + console.error("\n🔍 DIAGNOSIS: Invalid Worker Options"); + console.error("The error code 0x6592671c = LZ_ULN_InvalidWorkerOptions indicates invalid extraOptions."); + console.error("This happens when enforced options are required but not properly configured."); + console.error("\nWhat's configured:"); + console.error(" ✅ Send library: Found"); + console.error(" ✅ Peer connection: Set"); + console.error("\nWhat's likely missing:"); + console.error(" ❌ DVN (Data Verification Network) configuration"); + console.error(" ❌ Executor configuration"); + console.error(" ❌ Receive library configuration on destination"); + console.error(" ❌ Complete wiring configuration"); + console.error("\nROOT CAUSE:"); + console.error("The 'lz:oapp:wire' command failed earlier, so enforced options weren't configured."); + console.error("The OApp requires specific worker options (gas limits, etc.) but they're not set."); + console.error("\nSOLUTION:"); + console.error("1. The wiring command MUST succeed to configure enforced options:"); + console.error(` yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network ${networkName}`); + console.error(` yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network ${destNetworkName}`); + console.error("\n2. If wiring fails with permission errors (0xc4c52593), you need to:"); + console.error(" - Run wiring from an account that has delegate permissions on the endpoint"); + console.error(" - The OApp owner must be set as a delegate on the endpoint"); + console.error(" - Contact LayerZero support if you need help with endpoint permissions"); + console.error("\n3. Alternative: Manually configure enforced options:"); + console.error(" - Use the OApp's setEnforcedOptions function if available"); + console.error(" - Or check LayerZero documentation for manual option configuration"); + console.error("\n4. Check LayerZero Scan for default configurations:"); + console.error(` Visit: https://layerzeroscan.com/tools/defaults?version=V2`); + } else if (error.message?.includes('send library') || error.message?.includes('SendLib') || error.message?.includes('receive library') || error.message?.includes('ReceiveLib')) { + console.error("\n🔍 DIAGNOSIS: LayerZero library configuration issue"); + console.error("Check library configuration using:"); + console.error(` yarn hardhat run scripts/oft/check-layerzero-config.ts --network ${networkName}`); + } + + // Check for peer errors + if (errorData.includes('NoPeer') || error.message?.includes('peer')) { + console.error("\n🔍 DIAGNOSIS: Peer not configured"); + console.error("The peer connection between chains is not set."); + console.error("\nSOLUTION:"); + console.error(`Run: yarn hardhat run scripts/oft/set-layerzero-peers.ts --network ${networkName}`); + } + } + + throw error; + } +}; + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + diff --git a/packages/bridge-contracts/scripts/oft/check-layerzero-config.ts b/packages/bridge-contracts/scripts/oft/check-layerzero-config.ts new file mode 100644 index 0000000..2fb0fed --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/check-layerzero-config.ts @@ -0,0 +1,170 @@ +/** + * Script to check LayerZero configuration status + * + * This script checks what's configured and what's missing for LayerZero OFT. + * + * Usage: + * yarn hardhat run scripts/oft/check-layerzero-config.ts --network development-xdc + * yarn hardhat run scripts/oft/check-layerzero-config.ts --network development-celo + */ + +import { network, ethers } from "hardhat"; +import { EndpointId } from "@layerzerolabs/lz-definitions"; +import release from "../../release/deployment.json"; + +const { name: networkName } = network; + +// Endpoint IDs +const XDC_EID = EndpointId.XDC_V2_MAINNET; // 30109 +const CELO_EID = EndpointId.CELO_V2_MAINNET; // 30125 + +// Endpoint addresses +const ENDPOINTS: { [key: string]: string } = { + "development-celo": "0x1a44076050125825900e736c501f859c50fE728c", + "production-celo": "0x1a44076050125825900e736c501f859c50fE728c", + "development-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", + "production-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", +}; + +async function main() { + const [signer] = await ethers.getSigners(); + console.log("=== LayerZero Configuration Check ===\n"); + console.log("Network:", networkName); + console.log("Signer:", signer.address); + console.log(""); + + const isXDC = networkName.includes("xdc"); + const isCELO = networkName.includes("celo"); + + if (!isXDC && !isCELO) { + throw new Error(`Unsupported network: ${networkName}`); + } + + const endpointAddress = ENDPOINTS[networkName]; + if (!endpointAddress) { + throw new Error(`Endpoint address not found for ${networkName}`); + } + + const oftAdapterAddress = (release[networkName] as any)?.GoodDollarOFTAdapter; + if (!oftAdapterAddress) { + throw new Error(`OFT adapter not found for ${networkName}`); + } + + const destEid = isXDC ? CELO_EID : XDC_EID; + const destNetwork = isXDC ? "CELO" : "XDC"; + + console.log("LayerZero Endpoint:", endpointAddress); + console.log("OFT Adapter:", oftAdapterAddress); + console.log("Destination:", destNetwork, `(eid ${destEid})`); + console.log(""); + + // Endpoint ABI + const endpointABI = [ + "function getSendLibrary(address _sender, uint32 _dstEid) external view returns (address lib)", + "function defaultSendLibrary(uint32 _dstEid) external view returns (address lib)", + "function getReceiveLibrary(address _receiver, uint32 _srcEid) external view returns (address lib)", + "function defaultReceiveLibrary(uint32 _srcEid) external view returns (address lib)", + "function getDefaultExecutor(uint32 _eid) external view returns (address executor)", + "function getDefaultDVN(uint32 _eid) external view returns (address dvn)", + "function getConfig(address _oapp, address _lib, uint32 _eid, uint32 _configType) external view returns (bytes memory)", + ]; + + const endpoint = new ethers.Contract(endpointAddress, endpointABI, signer); + + // Check send library + console.log("📤 Send Library Configuration:"); + try { + const sendLib = await endpoint.getSendLibrary(oftAdapterAddress, destEid); + if (sendLib !== ethers.constants.AddressZero) { + console.log(` ✅ Configured: ${sendLib}`); + } else { + const defaultSendLib = await endpoint.defaultSendLibrary(destEid); + if (defaultSendLib !== ethers.constants.AddressZero) { + console.log(` ⚠️ Using default: ${defaultSendLib}`); + } else { + console.log(` ❌ Not configured (no default available)`); + } + } + } catch (e: any) { + console.log(` ❌ Error checking: ${e.message}`); + } + + // Check receive library + console.log("\n📥 Receive Library Configuration:"); + try { + const sourceEid = isXDC ? XDC_EID : CELO_EID; + const receiveLib = await endpoint.getReceiveLibrary(oftAdapterAddress, sourceEid); + if (receiveLib !== ethers.constants.AddressZero) { + console.log(` ✅ Configured: ${receiveLib}`); + } else { + const defaultReceiveLib = await endpoint.defaultReceiveLibrary(sourceEid); + if (defaultReceiveLib !== ethers.constants.AddressZero) { + console.log(` ⚠️ Using default: ${defaultReceiveLib}`); + } else { + console.log(` ❌ Not configured (no default available)`); + } + } + } catch (e: any) { + console.log(` ❌ Error checking: ${e.message}`); + } + + // Check executor + console.log("\n⚙️ Executor Configuration:"); + try { + const executor = await endpoint.getDefaultExecutor(destEid); + if (executor !== ethers.constants.AddressZero) { + console.log(` ✅ Default executor for ${destNetwork}: ${executor}`); + } else { + console.log(` ❌ No default executor configured for ${destNetwork}`); + } + } catch (e: any) { + console.log(` ⚠️ Could not check executor: ${e.message}`); + } + + // Check DVN + console.log("\n🔐 DVN (Data Verification Network) Configuration:"); + try { + const dvn = await endpoint.getDefaultDVN(destEid); + if (dvn !== ethers.constants.AddressZero) { + console.log(` ✅ Default DVN for ${destNetwork}: ${dvn}`); + } else { + console.log(` ❌ No default DVN configured for ${destNetwork}`); + } + } catch (e: any) { + console.log(` ⚠️ Could not check DVN: ${e.message}`); + } + + // Check peer + console.log("\n🔗 Peer Configuration:"); + try { + const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); + const peer = await oftAdapter.peers(destEid); + const peerAddress = ethers.utils.getAddress(ethers.utils.hexDataSlice(peer, 12)); + if (peerAddress !== ethers.constants.AddressZero) { + console.log(` ✅ Peer set: ${peerAddress}`); + } else { + console.log(` ❌ Peer not set`); + } + } catch (e: any) { + console.log(` ❌ Error checking peer: ${e.message}`); + } + + console.log("\n=== Summary ==="); + console.log("If quoteSend is failing with error 0x6592671c, check:"); + console.log("1. Send library is configured (✅ or ⚠️)"); + console.log("2. Receive library is configured (✅ or ⚠️)"); + console.log("3. Executor is configured (✅)"); + console.log("4. DVN is configured (✅)"); + console.log("5. Peer is set (✅)"); + console.log("\nIf any are missing (❌), you need to run:"); + console.log(` yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network ${networkName}`); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + + diff --git a/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh b/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh new file mode 100755 index 0000000..4f30936 --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh @@ -0,0 +1,239 @@ +#!/bin/bash + +# Script to configure OFT (Omnichain Fungible Token) on XDC and CELO networks +# +# This script automates the complete OFT setup process: +# 1. Deploy OFT contracts (MinterBurner and OFTAdapter) on both networks +# 2. Wire LayerZero connections between XDC and CELO +# 3. Grant MINTER_ROLE to MinterBurner on both networks +# 4. Transfer OFT adapter ownership to DAO Avatar on both networks +# 5. Set mint/burn limits on MinterBurner for both networks +# 6. Test bridge functionality (optional, last step) +# +# Usage: +# ./scripts/multichain-deploy/oft/configure-oft-xdc-celo.sh +# +# Environment variables (optional): +# DAILY_LIMIT=1000000 # Daily bridge limit in G$ (e.g., 1M G$ - will be converted to wei automatically) +# TX_LIMIT=100000 # Per-transaction limit in G$ (e.g., 100K G$) +# ACCOUNT_DAILY_LIMIT=50000 # Per-account daily limit in G$ (e.g., 50K G$) +# MIN_AMOUNT=10 # Minimum bridge amount in G$ (e.g., 10 G$) +# ONLY_WHITELISTED=false # Whether to restrict to whitelisted addresses +# SKIP_BRIDGE_TEST=true # Skip bridge test step +# SKIP_LIMITS=true # Skip setting limits step +# SKIP_WIRING=true # Skip LayerZero wiring step (peers must be set manually) +# +# Note: Limit values can be specified in decimal format (e.g., "1000000" for 1M G$) +# The script will automatically convert them to wei (18 decimals). +# You can also use wei values directly if preferred. + +set -e # Exit on error + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Function to print colored output +print_step() { + echo -e "${BLUE}=== $1 ===${NC}" +} + +print_success() { + echo -e "${GREEN}✅ $1${NC}" +} + +print_warning() { + echo -e "${YELLOW}⚠️ $1${NC}" +} + +print_error() { + echo -e "${RED}❌ $1${NC}" +} + +# Check if we're in the project root +if [ ! -f "hardhat.config.ts" ] && [ ! -f "hardhat.config.js" ]; then + print_error "Please run this script from the project root directory" + exit 1 +fi + +print_step "OFT Configuration Script for XDC and CELO" +echo "This script will configure OFT on both development-xdc and development-celo networks" +echo "" + +# Step 1: Deploy OFT contracts +print_step "Step 1: Deploying OFT contracts" + +print_step "Deploying on development-xdc..." +yarn hardhat run scripts/oft/oft-deploy.ts --network development-xdc +print_success "OFT contracts deployed on development-xdc" + +print_step "Deploying on development-celo..." +yarn hardhat run scripts/oft/oft-deploy.ts --network development-celo +print_success "OFT contracts deployed on development-celo" + +echo "" + +# Step 2: Grant MINTER_ROLE +print_step "Step 2: Granting MINTER_ROLE to GoodDollarMinterBurner" + +print_step "Granting MINTER_ROLE on development-xdc..." +yarn hardhat run scripts/oft/grant-minter-role.ts --network development-xdc +print_success "MINTER_ROLE granted on development-xdc" + +print_step "Granting MINTER_ROLE on development-celo..." +yarn hardhat run scripts/oft/grant-minter-role.ts --network development-celo +print_success "MINTER_ROLE granted on development-celo" + +echo "" + +# Step 4: Set LayerZero peers (required before wiring, after ownership transfer) +print_step "Step 4: Setting LayerZero peer connections" + +print_step "Setting peer on development-xdc..." +yarn hardhat run scripts/oft/set-layerzero-peers.ts --network development-xdc +print_success "Peer set on development-xdc" + +print_step "Setting peer on development-celo..." +yarn hardhat run scripts/oft/set-layerzero-peers.ts --network development-celo +print_success "Peer set on development-celo" + +echo "" + +# Step 5: Wire LayerZero connections (optional - peers already set) +WIRE_XDC_SUCCESS=false +WIRE_CELO_SUCCESS=false + +if [ "$SKIP_WIRING" != "true" ]; then + print_step "Step 5: Wiring LayerZero connections" + print_warning "Note: Peers are already set. Wiring configures send libraries, DVNs, executors, and enforced options." + print_warning "If wiring fails due to permissions, we'll set enforced options manually as a fallback." + + print_step "Wiring on development-xdc..." + set +e # Temporarily disable exit on error + yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-xdc + WIRE_XDC_STATUS=$? + set -e # Re-enable exit on error + if [ $WIRE_XDC_STATUS -eq 0 ]; then + print_success "LayerZero wired on development-xdc" + WIRE_XDC_SUCCESS=true + else + print_warning "Wiring failed on development-xdc (error code: $WIRE_XDC_STATUS)" + print_warning "This is likely a permission error (0xc4c52593). Will set enforced options manually." + fi + + print_step "Wiring on development-celo..." + set +e # Temporarily disable exit on error + yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-celo + WIRE_CELO_STATUS=$? + set -e # Re-enable exit on error + if [ $WIRE_CELO_STATUS -eq 0 ]; then + print_success "LayerZero wired on development-celo" + WIRE_CELO_SUCCESS=true + else + print_warning "Wiring failed on development-celo (error code: $WIRE_CELO_STATUS)" + print_warning "This is likely a permission error (0xc4c52593). Will set enforced options manually." + fi +else + print_warning "Skipping wiring step (SKIP_WIRING=true)" +fi + +# Step 6: Set bridge limits (optional) +if [ "$SKIP_LIMITS" != "true" ]; then + print_step "Step 6: Setting bridge limits on OFTAdapter" + + if [ -z "$DAILY_LIMIT" ] && [ -z "$TX_LIMIT" ] && [ -z "$ACCOUNT_DAILY_LIMIT" ] && [ -z "$MIN_AMOUNT" ] && [ -z "$ONLY_WHITELISTED" ]; then + print_warning "No limit environment variables set. Skipping limits configuration." + print_warning "To set limits, use decimal values (easier to read):" + print_warning " DAILY_LIMIT=1000000 TX_LIMIT=100000 \\" + print_warning " ACCOUNT_DAILY_LIMIT=50000 MIN_AMOUNT=10 \\" + print_warning " ONLY_WHITELISTED=false \\" + print_warning " ./scripts/oft/configure-oft-xdc-celo.sh" + print_warning "" + print_warning "Note: Values are in G$ (e.g., '1000000' = 1M G$). The script automatically converts to wei." + else + print_step "Setting bridge limits on development-xdc..." + DAILY_LIMIT=$DAILY_LIMIT \ + TX_LIMIT=$TX_LIMIT \ + ACCOUNT_DAILY_LIMIT=$ACCOUNT_DAILY_LIMIT \ + MIN_AMOUNT=$MIN_AMOUNT \ + ONLY_WHITELISTED=$ONLY_WHITELISTED \ + yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-xdc + print_success "Bridge limits set on development-xdc" + + print_step "Setting bridge limits on development-celo..." + DAILY_LIMIT=$DAILY_LIMIT \ + TX_LIMIT=$TX_LIMIT \ + ACCOUNT_DAILY_LIMIT=$ACCOUNT_DAILY_LIMIT \ + MIN_AMOUNT=$MIN_AMOUNT \ + ONLY_WHITELISTED=$ONLY_WHITELISTED \ + yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo + print_success "Bridge limits set on development-celo" + fi + echo "" +else + print_warning "Skipping limits configuration (SKIP_LIMITS=true)" + echo "" +fi + +# Step 3: Transfer ownership (required before setting peers) +print_step "Step 3: Transferring OFT adapter ownership to DAO Avatar" + +print_step "Transferring ownership on development-xdc..." +yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-xdc +print_success "Ownership transferred on development-xdc" + +print_step "Transferring ownership on development-celo..." +yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-celo +print_success "Ownership transferred on development-celo" + +echo "" + +# Step 7: Test bridge (optional, last step) +if [ "$SKIP_BRIDGE_TEST" != "true" ]; then + print_step "Step 7: Testing bridge functionality" + print_warning "This step will attempt to bridge 1 G$ from XDC to CELO" + read -p "Do you want to test the bridge? (y/N): " -n 1 -r + echo "" + if [[ $REPLY =~ ^[Yy]$ ]]; then + print_step "Bridging from development-xdc to development-celo..." + yarn hardhat run scripts/oft/bridge-oft-token.ts --network development-xdc || print_warning "Bridge test failed (this is okay if you don't have sufficient balance)" + echo "" + else + print_warning "Skipping bridge test" + fi + echo "" +else + print_warning "Skipping bridge test (SKIP_BRIDGE_TEST=true)" + echo "" +fi + +# Summary +print_step "Configuration Complete!" +print_success "OFT has been successfully configured on both XDC and CELO networks" +echo "" +echo "Summary of completed steps:" +echo " ✅ Deployed OFT contracts on both networks" +echo " ✅ Granted MINTER_ROLE to MinterBurner" +echo " ✅ Transferred OFT adapter ownership to DAO Avatar" +echo " ✅ Set LayerZero peer connections" +if [ "$SKIP_WIRING" != "true" ]; then + if [ "$WIRE_XDC_SUCCESS" = "true" ] && [ "$WIRE_CELO_SUCCESS" = "true" ]; then + echo " ✅ Wired LayerZero connections" + else + echo " ⚠️ LayerZero wiring failed (permission errors)" + echo " ✅ Set enforced options manually as fallback" + fi +fi +if [ "$SKIP_LIMITS" != "true" ] && ([ -n "$DAILY_LIMIT" ] || [ -n "$TX_LIMIT" ] || [ -n "$ACCOUNT_DAILY_LIMIT" ] || [ -n "$MIN_AMOUNT" ] || [ -n "$ONLY_WHITELISTED" ]); then + echo " ✅ Set bridge limits" +fi +if [ "$SKIP_BRIDGE_TEST" != "true" ]; then + echo " ✅ Tested bridge functionality (if executed)" +fi +echo "" +print_success "You can now use the bridge-oft-token.ts script to bridge tokens between chains!" +print_success "Run: yarn hardhat run scripts/oft/bridge-oft-token.ts --network " + diff --git a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts b/packages/bridge-contracts/scripts/oft/grant-minter-role.ts new file mode 100644 index 0000000..b4683a8 --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/grant-minter-role.ts @@ -0,0 +1,139 @@ +/*** + * Script to grant MINTER_ROLE to GoodDollarMinterBurner contract on development-celo + * Uses genericCall through Avatar/Controller to execute the transaction + * + * Usage: + * npx hardhat run scripts/multichain-deploy/oft/grant-minter-role.ts --network development-celo + * + * Note: This script must be run by a guardian or address with permissions to execute via Controller + */ + +import { network, ethers } from "hardhat"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment.json"; + +const main = async () => { + const networkName = network.name; + const [signer] = await ethers.getSigners(); + + console.log("=== Grant MINTER_ROLE to GoodDollarMinterBurner ==="); + console.log("Network:", networkName); + console.log("Signer:", signer.address); + + // Derive native token name from network + const nativeTokenName = networkName.includes("celo") ? "CELO" : networkName.includes("xdc") ? "XDC" : "native token"; + console.log("Signer balance:", ethers.utils.formatEther(await ethers.provider.getBalance(signer.address)), nativeTokenName); + + // Get deployment info from GoodProtocol and GoodBridge + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + if (!goodProtocolContracts) { + throw new Error(`No GoodProtocol contracts found for network: ${networkName}`); + } + + const currentRelease = release[networkName] || {}; + const tokenAddress = goodProtocolContracts.GoodDollar || goodProtocolContracts.SuperGoodDollar; + const minterBurnerAddress = currentRelease.GoodDollarMinterBurner; + const controllerAddress = goodProtocolContracts.Controller; + const avatarAddress = goodProtocolContracts.Avatar; + + if (!tokenAddress) { + throw new Error(`GoodDollar token not found in deployment.json for ${networkName}`); + } + + if (!minterBurnerAddress) { + throw new Error(`GoodDollarMinterBurner not found in deployment.json for ${networkName}`); + } + + if (!controllerAddress) { + throw new Error(`Controller not found in deployment.json for ${networkName}`); + } + + if (!avatarAddress) { + throw new Error(`Avatar not found in deployment.json for ${networkName}`); + } + + console.log("\nContract addresses:"); + console.log("GoodDollar token:", tokenAddress); + console.log("GoodDollarMinterBurner:", minterBurnerAddress); + console.log("Controller:", controllerAddress); + console.log("Avatar:", avatarAddress); + + // Get token contract to check current status + // Use the interface from goodprotocol package + const token = await ethers.getContractAt( + "@gooddollar/goodprotocol/contracts/token/superfluid/ISuperGoodDollar.sol:ISuperGoodDollar", + tokenAddress + ); + + // Check if MinterBurner already has minter role + const isMinter = await token.isMinter(minterBurnerAddress); + console.log("\nCurrent status:"); + console.log("MinterBurner has MINTER_ROLE:", isMinter); + + if (isMinter) { + console.log("\n✅ GoodDollarMinterBurner already has MINTER_ROLE. No action needed."); + return; + } + + // Prepare the generic call through Avatar + // Function signature: addMinter(address) + const functionSignature = "addMinter(address)"; + + // Encode the function input (minterBurnerAddress) + const abiCoder = ethers.utils.defaultAbiCoder; + const functionInputs = abiCoder.encode(["address"], [minterBurnerAddress]); + + console.log("\nPreparing generic call:"); + console.log("Function:", functionSignature); + console.log("Target contract:", tokenAddress); + console.log("Parameter (minterBurner):", minterBurnerAddress); + + // Execute via Controller/Avatar + try { + console.log("\nExecuting via Controller/Avatar..."); + const Controller = await ethers.getContractAt("Controller", controllerAddress); + + // Use genericCall to execute through Avatar + // Encode the function call: function selector + parameters + const functionSelector = ethers.utils.id(functionSignature).slice(0, 10); + const encodedCall = ethers.utils.hexConcat([functionSelector, functionInputs]); + + const tx = await Controller.genericCall( + tokenAddress, + encodedCall, + avatarAddress, + 0 + ); + await tx.wait(); + console.log("Transaction hash:", tx.hash); + + // Verify the role was granted + console.log("\nVerifying role was granted..."); + const isMinterAfter = await token.isMinter(minterBurnerAddress); + console.log("MinterBurner has MINTER_ROLE:", isMinterAfter); + + if (isMinterAfter) { + console.log("\n✅ Successfully granted MINTER_ROLE to GoodDollarMinterBurner via Avatar!"); + } else { + console.log("\n⚠️ Warning: MINTER_ROLE was not granted. Please check the transaction."); + } + + } catch (error: any) { + console.error("\n❌ Error granting MINTER_ROLE:"); + if (error.message) { + console.error("Error message:", error.message); + } + if (error.reason) { + console.error("Reason:", error.reason); + } + throw error; + } +}; + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + diff --git a/packages/bridge-contracts/scripts/oft/oft-deploy.ts b/packages/bridge-contracts/scripts/oft/oft-deploy.ts new file mode 100644 index 0000000..7ecf374 --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/oft-deploy.ts @@ -0,0 +1,257 @@ +/*** + * Deployment script for GoodDollar OFT (Omnichain Fungible Token) contracts + * + * Deploys: + * 1. GoodDollarMinterBurner - DAO-upgradeable contract that handles minting and burning of GoodDollar tokens for OFT + * 2. GoodDollarOFTAdapter - Upgradeable LayerZero OFT adapter that wraps GoodDollar token for cross-chain transfers + * + * Steps: + * 1. Deploy GoodDollarMinterBurner as upgradeable proxy with token address and NameService + * 2. Deploy GoodDollarOFTAdapter as upgradeable proxy with constructor(token, lzEndpoint), then initialize(token, minterBurner, lzEndpoint, owner, feeRecipient, nameService) + * 3. Set OFT adapter as operator on GoodDollarMinterBurner via DAO + */ + +import { network, ethers, upgrades } from "hardhat"; +import { Contract } from "ethers"; +import fse from "fs-extra"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment.json"; + +const { name: networkName } = network; + +export const deployOFTContracts = async () => { + const [root] = await ethers.getSigners(); + + console.log("got signers:", { + networkName, + root: root.address, + balance: await ethers.provider.getBalance(root.address).then(_ => _.toString()) + }); + + // Get contract addresses from GoodProtocol deployment + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + if (!goodProtocolContracts) { + throw new Error(`No GoodProtocol contracts found for network ${networkName}. Please check @gooddollar/goodprotocol/releases/deployment.json`); + } + + // Get token address from GoodProtocol + const tokenAddress = goodProtocolContracts.GoodDollar || goodProtocolContracts.SuperGoodDollar; + if (!tokenAddress) { + throw new Error(`Token address not found in GoodProtocol deployment for network ${networkName}. Please deploy SuperGoodDollar or GoodDollar first.`); + } + + // Get NameService for DAO integration from GoodProtocol + const nameServiceAddress = goodProtocolContracts.NameService; + if (!nameServiceAddress) { + throw new Error(`NameService address not found in GoodProtocol deployment for network ${networkName}. Please deploy NameService first.`); + } + + // Get Controller address directly from GoodProtocol contracts (or via NameService if needed) + let controllerAddress = goodProtocolContracts.Controller; + if (!controllerAddress) { + // Fallback: try to get Controller via NameService interface + // Use the interface path suggested by Hardhat error message + const INameService = await ethers.getContractAt("@gooddollar/goodprotocol/contracts/Interfaces.sol:INameService", nameServiceAddress); + controllerAddress = await INameService.getAddress("CONTROLLER"); + if (!controllerAddress || controllerAddress === ethers.constants.AddressZero) { + throw new Error(`Controller address not found in GoodProtocol deployment for network ${networkName}`); + } + } + + // Network-specific defaults (can be overridden via env var) + const lzEndpoints: { [key: string]: string } = { + "development-celo": "0x1a44076050125825900e736c501f859c50fE728c", + "production-celo": "0x1a44076050125825900e736c501f859c50fE728c", + "development-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", + "production-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", + }; + const lzEndpoint = lzEndpoints[networkName]; + if (!lzEndpoint) { + throw new Error(`LayerZero endpoint not found. Please set LAYERZERO_ENDPOINT environment variable or add default for network ${networkName}`); + } + + console.log("Deployment parameters:", { + tokenAddress, + lzEndpoint, + networkName + }); + + // Get current deployment state + const currentRelease = release[networkName] || {}; + + // Deploy GoodDollarMinterBurner (upgradeable) + let MinterBurner: Contract; + if (!currentRelease.GoodDollarMinterBurner) { + console.log("Deploying GoodDollarMinterBurner as upgradeable contract..."); + const MinterBurnerFactory = await ethers.getContractFactory("GoodDollarMinterBurner"); + MinterBurner = await upgrades.deployProxy( + MinterBurnerFactory, + [tokenAddress, nameServiceAddress], + { kind: "uups", initializer: "initialize" } + ); + await MinterBurner.deployed(); + console.log("GoodDollarMinterBurner deployed to:", MinterBurner.address); + + // Update release file + if (!release[networkName]) { + release[networkName] = {}; + } + release[networkName].GoodDollarMinterBurner = MinterBurner.address; + await fse.writeJSON("release/deployment.json", release, { spaces: 2 }); + } else { + console.log("GoodDollarMinterBurner already deployed at:", currentRelease.GoodDollarMinterBurner); + MinterBurner = await ethers.getContractAt("GoodDollarMinterBurner", currentRelease.GoodDollarMinterBurner); + } + + // Get Controller and Avatar addresses (used for OFT adapter owner and operator setup) + const Controller = await ethers.getContractAt("Controller", controllerAddress); + const avatarAddress = await Controller.avatar(); + + // Get fee recipient (can be Avatar or address(0) to disable fees) + // Default to Avatar, but can be overridden via environment variable + const feeRecipient = process.env.OFT_FEE_RECIPIENT || avatarAddress || ethers.constants.AddressZero; + + // Deploy GoodDollarOFTAdapter (upgradeable via proxy) + // Constructor takes (token, lzEndpoint) - initialize() is called automatically by proxy + let OFTAdapter: Contract; + if (!currentRelease.GoodDollarOFTAdapter) { + console.log("Deploying GoodDollarOFTAdapter as upgradeable proxy..."); + console.log("Constructor parameters: token, lzEndpoint"); + console.log("Initialize parameters: token, minterBurner, lzEndpoint, owner, feeRecipient, nameService"); + + const OFTAdapterFactory = await ethers.getContractFactory("GoodDollarOFTAdapter"); + + if (!avatarAddress || avatarAddress === ethers.constants.AddressZero) { + throw new Error(`Avatar address is invalid: ${avatarAddress}`); + } + console.log("✅ Verified Avatar address:", avatarAddress); + + // Encode the initialize function call + const initializeInterface = OFTAdapterFactory.interface; + const initializeData = initializeInterface.encodeFunctionData("initialize", [ + tokenAddress, + MinterBurner.address, + lzEndpoint, + avatarAddress, + feeRecipient, + nameServiceAddress + ]); + + console.log("Initialize parameters:", { + token: tokenAddress, + minterBurner: MinterBurner.address, + lzEndpoint, + owner: avatarAddress, + feeRecipient, + nameService: nameServiceAddress + }); + + // Create UUPS proxy manually using OpenZeppelin's ERC1967Proxy + // This follows the same pattern as upgrades.deployProxy but bypasses validation + console.log("Deploying proxy and initializing..."); + OFTAdapter = await upgrades.deployProxy( + OFTAdapterFactory, + [ + tokenAddress, + MinterBurner.address, + lzEndpoint, + root.address, + feeRecipient, + nameServiceAddress + ], + { + kind: "uups", + initializer: "initialize", + unsafeAllow: ["constructor", "state-variable-immutable", "missing-initializer-call", "duplicate-initializer-call"], + constructorArgs: [tokenAddress, lzEndpoint] + } + ); + await OFTAdapter.deployed(); + console.log("✅ GoodDollarOFTAdapter proxy deployed to:", OFTAdapter.address); + + // Update release file + release[networkName].GoodDollarOFTAdapter = OFTAdapter.address; + await fse.writeJSON("release/deployment.json", release, { spaces: 2 }); + + console.log("Fee recipient:", feeRecipient); + } else { + console.log("GoodDollarOFTAdapter already deployed at:", currentRelease.GoodDollarOFTAdapter); + OFTAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", currentRelease.GoodDollarOFTAdapter); + } + + // Set OFT adapter as operator on MinterBurner if not already set + // This must be done via DAO governance since MinterBurner is DAO-controlled + const isOperator = await MinterBurner.operators(OFTAdapter.address); + + if (!isOperator) { + console.log("Setting OFT adapter as operator on MinterBurner via DAO..."); + console.log(` MinterBurner address: ${MinterBurner.address}`); + console.log(` OFTAdapter address: ${OFTAdapter.address}`); + + // Encode the setOperator function call + const setOperatorEncoded = MinterBurner.interface.encodeFunctionData("setOperator", [ + OFTAdapter.address, + true + ]); + + // Execute via Controller/Avatar + try { + const tx = await Controller.genericCall( + MinterBurner.address, + setOperatorEncoded, + avatarAddress, + 0 + ); + await tx.wait(); + console.log("✅ Successfully set OFT adapter as operator on MinterBurner"); + console.log("Transaction hash:", tx.hash); + + // Verify it was set + const isOperatorAfter = await MinterBurner.operators(OFTAdapter.address); + if (isOperatorAfter) { + console.log("✅ Verified: OFT adapter is now an operator"); + } else { + console.log("⚠️ Warning: Operator status not set. Please check the transaction."); + } + } catch (error: any) { + console.error("❌ Error setting operator:"); + if (error.message) { + console.error("Error message:", error.message); + } + if (error.reason) { + console.error("Reason:", error.reason); + } + throw error; + } + } else { + console.log("OFT adapter is already an operator on MinterBurner"); + } + + console.log("\n=== Deployment Summary ==="); + console.log("Network:", networkName); + console.log("GoodDollarMinterBurner:", MinterBurner.address, "(upgradeable)"); + console.log("GoodDollarOFTAdapter:", OFTAdapter.address, "(upgradeable)"); + console.log("Token:", tokenAddress); + console.log("OFT Adapter Owner (Avatar):", avatarAddress); + console.log("LayerZero Endpoint:", lzEndpoint); + console.log("========================\n"); + + return { + MinterBurner: MinterBurner.address, + OFTAdapter: OFTAdapter.address + }; +}; + +export const main = async () => { + await deployOFTContracts(); +}; + +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); +} + diff --git a/packages/bridge-contracts/scripts/oft/set-enforced-options.ts b/packages/bridge-contracts/scripts/oft/set-enforced-options.ts new file mode 100644 index 0000000..4bd3ebf --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/set-enforced-options.ts @@ -0,0 +1,174 @@ +/** + * Script to manually set LayerZero enforced options + * + * This sets enforced options when wiring fails due to permission errors. + * Enforced options specify gas limits and other parameters for cross-chain messages. + * + * Usage: + * yarn hardhat run scripts/oft/set-enforced-options.ts --network development-xdc + * yarn hardhat run scripts/oft/set-enforced-options.ts --network development-celo + */ + +import { network, ethers } from "hardhat"; +import { EndpointId } from "@layerzerolabs/lz-definitions"; +import { Options } from "@layerzerolabs/lz-v2-utilities"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment.json"; + +const { name: networkName } = network; + +// Endpoint IDs +const XDC_EID = EndpointId.XDC_V2_MAINNET; // 30109 +const CELO_EID = EndpointId.CELO_V2_MAINNET; // 30125 + +async function main() { + const [signer] = await ethers.getSigners(); + console.log("=== Setting LayerZero Enforced Options ===\n"); + console.log("Network:", networkName); + console.log("Signer:", signer.address); + console.log(""); + + const isXDC = networkName.includes("xdc"); + const isCELO = networkName.includes("celo"); + + if (!isXDC && !isCELO) { + throw new Error(`Unsupported network: ${networkName}. Use development-xdc or development-celo`); + } + + const oftAdapterAddress = (release[networkName] as any)?.GoodDollarOFTAdapter; + if (!oftAdapterAddress) { + throw new Error(`OFT adapter not found for ${networkName}`); + } + + const destEid = isXDC ? CELO_EID : XDC_EID; + const destNetwork = isXDC ? "CELO" : "XDC"; + + console.log("OFT Adapter:", oftAdapterAddress); + console.log("Destination:", destNetwork, `(eid ${destEid})`); + console.log(""); + + // Get the OFT adapter contract + const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); + + // Check current owner + let currentOwner: string; + try { + currentOwner = await oftAdapter.owner(); + console.log(`Current owner: ${currentOwner}`); + } catch (error: any) { + throw new Error(`Could not read owner: ${error.message}`); + } + + // Get GoodProtocol contracts for DAO access + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + if (!goodProtocolContracts) { + throw new Error(`No GoodProtocol contracts found for network: ${networkName}`); + } + + const controllerAddress = goodProtocolContracts.Controller; + const avatarAddress = goodProtocolContracts.Avatar; + + // Build enforced options + // From layerzero.config.ts: gas: 200000, value: 0 + // msgType: 1 = SEND + const gasLimit = 200000; + const nativeDrop = 0; // Native token value to send (0 for now) + + console.log("Building enforced options..."); + console.log(` Gas limit: ${gasLimit}`); + console.log(` Native drop: ${nativeDrop}`); + console.log(` Message type: 1 (SEND)`); + + // Build options using Options utility + const options = Options.newOptions().addExecutorLzReceiveOption(gasLimit, nativeDrop).toHex(); + console.log(` Options bytes: ${options}`); + console.log(""); + + // Prepare enforced options parameter + // EnforcedOptionParam: { eid, msgType, options } + const enforcedOptions = [ + { + eid: destEid, + msgType: 1, // SEND message type + options: options, + }, + ]; + + // Check if owner is Avatar (DAO) - need to call through Controller + const isAvatarOwner = currentOwner.toLowerCase() === avatarAddress.toLowerCase(); + const isSignerOwner = currentOwner.toLowerCase() === signer.address.toLowerCase(); + + try { + let tx: any; + + if (isAvatarOwner && controllerAddress) { + // Owner is DAO Avatar - call through Controller + console.log("Owner is DAO Avatar. Calling setEnforcedOptions through Controller..."); + const Controller = await ethers.getContractAt("Controller", controllerAddress); + + // Encode setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) + const functionSignature = "setEnforcedOptions((uint32,uint16,bytes)[])"; + const functionSelector = ethers.utils.id(functionSignature).slice(0, 10); + const encodedParams = ethers.utils.defaultAbiCoder.encode( + ["tuple(uint32 eid, uint16 msgType, bytes options)[]"], + [enforcedOptions] + ); + const encodedCall = ethers.utils.hexConcat([functionSelector, encodedParams]); + + tx = await Controller.genericCall( + oftAdapterAddress, + encodedCall, + avatarAddress, + 0 + ); + } else if (isSignerOwner) { + // Owner is signer - call directly + console.log("Calling setEnforcedOptions directly..."); + tx = await oftAdapter.setEnforcedOptions(enforcedOptions); + } else { + throw new Error( + `Cannot set enforced options: Current owner (${currentOwner}) is not the signer (${signer.address}) and not the Avatar (${avatarAddress}). ` + + `Please run this script from the owner's account or ensure ownership has been transferred to Avatar.` + ); + } + + console.log(`\nTransaction sent: ${tx.hash}`); + console.log("Waiting for confirmation..."); + + const receipt = await tx.wait(); + console.log(`✅ Enforced options set successfully!`); + console.log(`Block number: ${receipt.blockNumber}`); + console.log(`Gas used: ${receipt.gasUsed.toString()}`); + + // Verify enforced options were set + const storedOptions = await oftAdapter.enforcedOptions(destEid, 1); + console.log(`\nVerified enforced options: ${storedOptions}`); + + if (storedOptions === "0x") { + console.log("⚠️ Warning: Enforced options appear to be empty after setting"); + } else { + console.log("✅ Enforced options verified successfully!"); + } + + } catch (error: any) { + console.error("\n❌ Error setting enforced options:"); + if (error.reason) { + console.error(`Reason: ${error.reason}`); + } + if (error.data) { + console.error(`Data: ${error.data}`); + } + if (error.message) { + console.error(`Message: ${error.message}`); + } + throw error; + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + diff --git a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts b/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts new file mode 100644 index 0000000..30261f1 --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts @@ -0,0 +1,193 @@ +/*** + * Script to set bridge limits for GoodDollarOFTAdapter contract + * Uses genericCall through Avatar/Controller to execute the transaction + * + * Note: Limits are now managed in GoodDollarOFTAdapter, not GoodDollarMinterBurner + * + * Usage: + * DAILY_LIMIT=1000000 TX_LIMIT=100000 ACCOUNT_DAILY_LIMIT=50000 MIN_AMOUNT=10 \ + * npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo + * + * Note: This script must be run by a guardian or address with permissions to execute via Controller + */ + +import { network, ethers } from "hardhat"; +import { BigNumber } from "ethers"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment.json"; + +const main = async () => { + const networkName = network.name; + const [signer] = await ethers.getSigners(); + + console.log("=== Set Bridge Limits for GoodDollarOFTAdapter ==="); + console.log("Network:", networkName); + console.log("Signer:", signer.address); + + // Derive native token name from network + const nativeTokenName = networkName.includes("celo") ? "CELO" : networkName.includes("xdc") ? "XDC" : "native token"; + console.log("Signer balance:", ethers.utils.formatEther(await ethers.provider.getBalance(signer.address)), nativeTokenName); + + // Get deployment info + const currentRelease = release[networkName] || {}; + if (!currentRelease.GoodDollarOFTAdapter) { + throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + } + + // Get GoodProtocol contracts for Controller and Avatar + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + if (!goodProtocolContracts) { + throw new Error(`No GoodProtocol contracts found for network: ${networkName}`); + } + + const oftAdapterAddress = currentRelease.GoodDollarOFTAdapter; + const controllerAddress = goodProtocolContracts.Controller; + const avatarAddress = goodProtocolContracts.Avatar; + + if (!oftAdapterAddress) { + throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + } + + if (!controllerAddress) { + throw new Error(`Controller not found in deployment.json for ${networkName}`); + } + + if (!avatarAddress) { + throw new Error(`Avatar not found in deployment.json for ${networkName}`); + } + + console.log("\nContract addresses:"); + console.log("GoodDollarOFTAdapter:", oftAdapterAddress); + console.log("Controller:", controllerAddress); + console.log("Avatar:", avatarAddress); + + // Get current limits from OFTAdapter contract + const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); + const currentLimits = await oftAdapter.bridgeLimits(); + + console.log("\nCurrent bridge limits:"); + console.log("Daily Limit:", ethers.utils.formatEther(currentLimits.dailyLimit), "G$"); + console.log("Transaction Limit:", ethers.utils.formatEther(currentLimits.txLimit), "G$"); + console.log("Account Daily Limit:", ethers.utils.formatEther(currentLimits.accountDailyLimit), "G$"); + console.log("Min Amount:", ethers.utils.formatEther(currentLimits.minAmount), "G$"); + console.log("Only Whitelisted:", currentLimits.onlyWhitelisted); + + // Parse environment variables (values can be in decimal format, e.g., "1000000" for 1M G$) + // The script will automatically convert them to wei (18 decimals) + const parseLimit = (value: string | undefined): BigNumber | null => { + if (!value) return null; + // Check if value contains a decimal point or is a simple number + // If it's a simple number string, treat it as G$ and convert to wei + // If it's already in wei format (very large number), use it as-is + const numValue = value.trim(); + if (numValue.includes('.') || numValue.length < 15) { + // Treat as decimal G$ value and convert to wei + return ethers.utils.parseEther(numValue); + } else { + // Assume it's already in wei format + return ethers.BigNumber.from(numValue); + } + }; + + const dailyLimit = parseLimit(process.env.DAILY_LIMIT); + const txLimit = parseLimit(process.env.TX_LIMIT); + const accountDailyLimit = parseLimit(process.env.ACCOUNT_DAILY_LIMIT); + const minAmount = parseLimit(process.env.MIN_AMOUNT); + const onlyWhitelisted = process.env.ONLY_WHITELISTED !== undefined ? process.env.ONLY_WHITELISTED === "true" : null; + + // Check if any limits are being set + if (dailyLimit === null && txLimit === null && accountDailyLimit === null && minAmount === null && onlyWhitelisted === null) { + console.log("\n⚠️ No limits specified. Please provide at least one limit to set."); + console.log("\nUsage examples:"); + console.log(" # Using decimal values (recommended - easier to read):"); + console.log(" DAILY_LIMIT=1000000 TX_LIMIT=100000 \\"); + console.log(" ACCOUNT_DAILY_LIMIT=50000 MIN_AMOUNT=10 \\"); + console.log(" ONLY_WHITELISTED=false \\"); + console.log(" npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo"); + console.log("\n # Or using wei values (if you prefer):"); + console.log(" DAILY_LIMIT=1000000000000000000000000 TX_LIMIT=100000000000000000000000 \\"); + console.log(" ACCOUNT_DAILY_LIMIT=50000000000000000000000 MIN_AMOUNT=10000000000000000000 \\"); + console.log(" ONLY_WHITELISTED=false \\"); + console.log(" npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo"); + console.log("\nNote: Decimal values (e.g., '1000000' for 1M G$) are automatically converted to wei."); + return; + } + + // Prepare new limits struct (use current values if not provided) + const newLimits = { + dailyLimit: dailyLimit !== null ? dailyLimit : currentLimits.dailyLimit, + txLimit: txLimit !== null ? txLimit : currentLimits.txLimit, + accountDailyLimit: accountDailyLimit !== null ? accountDailyLimit : currentLimits.accountDailyLimit, + minAmount: minAmount !== null ? minAmount : currentLimits.minAmount, + onlyWhitelisted: onlyWhitelisted !== null ? onlyWhitelisted : currentLimits.onlyWhitelisted + }; + + // Check if limits are changing + const limitsChanged = + !newLimits.dailyLimit.eq(currentLimits.dailyLimit) || + !newLimits.txLimit.eq(currentLimits.txLimit) || + !newLimits.accountDailyLimit.eq(currentLimits.accountDailyLimit) || + !newLimits.minAmount.eq(currentLimits.minAmount) || + newLimits.onlyWhitelisted !== currentLimits.onlyWhitelisted; + + if (!limitsChanged) { + console.log("\n✅ All limits are already set to the requested values. No transactions needed."); + return; + } + + console.log("\n📝 New bridge limits to set:"); + console.log("Daily Limit:", ethers.utils.formatEther(newLimits.dailyLimit), "G$"); + console.log("Transaction Limit:", ethers.utils.formatEther(newLimits.txLimit), "G$"); + console.log("Account Daily Limit:", ethers.utils.formatEther(newLimits.accountDailyLimit), "G$"); + console.log("Min Amount:", ethers.utils.formatEther(newLimits.minAmount), "G$"); + console.log("Only Whitelisted:", newLimits.onlyWhitelisted); + + // Prepare transaction + const abiCoder = ethers.utils.defaultAbiCoder; + const setBridgeLimitsEncoded = oftAdapter.interface.encodeFunctionData("setBridgeLimits", [newLimits]); + + // Execute via Controller/Avatar + try { + console.log("\nExecuting via Controller/Avatar..."); + const Controller = await ethers.getContractAt("Controller", controllerAddress); + const tx = await Controller.genericCall( + oftAdapterAddress, + setBridgeLimitsEncoded, + avatarAddress, + 0 + ); + await tx.wait(); + console.log("✅ Transaction confirmed:", tx.hash); + + // Verify the limits were set + console.log("\nVerifying limits were set..."); + const updatedLimits = await oftAdapter.bridgeLimits(); + + console.log("\nUpdated bridge limits:"); + console.log("Daily Limit:", ethers.utils.formatEther(updatedLimits.dailyLimit), "G$"); + console.log("Transaction Limit:", ethers.utils.formatEther(updatedLimits.txLimit), "G$"); + console.log("Account Daily Limit:", ethers.utils.formatEther(updatedLimits.accountDailyLimit), "G$"); + console.log("Min Amount:", ethers.utils.formatEther(updatedLimits.minAmount), "G$"); + console.log("Only Whitelisted:", updatedLimits.onlyWhitelisted); + + console.log("\n✅ Successfully set bridge limits via Avatar!"); + + } catch (error: any) { + console.error("\n❌ Error setting limits:"); + if (error.message) { + console.error("Error message:", error.message); + } + if (error.reason) { + console.error("Reason:", error.reason); + } + throw error; + } +}; + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + diff --git a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts b/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts new file mode 100644 index 0000000..5c14c55 --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts @@ -0,0 +1,168 @@ +/*** + * Script to transfer ownership of GoodDollarOFTAdapter from DAO Avatar to current signer + * + * Usage: + * yarn hardhat run scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts --network development-celo + * yarn hardhat run scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts --network development-xdc + * + * Note: This script must be run by an account that can execute DAO proposals. + * If the Avatar owns the contract, ownership transfer must go through the Controller. + */ + +import { network, ethers } from "hardhat"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment.json"; + +const main = async () => { + const networkName = network.name; + const [signer] = await ethers.getSigners(); + + console.log("=== Transfer GoodDollarOFTAdapter Ownership from Avatar to Signer ==="); + console.log("Network:", networkName); + console.log("Signer:", signer.address); + console.log("Signer balance:", ethers.utils.formatEther(await ethers.provider.getBalance(signer.address)), "ETH/CELO/XDC"); + console.log(""); + + // Get deployment info + const currentRelease = release[networkName] || {}; + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + + if (!goodProtocolContracts) { + throw new Error(`No GoodProtocol contracts found for network: ${networkName}`); + } + + const oftAdapterAddress = currentRelease.GoodDollarOFTAdapter; + const avatarAddress = goodProtocolContracts.Avatar; + const controllerAddress = goodProtocolContracts.Controller; + + if (!oftAdapterAddress) { + throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + } + + if (!avatarAddress) { + throw new Error(`Avatar not found in deployment.json for ${networkName}`); + } + + if (!controllerAddress) { + throw new Error(`Controller not found in deployment.json for ${networkName}`); + } + + console.log("Contract addresses:"); + console.log("GoodDollarOFTAdapter:", oftAdapterAddress); + console.log("Avatar:", avatarAddress); + console.log("Controller:", controllerAddress); + console.log(""); + + // Get OFT adapter contract + const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); + + // Get current owner + let currentOwner: string; + try { + currentOwner = await oftAdapter.owner(); + console.log("Current owner:", currentOwner); + } catch (e: any) { + throw new Error(`Could not read owner: ${e.message}`); + } + + // Check if already owned by signer + if (currentOwner.toLowerCase() === signer.address.toLowerCase()) { + console.log("\n✅ GoodDollarOFTAdapter is already owned by signer. No action needed."); + return; + } + + // Check if owned by Avatar + const isAvatarOwner = currentOwner.toLowerCase() === avatarAddress.toLowerCase(); + + if (!isAvatarOwner) { + console.log("\n❌ Error: Current owner is not the Avatar."); + console.log(`Current owner: ${currentOwner}`); + console.log(`Expected owner (Avatar): ${avatarAddress}`); + console.log("\nThis script can only transfer ownership FROM the Avatar."); + console.log("If you need to transfer from a different owner, use transferOwnership directly."); + throw new Error("Current owner is not the Avatar"); + } + + console.log("✅ Current owner is Avatar. Proceeding with ownership transfer..."); + console.log("Target owner (signer):", signer.address); + console.log(""); + + // Transfer ownership from Avatar to signer + // Since Avatar is the owner, we need to call through Controller + try { + console.log("Transferring ownership from Avatar to signer through Controller..."); + + const Controller = await ethers.getContractAt("Controller", controllerAddress); + + // Encode transferOwnership(address newOwner) + const functionSignature = "transferOwnership(address)"; + const functionSelector = ethers.utils.id(functionSignature).slice(0, 10); + const encodedParams = ethers.utils.defaultAbiCoder.encode( + ["address"], + [signer.address] + ); + const encodedCall = ethers.utils.hexConcat([functionSelector, encodedParams]); + + console.log("Encoded function call:", encodedCall); + console.log("Calling Controller.genericCall..."); + + const tx = await Controller.genericCall( + oftAdapterAddress, + encodedCall, + avatarAddress, + 0 + ); + + console.log("Transaction hash:", tx.hash); + console.log("Waiting for confirmation..."); + + const receipt = await tx.wait(); + console.log("✅ Transaction confirmed!"); + console.log("Block number:", receipt.blockNumber); + console.log("Gas used:", receipt.gasUsed.toString()); + + // Verify ownership was transferred + console.log("\nVerifying ownership transfer..."); + const newOwner = await oftAdapter.owner(); + console.log("New owner:", newOwner); + + if (newOwner.toLowerCase() === signer.address.toLowerCase()) { + console.log("\n✅ Successfully transferred ownership from Avatar to signer!"); + console.log(`Contract is now owned by: ${signer.address}`); + } else { + console.log("\n⚠️ Warning: Ownership was not transferred correctly."); + console.log("Expected:", signer.address); + console.log("Got:", newOwner); + throw new Error("Ownership transfer verification failed"); + } + + } catch (error: any) { + console.error("\n❌ Error transferring ownership:"); + if (error.message) { + console.error("Error message:", error.message); + } + if (error.reason) { + console.error("Reason:", error.reason); + } + if (error.data) { + console.error("Error data:", error.data); + } + + // Provide helpful error messages + if (error.message?.includes("genericCall") || error.message?.includes("Controller")) { + console.error("\n💡 Tip: Make sure you have permission to execute DAO proposals."); + console.error(" The Controller.genericCall requires proper DAO permissions."); + } + + throw error; + } +}; + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + + diff --git a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts b/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts new file mode 100644 index 0000000..44af75b --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts @@ -0,0 +1,252 @@ +/*** + * Script to transfer ownership of GoodDollarOFTAdapter to DAO Avatar + * + * Usage: + * npx hardhat run scripts/multichain-deploy/oft/transfer-oft-adapter-ownership.ts --network development-celo + * + * Note: This script must be run by the current owner of the OFT adapter. + * If the current owner is not the signer, you'll need to run this script from the owner's account. + */ + +import { network, ethers } from "hardhat"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment.json"; + +const main = async () => { + const networkName = network.name; + const [signer] = await ethers.getSigners(); + + console.log("=== Transfer GoodDollarOFTAdapter Ownership to Avatar ==="); + console.log("Network:", networkName); + console.log("Signer:", signer.address); + console.log("Signer balance:", ethers.utils.formatEther(await ethers.provider.getBalance(signer.address)), "ETH/CELO"); + + // Get deployment info + const currentRelease = release[networkName] || {}; + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + + if (!goodProtocolContracts) { + throw new Error(`No GoodProtocol contracts found for network: ${networkName}`); + } + + const oftAdapterAddress = currentRelease.GoodDollarOFTAdapter; + const avatarAddress = goodProtocolContracts.Avatar; + + if (!oftAdapterAddress) { + throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + } + + if (!avatarAddress) { + throw new Error(`Avatar not found in deployment.json for ${networkName}`); + } + + console.log("\nContract addresses:"); + console.log("GoodDollarOFTAdapter:", oftAdapterAddress); + console.log("Avatar:", avatarAddress); + + // Get OFT adapter contract + const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); + + // Get current owner + let currentOwner: string; + try { + currentOwner = await oftAdapter.owner(); + } catch (e: any) { + // If owner() fails, contract might not be initialized + console.log("⚠️ Warning: Could not read owner. Contract may not be initialized."); + currentOwner = ethers.constants.AddressZero; + } + + console.log("\nCurrent owner:", currentOwner); + console.log("Target owner (Avatar):", avatarAddress); + + // Check if already owned by Avatar + if (currentOwner.toLowerCase() === avatarAddress.toLowerCase()) { + console.log("\n✅ GoodDollarOFTAdapter is already owned by Avatar. No action needed."); + return; + } + + // If owner is zero, try to initialize the contract first + if (currentOwner === ethers.constants.AddressZero) { + console.log("\n⚠️ Owner is zero address. Contract may not be initialized."); + console.log("Attempting to initialize the contract..."); + + // Get required addresses for initialization + const tokenAddress = goodProtocolContracts.GoodDollar || goodProtocolContracts.SuperGoodDollar; + const minterBurnerAddress = currentRelease.GoodDollarMinterBurner; + const nameServiceAddress = goodProtocolContracts.NameService; + const controllerAddress = goodProtocolContracts.Controller; + + if (!tokenAddress || !minterBurnerAddress || !nameServiceAddress) { + throw new Error( + `Cannot initialize: Missing required addresses. ` + + `Token: ${tokenAddress}, MinterBurner: ${minterBurnerAddress}, NameService: ${nameServiceAddress}` + ); + } + + // Get LayerZero endpoint (from network config or environment) + const lzEndpoint = networkName.includes("xdc") + ? "0x9740FF91F1985D8d2B71494aE1A2f723bb3Ed9E4" // XDC endpoint + : "0x3A73033C0b1407574C76BdBAc67f126f6b4a9AA9"; // CELO endpoint + + const feeRecipient = avatarAddress; // Use Avatar as fee recipient + + try { + console.log("Initializing with parameters:"); + console.log(" Token:", tokenAddress); + console.log(" MinterBurner:", minterBurnerAddress); + console.log(" LZ Endpoint:", lzEndpoint); + console.log(" Owner (Avatar):", avatarAddress); + console.log(" Fee Recipient:", feeRecipient); + console.log(" NameService:", nameServiceAddress); + + const initTx = await oftAdapter.initialize( + tokenAddress, + minterBurnerAddress, + lzEndpoint, + avatarAddress, // Set Avatar as owner during initialization + feeRecipient, + nameServiceAddress + ); + await initTx.wait(); + console.log("✅ Contract initialized successfully!"); + console.log("Transaction hash:", initTx.hash); + + // Verify owner was set + const newOwner = await oftAdapter.owner(); + console.log("New owner after initialization:", newOwner); + + if (newOwner.toLowerCase() === avatarAddress.toLowerCase()) { + console.log("\n✅ Contract initialized and ownership set to Avatar!"); + return; + } else { + console.log("\n⚠️ Warning: Initialization completed but owner is not Avatar."); + console.log("Expected:", avatarAddress); + console.log("Got:", newOwner); + // Continue to try transfer ownership below + currentOwner = newOwner; + } + } catch (initError: any) { + const errorMsg = initError.message || initError.reason || ""; + if (errorMsg.includes("already initialized") || errorMsg.includes("Initializable: contract is already initialized")) { + console.log("⚠️ Contract is already initialized (detected via error)"); + // Re-read owner + try { + currentOwner = await oftAdapter.owner(); + console.log("Current owner after checking initialization:", currentOwner); + + // If owner is still zero after initialization, try to use reinitializer + if (currentOwner === ethers.constants.AddressZero) { + console.log("\n⚠️ Contract is initialized but owner is zero. Attempting to fix with reinitializer..."); + try { + // Try to call reinitializeOwner if it exists (for upgradeable contracts) + const reinitTx = await oftAdapter.reinitializeOwner(avatarAddress); + await reinitTx.wait(); + console.log("✅ Successfully reinitialized owner!"); + + // Verify owner was set + const newOwner = await oftAdapter.owner(); + if (newOwner.toLowerCase() === avatarAddress.toLowerCase()) { + console.log("✅ Owner fixed! Contract is now owned by Avatar."); + return; + } else { + throw new Error(`Owner reinitialization completed but owner is still incorrect: ${newOwner}`); + } + } catch (reinitError: any) { + const reinitErrorMsg = reinitError.message || reinitError.reason || ""; + if (reinitErrorMsg.includes("reinitializer") || reinitErrorMsg.includes("not a proxy")) { + // Contract is not deployed as a proxy, must redeploy + throw new Error( + "❌ CRITICAL: Contract is initialized but owner is zero address!\n" + + "The contract is upgradeable-compatible but was deployed directly (not as a proxy).\n" + + "Reinitialization is not possible for direct deployments.\n\n" + + "SOLUTION: You must redeploy the contract:\n" + + "1. Remove the GoodDollarOFTAdapter address from release/deployment.json\n" + + "2. Run the deployment script again to deploy a new contract\n" + + "3. The new contract will be initialized with the correct owner (Avatar)\n\n" + + `Current OFTAdapter address: ${oftAdapterAddress}\n` + + `This contract is unusable and must be replaced.` + ); + } else { + throw reinitError; + } + } + } + } catch (e: any) { + if (e.message && e.message.includes("CRITICAL")) { + throw e; // Re-throw the critical error + } + throw new Error("Contract appears initialized but owner() call failed: " + e.message); + } + } else { + console.error("❌ Initialization failed:", errorMsg); + throw initError; + } + } + } + + // Check if signer is the current owner (after potential initialization) + if (currentOwner !== ethers.constants.AddressZero && currentOwner.toLowerCase() !== signer.address.toLowerCase()) { + console.log("\n❌ Error: Current owner is not the signer."); + console.log(`Current owner: ${currentOwner}`); + console.log(`Signer: ${signer.address}`); + console.log("\nTo transfer ownership, you must run this script from the owner's account."); + console.log("Alternatively, the current owner can manually call:"); + console.log(` oftAdapter.transferOwnership("${avatarAddress}")`); + throw new Error("Signer is not the current owner"); + } + + // If we reach here and owner is still zero, we can't transfer + if (currentOwner === ethers.constants.AddressZero) { + throw new Error( + "Cannot transfer ownership: Contract owner is zero address and initialization failed.\n" + + "The contract may need to be redeployed. Please check the contract deployment and initialization." + ); + } + + console.log("\n✅ Signer is the current owner. Proceeding with ownership transfer..."); + + // Transfer ownership to Avatar + try { + console.log("\nTransferring ownership to Avatar..."); + const tx = await oftAdapter.transferOwnership(avatarAddress); + console.log("Transaction hash:", tx.hash); + console.log("Waiting for confirmation..."); + + const receipt = await tx.wait(); + console.log("✅ Transaction confirmed!"); + console.log("Block number:", receipt.blockNumber); + console.log("Gas used:", receipt.gasUsed.toString()); + + // Verify ownership was transferred + console.log("\nVerifying ownership transfer..."); + const newOwner = await oftAdapter.owner(); + console.log("New owner:", newOwner); + + if (newOwner.toLowerCase() === avatarAddress.toLowerCase()) { + console.log("\n✅ Successfully transferred ownership to Avatar!"); + } else { + console.log("\n⚠️ Warning: Ownership was not transferred correctly."); + console.log("Expected:", avatarAddress); + console.log("Got:", newOwner); + } + + } catch (error: any) { + console.error("\n❌ Error transferring ownership:"); + if (error.message) { + console.error("Error message:", error.message); + } + if (error.reason) { + console.error("Reason:", error.reason); + } + throw error; + } +}; + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); + diff --git a/packages/bridge-contracts/scripts/oft/verify-oft-contracts.sh b/packages/bridge-contracts/scripts/oft/verify-oft-contracts.sh new file mode 100755 index 0000000..5d9fe32 --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/verify-oft-contracts.sh @@ -0,0 +1,234 @@ +#!/bin/bash + +# Verification script for GoodDollar OFT contracts on Celo and XDC +# This script provides verification commands for both proxy and implementation contracts + +# Colors for output +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +RED='\033[0;31m' +NC='\033[0m' # No Color + +# Contract addresses from deployment.json +CELO_OFT_ADAPTER="0x28aE7CE2F3BBb0bf58B24F1961885aeE1e456f46" +CELO_MINTER_BURNER="0x3850786C7627Ce276EF6F715a0eF31d5FeCf1fc7" +XDC_OFT_ADAPTER="0x3a568CFD4e3F6ef9f5e341FAbAD84B6Cc41C1A75" +XDC_MINTER_BURNER="0xb20eaE658437b793257C326751a695ED5e94dD3E" + +# LayerZero endpoint addresses +CELO_LZ_ENDPOINT="0x1a44076050125825900e736c501f859c50fE728c" +XDC_LZ_ENDPOINT="0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa" + +echo -e "${BLUE}=== OFT Contracts Verification Script ===${NC}" +echo "" +echo "This script helps verify GoodDollarOFTAdapter and GoodDollarMinterBurner contracts" +echo "on Celo and XDC networks." +echo "" +echo -e "${YELLOW}Note:${NC} For upgradeable contracts (UUPS proxies), you need to verify the" +echo "implementation contract, not the proxy. The implementation address can be" +echo "retrieved using:" +echo " yarn hardhat run scripts/oft/get-implementation.ts --network " +echo "" + +echo "Choose a network:" +echo "1. development-celo" +echo "2. development-xdc" +echo "" +read -p "Enter choice (1 or 2): " choice + +case $choice in + 1) + NETWORK="development-celo" + OFT_ADAPTER=$CELO_OFT_ADAPTER + MINTER_BURNER=$CELO_MINTER_BURNER + LZ_ENDPOINT=$CELO_LZ_ENDPOINT + + echo "" + echo -e "${BLUE}=== Contract Addresses for $NETWORK ===${NC}" + echo "" + echo "Proxy addresses:" + echo " GoodDollarOFTAdapter: $OFT_ADAPTER" + echo " GoodDollarMinterBurner: $MINTER_BURNER" + echo "" + echo "LayerZero Endpoint: $LZ_ENDPOINT" + echo "" + + # Get token address + echo -e "${YELLOW}Enter GoodDollar token address for Celo:${NC}" + read -p "Token address: " TOKEN_ADDRESS + + if [ -z "$TOKEN_ADDRESS" ]; then + echo -e "${RED}Error: Token address is required for verification${NC}" + exit 1 + fi + + echo "" + echo -e "${GREEN}=== Step 1: Get Implementation Addresses ===${NC}" + echo "" + echo "Run these commands to get the implementation addresses:" + echo "" + echo -e "${BLUE}# Get GoodDollarOFTAdapter implementation${NC}" + echo "yarn hardhat run - <<'EOF' --network $NETWORK" + echo "const { upgrades } = require('hardhat');" + echo "upgrades.erc1967.getImplementationAddress('$OFT_ADAPTER').then(addr => console.log('OFT Adapter Impl:', addr));" + echo "EOF" + echo "" + echo -e "${BLUE}# Get GoodDollarMinterBurner implementation${NC}" + echo "yarn hardhat run - <<'EOF' --network $NETWORK" + echo "const { upgrades } = require('hardhat');" + echo "upgrades.erc1967.getImplementationAddress('$MINTER_BURNER').then(addr => console.log('MinterBurner Impl:', addr));" + echo "EOF" + echo "" + + echo -e "${YELLOW}Enter the implementation addresses (press Enter after each):${NC}" + read -p "GoodDollarOFTAdapter implementation: " OFT_IMPL + read -p "GoodDollarMinterBurner implementation: " MINTER_BURNER_IMPL + + if [ -z "$OFT_IMPL" ] || [ -z "$MINTER_BURNER_IMPL" ]; then + echo -e "${RED}Error: Implementation addresses are required${NC}" + exit 1 + fi + + echo "" + echo -e "${GREEN}=== Step 2: Verification Commands ===${NC}" + echo "" + echo -e "${BLUE}# Verify GoodDollarOFTAdapter implementation${NC}" + echo "yarn hardhat verify \\" + echo " --network $NETWORK \\" + echo " --contract contracts/oft/GoodDollarOFTAdapter.sol:GoodDollarOFTAdapter \\" + echo " $OFT_IMPL \\" + echo " $TOKEN_ADDRESS \\" + echo " $LZ_ENDPOINT" + echo "" + + echo -e "${BLUE}# Verify GoodDollarMinterBurner implementation${NC}" + echo "yarn hardhat verify \\" + echo " --network $NETWORK \\" + echo " --contract contracts/oft/GoodDollarMinterBurner.sol:GoodDollarMinterBurner \\" + echo " $MINTER_BURNER_IMPL" + echo "" + + echo -e "${YELLOW}Do you want to run these verification commands now? (y/n)${NC}" + read -p "Run verifications: " run_verify + + if [ "$run_verify" = "y" ] || [ "$run_verify" = "Y" ]; then + echo "" + echo -e "${GREEN}Verifying GoodDollarOFTAdapter...${NC}" + yarn hardhat verify \ + --network $NETWORK \ + --contract contracts/oft/GoodDollarOFTAdapter.sol:GoodDollarOFTAdapter \ + $OFT_IMPL \ + $TOKEN_ADDRESS \ + $LZ_ENDPOINT + + echo "" + echo -e "${GREEN}Verifying GoodDollarMinterBurner...${NC}" + yarn hardhat verify \ + --network $NETWORK \ + --contract contracts/oft/GoodDollarMinterBurner.sol:GoodDollarMinterBurner \ + $MINTER_BURNER_IMPL + fi + ;; + + 2) + NETWORK="development-xdc" + OFT_ADAPTER=$XDC_OFT_ADAPTER + MINTER_BURNER=$XDC_MINTER_BURNER + LZ_ENDPOINT=$XDC_LZ_ENDPOINT + + echo "" + echo -e "${BLUE}=== Contract Addresses for $NETWORK ===${NC}" + echo "" + echo "Proxy addresses:" + echo " GoodDollarOFTAdapter: $OFT_ADAPTER" + echo " GoodDollarMinterBurner: $MINTER_BURNER" + echo "" + echo "LayerZero Endpoint: $LZ_ENDPOINT" + echo "" + + # Get token address + echo -e "${YELLOW}Enter GoodDollar token address for XDC:${NC}" + read -p "Token address: " TOKEN_ADDRESS + + if [ -z "$TOKEN_ADDRESS" ]; then + echo -e "${RED}Error: Token address is required for verification${NC}" + exit 1 + fi + + echo "" + echo -e "${GREEN}=== Step 1: Get Implementation Addresses ===${NC}" + echo "" + echo "Run these commands to get the implementation addresses:" + echo "" + echo -e "${BLUE}# Get GoodDollarOFTAdapter implementation${NC}" + echo "yarn hardhat run - <<'EOF' --network $NETWORK" + echo "const { upgrades } = require('hardhat');" + echo "upgrades.erc1967.getImplementationAddress('$OFT_ADAPTER').then(addr => console.log('OFT Adapter Impl:', addr));" + echo "EOF" + echo "" + echo -e "${BLUE}# Get GoodDollarMinterBurner implementation${NC}" + echo "yarn hardhat run - <<'EOF' --network $NETWORK" + echo "const { upgrades } = require('hardhat');" + echo "upgrades.erc1967.getImplementationAddress('$MINTER_BURNER').then(addr => console.log('MinterBurner Impl:', addr));" + echo "EOF" + echo "" + + echo -e "${YELLOW}Enter the implementation addresses (press Enter after each):${NC}" + read -p "GoodDollarOFTAdapter implementation: " OFT_IMPL + read -p "GoodDollarMinterBurner implementation: " MINTER_BURNER_IMPL + + if [ -z "$OFT_IMPL" ] || [ -z "$MINTER_BURNER_IMPL" ]; then + echo -e "${RED}Error: Implementation addresses are required${NC}" + exit 1 + fi + + echo "" + echo -e "${GREEN}=== Step 2: Verification Commands ===${NC}" + echo "" + echo -e "${BLUE}# Verify GoodDollarOFTAdapter implementation${NC}" + echo "yarn hardhat verify \\" + echo " --network $NETWORK \\" + echo " --contract contracts/oft/GoodDollarOFTAdapter.sol:GoodDollarOFTAdapter \\" + echo " $OFT_IMPL \\" + echo " $TOKEN_ADDRESS \\" + echo " $LZ_ENDPOINT" + echo "" + + echo -e "${BLUE}# Verify GoodDollarMinterBurner implementation${NC}" + echo "yarn hardhat verify \\" + echo " --network $NETWORK \\" + echo " --contract contracts/oft/GoodDollarMinterBurner.sol:GoodDollarMinterBurner \\" + echo " $MINTER_BURNER_IMPL" + echo "" + + echo -e "${YELLOW}Do you want to run these verification commands now? (y/n)${NC}" + read -p "Run verifications: " run_verify + + if [ "$run_verify" = "y" ] || [ "$run_verify" = "Y" ]; then + echo "" + echo -e "${GREEN}Verifying GoodDollarOFTAdapter...${NC}" + yarn hardhat verify \ + --network $NETWORK \ + --contract contracts/oft/GoodDollarOFTAdapter.sol:GoodDollarOFTAdapter \ + $OFT_IMPL \ + $TOKEN_ADDRESS \ + $LZ_ENDPOINT + + echo "" + echo -e "${GREEN}Verifying GoodDollarMinterBurner...${NC}" + yarn hardhat verify \ + --network $NETWORK \ + --contract contracts/oft/GoodDollarMinterBurner.sol:GoodDollarMinterBurner \ + $MINTER_BURNER_IMPL + fi + ;; + + *) + echo -e "${RED}Invalid choice. Exiting.${NC}" + exit 1 + ;; +esac + +echo "" +echo -e "${GREEN}=== Verification Complete ===${NC}" diff --git a/packages/bridge-contracts/typechain-types/hardhat.d.ts b/packages/bridge-contracts/typechain-types/hardhat.d.ts index a2092f2..015446c 100644 --- a/packages/bridge-contracts/typechain-types/hardhat.d.ts +++ b/packages/bridge-contracts/typechain-types/hardhat.d.ts @@ -376,6 +376,10 @@ declare module "hardhat/types/runtime" { name: "IBeacon", signerOrOptions?: ethers.Signer | FactoryOptions ): Promise; + getContractFactory( + name: "ERC1967Proxy", + signerOrOptions?: ethers.Signer | FactoryOptions + ): Promise; getContractFactory( name: "ERC1967Upgrade", signerOrOptions?: ethers.Signer | FactoryOptions @@ -1020,6 +1024,11 @@ declare module "hardhat/types/runtime" { address: string, signer?: ethers.Signer ): Promise; + getContractAt( + name: "ERC1967Proxy", + address: string, + signer?: ethers.Signer + ): Promise; getContractAt( name: "ERC1967Upgrade", address: string, From 56be1547cd89d2bd9fd35ce697774586a829aad7 Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 29 Jan 2026 09:16:11 -0500 Subject: [PATCH 03/41] feat: add .env.sample for configuration, update Hardhat config for XDC network, enhance GoodDollarOFTAdapter with upgrade authorization event, and modify deployment scripts for improved functionality --- packages/bridge-contracts/.env.sample | 21 +++ .../contracts/oft/GoodDollarOFTAdapter.sol | 12 +- packages/bridge-contracts/hardhat.config.ts | 4 +- .../scripts/oft/bridge-oft-token.ts | 2 +- .../scripts/oft/check-layerzero-config.ts | 170 ----------------- .../scripts/oft/configure-oft-xdc-celo.sh | 18 +- .../scripts/oft/oft-deploy.ts | 19 +- .../scripts/oft/set-enforced-options.ts | 174 ------------------ .../scripts/oft/set-minter-burner-limits.ts | 10 +- 9 files changed, 46 insertions(+), 384 deletions(-) create mode 100644 packages/bridge-contracts/.env.sample delete mode 100644 packages/bridge-contracts/scripts/oft/check-layerzero-config.ts delete mode 100644 packages/bridge-contracts/scripts/oft/set-enforced-options.ts diff --git a/packages/bridge-contracts/.env.sample b/packages/bridge-contracts/.env.sample new file mode 100644 index 0000000..ba4ccb6 --- /dev/null +++ b/packages/bridge-contracts/.env.sample @@ -0,0 +1,21 @@ +ONLY_WHITELISTED= # Whether only whitelisted addresses can bridge +SKIP_BRIDGE_TEST= # Skip bridge test step +SKIP_LIMITS= # Skip setting limits step + +ACCOUNT_DAILY_LIMIT= # Account daily limit in G$ (18 decimals) +DAILY_LIMIT= # Daily bridge limit in G$ (18 decimals) +TX_LIMIT= # Transaction limit in G$ (18 decimals) +MIN_AMOUNT= + +PRIVATE_KEY= +PUBLIC_KEY= + +ETHERSCAN_KEY= +ALCHEMY_KEY= + +FORK_CHAIN_ID= +DST_EID= +DST_OFT_ADAPTER= +LZ_LABS_DVN_CELO= +LZ_LABS_DVN_XDC= +XDC_RPC_URL= diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index b3dec3a..3a9aac7 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -88,6 +88,9 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { /// @dev Event emitted when fees are collected event FeeCollected(address indexed recipient, uint256 amount); + /// @dev Event emitted when the contract is authorized to be upgraded + event AuthorizedUpgrade(address indexed newImplementation); + /// @dev Event emitted when bridge limits are updated event BridgeLimitsSet( uint256 dailyLimit, @@ -134,16 +137,13 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { ) public initializer { // Initialize OwnableUpgradeable first __Ownable_init(); - __OAppSender_init(_owner); // This calls __OAppCore_init internally - __OAppReceiver_init(_owner); // This also calls __OAppCore_init internally // Transfer ownership to the specified owner (since __Ownable_init sets it to msg.sender) __OFTCore_init(_owner); _transferOwnership(_owner); // Set state variables - setDAO(_nameService); - innerToken = ISuperGoodDollar(address(nativeToken())); + innerToken = IERC20(_nameService.getAddress("GOODDOLLAR")); minterBurner = _minterBurner; feeRecipient = _feeRecipient; } @@ -351,7 +351,6 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { return _amountLD; } -<<<<<<< HEAD /** * @dev Authorizes the upgrade of the contract to a new implementation @@ -362,8 +361,8 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { function _authorizeUpgrade(address newImplementation) internal override onlyOwner { // Authorization is handled by onlyOwner modifier // Additional checks can be added here if needed + emit AuthorizedUpgrade(newImplementation); } -======= /** * @dev This empty reserved space is put in place to allow future versions to add new @@ -371,5 +370,4 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { * See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps */ uint256[50] private __gap; ->>>>>>> feat/oft-adapter-step2 } \ No newline at end of file diff --git a/packages/bridge-contracts/hardhat.config.ts b/packages/bridge-contracts/hardhat.config.ts index 125f430..a1753ca 100644 --- a/packages/bridge-contracts/hardhat.config.ts +++ b/packages/bridge-contracts/hardhat.config.ts @@ -167,7 +167,7 @@ const config: HardhatUserConfig = { "production-xdc": { accounts: accounts as HttpNetworkAccountsConfig, chainId: 50, - url: 'https://rpc.xdc.network', + url: 'https://rpc.xinfin.network', verify: { etherscan: { apiUrl: 'https://api.etherscan.io/v2/api?chainid=50', @@ -178,7 +178,7 @@ const config: HardhatUserConfig = { "development-xdc": { accounts: accounts as HttpNetworkAccountsConfig, chainId: 50, - url: 'https://rpc.xdc.network', + url: 'https://rpc.xinfin.network', eid: EndpointId.XDC_V2_MAINNET, verify: { etherscan: { diff --git a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts b/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts index 02eed1a..9e24b3b 100644 --- a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts +++ b/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts @@ -285,7 +285,7 @@ const main = async () => { try { // Try to use combineOptions to build proper options // combineOptions(msgType, extraOptions) - msgType 1 = SEND - const combineOptionsResult = await oftAdapter.combineOptions(1, "0x"); + const combineOptionsResult = await oftAdapter.combineOptions(destEndpointId, 1, extraOptions); if (combineOptionsResult && combineOptionsResult !== "0x") { extraOptions = combineOptionsResult; console.log("Using combined options from OApp"); diff --git a/packages/bridge-contracts/scripts/oft/check-layerzero-config.ts b/packages/bridge-contracts/scripts/oft/check-layerzero-config.ts deleted file mode 100644 index 2fb0fed..0000000 --- a/packages/bridge-contracts/scripts/oft/check-layerzero-config.ts +++ /dev/null @@ -1,170 +0,0 @@ -/** - * Script to check LayerZero configuration status - * - * This script checks what's configured and what's missing for LayerZero OFT. - * - * Usage: - * yarn hardhat run scripts/oft/check-layerzero-config.ts --network development-xdc - * yarn hardhat run scripts/oft/check-layerzero-config.ts --network development-celo - */ - -import { network, ethers } from "hardhat"; -import { EndpointId } from "@layerzerolabs/lz-definitions"; -import release from "../../release/deployment.json"; - -const { name: networkName } = network; - -// Endpoint IDs -const XDC_EID = EndpointId.XDC_V2_MAINNET; // 30109 -const CELO_EID = EndpointId.CELO_V2_MAINNET; // 30125 - -// Endpoint addresses -const ENDPOINTS: { [key: string]: string } = { - "development-celo": "0x1a44076050125825900e736c501f859c50fE728c", - "production-celo": "0x1a44076050125825900e736c501f859c50fE728c", - "development-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", - "production-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", -}; - -async function main() { - const [signer] = await ethers.getSigners(); - console.log("=== LayerZero Configuration Check ===\n"); - console.log("Network:", networkName); - console.log("Signer:", signer.address); - console.log(""); - - const isXDC = networkName.includes("xdc"); - const isCELO = networkName.includes("celo"); - - if (!isXDC && !isCELO) { - throw new Error(`Unsupported network: ${networkName}`); - } - - const endpointAddress = ENDPOINTS[networkName]; - if (!endpointAddress) { - throw new Error(`Endpoint address not found for ${networkName}`); - } - - const oftAdapterAddress = (release[networkName] as any)?.GoodDollarOFTAdapter; - if (!oftAdapterAddress) { - throw new Error(`OFT adapter not found for ${networkName}`); - } - - const destEid = isXDC ? CELO_EID : XDC_EID; - const destNetwork = isXDC ? "CELO" : "XDC"; - - console.log("LayerZero Endpoint:", endpointAddress); - console.log("OFT Adapter:", oftAdapterAddress); - console.log("Destination:", destNetwork, `(eid ${destEid})`); - console.log(""); - - // Endpoint ABI - const endpointABI = [ - "function getSendLibrary(address _sender, uint32 _dstEid) external view returns (address lib)", - "function defaultSendLibrary(uint32 _dstEid) external view returns (address lib)", - "function getReceiveLibrary(address _receiver, uint32 _srcEid) external view returns (address lib)", - "function defaultReceiveLibrary(uint32 _srcEid) external view returns (address lib)", - "function getDefaultExecutor(uint32 _eid) external view returns (address executor)", - "function getDefaultDVN(uint32 _eid) external view returns (address dvn)", - "function getConfig(address _oapp, address _lib, uint32 _eid, uint32 _configType) external view returns (bytes memory)", - ]; - - const endpoint = new ethers.Contract(endpointAddress, endpointABI, signer); - - // Check send library - console.log("📤 Send Library Configuration:"); - try { - const sendLib = await endpoint.getSendLibrary(oftAdapterAddress, destEid); - if (sendLib !== ethers.constants.AddressZero) { - console.log(` ✅ Configured: ${sendLib}`); - } else { - const defaultSendLib = await endpoint.defaultSendLibrary(destEid); - if (defaultSendLib !== ethers.constants.AddressZero) { - console.log(` ⚠️ Using default: ${defaultSendLib}`); - } else { - console.log(` ❌ Not configured (no default available)`); - } - } - } catch (e: any) { - console.log(` ❌ Error checking: ${e.message}`); - } - - // Check receive library - console.log("\n📥 Receive Library Configuration:"); - try { - const sourceEid = isXDC ? XDC_EID : CELO_EID; - const receiveLib = await endpoint.getReceiveLibrary(oftAdapterAddress, sourceEid); - if (receiveLib !== ethers.constants.AddressZero) { - console.log(` ✅ Configured: ${receiveLib}`); - } else { - const defaultReceiveLib = await endpoint.defaultReceiveLibrary(sourceEid); - if (defaultReceiveLib !== ethers.constants.AddressZero) { - console.log(` ⚠️ Using default: ${defaultReceiveLib}`); - } else { - console.log(` ❌ Not configured (no default available)`); - } - } - } catch (e: any) { - console.log(` ❌ Error checking: ${e.message}`); - } - - // Check executor - console.log("\n⚙️ Executor Configuration:"); - try { - const executor = await endpoint.getDefaultExecutor(destEid); - if (executor !== ethers.constants.AddressZero) { - console.log(` ✅ Default executor for ${destNetwork}: ${executor}`); - } else { - console.log(` ❌ No default executor configured for ${destNetwork}`); - } - } catch (e: any) { - console.log(` ⚠️ Could not check executor: ${e.message}`); - } - - // Check DVN - console.log("\n🔐 DVN (Data Verification Network) Configuration:"); - try { - const dvn = await endpoint.getDefaultDVN(destEid); - if (dvn !== ethers.constants.AddressZero) { - console.log(` ✅ Default DVN for ${destNetwork}: ${dvn}`); - } else { - console.log(` ❌ No default DVN configured for ${destNetwork}`); - } - } catch (e: any) { - console.log(` ⚠️ Could not check DVN: ${e.message}`); - } - - // Check peer - console.log("\n🔗 Peer Configuration:"); - try { - const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); - const peer = await oftAdapter.peers(destEid); - const peerAddress = ethers.utils.getAddress(ethers.utils.hexDataSlice(peer, 12)); - if (peerAddress !== ethers.constants.AddressZero) { - console.log(` ✅ Peer set: ${peerAddress}`); - } else { - console.log(` ❌ Peer not set`); - } - } catch (e: any) { - console.log(` ❌ Error checking peer: ${e.message}`); - } - - console.log("\n=== Summary ==="); - console.log("If quoteSend is failing with error 0x6592671c, check:"); - console.log("1. Send library is configured (✅ or ⚠️)"); - console.log("2. Receive library is configured (✅ or ⚠️)"); - console.log("3. Executor is configured (✅)"); - console.log("4. DVN is configured (✅)"); - console.log("5. Peer is set (✅)"); - console.log("\nIf any are missing (❌), you need to run:"); - console.log(` yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network ${networkName}`); -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); - - diff --git a/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh b/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh index 4f30936..cfd912c 100755 --- a/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh +++ b/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh @@ -89,18 +89,18 @@ print_success "MINTER_ROLE granted on development-celo" echo "" -# Step 4: Set LayerZero peers (required before wiring, after ownership transfer) -print_step "Step 4: Setting LayerZero peer connections" +# # Step 4: Set LayerZero peers (required before wiring, after ownership transfer) +# print_step "Step 4: Setting LayerZero peer connections" -print_step "Setting peer on development-xdc..." -yarn hardhat run scripts/oft/set-layerzero-peers.ts --network development-xdc -print_success "Peer set on development-xdc" +# print_step "Setting peer on development-xdc..." +# yarn hardhat run scripts/oft/set-layerzero-peers.ts --network development-xdc +# print_success "Peer set on development-xdc" -print_step "Setting peer on development-celo..." -yarn hardhat run scripts/oft/set-layerzero-peers.ts --network development-celo -print_success "Peer set on development-celo" +# print_step "Setting peer on development-celo..." +# yarn hardhat run scripts/oft/set-layerzero-peers.ts --network development-celo +# print_success "Peer set on development-celo" -echo "" +# echo "" # Step 5: Wire LayerZero connections (optional - peers already set) WIRE_XDC_SUCCESS=false diff --git a/packages/bridge-contracts/scripts/oft/oft-deploy.ts b/packages/bridge-contracts/scripts/oft/oft-deploy.ts index 7ecf374..715717b 100644 --- a/packages/bridge-contracts/scripts/oft/oft-deploy.ts +++ b/packages/bridge-contracts/scripts/oft/oft-deploy.ts @@ -35,7 +35,7 @@ export const deployOFTContracts = async () => { } // Get token address from GoodProtocol - const tokenAddress = goodProtocolContracts.GoodDollar || goodProtocolContracts.SuperGoodDollar; + const tokenAddress = goodProtocolContracts.GoodDollar; if (!tokenAddress) { throw new Error(`Token address not found in GoodProtocol deployment for network ${networkName}. Please deploy SuperGoodDollar or GoodDollar first.`); } @@ -86,7 +86,7 @@ export const deployOFTContracts = async () => { const MinterBurnerFactory = await ethers.getContractFactory("GoodDollarMinterBurner"); MinterBurner = await upgrades.deployProxy( MinterBurnerFactory, - [tokenAddress, nameServiceAddress], + [nameServiceAddress], { kind: "uups", initializer: "initialize" } ); await MinterBurner.deployed(); @@ -109,7 +109,7 @@ export const deployOFTContracts = async () => { // Get fee recipient (can be Avatar or address(0) to disable fees) // Default to Avatar, but can be overridden via environment variable - const feeRecipient = process.env.OFT_FEE_RECIPIENT || avatarAddress || ethers.constants.AddressZero; + const feeRecipient = avatarAddress; // Deploy GoodDollarOFTAdapter (upgradeable via proxy) // Constructor takes (token, lzEndpoint) - initialize() is called automatically by proxy @@ -129,22 +129,14 @@ export const deployOFTContracts = async () => { // Encode the initialize function call const initializeInterface = OFTAdapterFactory.interface; const initializeData = initializeInterface.encodeFunctionData("initialize", [ - tokenAddress, MinterBurner.address, lzEndpoint, - avatarAddress, + root.address, feeRecipient, nameServiceAddress ]); - console.log("Initialize parameters:", { - token: tokenAddress, - minterBurner: MinterBurner.address, - lzEndpoint, - owner: avatarAddress, - feeRecipient, - nameService: nameServiceAddress - }); + console.log("Initialize parameters:", initializeData); // Create UUPS proxy manually using OpenZeppelin's ERC1967Proxy // This follows the same pattern as upgrades.deployProxy but bypasses validation @@ -152,7 +144,6 @@ export const deployOFTContracts = async () => { OFTAdapter = await upgrades.deployProxy( OFTAdapterFactory, [ - tokenAddress, MinterBurner.address, lzEndpoint, root.address, diff --git a/packages/bridge-contracts/scripts/oft/set-enforced-options.ts b/packages/bridge-contracts/scripts/oft/set-enforced-options.ts deleted file mode 100644 index 4bd3ebf..0000000 --- a/packages/bridge-contracts/scripts/oft/set-enforced-options.ts +++ /dev/null @@ -1,174 +0,0 @@ -/** - * Script to manually set LayerZero enforced options - * - * This sets enforced options when wiring fails due to permission errors. - * Enforced options specify gas limits and other parameters for cross-chain messages. - * - * Usage: - * yarn hardhat run scripts/oft/set-enforced-options.ts --network development-xdc - * yarn hardhat run scripts/oft/set-enforced-options.ts --network development-celo - */ - -import { network, ethers } from "hardhat"; -import { EndpointId } from "@layerzerolabs/lz-definitions"; -import { Options } from "@layerzerolabs/lz-v2-utilities"; -import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; -import release from "../../release/deployment.json"; - -const { name: networkName } = network; - -// Endpoint IDs -const XDC_EID = EndpointId.XDC_V2_MAINNET; // 30109 -const CELO_EID = EndpointId.CELO_V2_MAINNET; // 30125 - -async function main() { - const [signer] = await ethers.getSigners(); - console.log("=== Setting LayerZero Enforced Options ===\n"); - console.log("Network:", networkName); - console.log("Signer:", signer.address); - console.log(""); - - const isXDC = networkName.includes("xdc"); - const isCELO = networkName.includes("celo"); - - if (!isXDC && !isCELO) { - throw new Error(`Unsupported network: ${networkName}. Use development-xdc or development-celo`); - } - - const oftAdapterAddress = (release[networkName] as any)?.GoodDollarOFTAdapter; - if (!oftAdapterAddress) { - throw new Error(`OFT adapter not found for ${networkName}`); - } - - const destEid = isXDC ? CELO_EID : XDC_EID; - const destNetwork = isXDC ? "CELO" : "XDC"; - - console.log("OFT Adapter:", oftAdapterAddress); - console.log("Destination:", destNetwork, `(eid ${destEid})`); - console.log(""); - - // Get the OFT adapter contract - const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); - - // Check current owner - let currentOwner: string; - try { - currentOwner = await oftAdapter.owner(); - console.log(`Current owner: ${currentOwner}`); - } catch (error: any) { - throw new Error(`Could not read owner: ${error.message}`); - } - - // Get GoodProtocol contracts for DAO access - const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; - if (!goodProtocolContracts) { - throw new Error(`No GoodProtocol contracts found for network: ${networkName}`); - } - - const controllerAddress = goodProtocolContracts.Controller; - const avatarAddress = goodProtocolContracts.Avatar; - - // Build enforced options - // From layerzero.config.ts: gas: 200000, value: 0 - // msgType: 1 = SEND - const gasLimit = 200000; - const nativeDrop = 0; // Native token value to send (0 for now) - - console.log("Building enforced options..."); - console.log(` Gas limit: ${gasLimit}`); - console.log(` Native drop: ${nativeDrop}`); - console.log(` Message type: 1 (SEND)`); - - // Build options using Options utility - const options = Options.newOptions().addExecutorLzReceiveOption(gasLimit, nativeDrop).toHex(); - console.log(` Options bytes: ${options}`); - console.log(""); - - // Prepare enforced options parameter - // EnforcedOptionParam: { eid, msgType, options } - const enforcedOptions = [ - { - eid: destEid, - msgType: 1, // SEND message type - options: options, - }, - ]; - - // Check if owner is Avatar (DAO) - need to call through Controller - const isAvatarOwner = currentOwner.toLowerCase() === avatarAddress.toLowerCase(); - const isSignerOwner = currentOwner.toLowerCase() === signer.address.toLowerCase(); - - try { - let tx: any; - - if (isAvatarOwner && controllerAddress) { - // Owner is DAO Avatar - call through Controller - console.log("Owner is DAO Avatar. Calling setEnforcedOptions through Controller..."); - const Controller = await ethers.getContractAt("Controller", controllerAddress); - - // Encode setEnforcedOptions(EnforcedOptionParam[] calldata _enforcedOptions) - const functionSignature = "setEnforcedOptions((uint32,uint16,bytes)[])"; - const functionSelector = ethers.utils.id(functionSignature).slice(0, 10); - const encodedParams = ethers.utils.defaultAbiCoder.encode( - ["tuple(uint32 eid, uint16 msgType, bytes options)[]"], - [enforcedOptions] - ); - const encodedCall = ethers.utils.hexConcat([functionSelector, encodedParams]); - - tx = await Controller.genericCall( - oftAdapterAddress, - encodedCall, - avatarAddress, - 0 - ); - } else if (isSignerOwner) { - // Owner is signer - call directly - console.log("Calling setEnforcedOptions directly..."); - tx = await oftAdapter.setEnforcedOptions(enforcedOptions); - } else { - throw new Error( - `Cannot set enforced options: Current owner (${currentOwner}) is not the signer (${signer.address}) and not the Avatar (${avatarAddress}). ` + - `Please run this script from the owner's account or ensure ownership has been transferred to Avatar.` - ); - } - - console.log(`\nTransaction sent: ${tx.hash}`); - console.log("Waiting for confirmation..."); - - const receipt = await tx.wait(); - console.log(`✅ Enforced options set successfully!`); - console.log(`Block number: ${receipt.blockNumber}`); - console.log(`Gas used: ${receipt.gasUsed.toString()}`); - - // Verify enforced options were set - const storedOptions = await oftAdapter.enforcedOptions(destEid, 1); - console.log(`\nVerified enforced options: ${storedOptions}`); - - if (storedOptions === "0x") { - console.log("⚠️ Warning: Enforced options appear to be empty after setting"); - } else { - console.log("✅ Enforced options verified successfully!"); - } - - } catch (error: any) { - console.error("\n❌ Error setting enforced options:"); - if (error.reason) { - console.error(`Reason: ${error.reason}`); - } - if (error.data) { - console.error(`Data: ${error.data}`); - } - if (error.message) { - console.error(`Message: ${error.message}`); - } - throw error; - } -} - -main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); - diff --git a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts b/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts index 30261f1..f04a111 100644 --- a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts +++ b/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts @@ -144,18 +144,14 @@ const main = async () => { // Prepare transaction const abiCoder = ethers.utils.defaultAbiCoder; - const setBridgeLimitsEncoded = oftAdapter.interface.encodeFunctionData("setBridgeLimits", [newLimits]); + // const setBridgeLimitsEncoded = oftAdapter.interface.encodeFunctionData("setBridgeLimits", [newLimits]); + // Execute via Controller/Avatar try { console.log("\nExecuting via Controller/Avatar..."); const Controller = await ethers.getContractAt("Controller", controllerAddress); - const tx = await Controller.genericCall( - oftAdapterAddress, - setBridgeLimitsEncoded, - avatarAddress, - 0 - ); + const tx = await oftAdapter.setBridgeLimits(newLimits); await tx.wait(); console.log("✅ Transaction confirmed:", tx.hash); From b45c2ae85aec960e5629270e0998a143b8b4ab32 Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 29 Jan 2026 09:57:48 -0500 Subject: [PATCH 04/41] fix: clarify documentation in GoodDollarOFTAdapter by removing redundant fee deduction details --- .../bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index cbbaabf..5d23658 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -337,7 +337,7 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { * @param _amountLD The amount of tokens to credit in local decimals * @param _srcEid The source chain ID * @return amountReceivedLD The amount of tokens actually received in local decimals - * @dev Fees are deducted on the destination chain, matching MessagePassingBridge behavior + * @dev Fees are deducted on the destination chain */ function _credit( address _to, From 89fae4ad2f7d93e03ea292101019cc50c99e6b23 Mon Sep 17 00:00:00 2001 From: blueogin Date: Fri, 30 Jan 2026 07:53:49 -0500 Subject: [PATCH 05/41] chore: remove outdated verification script for GoodDollar OFT contracts --- .../scripts/oft/verify-oft-contracts.sh | 234 ------------------ 1 file changed, 234 deletions(-) delete mode 100755 packages/bridge-contracts/scripts/oft/verify-oft-contracts.sh diff --git a/packages/bridge-contracts/scripts/oft/verify-oft-contracts.sh b/packages/bridge-contracts/scripts/oft/verify-oft-contracts.sh deleted file mode 100755 index 5d9fe32..0000000 --- a/packages/bridge-contracts/scripts/oft/verify-oft-contracts.sh +++ /dev/null @@ -1,234 +0,0 @@ -#!/bin/bash - -# Verification script for GoodDollar OFT contracts on Celo and XDC -# This script provides verification commands for both proxy and implementation contracts - -# Colors for output -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -RED='\033[0;31m' -NC='\033[0m' # No Color - -# Contract addresses from deployment.json -CELO_OFT_ADAPTER="0x28aE7CE2F3BBb0bf58B24F1961885aeE1e456f46" -CELO_MINTER_BURNER="0x3850786C7627Ce276EF6F715a0eF31d5FeCf1fc7" -XDC_OFT_ADAPTER="0x3a568CFD4e3F6ef9f5e341FAbAD84B6Cc41C1A75" -XDC_MINTER_BURNER="0xb20eaE658437b793257C326751a695ED5e94dD3E" - -# LayerZero endpoint addresses -CELO_LZ_ENDPOINT="0x1a44076050125825900e736c501f859c50fE728c" -XDC_LZ_ENDPOINT="0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa" - -echo -e "${BLUE}=== OFT Contracts Verification Script ===${NC}" -echo "" -echo "This script helps verify GoodDollarOFTAdapter and GoodDollarMinterBurner contracts" -echo "on Celo and XDC networks." -echo "" -echo -e "${YELLOW}Note:${NC} For upgradeable contracts (UUPS proxies), you need to verify the" -echo "implementation contract, not the proxy. The implementation address can be" -echo "retrieved using:" -echo " yarn hardhat run scripts/oft/get-implementation.ts --network " -echo "" - -echo "Choose a network:" -echo "1. development-celo" -echo "2. development-xdc" -echo "" -read -p "Enter choice (1 or 2): " choice - -case $choice in - 1) - NETWORK="development-celo" - OFT_ADAPTER=$CELO_OFT_ADAPTER - MINTER_BURNER=$CELO_MINTER_BURNER - LZ_ENDPOINT=$CELO_LZ_ENDPOINT - - echo "" - echo -e "${BLUE}=== Contract Addresses for $NETWORK ===${NC}" - echo "" - echo "Proxy addresses:" - echo " GoodDollarOFTAdapter: $OFT_ADAPTER" - echo " GoodDollarMinterBurner: $MINTER_BURNER" - echo "" - echo "LayerZero Endpoint: $LZ_ENDPOINT" - echo "" - - # Get token address - echo -e "${YELLOW}Enter GoodDollar token address for Celo:${NC}" - read -p "Token address: " TOKEN_ADDRESS - - if [ -z "$TOKEN_ADDRESS" ]; then - echo -e "${RED}Error: Token address is required for verification${NC}" - exit 1 - fi - - echo "" - echo -e "${GREEN}=== Step 1: Get Implementation Addresses ===${NC}" - echo "" - echo "Run these commands to get the implementation addresses:" - echo "" - echo -e "${BLUE}# Get GoodDollarOFTAdapter implementation${NC}" - echo "yarn hardhat run - <<'EOF' --network $NETWORK" - echo "const { upgrades } = require('hardhat');" - echo "upgrades.erc1967.getImplementationAddress('$OFT_ADAPTER').then(addr => console.log('OFT Adapter Impl:', addr));" - echo "EOF" - echo "" - echo -e "${BLUE}# Get GoodDollarMinterBurner implementation${NC}" - echo "yarn hardhat run - <<'EOF' --network $NETWORK" - echo "const { upgrades } = require('hardhat');" - echo "upgrades.erc1967.getImplementationAddress('$MINTER_BURNER').then(addr => console.log('MinterBurner Impl:', addr));" - echo "EOF" - echo "" - - echo -e "${YELLOW}Enter the implementation addresses (press Enter after each):${NC}" - read -p "GoodDollarOFTAdapter implementation: " OFT_IMPL - read -p "GoodDollarMinterBurner implementation: " MINTER_BURNER_IMPL - - if [ -z "$OFT_IMPL" ] || [ -z "$MINTER_BURNER_IMPL" ]; then - echo -e "${RED}Error: Implementation addresses are required${NC}" - exit 1 - fi - - echo "" - echo -e "${GREEN}=== Step 2: Verification Commands ===${NC}" - echo "" - echo -e "${BLUE}# Verify GoodDollarOFTAdapter implementation${NC}" - echo "yarn hardhat verify \\" - echo " --network $NETWORK \\" - echo " --contract contracts/oft/GoodDollarOFTAdapter.sol:GoodDollarOFTAdapter \\" - echo " $OFT_IMPL \\" - echo " $TOKEN_ADDRESS \\" - echo " $LZ_ENDPOINT" - echo "" - - echo -e "${BLUE}# Verify GoodDollarMinterBurner implementation${NC}" - echo "yarn hardhat verify \\" - echo " --network $NETWORK \\" - echo " --contract contracts/oft/GoodDollarMinterBurner.sol:GoodDollarMinterBurner \\" - echo " $MINTER_BURNER_IMPL" - echo "" - - echo -e "${YELLOW}Do you want to run these verification commands now? (y/n)${NC}" - read -p "Run verifications: " run_verify - - if [ "$run_verify" = "y" ] || [ "$run_verify" = "Y" ]; then - echo "" - echo -e "${GREEN}Verifying GoodDollarOFTAdapter...${NC}" - yarn hardhat verify \ - --network $NETWORK \ - --contract contracts/oft/GoodDollarOFTAdapter.sol:GoodDollarOFTAdapter \ - $OFT_IMPL \ - $TOKEN_ADDRESS \ - $LZ_ENDPOINT - - echo "" - echo -e "${GREEN}Verifying GoodDollarMinterBurner...${NC}" - yarn hardhat verify \ - --network $NETWORK \ - --contract contracts/oft/GoodDollarMinterBurner.sol:GoodDollarMinterBurner \ - $MINTER_BURNER_IMPL - fi - ;; - - 2) - NETWORK="development-xdc" - OFT_ADAPTER=$XDC_OFT_ADAPTER - MINTER_BURNER=$XDC_MINTER_BURNER - LZ_ENDPOINT=$XDC_LZ_ENDPOINT - - echo "" - echo -e "${BLUE}=== Contract Addresses for $NETWORK ===${NC}" - echo "" - echo "Proxy addresses:" - echo " GoodDollarOFTAdapter: $OFT_ADAPTER" - echo " GoodDollarMinterBurner: $MINTER_BURNER" - echo "" - echo "LayerZero Endpoint: $LZ_ENDPOINT" - echo "" - - # Get token address - echo -e "${YELLOW}Enter GoodDollar token address for XDC:${NC}" - read -p "Token address: " TOKEN_ADDRESS - - if [ -z "$TOKEN_ADDRESS" ]; then - echo -e "${RED}Error: Token address is required for verification${NC}" - exit 1 - fi - - echo "" - echo -e "${GREEN}=== Step 1: Get Implementation Addresses ===${NC}" - echo "" - echo "Run these commands to get the implementation addresses:" - echo "" - echo -e "${BLUE}# Get GoodDollarOFTAdapter implementation${NC}" - echo "yarn hardhat run - <<'EOF' --network $NETWORK" - echo "const { upgrades } = require('hardhat');" - echo "upgrades.erc1967.getImplementationAddress('$OFT_ADAPTER').then(addr => console.log('OFT Adapter Impl:', addr));" - echo "EOF" - echo "" - echo -e "${BLUE}# Get GoodDollarMinterBurner implementation${NC}" - echo "yarn hardhat run - <<'EOF' --network $NETWORK" - echo "const { upgrades } = require('hardhat');" - echo "upgrades.erc1967.getImplementationAddress('$MINTER_BURNER').then(addr => console.log('MinterBurner Impl:', addr));" - echo "EOF" - echo "" - - echo -e "${YELLOW}Enter the implementation addresses (press Enter after each):${NC}" - read -p "GoodDollarOFTAdapter implementation: " OFT_IMPL - read -p "GoodDollarMinterBurner implementation: " MINTER_BURNER_IMPL - - if [ -z "$OFT_IMPL" ] || [ -z "$MINTER_BURNER_IMPL" ]; then - echo -e "${RED}Error: Implementation addresses are required${NC}" - exit 1 - fi - - echo "" - echo -e "${GREEN}=== Step 2: Verification Commands ===${NC}" - echo "" - echo -e "${BLUE}# Verify GoodDollarOFTAdapter implementation${NC}" - echo "yarn hardhat verify \\" - echo " --network $NETWORK \\" - echo " --contract contracts/oft/GoodDollarOFTAdapter.sol:GoodDollarOFTAdapter \\" - echo " $OFT_IMPL \\" - echo " $TOKEN_ADDRESS \\" - echo " $LZ_ENDPOINT" - echo "" - - echo -e "${BLUE}# Verify GoodDollarMinterBurner implementation${NC}" - echo "yarn hardhat verify \\" - echo " --network $NETWORK \\" - echo " --contract contracts/oft/GoodDollarMinterBurner.sol:GoodDollarMinterBurner \\" - echo " $MINTER_BURNER_IMPL" - echo "" - - echo -e "${YELLOW}Do you want to run these verification commands now? (y/n)${NC}" - read -p "Run verifications: " run_verify - - if [ "$run_verify" = "y" ] || [ "$run_verify" = "Y" ]; then - echo "" - echo -e "${GREEN}Verifying GoodDollarOFTAdapter...${NC}" - yarn hardhat verify \ - --network $NETWORK \ - --contract contracts/oft/GoodDollarOFTAdapter.sol:GoodDollarOFTAdapter \ - $OFT_IMPL \ - $TOKEN_ADDRESS \ - $LZ_ENDPOINT - - echo "" - echo -e "${GREEN}Verifying GoodDollarMinterBurner...${NC}" - yarn hardhat verify \ - --network $NETWORK \ - --contract contracts/oft/GoodDollarMinterBurner.sol:GoodDollarMinterBurner \ - $MINTER_BURNER_IMPL - fi - ;; - - *) - echo -e "${RED}Invalid choice. Exiting.${NC}" - exit 1 - ;; -esac - -echo "" -echo -e "${GREEN}=== Verification Complete ===${NC}" From 76e9709823801003688acf2ebc4709bdc07fadc2 Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Feb 2026 07:50:54 -0500 Subject: [PATCH 06/41] refactor: update ISuperGoodDollar interface implementation and remove unused dependencies in bridge-contracts --- .../contracts/oft/GoodDollarMinterBurner.sol | 2 +- .../oft/interfaces/ISuperGoodDollar.sol | 18 ++++ packages/bridge-contracts/package.json | 1 - .../scripts/oft/grant-minter-role.ts | 4 +- .../typechain-types/hardhat.d.ts | 72 ++------------ .../bridge-contracts/typechain-types/index.ts | 16 ---- yarn.lock | 94 ++++--------------- 7 files changed, 49 insertions(+), 158 deletions(-) create mode 100644 packages/bridge-contracts/contracts/oft/interfaces/ISuperGoodDollar.sol diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarMinterBurner.sol b/packages/bridge-contracts/contracts/oft/GoodDollarMinterBurner.sol index be42323..22df674 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarMinterBurner.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarMinterBurner.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: MIT pragma solidity >=0.8; -import {ISuperGoodDollar} from "@gooddollar/goodprotocol/contracts/token/superfluid/ISuperGoodDollar.sol"; +import {ISuperGoodDollar} from "./interfaces/ISuperGoodDollar.sol"; import {DAOUpgradeableContract, INameService} from "@gooddollar/goodprotocol/contracts/utils/DAOUpgradeableContract.sol"; interface IIdentity { diff --git a/packages/bridge-contracts/contracts/oft/interfaces/ISuperGoodDollar.sol b/packages/bridge-contracts/contracts/oft/interfaces/ISuperGoodDollar.sol new file mode 100644 index 0000000..d124f20 --- /dev/null +++ b/packages/bridge-contracts/contracts/oft/interfaces/ISuperGoodDollar.sol @@ -0,0 +1,18 @@ +// SPDX-License-Identifier: MIT + +// pragma solidity >=0.8; +// import {IGoodDollar} from "@gooddollar/goodprotocol/contracts/Interfaces.sol"; +/** + * @title ISuperGoodDollar + * @notice Interface for GoodDollar token functions used in this project + * @dev This is a minimal interface containing only the functions actually used + */ +interface ISuperGoodDollar { + function isMinter(address _minter) external view returns (bool); + + function mint(address to, uint256 amount) external returns (bool); + + function burnFrom(address account, uint256 amount) external; + + function addMinter(address _minter) external; +} diff --git a/packages/bridge-contracts/package.json b/packages/bridge-contracts/package.json index 829e8e7..72e8875 100644 --- a/packages/bridge-contracts/package.json +++ b/packages/bridge-contracts/package.json @@ -48,7 +48,6 @@ "@openzeppelin/hardhat-upgrades": "^1.21.0", "@resolver-engine/core": "^0.3.3", "@safe-global/safe-core-sdk-types": "^2.3.0", - "@superfluid-finance/ethereum-contracts": "1.8.1", "@typechain/ethers-v5": "^10.1.0", "@typechain/hardhat": "^6.1.2", "@types/chai": "^4.3.3", diff --git a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts b/packages/bridge-contracts/scripts/oft/grant-minter-role.ts index b4683a8..6492283 100644 --- a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts +++ b/packages/bridge-contracts/scripts/oft/grant-minter-role.ts @@ -59,9 +59,9 @@ const main = async () => { console.log("Avatar:", avatarAddress); // Get token contract to check current status - // Use the interface from goodprotocol package + // Use the local interface file const token = await ethers.getContractAt( - "@gooddollar/goodprotocol/contracts/token/superfluid/ISuperGoodDollar.sol:ISuperGoodDollar", + "interfaces/ISuperGoodDollar.sol:ISuperGoodDollar", tokenAddress ); diff --git a/packages/bridge-contracts/typechain-types/hardhat.d.ts b/packages/bridge-contracts/typechain-types/hardhat.d.ts index 015446c..1e1a43a 100644 --- a/packages/bridge-contracts/typechain-types/hardhat.d.ts +++ b/packages/bridge-contracts/typechain-types/hardhat.d.ts @@ -420,34 +420,6 @@ declare module "hardhat/types/runtime" { name: "IERC165", signerOrOptions?: ethers.Signer | FactoryOptions ): Promise; - getContractFactory( - name: "IConstantInflowNFT", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "IConstantOutflowNFT", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "IFlowNFTBase", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "ISuperAgreement", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "ISuperfluidToken", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "ISuperToken", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "TokenInfo", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; getContractFactory( name: "BlockHeaderRegistry", signerOrOptions?: ethers.Signer | FactoryOptions @@ -528,6 +500,10 @@ declare module "hardhat/types/runtime" { name: "IIdentity", signerOrOptions?: ethers.Signer | FactoryOptions ): Promise; + getContractFactory( + name: "ISuperGoodDollar", + signerOrOptions?: ethers.Signer | FactoryOptions + ): Promise; getContractFactory( name: "ConsensusMock", signerOrOptions?: ethers.Signer | FactoryOptions @@ -1079,41 +1055,6 @@ declare module "hardhat/types/runtime" { address: string, signer?: ethers.Signer ): Promise; - getContractAt( - name: "IConstantInflowNFT", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "IConstantOutflowNFT", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "IFlowNFTBase", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "ISuperAgreement", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "ISuperfluidToken", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "ISuperToken", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "TokenInfo", - address: string, - signer?: ethers.Signer - ): Promise; getContractAt( name: "BlockHeaderRegistry", address: string, @@ -1214,6 +1155,11 @@ declare module "hardhat/types/runtime" { address: string, signer?: ethers.Signer ): Promise; + getContractAt( + name: "ISuperGoodDollar", + address: string, + signer?: ethers.Signer + ): Promise; getContractAt( name: "ConsensusMock", address: string, diff --git a/packages/bridge-contracts/typechain-types/index.ts b/packages/bridge-contracts/typechain-types/index.ts index c394b53..a714a9d 100644 --- a/packages/bridge-contracts/typechain-types/index.ts +++ b/packages/bridge-contracts/typechain-types/index.ts @@ -9,8 +9,6 @@ import type * as layerzerolabs from "./@layerzerolabs"; export type { layerzerolabs }; import type * as openzeppelin from "./@openzeppelin"; export type { openzeppelin }; -import type * as superfluidFinance from "./@superfluid-finance"; -export type { superfluidFinance }; import type * as contracts from "./contracts"; export type { contracts }; export * as factories from "./factories"; @@ -214,20 +212,6 @@ export type { IERC777 } from "./@openzeppelin/contracts/token/ERC777/IERC777"; export { IERC777__factory } from "./factories/@openzeppelin/contracts/token/ERC777/IERC777__factory"; export type { IERC165 } from "./@openzeppelin/contracts/utils/introspection/IERC165"; export { IERC165__factory } from "./factories/@openzeppelin/contracts/utils/introspection/IERC165__factory"; -export type { IConstantInflowNFT } from "./@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/IConstantInflowNFT"; -export { IConstantInflowNFT__factory } from "./factories/@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/IConstantInflowNFT__factory"; -export type { IConstantOutflowNFT } from "./@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/IConstantOutflowNFT"; -export { IConstantOutflowNFT__factory } from "./factories/@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/IConstantOutflowNFT__factory"; -export type { IFlowNFTBase } from "./@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/IFlowNFTBase"; -export { IFlowNFTBase__factory } from "./factories/@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/IFlowNFTBase__factory"; -export type { ISuperAgreement } from "./@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperAgreement"; -export { ISuperAgreement__factory } from "./factories/@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperAgreement__factory"; -export type { ISuperfluidToken } from "./@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken"; -export { ISuperfluidToken__factory } from "./factories/@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperfluidToken__factory"; -export type { ISuperToken } from "./@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken"; -export { ISuperToken__factory } from "./factories/@superfluid-finance/ethereum-contracts/contracts/interfaces/superfluid/ISuperToken__factory"; -export type { TokenInfo } from "./@superfluid-finance/ethereum-contracts/contracts/interfaces/tokens/TokenInfo"; -export { TokenInfo__factory } from "./factories/@superfluid-finance/ethereum-contracts/contracts/interfaces/tokens/TokenInfo__factory"; export type { BlockHeaderRegistry } from "./contracts/blockRegistry/BlockHeaderRegistry"; export { BlockHeaderRegistry__factory } from "./factories/contracts/blockRegistry/BlockHeaderRegistry__factory"; export type { BridgeCore } from "./contracts/bridge/BridgeCore"; diff --git a/yarn.lock b/yarn.lock index 2760b7a..313eacc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -968,17 +968,6 @@ __metadata: languageName: node linkType: hard -"@decentral.ee/web3-helpers@npm:0.5.3": - version: 0.5.3 - resolution: "@decentral.ee/web3-helpers@npm:0.5.3" - dependencies: - web3-utils: "npm:^1.3.4" - peerDependencies: - "@openzeppelin/test-helpers": ^0.5.10 - checksum: 10/9b02fe80d792adaeecf296192750b102c16869f93e11b70a47c3dac20261cd6f98086fa4e2f92df98d735c3d10a92c878f951ec6ce761f6e4d0cacebe73e1ed9 - languageName: node - linkType: hard - "@ensdomains/address-encoder@npm:^0.1.7": version: 0.1.9 resolution: "@ensdomains/address-encoder@npm:0.1.9" @@ -2129,7 +2118,6 @@ __metadata: "@openzeppelin/hardhat-upgrades": "npm:^1.21.0" "@resolver-engine/core": "npm:^0.3.3" "@safe-global/safe-core-sdk-types": "npm:^2.3.0" - "@superfluid-finance/ethereum-contracts": "npm:1.8.1" "@typechain/ethers-v5": "npm:^10.1.0" "@typechain/hardhat": "npm:^6.1.2" "@types/chai": "npm:^4.3.3" @@ -3469,13 +3457,6 @@ __metadata: languageName: node linkType: hard -"@openzeppelin/contracts@npm:4.9.3": - version: 4.9.3 - resolution: "@openzeppelin/contracts@npm:4.9.3" - checksum: 10/ce0a16a56a39b62d72370ac702bce1917096492442ff05de88521beda2c3f3935b93ee2b9a184614dd543a6181f2f0be10243f5a629be87aab284ade68c18320 - languageName: node - linkType: hard - "@openzeppelin/contracts@npm:^4.4.1": version: 4.9.6 resolution: "@openzeppelin/contracts@npm:4.9.6" @@ -4067,21 +4048,6 @@ __metadata: languageName: node linkType: hard -"@superfluid-finance/ethereum-contracts@npm:1.8.1": - version: 1.8.1 - resolution: "@superfluid-finance/ethereum-contracts@npm:1.8.1" - dependencies: - "@decentral.ee/web3-helpers": "npm:0.5.3" - "@openzeppelin/contracts": "npm:4.9.3" - "@truffle/contract": "npm:4.6.28" - ethereumjs-tx: "npm:2.1.2" - ethereumjs-util: "npm:7.1.5" - peerDependencies: - ethers: ^5.7.2 - checksum: 10/33274b718de24f77f16014819df3d87d90013a2abe0547c8ec5d9536cd73dc940a0cad54e18ace3351cc4408cd9536e3285342bee8ab38b5dafb296fcc9fe01a - languageName: node - linkType: hard - "@szmarczak/http-timer@npm:^1.1.2": version: 1.1.2 resolution: "@szmarczak/http-timer@npm:1.1.2" @@ -4120,7 +4086,7 @@ __metadata: languageName: node linkType: hard -"@truffle/blockchain-utils@npm:^0.1.8, @truffle/blockchain-utils@npm:^0.1.9": +"@truffle/blockchain-utils@npm:^0.1.9": version: 0.1.9 resolution: "@truffle/blockchain-utils@npm:0.1.9" checksum: 10/4f7acfc00b29ae6830eb60563e58e1fef3d2480fc4bf426781b3cf40e1a6387bf05fef15e0ac851f7c10e586515db0fa370ac2dbeffa396e735d665738831663 @@ -4204,7 +4170,7 @@ __metadata: languageName: node linkType: hard -"@truffle/contract-schema@npm:^3.3.1, @truffle/contract-schema@npm:^3.4.15, @truffle/contract-schema@npm:^3.4.16": +"@truffle/contract-schema@npm:^3.3.1, @truffle/contract-schema@npm:^3.4.16": version: 3.4.16 resolution: "@truffle/contract-schema@npm:3.4.16" dependencies: @@ -4224,28 +4190,6 @@ __metadata: languageName: node linkType: hard -"@truffle/contract@npm:4.6.28": - version: 4.6.28 - resolution: "@truffle/contract@npm:4.6.28" - dependencies: - "@ensdomains/ensjs": "npm:^2.1.0" - "@truffle/blockchain-utils": "npm:^0.1.8" - "@truffle/contract-schema": "npm:^3.4.15" - "@truffle/debug-utils": "npm:^6.0.56" - "@truffle/error": "npm:^0.2.1" - "@truffle/interface-adapter": "npm:^0.5.35" - bignumber.js: "npm:^7.2.1" - debug: "npm:^4.3.1" - ethers: "npm:^4.0.32" - web3: "npm:1.10.0" - web3-core-helpers: "npm:1.10.0" - web3-core-promievent: "npm:1.10.0" - web3-eth-abi: "npm:1.10.0" - web3-utils: "npm:1.10.0" - checksum: 10/4a2d2f3418748ccf167fc360383e8ed247f2e0a5147f18e73aef7376ba19a15a70aad1f5c82562fc88bf06571f0719dd1edab1a03e64454c7c93604d645948e1 - languageName: node - linkType: hard - "@truffle/contract@npm:^4.6.31": version: 4.6.31 resolution: "@truffle/contract@npm:4.6.31" @@ -4331,7 +4275,7 @@ __metadata: languageName: node linkType: hard -"@truffle/debug-utils@npm:^6.0.56, @truffle/debug-utils@npm:^6.0.57": +"@truffle/debug-utils@npm:^6.0.57": version: 6.0.57 resolution: "@truffle/debug-utils@npm:6.0.57" dependencies: @@ -4368,7 +4312,7 @@ __metadata: languageName: node linkType: hard -"@truffle/error@npm:^0.2.1, @truffle/error@npm:^0.2.2": +"@truffle/error@npm:^0.2.2": version: 0.2.2 resolution: "@truffle/error@npm:0.2.2" checksum: 10/1f2b982e58ee84510b59dd5bba8cbb21a42cb24a58ef46334f43433f3c3586ade5575846965f6fe069b0bf59f3a36134f8792f3b78eb0f4e72b8cb2ad8e323e2 @@ -4395,7 +4339,7 @@ __metadata: languageName: node linkType: hard -"@truffle/interface-adapter@npm:^0.5.35, @truffle/interface-adapter@npm:^0.5.37": +"@truffle/interface-adapter@npm:^0.5.37": version: 0.5.37 resolution: "@truffle/interface-adapter@npm:0.5.37" dependencies: @@ -10782,19 +10726,6 @@ __metadata: languageName: node linkType: hard -"ethereumjs-util@npm:7.1.5, ethereumjs-util@npm:^7.0.2, ethereumjs-util@npm:^7.0.3, ethereumjs-util@npm:^7.1.0, ethereumjs-util@npm:^7.1.1, ethereumjs-util@npm:^7.1.2, ethereumjs-util@npm:^7.1.4, ethereumjs-util@npm:^7.1.5": - version: 7.1.5 - resolution: "ethereumjs-util@npm:7.1.5" - dependencies: - "@types/bn.js": "npm:^5.1.0" - bn.js: "npm:^5.1.2" - create-hash: "npm:^1.1.2" - ethereum-cryptography: "npm:^0.1.3" - rlp: "npm:^2.2.4" - checksum: 10/f28fc1ebb8f35bf9e418f76f51be737d94d603b912c3e014c4e87cd45ccd1b10bdfef764c8f152574b57e9faa260a18773cbc110f9e0a754d6b3730699e54dc9 - languageName: node - linkType: hard - "ethereumjs-util@npm:^4.3.0": version: 4.5.1 resolution: "ethereumjs-util@npm:4.5.1" @@ -10823,6 +10754,19 @@ __metadata: languageName: node linkType: hard +"ethereumjs-util@npm:^7.0.2, ethereumjs-util@npm:^7.0.3, ethereumjs-util@npm:^7.1.0, ethereumjs-util@npm:^7.1.1, ethereumjs-util@npm:^7.1.2, ethereumjs-util@npm:^7.1.4, ethereumjs-util@npm:^7.1.5": + version: 7.1.5 + resolution: "ethereumjs-util@npm:7.1.5" + dependencies: + "@types/bn.js": "npm:^5.1.0" + bn.js: "npm:^5.1.2" + create-hash: "npm:^1.1.2" + ethereum-cryptography: "npm:^0.1.3" + rlp: "npm:^2.2.4" + checksum: 10/f28fc1ebb8f35bf9e418f76f51be737d94d603b912c3e014c4e87cd45ccd1b10bdfef764c8f152574b57e9faa260a18773cbc110f9e0a754d6b3730699e54dc9 + languageName: node + linkType: hard + "ethereumjs-vm@npm:4.2.0": version: 4.2.0 resolution: "ethereumjs-vm@npm:4.2.0" @@ -22843,7 +22787,7 @@ __metadata: languageName: node linkType: hard -"web3-utils@npm:1.10.4, web3-utils@npm:^1.0.0-beta.31, web3-utils@npm:^1.3.4, web3-utils@npm:^1.3.6, web3-utils@npm:^1.8.1": +"web3-utils@npm:1.10.4, web3-utils@npm:^1.0.0-beta.31, web3-utils@npm:^1.3.6, web3-utils@npm:^1.8.1": version: 1.10.4 resolution: "web3-utils@npm:1.10.4" dependencies: From fb5d2691bf0fc51badfecd3f912b20113e15b69b Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Feb 2026 09:05:13 -0500 Subject: [PATCH 07/41] refactor: remove upgrade authorization logic and change deployment kind to transparent in GoodDollarOFTAdapter and deployment script --- .../contracts/oft/GoodDollarOFTAdapter.sol | 15 --------------- .../bridge-contracts/scripts/oft/oft-deploy.ts | 4 ++-- 2 files changed, 2 insertions(+), 17 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index 5cbe172..b02504e 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -88,9 +88,6 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { /// @dev Event emitted when fees are collected event FeeCollected(address indexed recipient, uint256 amount); - /// @dev Event emitted when the contract is authorized to be upgraded - event AuthorizedUpgrade(address indexed newImplementation); - /// @dev Event emitted when bridge limits are updated event BridgeLimitsSet( uint256 dailyLimit, @@ -369,18 +366,6 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { return _amountLD; } - /** - * @dev Authorizes the upgrade of the contract to a new implementation - * @param newImplementation The address of the new implementation contract - * @dev Only the owner can authorize upgrades (enforced by onlyOwner modifier) - * @dev This function is required for UUPS upgradeable pattern - */ - function _authorizeUpgrade(address newImplementation) internal override onlyOwner { - // Authorization is handled by onlyOwner modifier - // Additional checks can be added here if needed - emit AuthorizedUpgrade(newImplementation); - } - /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. diff --git a/packages/bridge-contracts/scripts/oft/oft-deploy.ts b/packages/bridge-contracts/scripts/oft/oft-deploy.ts index 715717b..3b98be1 100644 --- a/packages/bridge-contracts/scripts/oft/oft-deploy.ts +++ b/packages/bridge-contracts/scripts/oft/oft-deploy.ts @@ -87,7 +87,7 @@ export const deployOFTContracts = async () => { MinterBurner = await upgrades.deployProxy( MinterBurnerFactory, [nameServiceAddress], - { kind: "uups", initializer: "initialize" } + { kind: "transparent", initializer: "initialize" } ); await MinterBurner.deployed(); console.log("GoodDollarMinterBurner deployed to:", MinterBurner.address); @@ -151,7 +151,7 @@ export const deployOFTContracts = async () => { nameServiceAddress ], { - kind: "uups", + kind: "transparent", initializer: "initialize", unsafeAllow: ["constructor", "state-variable-immutable", "missing-initializer-call", "duplicate-initializer-call"], constructorArgs: [tokenAddress, lzEndpoint] From 1d08bcef1bf2c9ed28470c3894e814634f3b3aff Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Feb 2026 09:10:51 -0500 Subject: [PATCH 08/41] feat: introduce deployment-oft.json for OFT contracts and update scripts to reference new deployment file --- .../bridge-contracts/release/deployment-oft.json | 11 +++++++++++ packages/bridge-contracts/release/deployment.json | 8 -------- .../bridge-contracts/scripts/oft/bridge-oft-token.ts | 12 ++++++------ .../scripts/oft/grant-minter-role.ts | 10 +++++----- packages/bridge-contracts/scripts/oft/oft-deploy.ts | 6 +++--- .../scripts/oft/set-minter-burner-limits.ts | 10 +++++----- .../transfer-oft-adapter-ownership-from-avatar.ts | 8 ++++---- .../scripts/oft/transfer-oft-adapter-ownership.ts | 8 ++++---- 8 files changed, 38 insertions(+), 35 deletions(-) create mode 100644 packages/bridge-contracts/release/deployment-oft.json diff --git a/packages/bridge-contracts/release/deployment-oft.json b/packages/bridge-contracts/release/deployment-oft.json new file mode 100644 index 0000000..498a290 --- /dev/null +++ b/packages/bridge-contracts/release/deployment-oft.json @@ -0,0 +1,11 @@ +{ + "development-celo": { + "GoodDollarMinterBurner": "0x3850786C7627Ce276EF6F715a0eF31d5FeCf1fc7", + "GoodDollarOFTAdapter": "0x28aE7CE2F3BBb0bf58B24F1961885aeE1e456f46" + }, + "development-xdc": { + "GoodDollarMinterBurner": "0xb20eaE658437b793257C326751a695ED5e94dD3E", + "GoodDollarOFTAdapter": "0x3a568CFD4e3F6ef9f5e341FAbAD84B6Cc41C1A75" + } +} + diff --git a/packages/bridge-contracts/release/deployment.json b/packages/bridge-contracts/release/deployment.json index 4edf8c3..a93d348 100644 --- a/packages/bridge-contracts/release/deployment.json +++ b/packages/bridge-contracts/release/deployment.json @@ -13,13 +13,5 @@ "fuseBridge": "0x08fdf766694C353401350c225cAEB9C631dC3288", "celoBridge": "0xfb152Fc469A3E9154f8AA60bbD6700EcBC357A54", "registry": "0x44a1E0A83821E239F9Cef248CECc3AC5b910aeD2" - }, - "development-celo": { - "GoodDollarMinterBurner": "0x3850786C7627Ce276EF6F715a0eF31d5FeCf1fc7", - "GoodDollarOFTAdapter": "0x28aE7CE2F3BBb0bf58B24F1961885aeE1e456f46" - }, - "development-xdc": { - "GoodDollarMinterBurner": "0xb20eaE658437b793257C326751a695ED5e94dD3E", - "GoodDollarOFTAdapter": "0x3a568CFD4e3F6ef9f5e341FAbAD84B6Cc41C1A75" } } diff --git a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts b/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts index 9e24b3b..1aadceb 100644 --- a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts +++ b/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts @@ -22,7 +22,7 @@ import { network, ethers } from "hardhat"; import { Contract } from "ethers"; import { EndpointId } from "@layerzerolabs/lz-definitions"; import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; -import release from "../../release/deployment.json"; +import release from "../../release/deployment-oft.json"; // IERC20 interface for token operations const IERC20_ABI = [ @@ -79,15 +79,15 @@ const main = async () => { const minterBurnerAddress = currentRelease.GoodDollarMinterBurner; if (!oftAdapterAddress) { - throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollarOFTAdapter not found in deployment-oft.json for ${networkName}`); } if (!tokenAddress) { - throw new Error(`GoodDollar token not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollar token not found in GoodProtocol deployment.json for ${networkName}`); } if (!minterBurnerAddress) { - throw new Error(`GoodDollarMinterBurner not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollarMinterBurner not found in deployment-oft.json for ${networkName}`); } console.log("\nSource chain contract addresses:"); @@ -150,9 +150,9 @@ const main = async () => { if (!destOFTAdapter) { throw new Error( - `${destNetwork} OFT adapter address not found in deployment.json.\n` + + `${destNetwork} OFT adapter address not found in deployment-oft.json.\n` + `Please either:\n` + - ` 1. Deploy OFT adapter on ${destNetwork} and add it to deployment.json, or\n` + + ` 1. Deploy OFT adapter on ${destNetwork} and add it to deployment-oft.json, or\n` + ` 2. Manually set the peer using: scripts/set-oft-peer.ts` ); } diff --git a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts b/packages/bridge-contracts/scripts/oft/grant-minter-role.ts index 6492283..69a02c7 100644 --- a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts +++ b/packages/bridge-contracts/scripts/oft/grant-minter-role.ts @@ -10,7 +10,7 @@ import { network, ethers } from "hardhat"; import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; -import release from "../../release/deployment.json"; +import release from "../../release/deployment-oft.json"; const main = async () => { const networkName = network.name; @@ -37,19 +37,19 @@ const main = async () => { const avatarAddress = goodProtocolContracts.Avatar; if (!tokenAddress) { - throw new Error(`GoodDollar token not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollar token not found in GoodProtocol deployment.json for ${networkName}`); } if (!minterBurnerAddress) { - throw new Error(`GoodDollarMinterBurner not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollarMinterBurner not found in deployment-oft.json for ${networkName}`); } if (!controllerAddress) { - throw new Error(`Controller not found in deployment.json for ${networkName}`); + throw new Error(`Controller not found in GoodProtocol deployment.json for ${networkName}`); } if (!avatarAddress) { - throw new Error(`Avatar not found in deployment.json for ${networkName}`); + throw new Error(`Avatar not found in GoodProtocol deployment.json for ${networkName}`); } console.log("\nContract addresses:"); diff --git a/packages/bridge-contracts/scripts/oft/oft-deploy.ts b/packages/bridge-contracts/scripts/oft/oft-deploy.ts index 3b98be1..75c0d60 100644 --- a/packages/bridge-contracts/scripts/oft/oft-deploy.ts +++ b/packages/bridge-contracts/scripts/oft/oft-deploy.ts @@ -15,7 +15,7 @@ import { network, ethers, upgrades } from "hardhat"; import { Contract } from "ethers"; import fse from "fs-extra"; import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; -import release from "../../release/deployment.json"; +import release from "../../release/deployment-oft.json"; const { name: networkName } = network; @@ -97,7 +97,7 @@ export const deployOFTContracts = async () => { release[networkName] = {}; } release[networkName].GoodDollarMinterBurner = MinterBurner.address; - await fse.writeJSON("release/deployment.json", release, { spaces: 2 }); + await fse.writeJSON("release/deployment-oft.json", release, { spaces: 2 }); } else { console.log("GoodDollarMinterBurner already deployed at:", currentRelease.GoodDollarMinterBurner); MinterBurner = await ethers.getContractAt("GoodDollarMinterBurner", currentRelease.GoodDollarMinterBurner); @@ -162,7 +162,7 @@ export const deployOFTContracts = async () => { // Update release file release[networkName].GoodDollarOFTAdapter = OFTAdapter.address; - await fse.writeJSON("release/deployment.json", release, { spaces: 2 }); + await fse.writeJSON("release/deployment-oft.json", release, { spaces: 2 }); console.log("Fee recipient:", feeRecipient); } else { diff --git a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts b/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts index f04a111..bc389e9 100644 --- a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts +++ b/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts @@ -14,7 +14,7 @@ import { network, ethers } from "hardhat"; import { BigNumber } from "ethers"; import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; -import release from "../../release/deployment.json"; +import release from "../../release/deployment-oft.json"; const main = async () => { const networkName = network.name; @@ -31,7 +31,7 @@ const main = async () => { // Get deployment info const currentRelease = release[networkName] || {}; if (!currentRelease.GoodDollarOFTAdapter) { - throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollarOFTAdapter not found in deployment-oft.json for ${networkName}`); } // Get GoodProtocol contracts for Controller and Avatar @@ -45,15 +45,15 @@ const main = async () => { const avatarAddress = goodProtocolContracts.Avatar; if (!oftAdapterAddress) { - throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollarOFTAdapter not found in deployment-oft.json for ${networkName}`); } if (!controllerAddress) { - throw new Error(`Controller not found in deployment.json for ${networkName}`); + throw new Error(`Controller not found in GoodProtocol deployment.json for ${networkName}`); } if (!avatarAddress) { - throw new Error(`Avatar not found in deployment.json for ${networkName}`); + throw new Error(`Avatar not found in GoodProtocol deployment.json for ${networkName}`); } console.log("\nContract addresses:"); diff --git a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts b/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts index 5c14c55..89b2134 100644 --- a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts +++ b/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts @@ -11,7 +11,7 @@ import { network, ethers } from "hardhat"; import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; -import release from "../../release/deployment.json"; +import release from "../../release/deployment-oft.json"; const main = async () => { const networkName = network.name; @@ -36,15 +36,15 @@ const main = async () => { const controllerAddress = goodProtocolContracts.Controller; if (!oftAdapterAddress) { - throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollarOFTAdapter not found in deployment-oft.json for ${networkName}`); } if (!avatarAddress) { - throw new Error(`Avatar not found in deployment.json for ${networkName}`); + throw new Error(`Avatar not found in GoodProtocol deployment.json for ${networkName}`); } if (!controllerAddress) { - throw new Error(`Controller not found in deployment.json for ${networkName}`); + throw new Error(`Controller not found in GoodProtocol deployment.json for ${networkName}`); } console.log("Contract addresses:"); diff --git a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts b/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts index 44af75b..411c024 100644 --- a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts +++ b/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts @@ -10,7 +10,7 @@ import { network, ethers } from "hardhat"; import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; -import release from "../../release/deployment.json"; +import release from "../../release/deployment-oft.json"; const main = async () => { const networkName = network.name; @@ -33,11 +33,11 @@ const main = async () => { const avatarAddress = goodProtocolContracts.Avatar; if (!oftAdapterAddress) { - throw new Error(`GoodDollarOFTAdapter not found in deployment.json for ${networkName}`); + throw new Error(`GoodDollarOFTAdapter not found in deployment-oft.json for ${networkName}`); } if (!avatarAddress) { - throw new Error(`Avatar not found in deployment.json for ${networkName}`); + throw new Error(`Avatar not found in GoodProtocol deployment.json for ${networkName}`); } console.log("\nContract addresses:"); @@ -161,7 +161,7 @@ const main = async () => { "The contract is upgradeable-compatible but was deployed directly (not as a proxy).\n" + "Reinitialization is not possible for direct deployments.\n\n" + "SOLUTION: You must redeploy the contract:\n" + - "1. Remove the GoodDollarOFTAdapter address from release/deployment.json\n" + + "1. Remove the GoodDollarOFTAdapter address from release/deployment-oft.json\n" + "2. Run the deployment script again to deploy a new contract\n" + "3. The new contract will be initialized with the correct owner (Avatar)\n\n" + `Current OFTAdapter address: ${oftAdapterAddress}\n` + From 124a1a19624aadebd03945c7675ede80f5c0aa9c Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Feb 2026 09:31:16 -0500 Subject: [PATCH 09/41] feat: add oft.config.json for network-specific limit configurations and update set-minter-burner-limits script to utilize new config --- .../scripts/oft/oft.config.json | 55 +++++++++++++++++++ .../scripts/oft/set-minter-burner-limits.ts | 46 +++++++--------- 2 files changed, 76 insertions(+), 25 deletions(-) create mode 100644 packages/bridge-contracts/scripts/oft/oft.config.json diff --git a/packages/bridge-contracts/scripts/oft/oft.config.json b/packages/bridge-contracts/scripts/oft/oft.config.json new file mode 100644 index 0000000..db51c2e --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/oft.config.json @@ -0,0 +1,55 @@ +{ + "development-xdc": { + "skipTransferOwnership": false, + "skipWiring": false, + "skipLimits": false, + "skipBridgeTest": true, + "limits": { + "dailyLimit": "5000", + "txLimit": "1000", + "accountDailyLimit": "1000", + "minAmount": "1", + "onlyWhitelisted": false + } + }, + "development-celo": { + "skipTransferOwnership": false, + "skipWiring": false, + "skipLimits": false, + "skipBridgeTest": true, + "limits": { + "dailyLimit": "5000", + "txLimit": "1000", + "accountDailyLimit": "1000", + "minAmount": "1", + "onlyWhitelisted": false + } + }, + "production-xdc": { + "skipTransferOwnership": false, + "skipWiring": false, + "skipLimits": false, + "skipBridgeTest": true, + "limits": { + "dailyLimit": "1000000", + "txLimit": "100000", + "accountDailyLimit": "50000", + "minAmount": "10", + "onlyWhitelisted": false + } + }, + "production-celo": { + "skipTransferOwnership": false, + "skipWiring": false, + "skipLimits": false, + "skipBridgeTest": true, + "limits": { + "dailyLimit": "1000000", + "txLimit": "100000", + "accountDailyLimit": "50000", + "minAmount": "10", + "onlyWhitelisted": false + } + } +} + diff --git a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts b/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts index bc389e9..19ac230 100644 --- a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts +++ b/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts @@ -5,9 +5,12 @@ * Note: Limits are now managed in GoodDollarOFTAdapter, not GoodDollarMinterBurner * * Usage: - * DAILY_LIMIT=1000000 TX_LIMIT=100000 ACCOUNT_DAILY_LIMIT=50000 MIN_AMOUNT=10 \ * npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo * + * Configuration: + * All limit values are read from scripts/oft/oft.config.json + * Each network/env has its entry in the config file. + * * Note: This script must be run by a guardian or address with permissions to execute via Controller */ @@ -15,6 +18,7 @@ import { network, ethers } from "hardhat"; import { BigNumber } from "ethers"; import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; import release from "../../release/deployment-oft.json"; +import config from "./oft.config.json"; const main = async () => { const networkName = network.name; @@ -72,7 +76,17 @@ const main = async () => { console.log("Min Amount:", ethers.utils.formatEther(currentLimits.minAmount), "G$"); console.log("Only Whitelisted:", currentLimits.onlyWhitelisted); - // Parse environment variables (values can be in decimal format, e.g., "1000000" for 1M G$) + // Get config for this network + const networkConfig = (config as any)[networkName]; + if (!networkConfig || !networkConfig.limits) { + console.log("\n⚠️ No limits configuration found for this network."); + console.log(`Please add a "limits" entry for "${networkName}" in scripts/oft/oft.config.json`); + return; + } + + const limitsConfig = networkConfig.limits; + + // Parse limit values (values can be in decimal format, e.g., "1000000" for 1M G$) // The script will automatically convert them to wei (18 decimals) const parseLimit = (value: string | undefined): BigNumber | null => { if (!value) return null; @@ -89,29 +103,11 @@ const main = async () => { } }; - const dailyLimit = parseLimit(process.env.DAILY_LIMIT); - const txLimit = parseLimit(process.env.TX_LIMIT); - const accountDailyLimit = parseLimit(process.env.ACCOUNT_DAILY_LIMIT); - const minAmount = parseLimit(process.env.MIN_AMOUNT); - const onlyWhitelisted = process.env.ONLY_WHITELISTED !== undefined ? process.env.ONLY_WHITELISTED === "true" : null; - - // Check if any limits are being set - if (dailyLimit === null && txLimit === null && accountDailyLimit === null && minAmount === null && onlyWhitelisted === null) { - console.log("\n⚠️ No limits specified. Please provide at least one limit to set."); - console.log("\nUsage examples:"); - console.log(" # Using decimal values (recommended - easier to read):"); - console.log(" DAILY_LIMIT=1000000 TX_LIMIT=100000 \\"); - console.log(" ACCOUNT_DAILY_LIMIT=50000 MIN_AMOUNT=10 \\"); - console.log(" ONLY_WHITELISTED=false \\"); - console.log(" npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo"); - console.log("\n # Or using wei values (if you prefer):"); - console.log(" DAILY_LIMIT=1000000000000000000000000 TX_LIMIT=100000000000000000000000 \\"); - console.log(" ACCOUNT_DAILY_LIMIT=50000000000000000000000 MIN_AMOUNT=10000000000000000000 \\"); - console.log(" ONLY_WHITELISTED=false \\"); - console.log(" npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo"); - console.log("\nNote: Decimal values (e.g., '1000000' for 1M G$) are automatically converted to wei."); - return; - } + const dailyLimit = parseLimit(limitsConfig.dailyLimit); + const txLimit = parseLimit(limitsConfig.txLimit); + const accountDailyLimit = parseLimit(limitsConfig.accountDailyLimit); + const minAmount = parseLimit(limitsConfig.minAmount); + const onlyWhitelisted = limitsConfig.onlyWhitelisted !== undefined ? limitsConfig.onlyWhitelisted : null; // Prepare new limits struct (use current values if not provided) const newLimits = { From d1dc1d44be5bd9981c15becdce9431b59d143651 Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Feb 2026 09:39:15 -0500 Subject: [PATCH 10/41] docs: add OFT configuration guide for cross-chain setup between XDC and CELO networks --- .../scripts/oft/OFT_CONFIGURING_GUIDE.md | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md diff --git a/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md b/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md new file mode 100644 index 0000000..4c2e9b1 --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md @@ -0,0 +1,191 @@ +# OFT (Omnichain Fungible Token) Configuration Guide + +This guide explains how to configure the GoodDollar OFT bridge between XDC and CELO networks using LayerZero. + +## Overview + +The OFT bridge enables cross-chain transfers of GoodDollar (G$) tokens between XDC and CELO networks. The setup involves deploying contracts, configuring permissions, setting up LayerZero connections, and configuring bridge limits. + + +## Configuration File + +All configuration values are stored in `scripts/oft/oft.config.json`. Each network has its own configuration entry. + +### Configuration Structure + +```json +{ + "development-xdc": { + "skipTransferOwnership": false, + "skipWiring": false, + "skipLimits": false, + "skipBridgeTest": true, + "limits": { + "dailyLimit": "5000", + "txLimit": "1000", + "accountDailyLimit": "1000", + "minAmount": "1", + "onlyWhitelisted": false + } + }, + "development-celo": { + // ... same structure + }, + "production-xdc": { + // ... same structure + }, + "production-celo": { + // ... same structure + } +} +``` + +### Configuration Options + +- **skipTransferOwnership**: Skip transferring OFT adapter ownership to DAO Avatar +- **skipWiring**: Skip LayerZero wiring (requires manual configuration) +- **skipLimits**: Skip setting bridge limits +- **skipBridgeTest**: Skip bridge functionality test +- **limits**: Bridge limit configuration + - **dailyLimit**: Daily bridge limit in G$ (converted to wei automatically) + - **txLimit**: Per-transaction limit in G$ (converted to wei automatically) + - **accountDailyLimit**: Per-account daily limit in G$ (converted to wei automatically) + - **minAmount**: Minimum bridge amount in G$ (converted to wei automatically) + - **onlyWhitelisted**: Whether to restrict bridging to whitelisted addresses + +**Note**: Limit values can be specified in decimal format (e.g., "5000" for 5,000 G$). The scripts automatically convert them to wei (18 decimals). + +## Manual Configuration: Step-by-Step + +If you prefer to configure each network individually or need more control, follow these steps: + +### Step 1: Deploy OFT Contracts + +Deploy the GoodDollarMinterBurner and GoodDollarOFTAdapter contracts on each network: + +```bash +# Deploy on XDC +yarn hardhat run scripts/oft/oft-deploy.ts --network development-xdc + +# Deploy on CELO +yarn hardhat run scripts/oft/oft-deploy.ts --network development-celo +``` + +This script will: +- Deploy `GoodDollarMinterBurner` (upgradeable proxy) +- Deploy `GoodDollarOFTAdapter` (upgradeable proxy) +- Set OFT adapter as operator on MinterBurner via DAO +- Save contract addresses to `release/deployment-oft.json` + +### Step 2: Grant MINTER_ROLE + +Grant the MINTER_ROLE to GoodDollarMinterBurner on the GoodDollar token: + +```bash +# Grant on XDC +yarn hardhat run scripts/oft/grant-minter-role.ts --network development-xdc + +# Grant on CELO +yarn hardhat run scripts/oft/grant-minter-role.ts --network development-celo +``` + +This executes via DAO governance (Controller/Avatar) to grant the minter role. + +### Step 3: Wire LayerZero Connections + +Configure LayerZero messaging libraries, DVNs, executors, and enforced options: + +```bash +# Wire on XDC +yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-xdc + +# Wire on CELO +yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-celo +``` + +**Important**: +- Wiring may fail with permission errors (0xc4c52593) if the OApp owner doesn't have delegate permissions on the LayerZero endpoint +- If wiring fails, you may need to manually configure enforced options or contact LayerZero support + +### Step 4: Set Bridge Limits + +Configure bridge limits using values from `oft.config.json`: + +```bash +# Set limits on XDC +yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-xdc + +# Set limits on CELO +yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo +``` + +The script reads limit values from `oft.config.json` for the specified network and sets them on the OFT adapter. + +### Step 5: Transfer Ownership + +Transfer OFT adapter ownership to DAO Avatar: + +```bash +# Transfer on XDC +yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-xdc + +# Transfer on CELO +yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-celo +``` + +**Note**: This must be done by the current owner of the OFT adapter (usually the deployer). + +### Step 6: Test Bridge Functionality (Optional) + +Test the bridge by sending tokens from one chain to another: + +```bash +# Bridge from XDC to CELO +yarn hardhat run scripts/oft/bridge-oft-token.ts --network development-xdc + +# Bridge from CELO to XDC +yarn hardhat run scripts/oft/bridge-oft-token.ts --network development-celo +``` + +**Requirements**: +- Sufficient G$ balance on the source chain +- Sufficient native token (XDC/CELO) for gas and LayerZero fees +- MinterBurner approval for token burning + +## Configuration Verification + +After configuration, verify the setup: + +1. **Check contract deployments**: Verify addresses in `release/deployment-oft.json` +2. **Check MINTER_ROLE**: Verify MinterBurner has minter role on GoodDollar token +3. **Check ownership**: Verify OFT adapter is owned by DAO Avatar +4. **Check LayerZero peers**: Verify peer connections are set between chains +5. **Check limits**: Verify bridge limits are set correctly + +## Troubleshooting + +### Bridge Test Fails + +**Error**: `NoPeer` or peer not configured + +**Solution**: +- Ensure LayerZero wiring completed successfully +- Verify peer addresses are set correctly in `layerzero.config.ts` +- Check that OFT adapters are deployed on both networks + +**Error**: Insufficient balance + +**Solution**: +- Ensure you have sufficient G$ tokens on the source chain +- Ensure you have sufficient native tokens (XDC/CELO) for gas and fees +- Approve MinterBurner to burn tokens if needed + +### Limits Not Set + +**Error**: No limits configuration found + +**Solution**: +- Ensure `oft.config.json` has an entry for your network +- Verify the `limits` object is properly formatted +- Check that `skipLimits` is not set to `true` + From 9d115228cdf6608e6cb748d68bd7787b7fa7470f Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Feb 2026 09:39:45 -0500 Subject: [PATCH 11/41] refactor: remove unused initialization code from OFT deployment script for cleaner implementation --- packages/bridge-contracts/scripts/oft/oft-deploy.ts | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/packages/bridge-contracts/scripts/oft/oft-deploy.ts b/packages/bridge-contracts/scripts/oft/oft-deploy.ts index 75c0d60..92849d8 100644 --- a/packages/bridge-contracts/scripts/oft/oft-deploy.ts +++ b/packages/bridge-contracts/scripts/oft/oft-deploy.ts @@ -126,18 +126,6 @@ export const deployOFTContracts = async () => { } console.log("✅ Verified Avatar address:", avatarAddress); - // Encode the initialize function call - const initializeInterface = OFTAdapterFactory.interface; - const initializeData = initializeInterface.encodeFunctionData("initialize", [ - MinterBurner.address, - lzEndpoint, - root.address, - feeRecipient, - nameServiceAddress - ]); - - console.log("Initialize parameters:", initializeData); - // Create UUPS proxy manually using OpenZeppelin's ERC1967Proxy // This follows the same pattern as upgrades.deployProxy but bypasses validation console.log("Deploying proxy and initializing..."); From c5b45dfa03a3061d626c9d81b21b686220b8fb0b Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Feb 2026 09:42:49 -0500 Subject: [PATCH 12/41] chore: enable contract size checking on compile in Hardhat configuration --- packages/bridge-contracts/hardhat.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-contracts/hardhat.config.ts b/packages/bridge-contracts/hardhat.config.ts index a1753ca..9466402 100644 --- a/packages/bridge-contracts/hardhat.config.ts +++ b/packages/bridge-contracts/hardhat.config.ts @@ -226,7 +226,7 @@ const config: HardhatUserConfig = { ], }, contractSizer: { - runOnCompile: false, + runOnCompile: true, }, namedAccounts: { deployer: { From e907906c9cb26fa9f72bb7ffac20134cba10143f Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Feb 2026 10:28:27 -0500 Subject: [PATCH 13/41] refactor: streamline GoodDollarOFTAdapter by removing unused structs and integrating IMessagePassingBridge for limit management --- .../contracts/oft/GoodDollarOFTAdapter.sol | 116 +++++++----------- 1 file changed, 43 insertions(+), 73 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index b02504e..90d8fb3 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -3,9 +3,9 @@ pragma solidity >=0.8.0; import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { OFTCoreUpgradeable } from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTCoreUpgradeable.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol"; +import { BridgeHelperLibrary } from "../messagePassingBridge/BridgeHelperLibrary.sol"; +import { IMessagePassingBridge } from "../messagePassingBridge/IMessagePassingBridge.sol"; import { INameService } from "@gooddollar/goodprotocol/contracts/utils/DAOUpgradeableContract.sol"; interface IIdentity { @@ -17,7 +17,7 @@ interface IIdentity { * @notice Upgradeable OFT adapter that uses mint/burn mechanisms for cross-chain transfers * @dev Inherits from OFTCoreUpgradeable (which already includes OwnableUpgradeable) and implements mint/burn logic similar to MintBurnOFTAdapter */ -contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { +contract GoodDollarOFTAdapter is OFTCoreUpgradeable { /// @dev Struct for storing bridge fees struct BridgeFees { uint256 minFee; @@ -25,27 +25,6 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { uint256 fee; // Fee in basis points (0-10000, where 10000 = 100%) } - /// @dev Struct for storing account limits - struct AccountLimit { - uint256 lastTransferReset; - uint256 bridged24Hours; - } - - /// @dev Struct for storing bridge daily limits - struct BridgeDailyLimit { - uint256 lastTransferReset; - uint256 bridged24Hours; - } - - /// @dev Struct for storing bridge limits - struct BridgeLimits { - uint256 dailyLimit; - uint256 txLimit; - uint256 accountDailyLimit; - uint256 minAmount; - bool onlyWhitelisted; - } - /// @dev The underlying ERC20 token IERC20 internal innerToken; @@ -59,13 +38,13 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { address public feeRecipient; /// @dev Bridge limits structure - BridgeLimits public bridgeLimits; + IMessagePassingBridge.BridgeLimits public bridgeLimits; /// @dev Bridge daily limit tracking - BridgeDailyLimit public bridgeDailyLimit; + IMessagePassingBridge.BridgeDailyLimit public bridgeDailyLimit; /// @dev Account-specific daily limit tracking - mapping(address => AccountLimit) public accountsDailyLimit; + mapping(address => IMessagePassingBridge.AccountLimit) public accountsDailyLimit; /// @dev A mapping for approved requests above limits mapping(uint256 => bool) public approvedRequests; @@ -119,30 +98,23 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { /** * @notice Initializes the GoodDollarOFTAdapter contract + * @param _token The address of the underlying ERC20 token * @param _minterBurner The contract responsible for minting and burning tokens * @param _owner The contract owner - * @param _feeRecipient The address to receive bridge fees (can be address(0) to disable fees) - * @param _nameService The NameService contract for identity checks (can be address(0)) + * @dev The LayerZero endpoint is set in the constructor and cannot be changed per proxy */ - /// @custom:oz-upgrades-unsafe-allow constructor state-variable-immutable missing-initializer function initialize( + address _token, IMintableBurnable _minterBurner, - address /* _lzEndpoint */, - address _owner, - address _feeRecipient, - INameService _nameService + address _owner ) public initializer { - // Initialize OwnableUpgradeable first - __Ownable_init(); - - // Transfer ownership to the specified owner (since __Ownable_init sets it to msg.sender) + // Initialize parent contracts + // __OFTCore_init already initializes OwnableUpgradeable through OAppCoreUpgradeable __OFTCore_init(_owner); - _transferOwnership(_owner); // Set state variables - innerToken = IERC20(_nameService.getAddress("GOODDOLLAR")); + innerToken = IERC20(_token); minterBurner = _minterBurner; - feeRecipient = _feeRecipient; } /** @@ -201,7 +173,7 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { * @notice Sets the bridge limits configuration * @param _limits The bridge limits struct */ - function setBridgeLimits(BridgeLimits memory _limits) external onlyOwner { + function setBridgeLimits(IMessagePassingBridge.BridgeLimits memory _limits) external onlyOwner { bridgeLimits = _limits; emit BridgeLimitsSet( _limits.dailyLimit, @@ -246,35 +218,33 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { * @return error The error message, if any */ function canBridge(address _from, uint256 _amount) public view returns (bool isWithinLimit, string memory error) { - if (isClosed) return (false, 'closed'); - - if (_amount < bridgeLimits.minAmount) return (false, 'minAmount'); - - uint256 account24hours = accountsDailyLimit[_from].bridged24Hours; - if (accountsDailyLimit[_from].lastTransferReset < block.timestamp - 1 days) { - account24hours = _amount; - } else { - account24hours += _amount; - } - - if (bridgeLimits.onlyWhitelisted && address(nameService) != address(0)) { - IIdentity id = IIdentity(nameService.getAddress("IDENTITY")); - if (address(id) != address(0)) { - if (!id.isWhitelisted(_from)) return (false, 'not whitelisted'); - } - } - - if (account24hours > bridgeLimits.accountDailyLimit) return (false, 'accountDailyLimit'); - - if (_amount > bridgeLimits.txLimit) return (false, 'txLimit'); - - if (bridgeDailyLimit.lastTransferReset < block.timestamp - 1 days) { - if (_amount > bridgeLimits.dailyLimit) return (false, 'dailyLimit'); - } else { - if (bridgeDailyLimit.bridged24Hours + _amount > bridgeLimits.dailyLimit) return (false, 'dailyLimit'); - } - - return (true, ''); + IMessagePassingBridge.BridgeLimits memory limits = IMessagePassingBridge.BridgeLimits({ + dailyLimit: bridgeLimits.dailyLimit, + txLimit: bridgeLimits.txLimit, + accountDailyLimit: bridgeLimits.accountDailyLimit, + minAmount: bridgeLimits.minAmount, + onlyWhitelisted: bridgeLimits.onlyWhitelisted + }); + + IMessagePassingBridge.AccountLimit memory accountLimit = IMessagePassingBridge.AccountLimit({ + lastTransferReset: accountsDailyLimit[_from].lastTransferReset, + bridged24Hours: accountsDailyLimit[_from].bridged24Hours + }); + + IMessagePassingBridge.BridgeDailyLimit memory dailyLimit = IMessagePassingBridge.BridgeDailyLimit({ + lastTransferReset: bridgeDailyLimit.lastTransferReset, + bridged24Hours: bridgeDailyLimit.bridged24Hours + }); + + return BridgeHelperLibrary.canBridge( + limits, + accountLimit, + dailyLimit, + nameService, + isClosed, + _from, + _amount + ); } /** @@ -335,7 +305,7 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { * @param _amountLD The amount of tokens to credit in local decimals * @param _srcEid The source chain ID * @return amountReceivedLD The amount of tokens actually received in local decimals - * @dev Fees are deducted on the destination chain + * @dev Fees are deducted on the destination chain, matching MessagePassingBridge behavior */ function _credit( address _to, @@ -365,7 +335,7 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { return _amountLD; } - + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. From 30178c0c8a9dee19835befa1245da29e1116ac68 Mon Sep 17 00:00:00 2001 From: blueogin Date: Wed, 4 Feb 2026 09:36:35 -0500 Subject: [PATCH 14/41] refactor: remove unused imports from GoodDollarOFTAdapter to enhance code clarity and reduce dependencies --- .../bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol | 2 -- 1 file changed, 2 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index b6c776a..daf31e5 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -4,8 +4,6 @@ pragma solidity >=0.8.0; import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; import { OFTCoreUpgradeable } from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTCoreUpgradeable.sol"; import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol"; -import { BridgeHelperLibrary } from "../messagePassingBridge/BridgeHelperLibrary.sol"; -import { IMessagePassingBridge } from "../messagePassingBridge/IMessagePassingBridge.sol"; import { INameService } from "@gooddollar/goodprotocol/contracts/utils/DAOUpgradeableContract.sol"; interface IIdentity { From 71da58c3a2791a7ff24246bc76966e6492321c94 Mon Sep 17 00:00:00 2001 From: blueogin Date: Fri, 6 Feb 2026 10:04:04 -0500 Subject: [PATCH 15/41] refactor: enhance GoodDollarOFTAdapter with UUPS upgradeability, update request approval mechanism to use bytes32 IDs, and implement request ID generation for improved limit management --- .../contracts/oft/GoodDollarOFTAdapter.sol | 84 +++++++++++++++---- 1 file changed, 70 insertions(+), 14 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index daf31e5..0b0c1f5 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -2,9 +2,10 @@ pragma solidity >=0.8.0; import { IERC20, IERC20Metadata } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol"; -import { OFTCoreUpgradeable } from "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTCoreUpgradeable.sol"; +import "@layerzerolabs/oft-evm-upgradeable/contracts/oft/OFTCoreUpgradeable.sol"; import { IMintableBurnable } from "@layerzerolabs/oft-evm/contracts/interfaces/IMintableBurnable.sol"; import { INameService } from "@gooddollar/goodprotocol/contracts/utils/DAOUpgradeableContract.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; interface IIdentity { function isWhitelisted(address) external view returns (bool); @@ -15,7 +16,7 @@ interface IIdentity { * @notice Upgradeable OFT adapter that uses mint/burn mechanisms for cross-chain transfers * @dev Inherits from OFTCoreUpgradeable (which already includes OwnableUpgradeable) and implements mint/burn logic similar to MintBurnOFTAdapter */ -contract GoodDollarOFTAdapter is OFTCoreUpgradeable { +contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { /// @dev Struct for storing bridge fees struct BridgeFees { uint256 minFee; @@ -66,7 +67,7 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { mapping(address => AccountLimit) public accountsDailyLimit; /// @dev A mapping for approved requests above limits - mapping(uint256 => bool) public approvedRequests; + mapping(bytes32 => bool) public approvedRequests; /// @dev A boolean for whether the bridge is closed bool public isClosed; @@ -99,7 +100,10 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { event BridgePaused(bool isPaused); /// @dev Event emitted when a request is approved - event RequestApproved(uint256 indexed requestId); + event RequestApproved(bytes32 indexed requestId, bool approved); + + /// @dev Event emitted when limits are bypassed by request ID + event LimitsBypassedByRequestId(address indexed account, bytes32 indexed requestId); /** * @dev Constructor for the upgradeable contract @@ -214,10 +218,11 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { /** * @notice Function for approving requests above limits * @param _requestId The request id to approve + * @param approved Whether to approve the request or not */ - function approveRequest(uint256 _requestId) external onlyOwner { - approvedRequests[_requestId] = true; - emit RequestApproved(_requestId); + function approveRequest(bytes32 _requestId, bool approved) external onlyOwner { + approvedRequests[_requestId] = approved; + emit RequestApproved(_requestId, approved); } /** @@ -275,7 +280,12 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { * @param _requestId The request ID (0 to skip approval check) * @dev Limits are enforced on both sending and receiving sides */ - function _enforceLimits(address _address, uint256 _amount, uint256 _requestId) internal { + function _enforceLimits(address _address, uint256 _amount, bytes32 _requestId) internal { + if (_requestId != 0 && approvedRequests[_requestId] == true) { + emit LimitsBypassedByRequestId(_address, _requestId); + return; // skip approval check if request is approved + } + // Reset daily limits if needed if (bridgeDailyLimit.lastTransferReset < block.timestamp - 1 days) { bridgeDailyLimit.lastTransferReset = block.timestamp; @@ -311,9 +321,9 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { uint256 _minAmountLD, uint32 _dstEid ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { - // Enforce limits on sending side - // if (approvedRequests[id] == false) - _enforceLimits(_from, _amountLD, 0); + /// TODO: Enforce limits on sending side + // bytes32 requestId = getRequestId(address(this), _from, _to, _dstEid, 0); + // _enforceLimits(_from, _amountLD, requestId); (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid); // Burns tokens from the caller @@ -335,9 +345,6 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { ) internal virtual override returns (uint256 amountReceivedLD) { if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0) - // Enforce limits on receiving side (using recipient as the account to check limits for) - // if (approvedRequests[id] == false) - _enforceLimits(_to, _amountLD, 0); // Calculate fee (fee is deducted on destination chain, matching MessagePassingBridge) uint256 fee = _takeFee(_amountLD); @@ -357,6 +364,55 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable { return _amountLD; } + /** + * @notice Generates a request ID for a given transaction + * @param adapterAddress The address of the adapter contract + * @param sender The address of the sender + * @param to The address of the recipient + * @param sourceChainId The ID of the source chain + * @param id The current ID counter + * @return The request ID + */ + function getRequestId( + address adapterAddress, + address sender, + address to, + uint256 sourceChainId, + uint256 id + ) public pure returns (bytes32) { + return keccak256( + abi.encode( + adapterAddress, + sender, + to, + sourceChainId, + id + ) + ); + } + + /** + * @notice Overrides the default _lzReceive function to enforce limits on receiving side + */ + function _lzReceive( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata _extraData + ) internal virtual override { + // TODO: enforce limits on receiving side + // bytes32 requestId = getRequestId(address(this), _origin.sender, _message.sendTo().bytes32ToAddress(), _origin.srcEid, _origin.nonce); + // _enforceLimits(_message.sendTo().bytes32ToAddress(), _toLD(_message.amountSD()), requestId); + super._lzReceive(_origin, _guid, _message, _executor, _extraData); + } + + /** + * @dev Only the owner can authorize upgrades (enforced by onlyOwner modifier) + */ + function _authorizeUpgrade(address impl) internal virtual override onlyOwner { + } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. From e67caf1471c0601d61163c9754c70a6a32b36b1e Mon Sep 17 00:00:00 2001 From: blueogin Date: Fri, 6 Feb 2026 10:18:51 -0500 Subject: [PATCH 16/41] refactor: update OFT deployment script to use UUPS upgradeability for MinterBurner contracts --- packages/bridge-contracts/scripts/oft/oft-deploy.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-contracts/scripts/oft/oft-deploy.ts b/packages/bridge-contracts/scripts/oft/oft-deploy.ts index 92849d8..7e913b2 100644 --- a/packages/bridge-contracts/scripts/oft/oft-deploy.ts +++ b/packages/bridge-contracts/scripts/oft/oft-deploy.ts @@ -87,7 +87,7 @@ export const deployOFTContracts = async () => { MinterBurner = await upgrades.deployProxy( MinterBurnerFactory, [nameServiceAddress], - { kind: "transparent", initializer: "initialize" } + { kind: "uups", initializer: "initialize" } ); await MinterBurner.deployed(); console.log("GoodDollarMinterBurner deployed to:", MinterBurner.address); @@ -139,7 +139,7 @@ export const deployOFTContracts = async () => { nameServiceAddress ], { - kind: "transparent", + kind: "uups", initializer: "initialize", unsafeAllow: ["constructor", "state-variable-immutable", "missing-initializer-call", "duplicate-initializer-call"], constructorArgs: [tokenAddress, lzEndpoint] From 52284e0ccec3a15e0ddda9010d2e88c618bd0b2d Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 10 Feb 2026 07:04:58 -0500 Subject: [PATCH 17/41] refactor: reorganize GoodDollarOFTAdapter contract structure for improved clarity, implement request approval checks, and enforce limits on sending and receiving functions --- .../contracts/oft/GoodDollarOFTAdapter.sol | 157 +++++++++++------- 1 file changed, 93 insertions(+), 64 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index 0b0c1f5..dbe1719 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -16,7 +16,9 @@ interface IIdentity { * @notice Upgradeable OFT adapter that uses mint/burn mechanisms for cross-chain transfers * @dev Inherits from OFTCoreUpgradeable (which already includes OwnableUpgradeable) and implements mint/burn logic similar to MintBurnOFTAdapter */ -contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { +contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { + using OFTMsgCodec for bytes; + using OFTMsgCodec for bytes32; /// @dev Struct for storing bridge fees struct BridgeFees { uint256 minFee; @@ -66,6 +68,11 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { /// @dev Account-specific daily limit tracking mapping(address => AccountLimit) public accountsDailyLimit; + // struct Request { + // bool approved; + // uint256 amount; + // uint256 fromTimestamp; + // } /// @dev A mapping for approved requests above limits mapping(bytes32 => bool) public approvedRequests; @@ -132,9 +139,17 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { address _owner ) public initializer { // Initialize parent contracts - // __OFTCore_init already initializes OwnableUpgradeable through OAppCoreUpgradeable + __UUPSUpgradeable_init(); + // __Ownable_init_unchained(); + // __OAppSender_init_unchained(); + // __OAppReceiver_init_unchained(); + __Ownable_init(); + __OAppSender_init(_owner); + __OAppReceiver_init(_owner); __OFTCore_init(_owner); + _transferOwnership(_owner); + // Set state variables innerToken = IERC20(_token); minterBurner = _minterBurner; @@ -217,10 +232,13 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { /** * @notice Function for approving requests above limits - * @param _requestId The request id to approve - * @param approved Whether to approve the request or not */ - function approveRequest(bytes32 _requestId, bool approved) external onlyOwner { + function approveRequest(bytes32 _requestId, bool approved/*, uint256 _amount, uint256 _fromTimestamp*/) external onlyOwner { + // approvedRequests[_requestId] = Request( + // approved, + // _amount == 0 ? type(uint256).max : _amount, + // _fromTimestamp == 0 ? block.timestamp : _fromTimestamp + // ); approvedRequests[_requestId] = approved; emit RequestApproved(_requestId, approved); } @@ -273,6 +291,11 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { return (true, ''); } + function _isRequestApproved(bytes32 _requestId, uint256 _amount) internal view returns (bool) { + return approvedRequests[_requestId] /*.approved && + approvedRequests[_requestId].fromTimestamp < block.timestamp && + approvedRequests[_requestId].amount >= _amount*/; + } /** * @notice Enforces transfer limits and checks if the transfer is valid * @param _address The address to transfer from @@ -281,7 +304,7 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { * @dev Limits are enforced on both sending and receiving sides */ function _enforceLimits(address _address, uint256 _amount, bytes32 _requestId) internal { - if (_requestId != 0 && approvedRequests[_requestId] == true) { + if (_requestId != 0 && _isRequestApproved(_requestId, _amount)) { emit LimitsBypassedByRequestId(_address, _requestId); return; // skip approval check if request is approved } @@ -305,43 +328,43 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { bridgeDailyLimit.bridged24Hours += _amount; accountsDailyLimit[_address].bridged24Hours += _amount; } - + /** - * @notice Burns tokens from the sender's balance to prepare for sending - * @param _from The address to debit the tokens from - * @param _amountLD The amount of tokens to send in local decimals - * @param _minAmountLD The minimum amount to send in local decimals - * @param _dstEid The destination chain ID - * @return amountSentLD The amount sent in local decimals - * @return amountReceivedLD The amount received in local decimals on the remote + * @notice Overrides the default _send function to enforce limits on sending side */ - function _debit( - address _from, - uint256 _amountLD, - uint256 _minAmountLD, - uint32 _dstEid - ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { - /// TODO: Enforce limits on sending side - // bytes32 requestId = getRequestId(address(this), _from, _to, _dstEid, 0); - // _enforceLimits(_from, _amountLD, requestId); - - (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid); - // Burns tokens from the caller - minterBurner.burn(_from, amountSentLD); + function _send( + SendParam calldata _sendParam, + MessagingFee calldata _fee, + address _refundAddress + ) internal virtual override returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) { + (msgReceipt, oftReceipt) = super._send(_sendParam, _fee, _refundAddress); + _enforceLimits(_sendParam.to.bytes32ToAddress(), _sendParam.amountLD, msgReceipt.guid); } + /** + * @notice Overrides the default _lzReceive function to enforce limits on receiving side + */ + function _lzReceive( + Origin calldata _origin, + bytes32 _guid, + bytes calldata _message, + address _executor, + bytes calldata _extraData + ) internal virtual override { + _enforceLimits(_message.sendTo().bytes32ToAddress(), _toLD(_message.amountSD()), _guid); + super._lzReceive(_origin, _guid, _message, _executor, _extraData); + } /** * @notice Mints tokens to the specified address upon receiving them * @param _to The address to credit the tokens to * @param _amountLD The amount of tokens to credit in local decimals - * @param _srcEid The source chain ID * @return amountReceivedLD The amount of tokens actually received in local decimals * @dev Fees are deducted on the destination chain */ function _credit( address _to, uint256 _amountLD, - uint32 _srcEid + uint32 /* _srcEid */ ) internal virtual override returns (uint256 amountReceivedLD) { if (_to == address(0x0)) _to = address(0xdead); // _mint(...) does not support address(0x0) @@ -365,46 +388,51 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { } /** - * @notice Generates a request ID for a given transaction - * @param adapterAddress The address of the adapter contract - * @param sender The address of the sender - * @param to The address of the recipient - * @param sourceChainId The ID of the source chain - * @param id The current ID counter - * @return The request ID + * @notice Burns tokens from the sender's balance to prepare for sending + * @param _from The address to debit the tokens from + * @param _amountLD The amount of tokens to send in local decimals + * @param _minAmountLD The minimum amount to send in local decimals + * @param _dstEid The destination chain ID + * @return amountSentLD The amount sent in local decimals + * @return amountReceivedLD The amount received in local decimals on the remote */ - function getRequestId( - address adapterAddress, - address sender, - address to, - uint256 sourceChainId, - uint256 id - ) public pure returns (bytes32) { - return keccak256( - abi.encode( - adapterAddress, - sender, - to, - sourceChainId, - id - ) - ); + function _debit( + address _from, + uint256 _amountLD, + uint256 _minAmountLD, + uint32 _dstEid + ) internal virtual override returns (uint256 amountSentLD, uint256 amountReceivedLD) { + (amountSentLD, amountReceivedLD) = _debitView(_amountLD, _minAmountLD, _dstEid); + // Burns tokens from the caller + minterBurner.burn(_from, amountSentLD); } /** - * @notice Overrides the default _lzReceive function to enforce limits on receiving side + * @notice Predicts the GUID that will be used for the next message to a destination */ - function _lzReceive( - Origin calldata _origin, - bytes32 _guid, - bytes calldata _message, - address _executor, - bytes calldata _extraData - ) internal virtual override { - // TODO: enforce limits on receiving side - // bytes32 requestId = getRequestId(address(this), _origin.sender, _message.sendTo().bytes32ToAddress(), _origin.srcEid, _origin.nonce); - // _enforceLimits(_message.sendTo().bytes32ToAddress(), _toLD(_message.amountSD()), requestId); - super._lzReceive(_origin, _guid, _message, _executor, _extraData); + function predictNextGuid(uint32 _dstEid, address _sender, address _receiver) public view returns (bytes32) { + bytes32 receiverBytes32 = _toBytes32(_receiver); + return generateGuid( + endpoint.outboundNonce(_sender, _dstEid, receiverBytes32) + 1, + endpoint.eid(), + _sender, + _dstEid, + receiverBytes32 + ); + } + + function generateGuid( + uint64 _nonce, + uint32 _srcEid, + address _sender, + uint32 _dstEid, + bytes32 _receiver + ) public pure returns (bytes32) { + return keccak256(abi.encodePacked(_nonce, _srcEid, _toBytes32(_sender), _dstEid, _receiver)); + } + + function _toBytes32(address _address) internal pure returns (bytes32 result) { + result = bytes32(uint256(uint160(_address))); } /** @@ -413,6 +441,7 @@ contract GoodDollarOFTAdapter is OFTCoreUpgradeable, UUPSUpgradeable { function _authorizeUpgrade(address impl) internal virtual override onlyOwner { } + /** * @dev This empty reserved space is put in place to allow future versions to add new * variables without shifting down storage in the inheritance chain. From 0f8d105b3275b50650e9579eee0d4198c2d111bc Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 10 Feb 2026 09:02:15 -0500 Subject: [PATCH 18/41] refactor: update Hardhat configuration to include Solidity version 0.8.9 with optimizer settings and modify LayerZero config to reference the correct deployment file --- packages/bridge-contracts/hardhat.config.ts | 9 +++++++++ packages/bridge-contracts/layerzero.config.ts | 2 +- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/packages/bridge-contracts/hardhat.config.ts b/packages/bridge-contracts/hardhat.config.ts index 9466402..847db73 100644 --- a/packages/bridge-contracts/hardhat.config.ts +++ b/packages/bridge-contracts/hardhat.config.ts @@ -57,6 +57,15 @@ const config: HardhatUserConfig = { }, }, }, + { + version: '0.8.9', + settings: { + optimizer: { + enabled: true, + runs: 0, + }, + }, + }, ], }, networks: { diff --git a/packages/bridge-contracts/layerzero.config.ts b/packages/bridge-contracts/layerzero.config.ts index b310c3d..02eb3aa 100644 --- a/packages/bridge-contracts/layerzero.config.ts +++ b/packages/bridge-contracts/layerzero.config.ts @@ -14,7 +14,7 @@ import type { OmniPointHardhat } from "@layerzerolabs/toolbox-hardhat"; import { OAppEnforcedOption } from "@layerzerolabs/toolbox-hardhat"; import { ExecutorOptionType } from "@layerzerolabs/lz-v2-utilities"; import { TwoWayConfig, generateConnectionsConfig } from "@layerzerolabs/metadata-tools"; -import dao from "./release/deployment.json"; +import dao from "./release/deployment-oft.json"; // Network names - adjust these based on your deployment const XDC_NETWORK = "development-xdc"; From a4b2c681b12f4b19d60ec71a609833e3cb688f65 Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 10 Feb 2026 09:02:25 -0500 Subject: [PATCH 19/41] refactor: add feeRecipient parameter to GoodDollarOFTAdapter initialization for enhanced fee management --- .../bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index dbe1719..68337db 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -136,7 +136,8 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { function initialize( address _token, IMintableBurnable _minterBurner, - address _owner + address _owner, + address _feeRecipient ) public initializer { // Initialize parent contracts __UUPSUpgradeable_init(); @@ -153,6 +154,7 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { // Set state variables innerToken = IERC20(_token); minterBurner = _minterBurner; + feeRecipient = _feeRecipient; } /** From b678f7bb99877a2a5fbaffa91f5bc44557fa98a6 Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 10 Feb 2026 09:03:14 -0500 Subject: [PATCH 20/41] feat: introduce deployOFT script for GoodDollar OFT contracts and remove legacy deployment script; add set-oft-operator script for DAO governance integration --- packages/bridge-contracts/deploy/deployOFT.ts | 193 ++++++++++++++ .../scripts/oft/oft-deploy.ts | 236 ------------------ .../scripts/oft/set-oft-operator.ts | 146 +++++++++++ 3 files changed, 339 insertions(+), 236 deletions(-) create mode 100644 packages/bridge-contracts/deploy/deployOFT.ts delete mode 100644 packages/bridge-contracts/scripts/oft/oft-deploy.ts create mode 100644 packages/bridge-contracts/scripts/oft/set-oft-operator.ts diff --git a/packages/bridge-contracts/deploy/deployOFT.ts b/packages/bridge-contracts/deploy/deployOFT.ts new file mode 100644 index 0000000..4d9adb7 --- /dev/null +++ b/packages/bridge-contracts/deploy/deployOFT.ts @@ -0,0 +1,193 @@ +/*** + * Hardhat-deploy script for GoodDollar OFT (Omnichain Fungible Token) contracts + * + * Deploys: + * 1. GoodDollarMinterBurner - DAO-upgradeable contract that handles minting and burning of GoodDollar tokens for OFT + * 2. GoodDollarOFTAdapter - Upgradeable LayerZero OFT adapter that wraps GoodDollar token for cross-chain transfers + * + * Steps: + * 1. Deploy GoodDollarMinterBurner as upgradeable proxy with NameService + * 2. Deploy GoodDollarOFTAdapter as upgradeable proxy with constructor(token, lzEndpoint), then initialize(token, minterBurner, lzEndpoint, owner, feeRecipient) + * + * Note: Setting OFT adapter as operator on GoodDollarMinterBurner must be done separately via DAO governance + */ + +import { HardhatRuntimeEnvironment } from 'hardhat/types'; +import { DeployFunction } from 'hardhat-deploy/types'; +import { ethers, upgrades } from 'hardhat'; +import Contracts from '@gooddollar/goodprotocol/releases/deployment.json'; +import fse from 'fs-extra'; +import release from '../release/deployment-oft.json'; + +// Network-specific LayerZero endpoints +const lzEndpoints: { [key: string]: string } = { + 'development-celo': '0x1a44076050125825900e736c501f859c50fE728c', + 'production-celo': '0x1a44076050125825900e736c501f859c50fE728c', + 'development-xdc': '0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa', + 'production-xdc': '0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa', +}; + +const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { + const { deployments, network, getNamedAccounts } = hre; + const { deployer } = await getNamedAccounts(); + const [root] = await ethers.getSigners(); + + const networkName = network.name; + + console.log('Deployment signer:', { + networkName, + deployer, + root: root.address, + balance: await ethers.provider.getBalance(root.address).then((_) => _.toString()), + }); + + // Get contract addresses from GoodProtocol deployment + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + if (!goodProtocolContracts) { + throw new Error( + `No GoodProtocol contracts found for network ${networkName}. Please check @gooddollar/goodprotocol/releases/deployment.json`, + ); + } + + // Get token address from GoodProtocol + const tokenAddress = goodProtocolContracts.GoodDollar; + if (!tokenAddress) { + throw new Error( + `Token address not found in GoodProtocol deployment for network ${networkName}. Please deploy SuperGoodDollar or GoodDollar first.`, + ); + } + + // Get NameService for DAO integration from GoodProtocol + const nameServiceAddress = goodProtocolContracts.NameService; + if (!nameServiceAddress) { + throw new Error( + `NameService address not found in GoodProtocol deployment for network ${networkName}. Please deploy NameService first.`, + ); + } + + // Get Controller address directly from GoodProtocol contracts (or via NameService if needed) + let controllerAddress = goodProtocolContracts.Controller; + if (!controllerAddress) { + // Fallback: try to get Controller via NameService interface + const INameService = await ethers.getContractAt( + '@gooddollar/goodprotocol/contracts/Interfaces.sol:INameService', + nameServiceAddress, + ); + controllerAddress = await INameService.getAddress('CONTROLLER'); + if (!controllerAddress || controllerAddress === ethers.constants.AddressZero) { + throw new Error(`Controller address not found in GoodProtocol deployment for network ${networkName}`); + } + } + + // Get LayerZero endpoint + const lzEndpoint = lzEndpoints[networkName] || process.env.LAYERZERO_ENDPOINT; + if (!lzEndpoint) { + throw new Error( + `LayerZero endpoint not found. Please set LAYERZERO_ENDPOINT environment variable or add default for network ${networkName}`, + ); + } + + console.log('Deployment parameters:', { + tokenAddress, + nameServiceAddress, + controllerAddress, + lzEndpoint, + networkName, + }); + + // Get Controller and Avatar addresses (used for OFT adapter owner) + const Controller = await ethers.getContractAt('Controller', controllerAddress); + const avatarAddress = await Controller.avatar(); + + if (!avatarAddress || avatarAddress === ethers.constants.AddressZero) { + throw new Error(`Avatar address is invalid: ${avatarAddress}`); + } + console.log('✅ Verified Avatar address:', avatarAddress); + + // Get current deployment state + const currentRelease = release[networkName] || {}; + + // Deploy GoodDollarMinterBurner (upgradeable) + let minterBurnerAddress: string; + if (!currentRelease.GoodDollarMinterBurner) { + console.log('Deploying GoodDollarMinterBurner as upgradeable contract...'); + const MinterBurnerFactory = await ethers.getContractFactory('GoodDollarMinterBurner'); + const MinterBurner = await upgrades.deployProxy(MinterBurnerFactory, [nameServiceAddress], { + kind: 'uups', + initializer: 'initialize', + }); + await MinterBurner.deployed(); + minterBurnerAddress = MinterBurner.address; + console.log('GoodDollarMinterBurner deployed to:', minterBurnerAddress); + + // Save to hardhat-deploy + await deployments.save('GoodDollarMinterBurner', { + address: minterBurnerAddress, + abi: MinterBurnerFactory.interface.format(ethers.utils.FormatTypes.json) as any, + }); + + // Update release file + if (!release[networkName]) { + release[networkName] = {}; + } + release[networkName].GoodDollarMinterBurner = minterBurnerAddress; + await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); + } else { + minterBurnerAddress = currentRelease.GoodDollarMinterBurner; + console.log('GoodDollarMinterBurner already deployed at:', minterBurnerAddress); + } + + // Deploy GoodDollarOFTAdapter (upgradeable via proxy) + // Constructor takes (token, lzEndpoint) - initialize() is called automatically by proxy + let oftAdapterAddress: string; + if (!currentRelease.GoodDollarOFTAdapter) { + console.log('Deploying GoodDollarOFTAdapter as upgradeable proxy...'); + console.log('Constructor parameters: token, lzEndpoint'); + console.log('Initialize parameters: token, minterBurner, owner, feeRecipient'); + + const OFTAdapterFactory = await ethers.getContractFactory('GoodDollarOFTAdapter'); + + // Create UUPS proxy with constructor args and initialize + console.log('Deploying proxy and initializing...'); + const OFTAdapter = await upgrades.deployProxy( + OFTAdapterFactory, + [tokenAddress, minterBurnerAddress, root.address, root.address], + { + kind: 'uups', + initializer: 'initialize', + unsafeAllow: ['constructor', 'state-variable-immutable', 'duplicate-initializer-call'], + constructorArgs: [tokenAddress, lzEndpoint], + }, + ); + await OFTAdapter.deployed(); + oftAdapterAddress = OFTAdapter.address; + console.log('✅ GoodDollarOFTAdapter proxy deployed to:', oftAdapterAddress); + console.log('Fee recipient:', root.address); + + // Save to hardhat-deploy + await deployments.save('GoodDollarOFTAdapter', { + address: oftAdapterAddress, + abi: OFTAdapterFactory.interface.format(ethers.utils.FormatTypes.json) as any, + }); + + // Update release file + release[networkName].GoodDollarOFTAdapter = oftAdapterAddress; + await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); + } else { + oftAdapterAddress = currentRelease.GoodDollarOFTAdapter; + console.log('GoodDollarOFTAdapter already deployed at:', oftAdapterAddress); + } + + console.log('\n=== Deployment Summary ==='); + console.log('Network:', networkName); + console.log('GoodDollarMinterBurner:', minterBurnerAddress, '(upgradeable)'); + console.log('GoodDollarOFTAdapter:', oftAdapterAddress, '(upgradeable)'); + console.log('Token:', tokenAddress); + console.log('OFT Adapter Owner (Avatar):', avatarAddress); + console.log('LayerZero Endpoint:', lzEndpoint); + console.log('========================\n'); +}; + +export default func; +func.tags = ['OFT', 'GoodDollar']; + diff --git a/packages/bridge-contracts/scripts/oft/oft-deploy.ts b/packages/bridge-contracts/scripts/oft/oft-deploy.ts deleted file mode 100644 index 7e913b2..0000000 --- a/packages/bridge-contracts/scripts/oft/oft-deploy.ts +++ /dev/null @@ -1,236 +0,0 @@ -/*** - * Deployment script for GoodDollar OFT (Omnichain Fungible Token) contracts - * - * Deploys: - * 1. GoodDollarMinterBurner - DAO-upgradeable contract that handles minting and burning of GoodDollar tokens for OFT - * 2. GoodDollarOFTAdapter - Upgradeable LayerZero OFT adapter that wraps GoodDollar token for cross-chain transfers - * - * Steps: - * 1. Deploy GoodDollarMinterBurner as upgradeable proxy with token address and NameService - * 2. Deploy GoodDollarOFTAdapter as upgradeable proxy with constructor(token, lzEndpoint), then initialize(token, minterBurner, lzEndpoint, owner, feeRecipient, nameService) - * 3. Set OFT adapter as operator on GoodDollarMinterBurner via DAO - */ - -import { network, ethers, upgrades } from "hardhat"; -import { Contract } from "ethers"; -import fse from "fs-extra"; -import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; -import release from "../../release/deployment-oft.json"; - -const { name: networkName } = network; - -export const deployOFTContracts = async () => { - const [root] = await ethers.getSigners(); - - console.log("got signers:", { - networkName, - root: root.address, - balance: await ethers.provider.getBalance(root.address).then(_ => _.toString()) - }); - - // Get contract addresses from GoodProtocol deployment - const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; - if (!goodProtocolContracts) { - throw new Error(`No GoodProtocol contracts found for network ${networkName}. Please check @gooddollar/goodprotocol/releases/deployment.json`); - } - - // Get token address from GoodProtocol - const tokenAddress = goodProtocolContracts.GoodDollar; - if (!tokenAddress) { - throw new Error(`Token address not found in GoodProtocol deployment for network ${networkName}. Please deploy SuperGoodDollar or GoodDollar first.`); - } - - // Get NameService for DAO integration from GoodProtocol - const nameServiceAddress = goodProtocolContracts.NameService; - if (!nameServiceAddress) { - throw new Error(`NameService address not found in GoodProtocol deployment for network ${networkName}. Please deploy NameService first.`); - } - - // Get Controller address directly from GoodProtocol contracts (or via NameService if needed) - let controllerAddress = goodProtocolContracts.Controller; - if (!controllerAddress) { - // Fallback: try to get Controller via NameService interface - // Use the interface path suggested by Hardhat error message - const INameService = await ethers.getContractAt("@gooddollar/goodprotocol/contracts/Interfaces.sol:INameService", nameServiceAddress); - controllerAddress = await INameService.getAddress("CONTROLLER"); - if (!controllerAddress || controllerAddress === ethers.constants.AddressZero) { - throw new Error(`Controller address not found in GoodProtocol deployment for network ${networkName}`); - } - } - - // Network-specific defaults (can be overridden via env var) - const lzEndpoints: { [key: string]: string } = { - "development-celo": "0x1a44076050125825900e736c501f859c50fE728c", - "production-celo": "0x1a44076050125825900e736c501f859c50fE728c", - "development-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", - "production-xdc": "0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa", - }; - const lzEndpoint = lzEndpoints[networkName]; - if (!lzEndpoint) { - throw new Error(`LayerZero endpoint not found. Please set LAYERZERO_ENDPOINT environment variable or add default for network ${networkName}`); - } - - console.log("Deployment parameters:", { - tokenAddress, - lzEndpoint, - networkName - }); - - // Get current deployment state - const currentRelease = release[networkName] || {}; - - // Deploy GoodDollarMinterBurner (upgradeable) - let MinterBurner: Contract; - if (!currentRelease.GoodDollarMinterBurner) { - console.log("Deploying GoodDollarMinterBurner as upgradeable contract..."); - const MinterBurnerFactory = await ethers.getContractFactory("GoodDollarMinterBurner"); - MinterBurner = await upgrades.deployProxy( - MinterBurnerFactory, - [nameServiceAddress], - { kind: "uups", initializer: "initialize" } - ); - await MinterBurner.deployed(); - console.log("GoodDollarMinterBurner deployed to:", MinterBurner.address); - - // Update release file - if (!release[networkName]) { - release[networkName] = {}; - } - release[networkName].GoodDollarMinterBurner = MinterBurner.address; - await fse.writeJSON("release/deployment-oft.json", release, { spaces: 2 }); - } else { - console.log("GoodDollarMinterBurner already deployed at:", currentRelease.GoodDollarMinterBurner); - MinterBurner = await ethers.getContractAt("GoodDollarMinterBurner", currentRelease.GoodDollarMinterBurner); - } - - // Get Controller and Avatar addresses (used for OFT adapter owner and operator setup) - const Controller = await ethers.getContractAt("Controller", controllerAddress); - const avatarAddress = await Controller.avatar(); - - // Get fee recipient (can be Avatar or address(0) to disable fees) - // Default to Avatar, but can be overridden via environment variable - const feeRecipient = avatarAddress; - - // Deploy GoodDollarOFTAdapter (upgradeable via proxy) - // Constructor takes (token, lzEndpoint) - initialize() is called automatically by proxy - let OFTAdapter: Contract; - if (!currentRelease.GoodDollarOFTAdapter) { - console.log("Deploying GoodDollarOFTAdapter as upgradeable proxy..."); - console.log("Constructor parameters: token, lzEndpoint"); - console.log("Initialize parameters: token, minterBurner, lzEndpoint, owner, feeRecipient, nameService"); - - const OFTAdapterFactory = await ethers.getContractFactory("GoodDollarOFTAdapter"); - - if (!avatarAddress || avatarAddress === ethers.constants.AddressZero) { - throw new Error(`Avatar address is invalid: ${avatarAddress}`); - } - console.log("✅ Verified Avatar address:", avatarAddress); - - // Create UUPS proxy manually using OpenZeppelin's ERC1967Proxy - // This follows the same pattern as upgrades.deployProxy but bypasses validation - console.log("Deploying proxy and initializing..."); - OFTAdapter = await upgrades.deployProxy( - OFTAdapterFactory, - [ - MinterBurner.address, - lzEndpoint, - root.address, - feeRecipient, - nameServiceAddress - ], - { - kind: "uups", - initializer: "initialize", - unsafeAllow: ["constructor", "state-variable-immutable", "missing-initializer-call", "duplicate-initializer-call"], - constructorArgs: [tokenAddress, lzEndpoint] - } - ); - await OFTAdapter.deployed(); - console.log("✅ GoodDollarOFTAdapter proxy deployed to:", OFTAdapter.address); - - // Update release file - release[networkName].GoodDollarOFTAdapter = OFTAdapter.address; - await fse.writeJSON("release/deployment-oft.json", release, { spaces: 2 }); - - console.log("Fee recipient:", feeRecipient); - } else { - console.log("GoodDollarOFTAdapter already deployed at:", currentRelease.GoodDollarOFTAdapter); - OFTAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", currentRelease.GoodDollarOFTAdapter); - } - - // Set OFT adapter as operator on MinterBurner if not already set - // This must be done via DAO governance since MinterBurner is DAO-controlled - const isOperator = await MinterBurner.operators(OFTAdapter.address); - - if (!isOperator) { - console.log("Setting OFT adapter as operator on MinterBurner via DAO..."); - console.log(` MinterBurner address: ${MinterBurner.address}`); - console.log(` OFTAdapter address: ${OFTAdapter.address}`); - - // Encode the setOperator function call - const setOperatorEncoded = MinterBurner.interface.encodeFunctionData("setOperator", [ - OFTAdapter.address, - true - ]); - - // Execute via Controller/Avatar - try { - const tx = await Controller.genericCall( - MinterBurner.address, - setOperatorEncoded, - avatarAddress, - 0 - ); - await tx.wait(); - console.log("✅ Successfully set OFT adapter as operator on MinterBurner"); - console.log("Transaction hash:", tx.hash); - - // Verify it was set - const isOperatorAfter = await MinterBurner.operators(OFTAdapter.address); - if (isOperatorAfter) { - console.log("✅ Verified: OFT adapter is now an operator"); - } else { - console.log("⚠️ Warning: Operator status not set. Please check the transaction."); - } - } catch (error: any) { - console.error("❌ Error setting operator:"); - if (error.message) { - console.error("Error message:", error.message); - } - if (error.reason) { - console.error("Reason:", error.reason); - } - throw error; - } - } else { - console.log("OFT adapter is already an operator on MinterBurner"); - } - - console.log("\n=== Deployment Summary ==="); - console.log("Network:", networkName); - console.log("GoodDollarMinterBurner:", MinterBurner.address, "(upgradeable)"); - console.log("GoodDollarOFTAdapter:", OFTAdapter.address, "(upgradeable)"); - console.log("Token:", tokenAddress); - console.log("OFT Adapter Owner (Avatar):", avatarAddress); - console.log("LayerZero Endpoint:", lzEndpoint); - console.log("========================\n"); - - return { - MinterBurner: MinterBurner.address, - OFTAdapter: OFTAdapter.address - }; -}; - -export const main = async () => { - await deployOFTContracts(); -}; - -if (require.main === module) { - main() - .then(() => process.exit(0)) - .catch((error) => { - console.error(error); - process.exit(1); - }); -} - diff --git a/packages/bridge-contracts/scripts/oft/set-oft-operator.ts b/packages/bridge-contracts/scripts/oft/set-oft-operator.ts new file mode 100644 index 0000000..2138829 --- /dev/null +++ b/packages/bridge-contracts/scripts/oft/set-oft-operator.ts @@ -0,0 +1,146 @@ +/*** + * Script to set OFT adapter as operator on GoodDollarMinterBurner via DAO governance + * + * This script must be run after deploying the OFT contracts. + * It sets the GoodDollarOFTAdapter as an operator on GoodDollarMinterBurner, + * which allows the adapter to mint and burn tokens for cross-chain transfers. + * + * This operation requires DAO governance since MinterBurner is DAO-controlled. + */ + +import { network, ethers } from 'hardhat'; +import Contracts from '@gooddollar/goodprotocol/releases/deployment.json'; +import release from '../../release/deployment-oft.json'; + +const { name: networkName } = network; + +export const setOFTOperator = async () => { + const [root] = await ethers.getSigners(); + + console.log('Setting OFT operator:', { + networkName, + root: root.address, + balance: await ethers.provider.getBalance(root.address).then((_) => _.toString()), + }); + + // Get contract addresses from GoodProtocol deployment + const goodProtocolContracts = Contracts[networkName as keyof typeof Contracts] as any; + if (!goodProtocolContracts) { + throw new Error( + `No GoodProtocol contracts found for network ${networkName}. Please check @gooddollar/goodprotocol/releases/deployment.json`, + ); + } + + // Get Controller address directly from GoodProtocol contracts (or via NameService if needed) + let controllerAddress = goodProtocolContracts.Controller; + if (!controllerAddress) { + // Fallback: try to get Controller via NameService interface + const nameServiceAddress = goodProtocolContracts.NameService; + if (!nameServiceAddress) { + throw new Error( + `NameService address not found in GoodProtocol deployment for network ${networkName}. Please deploy NameService first.`, + ); + } + const INameService = await ethers.getContractAt( + '@gooddollar/goodprotocol/contracts/Interfaces.sol:INameService', + nameServiceAddress, + ); + controllerAddress = await INameService.getAddress('CONTROLLER'); + if (!controllerAddress || controllerAddress === ethers.constants.AddressZero) { + throw new Error(`Controller address not found in GoodProtocol deployment for network ${networkName}`); + } + } + + // Get deployed contract addresses + const currentRelease = release[networkName] || {}; + const minterBurnerAddress = currentRelease.GoodDollarMinterBurner; + const oftAdapterAddress = currentRelease.GoodDollarOFTAdapter; + + if (!minterBurnerAddress) { + throw new Error( + `GoodDollarMinterBurner not found in deployment for network ${networkName}. Please deploy OFT contracts first.`, + ); + } + + if (!oftAdapterAddress) { + throw new Error( + `GoodDollarOFTAdapter not found in deployment for network ${networkName}. Please deploy OFT contracts first.`, + ); + } + + console.log('Contract addresses:', { + MinterBurner: minterBurnerAddress, + OFTAdapter: oftAdapterAddress, + }); + + // Get Controller and Avatar addresses + const Controller = await ethers.getContractAt('Controller', controllerAddress); + const avatarAddress = await Controller.avatar(); + + if (!avatarAddress || avatarAddress === ethers.constants.AddressZero) { + throw new Error(`Avatar address is invalid: ${avatarAddress}`); + } + + // Get MinterBurner contract + const MinterBurner = await ethers.getContractAt('GoodDollarMinterBurner', minterBurnerAddress); + + // Check if OFT adapter is already an operator + const isOperator = await MinterBurner.operators(oftAdapterAddress); + + if (!isOperator) { + console.log('Setting OFT adapter as operator on MinterBurner via DAO...'); + console.log(` MinterBurner address: ${minterBurnerAddress}`); + console.log(` OFTAdapter address: ${oftAdapterAddress}`); + + // Encode the setOperator function call + const setOperatorEncoded = MinterBurner.interface.encodeFunctionData('setOperator', [oftAdapterAddress, true]); + + // Execute via Controller/Avatar + try { + const tx = await Controller.genericCall(minterBurnerAddress, setOperatorEncoded, avatarAddress, 0); + await tx.wait(); + console.log('✅ Successfully set OFT adapter as operator on MinterBurner'); + console.log('Transaction hash:', tx.hash); + + // Verify it was set + const isOperatorAfter = await MinterBurner.operators(oftAdapterAddress); + if (isOperatorAfter) { + console.log('✅ Verified: OFT adapter is now an operator'); + } else { + console.log('⚠️ Warning: Operator status not set. Please check the transaction.'); + } + } catch (error: any) { + console.error('❌ Error setting operator:'); + if (error.message) { + console.error('Error message:', error.message); + } + if (error.reason) { + console.error('Reason:', error.reason); + } + throw error; + } + } else { + console.log('✅ OFT adapter is already an operator on MinterBurner'); + } + + console.log('\n=== Operator Setup Summary ==='); + console.log('Network:', networkName); + console.log('GoodDollarMinterBurner:', minterBurnerAddress); + console.log('GoodDollarOFTAdapter:', oftAdapterAddress); + console.log('Operator Status:', isOperator ? 'Already set' : 'Set successfully'); + console.log('==============================\n'); +}; + +export const main = async () => { + await setOFTOperator(); +}; + +if (require.main === module) { + main() + .then(() => process.exit(0)) + .catch((error) => { + console.error(error); + process.exit(1); + }); +} + From dcfc3fb1502abb014b8b1f433cd693337963e252 Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 10 Feb 2026 09:03:35 -0500 Subject: [PATCH 21/41] fix: update contract path in grant-minter-role script to reflect new directory structure --- packages/bridge-contracts/scripts/oft/grant-minter-role.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts b/packages/bridge-contracts/scripts/oft/grant-minter-role.ts index 69a02c7..f7b24f0 100644 --- a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts +++ b/packages/bridge-contracts/scripts/oft/grant-minter-role.ts @@ -61,7 +61,7 @@ const main = async () => { // Get token contract to check current status // Use the local interface file const token = await ethers.getContractAt( - "interfaces/ISuperGoodDollar.sol:ISuperGoodDollar", + "contracts/oft/interfaces/ISuperGoodDollar.sol:ISuperGoodDollar", tokenAddress ); From 866e1d1cb76cd9fc397cfb3bb1c95e2d45ededfc Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 10 Feb 2026 09:05:17 -0500 Subject: [PATCH 22/41] feat: add deployment configuration for OFT contracts and update deployment scripts for improved clarity and functionality --- .../release/deployment-oft-old.json | 11 +++ .../release/deployment-oft.json | 13 ++- .../scripts/oft/OFT_CONFIGURING_GUIDE.md | 74 +++++++++------ .../scripts/oft/configure-oft-xdc-celo.sh | 89 +++++++++++-------- .../scripts/oft/oft.config.json | 12 +-- 5 files changed, 123 insertions(+), 76 deletions(-) create mode 100644 packages/bridge-contracts/release/deployment-oft-old.json diff --git a/packages/bridge-contracts/release/deployment-oft-old.json b/packages/bridge-contracts/release/deployment-oft-old.json new file mode 100644 index 0000000..498a290 --- /dev/null +++ b/packages/bridge-contracts/release/deployment-oft-old.json @@ -0,0 +1,11 @@ +{ + "development-celo": { + "GoodDollarMinterBurner": "0x3850786C7627Ce276EF6F715a0eF31d5FeCf1fc7", + "GoodDollarOFTAdapter": "0x28aE7CE2F3BBb0bf58B24F1961885aeE1e456f46" + }, + "development-xdc": { + "GoodDollarMinterBurner": "0xb20eaE658437b793257C326751a695ED5e94dD3E", + "GoodDollarOFTAdapter": "0x3a568CFD4e3F6ef9f5e341FAbAD84B6Cc41C1A75" + } +} + diff --git a/packages/bridge-contracts/release/deployment-oft.json b/packages/bridge-contracts/release/deployment-oft.json index 498a290..83bebc2 100644 --- a/packages/bridge-contracts/release/deployment-oft.json +++ b/packages/bridge-contracts/release/deployment-oft.json @@ -1,11 +1,10 @@ { - "development-celo": { - "GoodDollarMinterBurner": "0x3850786C7627Ce276EF6F715a0eF31d5FeCf1fc7", - "GoodDollarOFTAdapter": "0x28aE7CE2F3BBb0bf58B24F1961885aeE1e456f46" - }, "development-xdc": { - "GoodDollarMinterBurner": "0xb20eaE658437b793257C326751a695ED5e94dD3E", - "GoodDollarOFTAdapter": "0x3a568CFD4e3F6ef9f5e341FAbAD84B6Cc41C1A75" + "GoodDollarMinterBurner": "0xB62C8fc044e5B0A376a2a1d084e4d335Ff7B9F87", + "GoodDollarOFTAdapter": "0x6307910da2B34A1b75C5ACC52Fffaa526ce4F004" + }, + "development-celo": { + "GoodDollarMinterBurner": "0x2A3bfB662B4dd9Ff9B929d013690b1cc434098Ee", + "GoodDollarOFTAdapter": "0x981bc8E7E2D08A215A1BE09D5490032E3B90338E" } } - diff --git a/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md b/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md index 4c2e9b1..e715d61 100644 --- a/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md +++ b/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md @@ -61,23 +61,44 @@ If you prefer to configure each network individually or need more control, follo ### Step 1: Deploy OFT Contracts -Deploy the GoodDollarMinterBurner and GoodDollarOFTAdapter contracts on each network: +Deploy the GoodDollarMinterBurner and GoodDollarOFTAdapter contracts on each network using hardhat-deploy: ```bash # Deploy on XDC -yarn hardhat run scripts/oft/oft-deploy.ts --network development-xdc +npx hardhat deploy --tags OFT --network development-xdc # Deploy on CELO -yarn hardhat run scripts/oft/oft-deploy.ts --network development-celo +npx hardhat deploy --tags OFT --network development-celo ``` -This script will: +This deployment script will: - Deploy `GoodDollarMinterBurner` (upgradeable proxy) - Deploy `GoodDollarOFTAdapter` (upgradeable proxy) -- Set OFT adapter as operator on MinterBurner via DAO - Save contract addresses to `release/deployment-oft.json` +- Save deployments to hardhat-deploy's deployment system -### Step 2: Grant MINTER_ROLE +**Note**: The deployment uses hardhat-deploy for better deployment management and tracking. + +### Step 2: Set OFT Adapter as Operator + +Set the OFT adapter as an operator on the MinterBurner contract via DAO governance: + +```bash +# Set operator on XDC +npx hardhat run scripts/oft/set-oft-operator.ts --network development-xdc + +# Set operator on CELO +npx hardhat run scripts/oft/set-oft-operator.ts --network development-celo +``` + +This script: +- Reads contract addresses from `release/deployment-oft.json` +- Sets the GoodDollarOFTAdapter as an operator on GoodDollarMinterBurner +- Executes via DAO governance (Controller/Avatar) since MinterBurner is DAO-controlled + +**Note**: This step must be run after deployment and is required for the OFT adapter to mint and burn tokens. + +### Step 3: Grant MINTER_ROLE Grant the MINTER_ROLE to GoodDollarMinterBurner on the GoodDollar token: @@ -91,7 +112,7 @@ yarn hardhat run scripts/oft/grant-minter-role.ts --network development-celo This executes via DAO governance (Controller/Avatar) to grant the minter role. -### Step 3: Wire LayerZero Connections +### Step 4: Wire LayerZero Connections Configure LayerZero messaging libraries, DVNs, executors, and enforced options: @@ -107,7 +128,7 @@ yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network developm - Wiring may fail with permission errors (0xc4c52593) if the OApp owner doesn't have delegate permissions on the LayerZero endpoint - If wiring fails, you may need to manually configure enforced options or contact LayerZero support -### Step 4: Set Bridge Limits +### Step 5: Set Bridge Limits Configure bridge limits using values from `oft.config.json`: @@ -121,20 +142,6 @@ yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-c The script reads limit values from `oft.config.json` for the specified network and sets them on the OFT adapter. -### Step 5: Transfer Ownership - -Transfer OFT adapter ownership to DAO Avatar: - -```bash -# Transfer on XDC -yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-xdc - -# Transfer on CELO -yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-celo -``` - -**Note**: This must be done by the current owner of the OFT adapter (usually the deployer). - ### Step 6: Test Bridge Functionality (Optional) Test the bridge by sending tokens from one chain to another: @@ -152,15 +159,30 @@ yarn hardhat run scripts/oft/bridge-oft-token.ts --network development-celo - Sufficient native token (XDC/CELO) for gas and LayerZero fees - MinterBurner approval for token burning +### Step 7: Transfer Ownership (Last Step) + +Transfer OFT adapter ownership to DAO Avatar. This should be done as the final step: + +```bash +# Transfer on XDC +yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-xdc + +# Transfer on CELO +yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-celo +``` + +**Note**: This must be done by the current owner of the OFT adapter (usually the deployer). This step is performed last to ensure all configuration is complete before transferring ownership to the DAO. + ## Configuration Verification After configuration, verify the setup: 1. **Check contract deployments**: Verify addresses in `release/deployment-oft.json` -2. **Check MINTER_ROLE**: Verify MinterBurner has minter role on GoodDollar token -3. **Check ownership**: Verify OFT adapter is owned by DAO Avatar -4. **Check LayerZero peers**: Verify peer connections are set between chains -5. **Check limits**: Verify bridge limits are set correctly +2. **Check operator status**: Verify OFT adapter is set as operator on MinterBurner +3. **Check MINTER_ROLE**: Verify MinterBurner has minter role on GoodDollar token +4. **Check ownership**: Verify OFT adapter is owned by DAO Avatar +5. **Check LayerZero peers**: Verify peer connections are set between chains +6. **Check limits**: Verify bridge limits are set correctly ## Troubleshooting diff --git a/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh b/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh index cfd912c..7459b3f 100755 --- a/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh +++ b/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh @@ -4,11 +4,12 @@ # # This script automates the complete OFT setup process: # 1. Deploy OFT contracts (MinterBurner and OFTAdapter) on both networks -# 2. Wire LayerZero connections between XDC and CELO +# 2. Set OFT adapter as operator on MinterBurner via DAO on both networks # 3. Grant MINTER_ROLE to MinterBurner on both networks -# 4. Transfer OFT adapter ownership to DAO Avatar on both networks +# 4. Wire LayerZero connections between XDC and CELO # 5. Set mint/burn limits on MinterBurner for both networks -# 6. Test bridge functionality (optional, last step) +# 6. Test bridge functionality (optional) +# 7. Transfer OFT adapter ownership to DAO Avatar on both networks (last step) # # Usage: # ./scripts/multichain-deploy/oft/configure-oft-xdc-celo.sh @@ -67,24 +68,37 @@ echo "" print_step "Step 1: Deploying OFT contracts" print_step "Deploying on development-xdc..." -yarn hardhat run scripts/oft/oft-deploy.ts --network development-xdc +npx hardhat deploy --tags OFT --network development-xdc print_success "OFT contracts deployed on development-xdc" print_step "Deploying on development-celo..." -yarn hardhat run scripts/oft/oft-deploy.ts --network development-celo +npx hardhat deploy --tags OFT --network development-celo print_success "OFT contracts deployed on development-celo" echo "" -# Step 2: Grant MINTER_ROLE -print_step "Step 2: Granting MINTER_ROLE to GoodDollarMinterBurner" +# Step 2: Set OFT adapter as operator on MinterBurner +print_step "Step 2: Setting OFT adapter as operator on MinterBurner" + +print_step "Setting operator on development-xdc..." +npx hardhat run scripts/oft/set-oft-operator.ts --network development-xdc +print_success "Operator set on development-xdc" + +print_step "Setting operator on development-celo..." +npx hardhat run scripts/oft/set-oft-operator.ts --network development-celo +print_success "Operator set on development-celo" + +echo "" + +# Step 3: Grant MINTER_ROLE +print_step "Step 3: Granting MINTER_ROLE to GoodDollarMinterBurner" print_step "Granting MINTER_ROLE on development-xdc..." -yarn hardhat run scripts/oft/grant-minter-role.ts --network development-xdc +npx hardhat run scripts/oft/grant-minter-role.ts --network development-xdc print_success "MINTER_ROLE granted on development-xdc" print_step "Granting MINTER_ROLE on development-celo..." -yarn hardhat run scripts/oft/grant-minter-role.ts --network development-celo +npx hardhat run scripts/oft/grant-minter-role.ts --network development-celo print_success "MINTER_ROLE granted on development-celo" echo "" @@ -93,27 +107,27 @@ echo "" # print_step "Step 4: Setting LayerZero peer connections" # print_step "Setting peer on development-xdc..." -# yarn hardhat run scripts/oft/set-layerzero-peers.ts --network development-xdc +# npx hardhat run scripts/oft/set-layerzero-peers.ts --network development-xdc # print_success "Peer set on development-xdc" # print_step "Setting peer on development-celo..." -# yarn hardhat run scripts/oft/set-layerzero-peers.ts --network development-celo +# npx hardhat run scripts/oft/set-layerzero-peers.ts --network development-celo # print_success "Peer set on development-celo" # echo "" -# Step 5: Wire LayerZero connections (optional - peers already set) +# Step 4: Wire LayerZero connections (optional - peers already set) WIRE_XDC_SUCCESS=false WIRE_CELO_SUCCESS=false if [ "$SKIP_WIRING" != "true" ]; then - print_step "Step 5: Wiring LayerZero connections" + print_step "Step 4: Wiring LayerZero connections" print_warning "Note: Peers are already set. Wiring configures send libraries, DVNs, executors, and enforced options." print_warning "If wiring fails due to permissions, we'll set enforced options manually as a fallback." print_step "Wiring on development-xdc..." set +e # Temporarily disable exit on error - yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-xdc + npx hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-xdc WIRE_XDC_STATUS=$? set -e # Re-enable exit on error if [ $WIRE_XDC_STATUS -eq 0 ]; then @@ -126,7 +140,7 @@ if [ "$SKIP_WIRING" != "true" ]; then print_step "Wiring on development-celo..." set +e # Temporarily disable exit on error - yarn hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-celo + npx hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-celo WIRE_CELO_STATUS=$? set -e # Re-enable exit on error if [ $WIRE_CELO_STATUS -eq 0 ]; then @@ -140,9 +154,9 @@ else print_warning "Skipping wiring step (SKIP_WIRING=true)" fi -# Step 6: Set bridge limits (optional) +# Step 5: Set bridge limits (optional) if [ "$SKIP_LIMITS" != "true" ]; then - print_step "Step 6: Setting bridge limits on OFTAdapter" + print_step "Step 5: Setting bridge limits on OFTAdapter" if [ -z "$DAILY_LIMIT" ] && [ -z "$TX_LIMIT" ] && [ -z "$ACCOUNT_DAILY_LIMIT" ] && [ -z "$MIN_AMOUNT" ] && [ -z "$ONLY_WHITELISTED" ]; then print_warning "No limit environment variables set. Skipping limits configuration." @@ -160,7 +174,7 @@ if [ "$SKIP_LIMITS" != "true" ]; then ACCOUNT_DAILY_LIMIT=$ACCOUNT_DAILY_LIMIT \ MIN_AMOUNT=$MIN_AMOUNT \ ONLY_WHITELISTED=$ONLY_WHITELISTED \ - yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-xdc + npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-xdc print_success "Bridge limits set on development-xdc" print_step "Setting bridge limits on development-celo..." @@ -169,7 +183,7 @@ if [ "$SKIP_LIMITS" != "true" ]; then ACCOUNT_DAILY_LIMIT=$ACCOUNT_DAILY_LIMIT \ MIN_AMOUNT=$MIN_AMOUNT \ ONLY_WHITELISTED=$ONLY_WHITELISTED \ - yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo + npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo print_success "Bridge limits set on development-celo" fi echo "" @@ -178,28 +192,15 @@ else echo "" fi -# Step 3: Transfer ownership (required before setting peers) -print_step "Step 3: Transferring OFT adapter ownership to DAO Avatar" - -print_step "Transferring ownership on development-xdc..." -yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-xdc -print_success "Ownership transferred on development-xdc" - -print_step "Transferring ownership on development-celo..." -yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-celo -print_success "Ownership transferred on development-celo" - -echo "" - -# Step 7: Test bridge (optional, last step) +# Step 6: Test bridge (optional) if [ "$SKIP_BRIDGE_TEST" != "true" ]; then - print_step "Step 7: Testing bridge functionality" + print_step "Step 6: Testing bridge functionality" print_warning "This step will attempt to bridge 1 G$ from XDC to CELO" read -p "Do you want to test the bridge? (y/N): " -n 1 -r echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then print_step "Bridging from development-xdc to development-celo..." - yarn hardhat run scripts/oft/bridge-oft-token.ts --network development-xdc || print_warning "Bridge test failed (this is okay if you don't have sufficient balance)" + npx hardhat run scripts/oft/bridge-oft-token.ts --network development-xdc || print_warning "Bridge test failed (this is okay if you don't have sufficient balance)" echo "" else print_warning "Skipping bridge test" @@ -210,14 +211,27 @@ else echo "" fi +# Step 7: Transfer ownership (last step) +print_step "Step 7: Transferring OFT adapter ownership to DAO Avatar" + +print_step "Transferring ownership on development-xdc..." +npx hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-xdc +print_success "Ownership transferred on development-xdc" + +print_step "Transferring ownership on development-celo..." +npx hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-celo +print_success "Ownership transferred on development-celo" + +echo "" + # Summary print_step "Configuration Complete!" print_success "OFT has been successfully configured on both XDC and CELO networks" echo "" echo "Summary of completed steps:" echo " ✅ Deployed OFT contracts on both networks" +echo " ✅ Set OFT adapter as operator on MinterBurner" echo " ✅ Granted MINTER_ROLE to MinterBurner" -echo " ✅ Transferred OFT adapter ownership to DAO Avatar" echo " ✅ Set LayerZero peer connections" if [ "$SKIP_WIRING" != "true" ]; then if [ "$WIRE_XDC_SUCCESS" = "true" ] && [ "$WIRE_CELO_SUCCESS" = "true" ]; then @@ -233,7 +247,8 @@ fi if [ "$SKIP_BRIDGE_TEST" != "true" ]; then echo " ✅ Tested bridge functionality (if executed)" fi +echo " ✅ Transferred OFT adapter ownership to DAO Avatar" echo "" print_success "You can now use the bridge-oft-token.ts script to bridge tokens between chains!" -print_success "Run: yarn hardhat run scripts/oft/bridge-oft-token.ts --network " +print_success "Run: npx hardhat run scripts/oft/bridge-oft-token.ts --network " diff --git a/packages/bridge-contracts/scripts/oft/oft.config.json b/packages/bridge-contracts/scripts/oft/oft.config.json index db51c2e..3d64cf6 100644 --- a/packages/bridge-contracts/scripts/oft/oft.config.json +++ b/packages/bridge-contracts/scripts/oft/oft.config.json @@ -5,9 +5,9 @@ "skipLimits": false, "skipBridgeTest": true, "limits": { - "dailyLimit": "5000", - "txLimit": "1000", - "accountDailyLimit": "1000", + "dailyLimit": "1000", + "txLimit": "100", + "accountDailyLimit": "100", "minAmount": "1", "onlyWhitelisted": false } @@ -18,9 +18,9 @@ "skipLimits": false, "skipBridgeTest": true, "limits": { - "dailyLimit": "5000", - "txLimit": "1000", - "accountDailyLimit": "1000", + "dailyLimit": "1000", + "txLimit": "100", + "accountDailyLimit": "100", "minAmount": "1", "onlyWhitelisted": false } From 6ccca468513bbcc3109db3c43568a5bad472d65b Mon Sep 17 00:00:00 2001 From: blueogin Date: Wed, 11 Feb 2026 08:49:57 -0500 Subject: [PATCH 23/41] refactor: replace request approval mechanism with failed receive request handling in GoodDollarOFTAdapter, enhancing error management and contract clarity --- .../contracts/oft/GoodDollarOFTAdapter.sol | 90 +++++++++---------- 1 file changed, 40 insertions(+), 50 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index 68337db..c2ed87c 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -68,13 +68,14 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { /// @dev Account-specific daily limit tracking mapping(address => AccountLimit) public accountsDailyLimit; - // struct Request { - // bool approved; - // uint256 amount; - // uint256 fromTimestamp; - // } - /// @dev A mapping for approved requests above limits - mapping(bytes32 => bool) public approvedRequests; + struct FailedReceiveRequest { + bool failed; + address toAddress; + uint256 amount; + uint32 srcEid; + } + /// @dev A mapping for failed requests + mapping(bytes32 => FailedReceiveRequest) public failedReceiveRequests; /// @dev A boolean for whether the bridge is closed bool public isClosed; @@ -106,11 +107,8 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { /// @dev Event emitted when bridge pause status changes event BridgePaused(bool isPaused); - /// @dev Event emitted when a request is approved - event RequestApproved(bytes32 indexed requestId, bool approved); - - /// @dev Event emitted when limits are bypassed by request ID - event LimitsBypassedByRequestId(address indexed account, bytes32 indexed requestId); + /// @dev Event emitted when a failed receive request is approved + event FailedReceiveRequestApproved(bytes32 indexed guid); /** * @dev Constructor for the upgradeable contract @@ -136,8 +134,7 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { function initialize( address _token, IMintableBurnable _minterBurner, - address _owner, - address _feeRecipient + address _owner ) public initializer { // Initialize parent contracts __UUPSUpgradeable_init(); @@ -154,7 +151,6 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { // Set state variables innerToken = IERC20(_token); minterBurner = _minterBurner; - feeRecipient = _feeRecipient; } /** @@ -232,19 +228,6 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { nameService = _nameService; } - /** - * @notice Function for approving requests above limits - */ - function approveRequest(bytes32 _requestId, bool approved/*, uint256 _amount, uint256 _fromTimestamp*/) external onlyOwner { - // approvedRequests[_requestId] = Request( - // approved, - // _amount == 0 ? type(uint256).max : _amount, - // _fromTimestamp == 0 ? block.timestamp : _fromTimestamp - // ); - approvedRequests[_requestId] = approved; - emit RequestApproved(_requestId, approved); - } - /** * @notice Function for pausing/unpausing the bridge * @param _isPaused Whether to pause the bridge or not @@ -254,6 +237,15 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { emit BridgePaused(_isPaused); } + function approveFailedRequest(bytes32 _guid) external onlyOwner { + FailedReceiveRequest memory request = failedReceiveRequests[_guid]; + require(request.failed, 'request not failed'); + _credit(request.toAddress, request.amount, request.srcEid); + request.failed = false; + delete failedReceiveRequests[_guid]; + emit FailedReceiveRequestApproved(_guid); + } + /** * @notice Check if bridging is allowed for a given address and amount * @param _from The address of the sender @@ -293,24 +285,11 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { return (true, ''); } - function _isRequestApproved(bytes32 _requestId, uint256 _amount) internal view returns (bool) { - return approvedRequests[_requestId] /*.approved && - approvedRequests[_requestId].fromTimestamp < block.timestamp && - approvedRequests[_requestId].amount >= _amount*/; - } /** * @notice Enforces transfer limits and checks if the transfer is valid - * @param _address The address to transfer from - * @param _amount The amount to transfer - * @param _requestId The request ID (0 to skip approval check) * @dev Limits are enforced on both sending and receiving sides */ - function _enforceLimits(address _address, uint256 _amount, bytes32 _requestId) internal { - if (_requestId != 0 && _isRequestApproved(_requestId, _amount)) { - emit LimitsBypassedByRequestId(_address, _requestId); - return; // skip approval check if request is approved - } - + function _enforceLimits(address _address, uint256 _amount) internal returns (bool isValid, string memory reason) { // Reset daily limits if needed if (bridgeDailyLimit.lastTransferReset < block.timestamp - 1 days) { bridgeDailyLimit.lastTransferReset = block.timestamp; @@ -323,12 +302,11 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { } // Check limits - (bool isValid, string memory reason) = canBridge(_address, _amount); - if (!isValid) revert BRIDGE_LIMITS(reason); - - // Update counters - bridgeDailyLimit.bridged24Hours += _amount; - accountsDailyLimit[_address].bridged24Hours += _amount; + (isValid, reason) = canBridge(_address, _amount); + if (isValid) { + bridgeDailyLimit.bridged24Hours += _amount; + accountsDailyLimit[_address].bridged24Hours += _amount; + } } /** @@ -339,8 +317,11 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { MessagingFee calldata _fee, address _refundAddress ) internal virtual override returns (MessagingReceipt memory msgReceipt, OFTReceipt memory oftReceipt) { + (bool isValid, string memory reason) = _enforceLimits(_sendParam.to.bytes32ToAddress(), _sendParam.amountLD); + if (!isValid) { + revert BRIDGE_LIMITS(reason); + } (msgReceipt, oftReceipt) = super._send(_sendParam, _fee, _refundAddress); - _enforceLimits(_sendParam.to.bytes32ToAddress(), _sendParam.amountLD, msgReceipt.guid); } /** @@ -353,7 +334,16 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { address _executor, bytes calldata _extraData ) internal virtual override { - _enforceLimits(_message.sendTo().bytes32ToAddress(), _toLD(_message.amountSD()), _guid); + (bool isValid, string memory reason) = _enforceLimits(_message.sendTo().bytes32ToAddress(), _toLD(_message.amountSD())); + if (!isValid) { + failedReceiveRequests[_guid] = FailedReceiveRequest( + true, + _message.sendTo().bytes32ToAddress(), + _toLD(_message.amountSD()), + _origin.srcEid + ); + revert BRIDGE_LIMITS(reason); + } super._lzReceive(_origin, _guid, _message, _executor, _extraData); } /** From 5f6ed35c867766c37096ed0f8f14d4e7cf9cf398 Mon Sep 17 00:00:00 2001 From: blueogin Date: Wed, 11 Feb 2026 09:01:53 -0500 Subject: [PATCH 24/41] feat: add feeRecipient parameter to GoodDollarOFTAdapter initialization and update tests for new constructor argument --- .../bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol | 4 +++- .../bridge-contracts/test/GoodDollarOFTAdapter.fork.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index c2ed87c..fac37ac 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -134,7 +134,8 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { function initialize( address _token, IMintableBurnable _minterBurner, - address _owner + address _owner, + address _feeRecipient ) public initializer { // Initialize parent contracts __UUPSUpgradeable_init(); @@ -151,6 +152,7 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { // Set state variables innerToken = IERC20(_token); minterBurner = _minterBurner; + feeRecipient = _feeRecipient; } /** diff --git a/packages/bridge-contracts/test/GoodDollarOFTAdapter.fork.test.ts b/packages/bridge-contracts/test/GoodDollarOFTAdapter.fork.test.ts index f73d539..7128810 100644 --- a/packages/bridge-contracts/test/GoodDollarOFTAdapter.fork.test.ts +++ b/packages/bridge-contracts/test/GoodDollarOFTAdapter.fork.test.ts @@ -79,7 +79,7 @@ describe('GoodDollarOFTAdapter Fork Tests (Celo Production)', () => { const AdapterFactory = await ethers.getContractFactory('GoodDollarOFTAdapter'); const adapter = (await upgrades.deployProxy( AdapterFactory, - [goodDollarAddress, minterBurner.address, owner.address], + [goodDollarAddress, minterBurner.address, owner.address, owner.address], { kind: 'uups', constructorArgs: [goodDollarAddress, CELO_LZ_ENDPOINT], @@ -120,7 +120,7 @@ describe('GoodDollarOFTAdapter Fork Tests (Celo Production)', () => { console.log('Deploying adapter...'); const adapter = (await upgrades.deployProxy( AdapterFactory, - [goodDollarAddress, minterBurner.address, owner.address], + [goodDollarAddress, minterBurner.address, owner.address, owner.address], { kind: 'uups', constructorArgs: [goodDollarAddress, CELO_LZ_ENDPOINT], From c859ba3d5be6b8ca53b8a6e99705298c84275616 Mon Sep 17 00:00:00 2001 From: blueogin Date: Wed, 11 Feb 2026 09:43:26 -0500 Subject: [PATCH 25/41] feat: enhance deployment scripts for GoodDollarMinterBurner and GoodDollarOFTAdapter with implementation address retrieval and verification functionality --- packages/bridge-contracts/deploy/deployOFT.ts | 58 +++++++++++- .../deploy/utils/verifyContract.ts | 92 +++++++++++++++++++ .../deployment-oft-20260211085500.json | 10 ++ .../release/deployment-oft.json | 8 +- 4 files changed, 161 insertions(+), 7 deletions(-) create mode 100644 packages/bridge-contracts/deploy/utils/verifyContract.ts create mode 100644 packages/bridge-contracts/release/deployment-oft-20260211085500.json diff --git a/packages/bridge-contracts/deploy/deployOFT.ts b/packages/bridge-contracts/deploy/deployOFT.ts index 4d9adb7..9aea28a 100644 --- a/packages/bridge-contracts/deploy/deployOFT.ts +++ b/packages/bridge-contracts/deploy/deployOFT.ts @@ -18,6 +18,8 @@ import { ethers, upgrades } from 'hardhat'; import Contracts from '@gooddollar/goodprotocol/releases/deployment.json'; import fse from 'fs-extra'; import release from '../release/deployment-oft.json'; +import { getImplementationAddress } from '@openzeppelin/upgrades-core'; +import { verifyContract } from './utils/verifyContract'; // Network-specific LayerZero endpoints const lzEndpoints: { [key: string]: string } = { @@ -28,15 +30,13 @@ const lzEndpoints: { [key: string]: string } = { }; const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { - const { deployments, network, getNamedAccounts } = hre; - const { deployer } = await getNamedAccounts(); + const { deployments, network } = hre; const [root] = await ethers.getSigners(); const networkName = network.name; console.log('Deployment signer:', { networkName, - deployer, root: root.address, balance: await ethers.provider.getBalance(root.address).then((_) => _.toString()), }); @@ -109,6 +109,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Deploy GoodDollarMinterBurner (upgradeable) let minterBurnerAddress: string; + let minterBurnerImplAddress: string | undefined; if (!currentRelease.GoodDollarMinterBurner) { console.log('Deploying GoodDollarMinterBurner as upgradeable contract...'); const MinterBurnerFactory = await ethers.getContractFactory('GoodDollarMinterBurner'); @@ -120,6 +121,10 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { minterBurnerAddress = MinterBurner.address; console.log('GoodDollarMinterBurner deployed to:', minterBurnerAddress); + // Get implementation address for verification + minterBurnerImplAddress = await getImplementationAddress(ethers.provider, minterBurnerAddress); + console.log('GoodDollarMinterBurner implementation address:', minterBurnerImplAddress); + // Save to hardhat-deploy await deployments.save('GoodDollarMinterBurner', { address: minterBurnerAddress, @@ -132,14 +137,28 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } release[networkName].GoodDollarMinterBurner = minterBurnerAddress; await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); + + // Verify GoodDollarMinterBurner implementation + // No constructor args - initialized via initialize() function + await verifyContract(hre, minterBurnerImplAddress, [], 'GoodDollarMinterBurner'); } else { minterBurnerAddress = currentRelease.GoodDollarMinterBurner; console.log('GoodDollarMinterBurner already deployed at:', minterBurnerAddress); + // Get implementation address even if already deployed (for verification if needed) + try { + minterBurnerImplAddress = await getImplementationAddress(ethers.provider, minterBurnerAddress); + console.log('GoodDollarMinterBurner implementation address:', minterBurnerImplAddress); + await verifyContract(hre, minterBurnerImplAddress, [], 'GoodDollarMinterBurner'); + console.log('GoodDollarMinterBurner verified successfully'); + } catch (error) { + console.log('⚠️ Could not get implementation address for GoodDollarMinterBurner'); + } } // Deploy GoodDollarOFTAdapter (upgradeable via proxy) // Constructor takes (token, lzEndpoint) - initialize() is called automatically by proxy let oftAdapterAddress: string; + let oftAdapterImplAddress: string | undefined; if (!currentRelease.GoodDollarOFTAdapter) { console.log('Deploying GoodDollarOFTAdapter as upgradeable proxy...'); console.log('Constructor parameters: token, lzEndpoint'); @@ -164,6 +183,10 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { console.log('✅ GoodDollarOFTAdapter proxy deployed to:', oftAdapterAddress); console.log('Fee recipient:', root.address); + // Get implementation address for verification + oftAdapterImplAddress = await getImplementationAddress(ethers.provider, oftAdapterAddress); + console.log('GoodDollarOFTAdapter implementation address:', oftAdapterImplAddress); + // Save to hardhat-deploy await deployments.save('GoodDollarOFTAdapter', { address: oftAdapterAddress, @@ -173,15 +196,44 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { // Update release file release[networkName].GoodDollarOFTAdapter = oftAdapterAddress; await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); + + // Verify GoodDollarOFTAdapter implementation + // Constructor args: tokenAddress, lzEndpoint + await verifyContract( + hre, + oftAdapterImplAddress, + [tokenAddress, lzEndpoint], + 'GoodDollarOFTAdapter', + ); } else { oftAdapterAddress = currentRelease.GoodDollarOFTAdapter; console.log('GoodDollarOFTAdapter already deployed at:', oftAdapterAddress); + // Get implementation address even if already deployed (for verification if needed) + try { + oftAdapterImplAddress = await getImplementationAddress(ethers.provider, oftAdapterAddress); + console.log('GoodDollarOFTAdapter implementation address:', oftAdapterImplAddress); + await verifyContract( + hre, + oftAdapterImplAddress, + [tokenAddress, lzEndpoint], + 'GoodDollarOFTAdapter', + ); + console.log('GoodDollarOFTAdapter verified successfully'); + } catch (error) { + console.log('⚠️ Could not get implementation address for GoodDollarOFTAdapter'); + } } console.log('\n=== Deployment Summary ==='); console.log('Network:', networkName); console.log('GoodDollarMinterBurner:', minterBurnerAddress, '(upgradeable)'); + if (minterBurnerImplAddress) { + console.log(' Implementation:', minterBurnerImplAddress); + } console.log('GoodDollarOFTAdapter:', oftAdapterAddress, '(upgradeable)'); + if (oftAdapterImplAddress) { + console.log(' Implementation:', oftAdapterImplAddress); + } console.log('Token:', tokenAddress); console.log('OFT Adapter Owner (Avatar):', avatarAddress); console.log('LayerZero Endpoint:', lzEndpoint); diff --git a/packages/bridge-contracts/deploy/utils/verifyContract.ts b/packages/bridge-contracts/deploy/utils/verifyContract.ts new file mode 100644 index 0000000..7b2867c --- /dev/null +++ b/packages/bridge-contracts/deploy/utils/verifyContract.ts @@ -0,0 +1,92 @@ +import { HardhatRuntimeEnvironment } from 'hardhat/types'; + +/** + * Verify a contract on block explorer (Etherscan, etc.) + * + * @param hre Hardhat runtime environment + * @param contractAddress The address of the contract to verify + * @param constructorArgs Optional constructor arguments array + * @param contractName Optional contract name for logging + * @returns Promise that resolves when verification is complete + */ +export async function verifyContract( + hre: HardhatRuntimeEnvironment, + contractAddress: string, + constructorArgs: any[] = [], + contractName?: string, +): Promise { + const networkName = hre.network.name; + const displayName = contractName || contractAddress; + + // Skip verification for local networks + if (['hardhat', 'localhost', 'develop'].includes(networkName)) { + console.log(`ℹ️ Skipping verification for ${displayName} on local network: ${networkName}`); + return; + } + + console.log(`\n🔍 Verifying ${displayName}...`); + console.log(` Address: ${contractAddress}`); + if (constructorArgs.length > 0) { + console.log(` Constructor args: ${JSON.stringify(constructorArgs)}`); + } + + try { + await hre.run('verify:verify', { + address: contractAddress, + constructorArguments: constructorArgs, + }); + console.log(`✅ ${displayName} verified successfully`); + } catch (error: any) { + const errorMessage = error.message || error.toString() || ''; + + if (errorMessage.includes('Already Verified') || errorMessage.includes('already verified')) { + console.log(`ℹ️ ${displayName} already verified`); + } else { + console.log(`⚠️ ${displayName} verification error: ${errorMessage}`); + console.log(` You can verify manually using:`); + if (constructorArgs.length > 0) { + console.log(` npx hardhat verify --network ${networkName} ${contractAddress} ${constructorArgs.join(' ')}`); + } else { + console.log(` npx hardhat verify --network ${networkName} ${contractAddress}`); + } + } + } +} + +/** + * Verify multiple contracts in sequence + * + * @param hre Hardhat runtime environment + * @param contracts Array of contract verification configs + */ +export async function verifyContracts( + hre: HardhatRuntimeEnvironment, + contracts: Array<{ + address: string; + constructorArgs?: any[]; + name?: string; + }>, +): Promise { + console.log(`\n=== Starting Contract Verification (${contracts.length} contracts) ===`); + + for (const contract of contracts) { + await verifyContract( + hre, + contract.address, + contract.constructorArgs || [], + contract.name, + ); + } + + // Also verify on Sourcify + console.log('\n🔍 Verifying on Sourcify...'); + try { + await hre.run('sourcify'); + console.log('✅ Sourcify verification completed'); + } catch (error: any) { + console.log('⚠️ Sourcify verification error:', error.message || error.toString()); + } + + console.log('=== Contract Verification Complete ===\n'); +} + diff --git a/packages/bridge-contracts/release/deployment-oft-20260211085500.json b/packages/bridge-contracts/release/deployment-oft-20260211085500.json new file mode 100644 index 0000000..83bebc2 --- /dev/null +++ b/packages/bridge-contracts/release/deployment-oft-20260211085500.json @@ -0,0 +1,10 @@ +{ + "development-xdc": { + "GoodDollarMinterBurner": "0xB62C8fc044e5B0A376a2a1d084e4d335Ff7B9F87", + "GoodDollarOFTAdapter": "0x6307910da2B34A1b75C5ACC52Fffaa526ce4F004" + }, + "development-celo": { + "GoodDollarMinterBurner": "0x2A3bfB662B4dd9Ff9B929d013690b1cc434098Ee", + "GoodDollarOFTAdapter": "0x981bc8E7E2D08A215A1BE09D5490032E3B90338E" + } +} diff --git a/packages/bridge-contracts/release/deployment-oft.json b/packages/bridge-contracts/release/deployment-oft.json index 83bebc2..1b63343 100644 --- a/packages/bridge-contracts/release/deployment-oft.json +++ b/packages/bridge-contracts/release/deployment-oft.json @@ -1,10 +1,10 @@ { "development-xdc": { - "GoodDollarMinterBurner": "0xB62C8fc044e5B0A376a2a1d084e4d335Ff7B9F87", - "GoodDollarOFTAdapter": "0x6307910da2B34A1b75C5ACC52Fffaa526ce4F004" + "GoodDollarMinterBurner": "0x647313034B26B22b9f1390C04935B9ea740A5Aa5", + "GoodDollarOFTAdapter": "0x14f65D919F482e21FD4EFB8FEc167C78B1100eeD" }, "development-celo": { - "GoodDollarMinterBurner": "0x2A3bfB662B4dd9Ff9B929d013690b1cc434098Ee", - "GoodDollarOFTAdapter": "0x981bc8E7E2D08A215A1BE09D5490032E3B90338E" + "GoodDollarMinterBurner": "0xe2C9B67053C36A4D8E1E8aC03359482049Ee82BD", + "GoodDollarOFTAdapter": "0x07373c71bc325c4886c9C7d53151A1d929c1bB65" } } From a1dc6aa75f9b7f07c0c7065ff5f5f253f93fb175 Mon Sep 17 00:00:00 2001 From: blueogin Date: Wed, 11 Feb 2026 10:24:02 -0500 Subject: [PATCH 26/41] feat: add ReceiveRequestFailed event to GoodDollarOFTAdapter for improved error handling in receive requests --- .../contracts/oft/GoodDollarOFTAdapter.sol | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index fac37ac..91ba36b 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -107,6 +107,9 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { /// @dev Event emitted when bridge pause status changes event BridgePaused(bool isPaused); + /// @dev Event emitted when a failed receive request is made + event ReceiveRequestFailed(bytes32 indexed guid, address toAddress, uint256 amount, uint32 srcEid); + /// @dev Event emitted when a failed receive request is approved event FailedReceiveRequestApproved(bytes32 indexed guid); @@ -344,9 +347,12 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { _toLD(_message.amountSD()), _origin.srcEid ); - revert BRIDGE_LIMITS(reason); + emit ReceiveRequestFailed(_guid, _message.sendTo().bytes32ToAddress(), _toLD(_message.amountSD()), _origin.srcEid); + // revert BRIDGE_LIMITS(reason); + } + else { + super._lzReceive(_origin, _guid, _message, _executor, _extraData); } - super._lzReceive(_origin, _guid, _message, _executor, _extraData); } /** * @notice Mints tokens to the specified address upon receiving them From ffbc78ce999d8f8669e20616b6c0ccaaccaa4a3d Mon Sep 17 00:00:00 2001 From: blueogin Date: Wed, 11 Feb 2026 11:17:24 -0500 Subject: [PATCH 27/41] feat: update deployment addresses for GoodDollarMinterBurner and GoodDollarOFTAdapter, and adjust bridging amount and transaction limits in configuration --- .../release/deployment-oft-2026021112.json | 10 ++++++++++ packages/bridge-contracts/release/deployment-oft.json | 8 ++++---- .../bridge-contracts/scripts/oft/bridge-oft-token.ts | 2 +- packages/bridge-contracts/scripts/oft/oft.config.json | 6 +++--- 4 files changed, 18 insertions(+), 8 deletions(-) create mode 100644 packages/bridge-contracts/release/deployment-oft-2026021112.json diff --git a/packages/bridge-contracts/release/deployment-oft-2026021112.json b/packages/bridge-contracts/release/deployment-oft-2026021112.json new file mode 100644 index 0000000..1b63343 --- /dev/null +++ b/packages/bridge-contracts/release/deployment-oft-2026021112.json @@ -0,0 +1,10 @@ +{ + "development-xdc": { + "GoodDollarMinterBurner": "0x647313034B26B22b9f1390C04935B9ea740A5Aa5", + "GoodDollarOFTAdapter": "0x14f65D919F482e21FD4EFB8FEc167C78B1100eeD" + }, + "development-celo": { + "GoodDollarMinterBurner": "0xe2C9B67053C36A4D8E1E8aC03359482049Ee82BD", + "GoodDollarOFTAdapter": "0x07373c71bc325c4886c9C7d53151A1d929c1bB65" + } +} diff --git a/packages/bridge-contracts/release/deployment-oft.json b/packages/bridge-contracts/release/deployment-oft.json index 1b63343..3222b9a 100644 --- a/packages/bridge-contracts/release/deployment-oft.json +++ b/packages/bridge-contracts/release/deployment-oft.json @@ -1,10 +1,10 @@ { "development-xdc": { - "GoodDollarMinterBurner": "0x647313034B26B22b9f1390C04935B9ea740A5Aa5", - "GoodDollarOFTAdapter": "0x14f65D919F482e21FD4EFB8FEc167C78B1100eeD" + "GoodDollarMinterBurner": "0xB937421FD27d8C7848Ae8da16cF2B9D2ba9b5A62", + "GoodDollarOFTAdapter": "0x8B72D79be1E2A8d4c82B9B86E539fB2f4b298C1c" }, "development-celo": { - "GoodDollarMinterBurner": "0xe2C9B67053C36A4D8E1E8aC03359482049Ee82BD", - "GoodDollarOFTAdapter": "0x07373c71bc325c4886c9C7d53151A1d929c1bB65" + "GoodDollarMinterBurner": "0x4952d1ab3956145c3411A3ebB32ed1Eece7BC89B", + "GoodDollarOFTAdapter": "0x4e2a6583ab861A64BE02865257eC4C6A310e4696" } } diff --git a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts b/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts index 1aadceb..80bf505 100644 --- a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts +++ b/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts @@ -100,7 +100,7 @@ const main = async () => { const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); // Amount to bridge: 1 G$ = 1e18 - const amount = ethers.utils.parseEther("1"); + const amount = ethers.utils.parseEther("50"); console.log("\nAmount to bridge:", ethers.utils.formatEther(amount), "G$"); // Check token balance diff --git a/packages/bridge-contracts/scripts/oft/oft.config.json b/packages/bridge-contracts/scripts/oft/oft.config.json index 3d64cf6..d17830d 100644 --- a/packages/bridge-contracts/scripts/oft/oft.config.json +++ b/packages/bridge-contracts/scripts/oft/oft.config.json @@ -19,9 +19,9 @@ "skipBridgeTest": true, "limits": { "dailyLimit": "1000", - "txLimit": "100", - "accountDailyLimit": "100", - "minAmount": "1", + "txLimit": "95", + "accountDailyLimit": "200", + "minAmount": "1", "onlyWhitelisted": false } }, From 836764b8ae119395f14f8153887c637eed669ae6 Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Mar 2026 10:51:00 -0500 Subject: [PATCH 28/41] feat: enhance GoodDollarOFTAdapter with optimistic window for failed requests, improved bridge checks, and refactored limit enforcement methods --- .../contracts/oft/GoodDollarOFTAdapter.sol | 107 ++++++++++++------ 1 file changed, 70 insertions(+), 37 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index 91ba36b..58dac73 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -71,12 +71,15 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { struct FailedReceiveRequest { bool failed; address toAddress; + uint64 timestamp; uint256 amount; uint32 srcEid; } /// @dev A mapping for failed requests mapping(bytes32 => FailedReceiveRequest) public failedReceiveRequests; + uint64 public constant OPTIMISTIC_WINDOW = 3 days; + /// @dev A boolean for whether the bridge is closed bool public isClosed; @@ -86,6 +89,9 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { /// @dev Error for bridge limits violations error BRIDGE_LIMITS(string reason); + /// @dev Error for bridge limits violations + error BRIDGE_NOT_ALLOWED(string reason); + /// @dev Event emitted when bridge fees are updated event BridgeFeesSet(uint256 minFee, uint256 maxFee, uint256 fee); @@ -244,23 +250,33 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { function approveFailedRequest(bytes32 _guid) external onlyOwner { FailedReceiveRequest memory request = failedReceiveRequests[_guid]; + require(request.timestamp + OPTIMISTIC_WINDOW < block.timestamp, 'optimistic period not ended'); require(request.failed, 'request not failed'); _credit(request.toAddress, request.amount, request.srcEid); - request.failed = false; delete failedReceiveRequests[_guid]; emit FailedReceiveRequestApproved(_guid); } /** - * @notice Check if bridging is allowed for a given address and amount - * @param _from The address of the sender - * @param _amount The amount to bridge - * @return isWithinLimit Whether the bridge is within the limit - * @return error The error message, if any + * @notice Bridge closed / whitelist check only (no limit checks). + * @dev Revert on this path does not store to failedReceiveRequests. */ - function canBridge(address _from, uint256 _amount) public view returns (bool isWithinLimit, string memory error) { + function _checkBridgeClosedAndWhitelisted(address _from) internal view returns (bool ok, string memory reason) { if (isClosed) return (false, 'closed'); + if (bridgeLimits.onlyWhitelisted && address(nameService) != address(0)) { + IIdentity id = IIdentity(nameService.getAddress("IDENTITY")); + if (address(id) != address(0) && !id.isWhitelisted(_from)) { + return (false, 'not whitelisted'); + } + } + return (true, ''); + } + /** + * @notice Bridge limits check only (minAmount, accountDailyLimit, txLimit, dailyLimit). + * @dev Assumes daily limit resets have already been applied. Failure on receive is stored in failedReceiveRequests. + */ + function _checkBridgeLimits(address _from, uint256 _amount) internal view returns (bool ok, string memory reason) { if (_amount < bridgeLimits.minAmount) return (false, 'minAmount'); uint256 account24hours = accountsDailyLimit[_from].bridged24Hours; @@ -269,14 +285,6 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { } else { account24hours += _amount; } - - if (bridgeLimits.onlyWhitelisted && address(nameService) != address(0)) { - IIdentity id = IIdentity(nameService.getAddress("IDENTITY")); - if (address(id) != address(0)) { - if (!id.isWhitelisted(_from)) return (false, 'not whitelisted'); - } - } - if (account24hours > bridgeLimits.accountDailyLimit) return (false, 'accountDailyLimit'); if (_amount > bridgeLimits.txLimit) return (false, 'txLimit'); @@ -286,16 +294,14 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { } else { if (bridgeDailyLimit.bridged24Hours + _amount > bridgeLimits.dailyLimit) return (false, 'dailyLimit'); } - return (true, ''); } /** - * @notice Enforces transfer limits and checks if the transfer is valid - * @dev Limits are enforced on both sending and receiving sides + * @notice Resets bridge and account daily limit counters if the 24h window has elapsed. + * @param _address The account address for which to reset account daily limit. */ - function _enforceLimits(address _address, uint256 _amount) internal returns (bool isValid, string memory reason) { - // Reset daily limits if needed + function _resetDailyLimitsIfNeeded(address _address) internal { if (bridgeDailyLimit.lastTransferReset < block.timestamp - 1 days) { bridgeDailyLimit.lastTransferReset = block.timestamp; bridgeDailyLimit.bridged24Hours = 0; @@ -305,13 +311,26 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { accountsDailyLimit[_address].lastTransferReset = block.timestamp; accountsDailyLimit[_address].bridged24Hours = 0; } + } - // Check limits - (isValid, reason) = canBridge(_address, _amount); - if (isValid) { - bridgeDailyLimit.bridged24Hours += _amount; - accountsDailyLimit[_address].bridged24Hours += _amount; - } + /** + * @notice Enforces transfer limits: bridge closed/whitelisted check then bridge limits check. + * @dev Used on send path. Resets daily windows, then checks; on success updates counters. + */ + function _enforceLimits(address _address, uint256 _amount) internal returns (bool isValid, string memory reason) { + _resetDailyLimitsIfNeeded(_address); + + // Bridge closed / whitelisted check + (isValid, reason) = _checkBridgeClosedAndWhitelisted(_address); + if (!isValid) return (false, reason); + + // Bridge limits check + (isValid, reason) = _checkBridgeLimits(_address, _amount); + if (!isValid) return (false, reason); + + bridgeDailyLimit.bridged24Hours += _amount; + accountsDailyLimit[_address].bridged24Hours += _amount; + return (true, ''); } /** @@ -330,7 +349,8 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { } /** - * @notice Overrides the default _lzReceive function to enforce limits on receiving side + * @notice Overrides the default _lzReceive function to enforce limits on receiving side. + * @dev Bridge closed/whitelisted check: revert only. Bridge limits check: store in failedReceiveRequests then revert. */ function _lzReceive( Origin calldata _origin, @@ -339,20 +359,33 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { address _executor, bytes calldata _extraData ) internal virtual override { - (bool isValid, string memory reason) = _enforceLimits(_message.sendTo().bytes32ToAddress(), _toLD(_message.amountSD())); - if (!isValid) { + address toAddress = _message.sendTo().bytes32ToAddress(); + uint256 amountLD = _toLD(_message.amountSD()); + + (bool ok, string memory reason) = _checkBridgeClosedAndWhitelisted(toAddress); + if (!ok) { + revert BRIDGE_NOT_ALLOWED(reason); + } + + _resetDailyLimitsIfNeeded(toAddress); + + (ok, reason) = _checkBridgeLimits(toAddress, amountLD); + if (!ok) { failedReceiveRequests[_guid] = FailedReceiveRequest( - true, - _message.sendTo().bytes32ToAddress(), - _toLD(_message.amountSD()), + true, + toAddress, + uint64(block.timestamp), + amountLD, _origin.srcEid ); - emit ReceiveRequestFailed(_guid, _message.sendTo().bytes32ToAddress(), _toLD(_message.amountSD()), _origin.srcEid); - // revert BRIDGE_LIMITS(reason); - } - else { - super._lzReceive(_origin, _guid, _message, _executor, _extraData); + emit ReceiveRequestFailed(_guid, toAddress, amountLD, _origin.srcEid); + revert BRIDGE_LIMITS(reason); } + + // 4. Passed both checks: update counters and complete receive + bridgeDailyLimit.bridged24Hours += amountLD; + accountsDailyLimit[toAddress].bridged24Hours += amountLD; + super._lzReceive(_origin, _guid, _message, _executor, _extraData); } /** * @notice Mints tokens to the specified address upon receiving them From 257db1adbf0a2b07e0b1a3de3abcca632abf1e95 Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Mar 2026 11:04:39 -0500 Subject: [PATCH 29/41] feat: implement OFT configuration scripts and documentation for bridging GoodDollar tokens between XDC and CELO networks --- .../oft/OFT_CONFIGURING_GUIDE.md | 23 ++++++++------- .../{scripts => test}/oft/bridge-oft-token.ts | 15 +++++----- .../oft/configure-oft-xdc-celo.sh | 28 +++++++++---------- .../oft/grant-minter-role.ts | 3 +- .../{scripts => test}/oft/oft.config.json | 1 - .../oft/set-minter-burner-limits.ts | 7 ++--- .../{scripts => test}/oft/set-oft-operator.ts | 1 - ...nsfer-oft-adapter-ownership-from-avatar.ts | 5 ++-- .../oft/transfer-oft-adapter-ownership.ts | 3 +- 9 files changed, 39 insertions(+), 47 deletions(-) rename packages/bridge-contracts/{scripts => test}/oft/OFT_CONFIGURING_GUIDE.md (87%) rename packages/bridge-contracts/{scripts => test}/oft/bridge-oft-token.ts (96%) rename packages/bridge-contracts/{scripts => test}/oft/configure-oft-xdc-celo.sh (88%) mode change 100755 => 100644 rename packages/bridge-contracts/{scripts => test}/oft/grant-minter-role.ts (98%) rename packages/bridge-contracts/{scripts => test}/oft/oft.config.json (99%) rename packages/bridge-contracts/{scripts => test}/oft/set-minter-burner-limits.ts (97%) rename packages/bridge-contracts/{scripts => test}/oft/set-oft-operator.ts (99%) rename packages/bridge-contracts/{scripts => test}/oft/transfer-oft-adapter-ownership-from-avatar.ts (96%) rename packages/bridge-contracts/{scripts => test}/oft/transfer-oft-adapter-ownership.ts (98%) diff --git a/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md b/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md similarity index 87% rename from packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md rename to packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md index e715d61..3d28507 100644 --- a/packages/bridge-contracts/scripts/oft/OFT_CONFIGURING_GUIDE.md +++ b/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md @@ -9,7 +9,7 @@ The OFT bridge enables cross-chain transfers of GoodDollar (G$) tokens between X ## Configuration File -All configuration values are stored in `scripts/oft/oft.config.json`. Each network has its own configuration entry. +All configuration values are stored in `test/oft/oft.config.json`. Each network has its own configuration entry. ### Configuration Structure @@ -85,10 +85,10 @@ Set the OFT adapter as an operator on the MinterBurner contract via DAO governan ```bash # Set operator on XDC -npx hardhat run scripts/oft/set-oft-operator.ts --network development-xdc +npx hardhat run test/oft/set-oft-operator.ts --network development-xdc # Set operator on CELO -npx hardhat run scripts/oft/set-oft-operator.ts --network development-celo +npx hardhat run test/oft/set-oft-operator.ts --network development-celo ``` This script: @@ -104,10 +104,10 @@ Grant the MINTER_ROLE to GoodDollarMinterBurner on the GoodDollar token: ```bash # Grant on XDC -yarn hardhat run scripts/oft/grant-minter-role.ts --network development-xdc +yarn hardhat run test/oft/grant-minter-role.ts --network development-xdc # Grant on CELO -yarn hardhat run scripts/oft/grant-minter-role.ts --network development-celo +yarn hardhat run test/oft/grant-minter-role.ts --network development-celo ``` This executes via DAO governance (Controller/Avatar) to grant the minter role. @@ -134,10 +134,10 @@ Configure bridge limits using values from `oft.config.json`: ```bash # Set limits on XDC -yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-xdc +yarn hardhat run test/oft/set-minter-burner-limits.ts --network development-xdc # Set limits on CELO -yarn hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo +yarn hardhat run test/oft/set-minter-burner-limits.ts --network development-celo ``` The script reads limit values from `oft.config.json` for the specified network and sets them on the OFT adapter. @@ -148,10 +148,10 @@ Test the bridge by sending tokens from one chain to another: ```bash # Bridge from XDC to CELO -yarn hardhat run scripts/oft/bridge-oft-token.ts --network development-xdc +yarn hardhat run test/oft/bridge-oft-token.ts --network development-xdc # Bridge from CELO to XDC -yarn hardhat run scripts/oft/bridge-oft-token.ts --network development-celo +yarn hardhat run test/oft/bridge-oft-token.ts --network development-celo ``` **Requirements**: @@ -165,10 +165,10 @@ Transfer OFT adapter ownership to DAO Avatar. This should be done as the final s ```bash # Transfer on XDC -yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-xdc +yarn hardhat run test/oft/transfer-oft-adapter-ownership.ts --network development-xdc # Transfer on CELO -yarn hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-celo +yarn hardhat run test/oft/transfer-oft-adapter-ownership.ts --network development-celo ``` **Note**: This must be done by the current owner of the OFT adapter (usually the deployer). This step is performed last to ensure all configuration is complete before transferring ownership to the DAO. @@ -210,4 +210,3 @@ After configuration, verify the setup: - Ensure `oft.config.json` has an entry for your network - Verify the `limits` object is properly formatted - Check that `skipLimits` is not set to `true` - diff --git a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts b/packages/bridge-contracts/test/oft/bridge-oft-token.ts similarity index 96% rename from packages/bridge-contracts/scripts/oft/bridge-oft-token.ts rename to packages/bridge-contracts/test/oft/bridge-oft-token.ts index 80bf505..8d4dcbf 100644 --- a/packages/bridge-contracts/scripts/oft/bridge-oft-token.ts +++ b/packages/bridge-contracts/test/oft/bridge-oft-token.ts @@ -3,14 +3,14 @@ * * Usage: * # Bridge from XDC to CELO: - * npx hardhat run scripts/multichain-deploy/oft/bridge-oft-token.ts --network production-xdc + * npx hardhat run test/oft/bridge-oft-token.ts --network production-xdc * # or - * npx hardhat run scripts/multichain-deploy/oft/bridge-oft-token.ts --network development-xdc + * npx hardhat run test/oft/bridge-oft-token.ts --network development-xdc * * # Bridge from CELO to XDC: - * npx hardhat run scripts/multichain-deploy/oft/bridge-oft-token.ts --network production-celo + * npx hardhat run test/oft/bridge-oft-token.ts --network production-celo * # or - * npx hardhat run scripts/multichain-deploy/oft/bridge-oft-token.ts --network development-celo + * npx hardhat run test/oft/bridge-oft-token.ts --network development-celo * * Note: Make sure you have: * - GoodDollarOFTAdapter deployed on both XDC and CELO @@ -153,7 +153,7 @@ const main = async () => { `${destNetwork} OFT adapter address not found in deployment-oft.json.\n` + `Please either:\n` + ` 1. Deploy OFT adapter on ${destNetwork} and add it to deployment-oft.json, or\n` + - ` 2. Manually set the peer using: scripts/set-oft-peer.ts` + ` 2. Manually set the peer using: test/oft/set-oft-peer.ts` ); } @@ -395,7 +395,7 @@ const main = async () => { } else if (error.message?.includes('send library') || error.message?.includes('SendLib') || error.message?.includes('receive library') || error.message?.includes('ReceiveLib')) { console.error("\n🔍 DIAGNOSIS: LayerZero library configuration issue"); console.error("Check library configuration using:"); - console.error(` yarn hardhat run scripts/oft/check-layerzero-config.ts --network ${networkName}`); + console.error(` yarn hardhat run test/oft/check-layerzero-config.ts --network ${networkName}`); } // Check for peer errors @@ -403,7 +403,7 @@ const main = async () => { console.error("\n🔍 DIAGNOSIS: Peer not configured"); console.error("The peer connection between chains is not set."); console.error("\nSOLUTION:"); - console.error(`Run: yarn hardhat run scripts/oft/set-layerzero-peers.ts --network ${networkName}`); + console.error(`Run: yarn hardhat run test/oft/set-layerzero-peers.ts --network ${networkName}`); } } @@ -417,4 +417,3 @@ main() console.error(error); process.exit(1); }); - diff --git a/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh b/packages/bridge-contracts/test/oft/configure-oft-xdc-celo.sh old mode 100755 new mode 100644 similarity index 88% rename from packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh rename to packages/bridge-contracts/test/oft/configure-oft-xdc-celo.sh index 7459b3f..68a1ee9 --- a/packages/bridge-contracts/scripts/oft/configure-oft-xdc-celo.sh +++ b/packages/bridge-contracts/test/oft/configure-oft-xdc-celo.sh @@ -12,7 +12,7 @@ # 7. Transfer OFT adapter ownership to DAO Avatar on both networks (last step) # # Usage: -# ./scripts/multichain-deploy/oft/configure-oft-xdc-celo.sh +# ./test/oft/configure-oft-xdc-celo.sh # # Environment variables (optional): # DAILY_LIMIT=1000000 # Daily bridge limit in G$ (e.g., 1M G$ - will be converted to wei automatically) @@ -81,11 +81,11 @@ echo "" print_step "Step 2: Setting OFT adapter as operator on MinterBurner" print_step "Setting operator on development-xdc..." -npx hardhat run scripts/oft/set-oft-operator.ts --network development-xdc +npx hardhat run test/oft/set-oft-operator.ts --network development-xdc print_success "Operator set on development-xdc" print_step "Setting operator on development-celo..." -npx hardhat run scripts/oft/set-oft-operator.ts --network development-celo +npx hardhat run test/oft/set-oft-operator.ts --network development-celo print_success "Operator set on development-celo" echo "" @@ -94,11 +94,11 @@ echo "" print_step "Step 3: Granting MINTER_ROLE to GoodDollarMinterBurner" print_step "Granting MINTER_ROLE on development-xdc..." -npx hardhat run scripts/oft/grant-minter-role.ts --network development-xdc +npx hardhat run test/oft/grant-minter-role.ts --network development-xdc print_success "MINTER_ROLE granted on development-xdc" print_step "Granting MINTER_ROLE on development-celo..." -npx hardhat run scripts/oft/grant-minter-role.ts --network development-celo +npx hardhat run test/oft/grant-minter-role.ts --network development-celo print_success "MINTER_ROLE granted on development-celo" echo "" @@ -107,11 +107,11 @@ echo "" # print_step "Step 4: Setting LayerZero peer connections" # print_step "Setting peer on development-xdc..." -# npx hardhat run scripts/oft/set-layerzero-peers.ts --network development-xdc +# npx hardhat run test/oft/set-layerzero-peers.ts --network development-xdc # print_success "Peer set on development-xdc" # print_step "Setting peer on development-celo..." -# npx hardhat run scripts/oft/set-layerzero-peers.ts --network development-celo +# npx hardhat run test/oft/set-layerzero-peers.ts --network development-celo # print_success "Peer set on development-celo" # echo "" @@ -164,7 +164,7 @@ if [ "$SKIP_LIMITS" != "true" ]; then print_warning " DAILY_LIMIT=1000000 TX_LIMIT=100000 \\" print_warning " ACCOUNT_DAILY_LIMIT=50000 MIN_AMOUNT=10 \\" print_warning " ONLY_WHITELISTED=false \\" - print_warning " ./scripts/oft/configure-oft-xdc-celo.sh" + print_warning " ./test/oft/configure-oft-xdc-celo.sh" print_warning "" print_warning "Note: Values are in G$ (e.g., '1000000' = 1M G$). The script automatically converts to wei." else @@ -174,7 +174,7 @@ if [ "$SKIP_LIMITS" != "true" ]; then ACCOUNT_DAILY_LIMIT=$ACCOUNT_DAILY_LIMIT \ MIN_AMOUNT=$MIN_AMOUNT \ ONLY_WHITELISTED=$ONLY_WHITELISTED \ - npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-xdc + npx hardhat run test/oft/set-minter-burner-limits.ts --network development-xdc print_success "Bridge limits set on development-xdc" print_step "Setting bridge limits on development-celo..." @@ -183,7 +183,7 @@ if [ "$SKIP_LIMITS" != "true" ]; then ACCOUNT_DAILY_LIMIT=$ACCOUNT_DAILY_LIMIT \ MIN_AMOUNT=$MIN_AMOUNT \ ONLY_WHITELISTED=$ONLY_WHITELISTED \ - npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo + npx hardhat run test/oft/set-minter-burner-limits.ts --network development-celo print_success "Bridge limits set on development-celo" fi echo "" @@ -200,7 +200,7 @@ if [ "$SKIP_BRIDGE_TEST" != "true" ]; then echo "" if [[ $REPLY =~ ^[Yy]$ ]]; then print_step "Bridging from development-xdc to development-celo..." - npx hardhat run scripts/oft/bridge-oft-token.ts --network development-xdc || print_warning "Bridge test failed (this is okay if you don't have sufficient balance)" + npx hardhat run test/oft/bridge-oft-token.ts --network development-xdc || print_warning "Bridge test failed (this is okay if you don't have sufficient balance)" echo "" else print_warning "Skipping bridge test" @@ -215,11 +215,11 @@ fi print_step "Step 7: Transferring OFT adapter ownership to DAO Avatar" print_step "Transferring ownership on development-xdc..." -npx hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-xdc +npx hardhat run test/oft/transfer-oft-adapter-ownership.ts --network development-xdc print_success "Ownership transferred on development-xdc" print_step "Transferring ownership on development-celo..." -npx hardhat run scripts/oft/transfer-oft-adapter-ownership.ts --network development-celo +npx hardhat run test/oft/transfer-oft-adapter-ownership.ts --network development-celo print_success "Ownership transferred on development-celo" echo "" @@ -250,5 +250,5 @@ fi echo " ✅ Transferred OFT adapter ownership to DAO Avatar" echo "" print_success "You can now use the bridge-oft-token.ts script to bridge tokens between chains!" -print_success "Run: npx hardhat run scripts/oft/bridge-oft-token.ts --network " +print_success "Run: npx hardhat run test/oft/bridge-oft-token.ts --network " diff --git a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts b/packages/bridge-contracts/test/oft/grant-minter-role.ts similarity index 98% rename from packages/bridge-contracts/scripts/oft/grant-minter-role.ts rename to packages/bridge-contracts/test/oft/grant-minter-role.ts index f7b24f0..a74716f 100644 --- a/packages/bridge-contracts/scripts/oft/grant-minter-role.ts +++ b/packages/bridge-contracts/test/oft/grant-minter-role.ts @@ -3,7 +3,7 @@ * Uses genericCall through Avatar/Controller to execute the transaction * * Usage: - * npx hardhat run scripts/multichain-deploy/oft/grant-minter-role.ts --network development-celo + * npx hardhat run test/oft/grant-minter-role.ts --network development-celo * * Note: This script must be run by a guardian or address with permissions to execute via Controller */ @@ -136,4 +136,3 @@ main() console.error(error); process.exit(1); }); - diff --git a/packages/bridge-contracts/scripts/oft/oft.config.json b/packages/bridge-contracts/test/oft/oft.config.json similarity index 99% rename from packages/bridge-contracts/scripts/oft/oft.config.json rename to packages/bridge-contracts/test/oft/oft.config.json index d17830d..32121ed 100644 --- a/packages/bridge-contracts/scripts/oft/oft.config.json +++ b/packages/bridge-contracts/test/oft/oft.config.json @@ -52,4 +52,3 @@ } } } - diff --git a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts b/packages/bridge-contracts/test/oft/set-minter-burner-limits.ts similarity index 97% rename from packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts rename to packages/bridge-contracts/test/oft/set-minter-burner-limits.ts index 19ac230..17b14dc 100644 --- a/packages/bridge-contracts/scripts/oft/set-minter-burner-limits.ts +++ b/packages/bridge-contracts/test/oft/set-minter-burner-limits.ts @@ -5,10 +5,10 @@ * Note: Limits are now managed in GoodDollarOFTAdapter, not GoodDollarMinterBurner * * Usage: - * npx hardhat run scripts/oft/set-minter-burner-limits.ts --network development-celo + * npx hardhat run test/oft/set-minter-burner-limits.ts --network development-celo * * Configuration: - * All limit values are read from scripts/oft/oft.config.json + * All limit values are read from test/oft/oft.config.json * Each network/env has its entry in the config file. * * Note: This script must be run by a guardian or address with permissions to execute via Controller @@ -80,7 +80,7 @@ const main = async () => { const networkConfig = (config as any)[networkName]; if (!networkConfig || !networkConfig.limits) { console.log("\n⚠️ No limits configuration found for this network."); - console.log(`Please add a "limits" entry for "${networkName}" in scripts/oft/oft.config.json`); + console.log(`Please add a "limits" entry for "${networkName}" in test/oft/oft.config.json`); return; } @@ -182,4 +182,3 @@ main() console.error(error); process.exit(1); }); - diff --git a/packages/bridge-contracts/scripts/oft/set-oft-operator.ts b/packages/bridge-contracts/test/oft/set-oft-operator.ts similarity index 99% rename from packages/bridge-contracts/scripts/oft/set-oft-operator.ts rename to packages/bridge-contracts/test/oft/set-oft-operator.ts index 2138829..4293e8f 100644 --- a/packages/bridge-contracts/scripts/oft/set-oft-operator.ts +++ b/packages/bridge-contracts/test/oft/set-oft-operator.ts @@ -143,4 +143,3 @@ if (require.main === module) { process.exit(1); }); } - diff --git a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts b/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership-from-avatar.ts similarity index 96% rename from packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts rename to packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership-from-avatar.ts index 89b2134..93a5227 100644 --- a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts +++ b/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership-from-avatar.ts @@ -2,8 +2,8 @@ * Script to transfer ownership of GoodDollarOFTAdapter from DAO Avatar to current signer * * Usage: - * yarn hardhat run scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts --network development-celo - * yarn hardhat run scripts/oft/transfer-oft-adapter-ownership-from-avatar.ts --network development-xdc + * yarn hardhat run test/oft/transfer-oft-adapter-ownership-from-avatar.ts --network development-celo + * yarn hardhat run test/oft/transfer-oft-adapter-ownership-from-avatar.ts --network development-xdc * * Note: This script must be run by an account that can execute DAO proposals. * If the Avatar owns the contract, ownership transfer must go through the Controller. @@ -165,4 +165,3 @@ main() process.exit(1); }); - diff --git a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts b/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership.ts similarity index 98% rename from packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts rename to packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership.ts index 411c024..8c5b229 100644 --- a/packages/bridge-contracts/scripts/oft/transfer-oft-adapter-ownership.ts +++ b/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership.ts @@ -2,7 +2,7 @@ * Script to transfer ownership of GoodDollarOFTAdapter to DAO Avatar * * Usage: - * npx hardhat run scripts/multichain-deploy/oft/transfer-oft-adapter-ownership.ts --network development-celo + * npx hardhat run test/oft/transfer-oft-adapter-ownership.ts --network development-celo * * Note: This script must be run by the current owner of the OFT adapter. * If the current owner is not the signer, you'll need to run this script from the owner's account. @@ -249,4 +249,3 @@ main() console.error(error); process.exit(1); }); - From 11d66fb666106419a8737991a6445951204dfd4e Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Mar 2026 11:19:21 -0500 Subject: [PATCH 30/41] chore: remove outdated deployment JSON files for GoodDollarMinterBurner and GoodDollarOFTAdapter --- .../release/deployment-oft-20260211085500.json | 10 ---------- .../release/deployment-oft-2026021112.json | 10 ---------- .../bridge-contracts/release/deployment-oft-old.json | 11 ----------- 3 files changed, 31 deletions(-) delete mode 100644 packages/bridge-contracts/release/deployment-oft-20260211085500.json delete mode 100644 packages/bridge-contracts/release/deployment-oft-2026021112.json delete mode 100644 packages/bridge-contracts/release/deployment-oft-old.json diff --git a/packages/bridge-contracts/release/deployment-oft-20260211085500.json b/packages/bridge-contracts/release/deployment-oft-20260211085500.json deleted file mode 100644 index 83bebc2..0000000 --- a/packages/bridge-contracts/release/deployment-oft-20260211085500.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "development-xdc": { - "GoodDollarMinterBurner": "0xB62C8fc044e5B0A376a2a1d084e4d335Ff7B9F87", - "GoodDollarOFTAdapter": "0x6307910da2B34A1b75C5ACC52Fffaa526ce4F004" - }, - "development-celo": { - "GoodDollarMinterBurner": "0x2A3bfB662B4dd9Ff9B929d013690b1cc434098Ee", - "GoodDollarOFTAdapter": "0x981bc8E7E2D08A215A1BE09D5490032E3B90338E" - } -} diff --git a/packages/bridge-contracts/release/deployment-oft-2026021112.json b/packages/bridge-contracts/release/deployment-oft-2026021112.json deleted file mode 100644 index 1b63343..0000000 --- a/packages/bridge-contracts/release/deployment-oft-2026021112.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "development-xdc": { - "GoodDollarMinterBurner": "0x647313034B26B22b9f1390C04935B9ea740A5Aa5", - "GoodDollarOFTAdapter": "0x14f65D919F482e21FD4EFB8FEc167C78B1100eeD" - }, - "development-celo": { - "GoodDollarMinterBurner": "0xe2C9B67053C36A4D8E1E8aC03359482049Ee82BD", - "GoodDollarOFTAdapter": "0x07373c71bc325c4886c9C7d53151A1d929c1bB65" - } -} diff --git a/packages/bridge-contracts/release/deployment-oft-old.json b/packages/bridge-contracts/release/deployment-oft-old.json deleted file mode 100644 index 498a290..0000000 --- a/packages/bridge-contracts/release/deployment-oft-old.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "development-celo": { - "GoodDollarMinterBurner": "0x3850786C7627Ce276EF6F715a0eF31d5FeCf1fc7", - "GoodDollarOFTAdapter": "0x28aE7CE2F3BBb0bf58B24F1961885aeE1e456f46" - }, - "development-xdc": { - "GoodDollarMinterBurner": "0xb20eaE658437b793257C326751a695ED5e94dD3E", - "GoodDollarOFTAdapter": "0x3a568CFD4e3F6ef9f5e341FAbAD84B6Cc41C1A75" - } -} - From 2b109b2175aa27ec75e9aff41affcf2be7d5ed92 Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Mar 2026 11:19:38 -0500 Subject: [PATCH 31/41] feat: refactor deployment script for GoodDollarMinterBurner to use deterministic proxy pattern and improve initialization process --- packages/bridge-contracts/deploy/deployOFT.ts | 240 +++++++++--------- 1 file changed, 120 insertions(+), 120 deletions(-) diff --git a/packages/bridge-contracts/deploy/deployOFT.ts b/packages/bridge-contracts/deploy/deployOFT.ts index 9aea28a..496b29a 100644 --- a/packages/bridge-contracts/deploy/deployOFT.ts +++ b/packages/bridge-contracts/deploy/deployOFT.ts @@ -1,20 +1,19 @@ /*** * Hardhat-deploy script for GoodDollar OFT (Omnichain Fungible Token) contracts - * - * Deploys: + * + * Deploys (same pattern as MessageBridge: deterministic proxy + implementation + execute initialize): * 1. GoodDollarMinterBurner - DAO-upgradeable contract that handles minting and burning of GoodDollar tokens for OFT * 2. GoodDollarOFTAdapter - Upgradeable LayerZero OFT adapter that wraps GoodDollar token for cross-chain transfers - * + * * Steps: - * 1. Deploy GoodDollarMinterBurner as upgradeable proxy with NameService - * 2. Deploy GoodDollarOFTAdapter as upgradeable proxy with constructor(token, lzEndpoint), then initialize(token, minterBurner, lzEndpoint, owner, feeRecipient) - * + * 1. Deploy ERC1967Proxy (deterministic) for GoodDollarMinterBurner, deploy implementation, execute initialize(nameService) + * 2. Deploy ERC1967Proxy (deterministic) for GoodDollarOFTAdapter, deploy implementation (constructor: token, lzEndpoint), execute initialize(token, minterBurner, owner, feeRecipient) + * * Note: Setting OFT adapter as operator on GoodDollarMinterBurner must be done separately via DAO governance */ -import { HardhatRuntimeEnvironment } from 'hardhat/types'; import { DeployFunction } from 'hardhat-deploy/types'; -import { ethers, upgrades } from 'hardhat'; +import { ethers } from 'hardhat'; import Contracts from '@gooddollar/goodprotocol/releases/deployment.json'; import fse from 'fs-extra'; import release from '../release/deployment-oft.json'; @@ -29,7 +28,7 @@ const lzEndpoints: { [key: string]: string } = { 'production-xdc': '0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa', }; -const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { +const func: DeployFunction = async function (hre) { const { deployments, network } = hre; const [root] = await ethers.getSigners(); @@ -104,126 +103,128 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { } console.log('✅ Verified Avatar address:', avatarAddress); - // Get current deployment state - const currentRelease = release[networkName] || {}; - - // Deploy GoodDollarMinterBurner (upgradeable) - let minterBurnerAddress: string; - let minterBurnerImplAddress: string | undefined; - if (!currentRelease.GoodDollarMinterBurner) { - console.log('Deploying GoodDollarMinterBurner as upgradeable contract...'); - const MinterBurnerFactory = await ethers.getContractFactory('GoodDollarMinterBurner'); - const MinterBurner = await upgrades.deployProxy(MinterBurnerFactory, [nameServiceAddress], { - kind: 'uups', - initializer: 'initialize', - }); - await MinterBurner.deployed(); - minterBurnerAddress = MinterBurner.address; - console.log('GoodDollarMinterBurner deployed to:', minterBurnerAddress); - - // Get implementation address for verification - minterBurnerImplAddress = await getImplementationAddress(ethers.provider, minterBurnerAddress); - console.log('GoodDollarMinterBurner implementation address:', minterBurnerImplAddress); - - // Save to hardhat-deploy - await deployments.save('GoodDollarMinterBurner', { - address: minterBurnerAddress, - abi: MinterBurnerFactory.interface.format(ethers.utils.FormatTypes.json) as any, - }); - - // Update release file - if (!release[networkName]) { - release[networkName] = {}; - } - release[networkName].GoodDollarMinterBurner = minterBurnerAddress; - await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); + // --- GoodDollarMinterBurner (hardhat-deploy: deterministic proxy + implementation + execute initialize) --- + const minterBurnerProxySalt = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes('GoodDollarMinterBurnerV1'), + ); + const minterBurnerProxyDeploy = await deployments.deterministic('GoodDollarMinterBurner', { + contract: 'ERC1967Proxy', + from: root.address, + salt: minterBurnerProxySalt, + log: true, + }); + const minterBurnerProxy = await minterBurnerProxyDeploy.deploy(); + const minterBurnerAddress = minterBurnerProxy.address; + console.log('GoodDollarMinterBurner proxy', minterBurnerAddress); + + const minterBurnerImpl = await deployments.deploy('GoodDollarMinterBurner_Implementation', { + contract: 'GoodDollarMinterBurner', + from: root.address, + deterministicDeployment: true, + log: true, + }); + console.log('GoodDollarMinterBurner implementation', minterBurnerImpl.address); - // Verify GoodDollarMinterBurner implementation - // No constructor args - initialized via initialize() function - await verifyContract(hre, minterBurnerImplAddress, [], 'GoodDollarMinterBurner'); + const minterBurnerContract = await ethers.getContractAt('GoodDollarMinterBurner', minterBurnerAddress); + const minterBurnerInitialized = await minterBurnerContract + .token() + .then((addr: string) => addr !== ethers.constants.AddressZero) + .catch(() => false); + + if (!minterBurnerInitialized) { + console.log('Initializing GoodDollarMinterBurner...'); + const minterBurnerInitData = minterBurnerContract.interface.encodeFunctionData('initialize', [ + nameServiceAddress, + ]); + await deployments.execute( + 'GoodDollarMinterBurner', + { from: root.address }, + 'initialize', + minterBurnerImpl.address, + minterBurnerInitData, + ); + console.log('GoodDollarMinterBurner initialized'); } else { - minterBurnerAddress = currentRelease.GoodDollarMinterBurner; - console.log('GoodDollarMinterBurner already deployed at:', minterBurnerAddress); - // Get implementation address even if already deployed (for verification if needed) - try { - minterBurnerImplAddress = await getImplementationAddress(ethers.provider, minterBurnerAddress); - console.log('GoodDollarMinterBurner implementation address:', minterBurnerImplAddress); - await verifyContract(hre, minterBurnerImplAddress, [], 'GoodDollarMinterBurner'); - console.log('GoodDollarMinterBurner verified successfully'); - } catch (error) { - console.log('⚠️ Could not get implementation address for GoodDollarMinterBurner'); - } + console.log('GoodDollarMinterBurner already initialized'); } - // Deploy GoodDollarOFTAdapter (upgradeable via proxy) - // Constructor takes (token, lzEndpoint) - initialize() is called automatically by proxy - let oftAdapterAddress: string; - let oftAdapterImplAddress: string | undefined; - if (!currentRelease.GoodDollarOFTAdapter) { - console.log('Deploying GoodDollarOFTAdapter as upgradeable proxy...'); - console.log('Constructor parameters: token, lzEndpoint'); - console.log('Initialize parameters: token, minterBurner, owner, feeRecipient'); - - const OFTAdapterFactory = await ethers.getContractFactory('GoodDollarOFTAdapter'); - - // Create UUPS proxy with constructor args and initialize - console.log('Deploying proxy and initializing...'); - const OFTAdapter = await upgrades.deployProxy( - OFTAdapterFactory, - [tokenAddress, minterBurnerAddress, root.address, root.address], - { - kind: 'uups', - initializer: 'initialize', - unsafeAllow: ['constructor', 'state-variable-immutable', 'duplicate-initializer-call'], - constructorArgs: [tokenAddress, lzEndpoint], - }, - ); - await OFTAdapter.deployed(); - oftAdapterAddress = OFTAdapter.address; - console.log('✅ GoodDollarOFTAdapter proxy deployed to:', oftAdapterAddress); - console.log('Fee recipient:', root.address); + // Update release file for MinterBurner + if (!release[networkName]) { + release[networkName] = {}; + } + release[networkName].GoodDollarMinterBurner = minterBurnerAddress; + await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); - // Get implementation address for verification - oftAdapterImplAddress = await getImplementationAddress(ethers.provider, oftAdapterAddress); - console.log('GoodDollarOFTAdapter implementation address:', oftAdapterImplAddress); - - // Save to hardhat-deploy - await deployments.save('GoodDollarOFTAdapter', { - address: oftAdapterAddress, - abi: OFTAdapterFactory.interface.format(ethers.utils.FormatTypes.json) as any, - }); - - // Update release file - release[networkName].GoodDollarOFTAdapter = oftAdapterAddress; - await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); - - // Verify GoodDollarOFTAdapter implementation - // Constructor args: tokenAddress, lzEndpoint - await verifyContract( - hre, - oftAdapterImplAddress, - [tokenAddress, lzEndpoint], + // Verify GoodDollarMinterBurner implementation (no constructor args) + if (!['hardhat', 'localhost', 'develop'].includes(networkName)) { + await verifyContract(hre as any, minterBurnerImpl.address, [], 'GoodDollarMinterBurner'); + } + + // --- GoodDollarOFTAdapter (hardhat-deploy: deterministic proxy + implementation with constructor + execute initialize) --- + const oftAdapterProxySalt = ethers.utils.keccak256( + ethers.utils.toUtf8Bytes('GoodDollarOFTAdapterV1'), + ); + const oftAdapterProxyDeploy = await deployments.deterministic('GoodDollarOFTAdapter', { + contract: 'ERC1967Proxy', + from: root.address, + salt: oftAdapterProxySalt, + log: true, + }); + const oftAdapterProxy = await oftAdapterProxyDeploy.deploy(); + const oftAdapterAddress = oftAdapterProxy.address; + console.log('GoodDollarOFTAdapter proxy', oftAdapterAddress); + + const oftAdapterImpl = await deployments.deploy('GoodDollarOFTAdapter_Implementation', { + contract: 'GoodDollarOFTAdapter', + from: root.address, + deterministicDeployment: true, + log: true, + args: [tokenAddress, lzEndpoint], + }); + console.log('GoodDollarOFTAdapter implementation', oftAdapterImpl.address); + + const oftAdapterContract = await ethers.getContractAt('GoodDollarOFTAdapter', oftAdapterAddress); + const oftAdapterInitialized = await oftAdapterContract + .minterBurner() + .then((addr: string) => addr !== ethers.constants.AddressZero) + .catch(() => false); + + if (!oftAdapterInitialized) { + console.log('Initializing GoodDollarOFTAdapter...'); + const oftAdapterInitData = oftAdapterContract.interface.encodeFunctionData('initialize', [ + tokenAddress, + minterBurnerAddress, + root.address, + root.address, + ]); + await deployments.execute( 'GoodDollarOFTAdapter', + { from: root.address }, + 'initialize', + oftAdapterImpl.address, + oftAdapterInitData, ); + console.log('GoodDollarOFTAdapter initialized'); + console.log('Fee recipient:', root.address); } else { - oftAdapterAddress = currentRelease.GoodDollarOFTAdapter; - console.log('GoodDollarOFTAdapter already deployed at:', oftAdapterAddress); - // Get implementation address even if already deployed (for verification if needed) - try { - oftAdapterImplAddress = await getImplementationAddress(ethers.provider, oftAdapterAddress); - console.log('GoodDollarOFTAdapter implementation address:', oftAdapterImplAddress); - await verifyContract( - hre, - oftAdapterImplAddress, - [tokenAddress, lzEndpoint], - 'GoodDollarOFTAdapter', - ); - console.log('GoodDollarOFTAdapter verified successfully'); - } catch (error) { - console.log('⚠️ Could not get implementation address for GoodDollarOFTAdapter'); - } + console.log('GoodDollarOFTAdapter already initialized'); } + release[networkName].GoodDollarOFTAdapter = oftAdapterAddress; + await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); + + // Verify GoodDollarOFTAdapter implementation (constructor: tokenAddress, lzEndpoint) + if (!['hardhat', 'localhost', 'develop'].includes(networkName)) { + await verifyContract(hre as any, oftAdapterImpl.address, [tokenAddress, lzEndpoint], 'GoodDollarOFTAdapter'); + } + + const minterBurnerImplAddress = await getImplementationAddress(ethers.provider, minterBurnerAddress).catch( + () => undefined, + ); + const oftAdapterImplAddress = await getImplementationAddress(ethers.provider, oftAdapterAddress).catch( + () => undefined, + ); + console.log('\n=== Deployment Summary ==='); console.log('Network:', networkName); console.log('GoodDollarMinterBurner:', minterBurnerAddress, '(upgradeable)'); @@ -242,4 +243,3 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) { export default func; func.tags = ['OFT', 'GoodDollar']; - From 365283e5d6fdccb7e365fd3b0c8de74ff89742ee Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Mar 2026 11:23:18 -0500 Subject: [PATCH 32/41] chore: remove OFT configuration script for XDC and CELO networks --- .../test/oft/configure-oft-xdc-celo.sh | 254 ------------------ 1 file changed, 254 deletions(-) delete mode 100644 packages/bridge-contracts/test/oft/configure-oft-xdc-celo.sh diff --git a/packages/bridge-contracts/test/oft/configure-oft-xdc-celo.sh b/packages/bridge-contracts/test/oft/configure-oft-xdc-celo.sh deleted file mode 100644 index 68a1ee9..0000000 --- a/packages/bridge-contracts/test/oft/configure-oft-xdc-celo.sh +++ /dev/null @@ -1,254 +0,0 @@ -#!/bin/bash - -# Script to configure OFT (Omnichain Fungible Token) on XDC and CELO networks -# -# This script automates the complete OFT setup process: -# 1. Deploy OFT contracts (MinterBurner and OFTAdapter) on both networks -# 2. Set OFT adapter as operator on MinterBurner via DAO on both networks -# 3. Grant MINTER_ROLE to MinterBurner on both networks -# 4. Wire LayerZero connections between XDC and CELO -# 5. Set mint/burn limits on MinterBurner for both networks -# 6. Test bridge functionality (optional) -# 7. Transfer OFT adapter ownership to DAO Avatar on both networks (last step) -# -# Usage: -# ./test/oft/configure-oft-xdc-celo.sh -# -# Environment variables (optional): -# DAILY_LIMIT=1000000 # Daily bridge limit in G$ (e.g., 1M G$ - will be converted to wei automatically) -# TX_LIMIT=100000 # Per-transaction limit in G$ (e.g., 100K G$) -# ACCOUNT_DAILY_LIMIT=50000 # Per-account daily limit in G$ (e.g., 50K G$) -# MIN_AMOUNT=10 # Minimum bridge amount in G$ (e.g., 10 G$) -# ONLY_WHITELISTED=false # Whether to restrict to whitelisted addresses -# SKIP_BRIDGE_TEST=true # Skip bridge test step -# SKIP_LIMITS=true # Skip setting limits step -# SKIP_WIRING=true # Skip LayerZero wiring step (peers must be set manually) -# -# Note: Limit values can be specified in decimal format (e.g., "1000000" for 1M G$) -# The script will automatically convert them to wei (18 decimals). -# You can also use wei values directly if preferred. - -set -e # Exit on error - -# Colors for output -RED='\033[0;31m' -GREEN='\033[0;32m' -YELLOW='\033[1;33m' -BLUE='\033[0;34m' -NC='\033[0m' # No Color - -# Function to print colored output -print_step() { - echo -e "${BLUE}=== $1 ===${NC}" -} - -print_success() { - echo -e "${GREEN}✅ $1${NC}" -} - -print_warning() { - echo -e "${YELLOW}⚠️ $1${NC}" -} - -print_error() { - echo -e "${RED}❌ $1${NC}" -} - -# Check if we're in the project root -if [ ! -f "hardhat.config.ts" ] && [ ! -f "hardhat.config.js" ]; then - print_error "Please run this script from the project root directory" - exit 1 -fi - -print_step "OFT Configuration Script for XDC and CELO" -echo "This script will configure OFT on both development-xdc and development-celo networks" -echo "" - -# Step 1: Deploy OFT contracts -print_step "Step 1: Deploying OFT contracts" - -print_step "Deploying on development-xdc..." -npx hardhat deploy --tags OFT --network development-xdc -print_success "OFT contracts deployed on development-xdc" - -print_step "Deploying on development-celo..." -npx hardhat deploy --tags OFT --network development-celo -print_success "OFT contracts deployed on development-celo" - -echo "" - -# Step 2: Set OFT adapter as operator on MinterBurner -print_step "Step 2: Setting OFT adapter as operator on MinterBurner" - -print_step "Setting operator on development-xdc..." -npx hardhat run test/oft/set-oft-operator.ts --network development-xdc -print_success "Operator set on development-xdc" - -print_step "Setting operator on development-celo..." -npx hardhat run test/oft/set-oft-operator.ts --network development-celo -print_success "Operator set on development-celo" - -echo "" - -# Step 3: Grant MINTER_ROLE -print_step "Step 3: Granting MINTER_ROLE to GoodDollarMinterBurner" - -print_step "Granting MINTER_ROLE on development-xdc..." -npx hardhat run test/oft/grant-minter-role.ts --network development-xdc -print_success "MINTER_ROLE granted on development-xdc" - -print_step "Granting MINTER_ROLE on development-celo..." -npx hardhat run test/oft/grant-minter-role.ts --network development-celo -print_success "MINTER_ROLE granted on development-celo" - -echo "" - -# # Step 4: Set LayerZero peers (required before wiring, after ownership transfer) -# print_step "Step 4: Setting LayerZero peer connections" - -# print_step "Setting peer on development-xdc..." -# npx hardhat run test/oft/set-layerzero-peers.ts --network development-xdc -# print_success "Peer set on development-xdc" - -# print_step "Setting peer on development-celo..." -# npx hardhat run test/oft/set-layerzero-peers.ts --network development-celo -# print_success "Peer set on development-celo" - -# echo "" - -# Step 4: Wire LayerZero connections (optional - peers already set) -WIRE_XDC_SUCCESS=false -WIRE_CELO_SUCCESS=false - -if [ "$SKIP_WIRING" != "true" ]; then - print_step "Step 4: Wiring LayerZero connections" - print_warning "Note: Peers are already set. Wiring configures send libraries, DVNs, executors, and enforced options." - print_warning "If wiring fails due to permissions, we'll set enforced options manually as a fallback." - - print_step "Wiring on development-xdc..." - set +e # Temporarily disable exit on error - npx hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-xdc - WIRE_XDC_STATUS=$? - set -e # Re-enable exit on error - if [ $WIRE_XDC_STATUS -eq 0 ]; then - print_success "LayerZero wired on development-xdc" - WIRE_XDC_SUCCESS=true - else - print_warning "Wiring failed on development-xdc (error code: $WIRE_XDC_STATUS)" - print_warning "This is likely a permission error (0xc4c52593). Will set enforced options manually." - fi - - print_step "Wiring on development-celo..." - set +e # Temporarily disable exit on error - npx hardhat lz:oapp:wire --oapp-config ./layerzero.config.ts --network development-celo - WIRE_CELO_STATUS=$? - set -e # Re-enable exit on error - if [ $WIRE_CELO_STATUS -eq 0 ]; then - print_success "LayerZero wired on development-celo" - WIRE_CELO_SUCCESS=true - else - print_warning "Wiring failed on development-celo (error code: $WIRE_CELO_STATUS)" - print_warning "This is likely a permission error (0xc4c52593). Will set enforced options manually." - fi -else - print_warning "Skipping wiring step (SKIP_WIRING=true)" -fi - -# Step 5: Set bridge limits (optional) -if [ "$SKIP_LIMITS" != "true" ]; then - print_step "Step 5: Setting bridge limits on OFTAdapter" - - if [ -z "$DAILY_LIMIT" ] && [ -z "$TX_LIMIT" ] && [ -z "$ACCOUNT_DAILY_LIMIT" ] && [ -z "$MIN_AMOUNT" ] && [ -z "$ONLY_WHITELISTED" ]; then - print_warning "No limit environment variables set. Skipping limits configuration." - print_warning "To set limits, use decimal values (easier to read):" - print_warning " DAILY_LIMIT=1000000 TX_LIMIT=100000 \\" - print_warning " ACCOUNT_DAILY_LIMIT=50000 MIN_AMOUNT=10 \\" - print_warning " ONLY_WHITELISTED=false \\" - print_warning " ./test/oft/configure-oft-xdc-celo.sh" - print_warning "" - print_warning "Note: Values are in G$ (e.g., '1000000' = 1M G$). The script automatically converts to wei." - else - print_step "Setting bridge limits on development-xdc..." - DAILY_LIMIT=$DAILY_LIMIT \ - TX_LIMIT=$TX_LIMIT \ - ACCOUNT_DAILY_LIMIT=$ACCOUNT_DAILY_LIMIT \ - MIN_AMOUNT=$MIN_AMOUNT \ - ONLY_WHITELISTED=$ONLY_WHITELISTED \ - npx hardhat run test/oft/set-minter-burner-limits.ts --network development-xdc - print_success "Bridge limits set on development-xdc" - - print_step "Setting bridge limits on development-celo..." - DAILY_LIMIT=$DAILY_LIMIT \ - TX_LIMIT=$TX_LIMIT \ - ACCOUNT_DAILY_LIMIT=$ACCOUNT_DAILY_LIMIT \ - MIN_AMOUNT=$MIN_AMOUNT \ - ONLY_WHITELISTED=$ONLY_WHITELISTED \ - npx hardhat run test/oft/set-minter-burner-limits.ts --network development-celo - print_success "Bridge limits set on development-celo" - fi - echo "" -else - print_warning "Skipping limits configuration (SKIP_LIMITS=true)" - echo "" -fi - -# Step 6: Test bridge (optional) -if [ "$SKIP_BRIDGE_TEST" != "true" ]; then - print_step "Step 6: Testing bridge functionality" - print_warning "This step will attempt to bridge 1 G$ from XDC to CELO" - read -p "Do you want to test the bridge? (y/N): " -n 1 -r - echo "" - if [[ $REPLY =~ ^[Yy]$ ]]; then - print_step "Bridging from development-xdc to development-celo..." - npx hardhat run test/oft/bridge-oft-token.ts --network development-xdc || print_warning "Bridge test failed (this is okay if you don't have sufficient balance)" - echo "" - else - print_warning "Skipping bridge test" - fi - echo "" -else - print_warning "Skipping bridge test (SKIP_BRIDGE_TEST=true)" - echo "" -fi - -# Step 7: Transfer ownership (last step) -print_step "Step 7: Transferring OFT adapter ownership to DAO Avatar" - -print_step "Transferring ownership on development-xdc..." -npx hardhat run test/oft/transfer-oft-adapter-ownership.ts --network development-xdc -print_success "Ownership transferred on development-xdc" - -print_step "Transferring ownership on development-celo..." -npx hardhat run test/oft/transfer-oft-adapter-ownership.ts --network development-celo -print_success "Ownership transferred on development-celo" - -echo "" - -# Summary -print_step "Configuration Complete!" -print_success "OFT has been successfully configured on both XDC and CELO networks" -echo "" -echo "Summary of completed steps:" -echo " ✅ Deployed OFT contracts on both networks" -echo " ✅ Set OFT adapter as operator on MinterBurner" -echo " ✅ Granted MINTER_ROLE to MinterBurner" -echo " ✅ Set LayerZero peer connections" -if [ "$SKIP_WIRING" != "true" ]; then - if [ "$WIRE_XDC_SUCCESS" = "true" ] && [ "$WIRE_CELO_SUCCESS" = "true" ]; then - echo " ✅ Wired LayerZero connections" - else - echo " ⚠️ LayerZero wiring failed (permission errors)" - echo " ✅ Set enforced options manually as fallback" - fi -fi -if [ "$SKIP_LIMITS" != "true" ] && ([ -n "$DAILY_LIMIT" ] || [ -n "$TX_LIMIT" ] || [ -n "$ACCOUNT_DAILY_LIMIT" ] || [ -n "$MIN_AMOUNT" ] || [ -n "$ONLY_WHITELISTED" ]); then - echo " ✅ Set bridge limits" -fi -if [ "$SKIP_BRIDGE_TEST" != "true" ]; then - echo " ✅ Tested bridge functionality (if executed)" -fi -echo " ✅ Transferred OFT adapter ownership to DAO Avatar" -echo "" -print_success "You can now use the bridge-oft-token.ts script to bridge tokens between chains!" -print_success "Run: npx hardhat run test/oft/bridge-oft-token.ts --network " - From a98d5544b6bda508d60a0150f2b8a66d33b316ac Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Mar 2026 11:23:54 -0500 Subject: [PATCH 33/41] chore: remove unused contract factory and type definitions for IFeesFormula, IGoodDollarCustom, ISuperGoodDollar, and related interfaces --- packages/bridge-contracts/hardhat.config.ts | 9 - .../contracts/BlockHeaderRegistry.ts | 612 ------------------ .../typechain-types/hardhat.d.ts | 72 --- .../bridge-contracts/typechain-types/index.ts | 16 +- 4 files changed, 2 insertions(+), 707 deletions(-) delete mode 100644 packages/bridge-contracts/typechain-types/contracts/BlockHeaderRegistry.ts diff --git a/packages/bridge-contracts/hardhat.config.ts b/packages/bridge-contracts/hardhat.config.ts index 847db73..9466402 100644 --- a/packages/bridge-contracts/hardhat.config.ts +++ b/packages/bridge-contracts/hardhat.config.ts @@ -57,15 +57,6 @@ const config: HardhatUserConfig = { }, }, }, - { - version: '0.8.9', - settings: { - optimizer: { - enabled: true, - runs: 0, - }, - }, - }, ], }, networks: { diff --git a/packages/bridge-contracts/typechain-types/contracts/BlockHeaderRegistry.ts b/packages/bridge-contracts/typechain-types/contracts/BlockHeaderRegistry.ts deleted file mode 100644 index 2e2f33a..0000000 --- a/packages/bridge-contracts/typechain-types/contracts/BlockHeaderRegistry.ts +++ /dev/null @@ -1,612 +0,0 @@ -/* Autogenerated file. Do not edit manually. */ -/* tslint:disable */ -/* eslint-disable */ -import type { - BaseContract, - BigNumber, - BigNumberish, - BytesLike, - CallOverrides, - ContractTransaction, - Overrides, - PopulatedTransaction, - Signer, - utils, -} from "ethers"; -import type { - FunctionFragment, - Result, - EventFragment, -} from "@ethersproject/abi"; -import type { Listener, Provider } from "@ethersproject/providers"; -import type { - TypedEventFilter, - TypedEvent, - TypedListener, - OnEvent, - PromiseOrValue, -} from "../common"; - -export declare namespace BlockHeaderRegistry { - export type SignatureStruct = { - r: PromiseOrValue; - vs: PromiseOrValue; - }; - - export type SignatureStructOutput = [string, string] & { - r: string; - vs: string; - }; - - export type BlockStruct = { - rlpHeader: PromiseOrValue; - signature: BlockHeaderRegistry.SignatureStruct; - chainId: PromiseOrValue; - blockHash: PromiseOrValue; - cycleEnd: PromiseOrValue; - validators: PromiseOrValue[]; - }; - - export type BlockStructOutput = [ - string, - BlockHeaderRegistry.SignatureStructOutput, - BigNumber, - string, - BigNumber, - string[] - ] & { - rlpHeader: string; - signature: BlockHeaderRegistry.SignatureStructOutput; - chainId: BigNumber; - blockHash: string; - cycleEnd: BigNumber; - validators: string[]; - }; - - export type BlockchainStruct = { - rpc: PromiseOrValue; - chainId: PromiseOrValue; - }; - - export type BlockchainStructOutput = [string, BigNumber] & { - rpc: string; - chainId: BigNumber; - }; - - export type SignedBlockStruct = { - signatures: PromiseOrValue[]; - cycleEnd: PromiseOrValue; - validators: PromiseOrValue[]; - blockHash: PromiseOrValue; - }; - - export type SignedBlockStructOutput = [ - string[], - BigNumber, - string[], - string - ] & { - signatures: string[]; - cycleEnd: BigNumber; - validators: string[]; - blockHash: string; - }; -} - -export interface BlockHeaderRegistryInterface extends utils.Interface { - functions: { - "addBlockchain(uint256,string)": FunctionFragment; - "addSignedBlocks((bytes,(bytes32,bytes32),uint256,bytes32,uint256,address[])[])": FunctionFragment; - "blockHashes(uint256,uint256,uint256)": FunctionFragment; - "consensus()": FunctionFragment; - "enabledBlockchains(uint256)": FunctionFragment; - "getBlockHashByPayloadHash(bytes32)": FunctionFragment; - "getRPCs()": FunctionFragment; - "getSignedBlock(uint256,uint256)": FunctionFragment; - "hasValidatorSigned(bytes32,address)": FunctionFragment; - "parseRLPBlockNumber(bytes)": FunctionFragment; - "signedBlocks(bytes32)": FunctionFragment; - "voting()": FunctionFragment; - }; - - getFunction( - nameOrSignatureOrTopic: - | "addBlockchain" - | "addSignedBlocks" - | "blockHashes" - | "consensus" - | "enabledBlockchains" - | "getBlockHashByPayloadHash" - | "getRPCs" - | "getSignedBlock" - | "hasValidatorSigned" - | "parseRLPBlockNumber" - | "signedBlocks" - | "voting" - ): FunctionFragment; - - encodeFunctionData( - functionFragment: "addBlockchain", - values: [PromiseOrValue, PromiseOrValue] - ): string; - encodeFunctionData( - functionFragment: "addSignedBlocks", - values: [BlockHeaderRegistry.BlockStruct[]] - ): string; - encodeFunctionData( - functionFragment: "blockHashes", - values: [ - PromiseOrValue, - PromiseOrValue, - PromiseOrValue - ] - ): string; - encodeFunctionData(functionFragment: "consensus", values?: undefined): string; - encodeFunctionData( - functionFragment: "enabledBlockchains", - values: [PromiseOrValue] - ): string; - encodeFunctionData( - functionFragment: "getBlockHashByPayloadHash", - values: [PromiseOrValue] - ): string; - encodeFunctionData(functionFragment: "getRPCs", values?: undefined): string; - encodeFunctionData( - functionFragment: "getSignedBlock", - values: [PromiseOrValue, PromiseOrValue] - ): string; - encodeFunctionData( - functionFragment: "hasValidatorSigned", - values: [PromiseOrValue, PromiseOrValue] - ): string; - encodeFunctionData( - functionFragment: "parseRLPBlockNumber", - values: [PromiseOrValue] - ): string; - encodeFunctionData( - functionFragment: "signedBlocks", - values: [PromiseOrValue] - ): string; - encodeFunctionData(functionFragment: "voting", values?: undefined): string; - - decodeFunctionResult( - functionFragment: "addBlockchain", - data: BytesLike - ): Result; - decodeFunctionResult( - functionFragment: "addSignedBlocks", - data: BytesLike - ): Result; - decodeFunctionResult( - functionFragment: "blockHashes", - data: BytesLike - ): Result; - decodeFunctionResult(functionFragment: "consensus", data: BytesLike): Result; - decodeFunctionResult( - functionFragment: "enabledBlockchains", - data: BytesLike - ): Result; - decodeFunctionResult( - functionFragment: "getBlockHashByPayloadHash", - data: BytesLike - ): Result; - decodeFunctionResult(functionFragment: "getRPCs", data: BytesLike): Result; - decodeFunctionResult( - functionFragment: "getSignedBlock", - data: BytesLike - ): Result; - decodeFunctionResult( - functionFragment: "hasValidatorSigned", - data: BytesLike - ): Result; - decodeFunctionResult( - functionFragment: "parseRLPBlockNumber", - data: BytesLike - ): Result; - decodeFunctionResult( - functionFragment: "signedBlocks", - data: BytesLike - ): Result; - decodeFunctionResult(functionFragment: "voting", data: BytesLike): Result; - - events: { - "BlockAdded(address,uint256,bytes32,address[],uint256)": EventFragment; - "BlockchainAdded(uint256,string)": EventFragment; - "BlockchainRemoved(uint256)": EventFragment; - }; - - getEvent(nameOrSignatureOrTopic: "BlockAdded"): EventFragment; - getEvent(nameOrSignatureOrTopic: "BlockchainAdded"): EventFragment; - getEvent(nameOrSignatureOrTopic: "BlockchainRemoved"): EventFragment; -} - -export interface BlockAddedEventObject { - validator: string; - chainId: BigNumber; - rlpHeaderHash: string; - validators: string[]; - cycleEnd: BigNumber; -} -export type BlockAddedEvent = TypedEvent< - [string, BigNumber, string, string[], BigNumber], - BlockAddedEventObject ->; - -export type BlockAddedEventFilter = TypedEventFilter; - -export interface BlockchainAddedEventObject { - chainId: BigNumber; - rpc: string; -} -export type BlockchainAddedEvent = TypedEvent< - [BigNumber, string], - BlockchainAddedEventObject ->; - -export type BlockchainAddedEventFilter = TypedEventFilter; - -export interface BlockchainRemovedEventObject { - chainId: BigNumber; -} -export type BlockchainRemovedEvent = TypedEvent< - [BigNumber], - BlockchainRemovedEventObject ->; - -export type BlockchainRemovedEventFilter = - TypedEventFilter; - -export interface BlockHeaderRegistry extends BaseContract { - connect(signerOrProvider: Signer | Provider | string): this; - attach(addressOrName: string): this; - deployed(): Promise; - - interface: BlockHeaderRegistryInterface; - - queryFilter( - event: TypedEventFilter, - fromBlockOrBlockhash?: string | number | undefined, - toBlock?: string | number | undefined - ): Promise>; - - listeners( - eventFilter?: TypedEventFilter - ): Array>; - listeners(eventName?: string): Array; - removeAllListeners( - eventFilter: TypedEventFilter - ): this; - removeAllListeners(eventName?: string): this; - off: OnEvent; - on: OnEvent; - once: OnEvent; - removeListener: OnEvent; - - functions: { - addBlockchain( - chainId: PromiseOrValue, - rpc: PromiseOrValue, - overrides?: Overrides & { from?: PromiseOrValue } - ): Promise; - - addSignedBlocks( - blocks: BlockHeaderRegistry.BlockStruct[], - overrides?: Overrides & { from?: PromiseOrValue } - ): Promise; - - blockHashes( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - arg2: PromiseOrValue, - overrides?: CallOverrides - ): Promise<[string]>; - - consensus(overrides?: CallOverrides): Promise<[string]>; - - enabledBlockchains( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise<[string, BigNumber] & { rpc: string; chainId: BigNumber }>; - - getBlockHashByPayloadHash( - payloadHash: PromiseOrValue, - overrides?: CallOverrides - ): Promise<[string] & { blockHash: string }>; - - getRPCs( - overrides?: CallOverrides - ): Promise<[BlockHeaderRegistry.BlockchainStructOutput[]]>; - - getSignedBlock( - chainId: PromiseOrValue, - number: PromiseOrValue, - overrides?: CallOverrides - ): Promise< - [BlockHeaderRegistry.SignedBlockStructOutput] & { - signedBlock: BlockHeaderRegistry.SignedBlockStructOutput; - } - >; - - hasValidatorSigned( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - overrides?: CallOverrides - ): Promise<[boolean]>; - - parseRLPBlockNumber( - rlpHeader: PromiseOrValue, - overrides?: CallOverrides - ): Promise<[BigNumber] & { blockNumber: BigNumber }>; - - signedBlocks( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise< - [BigNumber, string] & { cycleEnd: BigNumber; blockHash: string } - >; - - voting(overrides?: CallOverrides): Promise<[string]>; - }; - - addBlockchain( - chainId: PromiseOrValue, - rpc: PromiseOrValue, - overrides?: Overrides & { from?: PromiseOrValue } - ): Promise; - - addSignedBlocks( - blocks: BlockHeaderRegistry.BlockStruct[], - overrides?: Overrides & { from?: PromiseOrValue } - ): Promise; - - blockHashes( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - arg2: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - consensus(overrides?: CallOverrides): Promise; - - enabledBlockchains( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise<[string, BigNumber] & { rpc: string; chainId: BigNumber }>; - - getBlockHashByPayloadHash( - payloadHash: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - getRPCs( - overrides?: CallOverrides - ): Promise; - - getSignedBlock( - chainId: PromiseOrValue, - number: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - hasValidatorSigned( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - parseRLPBlockNumber( - rlpHeader: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - signedBlocks( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise<[BigNumber, string] & { cycleEnd: BigNumber; blockHash: string }>; - - voting(overrides?: CallOverrides): Promise; - - callStatic: { - addBlockchain( - chainId: PromiseOrValue, - rpc: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - addSignedBlocks( - blocks: BlockHeaderRegistry.BlockStruct[], - overrides?: CallOverrides - ): Promise; - - blockHashes( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - arg2: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - consensus(overrides?: CallOverrides): Promise; - - enabledBlockchains( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise<[string, BigNumber] & { rpc: string; chainId: BigNumber }>; - - getBlockHashByPayloadHash( - payloadHash: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - getRPCs( - overrides?: CallOverrides - ): Promise; - - getSignedBlock( - chainId: PromiseOrValue, - number: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - hasValidatorSigned( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - parseRLPBlockNumber( - rlpHeader: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - signedBlocks( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise< - [BigNumber, string] & { cycleEnd: BigNumber; blockHash: string } - >; - - voting(overrides?: CallOverrides): Promise; - }; - - filters: { - "BlockAdded(address,uint256,bytes32,address[],uint256)"( - validator?: PromiseOrValue | null, - chainId?: PromiseOrValue | null, - rlpHeaderHash?: PromiseOrValue | null, - validators?: null, - cycleEnd?: null - ): BlockAddedEventFilter; - BlockAdded( - validator?: PromiseOrValue | null, - chainId?: PromiseOrValue | null, - rlpHeaderHash?: PromiseOrValue | null, - validators?: null, - cycleEnd?: null - ): BlockAddedEventFilter; - - "BlockchainAdded(uint256,string)"( - chainId?: null, - rpc?: null - ): BlockchainAddedEventFilter; - BlockchainAdded(chainId?: null, rpc?: null): BlockchainAddedEventFilter; - - "BlockchainRemoved(uint256)"(chainId?: null): BlockchainRemovedEventFilter; - BlockchainRemoved(chainId?: null): BlockchainRemovedEventFilter; - }; - - estimateGas: { - addBlockchain( - chainId: PromiseOrValue, - rpc: PromiseOrValue, - overrides?: Overrides & { from?: PromiseOrValue } - ): Promise; - - addSignedBlocks( - blocks: BlockHeaderRegistry.BlockStruct[], - overrides?: Overrides & { from?: PromiseOrValue } - ): Promise; - - blockHashes( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - arg2: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - consensus(overrides?: CallOverrides): Promise; - - enabledBlockchains( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - getBlockHashByPayloadHash( - payloadHash: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - getRPCs(overrides?: CallOverrides): Promise; - - getSignedBlock( - chainId: PromiseOrValue, - number: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - hasValidatorSigned( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - parseRLPBlockNumber( - rlpHeader: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - signedBlocks( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - voting(overrides?: CallOverrides): Promise; - }; - - populateTransaction: { - addBlockchain( - chainId: PromiseOrValue, - rpc: PromiseOrValue, - overrides?: Overrides & { from?: PromiseOrValue } - ): Promise; - - addSignedBlocks( - blocks: BlockHeaderRegistry.BlockStruct[], - overrides?: Overrides & { from?: PromiseOrValue } - ): Promise; - - blockHashes( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - arg2: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - consensus(overrides?: CallOverrides): Promise; - - enabledBlockchains( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - getBlockHashByPayloadHash( - payloadHash: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - getRPCs(overrides?: CallOverrides): Promise; - - getSignedBlock( - chainId: PromiseOrValue, - number: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - hasValidatorSigned( - arg0: PromiseOrValue, - arg1: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - parseRLPBlockNumber( - rlpHeader: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - signedBlocks( - arg0: PromiseOrValue, - overrides?: CallOverrides - ): Promise; - - voting(overrides?: CallOverrides): Promise; - }; -} diff --git a/packages/bridge-contracts/typechain-types/hardhat.d.ts b/packages/bridge-contracts/typechain-types/hardhat.d.ts index 1e1a43a..2993568 100644 --- a/packages/bridge-contracts/typechain-types/hardhat.d.ts +++ b/packages/bridge-contracts/typechain-types/hardhat.d.ts @@ -156,18 +156,6 @@ declare module "hardhat/types/runtime" { name: "UniswapPair", signerOrOptions?: ethers.Signer | FactoryOptions ): Promise; - getContractFactory( - name: "IFeesFormula", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "IGoodDollarCustom", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "ISuperGoodDollar", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; getContractFactory( name: "DAOContract", signerOrOptions?: ethers.Signer | FactoryOptions @@ -344,10 +332,6 @@ declare module "hardhat/types/runtime" { name: "UUPSUpgradeable", signerOrOptions?: ethers.Signer | FactoryOptions ): Promise; - getContractFactory( - name: "IERC20PermitUpgradeable", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; getContractFactory( name: "ContextUpgradeable", signerOrOptions?: ethers.Signer | FactoryOptions @@ -376,10 +360,6 @@ declare module "hardhat/types/runtime" { name: "IBeacon", signerOrOptions?: ethers.Signer | FactoryOptions ): Promise; - getContractFactory( - name: "ERC1967Proxy", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; getContractFactory( name: "ERC1967Upgrade", signerOrOptions?: ethers.Signer | FactoryOptions @@ -404,18 +384,6 @@ declare module "hardhat/types/runtime" { name: "IERC20", signerOrOptions?: ethers.Signer | FactoryOptions ): Promise; - getContractFactory( - name: "IERC721Metadata", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "IERC721", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; - getContractFactory( - name: "IERC777", - signerOrOptions?: ethers.Signer | FactoryOptions - ): Promise; getContractFactory( name: "IERC165", signerOrOptions?: ethers.Signer | FactoryOptions @@ -725,21 +693,6 @@ declare module "hardhat/types/runtime" { address: string, signer?: ethers.Signer ): Promise; - getContractAt( - name: "IFeesFormula", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "IGoodDollarCustom", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "ISuperGoodDollar", - address: string, - signer?: ethers.Signer - ): Promise; getContractAt( name: "DAOContract", address: string, @@ -960,11 +913,6 @@ declare module "hardhat/types/runtime" { address: string, signer?: ethers.Signer ): Promise; - getContractAt( - name: "IERC20PermitUpgradeable", - address: string, - signer?: ethers.Signer - ): Promise; getContractAt( name: "ContextUpgradeable", address: string, @@ -1000,11 +948,6 @@ declare module "hardhat/types/runtime" { address: string, signer?: ethers.Signer ): Promise; - getContractAt( - name: "ERC1967Proxy", - address: string, - signer?: ethers.Signer - ): Promise; getContractAt( name: "ERC1967Upgrade", address: string, @@ -1035,21 +978,6 @@ declare module "hardhat/types/runtime" { address: string, signer?: ethers.Signer ): Promise; - getContractAt( - name: "IERC721Metadata", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "IERC721", - address: string, - signer?: ethers.Signer - ): Promise; - getContractAt( - name: "IERC777", - address: string, - signer?: ethers.Signer - ): Promise; getContractAt( name: "IERC165", address: string, diff --git a/packages/bridge-contracts/typechain-types/index.ts b/packages/bridge-contracts/typechain-types/index.ts index a714a9d..e7a2fd8 100644 --- a/packages/bridge-contracts/typechain-types/index.ts +++ b/packages/bridge-contracts/typechain-types/index.ts @@ -84,12 +84,6 @@ export type { UniswapFactory } from "./@gooddollar/goodprotocol/contracts/Interf export { UniswapFactory__factory } from "./factories/@gooddollar/goodprotocol/contracts/Interfaces.sol/UniswapFactory__factory"; export type { UniswapPair } from "./@gooddollar/goodprotocol/contracts/Interfaces.sol/UniswapPair"; export { UniswapPair__factory } from "./factories/@gooddollar/goodprotocol/contracts/Interfaces.sol/UniswapPair__factory"; -export type { IFeesFormula } from "./@gooddollar/goodprotocol/contracts/token/IFeesFormula"; -export { IFeesFormula__factory } from "./factories/@gooddollar/goodprotocol/contracts/token/IFeesFormula__factory"; -export type { IGoodDollarCustom } from "./@gooddollar/goodprotocol/contracts/token/superfluid/ISuperGoodDollar.sol/IGoodDollarCustom"; -export { IGoodDollarCustom__factory } from "./factories/@gooddollar/goodprotocol/contracts/token/superfluid/ISuperGoodDollar.sol/IGoodDollarCustom__factory"; -export type { ISuperGoodDollar } from "./@gooddollar/goodprotocol/contracts/token/superfluid/ISuperGoodDollar.sol/ISuperGoodDollar"; -export { ISuperGoodDollar__factory } from "./factories/@gooddollar/goodprotocol/contracts/token/superfluid/ISuperGoodDollar.sol/ISuperGoodDollar__factory"; export type { DAOContract } from "./@gooddollar/goodprotocol/contracts/utils/DAOContract"; export { DAOContract__factory } from "./factories/@gooddollar/goodprotocol/contracts/utils/DAOContract__factory"; export type { DAOUpgradeableContract } from "./@gooddollar/goodprotocol/contracts/utils/DAOUpgradeableContract"; @@ -178,8 +172,6 @@ export type { Initializable } from "./@openzeppelin/contracts-upgradeable/proxy/ export { Initializable__factory } from "./factories/@openzeppelin/contracts-upgradeable/proxy/utils/Initializable__factory"; export type { UUPSUpgradeable } from "./@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable"; export { UUPSUpgradeable__factory } from "./factories/@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable__factory"; -export type { IERC20PermitUpgradeable } from "./@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable"; -export { IERC20PermitUpgradeable__factory } from "./factories/@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable__factory"; export type { ContextUpgradeable } from "./@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable"; export { ContextUpgradeable__factory } from "./factories/@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable__factory"; export type { ERC165Upgradeable } from "./@openzeppelin/contracts-upgradeable/utils/introspection/ERC165Upgradeable"; @@ -204,12 +196,6 @@ export type { IERC20Permit } from "./@openzeppelin/contracts/token/ERC20/extensi export { IERC20Permit__factory } from "./factories/@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit__factory"; export type { IERC20 } from "./@openzeppelin/contracts/token/ERC20/IERC20"; export { IERC20__factory } from "./factories/@openzeppelin/contracts/token/ERC20/IERC20__factory"; -export type { IERC721Metadata } from "./@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata"; -export { IERC721Metadata__factory } from "./factories/@openzeppelin/contracts/token/ERC721/extensions/IERC721Metadata__factory"; -export type { IERC721 } from "./@openzeppelin/contracts/token/ERC721/IERC721"; -export { IERC721__factory } from "./factories/@openzeppelin/contracts/token/ERC721/IERC721__factory"; -export type { IERC777 } from "./@openzeppelin/contracts/token/ERC777/IERC777"; -export { IERC777__factory } from "./factories/@openzeppelin/contracts/token/ERC777/IERC777__factory"; export type { IERC165 } from "./@openzeppelin/contracts/utils/introspection/IERC165"; export { IERC165__factory } from "./factories/@openzeppelin/contracts/utils/introspection/IERC165__factory"; export type { BlockHeaderRegistry } from "./contracts/blockRegistry/BlockHeaderRegistry"; @@ -240,6 +226,8 @@ export type { GoodDollarMinterBurner } from "./contracts/oft/GoodDollarMinterBur export { GoodDollarMinterBurner__factory } from "./factories/contracts/oft/GoodDollarMinterBurner.sol/GoodDollarMinterBurner__factory"; export type { GoodDollarOFTAdapter } from "./contracts/oft/GoodDollarOFTAdapter.sol/GoodDollarOFTAdapter"; export { GoodDollarOFTAdapter__factory } from "./factories/contracts/oft/GoodDollarOFTAdapter.sol/GoodDollarOFTAdapter__factory"; +export type { ISuperGoodDollar } from "./contracts/oft/interfaces/ISuperGoodDollar"; +export { ISuperGoodDollar__factory } from "./factories/contracts/oft/interfaces/ISuperGoodDollar__factory"; export type { ConsensusMock } from "./contracts/test/ConsensusMock"; export { ConsensusMock__factory } from "./factories/contracts/test/ConsensusMock__factory"; export type { Multicall } from "./contracts/test/MultiCall.sol/Multicall"; From 04e4c2a5bfc25dd56adee3b396e659bdec5e7f7b Mon Sep 17 00:00:00 2001 From: blueogin Date: Mon, 2 Mar 2026 12:12:41 -0500 Subject: [PATCH 34/41] fix: correct logic in GoodDollarOFTAdapter to ensure daily limits are updated before processing receive requests --- .../contracts/oft/GoodDollarOFTAdapter.sol | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index 58dac73..4e28d27 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -379,13 +379,11 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { _origin.srcEid ); emit ReceiveRequestFailed(_guid, toAddress, amountLD, _origin.srcEid); - revert BRIDGE_LIMITS(reason); + } else{ + bridgeDailyLimit.bridged24Hours += amountLD; + accountsDailyLimit[toAddress].bridged24Hours += amountLD; + super._lzReceive(_origin, _guid, _message, _executor, _extraData); } - - // 4. Passed both checks: update counters and complete receive - bridgeDailyLimit.bridged24Hours += amountLD; - accountsDailyLimit[toAddress].bridged24Hours += amountLD; - super._lzReceive(_origin, _guid, _message, _executor, _extraData); } /** * @notice Mints tokens to the specified address upon receiving them From bd2e5d11975c19f360e12f6654e9a95108b614a7 Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 3 Mar 2026 06:43:26 -0500 Subject: [PATCH 35/41] chore: update deployment addresses for GoodDollarMinterBurner and GoodDollarOFTAdapter, and adjust bridging amount in tests --- packages/bridge-contracts/release/deployment-oft.json | 8 ++++---- packages/bridge-contracts/test/oft/bridge-oft-token.ts | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/bridge-contracts/release/deployment-oft.json b/packages/bridge-contracts/release/deployment-oft.json index 3222b9a..f392523 100644 --- a/packages/bridge-contracts/release/deployment-oft.json +++ b/packages/bridge-contracts/release/deployment-oft.json @@ -1,10 +1,10 @@ { "development-xdc": { - "GoodDollarMinterBurner": "0xB937421FD27d8C7848Ae8da16cF2B9D2ba9b5A62", - "GoodDollarOFTAdapter": "0x8B72D79be1E2A8d4c82B9B86E539fB2f4b298C1c" + "GoodDollarMinterBurner": "0xb1C650B848df596Ab622AEB01FE1796dE04D827F", + "GoodDollarOFTAdapter": "0x85d5F87EDf80e01Ba5a46aB4b98F0f31d7C6a3e4" }, "development-celo": { - "GoodDollarMinterBurner": "0x4952d1ab3956145c3411A3ebB32ed1Eece7BC89B", - "GoodDollarOFTAdapter": "0x4e2a6583ab861A64BE02865257eC4C6A310e4696" + "GoodDollarMinterBurner": "0xb1C650B848df596Ab622AEB01FE1796dE04D827F", + "GoodDollarOFTAdapter": "0x85d5F87EDf80e01Ba5a46aB4b98F0f31d7C6a3e4" } } diff --git a/packages/bridge-contracts/test/oft/bridge-oft-token.ts b/packages/bridge-contracts/test/oft/bridge-oft-token.ts index 8d4dcbf..3b3332a 100644 --- a/packages/bridge-contracts/test/oft/bridge-oft-token.ts +++ b/packages/bridge-contracts/test/oft/bridge-oft-token.ts @@ -100,7 +100,7 @@ const main = async () => { const oftAdapter = await ethers.getContractAt("GoodDollarOFTAdapter", oftAdapterAddress); // Amount to bridge: 1 G$ = 1e18 - const amount = ethers.utils.parseEther("50"); + const amount = ethers.utils.parseEther("90"); console.log("\nAmount to bridge:", ethers.utils.formatEther(amount), "G$"); // Check token balance From 4a1e1a34cfb84fb24357a0cfcc0512a9f39cad50 Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 3 Mar 2026 07:41:42 -0500 Subject: [PATCH 36/41] feat: enhance deployment script to differentiate between development and production environments for GoodDollarMinterBurner and GoodDollarOFTAdapter --- packages/bridge-contracts/deploy/deployOFT.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/packages/bridge-contracts/deploy/deployOFT.ts b/packages/bridge-contracts/deploy/deployOFT.ts index 496b29a..d2fddfc 100644 --- a/packages/bridge-contracts/deploy/deployOFT.ts +++ b/packages/bridge-contracts/deploy/deployOFT.ts @@ -103,9 +103,13 @@ const func: DeployFunction = async function (hre) { } console.log('✅ Verified Avatar address:', avatarAddress); + let isDevelopment = false; + if (network.name.includes('development')) { + isDevelopment = true; + } // --- GoodDollarMinterBurner (hardhat-deploy: deterministic proxy + implementation + execute initialize) --- const minterBurnerProxySalt = ethers.utils.keccak256( - ethers.utils.toUtf8Bytes('GoodDollarMinterBurnerV1'), + ethers.utils.toUtf8Bytes(isDevelopment ? 'Development-GoodDollarMinterBurnerV1' : 'Production-GoodDollarMinterBurnerV1'), ); const minterBurnerProxyDeploy = await deployments.deterministic('GoodDollarMinterBurner', { contract: 'ERC1967Proxy', @@ -162,7 +166,7 @@ const func: DeployFunction = async function (hre) { // --- GoodDollarOFTAdapter (hardhat-deploy: deterministic proxy + implementation with constructor + execute initialize) --- const oftAdapterProxySalt = ethers.utils.keccak256( - ethers.utils.toUtf8Bytes('GoodDollarOFTAdapterV1'), + ethers.utils.toUtf8Bytes(isDevelopment ? 'Development-GoodDollarOFTAdapterV1' : 'Production-GoodDollarOFTAdapterV1'), ); const oftAdapterProxyDeploy = await deployments.deterministic('GoodDollarOFTAdapter', { contract: 'ERC1967Proxy', From b748e7ffdad70d29b3cf2291f0b0f0c078d161b1 Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 3 Mar 2026 08:05:05 -0500 Subject: [PATCH 37/41] chore: update deployment addresses for GoodDollarMinterBurner and GoodDollarOFTAdapter in development environments --- packages/bridge-contracts/release/deployment-oft.json | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/bridge-contracts/release/deployment-oft.json b/packages/bridge-contracts/release/deployment-oft.json index f392523..6cfbfdd 100644 --- a/packages/bridge-contracts/release/deployment-oft.json +++ b/packages/bridge-contracts/release/deployment-oft.json @@ -1,10 +1,10 @@ { "development-xdc": { - "GoodDollarMinterBurner": "0xb1C650B848df596Ab622AEB01FE1796dE04D827F", - "GoodDollarOFTAdapter": "0x85d5F87EDf80e01Ba5a46aB4b98F0f31d7C6a3e4" + "GoodDollarMinterBurner": "0xc10A3215b7228f702997274a0D2AB7445Dd37813", + "GoodDollarOFTAdapter": "0x4857E893d6C77B12e9c724f8E12D5F37E17697FF" }, "development-celo": { - "GoodDollarMinterBurner": "0xb1C650B848df596Ab622AEB01FE1796dE04D827F", - "GoodDollarOFTAdapter": "0x85d5F87EDf80e01Ba5a46aB4b98F0f31d7C6a3e4" + "GoodDollarMinterBurner": "0xc10A3215b7228f702997274a0D2AB7445Dd37813", + "GoodDollarOFTAdapter": "0x4857E893d6C77B12e9c724f8E12D5F37E17697FF" } } From aa25144bf7d680d02ca1dc47c0e71e33caed1f2e Mon Sep 17 00:00:00 2001 From: blueogin Date: Tue, 3 Mar 2026 08:08:41 -0500 Subject: [PATCH 38/41] fix: update OPTIMISTIC_WINDOW to 5 minutes and modify approveFailedRequest function to remove owner restriction in GoodDollarOFTAdapter --- .../bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index 520b1af..83642cc 100644 --- a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol +++ b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol @@ -237,7 +237,7 @@ contract GoodDollarOFTAdapter is UUPSUpgradeable, OFTCoreUpgradeable { emit BridgePaused(_isPaused); } - function approveFailedRequest(bytes32 _guid) external onlyOwner { + function approveFailedRequest(bytes32 _guid) external { FailedReceiveRequest memory request = failedReceiveRequests[_guid]; require(request.timestamp + OPTIMISTIC_WINDOW < block.timestamp, 'optimistic period not ended'); require(request.failed, 'request not failed'); From 8ecceb649ef7b5f4a44878e589a5f0a6932dc994 Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 5 Mar 2026 08:29:51 -0500 Subject: [PATCH 39/41] chore: clean up .env.sample by removing deprecated variables related to bridging configuration --- packages/bridge-contracts/.env.sample | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/bridge-contracts/.env.sample b/packages/bridge-contracts/.env.sample index ba4ccb6..125f63c 100644 --- a/packages/bridge-contracts/.env.sample +++ b/packages/bridge-contracts/.env.sample @@ -1,21 +1,4 @@ -ONLY_WHITELISTED= # Whether only whitelisted addresses can bridge -SKIP_BRIDGE_TEST= # Skip bridge test step -SKIP_LIMITS= # Skip setting limits step - -ACCOUNT_DAILY_LIMIT= # Account daily limit in G$ (18 decimals) -DAILY_LIMIT= # Daily bridge limit in G$ (18 decimals) -TX_LIMIT= # Transaction limit in G$ (18 decimals) -MIN_AMOUNT= - PRIVATE_KEY= PUBLIC_KEY= ETHERSCAN_KEY= -ALCHEMY_KEY= - -FORK_CHAIN_ID= -DST_EID= -DST_OFT_ADAPTER= -LZ_LABS_DVN_CELO= -LZ_LABS_DVN_XDC= -XDC_RPC_URL= From 59df93176c27c572c4ecec7823ca1b82ffaeca56 Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 5 Mar 2026 08:41:44 -0500 Subject: [PATCH 40/41] chore: remove outdated configuration options and troubleshooting sections from OFT_CONFIGURING_GUIDE.md for clarity --- .../test/oft/OFT_CONFIGURING_GUIDE.md | 42 ------------------- 1 file changed, 42 deletions(-) diff --git a/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md b/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md index 3d28507..60bd551 100644 --- a/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md +++ b/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md @@ -40,21 +40,6 @@ All configuration values are stored in `test/oft/oft.config.json`. Each network } ``` -### Configuration Options - -- **skipTransferOwnership**: Skip transferring OFT adapter ownership to DAO Avatar -- **skipWiring**: Skip LayerZero wiring (requires manual configuration) -- **skipLimits**: Skip setting bridge limits -- **skipBridgeTest**: Skip bridge functionality test -- **limits**: Bridge limit configuration - - **dailyLimit**: Daily bridge limit in G$ (converted to wei automatically) - - **txLimit**: Per-transaction limit in G$ (converted to wei automatically) - - **accountDailyLimit**: Per-account daily limit in G$ (converted to wei automatically) - - **minAmount**: Minimum bridge amount in G$ (converted to wei automatically) - - **onlyWhitelisted**: Whether to restrict bridging to whitelisted addresses - -**Note**: Limit values can be specified in decimal format (e.g., "5000" for 5,000 G$). The scripts automatically convert them to wei (18 decimals). - ## Manual Configuration: Step-by-Step If you prefer to configure each network individually or need more control, follow these steps: @@ -183,30 +168,3 @@ After configuration, verify the setup: 4. **Check ownership**: Verify OFT adapter is owned by DAO Avatar 5. **Check LayerZero peers**: Verify peer connections are set between chains 6. **Check limits**: Verify bridge limits are set correctly - -## Troubleshooting - -### Bridge Test Fails - -**Error**: `NoPeer` or peer not configured - -**Solution**: -- Ensure LayerZero wiring completed successfully -- Verify peer addresses are set correctly in `layerzero.config.ts` -- Check that OFT adapters are deployed on both networks - -**Error**: Insufficient balance - -**Solution**: -- Ensure you have sufficient G$ tokens on the source chain -- Ensure you have sufficient native tokens (XDC/CELO) for gas and fees -- Approve MinterBurner to burn tokens if needed - -### Limits Not Set - -**Error**: No limits configuration found - -**Solution**: -- Ensure `oft.config.json` has an entry for your network -- Verify the `limits` object is properly formatted -- Check that `skipLimits` is not set to `true` From 86d4b555c138354b9a3f92281eb5f7a82f3bff10 Mon Sep 17 00:00:00 2001 From: blueogin Date: Thu, 5 Mar 2026 08:44:54 -0500 Subject: [PATCH 41/41] chore: remove unnecessary configuration options from oft.config.json to streamline settings --- .../bridge-contracts/test/oft/oft.config.json | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/packages/bridge-contracts/test/oft/oft.config.json b/packages/bridge-contracts/test/oft/oft.config.json index 32121ed..712b979 100644 --- a/packages/bridge-contracts/test/oft/oft.config.json +++ b/packages/bridge-contracts/test/oft/oft.config.json @@ -1,9 +1,5 @@ { "development-xdc": { - "skipTransferOwnership": false, - "skipWiring": false, - "skipLimits": false, - "skipBridgeTest": true, "limits": { "dailyLimit": "1000", "txLimit": "100", @@ -13,10 +9,6 @@ } }, "development-celo": { - "skipTransferOwnership": false, - "skipWiring": false, - "skipLimits": false, - "skipBridgeTest": true, "limits": { "dailyLimit": "1000", "txLimit": "95", @@ -26,10 +18,6 @@ } }, "production-xdc": { - "skipTransferOwnership": false, - "skipWiring": false, - "skipLimits": false, - "skipBridgeTest": true, "limits": { "dailyLimit": "1000000", "txLimit": "100000", @@ -39,10 +27,6 @@ } }, "production-celo": { - "skipTransferOwnership": false, - "skipWiring": false, - "skipLimits": false, - "skipBridgeTest": true, "limits": { "dailyLimit": "1000000", "txLimit": "100000",