-
Notifications
You must be signed in to change notification settings - Fork 4
Feature: oft adapter step3 - deploy script and test #11
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
blueogin
wants to merge
62
commits into
feat/oft-adapter-step2
Choose a base branch
from
feat/oft-adatper-step3
base: feat/oft-adapter-step2
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
62 commits
Select commit
Hold shift + click to select a range
7d00674
chore: update hardhat configuration and dependencies for bridge contr…
blueogin 6a11b79
feat: enhance GoodDollarOFTAdapter with new bridge limits and account…
blueogin b54c09b
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin 56be154
feat: add .env.sample for configuration, update Hardhat config for XD…
blueogin 3b1ea49
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin b45c2ae
fix: clarify documentation in GoodDollarOFTAdapter by removing redund…
blueogin 89fae4a
chore: remove outdated verification script for GoodDollar OFT contracts
blueogin 76e9709
refactor: update ISuperGoodDollar interface implementation and remove…
blueogin d77238d
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin fb5d269
refactor: remove upgrade authorization logic and change deployment ki…
blueogin 1d08bce
feat: introduce deployment-oft.json for OFT contracts and update scri…
blueogin 124a1a1
feat: add oft.config.json for network-specific limit configurations a…
blueogin d1dc1d4
docs: add OFT configuration guide for cross-chain setup between XDC a…
blueogin 9d11522
refactor: remove unused initialization code from OFT deployment scrip…
blueogin c5b45df
chore: enable contract size checking on compile in Hardhat configuration
blueogin e907906
refactor: streamline GoodDollarOFTAdapter by removing unused structs …
blueogin 5c4c510
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin 30178c0
refactor: remove unused imports from GoodDollarOFTAdapter to enhance …
blueogin 71da58c
refactor: enhance GoodDollarOFTAdapter with UUPS upgradeability, upda…
blueogin e67caf1
refactor: update OFT deployment script to use UUPS upgradeability for…
blueogin 52284e0
refactor: reorganize GoodDollarOFTAdapter contract structure for impr…
blueogin 0f8d105
refactor: update Hardhat configuration to include Solidity version 0.…
blueogin a4b2c68
refactor: add feeRecipient parameter to GoodDollarOFTAdapter initiali…
blueogin b678f7b
feat: introduce deployOFT script for GoodDollar OFT contracts and rem…
blueogin dcfc3fb
fix: update contract path in grant-minter-role script to reflect new …
blueogin 866e1d1
feat: add deployment configuration for OFT contracts and update deplo…
blueogin 6ccca46
refactor: replace request approval mechanism with failed receive requ…
blueogin 73f995b
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin 5f6ed35
feat: add feeRecipient parameter to GoodDollarOFTAdapter initializati…
blueogin 2ac107d
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin c859ba3
feat: enhance deployment scripts for GoodDollarMinterBurner and GoodD…
blueogin a1dc6aa
feat: add ReceiveRequestFailed event to GoodDollarOFTAdapter for impr…
blueogin ffbc78c
feat: update deployment addresses for GoodDollarMinterBurner and Good…
blueogin 836764b
feat: enhance GoodDollarOFTAdapter with optimistic window for failed …
blueogin 257db1a
feat: implement OFT configuration scripts and documentation for bridg…
blueogin 11d66fb
chore: remove outdated deployment JSON files for GoodDollarMinterBurn…
blueogin 2b109b2
feat: refactor deployment script for GoodDollarMinterBurner to use de…
blueogin 365283e
chore: remove OFT configuration script for XDC and CELO networks
blueogin a98d554
chore: remove unused contract factory and type definitions for IFeesF…
blueogin 04e4c2a
fix: correct logic in GoodDollarOFTAdapter to ensure daily limits are…
blueogin bd2e5d1
chore: update deployment addresses for GoodDollarMinterBurner and Goo…
blueogin 9406240
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin 4a1e1a3
feat: enhance deployment script to differentiate between development …
blueogin b748e7f
chore: update deployment addresses for GoodDollarMinterBurner and Goo…
blueogin aa25144
fix: update OPTIMISTIC_WINDOW to 5 minutes and modify approveFailedRe…
blueogin 8ecceb6
chore: clean up .env.sample by removing deprecated variables related …
blueogin 59df931
chore: remove outdated configuration options and troubleshooting sect…
blueogin 86d4b55
chore: remove unnecessary configuration options from oft.config.json …
blueogin b368b8c
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin 75d4e99
feat: add token approval step for MinterBurner in bridge-oft-token te…
blueogin ac95378
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin 67c8191
feat: update XDC RPC URL in hardhat config and add upgrade script for…
blueogin 4274043
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin 9abc6ad
feat: add script to set bridge limits for GoodDollarOFTAdapter, enhan…
blueogin 82f7167
docs: update OFT_CONFIGURING_GUIDE.md to clarify optional steps for s…
blueogin 38ce793
fix: update deployment script for GoodDollarOFTAdapter and GoodDollar…
blueogin 5cd6658
refactor: implement CREATE2 salt for deterministic deployments of Goo…
blueogin dba7ec6
refactor: update deployment scripts to skip verification for 'develop…
blueogin 1c209f3
refactor: replace usage of deployment-oft.json with hardhat-deploy ar…
blueogin 4c87230
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin df199e2
Merge branch 'feat/oft-adapter-step2' into feat/oft-adatper-step3
blueogin 57507a2
refactor: rename GoodDollarMinterBurner to GoodDollarOFTMinterBurner …
blueogin File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| PRIVATE_KEY= | ||
| PUBLIC_KEY= | ||
|
|
||
| ETHERSCAN_KEY= |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,256 @@ | ||
| /*** | ||
| * Hardhat-deploy script for GoodDollar OFT (Omnichain Fungible Token) contracts | ||
| * | ||
| * Deploys (same pattern as MessageBridge: deterministic proxy + implementation + execute initialize): | ||
| * 1. GoodDollarOFTMinterBurner - 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 GoodDollarOFTMinterBurner, deploy implementation | ||
| * then execute initialize(nameService, adapter) once the adapter proxy address is known | ||
| * 2. Deploy ERC1967Proxy (deterministic) for GoodDollarOFTAdapter, deploy implementation (constructor: token, lzEndpoint), execute initialize(token, minterBurner, owner, feeRecipient) | ||
| * | ||
| * Note: GoodDollarOFTMinterBurner.initialize wires the adapter as operator. | ||
| */ | ||
|
|
||
| import { DeployFunction } from 'hardhat-deploy/types'; | ||
| import { ethers } from 'hardhat'; | ||
| import Contracts from '@gooddollar/goodprotocol/releases/deployment.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; | ||
| } | ||
|
|
||
| // CREATE2 salt for implementations: | ||
| // hardhat-deploy uses `deterministicDeployment` as the CREATE2 salt. | ||
| // We derive it from the contract's compiled bytecode so version changes | ||
| // map to different deterministic implementation addresses. | ||
| const minterBurnerArtifact = await hre.artifacts.readArtifact('GoodDollarOFTMinterBurner'); | ||
| const minterBurnerImplSalt = ethers.utils.keccak256(minterBurnerArtifact.bytecode); | ||
| const oftAdapterArtifact = await hre.artifacts.readArtifact('GoodDollarOFTAdapter'); | ||
| const oftAdapterImplSalt = ethers.utils.keccak256(oftAdapterArtifact.bytecode); | ||
|
|
||
| // --- GoodDollarOFTMinterBurner (hardhat-deploy: deterministic proxy + implementation + execute initialize) --- | ||
| const minterBurnerProxySalt = ethers.utils.keccak256( | ||
| ethers.utils.toUtf8Bytes( | ||
| isDevelopment ? 'Development-GoodDollarOFTMinterBurnerV1' : 'Production-GoodDollarOFTMinterBurnerV1' | ||
| ), | ||
| ); | ||
| const minterBurnerProxyDeploy = await deployments.deterministic('GoodDollarOFTMinterBurner', { | ||
| contract: 'ERC1967Proxy', | ||
| from: root.address, | ||
| salt: minterBurnerProxySalt, | ||
| log: true, | ||
| }); | ||
| const minterBurnerProxy = await minterBurnerProxyDeploy.deploy(); | ||
| const minterBurnerAddress = minterBurnerProxy.address; | ||
| console.log('GoodDollarOFTMinterBurner proxy', minterBurnerAddress); | ||
|
|
||
| const minterBurnerImpl = await deployments.deploy('GoodDollarOFTMinterBurner_Implementation', { | ||
| contract: 'GoodDollarOFTMinterBurner', | ||
| from: root.address, | ||
| deterministicDeployment: minterBurnerImplSalt, | ||
| log: true, | ||
| }); | ||
| console.log('GoodDollarOFTMinterBurner implementation', minterBurnerImpl.address); | ||
|
|
||
| const minterBurnerContract = await ethers.getContractAt('GoodDollarOFTMinterBurner', minterBurnerAddress); | ||
| const minterBurnerInitialized = await minterBurnerContract | ||
| .token() | ||
| .then((addr: string) => addr !== ethers.constants.AddressZero) | ||
| .catch(() => false); | ||
|
|
||
| if (!minterBurnerInitialized) { | ||
| console.log('GoodDollarOFTMinterBurner not initialized yet; will initialize after deploying adapter...'); | ||
| } else { | ||
| console.log('GoodDollarOFTMinterBurner already initialized'); | ||
| } | ||
|
|
||
| // Verify GoodDollarOFTMinterBurner implementation (no constructor args) on non-local networks (skip: 'hardhat', 'localhost') | ||
| if (!['hardhat', 'localhost'].includes(networkName)) { | ||
| await verifyContract(hre as any, minterBurnerImpl.address, [], 'GoodDollarOFTMinterBurner'); | ||
| } | ||
|
|
||
| // --- 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); | ||
|
|
||
| // Initialize minter/burner after we know the adapter address | ||
| if (!minterBurnerInitialized) { | ||
| console.log('Initializing GoodDollarOFTMinterBurner with adapter address...'); | ||
| const minterBurnerInitData = minterBurnerContract.interface.encodeFunctionData('initialize', [ | ||
| nameServiceAddress, | ||
| oftAdapterAddress, | ||
| ]); | ||
| await deployments.execute( | ||
| 'GoodDollarOFTMinterBurner', | ||
| { from: root.address }, | ||
| 'initialize', | ||
| minterBurnerImpl.address, | ||
| minterBurnerInitData, | ||
| ); | ||
| console.log('GoodDollarOFTMinterBurner initialized'); | ||
| } | ||
|
|
||
| const oftAdapterImpl = await deployments.deploy('GoodDollarOFTAdapter_Implementation', { | ||
| contract: 'GoodDollarOFTAdapter', | ||
| from: root.address, | ||
| deterministicDeployment: oftAdapterImplSalt, | ||
| 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'); | ||
| } | ||
|
|
||
| // Verify GoodDollarOFTAdapter implementation (constructor: tokenAddress, lzEndpoint) on non-local networks (skip: 'hardhat', 'localhost') | ||
| if (!['hardhat', 'localhost'].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('GoodDollarOFTMinterBurner:', 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']; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
make sure this scripts does all the steps required for full deployment.
besides DAO related actions such as giving minter rights to the minterburner
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I confirmed that all 7 steps are required
Step 1 deploys and initializes the OFT contracts, which has to happen before anything else.
Step 2 sets the adapter as operator via DAO governance
Step 3 grants MINTER_ROLE via DAO governance
Step 4 wires LayerZero connections/enforced options, an external integration step that can fail and has to be run with the correct owner/delegate permissions.
Step 5 sets bridge limits as an optional step because you may not want to set them.
Step 6 tests bridging and is optional because it depends on having balances/gas/fees and is meant as a validation step, not a core deployment requirement.
Step 7 transfers ownership to the DAO Avatar and is optional.