feat: bridges, PMM, flash workflow, token-aggregation, and deployment docs

- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault.
- Token-aggregation service routes, planner, chain config, relay env templates.
- Config snapshots and multi-chain deployment markdown updates.
- gitignore services/btc-intake/dist/ (tsc output); do not track dist.

Run forge build && forge test before deploy (large solc graph).

Made-with: Cursor
This commit is contained in:
defiQUG
2026-04-07 23:40:52 -07:00
parent 0fb7bba07b
commit 76aa419320
289 changed files with 28367 additions and 824 deletions

View File

@@ -0,0 +1,27 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Script, console} from "forge-std/Script.sol";
import {CCIPRelayRouter} from "../contracts/relay/CCIPRelayRouter.sol";
contract DeployCCIPRelayRouterOnly is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
address relayer = vm.envAddress("RELAYER_ADDRESS");
console.log("Deploying CCIPRelayRouter with deployer:", deployer);
console.log("Relayer:", relayer);
vm.startBroadcast(deployerPrivateKey);
CCIPRelayRouter relayRouter = new CCIPRelayRouter();
if (relayer != address(0)) {
relayRouter.grantRelayerRole(relayer);
}
vm.stopBroadcast();
console.log("CCIPRelayRouter deployed at:", address(relayRouter));
}
}

View File

@@ -0,0 +1,61 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {CWAssetReserveVerifier} from "../contracts/bridge/integration/CWAssetReserveVerifier.sol";
interface ICWMultiTokenBridgeL1AdminV2 {
function setReserveVerifier(address newVerifier) external;
}
/**
* @title DeployCWAssetReserveVerifier
* @notice Deploy a generic reserve verifier for stable, monetary-unit, or gas-native c* lanes.
*
* Env:
* PRIVATE_KEY (required)
* CW_L1_BRIDGE or CHAIN138_L1_BRIDGE (required)
* CW_ASSET_RESERVE_VAULT (optional)
* CW_ASSET_RESERVE_SYSTEM (optional)
* CW_ATTACH_VERIFIER_TO_L1=1 (optional, default 0)
*/
contract DeployCWAssetReserveVerifier is Script {
function run() external {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
address l1Bridge = vm.envOr("CW_L1_BRIDGE", address(0));
if (l1Bridge == address(0)) {
l1Bridge = vm.envOr("CHAIN138_L1_BRIDGE", address(0));
}
if (l1Bridge == address(0)) {
l1Bridge = vm.envOr("CW_L1_BRIDGE_CHAIN138", address(0));
}
address assetReserveVault = vm.envOr("CW_ASSET_RESERVE_VAULT", address(0));
address reserveSystem = vm.envOr("CW_ASSET_RESERVE_SYSTEM", address(0));
bool attachVerifierToBridge = vm.envOr("CW_ATTACH_VERIFIER_TO_L1", uint256(0)) == 1;
require(l1Bridge != address(0), "CW_L1_BRIDGE or CHAIN138_L1_BRIDGE required");
vm.startBroadcast(privateKey);
CWAssetReserveVerifier verifier = new CWAssetReserveVerifier(
vm.addr(privateKey),
l1Bridge,
assetReserveVault,
reserveSystem
);
if (attachVerifierToBridge) {
ICWMultiTokenBridgeL1AdminV2(l1Bridge).setReserveVerifier(address(verifier));
}
vm.stopBroadcast();
console.log("CWAssetReserveVerifier:", address(verifier));
console.log(" L1 bridge:", l1Bridge);
console.log(" attached to bridge:", attachVerifierToBridge);
console.log(" asset reserve vault:", assetReserveVault);
console.log(" reserve system:", reserveSystem);
}
}

View File

@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Script, console} from "forge-std/Script.sol";
import {CWMultiTokenBridgeL1} from "../contracts/bridge/CWMultiTokenBridgeL1.sol";
contract DeployCWMultiTokenBridgeL1 is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address sendRouter = vm.envAddress("CW_SEND_ROUTER");
address receiveRouter = vm.envAddress("CW_RECEIVE_ROUTER");
address feeToken = vm.envOr("CW_FEE_TOKEN", address(0));
vm.startBroadcast(deployerPrivateKey);
CWMultiTokenBridgeL1 bridge = new CWMultiTokenBridgeL1(sendRouter, receiveRouter, feeToken);
vm.stopBroadcast();
console.log("CWMultiTokenBridgeL1:", address(bridge));
}
}

View File

@@ -0,0 +1,20 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Script, console} from "forge-std/Script.sol";
import {CWMultiTokenBridgeL2} from "../contracts/bridge/CWMultiTokenBridgeL2.sol";
contract DeployCWMultiTokenBridgeL2 is Script {
function run() external {
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address sendRouter = vm.envAddress("CW_SEND_ROUTER");
address receiveRouter = vm.envAddress("CW_RECEIVE_ROUTER");
address feeToken = vm.envOr("CW_FEE_TOKEN", address(0));
vm.startBroadcast(deployerPrivateKey);
CWMultiTokenBridgeL2 bridge = new CWMultiTokenBridgeL2(sendRouter, receiveRouter, feeToken);
vm.stopBroadcast();
console.log("CWMultiTokenBridgeL2:", address(bridge));
}
}

View File

@@ -0,0 +1,99 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {CWReserveVerifier} from "../contracts/bridge/integration/CWReserveVerifier.sol";
interface ICWMultiTokenBridgeL1Admin {
function setReserveVerifier(address newVerifier) external;
}
/**
* @title DeployCWReserveVerifier
* @notice Deploy and optionally attach/configure the cW canonical reserve verifier.
*
* Env:
* PRIVATE_KEY (required)
* CW_L1_BRIDGE (required)
* CW_STABLECOIN_RESERVE_VAULT (optional)
* CW_RESERVE_SYSTEM (optional)
* CW_ATTACH_VERIFIER_TO_L1=1 (optional, default 1)
* CW_CANONICAL_USDT / CW_CANONICAL_USDC (optional)
* CW_USDT_RESERVE_ASSET / CW_USDC_RESERVE_ASSET (optional, used for reserve-system balance checks)
* CW_REQUIRE_VAULT_BACKING=1 (optional, defaults to 1 when vault address is set)
* CW_REQUIRE_RESERVE_SYSTEM_BALANCE=1 (optional, defaults to 1 when reserve system address is set)
* CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT=1 (optional, defaults to 1 when vault address is set)
*/
contract DeployCWReserveVerifier is Script {
function run() external {
uint256 privateKey = vm.envUint("PRIVATE_KEY");
address admin = vm.addr(privateKey);
address l1Bridge = vm.envAddress("CW_L1_BRIDGE");
address stablecoinReserveVault = vm.envOr("CW_STABLECOIN_RESERVE_VAULT", address(0));
address reserveSystem = vm.envOr("CW_RESERVE_SYSTEM", address(0));
bool attachVerifierToBridge = vm.envOr("CW_ATTACH_VERIFIER_TO_L1", uint256(1)) == 1;
bool defaultRequireVaultBacking = stablecoinReserveVault != address(0);
bool defaultRequireReserveSystemBalance = reserveSystem != address(0);
bool defaultRequireTokenOwnerMatchVault = stablecoinReserveVault != address(0);
bool requireVaultBacking = vm.envOr("CW_REQUIRE_VAULT_BACKING", defaultRequireVaultBacking ? uint256(1) : uint256(0)) == 1;
bool requireReserveSystemBalance =
vm.envOr("CW_REQUIRE_RESERVE_SYSTEM_BALANCE", defaultRequireReserveSystemBalance ? uint256(1) : uint256(0)) == 1;
bool requireTokenOwnerMatchVault =
vm.envOr("CW_REQUIRE_TOKEN_OWNER_MATCH_VAULT", defaultRequireTokenOwnerMatchVault ? uint256(1) : uint256(0)) == 1;
address canonicalUSDT = vm.envOr("CW_CANONICAL_USDT", address(0));
address canonicalUSDC = vm.envOr("CW_CANONICAL_USDC", address(0));
address usdtReserveAsset = vm.envOr("CW_USDT_RESERVE_ASSET", address(0));
address usdcReserveAsset = vm.envOr("CW_USDC_RESERVE_ASSET", address(0));
vm.startBroadcast(privateKey);
CWReserveVerifier verifier = new CWReserveVerifier(
admin,
l1Bridge,
stablecoinReserveVault,
reserveSystem
);
if (attachVerifierToBridge) {
ICWMultiTokenBridgeL1Admin(l1Bridge).setReserveVerifier(address(verifier));
}
if (canonicalUSDT != address(0)) {
verifier.configureToken(
canonicalUSDT,
usdtReserveAsset,
requireVaultBacking,
requireReserveSystemBalance && usdtReserveAsset != address(0),
requireTokenOwnerMatchVault
);
}
if (canonicalUSDC != address(0)) {
verifier.configureToken(
canonicalUSDC,
usdcReserveAsset,
requireVaultBacking,
requireReserveSystemBalance && usdcReserveAsset != address(0),
requireTokenOwnerMatchVault
);
}
vm.stopBroadcast();
console.log("CWReserveVerifier:", address(verifier));
console.log(" L1 bridge:", l1Bridge);
console.log(" attached to bridge:", attachVerifierToBridge);
console.log(" stablecoin reserve vault:", stablecoinReserveVault);
console.log(" reserve system:", reserveSystem);
console.log(" requireVaultBacking:", requireVaultBacking);
console.log(" requireReserveSystemBalance:", requireReserveSystemBalance);
console.log(" requireTokenOwnerMatchVault:", requireTokenOwnerMatchVault);
console.log(" configured cUSDT:", canonicalUSDT);
console.log(" configured cUSDC:", canonicalUSDC);
}
}

View File

