Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 2-stage deployment #700

Merged
merged 5 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 14 additions & 11 deletions .github/workflows/contracts-tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ jobs:
name: Foundry "E2E" tests
runs-on: ubuntu-latest
env:
SALT: Liquity2-E2E
FORK_URL: ${{ secrets.MAINNET_RPC_URL }}
FORK_CHAIN_ID: 1
FORK_BLOCK_NUMBER: 21571000
Expand Down Expand Up @@ -91,8 +92,11 @@ jobs:
- name: Fork mainnet
run: ./fork start && sleep 5

- name: Deploy contracts
run: ./fork deploy
- name: Deploy BOLD
run: ./fork deploy --mode bold-only

- name: Deploy everything else
run: ./fork deploy --mode use-existing-bold

- name: Run E2E tests
run: ./fork e2e -vvv
Expand Down Expand Up @@ -219,15 +223,14 @@ jobs:

# Filter
- name: Filter out from coverage
run:
lcov --remove lcov_merged.info -o lcov_merged.info
'test/*'
'script/*'
'src/Dependencies/Ownable.sol'
'src/Zappers/Modules/Exchanges/UniswapV3/UniPriceConverter.sol'
'src/NFTMetadata/*'
'src/MultiTroveGetter.sol'
'src/HintHelpers.sol'
run: lcov --remove lcov_merged.info -o lcov_merged.info
'test/*'
'script/*'
'src/Dependencies/Ownable.sol'
'src/Zappers/Modules/Exchanges/UniswapV3/UniPriceConverter.sol'
'src/NFTMetadata/*'
'src/MultiTroveGetter.sol'
'src/HintHelpers.sol'
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Autoformat.


# Send to coveralls
- name: Coveralls
Expand Down
5 changes: 5 additions & 0 deletions contracts/.env.template
Original file line number Diff line number Diff line change
Expand Up @@ -19,5 +19,10 @@ FORK_CHAIN_ID=1
# Defaults to Anvil account #0
# DEPLOYER=

# Deployment script will use `keccak256(bytes(...))` of this as salt when deploying via CREATE2
# Defaults to `block.timestamp.toString()`
# Must be explicitly set when using 2-stage deployment (bold-only, use-existing-bold)
# SALT=Liquity2

# Better call traces, e.g. when WETH/wstETH/RETH or Curve, etc. is involved
# ETHERSCAN_API_KEY=
4 changes: 2 additions & 2 deletions contracts/fork
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ deploy() {
)

# sourcify_start
"${deploy[@]}"
"${deploy[@]}" "$@"
# sourcify_stop
}

Expand Down Expand Up @@ -131,7 +131,7 @@ case $cmd in
deploy)
ensure_running
init_env
deploy
deploy "$@"
;;
e2e)
ensure_running
Expand Down
54 changes: 42 additions & 12 deletions contracts/script/DeployLiquity2.s.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ import "src/PriceFeeds/RETHPriceFeed.sol";
import "src/CollateralRegistry.sol";
import "test/TestContracts/PriceFeedTestnet.sol";
import "test/TestContracts/MetadataDeployment.sol";
import "test/Utils/Logging.sol";
import "test/Utils/StringEquality.sol";
import "src/Zappers/WETHZapper.sol";
import "src/Zappers/GasCompZapper.sol";
import "src/Zappers/LeverageLSTZapper.sol";
Expand All @@ -52,9 +54,14 @@ import {MockStakingV1} from "V2-gov/test/mocks/MockStakingV1.sol";

import {DeployGovernance} from "./DeployGovernance.s.sol";

