Skip to main content
PulseScanner.io

Address

0x52e6bee2bfaf72df717fc333998975f05bf4e993
Current Holdings
$467.54
TXs sent
not counted
First Active
2026-03-18
block 26,051,670
Last Active
14 days ago
block 27,424,269
Funded By
not identified

Net worth historyi

31 snapshots · to block 27,505,721coverage change 26 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Augcoverage change 27 Aug
partial matchGemJoinsolc 0.6.12+commit.27d51765runtime partial · creation partial
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2; 

interface VatLike {
    function slip(bytes32,address,int) external;
    function move(address,address,uint256) external;
    function flux(bytes32,address,address,uint256) external;
    function ilks(bytes32) external returns (uint256, uint256, uint256, uint256, uint256);
    function suck(address,address,uint256) external;
    function init(bytes32 ilk) external;
    function file(bytes32 ilk, bytes32 what, uint256 data) external;
    function rely(address usr) external;
}

interface PipLike {
    function peek() external returns (bytes32, bool);
}

interface JugLike {
    function init(bytes32 ilk) external;
    function file(bytes32 ilk, bytes32 what, uint256 data) external;
}

interface SpotterLike {
    function par() external returns (uint256);
    function ilks(bytes32) external returns (PipLike, uint256);
    function file(bytes32 ilk, bytes32 what, address pip) external;
    function file(bytes32 ilk, bytes32 what, uint256 data) external;
    function rely(address usr) external;
    function poke(bytes32 ilk) external;
}

interface DogLike {
    function chop(bytes32) external returns (uint256);
    function digs(bytes32, uint256) external;
    function rely(address usr) external; 
    function file(bytes32 ilk, bytes32 what, uint256 data) external;
    function file(bytes32 ilk, bytes32 what, address clip) external; 
}

interface AbacusLike {
    function price(uint256, uint256) external view returns (uint256);
}

// Interface for the Oracle - UNCHANGED from original
interface ISimpleTWAPOracle {
    function getPriceInUsd(address token, uint amountIn) external view returns (uint amountOutUsd);
}

// Standard ERC20 interface to query decimals
interface IERC20 {
    function decimals() external view returns (uint8);
}

interface GemLike {
    function decimals() external view returns (uint);
    function transfer(address,uint) external returns (bool);
    function transferFrom(address,address,uint) external returns (bool);
}

interface ClipperCallee {
    function clipperCall(address, uint256, uint256, bytes calldata) external;
}