@@ -0,0 +1,141 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Script, console} from "forge-std/Script.sol";
import "../../../contracts/bridge/trustless/EnhancedSwapRouterV2.sol";
import "../../../contracts/bridge/trustless/RouteTypesV2.sol";
import "../../../contracts/bridge/trustless/pilot/Chain138PilotDexVenues.sol";
contract DeployChain138PilotDexVenues is Script {
address constant CHAIN138_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address constant CHAIN138_USDT = 0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1;
address constant CHAIN138_USDC = 0x71D6687F38b93CCad569Fa6352c876eea967201b;
address constant LIVE_ROUTER_V2 = 0xF1c93F54A5C2fc0d7766Ccb0Ad8f157DFB4C99Ce;
uint24 constant UNISWAP_FEE = 3000;
uint256 constant BALANCER_FEE_BPS = 30;
uint256 constant CURVE_FEE_BPS = 4;
uint256 constant ONE_INCH_FEE_BPS = 35;
bytes32 constant BALANCER_WETH_USDT_POOL_ID = keccak256("chain138-pilot-balancer-weth-usdt");
bytes32 constant BALANCER_WETH_USDC_POOL_ID = keccak256("chain138-pilot-balancer-weth-usdc");
uint256 constant WETH_PAIR_WETH_AMOUNT = 100 ether;
uint256 constant WETH_PAIR_STABLE_AMOUNT = 210_000 * 1e6;
uint256 constant CURVE_STABLE_AMOUNT = 500_000 * 1e6;
function run() external {
require(block.chainid == 138, "DeployChain138PilotDexVenues: Chain 138 only");
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
address payable routerV2 = payable(vm.envOr("ENHANCED_SWAP_ROUTER_V2_ADDRESS", LIVE_ROUTER_V2));
vm.startBroadcast(deployerPrivateKey);
Chain138PilotUniswapV3Router uniswapRouter = new Chain138PilotUniswapV3Router();
Chain138PilotBalancerVault balancerVault = new Chain138PilotBalancerVault();
Chain138PilotCurve3Pool curve3Pool = new Chain138PilotCurve3Pool(CHAIN138_USDT, CHAIN138_USDC, address(0), CURVE_FEE_BPS);
Chain138PilotOneInchAggregationRouter oneInchRouter = new Chain138PilotOneInchAggregationRouter();
uint256 totalUsdtRequired = (WETH_PAIR_STABLE_AMOUNT * 3) + CURVE_STABLE_AMOUNT;
uint256 totalUsdcRequired = (WETH_PAIR_STABLE_AMOUNT * 3) + CURVE_STABLE_AMOUNT;
uint256 totalWethRequired = WETH_PAIR_WETH_AMOUNT * 6;
_ensureMintedStable(CHAIN138_USDT, deployer, totalUsdtRequired);
_ensureMintedStable(CHAIN138_USDC, deployer, totalUsdcRequired);
_ensureWrappedWeth(deployer, totalWethRequired);
_approveToken(CHAIN138_WETH, address(uniswapRouter), WETH_PAIR_WETH_AMOUNT * 2);
_approveToken(CHAIN138_USDT, address(uniswapRouter), WETH_PAIR_STABLE_AMOUNT);
_approveToken(CHAIN138_USDC, address(uniswapRouter), WETH_PAIR_STABLE_AMOUNT);
uniswapRouter.seedPair(CHAIN138_WETH, CHAIN138_USDT, UNISWAP_FEE, WETH_PAIR_WETH_AMOUNT, WETH_PAIR_STABLE_AMOUNT);
uniswapRouter.seedPair(CHAIN138_WETH, CHAIN138_USDC, UNISWAP_FEE, WETH_PAIR_WETH_AMOUNT, WETH_PAIR_STABLE_AMOUNT);
_approveToken(CHAIN138_WETH, address(balancerVault), WETH_PAIR_WETH_AMOUNT * 2);
_approveToken(CHAIN138_USDT, address(balancerVault), WETH_PAIR_STABLE_AMOUNT);
_approveToken(CHAIN138_USDC, address(balancerVault), WETH_PAIR_STABLE_AMOUNT);
balancerVault.seedPool(BALANCER_WETH_USDT_POOL_ID, CHAIN138_WETH, CHAIN138_USDT, WETH_PAIR_WETH_AMOUNT, WETH_PAIR_STABLE_AMOUNT, BALANCER_FEE_BPS);
balancerVault.seedPool(BALANCER_WETH_USDC_POOL_ID, CHAIN138_WETH, CHAIN138_USDC, WETH_PAIR_WETH_AMOUNT, WETH_PAIR_STABLE_AMOUNT, BALANCER_FEE_BPS);
_approveToken(CHAIN138_USDT, address(curve3Pool), CURVE_STABLE_AMOUNT);
_approveToken(CHAIN138_USDC, address(curve3Pool), CURVE_STABLE_AMOUNT);
curve3Pool.fund(CURVE_STABLE_AMOUNT, CURVE_STABLE_AMOUNT, 0);
_approveToken(CHAIN138_WETH, address(oneInchRouter), WETH_PAIR_WETH_AMOUNT * 2);
_approveToken(CHAIN138_USDT, address(oneInchRouter), WETH_PAIR_STABLE_AMOUNT);
_approveToken(CHAIN138_USDC, address(oneInchRouter), WETH_PAIR_STABLE_AMOUNT);
oneInchRouter.seedRoute(CHAIN138_WETH, CHAIN138_USDT, WETH_PAIR_WETH_AMOUNT, WETH_PAIR_STABLE_AMOUNT, ONE_INCH_FEE_BPS);
oneInchRouter.seedRoute(CHAIN138_WETH, CHAIN138_USDC, WETH_PAIR_WETH_AMOUNT, WETH_PAIR_STABLE_AMOUNT, ONE_INCH_FEE_BPS);
if (routerV2 != address(0)) {
EnhancedSwapRouterV2 router = EnhancedSwapRouterV2(routerV2);
bytes memory uniswapProviderData = abi.encode(bytes(""), UNISWAP_FEE, address(uniswapRouter), false);
bytes memory balancerUsdtProviderData = abi.encode(BALANCER_WETH_USDT_POOL_ID);
bytes memory balancerUsdcProviderData = abi.encode(BALANCER_WETH_USDC_POOL_ID);
bytes memory curveProviderData = abi.encode(int128(0), int128(1), false);
bytes memory oneInchProviderData = abi.encode(address(oneInchRouter), address(oneInchRouter), bytes(""));
_setBidirectionalRoute(router, CHAIN138_WETH, CHAIN138_USDT, RouteTypesV2.Provider.UniswapV3, address(uniswapRouter), uniswapProviderData);
_setBidirectionalRoute(router, CHAIN138_WETH, CHAIN138_USDC, RouteTypesV2.Provider.UniswapV3, address(uniswapRouter), uniswapProviderData);
_setBidirectionalRoute(router, CHAIN138_WETH, CHAIN138_USDT, RouteTypesV2.Provider.Balancer, address(balancerVault), balancerUsdtProviderData);
_setBidirectionalRoute(router, CHAIN138_WETH, CHAIN138_USDC, RouteTypesV2.Provider.Balancer, address(balancerVault), balancerUsdcProviderData);
_setBidirectionalRoute(router, CHAIN138_USDT, CHAIN138_USDC, RouteTypesV2.Provider.Curve, address(curve3Pool), curveProviderData);
_setBidirectionalRoute(router, CHAIN138_WETH, CHAIN138_USDT, RouteTypesV2.Provider.OneInch, address(oneInchRouter), oneInchProviderData);
_setBidirectionalRoute(router, CHAIN138_WETH, CHAIN138_USDC, RouteTypesV2.Provider.OneInch, address(oneInchRouter), oneInchProviderData);
router.setProviderEnabled(RouteTypesV2.Provider.OneInch, true);
}
vm.stopBroadcast();
console.log("CHAIN138_PILOT_UNISWAP_V3_ROUTER:", address(uniswapRouter));
console.log("CHAIN138_PILOT_UNISWAP_V3_QUOTER:", address(uniswapRouter));
console.log("CHAIN138_PILOT_BALANCER_VAULT:", address(balancerVault));
console.log("CHAIN138_PILOT_BALANCER_WETH_USDT_POOL_ID:");
console.logBytes32(BALANCER_WETH_USDT_POOL_ID);
console.log("CHAIN138_PILOT_BALANCER_WETH_USDC_POOL_ID:");
console.logBytes32(BALANCER_WETH_USDC_POOL_ID);
console.log("CHAIN138_PILOT_CURVE_3POOL:", address(curve3Pool));
console.log("CHAIN138_PILOT_ONEINCH_ROUTER:", address(oneInchRouter));
}
function _ensureMintedStable(address token, address deployer, uint256 requiredBalance) internal {
uint256 currentBalance = IERC20(token).balanceOf(deployer);
if (currentBalance >= requiredBalance) {
return;
}
uint256 delta = requiredBalance - currentBalance;
(bool ok,) = token.call(abi.encodeWithSignature("mint(address,uint256)", deployer, delta));
require(ok, "DeployChain138PilotDexVenues: stable mint failed");
}
function _ensureWrappedWeth(address deployer, uint256 requiredBalance) internal {
uint256 currentBalance = IERC20(CHAIN138_WETH).balanceOf(deployer);
if (currentBalance >= requiredBalance) {
return;
}
uint256 delta = requiredBalance - currentBalance;
(bool ok,) = CHAIN138_WETH.call{value: delta}(abi.encodeWithSignature("deposit()"));
require(ok, "DeployChain138PilotDexVenues: WETH deposit failed");
}
function _approveToken(address token, address spender, uint256 amount) internal {
IERC20(token).approve(spender, 0);
IERC20(token).approve(spender, amount);
}
function _setBidirectionalRoute(
EnhancedSwapRouterV2 router,
address tokenA,
address tokenB,
RouteTypesV2.Provider provider,
address target,
bytes memory providerData
) internal {
router.setProviderRoute(tokenA, tokenB, provider, target, providerData, true);
router.setProviderRoute(tokenB, tokenA, provider, target, providerData, true);
}
}

View File

