diff --git a/packages/bridge-contracts/.env.sample b/packages/bridge-contracts/.env.sample new file mode 100644 index 0000000..125f63c --- /dev/null +++ b/packages/bridge-contracts/.env.sample @@ -0,0 +1,4 @@ +PRIVATE_KEY= +PUBLIC_KEY= + +ETHERSCAN_KEY= diff --git a/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol b/packages/bridge-contracts/contracts/oft/GoodDollarOFTAdapter.sol index 143ef31..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'); @@ -364,11 +364,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); } - bridgeDailyLimit.bridged24Hours += amountLD; - accountsDailyLimit[toAddress].bridged24Hours += amountLD; - super._lzReceive(_origin, _guid, _message, _executor, _extraData); } /** * @notice Mints tokens to the specified address upon receiving them diff --git a/packages/bridge-contracts/deploy/deployOFT.ts b/packages/bridge-contracts/deploy/deployOFT.ts new file mode 100644 index 0000000..d2fddfc --- /dev/null +++ b/packages/bridge-contracts/deploy/deployOFT.ts @@ -0,0 +1,249 @@ +/*** + * Hardhat-deploy script for GoodDollar OFT (Omnichain Fungible Token) contracts + * + * 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 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 { DeployFunction } from 'hardhat-deploy/types'; +import { ethers } 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 } = { + 'development-celo': '0x1a44076050125825900e736c501f859c50fE728c', + 'production-celo': '0x1a44076050125825900e736c501f859c50fE728c', + 'development-xdc': '0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa', + 'production-xdc': '0xcb566e3B6934Fa77258d68ea18E931fa75e1aaAa', +}; + +const func: DeployFunction = async function (hre) { + const { deployments, network } = hre; + const [root] = await ethers.getSigners(); + + const networkName = network.name; + + console.log('Deployment signer:', { + 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 + 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); + + 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(isDevelopment ? 'Development-GoodDollarMinterBurnerV1' : 'Production-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); + + 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 { + console.log('GoodDollarMinterBurner already initialized'); + } + + // Update release file for MinterBurner + if (!release[networkName]) { + release[networkName] = {}; + } + release[networkName].GoodDollarMinterBurner = minterBurnerAddress; + await fse.writeJSON('release/deployment-oft.json', release, { spaces: 2 }); + + // 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(isDevelopment ? 'Development-GoodDollarOFTAdapterV1' : 'Production-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 { + 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)'); + 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); + console.log('========================\n'); +}; + +export default func; +func.tags = ['OFT', 'GoodDollar']; 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/hardhat.config.ts b/packages/bridge-contracts/hardhat.config.ts index 25a916d..9466402 100644 --- a/packages/bridge-contracts/hardhat.config.ts +++ b/packages/bridge-contracts/hardhat.config.ts @@ -149,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.xinfin.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.xinfin.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, @@ -190,6 +228,16 @@ const config: HardhatUserConfig = { contractSizer: { runOnCompile: true, }, + 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', + }, }; export default config; diff --git a/packages/bridge-contracts/layerzero.config.ts b/packages/bridge-contracts/layerzero.config.ts index 7570da1..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 "./releases/deployment.json"; +import dao from "./release/deployment-oft.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 2057ed1..2c523d5 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", @@ -56,7 +60,7 @@ "ethers": "^5.*", "hardhat": "2.26", "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/packages/bridge-contracts/release/deployment-oft.json b/packages/bridge-contracts/release/deployment-oft.json new file mode 100644 index 0000000..6cfbfdd --- /dev/null +++ b/packages/bridge-contracts/release/deployment-oft.json @@ -0,0 +1,10 @@ +{ + "development-xdc": { + "GoodDollarMinterBurner": "0xc10A3215b7228f702997274a0D2AB7445Dd37813", + "GoodDollarOFTAdapter": "0x4857E893d6C77B12e9c724f8E12D5F37E17697FF" + }, + "development-celo": { + "GoodDollarMinterBurner": "0xc10A3215b7228f702997274a0D2AB7445Dd37813", + "GoodDollarOFTAdapter": "0x4857E893d6C77B12e9c724f8E12D5F37E17697FF" + } +} diff --git a/packages/bridge-contracts/release/deployment.json b/packages/bridge-contracts/release/deployment.json index 24f06f7..a93d348 100644 --- a/packages/bridge-contracts/release/deployment.json +++ b/packages/bridge-contracts/release/deployment.json @@ -1,17 +1,17 @@ { - "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" + } +} diff --git a/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md b/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md new file mode 100644 index 0000000..60bd551 --- /dev/null +++ b/packages/bridge-contracts/test/oft/OFT_CONFIGURING_GUIDE.md @@ -0,0 +1,170 @@ +# 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 `test/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 + } +} +``` + +## 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 using hardhat-deploy: + +```bash +# Deploy on XDC +npx hardhat deploy --tags OFT --network development-xdc + +# Deploy on CELO +npx hardhat deploy --tags OFT --network development-celo +``` + +This deployment script will: +- Deploy `GoodDollarMinterBurner` (upgradeable proxy) +- Deploy `GoodDollarOFTAdapter` (upgradeable proxy) +- Save contract addresses to `release/deployment-oft.json` +- Save deployments to hardhat-deploy's deployment system + +**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 test/oft/set-oft-operator.ts --network development-xdc + +# Set operator on CELO +npx hardhat run test/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: + +```bash +# Grant on XDC +yarn hardhat run test/oft/grant-minter-role.ts --network development-xdc + +# Grant on 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. + +### Step 4: 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 5: Set Bridge Limits + +Configure bridge limits using values from `oft.config.json`: + +```bash +# Set limits on XDC +yarn hardhat run test/oft/set-minter-burner-limits.ts --network development-xdc + +# Set limits on 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. + +### 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 test/oft/bridge-oft-token.ts --network development-xdc + +# Bridge from CELO to XDC +yarn hardhat run test/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 + +### 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 test/oft/transfer-oft-adapter-ownership.ts --network development-xdc + +# Transfer on 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. + +## Configuration Verification + +After configuration, verify the setup: + +1. **Check contract deployments**: Verify addresses in `release/deployment-oft.json` +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 diff --git a/packages/bridge-contracts/test/oft/bridge-oft-token.ts b/packages/bridge-contracts/test/oft/bridge-oft-token.ts new file mode 100644 index 0000000..3b3332a --- /dev/null +++ b/packages/bridge-contracts/test/oft/bridge-oft-token.ts @@ -0,0 +1,419 @@ +/*** + * Script to bridge 1 G$ token between XDC and CELO using LayerZero OFT adapter + * + * Usage: + * # Bridge from XDC to CELO: + * npx hardhat run test/oft/bridge-oft-token.ts --network production-xdc + * # or + * npx hardhat run test/oft/bridge-oft-token.ts --network development-xdc + * + * # Bridge from CELO to XDC: + * npx hardhat run test/oft/bridge-oft-token.ts --network production-celo + * # or + * npx hardhat run test/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-oft.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-oft.json for ${networkName}`); + } + + if (!tokenAddress) { + throw new Error(`GoodDollar token not found in GoodProtocol deployment.json for ${networkName}`); + } + + if (!minterBurnerAddress) { + throw new Error(`GoodDollarMinterBurner not found in deployment-oft.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("90"); + 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-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: test/oft/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(destEndpointId, 1, extraOptions); + 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 test/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 test/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/test/oft/grant-minter-role.ts b/packages/bridge-contracts/test/oft/grant-minter-role.ts new file mode 100644 index 0000000..a74716f --- /dev/null +++ b/packages/bridge-contracts/test/oft/grant-minter-role.ts @@ -0,0 +1,138 @@ +/*** + * Script to grant MINTER_ROLE to GoodDollarMinterBurner contract on development-celo + * Uses genericCall through Avatar/Controller to execute the transaction + * + * Usage: + * 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 + */ + +import { network, ethers } from "hardhat"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment-oft.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 GoodProtocol deployment.json for ${networkName}`); + } + + if (!minterBurnerAddress) { + throw new Error(`GoodDollarMinterBurner not found in deployment-oft.json for ${networkName}`); + } + + if (!controllerAddress) { + throw new Error(`Controller not found in GoodProtocol deployment.json for ${networkName}`); + } + + if (!avatarAddress) { + throw new Error(`Avatar not found in GoodProtocol 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 local interface file + const token = await ethers.getContractAt( + "contracts/oft/interfaces/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/test/oft/oft.config.json b/packages/bridge-contracts/test/oft/oft.config.json new file mode 100644 index 0000000..712b979 --- /dev/null +++ b/packages/bridge-contracts/test/oft/oft.config.json @@ -0,0 +1,38 @@ +{ + "development-xdc": { + "limits": { + "dailyLimit": "1000", + "txLimit": "100", + "accountDailyLimit": "100", + "minAmount": "1", + "onlyWhitelisted": false + } + }, + "development-celo": { + "limits": { + "dailyLimit": "1000", + "txLimit": "95", + "accountDailyLimit": "200", + "minAmount": "1", + "onlyWhitelisted": false + } + }, + "production-xdc": { + "limits": { + "dailyLimit": "1000000", + "txLimit": "100000", + "accountDailyLimit": "50000", + "minAmount": "10", + "onlyWhitelisted": false + } + }, + "production-celo": { + "limits": { + "dailyLimit": "1000000", + "txLimit": "100000", + "accountDailyLimit": "50000", + "minAmount": "10", + "onlyWhitelisted": false + } + } +} diff --git a/packages/bridge-contracts/test/oft/set-minter-burner-limits.ts b/packages/bridge-contracts/test/oft/set-minter-burner-limits.ts new file mode 100644 index 0000000..17b14dc --- /dev/null +++ b/packages/bridge-contracts/test/oft/set-minter-burner-limits.ts @@ -0,0 +1,184 @@ +/*** + * 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: + * npx hardhat run test/oft/set-minter-burner-limits.ts --network development-celo + * + * Configuration: + * 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 + */ + +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; + 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-oft.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-oft.json for ${networkName}`); + } + + if (!controllerAddress) { + throw new Error(`Controller not found in GoodProtocol deployment.json for ${networkName}`); + } + + if (!avatarAddress) { + throw new Error(`Avatar not found in GoodProtocol 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); + + // 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 test/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; + // 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(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 = { + 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 oftAdapter.setBridgeLimits(newLimits); + 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/test/oft/set-oft-operator.ts b/packages/bridge-contracts/test/oft/set-oft-operator.ts new file mode 100644 index 0000000..4293e8f --- /dev/null +++ b/packages/bridge-contracts/test/oft/set-oft-operator.ts @@ -0,0 +1,145 @@ +/*** + * 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); + }); +} diff --git a/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership-from-avatar.ts b/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership-from-avatar.ts new file mode 100644 index 0000000..93a5227 --- /dev/null +++ b/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership-from-avatar.ts @@ -0,0 +1,167 @@ +/*** + * Script to transfer ownership of GoodDollarOFTAdapter from DAO Avatar to current signer + * + * Usage: + * 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. + */ + +import { network, ethers } from "hardhat"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment-oft.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-oft.json for ${networkName}`); + } + + if (!avatarAddress) { + throw new Error(`Avatar not found in GoodProtocol deployment.json for ${networkName}`); + } + + if (!controllerAddress) { + throw new Error(`Controller not found in GoodProtocol 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/test/oft/transfer-oft-adapter-ownership.ts b/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership.ts new file mode 100644 index 0000000..8c5b229 --- /dev/null +++ b/packages/bridge-contracts/test/oft/transfer-oft-adapter-ownership.ts @@ -0,0 +1,251 @@ +/*** + * Script to transfer ownership of GoodDollarOFTAdapter to DAO Avatar + * + * Usage: + * 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. + */ + +import { network, ethers } from "hardhat"; +import Contracts from "@gooddollar/goodprotocol/releases/deployment.json"; +import release from "../../release/deployment-oft.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-oft.json for ${networkName}`); + } + + if (!avatarAddress) { + throw new Error(`Avatar not found in GoodProtocol 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-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` + + `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/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"; diff --git a/yarn.lock b/yarn.lock index dfa99ea..10bd1dd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2096,8 +2096,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" @@ -2105,6 +2108,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" @@ -2126,7 +2130,7 @@ __metadata: ethers: "npm:^5.*" hardhat: "npm:2.26" 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" @@ -2631,7 +2635,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: @@ -2803,7 +2807,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: @@ -2819,6 +2823,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" @@ -3039,7 +3053,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: @@ -12418,9 +12432,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" @@ -12433,6 +12447,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" @@ -12443,9 +12458,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 @@ -16401,13 +16416,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"