From a594e0740d5f752484532ea813dd61f95e85ba25 Mon Sep 17 00:00:00 2001 From: Eric Lau Date: Wed, 20 Mar 2024 10:12:10 -0400 Subject: [PATCH] Add download option for upgradeable Foundry package (#346) --- packages/core/src/zip-foundry.test.ts | 26 +- packages/core/src/zip-foundry.test.ts.md | 715 +++++++++++++++++++-- packages/core/src/zip-foundry.test.ts.snap | Bin 2266 -> 3383 bytes packages/core/src/zip-foundry.ts | 175 ++++- packages/ui/src/App.svelte | 2 +- 5 files changed, 834 insertions(+), 84 deletions(-) diff --git a/packages/core/src/zip-foundry.test.ts b/packages/core/src/zip-foundry.test.ts index dcbaa846..eef1c879 100644 --- a/packages/core/src/zip-foundry.test.ts +++ b/packages/core/src/zip-foundry.test.ts @@ -49,8 +49,14 @@ test.serial('erc20 full', async t => { await runTest(c, t, opts); }); -test.serial('erc721 basic', async t => { - const opts: GenericOptions = { kind: 'ERC721', name: 'My Token', symbol: 'MTK'}; +test.serial('erc20 uups, roles', async t => { + const opts: GenericOptions = { kind: 'ERC20', name: 'My Token', symbol: 'MTK', upgradeable: 'uups', access: 'roles' }; + const c = buildERC20(opts); + await runTest(c, t, opts); +}); + +test.serial('erc721 uups, ownable', async t => { + const opts: GenericOptions = { kind: 'ERC721', name: 'My Token', symbol: 'MTK', upgradeable: 'uups', access: 'ownable' }; const c = buildERC721(opts); await runTest(c, t, opts); }); @@ -61,12 +67,24 @@ test.serial('erc1155 basic', async t => { await runTest(c, t, opts); }); +test.serial('erc1155 transparent, ownable', async t => { + const opts: GenericOptions = { kind: 'ERC1155', name: 'My Token', uri: 'https://myuri/{id}', upgradeable: 'transparent', access: 'ownable' }; + const c = buildERC1155(opts); + await runTest(c, t, opts); +}); + test.serial('custom basic', async t => { const opts: GenericOptions = { kind: 'Custom', name: 'My Contract' }; const c = buildCustom(opts); await runTest(c, t, opts); }); +test.serial('custom transparent, managed', async t => { + const opts: GenericOptions = { kind: 'Custom', name: 'My Contract', upgradeable: 'transparent', access: 'managed' }; + const c = buildCustom(opts); + await runTest(c, t, opts); +}); + async function runTest(c: Contract, t: ExecutionContext, opts: GenericOptions) { const zip = await zipFoundry(c, opts); @@ -105,8 +123,8 @@ async function extractAndRunPackage(zip: JSZip, c: Contract, t: ExecutionContext const setGitUser = 'git init && git config user.email "test@test.test" && git config user.name "Test"'; const setup = 'bash setup.sh'; - const test = 'forge test'; - const script = `forge script script/${c.name}.s.sol`; + const test = 'forge test' + (c.upgradeable ? ' --force' : ''); + const script = `forge script script/${c.name}.s.sol` + (c.upgradeable ? ' --force' : ''); const exec = (cmd: string) => util.promisify(child.exec)(cmd, { env: { ...process.env, NO_COLOR: '' } }); diff --git a/packages/core/src/zip-foundry.test.ts.md b/packages/core/src/zip-foundry.test.ts.md index bb381cce..50c069b7 100644 --- a/packages/core/src/zip-foundry.test.ts.md +++ b/packages/core/src/zip-foundry.test.ts.md @@ -29,6 +29,9 @@ Generated by [AVA](https://avajs.dev). if ! [ -f "foundry.toml" ]␊ then␊ echo "Initializing Foundry project..."␊ + ␊ + # Backup Wizard template readme to avoid it being overwritten␊ + mv README.md README-oz.md␊ ␊ # Initialize sample Foundry project␊ forge init --force --no-commit --quiet␊ @@ -36,10 +39,14 @@ Generated by [AVA](https://avajs.dev). # Install OpenZeppelin Contracts␊ forge install OpenZeppelin/openzeppelin-contracts@vX.Y.Z --no-commit --quiet␊ ␊ - # Remove template contracts␊ + # Remove unneeded Foundry template files␊ rm src/Counter.sol␊ rm script/Counter.s.sol␊ rm test/Counter.t.sol␊ + rm README.md␊ + ␊ + # Restore Wizard template readme␊ + mv README-oz.md README.md␊ ␊ # Add remappings␊ if [ -f "remappings.txt" ]␊ @@ -90,18 +97,24 @@ Generated by [AVA](https://avajs.dev). `// SPDX-License-Identifier: MIT␊ pragma solidity ^0.8.20;␊ ␊ - import "forge-std/Script.sol";␊ - import "../src/MyToken.sol";␊ + import {Script} from "forge-std/Script.sol";␊ + import {console} from "forge-std/console.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ ␊ contract MyTokenScript is Script {␊ function setUp() public {}␊ ␊ function run() public {␊ - // TODO: Set addresses for the contract arguments below, then uncomment the following lines␊ - // vm.startBroadcast();␊ - // MyToken instance = new MyToken(defaultAdmin, pauser, minter);␊ - // console.log("Contract deployed to %s", address(instance));␊ - // vm.stopBroadcast();␊ + // TODO: Set addresses for the variables below, then uncomment the following section:␊ + /*␊ + vm.startBroadcast();␊ + address defaultAdmin = ;␊ + address pauser = ;␊ + address minter = ;␊ + MyToken instance = new MyToken(defaultAdmin, pauser, minter);␊ + console.log("Contract deployed to %s", address(instance));␊ + vm.stopBroadcast();␊ + */␊ }␊ }␊ `, @@ -165,8 +178,8 @@ Generated by [AVA](https://avajs.dev). `// SPDX-License-Identifier: MIT␊ pragma solidity ^0.8.20;␊ ␊ - import "forge-std/Test.sol";␊ - import "../src/MyToken.sol";␊ + import {Test} from "forge-std/Test.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ ␊ contract MyTokenTest is Test {␊ MyToken public instance;␊ @@ -178,14 +191,14 @@ Generated by [AVA](https://avajs.dev). instance = new MyToken(defaultAdmin, pauser, minter);␊ }␊ ␊ - function testName() public {␊ + function testName() public view {␊ assertEq(instance.name(), "My Token");␊ }␊ }␊ `, ] -## erc721 basic +## erc20 uups, roles > Snapshot 1 @@ -210,24 +223,40 @@ Generated by [AVA](https://avajs.dev). if ! [ -f "foundry.toml" ]␊ then␊ echo "Initializing Foundry project..."␊ + ␊ + # Backup Wizard template readme to avoid it being overwritten␊ + mv README.md README-oz.md␊ ␊ # Initialize sample Foundry project␊ forge init --force --no-commit --quiet␊ ␊ - # Install OpenZeppelin Contracts␊ - forge install OpenZeppelin/openzeppelin-contracts@vX.Y.Z --no-commit --quiet␊ + # Install OpenZeppelin Contracts and Upgrades␊ + forge install OpenZeppelin/openzeppelin-contracts-upgradeable@vX.Y.Z --no-commit --quiet␊ + forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit --quiet␊ ␊ - # Remove template contracts␊ + # Remove unneeded Foundry template files␊ rm src/Counter.sol␊ rm script/Counter.s.sol␊ rm test/Counter.t.sol␊ + rm README.md␊ + ␊ + # Restore Wizard template readme␊ + mv README-oz.md README.md␊ ␊ # Add remappings␊ if [ -f "remappings.txt" ]␊ then␊ echo "" >> remappings.txt␊ fi␊ - echo "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" >> remappings.txt␊ + echo "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" >> remappings.txt␊ + echo "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" >> remappings.txt␊ + ␊ + # Add settings in foundry.toml␊ + echo "" >> foundry.toml␊ + echo "ffi = true" >> foundry.toml␊ + echo "ast = true" >> foundry.toml␊ + echo "build_info = true" >> foundry.toml␊ + echo "extra_output = [\\"storageLayout\\"]" >> foundry.toml␊ ␊ # Perform initial git commit␊ git add .␊ @@ -255,7 +284,7 @@ Generated by [AVA](https://avajs.dev). ## Testing the contract␊ ␊ \`\`\`␊ - forge test␊ + forge test --force␊ \`\`\`␊ ␊ ## Deploying the contract␊ @@ -263,7 +292,7 @@ Generated by [AVA](https://avajs.dev). You can simulate a deployment by running the script:␊ ␊ \`\`\`␊ - forge script script/MyToken.s.sol␊ + forge script script/MyToken.s.sol --force␊ \`\`\`␊ ␊ See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information.␊ @@ -271,17 +300,28 @@ Generated by [AVA](https://avajs.dev). `// SPDX-License-Identifier: MIT␊ pragma solidity ^0.8.20;␊ ␊ - import "forge-std/Script.sol";␊ - import "../src/MyToken.sol";␊ + import {Script} from "forge-std/Script.sol";␊ + import {console} from "forge-std/console.sol";␊ + import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ ␊ contract MyTokenScript is Script {␊ function setUp() public {}␊ ␊ function run() public {␊ + // TODO: Set addresses for the variables below, then uncomment the following section:␊ + /*␊ vm.startBroadcast();␊ - MyToken instance = new MyToken();␊ - console.log("Contract deployed to %s", address(instance));␊ + address defaultAdmin = ;␊ + address upgrader = ;␊ + address proxy = Upgrades.deployUUPSProxy(␊ + "MyToken.sol",␊ + abi.encodeCall(MyToken.initialize, (defaultAdmin, upgrader))␊ + );␊ + MyToken instance = MyToken(proxy);␊ + console.log("Proxy deployed to %s", address(instance));␊ vm.stopBroadcast();␊ + */␊ }␊ }␊ `, @@ -289,26 +329,240 @@ Generated by [AVA](https://avajs.dev). // Compatible with OpenZeppelin Contracts ^5.0.0␊ pragma solidity ^0.8.20;␊ ␊ - import "@openzeppelin/contracts/token/ERC721/ERC721.sol";␊ + import "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";␊ + import "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol";␊ + import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";␊ + import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ + import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";␊ + ␊ + contract MyToken is Initializable, ERC20Upgradeable, ERC20PermitUpgradeable, AccessControlUpgradeable, UUPSUpgradeable {␊ + bytes32 public constant UPGRADER_ROLE = keccak256("UPGRADER_ROLE");␊ + ␊ + /// @custom:oz-upgrades-unsafe-allow constructor␊ + constructor() {␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(address defaultAdmin, address upgrader)␊ + initializer public␊ + {␊ + __ERC20_init("My Token", "MTK");␊ + __ERC20Permit_init("My Token");␊ + __AccessControl_init();␊ + __UUPSUpgradeable_init();␊ + ␊ + _grantRole(DEFAULT_ADMIN_ROLE, defaultAdmin);␊ + _grantRole(UPGRADER_ROLE, upgrader);␊ + }␊ ␊ - contract MyToken is ERC721 {␊ - constructor() ERC721("My Token", "MTK") {}␊ + function _authorizeUpgrade(address newImplementation)␊ + internal␊ + onlyRole(UPGRADER_ROLE)␊ + override␊ + {}␊ }␊ `, `// SPDX-License-Identifier: MIT␊ pragma solidity ^0.8.20;␊ ␊ - import "forge-std/Test.sol";␊ - import "../src/MyToken.sol";␊ + import {Test} from "forge-std/Test.sol";␊ + import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ ␊ contract MyTokenTest is Test {␊ MyToken public instance;␊ ␊ function setUp() public {␊ - instance = new MyToken();␊ + address defaultAdmin = vm.addr(1);␊ + address upgrader = vm.addr(2);␊ + address proxy = Upgrades.deployUUPSProxy(␊ + "MyToken.sol",␊ + abi.encodeCall(MyToken.initialize, (defaultAdmin, upgrader))␊ + );␊ + instance = MyToken(proxy);␊ }␊ ␊ - function testName() public {␊ + function testName() public view {␊ + assertEq(instance.name(), "My Token");␊ + }␊ + }␊ + `, + ] + +## erc721 uups, ownable + +> Snapshot 1 + + [ + `#!/usr/bin/env bash␊ + ␊ + # Check if git is installed␊ + if ! which git &> /dev/null␊ + then␊ + echo "git command not found. Install git and try again."␊ + exit 1␊ + fi␊ + ␊ + # Check if Foundry is installed␊ + if ! which forge &> /dev/null␊ + then␊ + echo "forge command not found. Install Foundry and try again. See https://book.getfoundry.sh/getting-started/installation"␊ + exit 1␊ + fi␊ + ␊ + # Setup Foundry project␊ + if ! [ -f "foundry.toml" ]␊ + then␊ + echo "Initializing Foundry project..."␊ + ␊ + # Backup Wizard template readme to avoid it being overwritten␊ + mv README.md README-oz.md␊ + ␊ + # Initialize sample Foundry project␊ + forge init --force --no-commit --quiet␊ + ␊ + # Install OpenZeppelin Contracts and Upgrades␊ + forge install OpenZeppelin/openzeppelin-contracts-upgradeable@vX.Y.Z --no-commit --quiet␊ + forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit --quiet␊ + ␊ + # Remove unneeded Foundry template files␊ + rm src/Counter.sol␊ + rm script/Counter.s.sol␊ + rm test/Counter.t.sol␊ + rm README.md␊ + ␊ + # Restore Wizard template readme␊ + mv README-oz.md README.md␊ + ␊ + # Add remappings␊ + if [ -f "remappings.txt" ]␊ + then␊ + echo "" >> remappings.txt␊ + fi␊ + echo "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" >> remappings.txt␊ + echo "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" >> remappings.txt␊ + ␊ + # Add settings in foundry.toml␊ + echo "" >> foundry.toml␊ + echo "ffi = true" >> foundry.toml␊ + echo "ast = true" >> foundry.toml␊ + echo "build_info = true" >> foundry.toml␊ + echo "extra_output = [\\"storageLayout\\"]" >> foundry.toml␊ + ␊ + # Perform initial git commit␊ + git add .␊ + git commit -m "openzeppelin: add wizard output" --quiet␊ + ␊ + echo "Done."␊ + else␊ + echo "Foundry project already initialized."␊ + fi␊ + `, + `# Sample Foundry Project␊ + ␊ + This project demonstrates a basic Foundry use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a script that deploys that contract.␊ + ␊ + ## Installing Foundry␊ + ␊ + See [Foundry installation guide](https://book.getfoundry.sh/getting-started/installation).␊ + ␊ + ## Initializing the project␊ + ␊ + \`\`\`␊ + bash setup.sh␊ + \`\`\`␊ + ␊ + ## Testing the contract␊ + ␊ + \`\`\`␊ + forge test --force␊ + \`\`\`␊ + ␊ + ## Deploying the contract␊ + ␊ + You can simulate a deployment by running the script:␊ + ␊ + \`\`\`␊ + forge script script/MyToken.s.sol --force␊ + \`\`\`␊ + ␊ + See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information.␊ + `, + `// SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.20;␊ + ␊ + import {Script} from "forge-std/Script.sol";␊ + import {console} from "forge-std/console.sol";␊ + import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ + ␊ + contract MyTokenScript is Script {␊ + function setUp() public {}␊ + ␊ + function run() public {␊ + // TODO: Set addresses for the variables below, then uncomment the following section:␊ + /*␊ + vm.startBroadcast();␊ + address initialOwner = ;␊ + address proxy = Upgrades.deployUUPSProxy(␊ + "MyToken.sol",␊ + abi.encodeCall(MyToken.initialize, (initialOwner))␊ + );␊ + MyToken instance = MyToken(proxy);␊ + console.log("Proxy deployed to %s", address(instance));␊ + vm.stopBroadcast();␊ + */␊ + }␊ + }␊ + `, + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.0.0␊ + pragma solidity ^0.8.20;␊ + ␊ + import "@openzeppelin/contracts-upgradeable/token/ERC721/ERC721Upgradeable.sol";␊ + import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";␊ + import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ + import "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";␊ + ␊ + contract MyToken is Initializable, ERC721Upgradeable, OwnableUpgradeable, UUPSUpgradeable {␊ + /// @custom:oz-upgrades-unsafe-allow constructor␊ + constructor() {␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(address initialOwner) initializer public {␊ + __ERC721_init("My Token", "MTK");␊ + __Ownable_init(initialOwner);␊ + __UUPSUpgradeable_init();␊ + }␊ + ␊ + function _authorizeUpgrade(address newImplementation)␊ + internal␊ + onlyOwner␊ + override␊ + {}␊ + }␊ + `, + `// SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.20;␊ + ␊ + import {Test} from "forge-std/Test.sol";␊ + import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ + ␊ + contract MyTokenTest is Test {␊ + MyToken public instance;␊ + ␊ + function setUp() public {␊ + address initialOwner = vm.addr(1);␊ + address proxy = Upgrades.deployUUPSProxy(␊ + "MyToken.sol",␊ + abi.encodeCall(MyToken.initialize, (initialOwner))␊ + );␊ + instance = MyToken(proxy);␊ + }␊ + ␊ + function testName() public view {␊ assertEq(instance.name(), "My Token");␊ }␊ }␊ @@ -340,6 +594,9 @@ Generated by [AVA](https://avajs.dev). if ! [ -f "foundry.toml" ]␊ then␊ echo "Initializing Foundry project..."␊ + ␊ + # Backup Wizard template readme to avoid it being overwritten␊ + mv README.md README-oz.md␊ ␊ # Initialize sample Foundry project␊ forge init --force --no-commit --quiet␊ @@ -347,10 +604,14 @@ Generated by [AVA](https://avajs.dev). # Install OpenZeppelin Contracts␊ forge install OpenZeppelin/openzeppelin-contracts@vX.Y.Z --no-commit --quiet␊ ␊ - # Remove template contracts␊ + # Remove unneeded Foundry template files␊ rm src/Counter.sol␊ rm script/Counter.s.sol␊ rm test/Counter.t.sol␊ + rm README.md␊ + ␊ + # Restore Wizard template readme␊ + mv README-oz.md README.md␊ ␊ # Add remappings␊ if [ -f "remappings.txt" ]␊ @@ -401,18 +662,22 @@ Generated by [AVA](https://avajs.dev). `// SPDX-License-Identifier: MIT␊ pragma solidity ^0.8.20;␊ ␊ - import "forge-std/Script.sol";␊ - import "../src/MyToken.sol";␊ + import {Script} from "forge-std/Script.sol";␊ + import {console} from "forge-std/console.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ ␊ contract MyTokenScript is Script {␊ function setUp() public {}␊ ␊ function run() public {␊ - // TODO: Set addresses for the contract arguments below, then uncomment the following lines␊ - // vm.startBroadcast();␊ - // MyToken instance = new MyToken(initialOwner);␊ - // console.log("Contract deployed to %s", address(instance));␊ - // vm.stopBroadcast();␊ + // TODO: Set addresses for the variables below, then uncomment the following section:␊ + /*␊ + vm.startBroadcast();␊ + address initialOwner = ;␊ + MyToken instance = new MyToken(initialOwner);␊ + console.log("Contract deployed to %s", address(instance));␊ + vm.stopBroadcast();␊ + */␊ }␊ }␊ `, @@ -437,8 +702,8 @@ Generated by [AVA](https://avajs.dev). `// SPDX-License-Identifier: MIT␊ pragma solidity ^0.8.20;␊ ␊ - import "forge-std/Test.sol";␊ - import "../src/MyToken.sol";␊ + import {Test} from "forge-std/Test.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ ␊ contract MyTokenTest is Test {␊ MyToken public instance;␊ @@ -448,7 +713,185 @@ Generated by [AVA](https://avajs.dev). instance = new MyToken(initialOwner);␊ }␊ ␊ - function testUri() public {␊ + function testUri() public view {␊ + assertEq(instance.uri(0), "https://myuri/{id}");␊ + }␊ + }␊ + `, + ] + +## erc1155 transparent, ownable + +> Snapshot 1 + + [ + `#!/usr/bin/env bash␊ + ␊ + # Check if git is installed␊ + if ! which git &> /dev/null␊ + then␊ + echo "git command not found. Install git and try again."␊ + exit 1␊ + fi␊ + ␊ + # Check if Foundry is installed␊ + if ! which forge &> /dev/null␊ + then␊ + echo "forge command not found. Install Foundry and try again. See https://book.getfoundry.sh/getting-started/installation"␊ + exit 1␊ + fi␊ + ␊ + # Setup Foundry project␊ + if ! [ -f "foundry.toml" ]␊ + then␊ + echo "Initializing Foundry project..."␊ + ␊ + # Backup Wizard template readme to avoid it being overwritten␊ + mv README.md README-oz.md␊ + ␊ + # Initialize sample Foundry project␊ + forge init --force --no-commit --quiet␊ + ␊ + # Install OpenZeppelin Contracts and Upgrades␊ + forge install OpenZeppelin/openzeppelin-contracts-upgradeable@vX.Y.Z --no-commit --quiet␊ + forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit --quiet␊ + ␊ + # Remove unneeded Foundry template files␊ + rm src/Counter.sol␊ + rm script/Counter.s.sol␊ + rm test/Counter.t.sol␊ + rm README.md␊ + ␊ + # Restore Wizard template readme␊ + mv README-oz.md README.md␊ + ␊ + # Add remappings␊ + if [ -f "remappings.txt" ]␊ + then␊ + echo "" >> remappings.txt␊ + fi␊ + echo "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" >> remappings.txt␊ + echo "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" >> remappings.txt␊ + ␊ + # Add settings in foundry.toml␊ + echo "" >> foundry.toml␊ + echo "ffi = true" >> foundry.toml␊ + echo "ast = true" >> foundry.toml␊ + echo "build_info = true" >> foundry.toml␊ + echo "extra_output = [\\"storageLayout\\"]" >> foundry.toml␊ + ␊ + # Perform initial git commit␊ + git add .␊ + git commit -m "openzeppelin: add wizard output" --quiet␊ + ␊ + echo "Done."␊ + else␊ + echo "Foundry project already initialized."␊ + fi␊ + `, + `# Sample Foundry Project␊ + ␊ + This project demonstrates a basic Foundry use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a script that deploys that contract.␊ + ␊ + ## Installing Foundry␊ + ␊ + See [Foundry installation guide](https://book.getfoundry.sh/getting-started/installation).␊ + ␊ + ## Initializing the project␊ + ␊ + \`\`\`␊ + bash setup.sh␊ + \`\`\`␊ + ␊ + ## Testing the contract␊ + ␊ + \`\`\`␊ + forge test --force␊ + \`\`\`␊ + ␊ + ## Deploying the contract␊ + ␊ + You can simulate a deployment by running the script:␊ + ␊ + \`\`\`␊ + forge script script/MyToken.s.sol --force␊ + \`\`\`␊ + ␊ + See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information.␊ + `, + `// SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.20;␊ + ␊ + import {Script} from "forge-std/Script.sol";␊ + import {console} from "forge-std/console.sol";␊ + import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ + ␊ + contract MyTokenScript is Script {␊ + function setUp() public {}␊ + ␊ + function run() public {␊ + // TODO: Set addresses for the variables below, then uncomment the following section:␊ + /*␊ + vm.startBroadcast();␊ + address initialOwner = ;␊ + address proxy = Upgrades.deployTransparentProxy(␊ + "MyToken.sol",␊ + initialOwner,␊ + abi.encodeCall(MyToken.initialize, (initialOwner))␊ + );␊ + MyToken instance = MyToken(proxy);␊ + console.log("Proxy deployed to %s", address(instance));␊ + vm.stopBroadcast();␊ + */␊ + }␊ + }␊ + `, + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.0.0␊ + pragma solidity ^0.8.20;␊ + ␊ + import "@openzeppelin/contracts-upgradeable/token/ERC1155/ERC1155Upgradeable.sol";␊ + import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";␊ + import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ + ␊ + contract MyToken is Initializable, ERC1155Upgradeable, OwnableUpgradeable {␊ + /// @custom:oz-upgrades-unsafe-allow constructor␊ + constructor() {␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(address initialOwner) initializer public {␊ + __ERC1155_init("https://myuri/{id}");␊ + __Ownable_init(initialOwner);␊ + }␊ + ␊ + function setURI(string memory newuri) public onlyOwner {␊ + _setURI(newuri);␊ + }␊ + }␊ + `, + `// SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.20;␊ + ␊ + import {Test} from "forge-std/Test.sol";␊ + import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";␊ + import {MyToken} from "src/MyToken.sol";␊ + ␊ + contract MyTokenTest is Test {␊ + MyToken public instance;␊ + ␊ + function setUp() public {␊ + address initialOwner = vm.addr(1);␊ + address proxy = Upgrades.deployTransparentProxy(␊ + "MyToken.sol",␊ + initialOwner,␊ + abi.encodeCall(MyToken.initialize, (initialOwner))␊ + );␊ + instance = MyToken(proxy);␊ + }␊ + ␊ + function testUri() public view {␊ assertEq(instance.uri(0), "https://myuri/{id}");␊ }␊ }␊ @@ -480,6 +923,9 @@ Generated by [AVA](https://avajs.dev). if ! [ -f "foundry.toml" ]␊ then␊ echo "Initializing Foundry project..."␊ + ␊ + # Backup Wizard template readme to avoid it being overwritten␊ + mv README.md README-oz.md␊ ␊ # Initialize sample Foundry project␊ forge init --force --no-commit --quiet␊ @@ -487,10 +933,14 @@ Generated by [AVA](https://avajs.dev). # Install OpenZeppelin Contracts␊ forge install OpenZeppelin/openzeppelin-contracts@vX.Y.Z --no-commit --quiet␊ ␊ - # Remove template contracts␊ + # Remove unneeded Foundry template files␊ rm src/Counter.sol␊ rm script/Counter.s.sol␊ rm test/Counter.t.sol␊ + rm README.md␊ + ␊ + # Restore Wizard template readme␊ + mv README-oz.md README.md␊ ␊ # Add remappings␊ if [ -f "remappings.txt" ]␊ @@ -541,8 +991,9 @@ Generated by [AVA](https://avajs.dev). `// SPDX-License-Identifier: MIT␊ pragma solidity ^0.8.20;␊ ␊ - import "forge-std/Script.sol";␊ - import "../src/MyContract.sol";␊ + import {Script} from "forge-std/Script.sol";␊ + import {console} from "forge-std/console.sol";␊ + import {MyContract} from "src/MyContract.sol";␊ ␊ contract MyContractScript is Script {␊ function setUp() public {}␊ @@ -565,8 +1016,8 @@ Generated by [AVA](https://avajs.dev). `// SPDX-License-Identifier: MIT␊ pragma solidity ^0.8.20;␊ ␊ - import "forge-std/Test.sol";␊ - import "../src/MyContract.sol";␊ + import {Test} from "forge-std/Test.sol";␊ + import {MyContract} from "src/MyContract.sol";␊ ␊ contract MyContractTest is Test {␊ MyContract public instance;␊ @@ -581,3 +1032,177 @@ Generated by [AVA](https://avajs.dev). }␊ `, ] + +## custom transparent, managed + +> Snapshot 1 + + [ + `#!/usr/bin/env bash␊ + ␊ + # Check if git is installed␊ + if ! which git &> /dev/null␊ + then␊ + echo "git command not found. Install git and try again."␊ + exit 1␊ + fi␊ + ␊ + # Check if Foundry is installed␊ + if ! which forge &> /dev/null␊ + then␊ + echo "forge command not found. Install Foundry and try again. See https://book.getfoundry.sh/getting-started/installation"␊ + exit 1␊ + fi␊ + ␊ + # Setup Foundry project␊ + if ! [ -f "foundry.toml" ]␊ + then␊ + echo "Initializing Foundry project..."␊ + ␊ + # Backup Wizard template readme to avoid it being overwritten␊ + mv README.md README-oz.md␊ + ␊ + # Initialize sample Foundry project␊ + forge init --force --no-commit --quiet␊ + ␊ + # Install OpenZeppelin Contracts and Upgrades␊ + forge install OpenZeppelin/openzeppelin-contracts-upgradeable@vX.Y.Z --no-commit --quiet␊ + forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit --quiet␊ + ␊ + # Remove unneeded Foundry template files␊ + rm src/Counter.sol␊ + rm script/Counter.s.sol␊ + rm test/Counter.t.sol␊ + rm README.md␊ + ␊ + # Restore Wizard template readme␊ + mv README-oz.md README.md␊ + ␊ + # Add remappings␊ + if [ -f "remappings.txt" ]␊ + then␊ + echo "" >> remappings.txt␊ + fi␊ + echo "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" >> remappings.txt␊ + echo "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" >> remappings.txt␊ + ␊ + # Add settings in foundry.toml␊ + echo "" >> foundry.toml␊ + echo "ffi = true" >> foundry.toml␊ + echo "ast = true" >> foundry.toml␊ + echo "build_info = true" >> foundry.toml␊ + echo "extra_output = [\\"storageLayout\\"]" >> foundry.toml␊ + ␊ + # Perform initial git commit␊ + git add .␊ + git commit -m "openzeppelin: add wizard output" --quiet␊ + ␊ + echo "Done."␊ + else␊ + echo "Foundry project already initialized."␊ + fi␊ + `, + `# Sample Foundry Project␊ + ␊ + This project demonstrates a basic Foundry use case. It comes with a contract generated by [OpenZeppelin Wizard](https://wizard.openzeppelin.com/), a test for that contract, and a script that deploys that contract.␊ + ␊ + ## Installing Foundry␊ + ␊ + See [Foundry installation guide](https://book.getfoundry.sh/getting-started/installation).␊ + ␊ + ## Initializing the project␊ + ␊ + \`\`\`␊ + bash setup.sh␊ + \`\`\`␊ + ␊ + ## Testing the contract␊ + ␊ + \`\`\`␊ + forge test --force␊ + \`\`\`␊ + ␊ + ## Deploying the contract␊ + ␊ + You can simulate a deployment by running the script:␊ + ␊ + \`\`\`␊ + forge script script/MyContract.s.sol --force␊ + \`\`\`␊ + ␊ + See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information.␊ + `, + `// SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.20;␊ + ␊ + import {Script} from "forge-std/Script.sol";␊ + import {console} from "forge-std/console.sol";␊ + import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";␊ + import {MyContract} from "src/MyContract.sol";␊ + ␊ + contract MyContractScript is Script {␊ + function setUp() public {}␊ + ␊ + function run() public {␊ + // TODO: Set addresses for the variables below, then uncomment the following section:␊ + /*␊ + vm.startBroadcast();␊ + address initialOwner = ;␊ + address initialAuthority = ;␊ + address proxy = Upgrades.deployTransparentProxy(␊ + "MyContract.sol",␊ + initialOwner,␊ + abi.encodeCall(MyContract.initialize, (initialAuthority))␊ + );␊ + MyContract instance = MyContract(proxy);␊ + console.log("Proxy deployed to %s", address(instance));␊ + vm.stopBroadcast();␊ + */␊ + }␊ + }␊ + `, + `// SPDX-License-Identifier: MIT␊ + // Compatible with OpenZeppelin Contracts ^5.0.0␊ + pragma solidity ^0.8.20;␊ + ␊ + import "@openzeppelin/contracts-upgradeable/access/manager/AccessManagedUpgradeable.sol";␊ + import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";␊ + ␊ + contract MyContract is Initializable, AccessManagedUpgradeable {␊ + /// @custom:oz-upgrades-unsafe-allow constructor␊ + constructor() {␊ + _disableInitializers();␊ + }␊ + ␊ + function initialize(address initialAuthority) initializer public {␊ + __AccessManaged_init(initialAuthority);␊ + }␊ + }␊ + `, + `// SPDX-License-Identifier: MIT␊ + pragma solidity ^0.8.20;␊ + ␊ + import {Test} from "forge-std/Test.sol";␊ + import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";␊ + import {MyContract} from "src/MyContract.sol";␊ + ␊ + contract MyContractTest is Test {␊ + MyContract public instance;␊ + ␊ + function setUp() public {␊ + address initialOwner = vm.addr(1);␊ + address initialAuthority = vm.addr(2);␊ + address proxy = Upgrades.deployTransparentProxy(␊ + "MyContract.sol",␊ + initialOwner,␊ + abi.encodeCall(MyContract.initialize, (initialAuthority))␊ + );␊ + instance = MyContract(proxy);␊ + }␊ + ␊ + function testSomething() public {␊ + // Add your test here␊ + }␊ + }␊ + `, + ] diff --git a/packages/core/src/zip-foundry.test.ts.snap b/packages/core/src/zip-foundry.test.ts.snap index c2e9f120a4d81b968e75696b7bcc623f07309262..b3cd77b1451144419ef7c309a573b62b5c31aa72 100644 GIT binary patch literal 3383 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 N{{eM^;BIn$008n5k@Nrn literal 2266 zcmV<02qpJHRzVtY%gU+!k`)kZWi&&MNsi>TphMA~`ybLnuSNSC`V)#Cdg`e_Z#@)6kA38jvorf4S*fZR z#t5)yNj|>s_xK+9b-x$$@Ie0hw?t7Dldp&fm%FH+#W9r(r*g=Z{N>kCgcW}D-9KDy zyw%Wu&FjrKe+u8b@b7om8jT@LKYP2;`0IBXjqf$ze6RV&st|b8Y`R}JZro@zy+-qm zM)O^OxYGE3^D2zqyRv)j=KDb=MbM)xAnXwJupD~cO|&*7;Q^w3G@uGmi71l_$1#aK zn0p@`4QV(uYd^e$f`}Xj40!g`kT4G+5)L_P>D7=Y31$&uT%kVCSmdM49Fy6smy{SG zJiwIsExq+QOfPwTT4Caz-U@K@80m8{Ag8dBub;wRD)?6~Wp|To9NRR}lDd#f-Rs0n`4?y zmt=l_1-!rNjyu*Jz5OA?v_lzz?ZK4<*j*y5a}f=TEtv#oz>+}F8tDM!h^isLIn|*7 zVMK3^P;Z1DSDf}!Bk>n)7tOf_{IY6%KpHG`0HO98?b={?tP1oUlO3@2wtOujY0O8m z3g|<)7qL@{SI^V=QVMx*>Gf|7JW)i}bHipO;bK7$Lt(1vamD)Jo z8twA~!fYHH+D37EJf;y$exP$(A-h@h3fH#t`Br@n+F!>y^JFYEP6Wn%n6?*Cn)PCcoZ}O( zyb3lbt(klVQtfZAZ?EXXNhbzDq=dN2U4Lv~gvB7!rj)2hVt&*y84&?uNHW^O8o1A6 zSkguTpC{7cKTLe%v3CW>5yY=*FWh!(R;WA@7#u_&Ax4hebUPw_oW*K2N+|1~6hj6O z9RveN=3^wlBq)pcF(0&B?rgH|2bn-|^aI)IIO*Ds*1{NrVUed39G-Y5g(F;bj_}D_ zXE_3_ukj=WPwGLwGZ||3>50C)<#+w=OL)$_<3wpK1sl6-%ia9{372}AedM_UF~B`! zK5;h_3rRROMEUxOQMzw!aIqHuQTVSo?6<5ElyVtUlb^*nP6QeSZVO z@qmOOK3Kl>qjsyb(6X^J569N#gZ-H}N(&l?L1K1^EQB=Own2)jk?&?*25@7W2@Dqf zXXFGTBV_alLEqZi|3zy8AlFa{EMGq^pq>l_2(b$}rMmv!l@6Ph7eFeNT|1=1QvPa=;{oAg5@V!3XM1^xb6#{jJ>fe!y%3{GWI)T zC5Gc-!qIJ)<9r<01D*-G|0OYY>0{GAZeA8;GS}p3j{{c?O zlo(JU3bpa9D5QL5pmee=GODYe?6p=*MEK+7rM#h=zSw%_?r9fW|G3_0{PXSlVynK` zsxP+ci>>-%tG?K(FSai1VvD_|7h7|0ozA#&vQYoyozgijl>XIt6)%*aShJrqE-7?X zcRba`FW99Sy|}jBjB@{HuU)x+nuGoSD(wU+?WLt#x8{}F@BZbqQX4~={quL~QoAm- z>r%Tewd+#5F171Y`@%}?zrUuX_N)DdY%|Mt>Av~0-;Do!rs2Ph5?@z|?t8u$TF_ { ); function getImports(c: Contract) { - return [ - 'import "forge-std/Test.sol";', - `import "../src/${c.name}.sol";`, + const result = [ + 'import {Test} from "forge-std/Test.sol";', ]; + if (c.upgradeable) { + result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); + } + result.push(`import {${c.name}} from "src/${c.name}.sol";`); + return result; } function getTestCase(c: Contract) { @@ -40,10 +44,8 @@ const test = (c: Contract, opts?: GenericOptions) => { ], [ 'function setUp() public {', - getAddressVariables(args), - [ - `instance = new ${getDeploymentCall(c, args)};`, - ], + getAddressVariables(c, args), + getDeploymentCode(c, args), '}', ], getContractSpecificTestFunction(), @@ -52,11 +54,45 @@ const test = (c: Contract, opts?: GenericOptions) => { ]; } - function getAddressVariables(args: string[]): Lines[] { + function getDeploymentCode(c: Contract, args: string[]): Lines[] { + if (c.upgradeable) { + if (opts?.upgradeable === 'transparent') { + return [ + `address proxy = Upgrades.deployTransparentProxy(`, + [ + `"${c.name}.sol",`, + `initialOwner,`, + `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + ], + ');', + `instance = ${c.name}(proxy);`, + ]; + } else { + return [ + `address proxy = Upgrades.deployUUPSProxy(`, + [ + `"${c.name}.sol",`, + `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + ], + ');', + `instance = ${c.name}(proxy);`, + ]; + } + } else { + return [ + `instance = new ${c.name}(${args.join(', ')});`, + ]; + } + } + + function getAddressVariables(c: Contract, args: string[]): Lines[] { const vars = []; - for (let i = 0; i < args.length; i++) { - // use i + 1 as the private key since it must be non-zero - vars.push(`address ${args[i]} = vm.addr(${i + 1});`); + let i = 1; // private key index starts from 1 since it must be non-zero + if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { + vars.push(`address initialOwner = vm.addr(${i++});`); + } + for (const arg of args) { + vars.push(`address ${arg} = vm.addr(${i++});`); } return vars; } @@ -67,7 +103,7 @@ const test = (c: Contract, opts?: GenericOptions) => { case 'ERC20': case 'ERC721': return [ - 'function testName() public {', + 'function testName() public view {', [ `assertEq(instance.name(), "${opts.name}");` ], @@ -76,7 +112,7 @@ const test = (c: Contract, opts?: GenericOptions) => { case 'ERC1155': return [ - 'function testUri() public {', + 'function testUri() public view {', [ `assertEq(instance.uri(0), "${opts.uri}");` ], @@ -111,11 +147,7 @@ function getAddressArgs(c: Contract): string[] { return args; } -function getDeploymentCall(c: Contract, args: string[]): string { - return `${c.name}(${args.join(', ')})`; -} - -const script = (c: Contract) => { +const script = (c: Contract, opts?: GenericOptions) => { return formatLinesWithSpaces( 2, ...spaceBetween( @@ -126,18 +158,24 @@ const script = (c: Contract) => { ); function getImports(c: Contract) { - return [ - 'import "forge-std/Script.sol";', - `import "../src/${c.name}.sol";`, + const result = [ + 'import {Script} from "forge-std/Script.sol";', + 'import {console} from "forge-std/console.sol";', ]; + if (c.upgradeable) { + result.push('import {Upgrades} from "openzeppelin-foundry-upgrades/Upgrades.sol";'); + } + result.push(`import {${c.name}} from "src/${c.name}.sol";`); + return result; } function getScript(c: Contract) { const args = getAddressArgs(c); const deploymentLines = [ 'vm.startBroadcast();', - `${c.name} instance = new ${getDeploymentCall(c, args)};`, - 'console.log("Contract deployed to %s", address(instance));', + ...getAddressVariables(c, args), + ...getDeploymentCode(c, args), + `console.log("${c.upgradeable ? 'Proxy' : 'Contract'} deployed to %s", address(instance));`, 'vm.stopBroadcast();', ]; return [ @@ -156,15 +194,59 @@ const script = (c: Contract) => { ]; } - function addTodoAndCommentOut(lines: string[]) { + function getDeploymentCode(c: Contract, args: string[]): Lines[] { + if (c.upgradeable) { + if (opts?.upgradeable === 'transparent') { + return [ + `address proxy = Upgrades.deployTransparentProxy(`, + [ + `"${c.name}.sol",`, + `initialOwner,`, + `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + ], + ');', + `${c.name} instance = ${c.name}(proxy);`, + ]; + } else { + return [ + `address proxy = Upgrades.deployUUPSProxy(`, + [ + `"${c.name}.sol",`, + `abi.encodeCall(${c.name}.initialize, (${args.join(', ')}))` + ], + ');', + `${c.name} instance = ${c.name}(proxy);`, + ]; + } + } else { + return [ + `${c.name} instance = new ${c.name}(${args.join(', ')});`, + ]; + } + } + + function getAddressVariables(c: Contract, args: string[]): Lines[] { + const vars = []; + if (c.upgradeable && opts?.upgradeable === 'transparent' && !args.includes('initialOwner')) { + vars.push('address initialOwner = ;'); + } + for (const arg of args) { + vars.push(`address ${arg} = ;`); + } + return vars; + } + + function addTodoAndCommentOut(lines: Lines[]) { return [ - '// TODO: Set addresses for the contract arguments below, then uncomment the following lines', - ...lines.map(l => `// ${l}`), + '// TODO: Set addresses for the variables below, then uncomment the following section:', + '/*', + ...lines, + '*/', ]; } }; -const setupSh = `\ +const setupSh = (c: Contract) => `\ #!/usr/bin/env bash # Check if git is installed @@ -186,23 +268,48 @@ if ! [ -f "foundry.toml" ] then echo "Initializing Foundry project..." + # Backup Wizard template readme to avoid it being overwritten + mv README.md README-oz.md + # Initialize sample Foundry project forge init --force --no-commit --quiet +${c.upgradeable ? `\ + # Install OpenZeppelin Contracts and Upgrades + forge install OpenZeppelin/openzeppelin-contracts-upgradeable@v${contracts.version} --no-commit --quiet + forge install OpenZeppelin/openzeppelin-foundry-upgrades --no-commit --quiet\ +` : `\ # Install OpenZeppelin Contracts - forge install OpenZeppelin/openzeppelin-contracts@v${contracts.version} --no-commit --quiet + forge install OpenZeppelin/openzeppelin-contracts@v${contracts.version} --no-commit --quiet\ +`} - # Remove template contracts + # Remove unneeded Foundry template files rm src/Counter.sol rm script/Counter.s.sol rm test/Counter.t.sol + rm README.md + + # Restore Wizard template readme + mv README-oz.md README.md # Add remappings if [ -f "remappings.txt" ] then echo "" >> remappings.txt fi - echo "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" >> remappings.txt +${c.upgradeable ? `\ + echo "@openzeppelin/contracts/=lib/openzeppelin-contracts-upgradeable/lib/openzeppelin-contracts/contracts/" >> remappings.txt + echo "@openzeppelin/contracts-upgradeable/=lib/openzeppelin-contracts-upgradeable/contracts/" >> remappings.txt + + # Add settings in foundry.toml + echo "" >> foundry.toml + echo "ffi = true" >> foundry.toml + echo "ast = true" >> foundry.toml + echo "build_info = true" >> foundry.toml + echo "extra_output = [\\"storageLayout\\"]" >> foundry.toml\ +` : `\ + echo "@openzeppelin/contracts/=lib/openzeppelin-contracts/contracts/" >> remappings.txt\ +`} # Perform initial git commit git add . @@ -232,7 +339,7 @@ bash setup.sh ## Testing the contract \`\`\` -forge test +forge test${c.upgradeable ? ' --force' : ''} \`\`\` ## Deploying the contract @@ -240,7 +347,7 @@ forge test You can simulate a deployment by running the script: \`\`\` -forge script script/${c.name}.s.sol +forge script script/${c.name}.s.sol${c.upgradeable ? ' --force' : ''} \`\`\` See [Solidity scripting guide](https://book.getfoundry.sh/tutorials/solidity-scripting) for more information. @@ -251,8 +358,8 @@ export async function zipFoundry(c: Contract, opts?: GenericOptions) { zip.file(`src/${c.name}.sol`, printContract(c)); zip.file(`test/${c.name}.t.sol`, test(c, opts)); - zip.file(`script/${c.name}.s.sol`, script(c)); - zip.file('setup.sh', setupSh); + zip.file(`script/${c.name}.s.sol`, script(c, opts)); + zip.file('setup.sh', setupSh(c)); zip.file('README.md', readme(c)); return zip; diff --git a/packages/ui/src/App.svelte b/packages/ui/src/App.svelte index 9c646739..420f44b3 100644 --- a/packages/ui/src/App.svelte +++ b/packages/ui/src/App.svelte @@ -243,7 +243,7 @@ {/if} - {#if opts?.kind !== "Governor" && opts?.upgradeable === false} + {#if opts?.kind !== "Governor"}