mirror of
https://github.com/luxfi/bank.git
synced 2026-10-30 17:02:07 +00:00
The markets could not be built at all — LiquidInitializationParams grew a maxPriceDeviation rate limiter, and SecurityTokenAdapter reports one address for both halves of a pair the engine now checks, which a like-kind market can never satisfy. Index is that pair's price: collateral in its own synthetic, opening at parity, and unable to fall, so an oracle can hand out room but never seize a position. What did deploy left the deploy key holding all of it: unlimited synthetic and collateral mint, arbitrary NAV, halt, and the fee vaults. Ownership now goes to OWNER and the yield index to ORACLE, and each step re-reads the chain and fails the run if the deploy key kept anything. Both handovers are by nomination, so each market is built by a Regent that spends itself in the transaction that creates it rather than by a key that would stay admin until the multisig signs. Nothing mints synthetic outside a market: the fee vaults' float was supply totalSyntheticsIssued never counted, which the transmuter's own arithmetic underflows on. The lib/ symlinks point at working trees, so pins records a hash of every source each step compiles and deploy.sh stops before broadcasting if upstream moved. deploy.json is gone — deploy/<chainId>.json is the one address book, and the two disagreed about which address held LBTC. Co-authored-by: Hanzo Dev <[email protected]>
114 lines
5.5 KiB
Solidity
114 lines
5.5 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.31;
|
|
|
|
import {Script, console} from "forge-std/Script.sol";
|
|
import {WLUX} from "@luxfi/standard/tokens/WLUX.sol";
|
|
import {LRC20B} from "@luxfi/standard/bridge/LRC20B.sol";
|
|
import {BridgedETH} from "@luxfi/standard/bridge/collateral/ETH.sol";
|
|
import {BridgedBTC} from "@luxfi/standard/bridge/collateral/BTC.sol";
|
|
import {LiquidToken} from "@luxfi/standard/liquid/LiquidToken.sol";
|
|
|
|
/// @title Tokens — the asset layer, deployed per chain.
|
|
/// @notice Two tiers, and they are not interchangeable.
|
|
///
|
|
/// Collateral is the bridged tier: LUX is the chain's own coin so it is not
|
|
/// deployed, WLUX is its ERC-20 wrapper, and BridgedETH and BridgedBTC are what
|
|
/// a bridge mints when ETH or BTC arrives. Their symbols are plain "ETH" and
|
|
/// "BTC", and BridgedBTC keeps Bitcoin's 8 decimals rather than padding to 18.
|
|
///
|
|
/// Debt is the liquid tier — LETH, LBTC, LLUX — one per collateral, and this is
|
|
/// where the safety argument lives. A 90% ceiling is only sane when the debt is
|
|
/// denominated in the same asset as the collateral, because then a price move
|
|
/// changes both sides at once and cannot move the ratio between them. Borrow a
|
|
/// dollar-denominated synthetic against volatile collateral at 90% and an
|
|
/// ordinary drawdown takes the position out. So each market gets its own
|
|
/// like-kind synthetic and there is no shared dollar debt token.
|
|
///
|
|
/// The liquid tier is deployed from LiquidToken rather than from the concrete
|
|
/// LiquidETH/LiquidBTC in liquid/tokens/. Those extend LRC20B, which offers
|
|
/// `burn(address,uint256)` and no `burnFrom` — while the protocol repays debt
|
|
/// through `burnFrom(address,uint256)`. LiquidToken, which the standard already
|
|
/// documents as the base for exactly these tokens, implements the surface the
|
|
/// protocol actually calls; the LRC20B-based ones would take deposits and never
|
|
/// let the debt be repaid.
|
|
///
|
|
/// The bridged tier leaves here finished: minting it is a bridge's job, so the
|
|
/// only account that can is `OWNER`, and the float this run creates is the
|
|
/// owner's too. The liquid tier does not — its markets do not exist yet, so it
|
|
/// stays under the deploy key until Grants points each synthetic at its market
|
|
/// and hands it over in turn.
|
|
contract Tokens is Script {
|
|
function run() external {
|
|
uint256 pk = vm.envUint("PRIVATE_KEY");
|
|
address deployer = vm.addr(pk);
|
|
address owner = vm.envAddress("OWNER");
|
|
require(owner != deployer, "owner is the deploy key");
|
|
|
|
// Native coin the treasury wraps, so a market has ERC-20 collateral.
|
|
uint256 wrap = vm.envOr("WRAP_NATIVE", uint256(1_000 ether));
|
|
// Bridged float the treasury holds, standing in for what a bridge would
|
|
// mint. Nothing has actually crossed a bridge, so a chain that means its
|
|
// collateral to be real sets this to zero and waits for the bridge.
|
|
uint256 bridged = vm.envOr("BRIDGE_FLOAT", uint256(1_000 ether));
|
|
|
|
vm.startBroadcast(pk);
|
|
|
|
WLUX wlux = new WLUX();
|
|
if (wrap > 0) {
|
|
wlux.deposit{value: wrap}();
|
|
require(wlux.transfer(owner, wrap), "wrapped LUX did not reach the owner");
|
|
}
|
|
|
|
BridgedETH eth = new BridgedETH();
|
|
if (bridged > 0) eth.mint(owner, bridged);
|
|
_hand(eth, deployer, owner);
|
|
|
|
BridgedBTC btc = new BridgedBTC();
|
|
if (bridged > 0) btc.mint(owner, bridged / 1e10); // 8 decimals, not 18
|
|
_hand(btc, deployer, owner);
|
|
|
|
// Flash fee at the floor the token enforces (1bp). The markets never
|
|
// flash-mint; this only satisfies the constructor's minimum.
|
|
LiquidToken llux = new LiquidToken("Liquid LUX", "LLUX", 1);
|
|
LiquidToken leth = new LiquidToken("Liquid ETH", "LETH", 1);
|
|
LiquidToken lbtc = new LiquidToken("Liquid BTC", "LBTC", 1);
|
|
|
|
vm.stopBroadcast();
|
|
|
|
_audit(eth, deployer, owner);
|
|
_audit(btc, deployer, owner);
|
|
|
|
string memory j = "tokens";
|
|
vm.serializeAddress(j, "WLUX", address(wlux));
|
|
vm.serializeAddress(j, "ETH", address(eth));
|
|
vm.serializeAddress(j, "BTC", address(btc));
|
|
vm.serializeAddress(j, "LLUX", address(llux));
|
|
vm.serializeAddress(j, "LETH", address(leth));
|
|
string memory out = vm.serializeAddress(j, "LBTC", address(lbtc));
|
|
vm.writeJson(out, string.concat("./out/tokens.", vm.toString(block.chainid), ".json"));
|
|
|
|
console.log("chain", block.chainid);
|
|
console.log("WLUX", address(wlux));
|
|
console.log("ETH ", address(eth));
|
|
console.log("BTC ", address(btc));
|
|
}
|
|
|
|
/// Moves every power a bridged token grants its deployer over to `owner`.
|
|
/// Admin goes last: it is what authorizes the rest.
|
|
function _hand(LRC20B token, address deployer, address owner) internal {
|
|
token.grantAdmin(owner);
|
|
token.grantMinter(owner);
|
|
token.transferOwnership(owner);
|
|
token.revokeMinter(deployer);
|
|
token.revokeAdmin(deployer);
|
|
}
|
|
|
|
function _audit(LRC20B token, address deployer, address owner) internal view {
|
|
require(token.owner() == owner, "bridged token is not the owner's");
|
|
require(token.hasRole(token.DEFAULT_ADMIN_ROLE(), owner), "owner cannot administer the bridged token");
|
|
require(token.hasRole(token.MINTER_ROLE(), owner), "owner cannot bridge the token in");
|
|
require(!token.hasRole(token.DEFAULT_ADMIN_ROLE(), deployer), "deploy key can administer the bridged token");
|
|
require(!token.hasRole(token.MINTER_ROLE(), deployer), "deploy key can mint collateral");
|
|
}
|
|
}
|