contract DeployLiquity2Script is DeployGovernance, UniPriceConverter, StdCheats, MetadataDeployment {
contract DeployLiquity2Script is DeployGovernance, UniPriceConverter, StdCheats, MetadataDeployment, Logging {
using Strings for *;
using StringFormatting for *;
using StringEquality for string;

string constant DEPLOYMENT_MODE_COMPLETE = "complete";
string constant DEPLOYMENT_MODE_BOLD_ONLY = "bold-only";
string constant DEPLOYMENT_MODE_USE_EXISTING_BOLD = "use-existing-bold";

address WETH_ADDRESS = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address USDC_ADDRESS = 0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48;
Expand Down Expand Up @@ -213,7 +220,8 @@ contract DeployLiquity2Script is DeployGovernance, UniPriceConverter, StdCheats,
}

function run() external {
SALT = keccak256(abi.encodePacked(block.timestamp));
string memory saltStr = vm.envOr("SALT", block.timestamp.toString());
SALT = keccak256(bytes(saltStr));

if (vm.envBytes("DEPLOYER").length == 20) {
// address
Expand All @@ -226,11 +234,38 @@ contract DeployLiquity2Script is DeployGovernance, UniPriceConverter, StdCheats,
vm.startBroadcast(privateKey);
}

string memory deploymentMode = vm.envOr("DEPLOYMENT_MODE", DEPLOYMENT_MODE_COMPLETE);
require(
deploymentMode.eq(DEPLOYMENT_MODE_COMPLETE) || deploymentMode.eq(DEPLOYMENT_MODE_BOLD_ONLY)
|| deploymentMode.eq(DEPLOYMENT_MODE_USE_EXISTING_BOLD),
string.concat("Bad deployment mode: ", deploymentMode)
);

useTestnetPriceFeeds = vm.envOr("USE_TESTNET_PRICEFEEDS", false);

console2.log(deployer, "deployer");
console2.log(deployer.balance, "deployer balance");
console2.log("Use Testnet PriceFeeds: ", useTestnetPriceFeeds);
_log("Deployer: ", deployer.toHexString());
_log("Deployer balance: ", deployer.balance.decimal());
_log("Deployment mode: ", deploymentMode);
_log("CREATE2 salt: ", 'keccak256(bytes("', saltStr, '")) = ', uint256(SALT).toHexString());
_log("Use testnet PriceFeeds: ", useTestnetPriceFeeds ? "yes" : "no");

// Deploy Bold or pick up existing deployment
bytes memory boldBytecode = bytes.concat(type(BoldToken).creationCode, abi.encode(deployer));
address boldAddress = vm.computeCreate2Address(SALT, keccak256(boldBytecode));
BoldToken boldToken;

if (deploymentMode.eq(DEPLOYMENT_MODE_USE_EXISTING_BOLD)) {
require(boldAddress.code.length > 0, string.concat("BOLD not found at ", boldAddress.toHexString()));
boldToken = BoldToken(boldAddress);
} else {
boldToken = new BoldToken{salt: SALT}(deployer);
assert(address(boldToken) == boldAddress);
}

if (deploymentMode.eq(DEPLOYMENT_MODE_BOLD_ONLY)) {
vm.writeFile("deployment-manifest.json", string.concat('{"boldToken":"', boldAddress.toHexString(), '"}'));
return;
}

if (block.chainid == 1) {
// mainnet
Expand Down Expand Up @@ -289,7 +324,7 @@ contract DeployLiquity2Script is DeployGovernance, UniPriceConverter, StdCheats,
stakingV1: stakingV1,
lqty: lqty,
lusd: lusd,
bold: address(0) // not yet known; will be set by `_deployAndConnectContracts()`
bold: boldAddress
});

DeploymentResult memory deployed =
Expand Down Expand Up @@ -466,12 +501,7 @@ contract DeployLiquity2Script is DeployGovernance, UniPriceConverter, StdCheats,

DeploymentVars memory vars;
vars.numCollaterals = troveManagerParamsArray.length;
// Deploy Bold
vars.bytecode = abi.encodePacked(type(BoldToken).creationCode, abi.encode(deployer));
vars.boldTokenAddress = vm.computeCreate2Address(SALT, keccak256(vars.bytecode));
r.boldToken = new BoldToken{salt: SALT}(deployer);
assert(address(r.boldToken) == vars.boldTokenAddress);
_deployGovernanceParams.bold = address(r.boldToken);
r.boldToken = BoldToken(_deployGovernanceParams.bold);

// USDC and USDC-BOLD pool
r.usdcCurvePool = _deployCurveBoldUsdcPool(r.boldToken);
Expand Down
11 changes: 1 addition & 10 deletions contracts/test/E2E.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {IPriceFeedV1} from "./Interfaces/LiquityV1/IPriceFeedV1.sol";
import {ISortedTrovesV1} from "./Interfaces/LiquityV1/ISortedTrovesV1.sol";
import {ITroveManagerV1} from "./Interfaces/LiquityV1/ITroveManagerV1.sol";
import {ERC20Faucet} from "./TestContracts/ERC20Faucet.sol";
import {StringEquality} from "./Utils/StringEquality.sol";

address constant ETH_WHALE = 0x70997970C51812dc3A010C7d01b50e0d17dc79C8; // Anvil account #1
address constant WETH_WHALE = 0x3C44CdDdB6a900fa2b585dd299e03d12FA4293BC; // Anvil account #2
Expand Down Expand Up @@ -109,16 +110,6 @@ library SideEffectFreeGetPrice {
}
}

