Files
zeekayandHanzo Dev 1778e53f00 the pin hashes what solc was fed, and names the upstream that was exercised
Settings belong in the preimage because source is not what deploys: turn
the optimizer off and every file stays byte-identical while the bytecode
changes. Hashing what solc recorded about its own inputs catches that, and
catches whatever moved them — a stray FOUNDRY_OPTIMIZER, a foundry.toml
edit, a toolchain default that shifted.

The protocol digest was stale: luxfi/liquid moved after it was written, so
deploy.sh refused, which is the guard doing its job. The new one is
written down having done what the file asks for it — the whole stack
deployed onto a local chain and the markets exercised against it: deposit,
borrow to the ceiling, the borrow past it refused on chain, repay,
withdraw, and every adversarial guard beside them.

Co-authored-by: Hanzo Dev <[email protected]>
2026-08-27 19:56:39 -07:00

88 lines
4.5 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity 0.8.28;
import {Liquid} from "@luxfi/liquid/Liquid.sol";
import {LiquidPosition} from "@luxfi/liquid/LiquidPosition.sol";
import {LiquidTransmuter} from "@luxfi/liquid/LiquidTransmuter.sol";
import {LiquidTokenVault} from "@luxfi/liquid/LiquidTokenVault.sol";
import {LiquidInitializationParams} from "@luxfi/liquid/interfaces/ILiquid.sol";
import {ILiquidTransmuter} from "@luxfi/liquid/interfaces/ILiquidTransmuter.sol";
import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";
/// @title Regent — holds a market's authority only long enough to build it.
/// @notice A market cannot be initialized already finished. Its position NFT and
/// its fee vault both need the market's own address, so they exist only after
/// the market does, and both are set by the admin. Whoever builds the market is
/// therefore its admin for as long as the build takes.
///
/// Handing that admin on afterwards does not end it: the engine and the
/// transmuter both transfer authority by nomination, and the nominee has to
/// accept. A deploy key that nominates a multisig stays admin until the multisig
/// signs, which on a market holding customer collateral is a hot key with the
/// power to swap the price feed.
///
/// So the builder is this contract, and it is built and spent in one
/// transaction. Everything it does happens in its constructor; it declares no
/// other function, so once that transaction returns there is no call that
/// reaches it and no call it can make. The authority it holds is real and
/// inert, and it holds it only until `owner` accepts the nomination it leaves
/// behind. The account that deployed it has none of it at any point.
///
/// Inert is not the same as harmless, and the interval before that acceptance
/// is the one thing here that has to be got right. The market takes deposits as
/// soon as minting is granted, and the only lever that stops a market fast is
/// the guardian pause, which only an admin appoints. So the pause is handed to
/// the owner in this constructor, before the market is reachable at all, rather
/// than left as the first thing the owner does after accepting.
contract Regent {
Liquid public immutable liquid;
LiquidTransmuter public immutable transmuter;
LiquidPosition public immutable position;
LiquidTokenVault public immutable vault;
constructor(
address owner,
address implementation,
LiquidInitializationParams memory params,
ILiquidTransmuter.TransmuterInitializationParams memory transmuterParams
) {
transmuter = new LiquidTransmuter(transmuterParams);
// Filled in here rather than taken from the caller: the market must be
// born under this contract's authority, and pointed at the transmuter
// that came with it, for either to be true at all.
params.admin = address(this);
params.transmuter = address(transmuter);
liquid = Liquid(address(new ERC1967Proxy(implementation, abi.encodeCall(Liquid.initialize, (params)))));
position = new LiquidPosition(address(liquid));
liquid.setLiquidPositionNFT(address(position));
// Liquidations that outrun a position's collateral pay the liquidator
// from this vault, in the underlying — which setLiquidFeeVault checks,
// so the vault holds the synthetic. It is authorized to `owner`, never
// to whoever ran the deploy.
vault = new LiquidTokenVault(params.underlyingToken, address(liquid), owner);
liquid.setLiquidFeeVault(address(vault));
// The transmuter is the other half of the loan: it holds repaid
// collateral and lets synthetic holders redeem against it. Without the
// back reference and a deposit cap it is deployed and inert.
transmuter.setLiquid(address(liquid));
transmuter.setDepositCap(params.depositCap);
// The market is open the moment minting is granted, and it stays open
// for however long the owner takes to accept the nomination below. Only
// an admin can appoint a guardian, and after this constructor returns
// the admin is this contract, which has no functions. So the pause has
// to be handed out here or it cannot be handed out at all until the
// handover completes, and the window is exactly the interval in which
// nobody can stop a market that is already taking deposits.
liquid.setGuardian(owner, true);
liquid.setPendingAdmin(owner);
transmuter.setPendingAdmin(owner);
}
}