@@ -29,14 +29,14 @@ contract DeployEnhancedSwapRouter is Script {
address constant CHAIN138_USDC = 0x71D6687F38b93CCad569Fa6352c876eea967201b;
address constant CHAIN138_DAI_PLACEHOLDER = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
// Chain 138 live DODO pool map (2026-03-26)
// Chain 138 live DODO pool map (2026-04-02 corrected stable stack)
address constant CHAIN138_cUSDT = 0x93E66202A11B1772E55407B32B44e5Cd8eda7f22;
address constant CHAIN138_cUSDC = 0xf22258f57794CC8E06237084b353Ab30fFfa640b;
address constant CHAIN138_cEURT = 0xdf4b71c61E5912712C1Bdd451416B9aC26949d72;
address constant CHAIN138_cXAUC = 0x290E52a8819A4fbD0714E517225429aA2B70EC6b;
address constant CHAIN138_POOL_CUSDTCUSDC = 0xff8d3b8fDF7B112759F076B69f4271D4209C0849;
address constant CHAIN138_POOL_CUSDTUSDT = 0x6fc60DEDc92a2047062294488539992710b99D71;
address constant CHAIN138_POOL_CUSDCUSDC = 0x0309178Ae30302D83C76d6DD402a684ef3160eeC;
address constant CHAIN138_POOL_CUSDTCUSDC = 0x9e89bAe009adf128782E19e8341996c596ac40dC;
address constant CHAIN138_POOL_CUSDTUSDT = 0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66;
address constant CHAIN138_POOL_CUSDCUSDC = 0xc39B7D0F40838cbFb54649d327f49a6DAC964062;
address constant CHAIN138_POOL_CUSDT_XAU_PUBLIC = 0x1AA55E2001E5651349aFf5a63FD7a7ae44f0f1b0;
address constant CHAIN138_POOL_CUSDC_XAU_PUBLIC = 0xEa9AC6357CaCB42a83b9082B870610363b177CbA;
address constant CHAIN138_POOL_CEURT_XAU_PUBLIC = 0xba99bc1eAac164569d5aca96c806934dDaf970CF;
@@ -171,6 +171,8 @@ contract DeployEnhancedSwapRouter is Script {
_registerPair(router, CHAIN138_cUSDT, CHAIN138_cXAUC, CHAIN138_POOL_CUSDT_XAU_PUBLIC);
_registerPair(router, CHAIN138_cUSDC, CHAIN138_cXAUC, CHAIN138_POOL_CUSDC_XAU_PUBLIC);
_registerPair(router, CHAIN138_cEURT, CHAIN138_cXAUC, CHAIN138_POOL_CEURT_XAU_PUBLIC);
_registerOptionalEnvPool(router, "CHAIN138_POOL_WETH_USDT", CHAIN138_WETH, CHAIN138_USDT);
_registerOptionalEnvPool(router, "CHAIN138_POOL_WETH_USDC", CHAIN138_WETH, CHAIN138_USDC);
if (dodoPmmProvider != address(0)) {
router.setDodoLiquidityProvider(dodoPmmProvider);
@@ -205,6 +207,20 @@ contract DeployEnhancedSwapRouter is Script {
console.log("WARNING: current Chain 138 DODO initialization is primarily for token-to-token pair mappings via swapTokenToToken().");
}
function _registerOptionalEnvPool(
EnhancedSwapRouter router,
string memory envKey,
address tokenA,
address tokenB
) internal {
address pool = vm.envOr(envKey, address(0));
if (pool == address(0)) {
console.log("Optional pool env not set:", envKey);
return;
}
_registerPair(router, tokenA, tokenB, pool);
}
function _registerPair(
EnhancedSwapRouter router,
address tokenA,

View File

@@ -0,0 +1,219 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Script, console} from "forge-std/Script.sol";
import "../../../contracts/bridge/trustless/EnhancedSwapRouterV2.sol";
import "../../../contracts/bridge/trustless/IntentBridgeCoordinatorV2.sol";
import "../../../contracts/bridge/trustless/RouteTypesV2.sol";
import "../../../contracts/bridge/trustless/adapters/DodoRouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/adapters/DodoV3RouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/adapters/UniswapV3RouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/adapters/BalancerRouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/adapters/CurveRouteExecutorAdapter.sol";
import "../../../contracts/bridge/trustless/adapters/OneInchRouteExecutorAdapter.sol";
contract DeployEnhancedSwapRouterV2 is Script {
address constant CHAIN138_WETH = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
address constant CHAIN138_WETH10 = 0xf4BB2e28688e89fCcE3c0580D37d36A7672E8A9F;
address constant CHAIN138_USDT = 0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1;
address constant CHAIN138_USDC = 0x71D6687F38b93CCad569Fa6352c876eea967201b;
address constant CHAIN138_DAI_PLACEHOLDER = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
address constant CHAIN138_cUSDT = 0x93E66202A11B1772E55407B32B44e5Cd8eda7f22;
address constant CHAIN138_cUSDC = 0xf22258f57794CC8E06237084b353Ab30fFfa640b;
address constant CHAIN138_cEURT = 0xdf4b71c61E5912712C1Bdd451416B9aC26949d72;
address constant CHAIN138_cXAUC = 0x290E52a8819A4fbD0714E517225429aA2B70EC6b;
address constant CHAIN138_POOL_CUSDTCUSDC = 0x9e89bAe009adf128782E19e8341996c596ac40dC;
address constant CHAIN138_POOL_CUSDTUSDT = 0x866Cb44b59303d8dc5f4F9E3E7A8e8b0bf238d66;
address constant CHAIN138_POOL_CUSDCUSDC = 0xc39B7D0F40838cbFb54649d327f49a6DAC964062;
address constant CHAIN138_POOL_CUSDT_XAU_PUBLIC = 0x1AA55E2001E5651349aFf5a63FD7a7ae44f0f1b0;
address constant CHAIN138_POOL_CUSDC_XAU_PUBLIC = 0xEa9AC6357CaCB42a83b9082B870610363b177CbA;
address constant CHAIN138_POOL_CEURT_XAU_PUBLIC = 0xba99bc1eAac164569d5aca96c806934dDaf970CF;
address constant CHAIN138_D3_PROXY = 0xc9a11abB7C63d88546Be24D58a6d95e3762cB843;
address constant CHAIN138_D3_MM = 0x6550A3a59070061a262a893A1D6F3F490afFDBDA;
function run() external {
require(block.chainid == 138, "DeployEnhancedSwapRouterV2: Chain 138 only");
uint256 deployerPrivateKey = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(deployerPrivateKey);
address dodoProvider = vm.envOr("DODO_PMM_PROVIDER_ADDRESS", address(0));
if (dodoProvider == address(0)) {
dodoProvider = vm.envOr("DODO_PMM_PROVIDER", address(0));
}
require(dodoProvider != address(0), "DeployEnhancedSwapRouterV2: DODO PMM provider required");
address uniswapRouter = vm.envOr("UNISWAP_V3_ROUTER", address(0));
address uniswapQuoter = vm.envOr("UNISWAP_QUOTER_ADDRESS", address(0));
address balancerVault = vm.envOr("BALANCER_VAULT", address(0));
address curvePool = vm.envOr("CURVE_3POOL", address(0));
address oneInchRouter = vm.envOr("ONEINCH_ROUTER", address(0));
address wethUsdtPool = vm.envOr("CHAIN138_POOL_WETH_USDT", address(0));
address wethUsdcPool = vm.envOr("CHAIN138_POOL_WETH_USDC", address(0));
uint24 uniswapWethUsdtFee = uint24(vm.envOr("UNISWAP_V3_WETH_USDT_FEE", uint256(3000)));
uint24 uniswapWethUsdcFee = uint24(vm.envOr("UNISWAP_V3_WETH_USDC_FEE", uint256(3000)));
bytes32 balancerWethUsdtPoolId = vm.envOr("BALANCER_WETH_USDT_POOL_ID", bytes32(0));
bytes32 balancerWethUsdcPoolId = vm.envOr("BALANCER_WETH_USDC_POOL_ID", bytes32(0));
address d3Proxy = vm.envOr("CHAIN138_D3_PROXY_ADDRESS", CHAIN138_D3_PROXY);
address d3Pool = vm.envOr("CHAIN138_D3_MM_ADDRESS", CHAIN138_D3_MM);
console.log("=== EnhancedSwapRouterV2 Deployment ===");
console.log("Deployer:", deployer);
console.log("DODO PMM provider:", dodoProvider);
console.log("UNISWAP_V3_ROUTER:", uniswapRouter);
console.log("UNISWAP_QUOTER_ADDRESS:", uniswapQuoter);
console.log("BALANCER_VAULT:", balancerVault);
console.log("CURVE_3POOL:", curvePool);
console.log("ONEINCH_ROUTER:", oneInchRouter);
console.log("CHAIN138_POOL_WETH_USDT:", wethUsdtPool);
console.log("CHAIN138_POOL_WETH_USDC:", wethUsdcPool);
console.log("CHAIN138_D3_PROXY_ADDRESS:", d3Proxy);
console.log("CHAIN138_D3_MM_ADDRESS:", d3Pool);
vm.startBroadcast(deployerPrivateKey);
DodoRouteExecutorAdapter dodoAdapter = new DodoRouteExecutorAdapter();
DodoV3RouteExecutorAdapter dodoV3Adapter = new DodoV3RouteExecutorAdapter();
UniswapV3RouteExecutorAdapter uniswapAdapter = new UniswapV3RouteExecutorAdapter();
BalancerRouteExecutorAdapter balancerAdapter = new BalancerRouteExecutorAdapter();
CurveRouteExecutorAdapter curveAdapter = new CurveRouteExecutorAdapter();
OneInchRouteExecutorAdapter oneInchAdapter = new OneInchRouteExecutorAdapter();
EnhancedSwapRouterV2 router = new EnhancedSwapRouterV2(
CHAIN138_WETH,
CHAIN138_USDT,
CHAIN138_USDC,
CHAIN138_DAI_PLACEHOLDER
);
IntentBridgeCoordinatorV2 coordinator = new IntentBridgeCoordinatorV2(address(router));
router.setProviderAdapter(RouteTypesV2.Provider.Dodo, address(dodoAdapter));
router.setProviderAdapter(RouteTypesV2.Provider.DodoV3, address(dodoV3Adapter));
router.setProviderAdapter(RouteTypesV2.Provider.UniswapV3, address(uniswapAdapter));
router.setProviderAdapter(RouteTypesV2.Provider.Balancer, address(balancerAdapter));
router.setProviderAdapter(RouteTypesV2.Provider.Curve, address(curveAdapter));
router.setProviderAdapter(RouteTypesV2.Provider.OneInch, address(oneInchAdapter));
_setDodoPair(router, CHAIN138_cUSDT, CHAIN138_cUSDC, dodoProvider, CHAIN138_POOL_CUSDTCUSDC);
_setDodoPair(router, CHAIN138_cUSDT, CHAIN138_USDT, dodoProvider, CHAIN138_POOL_CUSDTUSDT);
_setDodoPair(router, CHAIN138_cUSDC, CHAIN138_USDC, dodoProvider, CHAIN138_POOL_CUSDCUSDC);
_setDodoPair(router, CHAIN138_cUSDT, CHAIN138_cXAUC, dodoProvider, CHAIN138_POOL_CUSDT_XAU_PUBLIC);
_setDodoPair(router, CHAIN138_cUSDC, CHAIN138_cXAUC, dodoProvider, CHAIN138_POOL_CUSDC_XAU_PUBLIC);
_setDodoPair(router, CHAIN138_cEURT, CHAIN138_cXAUC, dodoProvider, CHAIN138_POOL_CEURT_XAU_PUBLIC);
if (wethUsdtPool != address(0)) {
_setDodoPair(router, CHAIN138_WETH, CHAIN138_USDT, dodoProvider, wethUsdtPool);
}
if (wethUsdcPool != address(0)) {
_setDodoPair(router, CHAIN138_WETH, CHAIN138_USDC, dodoProvider, wethUsdcPool);
}
if (d3Proxy != address(0) && d3Pool != address(0)) {
_setDodoV3Pair(router, CHAIN138_WETH10, CHAIN138_USDT, d3Proxy, d3Pool);
} else {
router.setProviderEnabled(RouteTypesV2.Provider.DodoV3, false);
}
if (uniswapRouter != address(0) && uniswapQuoter != address(0)) {
_setUniswapPair(router, CHAIN138_WETH, CHAIN138_USDT, uniswapRouter, uniswapQuoter, uniswapWethUsdtFee);
_setUniswapPair(router, CHAIN138_WETH, CHAIN138_USDC, uniswapRouter, uniswapQuoter, uniswapWethUsdcFee);
} else {
router.setProviderEnabled(RouteTypesV2.Provider.UniswapV3, false);
}
if (balancerVault != address(0) && balancerWethUsdtPoolId != bytes32(0)) {
_setBalancerPair(router, CHAIN138_WETH, CHAIN138_USDT, balancerVault, balancerWethUsdtPoolId);
}
if (balancerVault != address(0) && balancerWethUsdcPoolId != bytes32(0)) {
_setBalancerPair(router, CHAIN138_WETH, CHAIN138_USDC, balancerVault, balancerWethUsdcPoolId);
}
if (balancerVault == address(0) || (balancerWethUsdtPoolId == bytes32(0) && balancerWethUsdcPoolId == bytes32(0))) {
router.setProviderEnabled(RouteTypesV2.Provider.Balancer, false);
}
if (curvePool != address(0)) {
_setCurvePair(router, CHAIN138_USDT, CHAIN138_USDC, curvePool, 0, 1, false);
} else {
router.setProviderEnabled(RouteTypesV2.Provider.Curve, false);
}
if (oneInchRouter == address(0)) {
router.setProviderEnabled(RouteTypesV2.Provider.OneInch, false);
}
vm.stopBroadcast();
console.log("EnhancedSwapRouterV2:", address(router));
console.log("IntentBridgeCoordinatorV2:", address(coordinator));
console.log("DodoRouteExecutorAdapter:", address(dodoAdapter));
console.log("DodoV3RouteExecutorAdapter:", address(dodoV3Adapter));
console.log("UniswapV3RouteExecutorAdapter:", address(uniswapAdapter));
console.log("BalancerRouteExecutorAdapter:", address(balancerAdapter));
console.log("CurveRouteExecutorAdapter:", address(curveAdapter));
console.log("OneInchRouteExecutorAdapter:", address(oneInchAdapter));
}
function _setDodoPair(
EnhancedSwapRouterV2 router,
address tokenA,
address tokenB,
address target,
address pool
) internal {
bytes memory providerData = abi.encode(pool);
router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.Dodo, target, providerData, true);
router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.Dodo, target, providerData, true);
}
function _setUniswapPair(
EnhancedSwapRouterV2 router,
address tokenA,
address tokenB,
address target,
address quoter,
uint24 fee
) internal {
bytes memory providerData = abi.encode(bytes(""), fee, quoter, false);
router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.UniswapV3, target, providerData, true);
router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.UniswapV3, target, providerData, true);
}
function _setDodoV3Pair(
EnhancedSwapRouterV2 router,
address tokenA,
address tokenB,
address target,
address pool
) internal {
bytes memory providerData = abi.encode(pool);
router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.DodoV3, target, providerData, true);
router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.DodoV3, target, providerData, true);
}
function _setBalancerPair(
EnhancedSwapRouterV2 router,
address tokenA,
address tokenB,
address target,
bytes32 poolId
) internal {
bytes memory providerData = abi.encode(poolId);
router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.Balancer, target, providerData, true);
router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.Balancer, target, providerData, true);
}
function _setCurvePair(
EnhancedSwapRouterV2 router,
address tokenA,
address tokenB,
address target,
int128 i,
int128 j,
bool useUnderlying
) internal {
bytes memory providerData = abi.encode(i, j, useUnderlying);
router.setProviderRoute(tokenA, tokenB, RouteTypesV2.Provider.Curve, target, providerData, true);
router.setProviderRoute(tokenB, tokenA, RouteTypesV2.Provider.Curve, target, providerData, true);
}
}

View File