contract PipJoin {
    event PriceUpdated(uint256 price, bool ok);
    event PriceValidation(
        uint256 attemptedWad,
        bool accepted,
        uint256 minBound,
        uint256 maxBound
    );
    event PriceBoundsUpdated(uint256 newMin, uint256 newMax);

    struct PriceData {
        uint256 val; // Price in wad (10**18) - USD per 10**18 raw units
        bool ok;
    }

    ISimpleTWAPOracle public immutable oracle;
    address public immutable token;
    uint8 public immutable dec;

    PriceData public spot;

    address public ward;
    mapping(address => uint256) public bud;

    uint256 public constant WAD = 10 ** 18;
    uint256 public constant INITIAL_PRICE = 1 * WAD;

    // Configurable sanity bounds (wad units, price per 10**18 raw)
    uint256 public minPriceWad;
    uint256 public maxPriceWad;

    constructor(address _oracle, address _token) public {
        oracle = ISimpleTWAPOracle(_oracle);
        token = _token;

        uint8 _dec = IERC20(_token).decimals();
        require(_dec <= 18, "PipJoin/unsupported-decimals-gt-18");
        dec = _dec;

        // Extremely wide defaults: 1 wei to 10**36 wad (covers $1e-18 to $1e18 per whole token)
        minPriceWad = 1;
        maxPriceWad = 10 ** 36;

        ward = msg.sender;
        spot = PriceData(INITIAL_PRICE, false);
        emit PriceUpdated(INITIAL_PRICE, false);
        emit PriceBoundsUpdated(minPriceWad, maxPriceWad);
    }

    modifier auth() {
        require(msg.sender == ward, "PipJoin/not-authorized");
        _;
    }

    modifier kissed() {
        require(bud[msg.sender] == 1, "PipJoin/not-whitelisted");
        _;
    }

    function setWard(address newWard) external auth {
        ward = newWard;
    }

    function kiss(address who) external auth {
        bud[who] = 1;
    }

    function diss(address who) external auth {
        bud[who] = 0;
    }

    function setPriceBounds(uint256 newMin, uint256 newMax) external auth {
        require(newMin > 0 && newMax > newMin, "PipJoin/invalid-bounds");
        minPriceWad = newMin;
        maxPriceWad = newMax;
        emit PriceBoundsUpdated(newMin, newMax);
    }

    function poke() external {
        try this.getPrice() returns (uint256 priceWad, bool success) {
            if (!success && priceWad > 0) {
                emit PriceValidation(priceWad, false, minPriceWad, maxPriceWad);
            }

            if (success) {
                spot = PriceData(priceWad, true);
                emit PriceUpdated(priceWad, true);
            } else {
                spot.ok = false;
                emit PriceUpdated(spot.val, false);
            }
        } catch {
            spot.ok = false;
            emit PriceUpdated(spot.val, false);
        }
    }

    function getPrice() external view returns (uint256 val, bool success) {
        uint256 amountIn = 10 ** uint256(dec); // 1 token (e.g. 10^8 for HEX)

        try oracle.getPriceInUsd(token, amountIn) returns (
            uint256 priceForOneToken
        ) {
            if (priceForOneToken == 0) {
                return (0, false);
            }

            uint256 wad = priceForOneToken;

            bool inRange = (wad >= minPriceWad && wad <= maxPriceWad);

            return (inRange ? wad : 0, inRange);
        } catch {
            return (0, false);
        }
    }

    function peek() external view returns (bytes32, bool) {
        return (bytes32(spot.val), spot.ok);
    }

    function peep() external view kissed returns (bytes32, bool) {
        (uint256 priceWad, bool success) = this.getPrice();
        if (success) {
            return (bytes32(priceWad), true);
        } else {
            return (bytes32(spot.val), false);
        }
    }

    function read() external view returns (uint256) {
        require(spot.ok, "PipJoin/invalid-price");
        return spot.val;
    }
}



