From f2609600e74af776a2132e27b7cfc1a5403ed3f5 Mon Sep 17 00:00:00 2001 From: Pierre Bertet Date: Tue, 9 Apr 2024 10:26:53 +0100 Subject: [PATCH] Add a CLI tool to deploy the contracts using Foundry (#86) `deploy` can be launched with `--help` to see its documentation: ``` $ ./deploy --help deploy - deploy the Liquity contracts. Usage: ./deploy [NETWORK_PRESET] [OPTIONS] Arguments: NETWORK_PRESET A network preset, which is a shorthand for setting certain options such as the chain ID and RPC URL. Options take precedence over network presets. Available presets: - 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: --chain-id Chain ID to deploy to. --deployer Address or private key to deploy with. Requires a Ledger if an address is used. --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 (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 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. ``` --- .github/workflows/contracts.yml | 37 +- contracts/.gitignore | 1 + contracts/deploy | 8 + contracts/package.json | 4 +- contracts/pnpm-lock.yaml | 465 ++++++++++++++++++ contracts/src/deployment.sol | 115 +++++ contracts/src/scripts/DeployLiquity2.s.sol | 83 ++++ .../src/test/TestContracts/DevTestSetup.sol | 110 +---- contracts/utils/deploy-cli.ts | 269 ++++++++++ 9 files changed, 992 insertions(+), 100 deletions(-) create mode 100755 contracts/deploy create mode 100644 contracts/src/deployment.sol create mode 100644 contracts/src/scripts/DeployLiquity2.s.sol create mode 100644 contracts/utils/deploy-cli.ts diff --git a/.github/workflows/contracts.yml b/.github/workflows/contracts.yml index 78ad66f58..5a228b2ac 100644 --- a/.github/workflows/contracts.yml +++ b/.github/workflows/contracts.yml @@ -22,9 +22,6 @@ on: jobs: test-foundry: - strategy: - fail-fast: true - name: Foundry tests runs-on: ubuntu-latest steps: @@ -48,6 +45,40 @@ jobs: forge test -vvv id: test + deploy: + name: Deploy contracts to Liquity v2 Testnet + runs-on: ubuntu-latest + steps: + - name: Git checkout + uses: actions/checkout@v4 + with: + submodules: recursive + + - name: Install pnpm + uses: pnpm/action-setup@v3.0.0 + with: + version: 8 + + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version-file: '.node-version' + cache: 'pnpm' + cache-dependency-path: 'contracts/pnpm-lock.yaml' + + - name: Install dependencies + run: pnpm install + + - name: Install Foundry + uses: foundry-rs/foundry-toolchain@v1 + with: + version: nightly + + - name: Run deployment tool + run: ./deploy liquity-testnet --verify + env: + DEPLOYER: ${{ secrets.DEPLOYER }} + console-logs: name: Check we didn’t forget to remove console imports runs-on: ubuntu-latest diff --git a/contracts/.gitignore b/contracts/.gitignore index 842f3e850..26e798295 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/deploy b/contracts/deploy new file mode 100755 index 000000000..b48ee53fe --- /dev/null +++ b/contracts/deploy @@ -0,0 +1,8 @@ +#!/usr/bin/env -S npx tsx + +require("./utils/deploy-cli").main().catch(({ message }) => { + console.error(""); + console.error(` Error: ${message}`); + console.error(""); + process.exit(1); +}); diff --git a/contracts/package.json b/contracts/package.json index 834a6a872..5fc517f53 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -41,7 +41,9 @@ "hardhat-gas-reporter": "^1.0.8", "solidity-coverage": "^0.8.8", "ts-node": ">=8.0.0", + "tsx": "^4.7.1", "typechain": "^8.1.0", - "typescript": ">=4.5.0" + "typescript": ">=4.5.0", + "zx": "^7.2.3" } } diff --git a/contracts/pnpm-lock.yaml b/contracts/pnpm-lock.yaml index 1c1491660..6b96a8859 100644 --- a/contracts/pnpm-lock.yaml +++ b/contracts/pnpm-lock.yaml @@ -65,12 +65,18 @@ devDependencies: ts-node: specifier: '>=8.0.0' version: 10.9.2(@types/node@20.11.26)(typescript@5.4.2) + tsx: + specifier: ^4.7.1 + version: 4.7.2 typechain: specifier: ^8.1.0 version: 8.3.2(typescript@5.4.2) typescript: specifier: '>=4.5.0' version: 5.4.2 + zx: + specifier: ^7.2.3 + version: 7.2.3 packages: @@ -132,6 +138,213 @@ packages: deprecated: Please use @ensdomains/ens-contracts dev: true + /@esbuild/aix-ppc64@0.19.12: + resolution: {integrity: sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [aix] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64@0.19.12: + resolution: {integrity: sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm@0.19.12: + resolution: {integrity: sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64@0.19.12: + resolution: {integrity: sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64@0.19.12: + resolution: {integrity: sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64@0.19.12: + resolution: {integrity: sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64@0.19.12: + resolution: {integrity: sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64@0.19.12: + resolution: {integrity: sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64@0.19.12: + resolution: {integrity: sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm@0.19.12: + resolution: {integrity: sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32@0.19.12: + resolution: {integrity: sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64@0.19.12: + resolution: {integrity: sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el@0.19.12: + resolution: {integrity: sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64@0.19.12: + resolution: {integrity: sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64@0.19.12: + resolution: {integrity: sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x@0.19.12: + resolution: {integrity: sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64@0.19.12: + resolution: {integrity: sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64@0.19.12: + resolution: {integrity: sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64@0.19.12: + resolution: {integrity: sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64@0.19.12: + resolution: {integrity: sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64@0.19.12: + resolution: {integrity: sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32@0.19.12: + resolution: {integrity: sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64@0.19.12: + resolution: {integrity: sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@ethereumjs/common@2.5.0: resolution: {integrity: sha512-DEHjW6e38o+JmB/NO3GZBpW4lpaiBpkFgXF6jLcJ6gETBYpEyaA5nTimsWBUJR3Vmtm/didUEbNjajskugZORg==} dependencies: @@ -1389,6 +1602,13 @@ packages: '@types/node': 8.10.66 dev: true + /@types/fs-extra@11.0.4: + resolution: {integrity: sha512-yTbItCNreRooED33qjunPthRcSjERP1r4MqCZc7wv0u2sUkzTFp45tgUfS5+r7FrZPdmCCNflLhVSP/o+SemsQ==} + dependencies: + '@types/jsonfile': 6.1.4 + '@types/node': 20.11.26 + dev: true + /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: @@ -1400,6 +1620,12 @@ packages: resolution: {integrity: sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==} dev: true + /@types/jsonfile@6.1.4: + resolution: {integrity: sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ==} + dependencies: + '@types/node': 20.11.26 + dev: true + /@types/keyv@3.1.4: resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} dependencies: @@ -1414,6 +1640,10 @@ packages: resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==} dev: true + /@types/minimist@1.2.5: + resolution: {integrity: sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==} + dev: true + /@types/mocha@10.0.6: resolution: {integrity: sha512-dJvrYWxP/UcXm36Qn36fxhUKu8A/xMRXVT2cliFF1Z7UA9liG5Psj3ezNSZw+5puH2czDXRLcXQxf8JbJt0ejg==} dev: true @@ -1426,6 +1656,12 @@ packages: resolution: {integrity: sha512-J8xLz7q2OFulZ2cyGTLE1TbbZcjpno7FaN6zdJNrgAdrJ+DZzh/uFR6YrTb4C+nXakvud8Q4+rbhoIWlYQbUFQ==} dev: true + /@types/node@18.19.31: + resolution: {integrity: sha512-ArgCD39YpyyrtFKIqMDvjz79jto5fcI/SVUs2HwB+f0dAzq68yqOdyaSivLiLugSziTpNXLQrVb7RZFmdZzbhA==} + dependencies: + undici-types: 5.26.5 + dev: true + /@types/node@20.11.26: resolution: {integrity: sha512-YwOMmyhNnAWijOBQweOJnQPl068Oqd4K3OFbTc6AHJwzweUwwWG3GIFY74OKks2PJUDkQPeddOQES9mLn1CTEQ==} dependencies: @@ -1446,6 +1682,10 @@ packages: resolution: {integrity: sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==} dev: true + /@types/ps-tree@1.1.6: + resolution: {integrity: sha512-PtrlVaOaI44/3pl3cvnlK+GxOM3re2526TJvPvh7W+keHIXdV4TE0ylpPBAcvFQCbGitaTXwL9u+RF7qtVeazQ==} + dev: true + /@types/qs@6.9.12: resolution: {integrity: sha512-bZcOkJ6uWrL0Qb2NAWKa7TBU+mJHPzhx9jjLL1KHF+XpzEcR7EXHvjbHlGtR/IsP1vyPrehuS6XqkmaePy//mg==} dev: true @@ -1462,6 +1702,10 @@ packages: '@types/node': 20.11.26 dev: true + /@types/which@3.0.3: + resolution: {integrity: sha512-2C1+XoY0huExTbs8MQv1DuS5FS86+SEjdM9F/+GS61gg5Hqbtj8ZiDSx8MfWcyei907fIPbfPGCOrNUTnVHY1g==} + dev: true + /abbrev@1.0.9: resolution: {integrity: sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==} dev: true @@ -2033,6 +2277,11 @@ packages: supports-color: 7.2.0 dev: true + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: true + /change-case@3.0.2: resolution: {integrity: sha512-Mww+SLF6MZ0U6kdg11algyKd5BARbyM4TbFBepwowYSR5ClfQGCGtxNXgykpN0uF/bstWeaGDT4JWaDh8zWAHA==} dependencies: @@ -2423,6 +2672,11 @@ packages: assert-plus: 1.0.0 dev: true + /data-uri-to-buffer@4.0.1: + resolution: {integrity: sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==} + engines: {node: '>= 12'} + dev: true + /death@1.1.0: resolution: {integrity: sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==} dev: true @@ -2594,6 +2848,10 @@ packages: no-case: 2.3.2 dev: true + /duplexer@0.1.2: + resolution: {integrity: sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==} + dev: true + /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} dependencies: @@ -2711,6 +2969,37 @@ packages: ext: 1.7.0 dev: true + /esbuild@0.19.12: + resolution: {integrity: sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/aix-ppc64': 0.19.12 + '@esbuild/android-arm': 0.19.12 + '@esbuild/android-arm64': 0.19.12 + '@esbuild/android-x64': 0.19.12 + '@esbuild/darwin-arm64': 0.19.12 + '@esbuild/darwin-x64': 0.19.12 + '@esbuild/freebsd-arm64': 0.19.12 + '@esbuild/freebsd-x64': 0.19.12 + '@esbuild/linux-arm': 0.19.12 + '@esbuild/linux-arm64': 0.19.12 + '@esbuild/linux-ia32': 0.19.12 + '@esbuild/linux-loong64': 0.19.12 + '@esbuild/linux-mips64el': 0.19.12 + '@esbuild/linux-ppc64': 0.19.12 + '@esbuild/linux-riscv64': 0.19.12 + '@esbuild/linux-s390x': 0.19.12 + '@esbuild/linux-x64': 0.19.12 + '@esbuild/netbsd-x64': 0.19.12 + '@esbuild/openbsd-x64': 0.19.12 + '@esbuild/sunos-x64': 0.19.12 + '@esbuild/win32-arm64': 0.19.12 + '@esbuild/win32-ia32': 0.19.12 + '@esbuild/win32-x64': 0.19.12 + dev: true + /escalade@3.1.2: resolution: {integrity: sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==} engines: {node: '>=6'} @@ -3011,6 +3300,18 @@ packages: es5-ext: 0.10.64 dev: true + /event-stream@3.3.4: + resolution: {integrity: sha512-QHpkERcGsR0T7Qm3HNJSyXKEEj8AHNxkY3PK8TS2KJvQ7NiSHe3DDpwVKKtoYprL/AreyzFBeIkBIWChAqn60g==} + dependencies: + duplexer: 0.1.2 + from: 0.1.7 + map-stream: 0.1.0 + pause-stream: 0.0.11 + split: 0.3.3 + stream-combiner: 0.0.4 + through: 2.3.8 + dev: true + /eventemitter3@4.0.4: resolution: {integrity: sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ==} dev: true @@ -3112,6 +3413,14 @@ packages: reusify: 1.0.4 dev: true + /fetch-blob@3.2.0: + resolution: {integrity: sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==} + engines: {node: ^12.20 || >= 14.13} + dependencies: + node-domexception: 1.0.0 + web-streams-polyfill: 3.3.3 + dev: true + /fill-range@7.0.1: resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} engines: {node: '>=8'} @@ -3230,6 +3539,13 @@ packages: mime-types: 2.1.35 dev: true + /formdata-polyfill@4.0.10: + resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} + engines: {node: '>=12.20.0'} + dependencies: + fetch-blob: 3.2.0 + dev: true + /forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -3244,6 +3560,10 @@ packages: engines: {node: '>= 0.6'} dev: true + /from@0.1.7: + resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} + dev: true + /fs-extra@0.30.0: resolution: {integrity: sha512-UvSPKyhMn6LEd/WpUaV9C9t3zATuqoqfWc3QdPhPLb58prN9tqYPlPWi8Krxi44loBoUzlobqZ3+8tGpxxSzwA==} dependencies: @@ -3254,6 +3574,15 @@ packages: rimraf: 2.7.1 dev: true + /fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + dev: true + /fs-extra@4.0.3: resolution: {integrity: sha512-q6rbdDd1o2mAnQreO7YADIxf/Whx4AHBiRf6d+/cVT8h44ss+lHgxf1FemcqDnQt9X3ct4McHr+JMGlYSsK7Cg==} dependencies: @@ -3316,6 +3645,11 @@ packages: resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} dev: true + /fx@34.0.0: + resolution: {integrity: sha512-/fZih3/WLsrtlaj2mahjWxAmyuikmcl3D5kKPqLtFmEilLsy9wp0+/vEmfvYXXhwJc+ajtCFDCf+yttXmPMHSQ==} + hasBin: true + dev: true + /get-caller-file@1.0.3: resolution: {integrity: sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==} dev: true @@ -3357,6 +3691,12 @@ packages: engines: {node: '>=10'} dev: true + /get-tsconfig@4.7.3: + resolution: {integrity: sha512-ZvkrzoUA0PQZM6fy6+/Hce561s+faD1rsNwhnO5FelNjyy7EMGJ3Rz1AQ8GYDWjhRs/7dBLOEJvhK8MiEJOAFg==} + dependencies: + resolve-pkg-maps: 1.0.0 + dev: true + /getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} dependencies: @@ -3469,6 +3809,17 @@ packages: slash: 3.0.0 dev: true + /globby@13.2.2: + resolution: {integrity: sha512-Y1zNGV+pzQdh7H39l9zgB4PJqjRNqydvdYCDG4HFXM4XuvSaQQlEc91IU1yALL8gUTDomgBAfz3XJdmUS+oo0w==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + dir-glob: 3.0.1 + fast-glob: 3.3.2 + ignore: 5.3.1 + merge2: 1.4.1 + slash: 4.0.0 + dev: true + /gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} dependencies: @@ -4235,6 +4586,10 @@ packages: resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==} dev: true + /map-stream@0.1.0: + resolution: {integrity: sha512-CkYQrPYZfWnu/DAmVCpTSX/xHpKZ80eKh2lAkyA6AJTef6bW+6JpbQZN5rofum7da+SyN1bi5ctTm+lTfcCW3g==} + dev: true + /markdown-table@1.1.3: resolution: {integrity: sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==} dev: true @@ -4501,6 +4856,11 @@ packages: resolution: {integrity: sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==} dev: true + /node-domexception@1.0.0: + resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} + engines: {node: '>=10.5.0'} + dev: true + /node-emoji@1.11.0: resolution: {integrity: sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==} dependencies: @@ -4519,6 +4879,15 @@ packages: whatwg-url: 5.0.0 dev: true + /node-fetch@3.3.1: + resolution: {integrity: sha512-cRVc/kyto/7E5shrWca1Wsea4y6tL9iYJE5FBCius3JQfb/4P4I295PfhgbJQBLTx6lATE4z+wK0rPM4VS2uow==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + data-uri-to-buffer: 4.0.1 + fetch-blob: 3.2.0 + formdata-polyfill: 4.0.10 + dev: true + /node-gyp-build@4.8.0: resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==} hasBin: true @@ -4816,6 +5185,12 @@ packages: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true + /pause-stream@0.0.11: + resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} + dependencies: + through: 2.3.8 + dev: true + /pbkdf2@3.1.2: resolution: {integrity: sha512-iuh7L6jA7JEGu2WxDwtQP1ddOpaJNC4KlDEFfdQajSGgGPNi4OyDc2R7QnbY2bR9QjBVGwgvTdNJZoE7RaxUMA==} engines: {node: '>=0.12'} @@ -4901,6 +5276,14 @@ packages: resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} dev: true + /ps-tree@1.2.0: + resolution: {integrity: sha512-0VnamPPYHl4uaU/nSFeZZpR21QAWRz+sRv4iW9+v/GS/J5U5iZB5BNN6J0RMoOvdx2gWM2+ZFMIm58q24e4UYA==} + engines: {node: '>= 0.10'} + hasBin: true + dependencies: + event-stream: 3.3.4 + dev: true + /psl@1.9.0: resolution: {integrity: sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==} dev: true @@ -5121,6 +5504,10 @@ packages: engines: {node: '>=4'} dev: true + /resolve-pkg-maps@1.0.0: + resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} + dev: true + /resolve@1.1.7: resolution: {integrity: sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==} dev: true @@ -5396,6 +5783,11 @@ packages: engines: {node: '>=8'} dev: true + /slash@4.0.0: + resolution: {integrity: sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==} + engines: {node: '>=12'} + dev: true + /slice-ansi@4.0.0: resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} engines: {node: '>=10'} @@ -5511,6 +5903,12 @@ packages: resolution: {integrity: sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==} dev: true + /split@0.3.3: + resolution: {integrity: sha512-wD2AeVmxXRBoX44wAycgjVpMhvbwdI2aZjCkvfNcH1YqHQvJVa1duWc73OyVGJUc05fhFaTZeQ/PYsrmyH0JVA==} + dependencies: + through: 2.3.8 + dev: true + /sprintf-js@1.0.3: resolution: {integrity: sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==} dev: true @@ -5543,6 +5941,12 @@ packages: engines: {node: '>= 0.8'} dev: true + /stream-combiner@0.0.4: + resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} + dependencies: + duplexer: 0.1.2 + dev: true + /strict-uri-encode@1.1.0: resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==} engines: {node: '>=0.10.0'} @@ -5766,6 +6170,10 @@ packages: qs: 6.12.0 dev: true + /through@2.3.8: + resolution: {integrity: sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==} + dev: true + /timed-out@4.0.1: resolution: {integrity: sha512-G7r3AhovYtr5YKOWQkta8RKAPb+J9IsO4uVmzjl8AZwfhs8UcUwTiD6gcJYSgOtzyjvQKrKYn41syHbUWMkafA==} engines: {node: '>=0.10.0'} @@ -5866,6 +6274,17 @@ packages: resolution: {integrity: sha512-Tyrf5mxF8Ofs1tNoxA13lFeZ2Zrbd6cKbuH3V+MQ5sb6DtBj5FjrXVsRWT8YvNAQTqNoz66dz1WsbigI22aEnw==} dev: true + /tsx@4.7.2: + resolution: {integrity: sha512-BCNd4kz6fz12fyrgCTEdZHGJ9fWTGeUzXmQysh0RVocDY3h4frk05ZNCXSy4kIenF7y/QnrdiVpTsyNRn6vlAw==} + engines: {node: '>=18.0.0'} + hasBin: true + dependencies: + esbuild: 0.19.12 + get-tsconfig: 4.7.3 + optionalDependencies: + fsevents: 2.3.3 + dev: true + /tunnel-agent@0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -6113,6 +6532,11 @@ packages: extsprintf: 1.3.0 dev: true + /web-streams-polyfill@3.3.3: + resolution: {integrity: sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==} + engines: {node: '>= 8'} + dev: true + /web3-bzz@1.10.0: resolution: {integrity: sha512-o9IR59io3pDUsXTsps5pO5hW1D5zBmg46iNc2t4j2DkaYHNdDLwk2IP9ukoM2wg47QILfPEJYzhTfkS/CcX0KA==} engines: {node: '>=8.0.0'} @@ -6699,6 +7123,11 @@ packages: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} dev: true + /webpod@0.0.2: + resolution: {integrity: sha512-cSwwQIeg8v4i3p4ajHhwgR7N6VyxAf+KYSSsY6Pd3aETE+xEU4vbitz7qQkB0I321xnhDdgtxuiSfk5r/FVtjg==} + hasBin: true + dev: true + /websocket@1.0.34: resolution: {integrity: sha512-PRDso2sGwF6kM75QykIesBijKSVceR6jL2G8NGYyq2XrItNC2P5/qL5XeR056GhA+Ly7JMFvJb9I312mJfmqnQ==} engines: {node: '>=4.0.0'} @@ -6742,6 +7171,14 @@ packages: isexe: 2.0.0 dev: true + /which@3.0.1: + resolution: {integrity: sha512-XA1b62dzQzLfaEOSQFTCOd5KFf/1VSzZo7/7TUjnya6u0vGGKzU96UQBZTAThCb2j4/xjBAyii1OhRLJEivHvg==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + /widest-line@3.1.0: resolution: {integrity: sha512-NsmoXalsWVDMGupxZ5R08ka9flZjjiLvHVAWYOKtiKM8ujtZWr9cRffak+uSE48+Ob8ObalXpwyeUiyDD6QFgg==} engines: {node: '>=8'} @@ -6898,6 +7335,12 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yaml@2.4.1: + resolution: {integrity: sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==} + engines: {node: '>= 14'} + hasBin: true + dev: true + /yargs-parser@2.4.1: resolution: {integrity: sha512-9pIKIJhnI5tonzG6OnCFlz/yln8xHYcGl+pn3xR0Vzff0vzN1PbNRaelgfgRUwZ3s4i3jvxT9WhmUGL4whnasA==} dependencies: @@ -6961,3 +7404,25 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} dev: true + + /zx@7.2.3: + resolution: {integrity: sha512-QODu38nLlYXg/B/Gw7ZKiZrvPkEsjPN3LQ5JFXM7h0JvwhEdPNNl+4Ao1y4+o3CLNiDUNcwzQYZ4/Ko7kKzCMA==} + engines: {node: '>= 16.0.0'} + hasBin: true + dependencies: + '@types/fs-extra': 11.0.4 + '@types/minimist': 1.2.5 + '@types/node': 18.19.31 + '@types/ps-tree': 1.1.6 + '@types/which': 3.0.3 + chalk: 5.3.0 + fs-extra: 11.2.0 + fx: 34.0.0 + globby: 13.2.2 + minimist: 1.2.8 + node-fetch: 3.3.1 + ps-tree: 1.2.0 + webpod: 0.0.2 + which: 3.0.1 + yaml: 2.4.1 + dev: true diff --git a/contracts/src/deployment.sol b/contracts/src/deployment.sol new file mode 100644 index 000000000..a25a52dc6 --- /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 000000000..faed6ab2e --- /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 2bd6f48cc..25add719d 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 new file mode 100644 index 000000000..79344f66f --- /dev/null +++ b/contracts/utils/deploy-cli.ts @@ -0,0 +1,269 @@ +import { $, argv, echo, fs } from "zx"; + +const HELP = ` +deploy - deploy the Liquity contracts. + +Usage: + ./deploy [NETWORK_PRESET] [OPTIONS] + +Arguments: + NETWORK_PRESET A network preset, which is a shorthand for setting certain options + such as the chain ID and RPC URL. Options take precedence over + network presets. Available presets: + - 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: + --chain-id Chain ID to deploy to. + --deployer Address or private key to deploy with. + Requires a Ledger if an address is used. + --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 + (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 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. +`; + +const ANVIL_FIRST_ACCOUNT = "0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80"; + +export async function main() { + const { networkPreset, options } = await parseArgs(); + + if (options.help) { + echo`${HELP}`; + process.exit(0); + } + + // network preset: local + if (networkPreset === "local") { + options.chainId ??= 31337; + options.deployer ??= ANVIL_FIRST_ACCOUNT; + 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; + options.rpcUrl ??= ( + await $`tenderly devnet spawn-rpc ${[ + "--project", + "project", + "--template", + "liquity2", + ]} 2>&1`.quiet() + ).stdout.trim(); + } + + // network preset: mainnet + if (networkPreset === "mainnet") { + options.chainId ??= 1; + } + + options.verifier ??= "etherscan"; + + // handle missing options + if (!options.chainId) { + throw new Error("--chain-id is required"); + } + if (!options.rpcUrl) { + throw new Error("--rpc-url is required"); + } + if (!options.deployer) { + throw new Error("--deployer is required"); + } + if (options.verify && options.verifier === "etherscan" && !options.etherscanApiKey) { + throw new Error( + "Verifying with Etherscan requires --etherscan-api-key ", + ); + } + + const forgeArgs: string[] = [ + "script", + "src/scripts/DeployLiquity2.s.sol", + "--chain-id", + String(options.chainId), + "--rpc-url", + options.rpcUrl, + "--broadcast", + ]; + + // verify + if (options.verify) { + forgeArgs.push("--verify"); + + // 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 + if (options.deployer.startsWith("0x") && options.deployer.length === 42) { + forgeArgs.push("--ledger"); + if (options.ledgerPath) { + forgeArgs.push("--hd-paths"); + forgeArgs.push(options.ledgerPath); + } + } + + echo` +Deploying Liquity contracts with the following settings: + + CHAIN_ID: ${options.chainId} + DEPLOYER: ${options.deployer} + LEDGER_PATH: ${options.ledgerPath} + ETHERSCAN_API_KEY: ${options.etherscanApiKey && "(secret)"} + 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 = [ + `DEPLOYER=${options.deployer}`, + ]; + + if (options.openDemoTroves) { + envVars.push("OPEN_DEMO_TROVES=true"); + } + + // deploy + await $`${envVars} forge ${forgeArgs}`; + + const deployedContracts = await getDeployedContracts( + `broadcast/DeployLiquity2.s.sol/${options.chainId}/run-latest.json`, + ); + + // format deployed contracts + const longestContractName = Math.max( + ...deployedContracts.map(([name]) => name.length), + ); + const deployedContractsFormatted = deployedContracts + .map(([name, address]) => `${name.padEnd(longestContractName)} ${address}`) + .join("\n"); + + echo("Contract deployment complete."); + echo(""); + echo(deployedContractsFormatted); + echo(""); +} + +function isDeploymentLog(log: unknown): log is { + transactions: Array<{ + transactionType: "CREATE"; + contractName: string; + contractAddress: string; + }>; +} { + return ( + typeof log === "object" + && log !== null + && "transactions" in log + && Array.isArray(log.transactions) + && log.transactions + .filter(tx => ( + typeof tx === "object" + && tx !== null + && tx.transactionType === "CREATE" + )) + .every(tx => ( + typeof tx.contractName === "string" + && typeof tx.contractAddress === "string" + )) + ); +} + +async function getDeployedContracts(jsonPath: string) { + const latestRun = await fs.readJson(jsonPath); + + if (isDeploymentLog(latestRun)) { + return latestRun.transactions + .filter((tx) => tx.transactionType === "CREATE") + .map((tx) => [tx.contractName, tx.contractAddress]); + } + + throw new Error("Invalid deployment log: " + JSON.stringify(latestRun)); +} + +function argInt(name: string) { + return typeof argv[name] === "number" ? parseInt(argv[name], 10) : undefined; +} + +function argBoolean(name: string) { + // allow "false" + return argv[name] === "false" ? false : Boolean(argv[name]); +} + +async function parseArgs() { + const options = { + chainId: argInt("chain-id"), + deployer: argv["deployer"], + etherscanApiKey: argv["etherscan-api-key"], + help: "help" in argv || "h" in argv, + ledgerPath: argv["ledger-path"], + openDemoTroves: argBoolean("open-demo-troves"), + rpcUrl: argv["rpc-url"], + verify: argBoolean("verify"), + verifier: argv["verifier"], + verifierUrl: argv["verifier-url"] + }; + + const [networkPreset] = argv._; + + if (options.chainId === undefined) { + const chainIdEnv = parseInt(process.env.CHAIN_ID ?? "", 10); + if (chainIdEnv && isNaN(chainIdEnv)) { + options.chainId = chainIdEnv; + } + } + options.deployer ??= process.env.DEPLOYER; + options.etherscanApiKey ??= process.env.ETHERSCAN_API_KEY; + options.ledgerPath ??= process.env.LEDGER_PATH; + options.openDemoTroves ??= Boolean( + process.env.OPEN_DEMO_TROVES && process.env.OPEN_DEMO_TROVES !== "false", + ); + options.rpcUrl ??= process.env.RPC_URL; + options.verify ??= Boolean( + process.env.VERIFY && process.env.VERIFY !== "false", + ); + options.verifier ??= process.env.VERIFIER; + options.verifierUrl ??= process.env.VERIFIER_URL; + + return { options, networkPreset }; +}