@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {AaveQuotePushFlashReceiver} from "../../contracts/flash/AaveQuotePushFlashReceiver.sol";
/**
* @title DeployAaveQuotePushFlashReceiver
* @notice Deploy the Aave V3 quote-push flash receiver.
*
* Env:
* PRIVATE_KEY required
* AAVE_POOL_ADDRESS optional; defaults to Aave V3 mainnet Pool
*
* Usage:
* forge script script/deploy/DeployAaveQuotePushFlashReceiver.s.sol:DeployAaveQuotePushFlashReceiver \
* --rpc-url $ETHEREUM_MAINNET_RPC --broadcast -vvvv
*/
contract DeployAaveQuotePushFlashReceiver is Script {
address internal constant DEFAULT_AAVE_POOL_MAINNET = 0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address pool = vm.envOr("AAVE_POOL_ADDRESS", DEFAULT_AAVE_POOL_MAINNET);
address deployer = vm.addr(pk);
console.log("Deployer:", deployer);
console.log("Aave Pool:", pool);
vm.startBroadcast(pk);
AaveQuotePushFlashReceiver receiver = new AaveQuotePushFlashReceiver(pool);
vm.stopBroadcast();
console.log("AaveQuotePushFlashReceiver:", address(receiver));
}
}

View File

@@ -0,0 +1,146 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {CompliantUSDTTokenV2} from "../../contracts/tokens/CompliantUSDTTokenV2.sol";
import {CompliantUSDCTokenV2} from "../../contracts/tokens/CompliantUSDCTokenV2.sol";
import {UniversalAssetRegistry} from "../../contracts/registry/UniversalAssetRegistry.sol";
/**
* @title DeployAndStageCompliantFiatTokensV2ForChain
* @notice Deploy fresh source-aligned cUSDT V2 / cUSDC V2 contracts, optionally wire governance/disclosure metadata,
* and stage them in UniversalAssetRegistry as version-aware GRU assets.
*
* Env:
* PRIVATE_KEY (required)
* INITIAL_OPERATOR (optional; default deployer)
* ADMIN (optional; default deployer / OWNER alias)
* OWNER (optional alias for ADMIN when ADMIN unset)
* INITIAL_SUPPLY (optional; default 0 for safe promotion)
* FORWARD_CANONICAL (optional; default true for promotion flow)
* GOVERNANCE_CONTROLLER (optional; when set, calls setGovernanceController on fresh deployments)
* UNIVERSAL_ASSET_REGISTRY (optional; when set and REGISTER_IN_GRU != 0, registers V2 assets)
* REGISTER_IN_GRU (optional; default 1)
* TOKEN_URI (optional generic fallback)
* REGULATORY_DISCLOSURE_URI (optional)
* REPORTING_URI (optional)
* CUSDT_V2_TOKEN_URI / CUSDC_V2_TOKEN_URI (optional; per-token overrides)
* CUSDT_V2_REGULATORY_DISCLOSURE_URI / CUSDC_V2_REGULATORY_DISCLOSURE_URI (optional; per-token overrides)
* CUSDT_V2_REPORTING_URI / CUSDC_V2_REPORTING_URI (optional; per-token overrides)
* DEPLOY_CUSDT_V2 / DEPLOY_CUSDC_V2 (optional; default both 1)
*/
contract DeployAndStageCompliantFiatTokensV2ForChain is Script {
uint256 internal constant DEFAULT_INITIAL_SUPPLY = 0;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address initialOperator = vm.envOr("INITIAL_OPERATOR", deployer);
address ownerAlias = vm.envOr("OWNER", deployer);
address admin = vm.envOr("ADMIN", ownerAlias);
uint256 initialSupply = vm.envOr("INITIAL_SUPPLY", DEFAULT_INITIAL_SUPPLY);
bool forwardCanonical = vm.envOr("FORWARD_CANONICAL", true);
address governanceController = vm.envOr("GOVERNANCE_CONTROLLER", address(0));
address registryAddr = vm.envOr("UNIVERSAL_ASSET_REGISTRY", address(0));
bool registerInGru = vm.envOr("REGISTER_IN_GRU", uint256(1)) != 0;
string memory genericTokenURI = vm.envOr("TOKEN_URI", string(""));
string memory disclosureURI = vm.envOr("REGULATORY_DISCLOSURE_URI", string(""));
string memory reportingURI = vm.envOr("REPORTING_URI", string(""));
vm.startBroadcast(pk);
if (vm.envOr("DEPLOY_CUSDT_V2", uint256(1)) != 0) {
CompliantUSDTTokenV2 cusdtV2 =
new CompliantUSDTTokenV2(initialOperator, admin, initialSupply, forwardCanonical);
_postDeploy(
address(cusdtV2),
vm.envOr("CUSDT_V2_TOKEN_URI", genericTokenURI),
"cUSDT",
governanceController,
vm.envOr("CUSDT_V2_REGULATORY_DISCLOSURE_URI", disclosureURI),
vm.envOr("CUSDT_V2_REPORTING_URI", reportingURI),
registryAddr,
registerInGru,
"Tether USD (Compliant V2)",
"cUSDT.v2"
);
console.log("cUSDT_V2", address(cusdtV2));
}
if (vm.envOr("DEPLOY_CUSDC_V2", uint256(1)) != 0) {
CompliantUSDCTokenV2 cusdcV2 =
new CompliantUSDCTokenV2(initialOperator, admin, initialSupply, forwardCanonical);
_postDeploy(
address(cusdcV2),
vm.envOr("CUSDC_V2_TOKEN_URI", genericTokenURI),
"cUSDC",
governanceController,
vm.envOr("CUSDC_V2_REGULATORY_DISCLOSURE_URI", disclosureURI),
vm.envOr("CUSDC_V2_REPORTING_URI", reportingURI),
registryAddr,
registerInGru,
"USD Coin (Compliant V2)",
"cUSDC.v2"
);
console.log("cUSDC_V2", address(cusdcV2));
}
vm.stopBroadcast();
}
function _postDeploy(
address token,
string memory tokenURI,
string memory symbolDisplay,
address governanceController,
string memory disclosureURI,
string memory reportingURI,
address registryAddr,
bool registerInGru,
string memory name,
string memory versionedSymbol
) internal {
if (bytes(tokenURI).length > 0 || bytes(symbolDisplay).length > 0) {
_setPresentationMetadata(token, tokenURI, symbolDisplay);
}
if (governanceController != address(0)) {
_setGovernanceController(token, governanceController);
}
if (bytes(disclosureURI).length > 0 || bytes(reportingURI).length > 0) {
_setDisclosureMetadata(token, disclosureURI, reportingURI);
}
if (registerInGru && registryAddr != address(0)) {
UniversalAssetRegistry(registryAddr).registerGRUCompliantAsset(token, name, versionedSymbol, 6, "International");
}
}
function _setPresentationMetadata(address token, string memory tokenURI, string memory symbolDisplay) internal {
(bool ok,) = token.call(
abi.encodeWithSignature(
"emergencySetPresentationMetadata(bool,string,string)",
true,
tokenURI,
symbolDisplay
)
);
require(ok, "emergencySetPresentationMetadata failed");
}
function _setGovernanceController(address token, address governanceController) internal {
(bool ok,) = token.call(
abi.encodeWithSignature("setGovernanceController(address)", governanceController)
);
require(ok, "setGovernanceController failed");
}
function _setDisclosureMetadata(address token, string memory disclosureURI, string memory reportingURI) internal {
(bool ok,) = token.call(
abi.encodeWithSignature(
"emergencySetDisclosureMetadata(string,string)",
disclosureURI,
reportingURI
)
);
require(ok, "emergencySetDisclosureMetadata failed");
}
}

View File

@@ -0,0 +1,126 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {CompliantFiatTokenV2} from "../../contracts/tokens/CompliantFiatTokenV2.sol";
import {UniversalAssetRegistry} from "../../contracts/registry/UniversalAssetRegistry.sol";
/**
* @title DeployAndStageGenericCompliantFiatTokenV2ForChain
* @notice Deploy a generic GRU c* V2 asset, optionally wire governance / disclosure metadata,
* and stage it in UniversalAssetRegistry.
*
* Env:
* PRIVATE_KEY (required)
* TOKEN_NAME (required)
* TOKEN_SYMBOL (required)
* CURRENCY_CODE (required)
* TOKEN_DECIMALS (optional; default 6)
* VERSION_TAG (optional; default "2")
* INITIAL_OPERATOR (optional; default deployer)
* ADMIN / OWNER (optional; default deployer)
* INITIAL_SUPPLY (optional; default 0)
* FORWARD_CANONICAL (optional; default true)
* GOVERNANCE_CONTROLLER (optional)
* UNIVERSAL_ASSET_REGISTRY (optional; when set and REGISTER_IN_GRU != 0, registers the asset)
* REGISTER_IN_GRU (optional; default 1)
* REGISTRY_NAME (optional; default TOKEN_NAME)
* REGISTRY_SYMBOL (optional; default TOKEN_SYMBOL.VERSION_TAG lower-suffix style is not enforced)
* TOKEN_URI / REGULATORY_DISCLOSURE_URI / REPORTING_URI (optional)
*/
contract DeployAndStageGenericCompliantFiatTokenV2ForChain is Script {
uint256 internal constant DEFAULT_INITIAL_SUPPLY = 0;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address initialOperator = vm.envOr("INITIAL_OPERATOR", deployer);
address ownerAlias = vm.envOr("OWNER", deployer);
address admin = vm.envOr("ADMIN", ownerAlias);
uint256 initialSupply = vm.envOr("INITIAL_SUPPLY", DEFAULT_INITIAL_SUPPLY);
bool forwardCanonical = vm.envOr("FORWARD_CANONICAL", true);
address governanceController = vm.envOr("GOVERNANCE_CONTROLLER", address(0));
address registryAddr = vm.envOr("UNIVERSAL_ASSET_REGISTRY", address(0));
bool registerInGru = vm.envOr("REGISTER_IN_GRU", uint256(1)) != 0;
string memory tokenName = vm.envString("TOKEN_NAME");
string memory tokenSymbol = vm.envString("TOKEN_SYMBOL");
string memory currencyCode = vm.envString("CURRENCY_CODE");
uint8 tokenDecimals = uint8(vm.envOr("TOKEN_DECIMALS", uint256(6)));
string memory versionTag = vm.envOr("VERSION_TAG", string("2"));
string memory registryName = vm.envOr("REGISTRY_NAME", tokenName);
string memory registrySymbol = vm.envOr("REGISTRY_SYMBOL", tokenSymbol);
string memory tokenURI = vm.envOr("TOKEN_URI", string(""));
string memory disclosureURI = vm.envOr("REGULATORY_DISCLOSURE_URI", string(""));
string memory reportingURI = vm.envOr("REPORTING_URI", string(""));
vm.startBroadcast(pk);
CompliantFiatTokenV2 token = new CompliantFiatTokenV2(
tokenName,
tokenSymbol,
tokenDecimals,
currencyCode,
versionTag,
initialOperator,
admin,
initialSupply,
forwardCanonical
);
if (bytes(tokenURI).length > 0 || bytes(tokenSymbol).length > 0) {
_setPresentationMetadata(address(token), tokenURI, tokenSymbol);
}
if (governanceController != address(0)) {
_setGovernanceController(address(token), governanceController);
}
if (bytes(disclosureURI).length > 0 || bytes(reportingURI).length > 0) {
_setDisclosureMetadata(address(token), disclosureURI, reportingURI);
}
if (registerInGru && registryAddr != address(0)) {
UniversalAssetRegistry(registryAddr).registerGRUCompliantAsset(
address(token),
registryName,
registrySymbol,
tokenDecimals,
"International"
);
}
console.log("generic_cstar_v2", address(token));
console.log("token_symbol", tokenSymbol);
console.log("currency_code", currencyCode);
vm.stopBroadcast();
}
function _setPresentationMetadata(address token, string memory tokenURI, string memory symbolDisplay) internal {
(bool ok,) = token.call(
abi.encodeWithSignature(
"emergencySetPresentationMetadata(bool,string,string)",
true,
tokenURI,
symbolDisplay
)
);
require(ok, "emergencySetPresentationMetadata failed");
}
function _setGovernanceController(address token, address governanceController) internal {
(bool ok,) = token.call(
abi.encodeWithSignature("setGovernanceController(address)", governanceController)
);
require(ok, "setGovernanceController failed");
}
function _setDisclosureMetadata(address token, string memory disclosureURI, string memory reportingURI) internal {
(bool ok,) = token.call(
abi.encodeWithSignature(
"emergencySetDisclosureMetadata(string,string)",
disclosureURI,
reportingURI
)
);
require(ok, "emergencySetDisclosureMetadata failed");
}
}