contract Clipper {
    mapping (address => uint256) public wards;
    function rely(address usr) external auth { wards[usr] = 1; emit Rely(usr); }
    function deny(address usr) external auth { wards[usr] = 0; emit Deny(usr); }
    modifier auth {
        require(wards[msg.sender] == 1, "Clipper/not-authorized");
        _;
    }

    bytes32  immutable public ilk;
    VatLike  immutable public vat;
    DogLike     public dog;
    address     public vow;
    SpotterLike public spotter;
    AbacusLike  public calc;

    uint256 public buf;
    uint256 public tail;
    uint256 public cusp;
    uint64  public chip;
    uint192 public tip;
    uint256 public chost;

    uint256   public kicks;
    uint256[] public active;

    struct Sale {
        uint256 pos;
        uint256 tab;
        uint256 lot;
        address usr;
        uint96  tic;
        uint256 top;
    }
    mapping(uint256 => Sale) public sales;

    uint256 internal locked;

    uint256 public stopped = 0;

    event Rely(address indexed usr);
    event Deny(address indexed usr);
    event File(bytes32 indexed what, uint256 data);
    event FileAddress(bytes32 indexed what, address data);
    event Kick(
        uint256 indexed id,
        uint256 top,
        uint256 tab,
        uint256 lot,
        address indexed usr,
        address indexed kpr,
        uint256 coin
    );
    event Take(
        uint256 indexed id,
        uint256 max,
        uint256 price,
        uint256 owe,
        uint256 tab,
        uint256 lot,
        address indexed usr
    );
    event Redo(
        uint256 indexed id,
        uint256 top,
        uint256 tab,
        uint256 lot,
        address indexed usr,
        address indexed kpr,
        uint256 coin
    );
    event Yank(uint256 indexed id);
    event Upchost(uint256 chost);

    constructor(address vat_, address spotter_, address dog_, bytes32 ilk_) public {
        vat     = VatLike(vat_);
        spotter = SpotterLike(spotter_);
        dog     = DogLike(dog_);
        ilk     = ilk_;
        buf     = RAY;
        wards[msg.sender] = 1;
        emit Rely(msg.sender);
    }

    modifier lock {
        require(locked == 0, "Clipper/system-locked");
        locked = 1;
        _;
        locked = 0;
    }

    modifier isStopped(uint256 level) {
        require(stopped < level, "Clipper/stopped-incorrect");
        _;
    }

    function file(bytes32 what, uint256 data) external auth lock {
        if      (what == "buf")         buf = data;
        else if (what == "tail")       tail = data;
        else if (what == "cusp")       cusp = data;
        else if (what == "chip")       chip = uint64(data);
        else if (what == "tip")         tip = uint192(data);
        else if (what == "stopped") stopped = data;
        else revert("Clipper/file-unrecognized-param");
        emit File(what, data);
    }
    function file(bytes32 what, address data) external auth lock {
        if (what == "spotter") spotter = SpotterLike(data);
        else if (what == "dog")    dog = DogLike(data);
        else if (what == "vow")    vow = address(data);
        else if (what == "calc")  calc = AbacusLike(data);
        else revert("Clipper/file-unrecognized-param");
        emit FileAddress(what, data);
    }

    uint256 constant BLN = 10 **  9;
    uint256 constant WAD = 10 ** 18;
    uint256 constant RAY = 10 ** 27;

    function min(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = x <= y ? x : y;
    }
    function add(uint256 x, uint256 y) internal pure returns (uint256 z) {
        require((z = x + y) >= x);
    }
    function sub(uint256 x, uint256 y) internal pure returns (uint256 z) {
        require((z = x - y) <= x);
    }
    function mul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        require(y == 0 || (z = x * y) / y == x);
    }
    function wmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = mul(x, y) / WAD;
    }
    function rmul(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = mul(x, y) / RAY;
    }
    function rdiv(uint256 x, uint256 y) internal pure returns (uint256 z) {
        z = mul(x, RAY) / y;
    }

    function getFeedPrice() internal returns (uint256 feedPrice) {
        (PipLike pip, ) = spotter.ilks(ilk);
        (bytes32 val, bool has) = pip.peek();
        require(has, "Clipper/invalid-price");
        feedPrice = rdiv(mul(uint256(val), BLN), spotter.par());
    }

    function kick(
        uint256 tab,
        uint256 lot,
        address usr,
        address kpr
    ) external auth lock isStopped(1) returns (uint256 id) {
        require(tab  >          0, "Clipper/zero-tab");
        require(lot  >          0, "Clipper/zero-lot");
        require(usr != address(0), "Clipper/zero-usr");
        id = ++kicks;
        require(id   >          0, "Clipper/overflow");

        active.push(id);

        sales[id].pos = active.length - 1;

        sales[id].tab = tab;
        sales[id].lot = lot;
        sales[id].usr = usr;
        sales[id].tic = uint96(block.timestamp);

        uint256 top;
        top = rmul(getFeedPrice(), buf);
        require(top > 0, "Clipper/zero-top-price");
        sales[id].top = top;

        uint256 _tip  = tip;
        uint256 _chip = chip;
        uint256 coin;
        if (_tip > 0 || _chip > 0) {
            coin = add(_tip, wmul(tab, _chip));
            vat.suck(vow, kpr, coin);
        }

        emit Kick(id, top, tab, lot, usr, kpr, coin);
    }

    function redo(
        uint256 id,
        address kpr
    ) external lock isStopped(2) {
        address usr = sales[id].usr;
        uint96  tic = sales[id].tic;
        uint256 top = sales[id].top;

        require(usr != address(0), "Clipper/not-running-auction");

        (bool done,) = status(tic, top);
        require(done, "Clipper/cannot-reset");

        uint256 tab   = sales[id].tab;
        uint256 lot   = sales[id].lot;
        sales[id].tic = uint96(block.timestamp);

        uint256 feedPrice = getFeedPrice();
        top = rmul(feedPrice, buf);
        require(top > 0, "Clipper/zero-top-price");
        sales[id].top = top;

        uint256 _tip  = tip;
        uint256 _chip = chip;
        uint256 coin;
        if (_tip > 0 || _chip > 0) {
            uint256 _chost = chost;
            if (tab >= _chost && mul(lot, feedPrice) >= _chost) {
                coin = add(_tip, wmul(tab, _chip));
                vat.suck(vow, kpr, coin);
            }
        }

        emit Redo(id, top, tab, lot, usr, kpr, coin);
    }

    function take(
        uint256 id,
        uint256 amt,
        uint256 max,
        address who,
        bytes calldata data
    ) external lock isStopped(3) {
        address usr = sales[id].usr;
        uint96  tic = sales[id].tic;

        require(usr != address(0), "Clipper/not-running-auction");

        uint256 price;
        {
            bool done;
            (done, price) = status(tic, sales[id].top);
            require(!done, "Clipper/needs-reset");
        }

        require(max >= price, "Clipper/too-expensive");

        uint256 lot = sales[id].lot;
        uint256 tab = sales[id].tab;
        uint256 owe;

        {
            uint256 slice = min(lot, amt);
            owe = mul(slice, price);

            if (owe > tab) {
                owe = tab;
                slice = owe / price;
            } else if (owe < tab && slice < lot) {
                uint256 _chost = chost;
                if (tab - owe < _chost) {
                    require(tab > _chost, "Clipper/no-partial-purchase");
                    owe = tab - _chost;
                    slice = owe / price;
                }
            }

            tab = tab - owe;
            lot = lot - slice;

            vat.flux(ilk, address(this), who, slice);

            DogLike dog_ = dog;
            if (data.length > 0 && who != address(vat) && who != address(dog_)) {
                ClipperCallee(who).clipperCall(msg.sender, owe, slice, data);
            }

            vat.move(msg.sender, vow, owe);
            dog_.digs(ilk, lot == 0 ? tab + owe : owe);
        }

        if (lot == 0) {
            _remove(id);
        } else if (tab == 0) {
            vat.flux(ilk, address(this), usr, lot);
            _remove(id);
        } else {
            sales[id].tab = tab;
            sales[id].lot = lot;
        }

        emit Take(id, max, price, owe, tab, lot, usr);
    }

    function _remove(uint256 id) internal {
        uint256 _move    = active[active.length - 1];
        if (id != _move) {
            uint256 _index   = sales[id].pos;
            active[_index]   = _move;
            sales[_move].pos = _index;
        }
        active.pop();
        delete sales[id];
    }

    function count() external view returns (uint256) {
        return active.length;
    }

    function list() external view returns (uint256[] memory) {
        return active;
    }

    function getStatus(uint256 id) external view returns (bool needsRedo, uint256 price, uint256 lot, uint256 tab) {
        address usr = sales[id].usr;
        uint96  tic = sales[id].tic;

        bool done;
        (done, price) = status(tic, sales[id].top);

        needsRedo = usr != address(0) && done;
        lot = sales[id].lot;
        tab = sales[id].tab;
    }

    function status(uint96 tic, uint256 top) internal view returns (bool done, uint256 price) {
        price = calc.price(top, sub(block.timestamp, tic));
        done  = (sub(block.timestamp, tic) > tail || rdiv(price, top) < cusp);
    }

    function upchost() external {
        (,,,, uint256 _dust) = VatLike(vat).ilks(ilk);
        chost = wmul(_dust, dog.chop(ilk));
        emit Upchost(chost);
    }

    function yank(uint256 id) external auth lock {
        require(sales[id].usr != address(0), "Clipper/not-running-auction");
        dog.digs(ilk, sales[id].tab);
        vat.flux(ilk, address(this), msg.sender, sales[id].lot);
        _remove(id);
        emit Yank(id);
    }
}

