From 6d85ce1d41ae1cd91068709531ed3469e14b4244 Mon Sep 17 00:00:00 2001 From: gotnoshoeson Date: Wed, 31 Jan 2024 08:46:03 -0800 Subject: [PATCH] Transparent Proxy UI added --- README.md | 25 +- .../YourTransparentUpgradeableProxy.sol | 7 + .../01_deploy_your_contract_upgrade.ts | 3 +- .../nextjs/contracts/deployedContracts.ts | 393 ++++++++++++++++++ packages/nextjs/pages/proxiesDebug.tsx | 18 +- 5 files changed, 429 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 69d8252..f6e4d51 100644 --- a/README.md +++ b/README.md @@ -58,15 +58,19 @@ We didn't make any changes to YourContract or Factory so hardhat won't re-deploy 4. Let's get all of the read and write methods for [TransparentUpgradeableProxy](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/release-v4.8/contracts/proxy/transparent/TransparentUpgradeableProxy.sol) so that we can upgrade a proxy. Let's also use the ABI for YourContract2. To do so, we need to uncomment a few lines in proxiesDebug.tsx file. +### proxiesDebug.tsx modifications + +You can search 'step3' to find all instances to uncomment + ``` -// Uncomment the two lines below after step 3 +// Uncomment the two lines below after step3 const yourTransparentUpgradeableProxy = deployedcontracts[chain.id].YourTransparentUpgradeableProxy; const yourContractUpgrade = deployedContracts[chain.id].YourContract2; ``` ``` -// Uncomment the line below after step 3 +// Uncomment the line below after step3 const [proxyTransparentContractData, setProxyTransparentContractData] = useState(); ``` @@ -74,13 +78,13 @@ const [proxyTransparentContractData, setProxyTransparentContractData] = useState We need to modify one line in an existing useEffect: ``` -const data = Object.create(yourContract); // Change "yourContract" to "yourContractUpgrade" after step 3. +const data = Object.create(yourContract); // Change "yourContract" to "yourContractUpgrade" after step3. ``` We want to have a copy of the TransparentUpgradeableProxy ABI for every proxy that has been deployed. We can do this by uncommenting the following: ``` -// Uncomment the following useEffect after step 3 +// Uncomment the following useEffect after step3 // Creates transparent contract data for each proxy deployed by the Factory contract // Contract data is then used for ContractProxyUI props @@ -114,14 +118,21 @@ And lastly we need to render the read and write methods: ))} ``` -5. Time to upgrade. Make sure you're using the owner (or admin) of the proxy account that you're trying to upgrade. Calls from all other addresses fallback to the implementation contract which isn't what we want. Copy the address of YourContract2 on the Debug page. Call '_upgradeTo' and provide the new implementation address. Now calls to the proxy (which aren't from the admin) will fallback to YourContract2. +You should get a load of error notifications. + +![fallback-errors](https://github.com/scaffold-eth/scaffold-eth-2/assets/22818990/894da216-5719-4d55-aebe-cad1e5a9069b) -6. With an address that is not the owner (or admin) try to make a call to our new function 'setFarewell'. Keep in mind, we're sending the call to the same address that existed before. Can you call 'setFarewell' on a different proxy that hasn't been upgraded? -If you're looking for a proxy pattern that upgrades all the proxy contracts with one upgrade check out the [Upgradeable Beacon Proxy pattern](https://blog.openzeppelin.com/blog/the-state-of-smart-contract-upgrades#beacons). A similar build will be coming for the beacon pattern soon. +We're now using the ABI for YourContract2 but we haven't upgraded the contract yet so these functions don't exist. Once we do the upgrade, we won't get these errors for this contract anymore. Keep in mind we'll need to upgrade each proxy individually. If you want a pattern where all proxies are upgraded from one upgrade call, check out the [Beacon Proxy pattern](https://blog.openzeppelin.com/the-state-of-smart-contract-upgrades#beacons). + +5. Time to upgrade. Make sure you're using the owner (or admin) of the proxy account that you're trying to upgrade. Calls from all other addresses fallback to the implementation contract which isn't what we want. Copy the address of YourContract2 on the Debug page. Call '_upgradeTo' and provide the new implementation address. Now calls to the proxy (which aren't from the admin) will fallback to YourContract2. + +6. With an address that is not the owner (or admin) try to make a call to our new function 'setFarewell'. Keep in mind, we're sending the call to the same address that existed before. Your Can you call 'setFarewell' on a different proxy that hasn't been upgraded? Now that you've learned the Transparent Upgradeable Proxy pattern, I have some bad news for you. This pattern is NOT the current recommended pattern for upgradeable proxy functionality. UUPS is now the pattern recommended by OpenZeppelin for this type of functionality. "This standard uses the same delegate call pattern, but places upgrade logic in the implementation contract instead of the proxy itself." [Read more about UUPS](https://blog.openzeppelin.com/blog/the-state-of-smart-contract-upgrades#universal-upgradeable-proxies). +If you're looking for a proxy pattern that upgrades all the proxy contracts with one upgrade check out the [Upgradeable Beacon Proxy pattern](https://blog.openzeppelin.com/blog/the-state-of-smart-contract-upgrades#beacons). A similar build will be coming soon for the Beacon Proxy pattern. + Want to use the latest recommended proxy pattern? Stay tuned for a UUPS build coming soon... Want to upgrade all proxies with one upgrade call? Stay tuned for an Upgradeable Beacon Proxy build coming soon... diff --git a/packages/hardhat/upgrade/contracts/YourTransparentUpgradeableProxy.sol b/packages/hardhat/upgrade/contracts/YourTransparentUpgradeableProxy.sol index 6d68695..1d7a3f7 100644 --- a/packages/hardhat/upgrade/contracts/YourTransparentUpgradeableProxy.sol +++ b/packages/hardhat/upgrade/contracts/YourTransparentUpgradeableProxy.sol @@ -11,6 +11,13 @@ import "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.so */ contract YourTransparentUpgradeableProxy is TransparentUpgradeableProxy { + string ABI = "this is for ABI"; + // Constructor: Called once on contract deployment // Check packages/hardhat/deploy/01_deploy_your_contract_upgrade.ts + constructor( + address _logic, + address _admin, + bytes memory _data + ) TransparentUpgradeableProxy (_logic, _admin, _data) {} } diff --git a/packages/hardhat/upgrade/deploy_script/01_deploy_your_contract_upgrade.ts b/packages/hardhat/upgrade/deploy_script/01_deploy_your_contract_upgrade.ts index 3bf432a..df0aeac 100644 --- a/packages/hardhat/upgrade/deploy_script/01_deploy_your_contract_upgrade.ts +++ b/packages/hardhat/upgrade/deploy_script/01_deploy_your_contract_upgrade.ts @@ -36,7 +36,8 @@ const deployYourContract: DeployFunction = async function (hre: HardhatRuntimeEn await deploy("YourTransparentUpgradeableProxy", { from: deployer, // Contract constructor arguments - args: [yourContract2.address, deployer, ""], + // Use 0x for data parameter here, use empty string ("") in Factory.sol contract + args: [yourContract2.address, deployer, '0x'], log: true, // autoMine: can be passed to the deploy function to make the deployment process faster on local networks by // automatically mining the contract deployment transaction. There is no effect on live networks. diff --git a/packages/nextjs/contracts/deployedContracts.ts b/packages/nextjs/contracts/deployedContracts.ts index 2574d06..e3599ca 100644 --- a/packages/nextjs/contracts/deployedContracts.ts +++ b/packages/nextjs/contracts/deployedContracts.ts @@ -224,6 +224,399 @@ const deployedContracts = { ], inheritedFunctions: {}, }, + YourContract2: { + address: "0xCf7Ed3AccA5a467e9e704C703E8D87F634fB0Fc9", + abi: [ + { + inputs: [ + { + internalType: "address", + name: "_owner", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "farewellSetter", + type: "address", + }, + { + indexed: false, + internalType: "string", + name: "newFarewell", + type: "string", + }, + { + indexed: false, + internalType: "bool", + name: "premium", + type: "bool", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "FarewellChange", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "greetingSetter", + type: "address", + }, + { + indexed: false, + internalType: "string", + name: "newGreeting", + type: "string", + }, + { + indexed: false, + internalType: "bool", + name: "premium", + type: "bool", + }, + { + indexed: false, + internalType: "uint256", + name: "value", + type: "uint256", + }, + ], + name: "GreetingChange", + type: "event", + }, + { + inputs: [], + name: "farewell", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "greeting", + outputs: [ + { + internalType: "string", + name: "", + type: "string", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "owner", + outputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "premium", + outputs: [ + { + internalType: "bool", + name: "", + type: "bool", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "_newFarewell", + type: "string", + }, + ], + name: "setFarewell", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [ + { + internalType: "string", + name: "_newGreeting", + type: "string", + }, + ], + name: "setGreeting", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + inputs: [], + name: "totalCounter", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "totalCounterFarewell", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "userFarewellCounter", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "", + type: "address", + }, + ], + name: "userGreetingCounter", + outputs: [ + { + internalType: "uint256", + name: "", + type: "uint256", + }, + ], + stateMutability: "view", + type: "function", + }, + { + inputs: [], + name: "withdraw", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + stateMutability: "payable", + type: "receive", + }, + ], + inheritedFunctions: {}, + }, + YourTransparentUpgradeableProxy: { + address: "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9", + abi: [ + { + inputs: [ + { + internalType: "address", + name: "_logic", + type: "address", + }, + { + internalType: "address", + name: "_admin", + type: "address", + }, + { + internalType: "bytes", + name: "_data", + type: "bytes", + }, + ], + stateMutability: "nonpayable", + type: "constructor", + }, + { + anonymous: false, + inputs: [ + { + indexed: false, + internalType: "address", + name: "previousAdmin", + type: "address", + }, + { + indexed: false, + internalType: "address", + name: "newAdmin", + type: "address", + }, + ], + name: "AdminChanged", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "beacon", + type: "address", + }, + ], + name: "BeaconUpgraded", + type: "event", + }, + { + anonymous: false, + inputs: [ + { + indexed: true, + internalType: "address", + name: "implementation", + type: "address", + }, + ], + name: "Upgraded", + type: "event", + }, + { + stateMutability: "payable", + type: "fallback", + }, + { + inputs: [], + name: "admin", + outputs: [ + { + internalType: "address", + name: "admin_", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newAdmin", + type: "address", + }, + ], + name: "changeAdmin", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [], + name: "implementation", + outputs: [ + { + internalType: "address", + name: "implementation_", + type: "address", + }, + ], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address", + }, + ], + name: "upgradeTo", + outputs: [], + stateMutability: "nonpayable", + type: "function", + }, + { + inputs: [ + { + internalType: "address", + name: "newImplementation", + type: "address", + }, + { + internalType: "bytes", + name: "data", + type: "bytes", + }, + ], + name: "upgradeToAndCall", + outputs: [], + stateMutability: "payable", + type: "function", + }, + { + stateMutability: "payable", + type: "receive", + }, + ], + inheritedFunctions: { + admin: + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol", + changeAdmin: + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol", + implementation: + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol", + upgradeTo: + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol", + upgradeToAndCall: + "@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol", + }, + }, }, } as const; diff --git a/packages/nextjs/pages/proxiesDebug.tsx b/packages/nextjs/pages/proxiesDebug.tsx index 11aa452..b99d8a3 100644 --- a/packages/nextjs/pages/proxiesDebug.tsx +++ b/packages/nextjs/pages/proxiesDebug.tsx @@ -22,15 +22,15 @@ const ClonesDebug: NextPage = () => { const factory = deployedContracts[chain.id].Factory; const yourContract = deployedContracts[chain.id].YourContract; - // Uncomment the two line below after step 3 - // const yourTransparentUpgradeableProxy = deployedcontracts[chain.id].YourTransparentUpgradeableProxy; + // Uncomment the two line below after step3 + // const yourTransparentUpgradeableProxy = deployedContracts[chain.id].YourTransparentUpgradeableProxy; // const yourContractUpgrade = deployedContracts[chain.id].YourContract2; // Array of contract addresses from contractRead const [proxyContracts, setProxyContracts] = useState(); const [proxyContractData, setProxyContractData] = useState(); - // Uncomment the line below after step 3 + // Uncomment the line below after step3 // const [proxyTransparentContractData, setProxyTransparentContractData] = useState(); const [selectedContract, setSelectedContract] = useLocalStorage( @@ -68,7 +68,7 @@ const ClonesDebug: NextPage = () => { const iterate = () => { for (let index = 0; index < proxyContracts.length; index++) { - const data = Object.create(yourContract); // Change "yourContract" to "yourContractUpgrade" after step 3. + const data = Object.create(yourContract); // Change "yourContract" to "yourContractUpgrade" after step3. data.address = proxyContracts[index]; dataArray.push(data); } @@ -79,7 +79,7 @@ const ClonesDebug: NextPage = () => { setProxyContractData(dataArray); }, [proxyContracts]); - // Uncomment the following useEffect after step 3 + // Uncomment the following useEffect after step3 // Creates transparent data for each proxy deployed by the Factory contract // Contract data is then used for ContractProxyUI /* useEffect(() => { @@ -95,7 +95,7 @@ const ClonesDebug: NextPage = () => { if (proxyContracts?.length > 0) iterate(); - setProxyContractData(dataArray); + setProxyTransparentContractData(dataArray); }, [proxyContracts]); */ @@ -144,7 +144,7 @@ const ClonesDebug: NextPage = () => { deployedContractData={data} /> ))} - {/* Uncomment the following code after step 3 */} + {/* Uncomment the following code after step3 */} {/* {proxyTransparentContractData?.map(data => ( { )}
-

Debug Contracts

+

Debug Proxies

You can debug & interact with your deployed contracts here.
Check{" "} - packages / nextjs / pages / debug.tsx + packages / nextjs / pages / proxiesDebug.tsx {" "}