View File

@@ -0,0 +1,53 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import {CREATE2Factory} from "../../contracts/utils/CREATE2Factory.sol";
import {CompliantFiatToken} from "../../contracts/tokens/CompliantFiatToken.sol";
/**
* @title DeployCAUSDT
* @notice Deterministically deploy the Chain 138 cAUSDT contract via CREATE2.
*
* Env:
* PRIVATE_KEY (required)
* CREATE2_FACTORY_ADDRESS (required; CREATE2_FACTORY accepted as fallback)
* OWNER / ADMIN (optional; default deployer)
* INITIAL_SUPPLY_CAUSDT (optional; defaults to 1_000_000e6)
*/
contract DeployCAUSDT is Script {
uint8 constant DECIMALS = 6;
string constant SYMBOL = "cAUSDT";
string constant NAME = "Alltra USD Token (Compliant)";
string constant CURRENCY_CODE = "USD";
function run() external returns (address deployed) {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address owner = vm.envOr("OWNER", deployer);
address admin = vm.envOr("ADMIN", deployer);
uint256 initialSupply = vm.envOr("INITIAL_SUPPLY_CAUSDT", uint256(1_000_000 * 10**6));
address factoryAddr = vm.envOr("CREATE2_FACTORY_ADDRESS", vm.envAddress("CREATE2_FACTORY"));
require(factoryAddr != address(0), "CREATE2 factory required");
CREATE2Factory factory = CREATE2Factory(factoryAddr);
uint256 salt = uint256(keccak256(abi.encodePacked("CompliantFiatToken.", SYMBOL)));
bytes memory bytecode = abi.encodePacked(
type(CompliantFiatToken).creationCode,
abi.encode(NAME, SYMBOL, uint8(DECIMALS), CURRENCY_CODE, owner, admin, initialSupply)
);
deployed = factory.computeAddress(bytecode, salt);
if (deployed.code.length > 0) {
console.log("cAUSDT already deployed", deployed);
return deployed;
}
vm.startBroadcast(pk);
deployed = factory.deploy(bytecode, salt);
vm.stopBroadcast();
console.log("cAUSDT", deployed);
}
}

View File

@@ -12,7 +12,10 @@ import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToke
* Env:
* PRIVATE_KEY (required)
* CW_BRIDGE_ADDRESS (required) — address that can mint/burn (e.g. CCIP receiver or custom bridge)
* DEPLOY_CWUSDT=1, DEPLOY_CWUSDC=1, DEPLOY_CWEURC=1, ... (default all 1; set 0 to skip a token)
* CW_STRICT_MODE=1 (optional) — revoke deployer MINTER/BURNER after bridge grant
* CW_GOVERNANCE_ADMIN=0x... (optional) — grant DEFAULT_ADMIN_ROLE to governance; if strict, revoke deployer admin when governance is set
* CW_FREEZE_OPERATIONAL_ROLES=1 (optional) — freeze future MINTER/BURNER changes after setup
* DEPLOY_CWUSDT=1, DEPLOY_CWUSDC=1, DEPLOY_CWUSDW=1, DEPLOY_CWEURC=1, ... (default all 1; set 0 to skip a token)
*/
contract DeployCWTokens is Script {
uint8 constant DECIMALS = 6;
@@ -21,22 +24,27 @@ contract DeployCWTokens is Script {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address bridge = vm.envAddress("CW_BRIDGE_ADDRESS");
bool strictMode = vm.envOr("CW_STRICT_MODE", uint256(0)) == 1;
bool freezeOperationalRoles = vm.envOr("CW_FREEZE_OPERATIONAL_ROLES", uint256(0)) == 1;
address governanceAdmin = vm.envOr("CW_GOVERNANCE_ADMIN", address(0));
require(bridge != address(0), "CW_BRIDGE_ADDRESS required");
vm.startBroadcast(pk);
_deployOne(deployer, "Wrapped cUSDT", "cWUSDT", "DEPLOY_CWUSDT", bridge);
_deployOne(deployer, "Wrapped cUSDC", "cWUSDC", "DEPLOY_CWUSDC", bridge);
_deployOne(deployer, "Wrapped cEURC", "cWEURC", "DEPLOY_CWEURC", bridge);
_deployOne(deployer, "Wrapped cEURT", "cWEURT", "DEPLOY_CWEURT", bridge);
_deployOne(deployer, "Wrapped cGBPC", "cWGBPC", "DEPLOY_CWGBPC", bridge);
_deployOne(deployer, "Wrapped cGBPT", "cWGBPT", "DEPLOY_CWGBPT", bridge);
_deployOne(deployer, "Wrapped cAUDC", "cWAUDC", "DEPLOY_CWAUDC", bridge);
_deployOne(deployer, "Wrapped cJPYC", "cWJPYC", "DEPLOY_CWJPYC", bridge);
_deployOne(deployer, "Wrapped cCHFC", "cWCHFC", "DEPLOY_CWCHFC", bridge);
_deployOne(deployer, "Wrapped cCADC", "cWCADC", "DEPLOY_CWCADC", bridge);
_deployOne(deployer, "Wrapped cXAUC", "cWXAUC", "DEPLOY_CWXAUC", bridge);
_deployOne(deployer, "Wrapped cXAUT", "cWXAUT", "DEPLOY_CWXAUT", bridge);
_deployOne(deployer, "Wrapped cUSDT", "cWUSDT", "DEPLOY_CWUSDT", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cUSDC", "cWUSDC", "DEPLOY_CWUSDC", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cAUSDT", "cWAUSDT", "DEPLOY_CWAUSDT", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cUSDW", "cWUSDW", "DEPLOY_CWUSDW", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cEURC", "cWEURC", "DEPLOY_CWEURC", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cEURT", "cWEURT", "DEPLOY_CWEURT", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cGBPC", "cWGBPC", "DEPLOY_CWGBPC", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cGBPT", "cWGBPT", "DEPLOY_CWGBPT", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cAUDC", "cWAUDC", "DEPLOY_CWAUDC", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cJPYC", "cWJPYC", "DEPLOY_CWJPYC", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cCHFC", "cWCHFC", "DEPLOY_CWCHFC", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cCADC", "cWCADC", "DEPLOY_CWCADC", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cXAUC", "cWXAUC", "DEPLOY_CWXAUC", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
_deployOne(deployer, "Wrapped cXAUT", "cWXAUT", "DEPLOY_CWXAUT", bridge, strictMode, governanceAdmin, freezeOperationalRoles);
vm.stopBroadcast();
}
@@ -46,12 +54,40 @@ contract DeployCWTokens is Script {
string memory name,
string memory symbol,
string memory envKey,
address bridge
address bridge,
bool strictMode,
address governanceAdmin,
bool freezeOperationalRoles
) internal {
if (vm.envOr(envKey, uint256(1)) == 0) return;
CompliantWrappedToken t = new CompliantWrappedToken(name, symbol, DECIMALS, admin);
t.grantRole(t.MINTER_ROLE(), bridge);
t.grantRole(t.BURNER_ROLE(), bridge);
if (strictMode) {
t.revokeRole(t.MINTER_ROLE(), admin);
t.revokeRole(t.BURNER_ROLE(), admin);
}
if (governanceAdmin != address(0) && governanceAdmin != admin) {
t.grantRole(t.DEFAULT_ADMIN_ROLE(), governanceAdmin);
}
if (freezeOperationalRoles) {
t.freezeOperationalRoles();
}
if (strictMode && governanceAdmin != address(0) && governanceAdmin != admin) {
t.revokeRole(t.DEFAULT_ADMIN_ROLE(), admin);
}
console.log(symbol, address(t));
console.log(" strictMode", strictMode);
console.log(" governanceAdmin", governanceAdmin);
console.log(" operationalRolesFrozen", t.operationalRolesFrozen());
console.log(" deployerHasMinter", t.hasRole(t.MINTER_ROLE(), admin));
console.log(" deployerHasBurner", t.hasRole(t.BURNER_ROLE(), admin));
console.log(" bridgeHasMinter", t.hasRole(t.MINTER_ROLE(), bridge));
console.log(" bridgeHasBurner", t.hasRole(t.BURNER_ROLE(), bridge));
}
}

View File

@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {CompliantUSDTTokenV2} from "../../contracts/tokens/CompliantUSDTTokenV2.sol";
import {CompliantUSDCTokenV2} from "../../contracts/tokens/CompliantUSDCTokenV2.sol";
/**
* @title DeployCompliantFiatTokensV2ForChain
* @notice Deploy canonical cUSDT V2 / cUSDC V2 contracts to the current chain.
* @dev Defaults to safe pre-cutover posture: new addresses with forwardCanonical disabled unless env overrides it.
*
* Env:
* PRIVATE_KEY (required)
* INITIAL_OPERATOR (optional; default deployer)
* ADMIN (optional; default deployer)
* OWNER (optional alias for ADMIN when ADMIN unset)
* INITIAL_SUPPLY (optional; default 1_000_000e6)
* FORWARD_CANONICAL=1 to mark deployed V2 as forward canonical immediately
* DEPLOY_CUSDT_V2=1 / DEPLOY_CUSDC_V2=1 (default both 1)
*/
contract DeployCompliantFiatTokensV2ForChain is Script {
uint256 internal constant DEFAULT_INITIAL_SUPPLY = 1_000_000 * 10 ** 6;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address initialOperator = vm.envOr("INITIAL_OPERATOR", deployer);
address ownerAlias = vm.envOr("OWNER", deployer);
address admin = vm.envOr("ADMIN", ownerAlias);
uint256 initialSupply = vm.envOr("INITIAL_SUPPLY", DEFAULT_INITIAL_SUPPLY);
bool forwardCanonical = vm.envOr("FORWARD_CANONICAL", false);
vm.startBroadcast(pk);
if (vm.envOr("DEPLOY_CUSDT_V2", uint256(1)) != 0) {
CompliantUSDTTokenV2 cusdtV2 =
new CompliantUSDTTokenV2(initialOperator, admin, initialSupply, forwardCanonical);
console.log("cUSDT_V2", address(cusdtV2));
console.log("cUSDT_V2_admin", admin);
console.log("cUSDT_V2_initialOperator", initialOperator);
console.log("cUSDT_V2_forwardCanonical", forwardCanonical);
}
if (vm.envOr("DEPLOY_CUSDC_V2", uint256(1)) != 0) {
CompliantUSDCTokenV2 cusdcV2 =
new CompliantUSDCTokenV2(initialOperator, admin, initialSupply, forwardCanonical);
console.log("cUSDC_V2", address(cusdcV2));
console.log("cUSDC_V2_admin", admin);
console.log("cUSDC_V2_initialOperator", initialOperator);
console.log("cUSDC_V2_forwardCanonical", forwardCanonical);
}
vm.stopBroadcast();
}
}

View File

