|
| 1 | +// SPDX-License-Identifier: GPL-2.0-or-later |
| 2 | +pragma solidity =0.7.6; |
| 3 | +pragma abicoder v2; |
| 4 | + |
| 5 | +import 'forge-std/Script.sol'; |
| 6 | +import 'forge-std/console.sol'; |
| 7 | + |
| 8 | +import {ListaV3Factory} from '../src/core/ListaV3Factory.sol'; |
| 9 | +import {NonfungiblePositionManager} from '../src/periphery/NonfungiblePositionManager.sol'; |
| 10 | +import {NonfungibleTokenPositionDescriptor} from '../src/periphery/NonfungibleTokenPositionDescriptor.sol'; |
| 11 | +import {SwapRouter} from '../src/periphery/SwapRouter.sol'; |
| 12 | + |
| 13 | +import {ProxyAdmin} from 'lib/openzeppelin-contracts/contracts/proxy/ProxyAdmin.sol'; |
| 14 | +import {TransparentUpgradeableProxy} from 'lib/openzeppelin-contracts/contracts/proxy/TransparentUpgradeableProxy.sol'; |
| 15 | + |
| 16 | +/// @title Deploy the full Lista V3 stack on BNB Chain mainnet (chainId 56) |
| 17 | +/// @notice Self-contained, chain-pinned counterpart to Deploy.s.sol. Everything BSC-mainnet-specific |
| 18 | +/// is hardcoded here (WBNB, native-currency label) and the run() hard-requires chainid == 56 so a |
| 19 | +/// mis-set RPC can never broadcast mainnet bytecode to the wrong chain. The deploy sequence matches |
| 20 | +/// the audited Deploy.s.sol exactly; only the chain config is fixed. |
| 21 | +/// |
| 22 | +/// The NFT collection name/symbol ("Lista V3 Positions NFT" / "LISTA-V3") are baked into the NPM |
| 23 | +/// implementation's initialize() — see NonfungiblePositionManager.sol — not set here. |
| 24 | +/// |
| 25 | +/// All privileged roles (factory owner / fee-tier admin, and ProxyAdmin owner) default to the |
| 26 | +/// broadcasting deployer EOA. The deployer address is printed before any broadcast so it can be |
| 27 | +/// confirmed against the funded signer. |
| 28 | +/// |
| 29 | +/// Optional env (with defaults): |
| 30 | +/// OWNER = factory owner / fee-tier admin (defaults to the deployer EOA). |
| 31 | +/// PROXY_ADMIN_OWNER = owner of the ProxyAdmin that controls the NPM proxy (defaults to OWNER). |
| 32 | +/// TOKEN_DESCRIPTOR = existing NFT position descriptor to reuse. If unset (0x0), a fresh |
| 33 | +/// NonfungibleTokenPositionDescriptor is deployed against THIS build's |
| 34 | +/// PoolAddress/init-code-hash and wired into the NPM. Only pass an address |
| 35 | +/// that was built from THIS repo — never a foreign fork. |
| 36 | +/// |
| 37 | +/// Usage: |
| 38 | +/// forge script script/DeployBscMainnet.s.sol:DeployBscMainnet \ |
| 39 | +/// --rpc-url $BSC_RPC --broadcast --verify --etherscan-api-key $BSCSCAN_API_KEY --slow |
| 40 | +contract DeployBscMainnet is Script { |
| 41 | + uint256 internal constant BSC_MAINNET_CHAIN_ID = 56; |
| 42 | + /// @dev Canonical WBNB on BNB Chain mainnet. |
| 43 | + address internal constant WBNB = 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c; |
| 44 | + /// @dev Native-currency label shown for WBNB positions in the freshly-deployed descriptor. |
| 45 | + bytes32 internal constant NATIVE_CURRENCY_LABEL = bytes32('BNB'); |
| 46 | + |
| 47 | + struct Deployment { |
| 48 | + address proxyAdmin; |
| 49 | + address factory; |
| 50 | + address tokenDescriptor; |
| 51 | + address npmImpl; |
| 52 | + address npmProxy; |
| 53 | + address swapRouter; |
| 54 | + } |
| 55 | + |
| 56 | + function run() external returns (Deployment memory out) { |
| 57 | + uint256 chainId; |
| 58 | + assembly { |
| 59 | + chainId := chainid() |
| 60 | + } |
| 61 | + require(chainId == BSC_MAINNET_CHAIN_ID, 'not BSC mainnet (expected chainid 56)'); |
| 62 | + |
| 63 | + // The deployer is the broadcasting signer; all privileged roles default to it. |
| 64 | + address deployer = msg.sender; |
| 65 | + address owner = vm.envOr('OWNER', deployer); |
| 66 | + address proxyAdminOwner = vm.envOr('PROXY_ADMIN_OWNER', owner); |
| 67 | + address tokenDescriptor = vm.envOr('TOKEN_DESCRIPTOR', address(0)); |
| 68 | + |
| 69 | + require(owner != address(0), 'OWNER=0'); |
| 70 | + require(proxyAdminOwner != address(0), 'PROXY_ADMIN_OWNER=0'); |
| 71 | + |
| 72 | + bool deployDescriptor = tokenDescriptor == address(0); |
| 73 | + |
| 74 | + console.log('--- Lista V3 deploy (BSC mainnet) ---'); |
| 75 | + console.log('chainId:', chainId); |
| 76 | + console.log('deployer:', deployer); |
| 77 | + console.log('owner:', owner); |
| 78 | + console.log('proxyAdminOwner:', proxyAdminOwner); |
| 79 | + console.log('WBNB:', WBNB); |
| 80 | + console.log('deploy descriptor?:', deployDescriptor); |
| 81 | + if (!deployDescriptor) { |
| 82 | + console.log('tokenDescriptor (reused):', tokenDescriptor); |
| 83 | + } |
| 84 | + |
| 85 | + vm.startBroadcast(); |
| 86 | + |
| 87 | + // ProxyAdmin: owner() is the broadcaster from its constructor; transfer only if a different |
| 88 | + // owner was requested. |
| 89 | + ProxyAdmin proxyAdmin = new ProxyAdmin(); |
| 90 | + if (proxyAdminOwner != proxyAdmin.owner()) { |
| 91 | + proxyAdmin.transferOwnership(proxyAdminOwner); |
| 92 | + } |
| 93 | + |
| 94 | + // Factory: plain deploy. owner = broadcaster; hand off to the requested owner if different. |
| 95 | + ListaV3Factory factory = new ListaV3Factory(); |
| 96 | + if (owner != msg.sender) { |
| 97 | + factory.setOwner(owner); |
| 98 | + } |
| 99 | + |
| 100 | + // Descriptor: fresh deploy only when TOKEN_DESCRIPTOR was not provided. Built from THIS repo's |
| 101 | + // PoolAddress/POOL_INIT_CODE_HASH so it derives pool addresses correctly for this deployment. |
| 102 | + // forge auto-deploys and links the NFTDescriptor library it depends on. |
| 103 | + if (deployDescriptor) { |
| 104 | + NonfungibleTokenPositionDescriptor descriptor = |
| 105 | + new NonfungibleTokenPositionDescriptor(WBNB, NATIVE_CURRENCY_LABEL); |
| 106 | + tokenDescriptor = address(descriptor); |
| 107 | + } |
| 108 | + |
| 109 | + // NPM: impl + proxy. factory/WETH9 are constructor immutables on the impl; every future |
| 110 | + // upgrade MUST re-pass the exact same (factory, WBNB) to the new impl. |
| 111 | + // |
| 112 | + // The impl is created AND its initializer consumed inside NpmImplDeployer's constructor — |
| 113 | + // a single CREATE tx — so there is no separate initialize() tx for a bot to front-run |
| 114 | + // (an earlier non-atomic `new impl(); impl.initialize(0xdead)` was front-run on mainnet). |
| 115 | + address npmImpl = address(new NpmImplDeployer(address(factory), WBNB).impl()); |
| 116 | + |
| 117 | + bytes memory npmInit = abi.encodeWithSelector(NonfungiblePositionManager.initialize.selector, tokenDescriptor); |
| 118 | + TransparentUpgradeableProxy npmProxy = |
| 119 | + new TransparentUpgradeableProxy(npmImpl, address(proxyAdmin), npmInit); |
| 120 | + |
| 121 | + // SwapRouter is not upgradeable; plain deploy against the factory. |
| 122 | + SwapRouter swapRouter = new SwapRouter(address(factory), WBNB); |
| 123 | + |
| 124 | + vm.stopBroadcast(); |
| 125 | + |
| 126 | + out = Deployment({ |
| 127 | + proxyAdmin: address(proxyAdmin), |
| 128 | + factory: address(factory), |
| 129 | + tokenDescriptor: tokenDescriptor, |
| 130 | + npmImpl: npmImpl, |
| 131 | + npmProxy: address(npmProxy), |
| 132 | + swapRouter: address(swapRouter) |
| 133 | + }); |
| 134 | + |
| 135 | + console.log('--- deployed ---'); |
| 136 | + console.log('ProxyAdmin:', out.proxyAdmin); |
| 137 | + console.log('Factory:', out.factory); |
| 138 | + console.log('TokenDescriptor:', out.tokenDescriptor); |
| 139 | + console.log('NPM impl:', out.npmImpl); |
| 140 | + console.log('NPM proxy:', out.npmProxy); |
| 141 | + console.log('SwapRouter:', out.swapRouter); |
| 142 | + } |
| 143 | +} |
| 144 | + |
| 145 | +/// @title Atomic NPM implementation deployer |
| 146 | +/// @notice Deploys a NonfungiblePositionManager implementation and consumes its initializer within |
| 147 | +/// this contract's constructor — i.e. inside a single CREATE transaction. Because the deploy and the |
| 148 | +/// `initialize(0xdead)` happen in the same tx, there is no intervening block in which a bot can call |
| 149 | +/// `initialize()` on the fresh impl. Read `impl()` afterwards for the deployed implementation address. |
| 150 | +contract NpmImplDeployer { |
| 151 | + NonfungiblePositionManager public immutable impl; |
| 152 | + |
| 153 | + constructor(address factory_, address weth9_) { |
| 154 | + NonfungiblePositionManager i = new NonfungiblePositionManager(factory_, weth9_); |
| 155 | + // Close the impl-takeover window atomically; harmless even though NPM has no selfdestruct. |
| 156 | + i.initialize(address(0xdead)); |
| 157 | + impl = i; |
| 158 | + } |
| 159 | +} |
0 commit comments