From a55c29d61ed8aa384eb8e065ca0cbe0f5df994c6 Mon Sep 17 00:00:00 2001 From: Daniel Simon Date: Tue, 9 Apr 2024 14:57:56 +0700 Subject: [PATCH] feat: add Sourcify support to deploy-cli Also refactor the deployment script and our test harnesses to share a single deployment function. --- contracts/.gitignore | 1 + contracts/scripts/DeployLiquity2.s.sol | 157 ------------------ contracts/src/deployment.sol | 115 +++++++++++++ contracts/src/scripts/DeployLiquity2.s.sol | 83 +++++++++ .../src/test/TestContracts/DevTestSetup.sol | 110 ++---------- contracts/utils/deploy-cli.ts | 54 +++++- 6 files changed, 258 insertions(+), 262 deletions(-) delete mode 100644 contracts/scripts/DeployLiquity2.s.sol create mode 100644 contracts/src/deployment.sol create mode 100644 contracts/src/scripts/DeployLiquity2.s.sol diff --git a/contracts/.gitignore b/contracts/.gitignore index 842f3e85..26e79829 100644 --- a/contracts/.gitignore +++ b/contracts/.gitignore @@ -5,6 +5,7 @@ out/ # Ignores development broadcast logs !/broadcast /broadcast/*/31337/ +/broadcast/*/1337/ /broadcast/**/dry-run/ # Docs diff --git a/contracts/scripts/DeployLiquity2.s.sol b/contracts/scripts/DeployLiquity2.s.sol deleted file mode 100644 index b80e4bfc..00000000 --- a/contracts/scripts/DeployLiquity2.s.sol +++ /dev/null @@ -1,157 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.18; - -import {Script} from "forge-std/Script.sol"; -import {ActivePool} from "../src/ActivePool.sol"; -import {BorrowerOperations} from "../src/BorrowerOperations.sol"; -import {CollSurplusPool} from "../src/CollSurplusPool.sol"; -import {DefaultPool} from "../src/DefaultPool.sol"; -import {GasPool} from "../src/GasPool.sol"; -import {PriceFeedTestnet} from "../src/TestContracts/PriceFeedTestnet.sol"; -import {PriceFeedMock} from "../src/test/TestContracts/PriceFeedMock.sol"; -import {SortedTroves} from "../src/SortedTroves.sol"; -import {StabilityPool} from "../src/StabilityPool.sol"; -import {TroveManager} from "../src/TroveManager.sol"; -import {BoldToken} from "../src/BoldToken.sol"; -import {FunctionCaller} from "../src/TestContracts/FunctionCaller.sol"; -import {HintHelpers} from "../src/HintHelpers.sol"; -import {Accounts} from "../src/test/TestContracts/Accounts.sol"; - -contract DeployLiquity2Script is Script { - ActivePool activePool; - BorrowerOperations borrowerOperations; - CollSurplusPool collSurplusPool; - DefaultPool defaultPool; - GasPool gasPool; - PriceFeedTestnet priceFeed; - SortedTroves sortedTroves; - StabilityPool stabilityPool; - TroveManager troveManager; - BoldToken boldToken; - FunctionCaller functionCaller; - HintHelpers hintHelpers; - - function run() external { - if (vm.envBytes("DEPLOYER").length == 20) { - // address - vm.startBroadcast(vm.envAddress("DEPLOYER")); - } else { - // private key - vm.startBroadcast(vm.envUint("DEPLOYER")); - } - - deployCoreContracts(); - connectCoreContracts(); - - vm.stopBroadcast(); - - if (vm.envOr("OPEN_DEMO_TROVES", false)) { - openDemoTroves(); - } - } - - function deployCoreContracts() internal { - activePool = new ActivePool(); - borrowerOperations = new BorrowerOperations(); - collSurplusPool = new CollSurplusPool(); - defaultPool = new DefaultPool(); - gasPool = new GasPool(); - priceFeed = new PriceFeedTestnet(); - sortedTroves = new SortedTroves(); - stabilityPool = new StabilityPool(); - troveManager = new TroveManager(); - boldToken = new BoldToken(address(troveManager), address(stabilityPool), address(borrowerOperations)); - functionCaller = new FunctionCaller(); - hintHelpers = new HintHelpers(); - } - - function connectCoreContracts() internal { - troveManager.setAddresses( - address(borrowerOperations), - address(activePool), - address(defaultPool), - address(stabilityPool), - address(gasPool), - address(collSurplusPool), - address(priceFeed), - address(boldToken), - address(sortedTroves) - ); - stabilityPool.setAddresses( - address(borrowerOperations), - address(troveManager), - address(activePool), - address(boldToken), - address(sortedTroves), - address(priceFeed) - ); - - sortedTroves.setParams( - 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff, - address(troveManager), - address(borrowerOperations) - ); - functionCaller.setTroveManagerAddress(address(troveManager)); - functionCaller.setSortedTrovesAddress(address(sortedTroves)); - borrowerOperations.setAddresses( - address(troveManager), - address(activePool), - address(defaultPool), - address(stabilityPool), - address(gasPool), - address(collSurplusPool), - address(priceFeed), - address(sortedTroves), - address(boldToken) - ); - activePool.setAddresses( - address(borrowerOperations), address(troveManager), address(stabilityPool), address(defaultPool) - ); - defaultPool.setAddresses(address(troveManager), address(activePool)); - collSurplusPool.setAddresses(address(borrowerOperations), address(troveManager), address(activePool)); - hintHelpers.setAddresses(address(sortedTroves), address(troveManager)); - } - - struct TroveParams { - uint256 coll; - uint256 debt; - } - - function openDemoTroves() internal { - address[10] memory accounts = createAccounts(); - - uint256 eth = 1e18; - uint256 bold = 1e18; - - TroveParams[8] memory troves = [ - TroveParams(20 * eth, 1800 * bold), - TroveParams(32 * eth, 2800 * bold), - TroveParams(30 * eth, 4000 * bold), - TroveParams(65 * eth, 6000 * bold), - TroveParams(50 * eth, 5000 * bold), - TroveParams(37 * eth, 2400 * bold), - TroveParams(37 * eth, 2800 * bold), - TroveParams(36 * eth, 2222 * bold) - ]; - - for (uint256 i = 0; i < troves.length; i++) { - vm.startPrank(accounts[i]); - borrowerOperations.openTrove{value: troves[i].coll}( - 1e18, // max fee, 100% - troves[i].debt, // debt in BOLD - address(0), // upperHint - address(0), // lowerHint - 5 * 1e16 // interest rate, 5% - ); - vm.stopPrank(); - } - } - - function createAccounts() internal returns (address[10] memory accountsList) { - Accounts accounts = new Accounts(); - for (uint256 i = 0; i < accounts.getAccountsCount(); i++) { - accountsList[i] = vm.addr(uint256(accounts.accountsPks(i))); - vm.deal(accountsList[i], 1000 * 1e18); - } - } -} diff --git a/contracts/src/deployment.sol b/contracts/src/deployment.sol new file mode 100644 index 00000000..a25a52dc --- /dev/null +++ b/contracts/src/deployment.sol @@ -0,0 +1,115 @@ +// SPDX-License-Identifier: MIT + +pragma solidity 0.8.18; + +import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; + +import "./ActivePool.sol"; +import "./BoldToken.sol"; +import "./BorrowerOperations.sol"; +import "./CollSurplusPool.sol"; +import "./DefaultPool.sol"; +import "./GasPool.sol"; +import "./HintHelpers.sol"; +import "./MultiTroveGetter.sol"; +import "./SortedTroves.sol"; +import "./StabilityPool.sol"; +import "./TroveManager.sol"; +import "./MockInterestRouter.sol"; +import "./test/TestContracts/PriceFeedTestnet.sol"; + +struct LiquityContracts { + IActivePool activePool; + IBorrowerOperations borrowerOperations; + ICollSurplusPool collSurplusPool; + IDefaultPool defaultPool; + ISortedTroves sortedTroves; + IStabilityPool stabilityPool; + ITroveManager troveManager; + IBoldToken boldToken; + IPriceFeedTestnet priceFeed; + GasPool gasPool; + IInterestRouter interestRouter; + IERC20 WETH; +} + +function _deployAndConnectContracts() returns (LiquityContracts memory contracts) { + contracts.WETH = new ERC20("Wrapped ETH", "WETH"); + + // TODO: optimize deployment order & constructor args & connector functions + + // Deploy all contracts + contracts.activePool = new ActivePool(address(contracts.WETH)); + contracts.borrowerOperations = new BorrowerOperations(address(contracts.WETH)); + contracts.collSurplusPool = new CollSurplusPool(address(contracts.WETH)); + contracts.defaultPool = new DefaultPool(address(contracts.WETH)); + contracts.gasPool = new GasPool(); + contracts.priceFeed = new PriceFeedTestnet(); + contracts.sortedTroves = new SortedTroves(); + contracts.stabilityPool = new StabilityPool(address(contracts.WETH)); + contracts.troveManager = new TroveManager(); + contracts.interestRouter = new MockInterestRouter(); + + contracts.boldToken = new BoldToken( + address(contracts.troveManager), + address(contracts.stabilityPool), + address(contracts.borrowerOperations), + address(contracts.activePool) + ); + + // Connect contracts + contracts.sortedTroves.setParams( + type(uint256).max, address(contracts.troveManager), address(contracts.borrowerOperations) + ); + + // set contracts in the Trove Manager + contracts.troveManager.setAddresses( + address(contracts.borrowerOperations), + address(contracts.activePool), + address(contracts.defaultPool), + address(contracts.stabilityPool), + address(contracts.gasPool), + address(contracts.collSurplusPool), + address(contracts.priceFeed), + address(contracts.boldToken), + address(contracts.sortedTroves) + ); + + // set contracts in BorrowerOperations + contracts.borrowerOperations.setAddresses( + address(contracts.troveManager), + address(contracts.activePool), + address(contracts.defaultPool), + address(contracts.stabilityPool), + address(contracts.gasPool), + address(contracts.collSurplusPool), + address(contracts.priceFeed), + address(contracts.sortedTroves), + address(contracts.boldToken) + ); + + // set contracts in the Pools + contracts.stabilityPool.setAddresses( + address(contracts.borrowerOperations), + address(contracts.troveManager), + address(contracts.activePool), + address(contracts.boldToken), + address(contracts.sortedTroves), + address(contracts.priceFeed) + ); + + contracts.activePool.setAddresses( + address(contracts.borrowerOperations), + address(contracts.troveManager), + address(contracts.stabilityPool), + address(contracts.defaultPool), + address(contracts.boldToken), + address(contracts.interestRouter) + ); + + contracts.defaultPool.setAddresses(address(contracts.troveManager), address(contracts.activePool)); + + contracts.collSurplusPool.setAddresses( + address(contracts.borrowerOperations), address(contracts.troveManager), address(contracts.activePool) + ); +} diff --git a/contracts/src/scripts/DeployLiquity2.s.sol b/contracts/src/scripts/DeployLiquity2.s.sol new file mode 100644 index 00000000..faed6ab2 --- /dev/null +++ b/contracts/src/scripts/DeployLiquity2.s.sol @@ -0,0 +1,83 @@ +// SPDX-License-Identifier: UNLICENSED +pragma solidity 0.8.18; + +import {Script} from "forge-std/Script.sol"; +import {StdCheats} from "forge-std/StdCheats.sol"; +import "../deployment.sol"; +import {Accounts} from "../test/TestContracts/Accounts.sol"; + +contract DeployLiquity2Script is Script, StdCheats { + struct TroveParams { + uint256 coll; + uint256 debt; + } + + function run() external { + if (vm.envBytes("DEPLOYER").length == 20) { + // address + vm.startBroadcast(vm.envAddress("DEPLOYER")); + } else { + // private key + vm.startBroadcast(vm.envUint("DEPLOYER")); + } + + LiquityContracts memory contracts = _deployAndConnectContracts(); + vm.stopBroadcast(); + + if (vm.envOr("OPEN_DEMO_TROVES", false)) { + openDemoTroves(contracts.WETH, contracts.borrowerOperations); + } + } + + function openDemoTroves(IERC20 WETH, IBorrowerOperations borrowerOperations) internal { + address[10] memory accounts = createAccounts(WETH, borrowerOperations); + + uint256 eth = 1e18; + uint256 bold = 1e18; + + TroveParams[8] memory troves = [ + TroveParams(20 * eth, 1800 * bold), + TroveParams(32 * eth, 2800 * bold), + TroveParams(30 * eth, 4000 * bold), + TroveParams(65 * eth, 6000 * bold), + TroveParams(50 * eth, 5000 * bold), + TroveParams(37 * eth, 2400 * bold), + TroveParams(37 * eth, 2800 * bold), + TroveParams(36 * eth, 2222 * bold) + ]; + + for (uint256 i = 0; i < troves.length; i++) { + vm.startPrank(accounts[i]); + + borrowerOperations.openTrove( + accounts[i], // _owner + 1, // _ownerIndex + 1e18, // _maxFeePercentage + troves[i].coll, // _ETHAmount + troves[i].debt, // _boldAmount + 0, // _upperHint + 0, // _lowerHint + 0.05e18 // _annualInterestRate + ); + + vm.stopPrank(); + } + } + + function createAccounts(IERC20 WETH, IBorrowerOperations borrowerOperations) + internal + returns (address[10] memory accountsList) + { + Accounts accounts = new Accounts(); + + for (uint256 i = 0; i < accounts.getAccountsCount(); i++) { + accountsList[i] = vm.addr(uint256(accounts.accountsPks(i))); + deal(address(WETH), accountsList[i], 1000e18); + + // Approve infinite WETH to BorrowerOperations + vm.startPrank(accountsList[i]); + WETH.approve(address(borrowerOperations), type(uint256).max); + vm.stopPrank(); + } + } +} diff --git a/contracts/src/test/TestContracts/DevTestSetup.sol b/contracts/src/test/TestContracts/DevTestSetup.sol index 2bd6f48c..25add719 100644 --- a/contracts/src/test/TestContracts/DevTestSetup.sol +++ b/contracts/src/test/TestContracts/DevTestSetup.sol @@ -2,24 +2,8 @@ // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.18; -import "openzeppelin-contracts/contracts/token/ERC20/ERC20.sol"; - -import "./Interfaces/IPriceFeedTestnet.sol"; - -import "../../ActivePool.sol"; -import "../../BoldToken.sol"; -import "../../BorrowerOperations.sol"; -import "../../CollSurplusPool.sol"; -import "../../DefaultPool.sol"; -import "../../GasPool.sol"; -import "../../HintHelpers.sol"; -import "../../MultiTroveGetter.sol"; -import "../../SortedTroves.sol"; -import "../../StabilityPool.sol"; -import "../../TroveManager.sol"; -import "../../MockInterestRouter.sol"; - import "./BaseTest.sol"; +import "../../deployment.sol"; contract DevTestSetup is BaseTest { @@ -51,85 +35,19 @@ contract DevTestSetup is BaseTest { (A, B, C, D, E, F, G) = (accountsList[0], accountsList[1], accountsList[2], accountsList[3], accountsList[4], accountsList[5], accountsList[6]); - WETH = new ERC20("Wrapped ETH", "WETH"); - - // TODO: optimize deployment order & constructor args & connector functions - - // Deploy all contracts - activePool = new ActivePool(address(WETH)); - borrowerOperations = new BorrowerOperations(address(WETH)); - collSurplusPool = new CollSurplusPool(address(WETH)); - defaultPool = new DefaultPool(address(WETH)); - gasPool = new GasPool(); - priceFeed = new PriceFeedTestnet(); - sortedTroves = new SortedTroves(); - stabilityPool = new StabilityPool(address(WETH)); - troveManager = new TroveManager(); - boldToken = new BoldToken(address(troveManager), address(stabilityPool), address(borrowerOperations), address(activePool)); - mockInterestRouter = new MockInterestRouter(); - - // Connect contracts - sortedTroves.setParams( - MAX_UINT256, - address(troveManager), - address(borrowerOperations) - ); - - // set contracts in the Trove Manager - troveManager.setAddresses( - address(borrowerOperations), - address(activePool), - address(defaultPool), - address(stabilityPool), - address(gasPool), - address(collSurplusPool), - address(priceFeed), - address(boldToken), - address(sortedTroves) - ); - - // set contracts in BorrowerOperations - borrowerOperations.setAddresses( - address(troveManager), - address(activePool), - address(defaultPool), - address(stabilityPool), - address(gasPool), - address(collSurplusPool), - address(priceFeed), - address(sortedTroves), - address(boldToken) - ); - - // set contracts in the Pools - stabilityPool.setAddresses( - address(borrowerOperations), - address(troveManager), - address(activePool), - address(boldToken), - address(sortedTroves), - address(priceFeed) - ); - - activePool.setAddresses( - address(borrowerOperations), - address(troveManager), - address(stabilityPool), - address(defaultPool), - address(boldToken), - address(mockInterestRouter) - ); - - defaultPool.setAddresses( - address(troveManager), - address(activePool) - ); - - collSurplusPool.setAddresses( - address(borrowerOperations), - address(troveManager), - address(activePool) - ); + LiquityContracts memory contracts = _deployAndConnectContracts(); + WETH = contracts.WETH; + activePool = contracts.activePool; + borrowerOperations = contracts.borrowerOperations; + collSurplusPool = contracts.collSurplusPool; + defaultPool = contracts.defaultPool; + gasPool = contracts.gasPool; + priceFeed = contracts.priceFeed; + sortedTroves = contracts.sortedTroves; + stabilityPool = contracts.stabilityPool; + troveManager = contracts.troveManager; + boldToken = contracts.boldToken; + mockInterestRouter = contracts.interestRouter; // Give some ETH to test accounts, and approve it to BorrowerOperations uint256 initialETHAmount = 10_000e18; diff --git a/contracts/utils/deploy-cli.ts b/contracts/utils/deploy-cli.ts index 3064c131..79344f66 100644 --- a/contracts/utils/deploy-cli.ts +++ b/contracts/utils/deploy-cli.ts @@ -13,6 +13,7 @@ Arguments: - local: Deploy to a local network - mainnet: Deploy to the Ethereum mainnet - tenderly-devnet: Deploy to a Tenderly devnet + - liquity-testnet: Deploy to the Liquity v2 testnet Options: @@ -22,13 +23,16 @@ Options: --ledger-path HD path to use with the Ledger (only used when DEPLOYER is an address). --etherscan-api-key Etherscan API key to verify the contracts - (mainnet only). + (required when verifying with Etherscan). --help, -h Show this help message. --open-demo-troves Open demo troves after deployment (local only). --rpc-url RPC URL to use. - --verify Verify contracts on Etherscan after - deployment (requires ETHERSCAN_API_KEY). + --verify Verify contracts after deployment. + --verifier Verification provider to use. + Possible values: etherscan, sourcify. + --verifier-url The verifier URL, if using a custom + provider. Note: options can also be set via corresponding environment variables, e.g. --chain-id can be set via CHAIN_ID instead. Parameters take precedence over variables. @@ -51,6 +55,14 @@ export async function main() { options.rpcUrl ??= "http://localhost:8545"; } + // network preset: liquity-testnet + if (networkPreset === "liquity-testnet") { + options.chainId ??= 1337; + options.rpcUrl ??= "https://testnet.liquity.org/rpc"; + options.verifier ??= "sourcify"; + options.verifierUrl ??= "https://testnet.liquity.org/sourcify/server"; + } + // network preset: tenderly-devnet if (networkPreset === "tenderly-devnet") { options.chainId ??= 1; @@ -69,6 +81,8 @@ export async function main() { options.chainId ??= 1; } + options.verifier ??= "etherscan"; + // handle missing options if (!options.chainId) { throw new Error("--chain-id is required"); @@ -79,15 +93,15 @@ export async function main() { if (!options.deployer) { throw new Error("--deployer is required"); } - if (options.verify && !options.etherscanApiKey) { + if (options.verify && options.verifier === "etherscan" && !options.etherscanApiKey) { throw new Error( - "--verify requires --etherscan-api-key ", + "Verifying with Etherscan requires --etherscan-api-key ", ); } const forgeArgs: string[] = [ "script", - "scripts/DeployLiquity2.s.sol:DeployLiquity2Script", + "src/scripts/DeployLiquity2.s.sol", "--chain-id", String(options.chainId), "--rpc-url", @@ -96,10 +110,26 @@ export async function main() { ]; // verify - if (options.verify && options.etherscanApiKey) { + if (options.verify) { forgeArgs.push("--verify"); - forgeArgs.push("--etherscan-api-key"); - forgeArgs.push(options.etherscanApiKey); + + // Etherscan API key + if (options.etherscanApiKey) { + forgeArgs.push("--etherscan-api-key"); + forgeArgs.push(options.etherscanApiKey); + } + + // verifier + if (options.verifier) { + forgeArgs.push("--verifier"); + forgeArgs.push(options.verifier); + } + + // verifier URL + if (options.verifierUrl) { + forgeArgs.push("--verifier-url"); + forgeArgs.push(options.verifierUrl); + } } // Ledger signing @@ -121,6 +151,8 @@ Deploying Liquity contracts with the following settings: OPEN_DEMO_TROVES: ${options.openDemoTroves ? "yes" : "no"} RPC_URL: ${options.rpcUrl} VERIFY: ${options.verify ? "yes" : "no"} + VERIFIER: ${options.verifier} + VERIFIER_URL: ${options.verifierUrl} `; const envVars = [ @@ -208,6 +240,8 @@ async function parseArgs() { openDemoTroves: argBoolean("open-demo-troves"), rpcUrl: argv["rpc-url"], verify: argBoolean("verify"), + verifier: argv["verifier"], + verifierUrl: argv["verifier-url"] }; const [networkPreset] = argv._; @@ -228,6 +262,8 @@ async function parseArgs() { options.verify ??= Boolean( process.env.VERIFY && process.env.VERIFY !== "false", ); + options.verifier ??= process.env.VERIFIER; + options.verifierUrl ??= process.env.VERIFIER_URL; return { options, networkPreset }; }