@@ -0,0 +1,77 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {UniversalCCIPFlashBridgeAdapter} from "../../contracts/flash/UniversalCCIPFlashBridgeAdapter.sol";
import {CrossChainFlashRepayReceiver} from "../../contracts/flash/CrossChainFlashRepayReceiver.sol";
import {CrossChainFlashVaultCreditReceiver} from "../../contracts/flash/CrossChainFlashVaultCreditReceiver.sol";
/**
* @title DeployCrossChainFlashInfrastructure
* @notice Deploys the Chain 138 cross-chain flash adapter plus both CCIP receivers.
*
* Env:
* PRIVATE_KEY required
* FLASH_UNIVERSAL_CCIP_BRIDGE optional; fallback UNIVERSAL_CCIP_BRIDGE
* FLASH_CCIP_ROUTER optional default router for both receivers
* FLASH_REPAY_RECEIVER_ROUTER optional; fallback FLASH_CCIP_ROUTER / CCIP_ROUTER*
* FLASH_VAULT_CREDIT_ROUTER optional; fallback FLASH_CCIP_ROUTER / CCIP_ROUTER*
* CCIP_ROUTER optional fallback
* CCIP_ROUTER_ADDRESS optional fallback
* CCIP_ROUTER_CHAIN138 optional fallback
*
* Usage:
* forge script script/deploy/DeployCrossChainFlashInfrastructure.s.sol:DeployCrossChainFlashInfrastructure \
* --rpc-url $RPC_URL_138 --broadcast --with-gas-price 1000000000 -vvvv
*/
contract DeployCrossChainFlashInfrastructure is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address universalBridge = vm.envOr("FLASH_UNIVERSAL_CCIP_BRIDGE", address(0));
if (universalBridge == address(0)) {
universalBridge = vm.envOr("UNIVERSAL_CCIP_BRIDGE", address(0));
}
require(universalBridge != address(0), "FLASH_UNIVERSAL_CCIP_BRIDGE or UNIVERSAL_CCIP_BRIDGE not set");
address router = vm.envOr("FLASH_CCIP_ROUTER", address(0));
if (router == address(0)) {
router = vm.envOr("CCIP_ROUTER", address(0));
}
if (router == address(0)) {
router = vm.envOr("CCIP_ROUTER_ADDRESS", address(0));
}
if (router == address(0)) {
router = vm.envOr("CCIP_ROUTER_CHAIN138", address(0));
}
require(router != address(0), "FLASH_CCIP_ROUTER or CCIP_ROUTER* not set");
address repayReceiverRouter = vm.envOr("FLASH_REPAY_RECEIVER_ROUTER", router);
address vaultCreditRouter = vm.envOr("FLASH_VAULT_CREDIT_ROUTER", router);
console.log("Deployer:", deployer);
console.log("UniversalCCIPBridge:", universalBridge);
console.log("Repay receiver router:", repayReceiverRouter);
console.log("Vault credit receiver router:", vaultCreditRouter);
vm.startBroadcast(pk);
UniversalCCIPFlashBridgeAdapter adapter = new UniversalCCIPFlashBridgeAdapter(universalBridge);
CrossChainFlashRepayReceiver repayReceiver = new CrossChainFlashRepayReceiver(repayReceiverRouter);
CrossChainFlashVaultCreditReceiver vaultCreditReceiver =
new CrossChainFlashVaultCreditReceiver(vaultCreditRouter);
vm.stopBroadcast();
console.log("UniversalCCIPFlashBridgeAdapter:", address(adapter));
console.log("CrossChainFlashRepayReceiver:", address(repayReceiver));
console.log("CrossChainFlashVaultCreditReceiver:", address(vaultCreditReceiver));
console.log("Export: CROSS_CHAIN_FLASH_BRIDGE_ADAPTER=%s", vm.toString(address(adapter)));
console.log("Export: CROSS_CHAIN_FLASH_REPAY_RECEIVER=%s", vm.toString(address(repayReceiver)));
console.log(
"Export: CROSS_CHAIN_FLASH_VAULT_CREDIT_RECEIVER=%s",
vm.toString(address(vaultCreditReceiver))
);
}
}

View File

@@ -0,0 +1,85 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import {CompliantFiatToken} from "../../contracts/tokens/CompliantFiatToken.sol";
/**
* @title DeployGasCanonicalTokens
* @notice Deploy Wave 1 gas-native canonical c* tokens on Chain 138.
*
* Env:
* PRIVATE_KEY (required)
* GAS_FAMILY (optional) - deploy one family only: eth_mainnet, eth_l2, bnb, pol, avax, cro, xdai, celo, wemix
* GAS_INITIAL_OWNER (optional, defaults to deployer)
* GAS_ADMIN (optional, defaults to deployer)
* GAS_INITIAL_SUPPLY (optional, defaults to 0)
* DEPLOY_GAS_<FAMILY>=0 to skip a family when GAS_FAMILY is unset
*/
contract DeployGasCanonicalTokens is Script {
uint8 internal constant DECIMALS = 18;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address owner = vm.envOr("GAS_INITIAL_OWNER", deployer);
address admin = vm.envOr("GAS_ADMIN", deployer);
uint256 initialSupply = vm.envOr("GAS_INITIAL_SUPPLY", uint256(0));
string memory targetFamily = vm.envOr("GAS_FAMILY", string(""));
vm.startBroadcast(pk);
_deployOne(owner, admin, initialSupply, targetFamily, "eth_mainnet", "DEPLOY_GAS_ETH_MAINNET", "Ethereum Mainnet Gas (Compliant)", "cETH", "ETH");
_deployOne(owner, admin, initialSupply, targetFamily, "eth_l2", "DEPLOY_GAS_ETH_L2", "Ethereum L2 Gas (Compliant)", "cETHL2", "ETH");
_deployOne(owner, admin, initialSupply, targetFamily, "bnb", "DEPLOY_GAS_BNB", "BNB Gas (Compliant)", "cBNB", "BNB");
_deployOne(owner, admin, initialSupply, targetFamily, "pol", "DEPLOY_GAS_POL", "Polygon Gas (Compliant)", "cPOL", "POL");
_deployOne(owner, admin, initialSupply, targetFamily, "avax", "DEPLOY_GAS_AVAX", "Avalanche Gas (Compliant)", "cAVAX", "AVAX");
_deployOne(owner, admin, initialSupply, targetFamily, "cro", "DEPLOY_GAS_CRO", "Cronos Gas (Compliant)", "cCRO", "CRO");
_deployOne(owner, admin, initialSupply, targetFamily, "xdai", "DEPLOY_GAS_XDAI", "Gnosis Gas (Compliant)", "cXDAI", "XDAI");
_deployOne(owner, admin, initialSupply, targetFamily, "celo", "DEPLOY_GAS_CELO", "Celo Gas (Compliant)", "cCELO", "CELO");
_deployOne(owner, admin, initialSupply, targetFamily, "wemix", "DEPLOY_GAS_WEMIX", "Wemix Gas (Compliant)", "cWEMIX", "WEMIX");
vm.stopBroadcast();
}
function _deployOne(
address owner,
address admin,
uint256 initialSupply,
string memory targetFamily,
string memory familyKey,
string memory envFlag,
string memory name,
string memory symbol,
string memory currencyCode
) internal {
if (!_shouldDeploy(targetFamily, familyKey, envFlag)) return;
CompliantFiatToken token = new CompliantFiatToken(
name,
symbol,
DECIMALS,
currencyCode,
owner,
admin,
initialSupply
);
console.log(symbol, address(token));
console.log(" familyKey", familyKey);
console.log(" owner", owner);
console.log(" admin", admin);
console.log(" initialSupply", initialSupply);
}
function _shouldDeploy(
string memory targetFamily,
string memory familyKey,
string memory envFlag
) internal view returns (bool) {
if (bytes(targetFamily).length != 0) {
return keccak256(bytes(targetFamily)) == keccak256(bytes(familyKey));
}
return vm.envOr(envFlag, uint256(1)) != 0;
}
}

View File

@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {QuotePushFlashWorkflowBorrower} from "../../contracts/flash/QuotePushFlashWorkflowBorrower.sol";
/**
* @title DeployQuotePushFlashWorkflowBorrower
* @notice Deploy the ERC-3156 quote-push borrower against a trusted flash lender.
*
* Env:
* PRIVATE_KEY required
* QUOTE_PUSH_FLASH_LENDER required
*
* Usage:
* forge script script/deploy/DeployQuotePushFlashWorkflowBorrower.s.sol:DeployQuotePushFlashWorkflowBorrower \
* --rpc-url <RPC> --broadcast -vvvv
*/
contract DeployQuotePushFlashWorkflowBorrower is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address lender = vm.envAddress("QUOTE_PUSH_FLASH_LENDER");
address deployer = vm.addr(pk);
console.log("Deployer:", deployer);
console.log("Trusted lender:", lender);
vm.startBroadcast(pk);
QuotePushFlashWorkflowBorrower borrower = new QuotePushFlashWorkflowBorrower(lender);
vm.stopBroadcast();
console.log("QuotePushFlashWorkflowBorrower:", address(borrower));
}
}

View File

@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SimpleERC3156FlashVault} from "../../contracts/flash/SimpleERC3156FlashVault.sol";
/**
* @title DeploySimpleERC3156FlashVault
* @notice Deploy ERC-3156 flash vault for Chain 138; optional USDT whitelist + seed transfer from deployer.
*
* Env (broadcast):
* PRIVATE_KEY — required
* FLASH_VAULT_OWNER — optional; default: deployer
* FLASH_VAULT_FEE_BPS — optional; default: 5 (0.05%)
* FLASH_VAULT_TOKEN — optional; token to whitelist (default: official USDT Chain 138)
* FLASH_VAULT_SEED_AMOUNT — optional; raw token units to transfer from deployer into vault after deploy (0 = skip)
*
* Usage:
* forge script script/deploy/DeploySimpleERC3156FlashVault.s.sol:DeploySimpleERC3156FlashVault \
* --rpc-url $RPC_URL_138 --broadcast -vvvv
*/
contract DeploySimpleERC3156FlashVault is Script {
/// @dev Canonical official USDT (Chain 138) per project config / explorer.
address internal constant DEFAULT_USDT_138 = 0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address vaultOwner = vm.envOr("FLASH_VAULT_OWNER", deployer);
uint256 feeBps = vm.envOr("FLASH_VAULT_FEE_BPS", uint256(5));
address token = vm.envOr("FLASH_VAULT_TOKEN", DEFAULT_USDT_138);
uint256 seedAmount = vm.envOr("FLASH_VAULT_SEED_AMOUNT", uint256(0));
console.log("Deployer:", deployer);
console.log("Vault owner:", vaultOwner);
console.log("feeBps:", feeBps);
console.log("Whitelist token:", token);
vm.startBroadcast(pk);
SimpleERC3156FlashVault vault = new SimpleERC3156FlashVault(vaultOwner, feeBps);
console.log("SimpleERC3156FlashVault:", address(vault));
if (vaultOwner == deployer) {
vault.setTokenSupported(token, true);
console.log("setTokenSupported: true");
if (seedAmount > 0) {
IERC20(token).transfer(address(vault), seedAmount);
console.log("Seeded vault (raw units):", seedAmount);
console.log("Vault balance:", IERC20(token).balanceOf(address(vault)));
}
} else {
console.log("Owner != deployer: owner must call setTokenSupported + seed separately.");
}
vm.stopBroadcast();
}
}

View File

@@ -0,0 +1,44 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToken.sol";
/**
* @title DeploySingleCWToken
* @notice Deploy exactly one CompliantWrappedToken and grant MINTER/BURNER to the bridge.
*
* Env:
* PRIVATE_KEY (required)
* CW_BRIDGE_ADDRESS (required)
* CW_TOKEN_NAME (required)
* CW_TOKEN_SYMBOL (required)
* CW_TOKEN_DECIMALS (optional, default 6)
*/
contract DeploySingleCWToken is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address admin = vm.addr(pk);
address bridge = vm.envAddress("CW_BRIDGE_ADDRESS");
string memory tokenName = vm.envString("CW_TOKEN_NAME");
string memory tokenSymbol = vm.envString("CW_TOKEN_SYMBOL");
uint8 decimals_ = uint8(vm.envOr("CW_TOKEN_DECIMALS", uint256(6)));
require(bridge != address(0), "CW_BRIDGE_ADDRESS required");
require(bytes(tokenName).length != 0, "CW_TOKEN_NAME required");
require(bytes(tokenSymbol).length != 0, "CW_TOKEN_SYMBOL required");
vm.startBroadcast(pk);
CompliantWrappedToken token = new CompliantWrappedToken(tokenName, tokenSymbol, decimals_, admin);
token.grantRole(token.MINTER_ROLE(), bridge);
token.grantRole(token.BURNER_ROLE(), bridge);
vm.stopBroadcast();
console.log(tokenSymbol, address(token));
console.log(" bridge", bridge);
console.log(" bridgeHasMinter", token.hasRole(token.MINTER_ROLE(), bridge));
console.log(" bridgeHasBurner", token.hasRole(token.BURNER_ROLE(), bridge));
}
}

