Skip to content

Commit e47eb77

Browse files
viatrixlastperson
andauthored
Add stablecoin pool (#49)
* Add stablecoin pool * Fix lint * Add test for borrow and repay with swap * Add stablecoin pool to deploy script * Cleanup deploy script and solidity warnings * Add standalone deploy stablecoin pool script Cleanup --------- Co-authored-by: Oleksii Matiiasevych <[email protected]>
1 parent bc3eb47 commit e47eb77

File tree

9 files changed

+1235
-10
lines changed

9 files changed

+1235
-10
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
// SPDX-License-Identifier: LGPL-3.0-only
2+
pragma solidity 0.8.28;
3+
4+
import {IERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
5+
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
6+
import {LiquidityPool} from "./LiquidityPool.sol";
7+
8+
/// @title A version of the liquidity pool contract that supports multiple assets for borrowing.
9+
/// It's possible to borrow any tokens that are present in the pool.
10+
/// @author Tanya Bushenyova <[email protected]>
11+
contract LiquidityPoolStablecoin is LiquidityPool {
12+
using SafeERC20 for IERC20;
13+
14+
error WithdrawProfitDenied();
15+
16+
constructor(
17+
address liquidityToken,
18+
address admin,
19+
address mpcAddress_
20+
) LiquidityPool(liquidityToken, admin, mpcAddress_) {
21+
return;
22+
}
23+
24+
function _borrowLogic(address /*borrowToken*/, uint256 /*amount*/, address /*target*/) internal pure override {
25+
return;
26+
}
27+
28+
function _withdrawProfitLogic(IERC20 token) internal view override returns (uint256) {
29+
uint256 assetBalance = ASSETS.balanceOf(address(this));
30+
uint256 deposited = totalDeposited;
31+
require(assetBalance >= deposited, WithdrawProfitDenied());
32+
if (token == ASSETS) return assetBalance - deposited;
33+
return token.balanceOf(address(this));
34+
}
35+
}

network.config.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as AAVEPools from "@bgd-labs/aave-address-book";
22

33
export const LiquidityPoolAaveUSDC: string = "LiquidityPoolAaveUSDC";
44
export const LiquidityPoolUSDC: string = "LiquidityPoolUSDC";
5+
export const LiquidityPoolUSDCStablecoin: string = "LiquidityPoolUSDCStablecoin";
56

67
export enum Network {
78
ETHEREUM = "ETHEREUM",
@@ -86,6 +87,7 @@ export interface NetworkConfig {
8687
Hub?: HubConfig;
8788
AavePool?: AavePoolConfig;
8889
USDCPool?: boolean;
90+
USDCStablecoinPool?: boolean;
8991
Stage?: NetworkConfig;
9092
};
9193

package.json

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,7 @@
2222
"redeploy-stash-base": "hardhat run ./scripts/redeployStash.ts --network BASE",
2323
"deploy-censoredmulticall-ethereum": "hardhat run ./scripts/deployCensoredMulticall.ts --network ETHEREUM",
2424
"deploy-usdcpool-base-stage": "DEPLOY_TYPE=STAGE hardhat run ./scripts/deployUSDCPool.ts --network BASE",
25-
"upgrade-liquiditypool": "hardhat run ./scripts/upgradeLiquidityPool.ts",
26-
"upgrade-liquiditypool-basesepolia": "hardhat run ./scripts/upgradeLiquidityPool.ts --network BASE_SEPOLIA",
27-
"upgrade-liquiditypool-ethereumsepolia": "hardhat run ./scripts/upgradeLiquidityPool.ts --network ETHEREUM_SEPOLIA",
25+
"deploy-usdcstablecoinpool-base-stage": "DEPLOY_TYPE=STAGE hardhat run ./scripts/deployUSDCStablecoinPool.ts --network BASE",
2826
"deploy-repayer-base": "hardhat run ./scripts/deployRepayer.ts --network BASE",
2927
"deploy-repayer-arbitrumone": "hardhat run ./scripts/deployRepayer.ts --network ARBITRUM_ONE",
3028
"deploy-repayer-opmainnet": "hardhat run ./scripts/deployRepayer.ts --network OP_MAINNET",
@@ -50,6 +48,7 @@
5048
"dry:redeploy-stash-base": "DRY_RUN=BASE VERIFY=false ts-node --files ./scripts/redeployStash.ts",
5149
"dry:deploy-censoredmulticall-ethereum": "DRY_RUN=ETHEREUM VERIFY=false ts-node --files ./scripts/deployCensoredMulticall.ts",
5250
"dry:deploy-usdcpool-base-stage": "DRY_RUN=BASE DEPLOY_TYPE=STAGE VERIFY=false ts-node --files ./scripts/deployUSDCPool.ts",
51+
"dry:deploy-usdcstablecoinpool-base-stage": "DRY_RUN=BASE DEPLOY_TYPE=STAGE VERIFY=false ts-node --files ./scripts/deployUSDCStablecoinPool.ts",
5352
"dry:deploy-repayer-base": "DRY_RUN=BASE VERIFY=false ts-node --files ./scripts/deployRepayer.ts",
5453
"dry:deploy-repayer-arbitrumone": "DRY_RUN=ARBITRUM_ONE VERIFY=false ts-node --files ./scripts/deployRepayer.ts",
5554
"dry:deploy-repayer-opmainnet": "DRY_RUN=OP_MAINNET VERIFY=false ts-node --files ./scripts/deployRepayer.ts",

scripts/deploy.ts

Lines changed: 40 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ import {
1212
import {
1313
TestUSDC, SprinterUSDCLPShare, LiquidityHub,
1414
SprinterLiquidityMining, TestCCTPTokenMessenger, TestCCTPMessageTransmitter,
15-
Rebalancer, Repayer, LiquidityPool, LiquidityPoolAave,
15+
Rebalancer, Repayer, LiquidityPool, LiquidityPoolAave, LiquidityPoolStablecoin
1616
} from "../typechain-types";
1717
import {
1818
networkConfig, Network, Provider, NetworkConfig, LiquidityPoolUSDC,
19-
LiquidityPoolAaveUSDC,
19+
LiquidityPoolAaveUSDC, LiquidityPoolUSDCStablecoin
2020
} from "../network.config";
2121

2222
export async function main() {
@@ -95,18 +95,21 @@ export async function main() {
9595
Providers: [Provider.CCTP],
9696
SupportsAllTokens: [false],
9797
},
98-
USDCPool: true
98+
USDCPool: true,
99+
USDCStablecoinPool: true,
99100
};
100101
}
101102

102-
assert(config.AavePool !== undefined || config.USDCPool!, "At least one pool should be present.");
103+
assert(config.AavePool! || config.USDCPool! || config.USDCStablecoinPool!,
104+
"At least one pool should be present.");
103105
assert(isAddress(config.USDC), "USDC must be an address");
104106
assert(isAddress(config.Admin), "Admin must be an address");
105107
assert(isAddress(config.WithdrawProfit), "WithdrawProfit must be an address");
106108
assert(isAddress(config.Pauser), "Pauser must be an address");
107109
assert(isAddress(config.RebalanceCaller), "RebalanceCaller must be an address");
108110
assert(isAddress(config.RepayerCaller), "RepayerCaller must be an address");
109111
assert(isAddress(config.MpcAddress), "MpcAddress must be an address");
112+
110113
if (config.Hub) {
111114
assert(config.Hub!.Tiers.length > 0, "Empty liquidity mining tiers configuration.");
112115
assert(config.Hub!.AssetsLimit <= MaxUint256 / 10n ** 12n, "Assets limit is too high");
@@ -189,6 +192,28 @@ export async function main() {
189192
}
190193
}
191194

195+
let usdcStablecoinPool: LiquidityPoolStablecoin;
196+
if (config.USDCStablecoinPool) {
197+
console.log("Deploying USDC Stablecoin Liquidity Pool");
198+
usdcStablecoinPool = (await verifier.deployX(
199+
"LiquidityPoolStablecoin", deployer, {}, [config.USDC, deployer, config.MpcAddress], LiquidityPoolUSDCStablecoin
200+
)) as LiquidityPool;
201+
console.log(`LiquidityPoolUSDCStablecoin: ${usdcStablecoinPool.target}`);
202+
203+
config.RebalancerRoutes.Pools.push(await usdcStablecoinPool.getAddress());
204+
config.RebalancerRoutes.Domains.push(network);
205+
config.RebalancerRoutes.Providers.push(Provider.LOCAL);
206+
207+
config.RepayerRoutes.Pools.push(await usdcStablecoinPool.getAddress());
208+
config.RepayerRoutes.Domains.push(network);
209+
config.RepayerRoutes.Providers.push(Provider.LOCAL);
210+
config.RepayerRoutes.SupportsAllTokens.push(false);
211+
212+
if ((!config.AavePool) && (!config.USDCPool)) {
213+
mainPool = usdcStablecoinPool;
214+
}
215+
}
216+
192217
const rebalancerVersion = config.IsTest ? "TestRebalancer" : "Rebalancer";
193218

194219
config.RebalancerRoutes.Pools = await verifier.predictDeployXAddresses(config.RebalancerRoutes!.Pools!, deployer);
@@ -221,6 +246,12 @@ export async function main() {
221246
await usdcPool!.grantRole(PAUSER_ROLE, config.Pauser);
222247
}
223248

249+
if (config.USDCStablecoinPool) {
250+
await usdcStablecoinPool!.grantRole(LIQUIDITY_ADMIN_ROLE, rebalancer);
251+
await usdcStablecoinPool!.grantRole(WITHDRAW_PROFIT_ROLE, config.WithdrawProfit);
252+
await usdcStablecoinPool!.grantRole(PAUSER_ROLE, config.Pauser);
253+
}
254+
224255
const repayerVersion = config.IsTest ? "TestRepayer" : "Repayer";
225256

226257
config.RepayerRoutes.Pools = await verifier.predictDeployXAddresses(config.RepayerRoutes!.Pools!, deployer);
@@ -304,6 +335,11 @@ export async function main() {
304335
await usdcPool!.grantRole(DEFAULT_ADMIN_ROLE, config.Admin);
305336
await usdcPool!.renounceRole(DEFAULT_ADMIN_ROLE, deployer);
306337
}
338+
339+
if (config.USDCStablecoinPool) {
340+
await usdcStablecoinPool!.grantRole(DEFAULT_ADMIN_ROLE, config.Admin);
341+
await usdcStablecoinPool!.renounceRole(DEFAULT_ADMIN_ROLE, deployer);
342+
}
307343
}
308344

309345
let multicall: string;

scripts/deployUSDCPool.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,10 @@ export async function main() {
6161
await usdcPool!.grantRole(WITHDRAW_PROFIT_ROLE, config.WithdrawProfit);
6262
await usdcPool!.grantRole(PAUSER_ROLE, config.Pauser);
6363

64-
await usdcPool!.grantRole(DEFAULT_ADMIN_ROLE, config.Admin);
65-
await usdcPool!.renounceRole(DEFAULT_ADMIN_ROLE, deployer);
64+
if (deployer.address !== config.Admin) {
65+
await usdcPool!.grantRole(DEFAULT_ADMIN_ROLE, config.Admin);
66+
await usdcPool!.renounceRole(DEFAULT_ADMIN_ROLE, deployer);
67+
}
6668

6769
await verifier.verify(process.env.VERIFY === "true");
6870
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
import dotenv from "dotenv";
2+
dotenv.config();
3+
import hre from "hardhat";
4+
import {getVerifier} from "./helpers";
5+
import {resolveProxyXAddress, toBytes32} from "../test/helpers";
6+
import {isSet, assert, DEFAULT_ADMIN_ROLE} from "./common";
7+
import {LiquidityPoolStablecoin} from "../typechain-types";
8+
import {networkConfig, Network, NetworkConfig, LiquidityPoolUSDCStablecoin} from "../network.config";
9+
10+
export async function main() {
11+
const [deployer] = await hre.ethers.getSigners();
12+
13+
const LIQUIDITY_ADMIN_ROLE = toBytes32("LIQUIDITY_ADMIN_ROLE");
14+
const WITHDRAW_PROFIT_ROLE = toBytes32("WITHDRAW_PROFIT_ROLE");
15+
const PAUSER_ROLE = toBytes32("PAUSER_ROLE");
16+
17+
assert(isSet(process.env.DEPLOY_ID), "DEPLOY_ID must be set");
18+
const verifier = getVerifier(process.env.DEPLOY_ID);
19+
console.log(`Deployment ID: ${process.env.DEPLOY_ID}`);
20+
21+
let network: Network;
22+
let config: NetworkConfig;
23+
console.log(`Deploying to: ${hre.network.name}`);
24+
if (hre.network.name === "hardhat" && Object.values(Network).includes(process.env.DRY_RUN as Network)) {
25+
network = process.env.DRY_RUN as Network;
26+
config = networkConfig[network];
27+
if (process.env.DEPLOY_TYPE == "STAGE") {
28+
assert(config.Stage != undefined, "Stage config must be defined");
29+
console.log(`Dry run for deploying staging USDC stablecoin pool on fork: ${network}`);
30+
config = config.Stage!;
31+
} else {
32+
console.log(`Dry run for deploying USDC stablecoin pool on fork: ${network}`);
33+
}
34+
} else if (Object.values(Network).includes(hre.network.name as Network)) {
35+
network = hre.network.name as Network;
36+
config = networkConfig[network];
37+
if (process.env.DEPLOY_TYPE == "STAGE") {
38+
assert(config.Stage != undefined, "Stage config must be defined");
39+
console.log(`Deploying staging USDC stablecoin pool on: ${network}`);
40+
config = config.Stage!;
41+
} else {
42+
console.log(`Deploying USDC stablecoin pool on: ${network}`);
43+
}
44+
} else {
45+
console.log(`Nothing to deploy on ${hre.network.name} network`);
46+
return;
47+
}
48+
49+
assert(config.USDCStablecoinPool, "USDC stablecoin pool is not configured");
50+
51+
const rebalancer = await resolveProxyXAddress("Rebalancer");
52+
console.log(`Rebalancer: ${rebalancer}`);
53+
54+
console.log("Deploying USDC Stablecoin Liquidity Pool");
55+
const usdcPoolStablecoin: LiquidityPoolStablecoin = (await verifier.deployX(
56+
"LiquidityPoolStablecoin", deployer, {}, [config.USDC, deployer, config.MpcAddress], LiquidityPoolUSDCStablecoin
57+
)) as LiquidityPoolStablecoin;
58+
console.log(`LiquidityPoolUSDCStablecoin: ${usdcPoolStablecoin.target}`);
59+
60+
await usdcPoolStablecoin!.grantRole(LIQUIDITY_ADMIN_ROLE, rebalancer);
61+
await usdcPoolStablecoin!.grantRole(WITHDRAW_PROFIT_ROLE, config.WithdrawProfit);
62+
await usdcPoolStablecoin!.grantRole(PAUSER_ROLE, config.Pauser);
63+
64+
if (deployer.address !== config.Admin) {
65+
await usdcPoolStablecoin!.grantRole(DEFAULT_ADMIN_ROLE, config.Admin);
66+
await usdcPoolStablecoin!.renounceRole(DEFAULT_ADMIN_ROLE, deployer);
67+
}
68+
69+
await verifier.verify(process.env.VERIFY === "true");
70+
}
71+
72+
if (process.env.SCRIPT_ENV !== "CI") {
73+
main();
74+
}

scripts/test.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {main as upgradeRebalancer} from "./upgradeRebalancer";
66
import {main as redeployStash} from "./redeployStash";
77
import {main as deployCensoredMulticall} from "./deployCensoredMulticall";
88
import {main as deployUSDCPool} from "./deployUSDCPool";
9+
import {main as deployUSDCStablecoinPool} from "./deployUSDCStablecoinPool";
910
import {main as deployRepayer} from "./deployRepayer";
1011
import {main as upgradeLiquidityHub} from "./upgradeLiquidityHub";
1112

@@ -21,6 +22,8 @@ async function main() {
2122
await deployCensoredMulticall();
2223
console.log("Test deployUSDCPool.")
2324
await deployUSDCPool();
25+
console.log("Test deployUSDCStablecoinPool.")
26+
await deployUSDCStablecoinPool();
2427
console.log("Test deployRepayer.")
2528
await deployRepayer();
2629
console.log("Test upgradeLiquidityHub.")

test/LiquidityPoolAave.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -866,7 +866,7 @@ describe("LiquidityPoolAave", function () {
866866
await expect(liquidityPool.connect(liquidityAdmin).deposit(amountCollateral))
867867
.to.emit(liquidityPool, "SuppliedToAave");
868868

869-
const amountToBorrow = 10n * UNI_DEC;
869+
const amountToBorrow = 20n * UNI_DEC;
870870

871871
const signature = await signBorrow(
872872
mpc_signer,

0 commit comments

Comments
 (0)