Address
0x52e6bee2bfaf72df717fc333998975f05bf4e993Current 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);
}
}