View File

@@ -0,0 +1,73 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {CompliantWrappedToken} from "../../contracts/tokens/CompliantWrappedToken.sol";
import {USDWPublicWrapVault} from "../../contracts/bridge/integration/USDWPublicWrapVault.sol";
/**
* @title DeployUSDWPublicWrapVault
* @notice Deploy the native USDW <-> cWUSDW wrap vault for a public chain.
* @dev Use with an existing cWUSDW deployment on BSC or a newly deployed cWUSDW on Polygon.
*
* Env:
* PRIVATE_KEY (required)
* USDW_NATIVE_ADDRESS (required) // e.g. dwinUsdWinPublic.chains.56.usdwCurrent
* CWUSDW_ADDRESS (required) // cWUSDW contract on the current public chain
* USDW_WRAP_ADMIN (optional) // additional admin to grant after deployment
* USDW_WRAP_OPERATOR (optional) // reserve seeding operator; default admin/deployer
* USDW_WRAP_EMERGENCY_ADMIN (optional)
* USDW_WRAP_GRANT_TOKEN_ROLES=1 // grant MINTER_ROLE and BURNER_ROLE on cWUSDW to the vault
* USDW_WRAP_STRICT_ADMIN=1 // revoke deployer DEFAULT_ADMIN_ROLE after additional grants
*/
contract DeployUSDWPublicWrapVault is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address nativeUsdw = vm.envAddress("USDW_NATIVE_ADDRESS");
address wrappedUsdw = vm.envAddress("CWUSDW_ADDRESS");
address admin = vm.envOr("USDW_WRAP_ADMIN", deployer);
address reserveOperator = vm.envOr("USDW_WRAP_OPERATOR", admin);
address emergencyAdmin = vm.envOr("USDW_WRAP_EMERGENCY_ADMIN", admin);
bool grantTokenRoles = vm.envOr("USDW_WRAP_GRANT_TOKEN_ROLES", uint256(0)) == 1;
bool strictAdmin = vm.envOr("USDW_WRAP_STRICT_ADMIN", uint256(0)) == 1;
vm.startBroadcast(pk);
USDWPublicWrapVault vault = new USDWPublicWrapVault(deployer, nativeUsdw, wrappedUsdw);
if (admin != deployer) {
vault.grantRole(vault.DEFAULT_ADMIN_ROLE(), admin);
vault.grantRole(vault.RESERVE_OPERATOR_ROLE(), admin);
vault.grantRole(vault.EMERGENCY_ADMIN_ROLE(), admin);
}
if (reserveOperator != admin && reserveOperator != deployer) {
vault.grantRole(vault.RESERVE_OPERATOR_ROLE(), reserveOperator);
}
if (emergencyAdmin != admin && emergencyAdmin != deployer) {
vault.grantRole(vault.EMERGENCY_ADMIN_ROLE(), emergencyAdmin);
}
if (grantTokenRoles) {
CompliantWrappedToken token = CompliantWrappedToken(wrappedUsdw);
token.grantRole(token.MINTER_ROLE(), address(vault));
token.grantRole(token.BURNER_ROLE(), address(vault));
}
if (strictAdmin && admin != deployer) {
vault.revokeRole(vault.DEFAULT_ADMIN_ROLE(), deployer);
}
console.log("USDWPublicWrapVault", address(vault));
console.log(" nativeUsdw", nativeUsdw);
console.log(" wrappedUsdw", wrappedUsdw);
console.log(" deployer", deployer);
console.log(" admin", admin);
console.log(" reserveOperator", reserveOperator);
console.log(" emergencyAdmin", emergencyAdmin);
console.log(" grantTokenRoles", grantTokenRoles);
console.log(" strictAdmin", strictAdmin);
vm.stopBroadcast();
}
}

View File

@@ -18,6 +18,7 @@ contract RegisterGRUCompliantTokens is Script {
_register(registry, vm.envOr("CUSDT_ADDRESS_138", address(0)), "Tether USD (Compliant)", "cUSDT");
_register(registry, vm.envOr("CUSDC_ADDRESS_138", address(0)), "USD Coin (Compliant)", "cUSDC");
_register(registry, vm.envOr("CAUSDT_ADDRESS_138", address(0)), "Alltra USD Token (Compliant)", "cAUSDT");
_register(registry, vm.envOr("CEURC_ADDRESS_138", address(0)), "Euro Coin (Compliant)", "cEURC");
_register(registry, vm.envOr("CEURT_ADDRESS_138", address(0)), "Tether EUR (Compliant)", "cEURT");
_register(registry, vm.envOr("CGBPC_ADDRESS_138", address(0)), "Pound Sterling (Compliant)", "cGBPC");

View File

@@ -0,0 +1,50 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import "forge-std/Script.sol";
import {UniversalAssetRegistry} from "../../contracts/registry/UniversalAssetRegistry.sol";
/**
* @title RegisterGRUCompliantTokensV2
* @notice Stage deployed c* V2 contracts in UniversalAssetRegistry using version-aware symbols.
* @dev Keeps live V1 symbols untouched while allowing indexers/operators to discover V2 addresses.
* Env: UNIVERSAL_ASSET_REGISTRY; optional CUSDT_V2_ADDRESS_138, CUSDC_V2_ADDRESS_138.
*/
contract RegisterGRUCompliantTokensV2 is Script {
function run() external {
address registryAddr = vm.envAddress("UNIVERSAL_ASSET_REGISTRY");
UniversalAssetRegistry registry = UniversalAssetRegistry(registryAddr);
uint256 pk = vm.envUint("PRIVATE_KEY");
vm.startBroadcast(pk);
_register(
registry,
vm.envOr("CUSDT_V2_ADDRESS_138", address(0)),
"Tether USD (Compliant V2)",
"cUSDT.v2"
);
_register(
registry,
vm.envOr("CUSDC_V2_ADDRESS_138", address(0)),
"USD Coin (Compliant V2)",
"cUSDC.v2"
);
vm.stopBroadcast();
}
function _register(
UniversalAssetRegistry registry,
address tokenAddr,
string memory name,
string memory symbol
) internal {
if (tokenAddr == address(0)) return;
if (registry.isAssetActive(tokenAddr)) {
console.log("Skip (already registered):", symbol, vm.toString(tokenAddr));
return;
}
registry.registerGRUCompliantAsset(tokenAddr, name, symbol, 6, "International");
console.log("Registered GRU V2:", symbol, vm.toString(tokenAddr));
}
}

View File

@@ -0,0 +1,56 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Script, console} from "forge-std/Script.sol";
import {DODOPMMIntegration} from "../../contracts/dex/DODOPMMIntegration.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
/**
* @title AddLiquidityCUSDWCUSDCV2PoolChain138
* @notice Add liquidity to the Chain 138 cUSDW / cUSDC_V2 DODO PMM pool.
* @dev Env: PRIVATE_KEY, DODO_PMM_INTEGRATION or DODO_PMM_INTEGRATION_ADDRESS,
* CUSDW_ADDRESS_138, CUSDC_V2_ADDRESS_138, POOL_CUSDWCUSDCV2,
* ADD_LIQUIDITY_CUSDWCUSDCV2_BASE, ADD_LIQUIDITY_CUSDWCUSDCV2_QUOTE.
* Optional fallback amounts:
* - ADD_LIQUIDITY_BASE_AMOUNT
* - ADD_LIQUIDITY_QUOTE_AMOUNT
* Optional: NEXT_NONCE.
*/
contract AddLiquidityCUSDWCUSDCV2PoolChain138 is Script {
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address integrationAddr = vm.envAddress("DODO_PMM_INTEGRATION");
if (integrationAddr == address(0)) {
integrationAddr = vm.envAddress("DODO_PMM_INTEGRATION_ADDRESS");
}
require(integrationAddr != address(0), "DODO_PMM_INTEGRATION not set");
address cusdw = vm.envAddress("CUSDW_ADDRESS_138");
address cusdcV2 = vm.envAddress("CUSDC_V2_ADDRESS_138");
address pool = vm.envAddress("POOL_CUSDWCUSDCV2");
require(cusdw != address(0), "CUSDW_ADDRESS_138 not set");
require(cusdcV2 != address(0), "CUSDC_V2_ADDRESS_138 not set");
require(pool != address(0), "POOL_CUSDWCUSDCV2 not set");
uint256 defaultBase = vm.envOr("ADD_LIQUIDITY_BASE_AMOUNT", uint256(0));
uint256 defaultQuote = vm.envOr("ADD_LIQUIDITY_QUOTE_AMOUNT", uint256(0));
uint256 baseAmount = vm.envOr("ADD_LIQUIDITY_CUSDWCUSDCV2_BASE", defaultBase);
uint256 quoteAmount = vm.envOr("ADD_LIQUIDITY_CUSDWCUSDCV2_QUOTE", defaultQuote);
require(baseAmount > 0, "ADD_LIQUIDITY_CUSDWCUSDCV2_BASE not set");
require(quoteAmount > 0, "ADD_LIQUIDITY_CUSDWCUSDCV2_QUOTE not set");
address deployer = vm.addr(pk);
uint64 nextNonce = uint64(vm.envOr("NEXT_NONCE", uint256(0)));
if (nextNonce > 0) {
vm.setNonce(deployer, nextNonce);
}
DODOPMMIntegration integration = DODOPMMIntegration(integrationAddr);
vm.startBroadcast(pk);
IERC20(cusdw).approve(address(integration), type(uint256).max);
IERC20(cusdcV2).approve(address(integration), type(uint256).max);
integration.addLiquidity(pool, baseAmount, quoteAmount);
console.log("Added liquidity to cUSDW/cUSDC_V2 pool:", pool);
vm.stopBroadcast();
}
}

View File

