Skip to content

Commit c109fc5

Browse files
authored
feat: stablecoins migration to hh v3 (#463)
* feat: migration to hh v3 * fix: scripts and tests * fix: review comments * fix: getPair * chore: bump create-eth 2.0.18 -> 2.0.20
1 parent cfc051d commit c109fc5

9 files changed

Lines changed: 305 additions & 205 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ Deploy your contracts to a testnet then build and upload your app to a public we
4848
📥 Then download the challenge to your computer and install dependencies by running:
4949

5050
```sh
51-
npx create-eth@2.0.18 -e scaffold-eth/se-2-challenges:challenge-stablecoins challenge-stablecoins
51+
npx create-eth@2.0.20 -e scaffold-eth/se-2-challenges:challenge-stablecoins challenge-stablecoins
5252
cd challenge-stablecoins
5353
```
5454

Lines changed: 146 additions & 124 deletions
Original file line numberDiff line numberDiff line change
@@ -1,131 +1,153 @@
1-
import { HardhatRuntimeEnvironment } from "hardhat/types";
2-
import { DeployFunction } from "hardhat-deploy/types";
3-
import { Contract } from "ethers";
4-
import { fetchPriceFromUniswap } from "../scripts/fetchPriceFromUniswap";
1+
import { artifacts, deployScript } from "../rocketh/deploy.js";
2+
import { parseEther, getContractAddress } from "viem";
3+
import { fetchPriceFromUniswap } from "../scripts/fetchPriceFromUniswap.js";
54

65
/**
7-
* Deploys a contract named "YourContract" using the deployer account and
8-
* constructor arguments set to the deployer address
6+
* Deploys "RateController", "MyUSD", "DEX", "Oracle", "MyUSDStaking" and "MyUSDEngine" using the deployer account.
97
*
10-
* @param hre HardhatRuntimeEnvironment object.
8+
* On localhost, the deployer account is the one that comes with Hardhat, which is already funded.
9+
*
10+
* When deploying to live networks (e.g `yarn deploy --network sepolia`), the deployer account
11+
* should have sufficient balance to pay for the gas fees for contract creation.
12+
*
13+
* You can generate a random account with `yarn generate` or `yarn account:import` to import your
14+
* existing PK which will fill DEPLOYER_PRIVATE_KEY_ENCRYPTED in the .env file (used in hardhat.config.ts).
15+
* Run `yarn account` to check the deployer balance on every network.
1116
*/
12-
const deployContracts: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
13-
/*
14-
On localhost, the deployer account is the one that comes with Hardhat, which is already funded.
15-
16-
When deploying to live networks (e.g `yarn deploy --network sepolia`), the deployer account
17-
should have sufficient balance to pay for the gas fees for contract creation.
18-
19-
You can generate a random account with `yarn generate` or `yarn account:import` to import your
20-
existing PK which will fill DEPLOYER_PRIVATE_KEY_ENCRYPTED in the .env file (then used on hardhat.config.ts)
21-
You can run the `yarn account` command to check your balance in every network.
22-
*/
23-
const { deployer } = await hre.getNamedAccounts();
24-
const { deploy } = hre.deployments;
25-
26-
const ethPrice = await fetchPriceFromUniswap();
27-
28-
// Add the account that you want to be the owner of your contracts when deployment is complete
29-
const CONTRACT_OWNER = deployer; // Change this if you want to update the rates with a different account than the deployer
30-
31-
// Get the deployer's current nonce
32-
const deployerNonce = await hre.ethers.provider.getTransactionCount(deployer);
33-
34-
// Calculate future addresses based on nonce
35-
const futureStakingAddress = hre.ethers.getCreateAddress({
36-
from: deployer,
37-
nonce: deployerNonce + 4, // +4 because it will be our fifth deployment (after MyUSD, DEX, Oracle, RateController)
38-
});
39-
40-
const futureEngineAddress = hre.ethers.getCreateAddress({
41-
from: deployer,
42-
nonce: deployerNonce + 5, // +5 because it will be our sixth deployment (after MyUSD, DEX, Oracle, Staking, RateController)
43-
});
44-
45-
await deploy("RateController", {
46-
from: deployer,
47-
args: [futureEngineAddress, futureStakingAddress],
48-
log: true,
49-
});
50-
const rateController = await hre.ethers.getContract<Contract>("RateController", deployer);
51-
52-
await deploy("MyUSD", {
53-
from: deployer,
54-
args: [futureEngineAddress, futureStakingAddress],
55-
log: true,
56-
});
57-
const stablecoin = await hre.ethers.getContract<Contract>("MyUSD", deployer);
58-
59-
await deploy("DEX", {
60-
from: deployer,
61-
args: [stablecoin.target],
62-
log: true,
63-
});
64-
const DEX = await hre.ethers.getContract<Contract>("DEX", deployer);
65-
66-
await deploy("Oracle", {
67-
from: deployer,
68-
args: [DEX.target, ethPrice],
69-
log: true,
70-
});
71-
const oracle = await hre.ethers.getContract<Contract>("Oracle", deployer);
72-
73-
await deploy("MyUSDStaking", {
74-
from: deployer,
75-
args: [stablecoin.target, futureEngineAddress, rateController.target],
76-
log: true,
77-
});
78-
const staking = await hre.ethers.getContract<Contract>("MyUSDStaking", deployer);
79-
80-
// Finally deploy the engine at the predicted address
81-
await deploy("MyUSDEngine", {
82-
from: deployer,
83-
args: [oracle.target, stablecoin.target, staking.target, rateController.target],
84-
log: true,
85-
});
86-
const engine = await hre.ethers.getContract<Contract>("MyUSDEngine", deployer);
87-
88-
if (engine.target !== futureEngineAddress) {
89-
throw new Error(
90-
"Engine address does not match predicted address, did you add transactions above this line that would skew the nonce set for 'futureEngineAddress'?",
91-
);
92-
}
93-
94-
if (hre.network.name === "localhost") {
95-
// Set deployer ETH balance
96-
await hre.ethers.provider.send("hardhat_setBalance", [
97-
deployer,
98-
`0x${hre.ethers.parseEther("100000000000000000000").toString(16)}`,
99-
]);
100-
101-
// The deployer is going to provide liquidity to the DEX so that we can swap tokens
102-
// First they will borrow stablecoins and then provide liquidity to the DEX
103-
// We will make the deployer account deposit a tone of collateral so they are rarely at risk of liquidation
104-
const ethCollateralAmount = hre.ethers.parseEther("10000000000000000000");
105-
// Set initial price of stablecoin (as determined by DEX liquidity)
106-
const ethDEXAmount = hre.ethers.parseEther("10000000");
107-
const myUSDAmount = ethPrice * 10000000n;
108-
109-
const GAS_LIMIT = 500000;
110-
111-
// Borrow stablecoins
112-
await engine.addCollateral({ value: ethCollateralAmount, gasLimit: GAS_LIMIT });
113-
await engine.mintMyUSD(myUSDAmount, { gasLimit: GAS_LIMIT });
114-
115-
const confirmedBalance = await stablecoin.balanceOf(deployer);
116-
// Don't add DEX liquidity if the deployer account doesn't have the stablecoins
117-
if (confirmedBalance == myUSDAmount) {
118-
// Approve DEX to use tokens and initialize DEX
119-
await stablecoin.approve(DEX.target, myUSDAmount, { gasLimit: GAS_LIMIT });
120-
await DEX.init(myUSDAmount, { value: ethDEXAmount, gasLimit: GAS_LIMIT });
17+
export default deployScript(
18+
async env => {
19+
const { deployer } = env.namedAccounts;
20+
21+
const ethPrice = await fetchPriceFromUniswap();
22+
23+
// Add the account that you want to be the owner of your contracts when deployment is complete
24+
const CONTRACT_OWNER = deployer; // Change this if you want to update the rates with a different account than the deployer
25+
26+
// Get the deployer's current nonce
27+
const nonceHex = (await env.network.provider.request({
28+
method: "eth_getTransactionCount",
29+
params: [deployer, "pending"],
30+
})) as `0x${string}`;
31+
const deployerNonce = Number(BigInt(nonceHex));
32+
33+
// Calculate future addresses based on nonce
34+
const futureStakingAddress = getContractAddress({
35+
from: deployer,
36+
nonce: BigInt(deployerNonce + 4), // +4 because it will be our fifth deployment (after MyUSD, DEX, Oracle, RateController)
37+
});
38+
39+
const futureEngineAddress = getContractAddress({
40+
from: deployer,
41+
nonce: BigInt(deployerNonce + 5), // +5 because it will be our sixth deployment (after MyUSD, DEX, Oracle, Staking, RateController)
42+
});
43+
44+
const rateController = await env.deploy("RateController", {
45+
account: deployer,
46+
artifact: artifacts.RateController,
47+
args: [futureEngineAddress, futureStakingAddress],
48+
});
49+
50+
const stablecoin = await env.deploy("MyUSD", {
51+
account: deployer,
52+
artifact: artifacts.MyUSD,
53+
args: [futureEngineAddress, futureStakingAddress],
54+
});
55+
56+
const DEX = await env.deploy("DEX", {
57+
account: deployer,
58+
artifact: artifacts.DEX,
59+
args: [stablecoin.address],
60+
});
61+
62+
const oracle = await env.deploy("Oracle", {
63+
account: deployer,
64+
artifact: artifacts.Oracle,
65+
args: [DEX.address, ethPrice],
66+
});
67+
68+
const staking = await env.deploy("MyUSDStaking", {
69+
account: deployer,
70+
artifact: artifacts.MyUSDStaking,
71+
args: [stablecoin.address, futureEngineAddress, rateController.address],
72+
});
73+
74+
// Finally deploy the engine at the predicted address
75+
const engine = await env.deploy("MyUSDEngine", {
76+
account: deployer,
77+
artifact: artifacts.MyUSDEngine,
78+
args: [oracle.address, stablecoin.address, staking.address, rateController.address],
79+
});
80+
81+
if (engine.address.toLowerCase() !== futureEngineAddress.toLowerCase()) {
82+
throw new Error(
83+
"Engine address does not match predicted address, did you add transactions above this line that would skew the nonce set for 'futureEngineAddress'?",
84+
);
12185
}
12286

123-
// Set the owner of the engine and staking contracts
124-
if (CONTRACT_OWNER !== deployer) {
125-
await engine.transferOwnership(CONTRACT_OWNER);
126-
await staking.transferOwnership(CONTRACT_OWNER);
87+
if (env.name === "default" || env.name === "localhost") {
88+
// Set deployer ETH balance
89+
// hardhat_setBalance is not part of the provider's typed RPC schema, so we
90+
// override the schema to type the params for this single call.
91+
await env.network.provider.request<{ params: [string, string]; result: null }>({
92+
method: "hardhat_setBalance",
93+
params: [deployer, `0x${parseEther("100000000000000000000").toString(16)}`],
94+
});
95+
96+
// The deployer is going to provide liquidity to the DEX so that we can swap tokens
97+
// First they will borrow stablecoins and then provide liquidity to the DEX
98+
// We will make the deployer account deposit a tone of collateral so they are rarely at risk of liquidation
99+
const ethCollateralAmount = parseEther("10000000000000000000");
100+
// Set initial price of stablecoin (as determined by DEX liquidity)
101+
const ethDEXAmount = parseEther("10000000");
102+
const myUSDAmount = ethPrice * 10000000n;
103+
104+
// Borrow stablecoins
105+
await env.execute(engine, {
106+
functionName: "addCollateral",
107+
args: [],
108+
value: ethCollateralAmount,
109+
account: deployer,
110+
});
111+
await env.execute(engine, {
112+
functionName: "mintMyUSD",
113+
args: [myUSDAmount],
114+
account: deployer,
115+
});
116+
117+
const confirmedBalance = (await env.read(stablecoin, {
118+
functionName: "balanceOf",
119+
args: [deployer],
120+
})) as bigint;
121+
// Don't add DEX liquidity if the deployer account doesn't have the stablecoins
122+
if (confirmedBalance === myUSDAmount) {
123+
// Approve DEX to use tokens and initialize DEX
124+
await env.execute(stablecoin, {
125+
functionName: "approve",
126+
args: [DEX.address, myUSDAmount],
127+
account: deployer,
128+
});
129+
await env.execute(DEX, {
130+
functionName: "init",
131+
args: [myUSDAmount],
132+
value: ethDEXAmount,
133+
account: deployer,
134+
});
135+
}
136+
137+
// Set the owner of the engine and staking contracts
138+
if (CONTRACT_OWNER !== deployer) {
139+
await env.execute(engine, {
140+
functionName: "transferOwnership",
141+
args: [CONTRACT_OWNER],
142+
account: deployer,
143+
});
144+
await env.execute(staking, {
145+
functionName: "transferOwnership",
146+
args: [CONTRACT_OWNER],
147+
account: deployer,
148+
});
149+
}
127150
}
128-
}
129-
};
130-
131-
export default deployContracts;
151+
},
152+
{ tags: ["MyUSDEngine"] },
153+
);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import fs from "node:fs";
2+
import path from "node:path";
3+
import { fileURLToPath } from "node:url";
4+
5+
const scriptsDir = path.dirname(fileURLToPath(import.meta.url));
6+
7+
// Returns a reader that resolves a deployed contract's address from its
8+
// deployment JSON for the given network.
9+
export function createDeploymentReader(networkName: string) {
10+
const deploymentsDir = path.join(scriptsDir, "..", "deployments", networkName);
11+
return (name: string): string => {
12+
const filePath = path.join(deploymentsDir, `${name}.json`);
13+
return JSON.parse(fs.readFileSync(filePath, "utf8")).address;
14+
};
15+
}

0 commit comments

Comments
 (0)