library StringEquality {
function eq(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}

function notEq(string memory a, string memory b) internal pure returns (bool) {
return !eq(a, b);
}
}

contract E2ETest is Test {
using SideEffectFreeGetPrice for IPriceFeed;
using SideEffectFreeGetPrice for IPriceFeedV1;
Expand Down
12 changes: 12 additions & 0 deletions contracts/test/Utils/StringEquality.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.24;

library StringEquality {
function eq(string memory a, string memory b) internal pure returns (bool) {
return keccak256(bytes(a)) == keccak256(bytes(b));
}

function notEq(string memory a, string memory b) internal pure returns (bool) {
return !eq(a, b);
}
}
17 changes: 16 additions & 1 deletion contracts/utils/deploy-cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,10 @@ Options:
(required when verifying with Etherscan).
--gas-price <GAS_PRICE> Max fee per gas to use in transactions.
--help, -h Show this help message.
--mode <DEPLOYMENT_MODE> Deploy in one of the following modes:
- complete (default)
- bold-only
- use-existing-bold
--open-demo-troves Open demo troves after deployment (local
only).
--rpc-url <RPC_URL> RPC URL to use.
Expand Down Expand Up @@ -65,6 +69,7 @@ const argv = minimist(process.argv.slice(2), {
"deployer",
"etherscan-api-key",
"ledger-path",
"mode",
"rpc-url",
"verifier",
"verifier-url",
Expand Down Expand Up @@ -100,6 +105,7 @@ export async function main() {
options.chainId ??= 1;
}

options.mode ??= "complete";
options.verifier ??= "etherscan";

// handle missing options
Expand Down Expand Up @@ -177,8 +183,9 @@ Deploying Liquity contracts with the following settings:

CHAIN_ID: ${options.chainId}
DEPLOYER: ${options.deployer}
LEDGER_PATH: ${options.ledgerPath}
DEPLOYMENT_MODE: ${options.mode}
ETHERSCAN_API_KEY: ${options.etherscanApiKey && "(secret)"}
LEDGER_PATH: ${options.ledgerPath}
OPEN_DEMO_TROVES: ${options.openDemoTroves ? "yes" : "no"}
RPC_URL: ${options.rpcUrl}
USE_TESTNET_PRICEFEEDS: ${options.useTestnetPricefeeds ? "yes" : "no"}
Expand All @@ -188,6 +195,7 @@ Deploying Liquity contracts with the following settings:
`;

process.env.DEPLOYER = options.deployer;
process.env.DEPLOYMENT_MODE = options.mode;

if (options.openDemoTroves) {
process.env.OPEN_DEMO_TROVES = "true";
Expand Down Expand Up @@ -218,6 +226,11 @@ Deploying Liquity contracts with the following settings:
multiTroveGetter: string;
};

if (options.mode === "bold-only") {
echo("BoldToken address:", deploymentManifest.boldToken);
return;
}

const protocolContracts = {
BoldToken: deploymentManifest.boldToken,
CollateralRegistry: deploymentManifest.collateralRegistry,
Expand Down Expand Up @@ -297,6 +310,7 @@ async function parseArgs() {
etherscanApiKey: argv["etherscan-api-key"],
help: argv["help"],
ledgerPath: argv["ledger-path"],
mode: argv["mode"],
openDemoTroves: argv["open-demo-troves"],
rpcUrl: argv["rpc-url"],
dryRun: argv["dry-run"],
Expand All @@ -317,6 +331,7 @@ async function parseArgs() {
options.deployer ??= process.env.DEPLOYER;
options.etherscanApiKey ??= process.env.ETHERSCAN_API_KEY;
options.ledgerPath ??= process.env.LEDGER_PATH;
options.mode ??= process.env.DEPLOYMENT_MODE;
options.openDemoTroves ??= Boolean(
process.env.OPEN_DEMO_TROVES && process.env.OPEN_DEMO_TROVES !== "false",
);
Expand Down
Loading