@@ -10,10 +10,10 @@ import {DODOPMMIntegration} from "../../contracts/dex/DODOPMMIntegration.sol";
* @dev Requires caller to have POOL_MANAGER_ROLE. Run with --broadcast.
*/
contract CreateCUSDCUSDCPool is Script {
uint256 constant LP_FEE_BPS = 3;
uint256 constant INITIAL_PRICE_1E18 = 1e18;
uint256 constant K_50PCT = 0.5e18;
bool constant USE_TWAP = true;
uint256 constant DEFAULT_LP_FEE_BPS = 10;
uint256 constant DEFAULT_INITIAL_PRICE_1E18 = 1e18;
uint256 constant DEFAULT_K_0PCT = 0;
bool constant DEFAULT_USE_TWAP = false;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
@@ -30,12 +30,16 @@ contract CreateCUSDCUSDCPool is Script {
}
DODOPMMIntegration integration = DODOPMMIntegration(integrationAddr);
uint256 lpFeeBps = vm.envOr("DODO_LP_FEE_BPS", DEFAULT_LP_FEE_BPS);
uint256 initialPrice = vm.envOr("DODO_INITIAL_PRICE_1E18", DEFAULT_INITIAL_PRICE_1E18);
uint256 k = vm.envOr("DODO_K_FACTOR_1E18", DEFAULT_K_0PCT);
bool useTwap = vm.envOr("DODO_ENABLE_TWAP", DEFAULT_USE_TWAP);
vm.startBroadcast(pk);
address pool = integration.createCUSDCUSDCPool(
LP_FEE_BPS,
INITIAL_PRICE_1E18,
K_50PCT,
USE_TWAP
lpFeeBps,
initialPrice,
k,
useTwap
);
console.log("cUSDC/USDC pool created at:", pool);
vm.stopBroadcast();

View File

@@ -10,10 +10,10 @@ import {DODOPMMIntegration} from "../../contracts/dex/DODOPMMIntegration.sol";
* @dev Requires caller to have POOL_MANAGER_ROLE. Run with --broadcast.
*/
contract CreateCUSDTCUSDCPool is Script {
uint256 constant LP_FEE_BPS = 3;
uint256 constant INITIAL_PRICE_1E18 = 1e18;
uint256 constant K_50PCT = 0.5e18;
bool constant USE_TWAP = true;
uint256 constant DEFAULT_LP_FEE_BPS = 10;
uint256 constant DEFAULT_INITIAL_PRICE_1E18 = 1e18;
uint256 constant DEFAULT_K_0PCT = 0;
bool constant DEFAULT_USE_TWAP = false;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
@@ -30,12 +30,16 @@ contract CreateCUSDTCUSDCPool is Script {
}
DODOPMMIntegration integration = DODOPMMIntegration(integrationAddr);
uint256 lpFeeBps = vm.envOr("DODO_LP_FEE_BPS", DEFAULT_LP_FEE_BPS);
uint256 initialPrice = vm.envOr("DODO_INITIAL_PRICE_1E18", DEFAULT_INITIAL_PRICE_1E18);
uint256 k = vm.envOr("DODO_K_FACTOR_1E18", DEFAULT_K_0PCT);
bool useTwap = vm.envOr("DODO_ENABLE_TWAP", DEFAULT_USE_TWAP);
vm.startBroadcast(pk);
address pool = integration.createCUSDTCUSDCPool(
LP_FEE_BPS,
INITIAL_PRICE_1E18,
K_50PCT,
USE_TWAP
lpFeeBps,
initialPrice,
k,
useTwap
);
console.log("cUSDT/cUSDC pool created at:", pool);
vm.stopBroadcast();

View File

@@ -10,10 +10,10 @@ import {DODOPMMIntegration} from "../../contracts/dex/DODOPMMIntegration.sol";
* @dev Requires caller to have POOL_MANAGER_ROLE. Run with --broadcast.
*/
contract CreateCUSDTUSDTPool is Script {
uint256 constant LP_FEE_BPS = 3;
uint256 constant INITIAL_PRICE_1E18 = 1e18;
uint256 constant K_50PCT = 0.5e18;
bool constant USE_TWAP = true;
uint256 constant DEFAULT_LP_FEE_BPS = 10;
uint256 constant DEFAULT_INITIAL_PRICE_1E18 = 1e18;
uint256 constant DEFAULT_K_0PCT = 0;
bool constant DEFAULT_USE_TWAP = false;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
@@ -30,12 +30,16 @@ contract CreateCUSDTUSDTPool is Script {
}
DODOPMMIntegration integration = DODOPMMIntegration(integrationAddr);
uint256 lpFeeBps = vm.envOr("DODO_LP_FEE_BPS", DEFAULT_LP_FEE_BPS);
uint256 initialPrice = vm.envOr("DODO_INITIAL_PRICE_1E18", DEFAULT_INITIAL_PRICE_1E18);
uint256 k = vm.envOr("DODO_K_FACTOR_1E18", DEFAULT_K_0PCT);
bool useTwap = vm.envOr("DODO_ENABLE_TWAP", DEFAULT_USE_TWAP);
vm.startBroadcast(pk);
address pool = integration.createCUSDTUSDTPool(
LP_FEE_BPS,
INITIAL_PRICE_1E18,
K_50PCT,
USE_TWAP
lpFeeBps,
initialPrice,
k,
useTwap
);
console.log("cUSDT/USDT pool created at:", pool);
vm.stopBroadcast();

View File

@@ -0,0 +1,63 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import {Script, console} from "forge-std/Script.sol";
import {DODOPMMIntegration} from "../../contracts/dex/DODOPMMIntegration.sol";
/**
* @title CreateCUSDWCUSDCV2Pool
* @notice Create a Chain 138 DODO PMM pool for cUSDW / cUSDC_V2 using the generic createPool path.
* @dev Assumes repo-native D-WIN-aligned cUSDW on Chain 138 and staged cUSDC V2 as the quote leg.
* Env: PRIVATE_KEY, DODO_PMM_INTEGRATION or DODO_PMM_INTEGRATION_ADDRESS,
* CUSDW_ADDRESS_138, CUSDC_V2_ADDRESS_138.
* Optional params:
* - DODO_LP_FEE_BPS (default 3)
* - DODO_INITIAL_PRICE_1E18 (default 1e18)
* - DODO_K_FACTOR_1E18 (default 0.5e18)
* - DODO_ENABLE_TWAP (default true)
* - NEXT_NONCE
*/
contract CreateCUSDWCUSDCV2Pool is Script {
uint256 constant DEFAULT_LP_FEE_BPS = 3;
uint256 constant DEFAULT_INITIAL_PRICE_1E18 = 1e18;
uint256 constant DEFAULT_K_50PCT = 0.5e18;
bool constant DEFAULT_USE_TWAP = true;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address integrationAddr = vm.envAddress("DODO_PMM_INTEGRATION");
if (integrationAddr == address(0)) {
integrationAddr = vm.envAddress("DODO_PMM_INTEGRATION_ADDRESS");
}
require(integrationAddr != address(0), "DODO_PMM_INTEGRATION not set");
address cusdw = vm.envAddress("CUSDW_ADDRESS_138");
address cusdcV2 = vm.envAddress("CUSDC_V2_ADDRESS_138");
require(cusdw != address(0), "CUSDW_ADDRESS_138 not set");
require(cusdcV2 != address(0), "CUSDC_V2_ADDRESS_138 not set");
uint256 lpFeeBps = vm.envOr("DODO_LP_FEE_BPS", DEFAULT_LP_FEE_BPS);
uint256 initialPrice = vm.envOr("DODO_INITIAL_PRICE_1E18", DEFAULT_INITIAL_PRICE_1E18);
uint256 kFactor = vm.envOr("DODO_K_FACTOR_1E18", DEFAULT_K_50PCT);
bool useTwap = vm.envOr("DODO_ENABLE_TWAP", DEFAULT_USE_TWAP);
address deployer = vm.addr(pk);
uint64 nextNonce = uint64(vm.envOr("NEXT_NONCE", uint256(0)));
if (nextNonce > 0) {
vm.setNonce(deployer, nextNonce);
}
DODOPMMIntegration integration = DODOPMMIntegration(integrationAddr);
vm.startBroadcast(pk);
address pool = integration.createPool(
cusdw,
cusdcV2,
lpFeeBps,
initialPrice,
kFactor,
useTwap
);
console.log("cUSDW/cUSDC_V2 pool created at:", pool);
vm.stopBroadcast();
}
}

View File

@@ -0,0 +1,64 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {SimpleERC3156FlashVault} from "../../contracts/flash/SimpleERC3156FlashVault.sol";
import {MinimalERC3156FlashBorrower} from "../../contracts/flash/MinimalERC3156FlashBorrower.sol";
/**
* @title TestOneUSDTFlash
* @notice Live / fork: deploy minimal borrower, pre-fund fee, execute 1 USDT flash against an existing vault.
*
* Env:
* PRIVATE_KEY — deployer (pays gas; seeds fee to borrower)
* FLASH_VAULT — deployed SimpleERC3156FlashVault
* FLASH_VAULT_TOKEN — optional; default official USDT Chain 138
* FLASH_TEST_AMOUNT — optional raw amount (default 1e6 = 1 USDT with 6 decimals)
*
* Usage:
* forge script script/flash/TestOneUSDTFlash.s.sol:TestOneUSDTFlash --rpc-url $RPC_URL_138 --broadcast -vvvv
*/
contract TestOneUSDTFlash is Script {
address internal constant DEFAULT_USDT_138 = 0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address vaultAddr = vm.envAddress("FLASH_VAULT");
address token = vm.envOr("FLASH_VAULT_TOKEN", DEFAULT_USDT_138);
uint256 amount = vm.envOr("FLASH_TEST_AMOUNT", uint256(1_000_000)); // 1 USDT (6 dp)
SimpleERC3156FlashVault vault = SimpleERC3156FlashVault(vaultAddr);
require(!vault.borrowerAllowlistEnabled() || vault.owner() == deployer, "allowlist on: deployer must be vault owner");
uint256 fee = vault.previewFlashFee(token, amount);
uint256 vaultBal = IERC20(token).balanceOf(vaultAddr);
console.log("Deployer:", deployer);
console.log("Vault:", vaultAddr);
console.log("Token:", token);
console.log("Amount (raw):", amount);
console.log("Fee (raw):", fee);
console.log("Vault token balance (raw):", vaultBal);
require(vaultBal >= amount, "vault liquidity < amount");
vm.startBroadcast(pk);
MinimalERC3156FlashBorrower borrower = new MinimalERC3156FlashBorrower(vaultAddr);
console.log("MinimalERC3156FlashBorrower:", address(borrower));
if (vault.borrowerAllowlistEnabled()) {
vault.setBorrowerApproved(address(borrower), true);
}
// Callback must return amount+fee; vault only credits `amount` before callback — need `fee` pre-funded on borrower.
IERC20(token).transfer(address(borrower), fee);
vault.flashLoan(IERC3156FlashBorrower(address(borrower)), token, amount, "");
vm.stopBroadcast();
console.log("Done. Vault balance after (raw):", IERC20(token).balanceOf(vaultAddr));
}
}

View File

@@ -0,0 +1,69 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Script, console} from "forge-std/Script.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC3156FlashBorrower} from "@openzeppelin/contracts/interfaces/IERC3156FlashBorrower.sol";
import {SimpleERC3156FlashVault} from "../../contracts/flash/SimpleERC3156FlashVault.sol";
import {MinimalERC3156FlashBorrower} from "../../contracts/flash/MinimalERC3156FlashBorrower.sol";
/**
* @title TestScaledFlash
* @notice Sequential flashes: 1, 10, 100, 1000 USDT (6 decimals) against FLASH_VAULT. One borrower, fee prefunded per step.
*
* Env: PRIVATE_KEY, FLASH_VAULT; optional FLASH_VAULT_TOKEN (default official USDT 138).
*
* If vault.borrowerAllowlistEnabled(), deployer must be vault owner so the script can setBorrowerApproved.
*
* Usage:
* forge script script/flash/TestScaledFlash.s.sol:TestScaledFlash --rpc-url $RPC_URL_138 --broadcast -vvvv
*/
contract TestScaledFlash is Script {
address internal constant DEFAULT_USDT_138 = 0x004b63A7B5b0E06f6bB6adb4a5F9f590BF3182D1;
function run() external {
uint256 pk = vm.envUint("PRIVATE_KEY");
address deployer = vm.addr(pk);
address vaultAddr = vm.envAddress("FLASH_VAULT");
address token = vm.envOr("FLASH_VAULT_TOKEN", DEFAULT_USDT_138);
SimpleERC3156FlashVault vault = SimpleERC3156FlashVault(vaultAddr);
require(!vault.borrowerAllowlistEnabled() || vault.owner() == deployer, "allowlist on: deployer must be vault owner");
uint256[4] memory amounts = [
uint256(1_000_000),
uint256(10_000_000),
uint256(100_000_000),
uint256(1_000_000_000)
];
vm.startBroadcast(pk);
MinimalERC3156FlashBorrower borrower = new MinimalERC3156FlashBorrower(vaultAddr);
console.log("Borrower:", address(borrower));
if (vault.borrowerAllowlistEnabled()) {
vault.setBorrowerApproved(address(borrower), true);
}
for (uint256 i = 0; i < amounts.length; i++) {
uint256 amount = amounts[i];
uint256 fee = vault.previewFlashFee(token, amount);
uint256 vb = IERC20(token).balanceOf(vaultAddr);
require(vb >= amount, "vault liquidity < amount");
console.log("--- step", i + 1);
console.log("amount (raw):", amount);
console.log("fee (raw):", fee);
console.log("vault before (raw):", vb);
IERC20(token).transfer(address(borrower), fee);
vault.flashLoan(IERC3156FlashBorrower(address(borrower)), token, amount, "");
console.log("vault after (raw):", IERC20(token).balanceOf(vaultAddr));
}
vm.stopBroadcast();
console.log("Scaled ladder complete.");
}
}