contract GemJoin {
    // --- Auth ---
    mapping (address => uint) public wards;
    function rely(address usr) external auth {
        wards[usr] = 1;
        emit Rely(usr);
    }
    function deny(address usr) external auth {
        wards[usr] = 0;
        emit Deny(usr);
    }
    modifier auth {
        require(wards[msg.sender] == 1, "GemJoin/not-authorized");
        _;
    }

    VatLike public vat;   // CDP Engine
    bytes32 public ilk;   // Collateral Type
    GemLike public gem;
    uint    public dec;
    uint    public live;  // Active Flag

    // Events
    event Rely(address indexed usr);
    event Deny(address indexed usr);
    event Join(address indexed usr, uint256 wad);
    event Exit(address indexed usr, uint256 wad);
    event Cage();

    constructor(address vat_, bytes32 ilk_, address gem_) public {
        wards[msg.sender] = 1;
        live = 1;
        vat = VatLike(vat_);
        ilk = ilk_;
        gem = GemLike(gem_);
        dec = gem.decimals();
        require(dec <= 18, "GemJoin/unsupported-decimals-gt-18");
        emit Rely(msg.sender);
    }

    function cage() external auth {
        live = 0;
        emit Cage();
    }

    function join(address usr, uint wad) external {
        require(live == 1, "GemJoin/not-live");
        uint256 scaledWad = wad * (10 ** (18 - dec));  // Scale to 18 decimals
        require(scaledWad / (10 ** (18 - dec)) == wad, "GemJoin/overflow-on-scale");  // Overflow check
        vat.slip(ilk, usr, int(scaledWad));
        require(gem.transferFrom(msg.sender, address(this), wad), "GemJoin/failed-transfer");
        emit Join(usr, wad);
    }

    function exit(address usr, uint wad) external {
        require(live == 1, "GemJoin/not-live");
        uint256 scaledWad = wad * (10 ** (18 - dec));
        require(scaledWad <= 2 ** 255, "GemJoin/overflow");
        vat.slip(ilk, msg.sender, -int(scaledWad));
        require(gem.transfer(usr, wad), "GemJoin/failed-transfer");
        emit Exit(usr, wad);
    }
}

contract DeployNewIlkSpell {

     struct IlkParams {
        address vat;
        address spotter;
        bytes32 ilk;
        address token;
        address oracle;
        uint256 mat;
        uint256 line;
        uint256 dust;   // ADDED: Minimum debt [rad]
        address dog;
        address vow;
        address calc;
        uint256 buf;
        uint256 tail;
        uint256 cusp;
        uint64 chip;
        uint192 tip;
        uint256 hole;   // ADDED: Max liquidation queue [rad]
        uint256 chop;   // ADDED: Liquidation penalty [wad]
        address gem;

        address jug;
        uint256 duty;
    }

    // Core system addresses
    address public immutable vat;
    address public immutable spotter;
    bytes32 public immutable ilk;
    address public immutable token;
    address public immutable oracle;
    
    // Spotter parameters
    uint256 public immutable mat; // Liquidation ratio [ray]
    
    // Vat parameters
    uint256 public immutable line; // Debt ceiling [rad]
    
    // Clipper parameters
    address public immutable dog;
    address public immutable vow;
    address public immutable calc;
    uint256 public immutable buf; // Starting price multiplier [ray]
    uint256 public immutable tail; // Time before auction reset [seconds]
    uint256 public immutable cusp; // Percentage drop before reset [ray]
    uint64 public immutable chip; // Keeper incentive percentage [wad]
    uint192 public immutable tip; // Keeper flat fee [rad]
    
    // GemJoin parameters
    address public immutable gem;

    uint256 public immutable dust;
    uint256 public immutable hole;
    uint256 public immutable chop;

    address public immutable jug;
    uint256 public immutable duty;
    

    // Deployed contract addresses
    address public pip; // PipJoin address
    address public clipper; // Clipper address
    address public gemJoin; // GemJoin address

     uint256 constant WAD = 10 ** 18;
    uint256 constant RAY = 10 ** 27;

    string public description;

    // Events for tracking deployments
    event PipJoinDeployed(address indexed pip, bytes32 indexed ilk);
    event ClipperDeployed(address indexed clipper, bytes32 indexed ilk);
    event GemJoinDeployed(address indexed gemJoin, bytes32 indexed ilk);

    constructor(IlkParams memory params) public {
        // Validate addresses
        require(params.vat != address(0), "DeployNewIlkSpell/invalid-vat");
        require(params.spotter != address(0), "DeployNewIlkSpell/invalid-spotter");
        require(params.token != address(0), "DeployNewIlkSpell/invalid-token");
        require(params.oracle != address(0), "DeployNewIlkSpell/invalid-oracle");
        require(params.dog != address(0), "DeployNewIlkSpell/invalid-dog");
        require(params.vow != address(0), "DeployNewIlkSpell/invalid-vow");
        require(params.calc != address(0), "DeployNewIlkSpell/invalid-calc");
        require(params.gem != address(0), "DeployNewIlkSpell/invalid-gem");

         // Validate numerical parameters
        require(params.mat >= RAY, "DeployNewIlkSpell/invalid-mat");
        require(params.line > 0, "DeployNewIlkSpell/invalid-line");
        require(params.buf >= RAY, "DeployNewIlkSpell/invalid-buf");
        require(params.tail > 0, "DeployNewIlkSpell/invalid-tail");
        require(params.cusp <= RAY, "DeployNewIlkSpell/invalid-cusp");

        require(params.dust > 0, "DeployNewIlkSpell/invalid-dust");
        require(params.hole > 0, "DeployNewIlkSpell/invalid-hole");
        
        // FIX: Chop is WAD in the Dog contract. Validate accordingly.
        // 1.13 * 10^18. Must be >= 1 WAD.
        require(params.chop >= WAD, "DeployNewIlkSpell/invalid-chop");

         require(params.jug != address(0), "DeployNewIlkSpell/invalid-jug");
        require(params.duty > 0, "DeployNewIlkSpell/invalid-duty"); // must be at least ONE

        // Assign to immutable variables
        vat = params.vat;
        spotter = params.spotter;
        ilk = params.ilk;
        token = params.token;
        oracle = params.oracle;
        mat = params.mat;
        line = params.line;
        dog = params.dog;
        vow = params.vow;
        calc = params.calc;
        buf = params.buf;
        tail = params.tail;
        cusp = params.cusp;
        chip = params.chip;
        tip = params.tip;
        gem = params.gem;
        dust = params.dust;
        hole = params.hole;
        chop = params.chop;
        jug = params.jug;
        duty = params.duty;

        // Generate description
        description = string(
            abi.encodePacked(
                "Deploy New Ilk: ",
                bytes32ToString(params.ilk),
                " (Token: ",
                toHexString(params.token),
                ", Mat: ",
                uintToString(params.mat / 10**25),
                "%, Line: ",
                uintToString(params.line / 10**45),
                " PAI, Buf: ",
                uintToString(params.buf / 10**25),
                "%)"
            )
        );
    }

    function execute() external {
        // 1. Deploy Contracts
        pip = address(new PipJoin(oracle, token));
        clipper = address(new Clipper(vat, spotter, dog, ilk));
        gemJoin = address(new GemJoin(vat, ilk, gem));

        // 2. Initialize Ilk in Vat (MUST be done first)
        VatLike(vat).init(ilk);

        // 3. Authorize Contracts
        // Grant Vat access to core modules
        VatLike(vat).rely(spotter);
        VatLike(vat).rely(gemJoin);
        VatLike(vat).rely(clipper);
        VatLike(vat).rely(dog);      // CRITICAL: Dog needs to suck/flux
        VatLike(vat).rely(jug); 

        JugLike(jug).init(ilk); 

        // Grant Clipper access
        PipJoin(pip).kiss(spotter);
        SpotterLike(spotter).rely(pip); // Spotter usually doesn't need rely on Pip, but rely on Clipper? 
                                        // Actually Clipper reads Spotter. Spotter reads Pip.
        
        // Dog auth
        DogLike(dog).rely(clipper);
        Clipper(clipper).rely(dog);
        
        // Clipper config
        Clipper(clipper).file("vow", vow);
        Clipper(clipper).file("calc", calc);

        // 4. Configure Vat
        VatLike(vat).file(ilk, "line", line);
        VatLike(vat).file(ilk, "dust", dust); // ADDED

        // 5. Configure Dog (Link the new Clipper to the Dog for this ilk)
        DogLike(dog).file(ilk, "clip", clipper); // CRITICAL
        DogLike(dog).file(ilk, "hole", hole);       // ADDED
        DogLike(dog).file(ilk, "chop", chop);       // ADDED

        // 6. Configure Spotter
        SpotterLike(spotter).file(ilk, "pip", pip);
        SpotterLike(spotter).file(ilk, "mat", mat);

        // 7. Configure Clipper
        Clipper(clipper).file("buf", buf);
        Clipper(clipper).file("tail", tail);
        Clipper(clipper).file("cusp", cusp);
        Clipper(clipper).file("chip", chip);
        Clipper(clipper).file("tip", tip);
        
        // 8. Calculate chost (MUST be after dust and chop are set)
        Clipper(clipper).upchost();

        JugLike(jug).file(ilk, "duty", duty); 

        // 9. Poke Prices
        PipJoin(pip).poke();
        SpotterLike(spotter).poke(ilk);
    }

    // Utility functions (unchanged)
    function bytes32ToString(bytes32 _bytes32) internal pure returns (string memory) {
        bytes memory bytesArray = new bytes(32);
        for (uint256 i; i < 32; i++) {
            bytesArray[i] = _bytes32[i];
        }
        return string(bytesArray);
    }

    function uintToString(uint256 _i) internal pure returns (string memory) {
        if (_i == 0) {
            return "0";
        }
        uint256 j = _i;
        uint256 len;
        while (j != 0) {
            len++;
            j /= 10;
        }
        bytes memory bstr = new bytes(len);
        uint256 k = len - 1;
        while (_i != 0) {
            bstr[k--] = byte(uint8(48 + _i % 10));
            _i /= 10;
        }
        return string(bstr);
    }

    function toHexString(address _addr) internal pure returns (string memory) {
        bytes memory buffer = new bytes(42);
        buffer[0] = "0";
        buffer[1] = "x";
        for (uint256 i = 0; i < 20; i++) {
            uint8 b = uint8(uint160(_addr) / (2**(8*(19 - i))));
            uint8 hi = b / 16;
            uint8 lo = b - 16 * hi;
            buffer[2 + i*2] = byte(hi < 10 ? hi + 48 : hi + 87);
            buffer[3 + i*2] = byte(lo < 10 ? lo + 48 : lo + 87);
        }
        return string(buffer);
    }
}