/**
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2016-2019 zOS Global Limited
*
*/
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see `ERC20Detailed`.
*/
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `recipient`.
*
* Returns always true. Throws error on failure.
*
* Emits a `Transfer` event.
*/
function transfer(address recipient, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through `transferFrom`. This is
* zero by default.
*
* This value can change when `approve` or `transferFrom` are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* > Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an `Approval` event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns always true. Throws error on failure.
*
* Emits a `Transfer` event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to `approve`. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./IReserve.sol";
interface IFrankencoin is IERC20 {
function suggestMinter(address _minter, uint256 _applicationPeriod, uint256 _applicationFee, string calldata _message) external;
function registerPosition(address position) external;
function denyMinter(address minter, address[] calldata helpers, string calldata message) external;
function reserve() external view returns (IReserve);
function minterReserve() external view returns (uint256);
function calculateAssignedReserve(uint256 mintedAmount, uint32 _reservePPM) external view returns (uint256);
function equity() external view returns (uint256);
function isMinter(address minter) external view returns (bool);
function getPositionParent(address position) external view returns (address);
function mint(address target, uint256 amount) external;
function mintWithReserve(address target, uint256 amount, uint32 reservePPM, uint32 feePPM) external;
function burnFrom(address target, uint256 amount) external;
function burnWithoutReserve(uint256 amountIncludingReserve, uint32 reservePPM) external;
function burnFromWithReserve(address payer, uint256 targetTotalBurnAmount, uint32 _reservePPM) external returns (uint256);
function burnWithReserve(uint256 amountExcludingReserve, uint32 reservePPM) external returns (uint256);
function coverLoss(address source, uint256 amount) external;
function collectProfits(address source, uint256 _amount) external;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./IReserve.sol";
import "./IFrankencoin.sol";
interface IPosition {
function original() external returns (address);
function collateral() external returns (IERC20);
function minimumCollateral() external returns (uint256);
function challengePeriod() external returns (uint64);
function expiration() external returns (uint256);
function price() external returns (uint256);
function reduceLimitForClone(uint256 amount) external;
function initializeClone(address owner, uint256 _price, uint256 _coll, uint256 _mint, uint256 expiration) external;
function deny(address[] calldata helpers, string calldata message) external;
function mint(address target, uint256 amount) external;
function minted() external returns (uint256);
function reserveContribution() external returns (uint32);
function getUsableMint(uint256 totalMint, bool beforeFees) external view returns (uint256);
function challengeData(uint256 challengeStart) external view returns (uint256 liqPrice, uint64 phase1, uint64 phase2);
function notifyChallengeStarted(uint256 size) external;
function notifyChallengeAverted(uint256 size) external;
function notifyChallengeSucceeded(address _bidder, uint256 _size) external returns (address, uint256, uint256, uint32);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IPositionFactory {
function createNewPosition(
address _owner,
address _zchf,
address _collateral,
uint256 _minCollateral,
uint256 _initialLimit,
uint256 _initPeriodSeconds,
uint256 _duration,
uint64 _challengePeriod,
uint32 _annualInterestPPM,
uint256 _liqPrice,
uint32 _reserve
) external returns (address);
function clonePosition(address _existing) external returns (address);
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IERC20.sol";
interface IReserve is IERC20 {
function invest(uint256 amount, uint256 expected) external returns (uint256);
function checkQualified(address sender, address[] calldata helpers) external view;
}
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./interface/IERC20.sol";
import "./interface/IReserve.sol";
import "./interface/IFrankencoin.sol";
import "./interface/IPosition.sol";
import "./interface/IPositionFactory.sol";
/**
* @title Minting Hub
* @notice The central hub for creating, cloning and challenging collateralized Frankencoin positions.
* @dev Only one instance of this contract is required, whereas every new position comes with a new position
* contract. Pending challenges are stored as structs in an array.
*/
contract MintingHub {
/**
* @notice Irrevocable fee in ZCHF when proposing a new position (but not when cloning an existing one).
*/
uint256 public constant OPENING_FEE = 1000 * 10 ** 18;
/**
* @notice The challenger reward in parts per million (ppm) relative to the challenged amount, whereas
* challenged amount if defined as the challenged collateral amount times the liquidation price.
*/
uint32 public constant CHALLENGER_REWARD = 20000; // 2%
IPositionFactory private immutable POSITION_FACTORY; // position contract to clone
IFrankencoin public immutable zchf; // currency
Challenge[] public challenges; // list of open challenges
/**
* @notice Map to remember pending postponed collateral returns.
* @dev It maps collateral => beneficiary => amount.
*/
mapping(address collateral => mapping(address owner => uint256 amount)) public pendingReturns;
struct Challenge {
address challenger; // the address from which the challenge was initiated
uint64 start; // the start of the challenge
IPosition position; // the position that was challenged
uint256 size; // how much collateral the challenger provided
}
event PositionOpened(
address indexed owner,
address indexed position,
address zchf,
address collateral,
uint256 price
);
event ChallengeStarted(address indexed challenger, address indexed position, uint256 size, uint256 number);
event ChallengeAverted(address indexed position, uint256 number, uint256 size);
event ChallengeSucceeded(
address indexed position,
uint256 number,
uint256 bid,
uint256 acquiredCollateral,
uint256 challengeSize
);
event PostPonedReturn(address collateral, address indexed beneficiary, uint256 amount);
error UnexpectedPrice();
error InvalidPos();
modifier validPos(address position) {
if (zchf.getPositionParent(position) != address(this)) revert InvalidPos();
_;
}
constructor(address _zchf, address _factory) {
zchf = IFrankencoin(_zchf);
POSITION_FACTORY = IPositionFactory(_factory);
}
function openPositionOneWeek(
address _collateralAddress,
uint256 _minCollateral,
uint256 _initialCollateral,
uint256 _mintingMaximum,
uint256 _expirationSeconds,
uint64 _challengeSeconds,
uint32 _annualInterestPPM,
uint256 _liqPrice,
uint32 _reservePPM
) public returns (address) {
return
openPosition(
_collateralAddress,
_minCollateral,
_initialCollateral,
_mintingMaximum,
7 days,
_expirationSeconds,
_challengeSeconds,
_annualInterestPPM,
_liqPrice,
_reservePPM
);
}
/**
* @notice Open a collateralized loan position. See also https://docs.frankencoin.com/positions/open .
* @dev For a successful call, you must set an allowance for the collateral token, allowing
* the minting hub to transfer the initial collateral amount to the newly created position and to
* withdraw the fees.
*
* @param _collateralAddress address of collateral token
* @param _minCollateral minimum collateral required to prevent dust amounts
* @param _initialCollateral amount of initial collateral to be deposited
* @param _mintingMaximum maximal amount of ZCHF that can be minted by the position owner
* @param _expirationSeconds position tenor in unit of timestamp (seconds) from 'now'
* @param _challengeSeconds challenge period. Longer for less liquid collateral.
* @param _annualInterestPPM ppm of minted amount that is paid as fee for each year of duration
* @param _liqPrice Liquidation price with (36 - token decimals) decimals,
* e.g. 18 decimals for an 18 dec collateral, 36 decs for a 0 dec collateral.
* @param _reservePPM ppm of minted amount that is locked as borrower's reserve, e.g. 20%
* @return address address of created position
*/
function openPosition(
address _collateralAddress,
uint256 _minCollateral,
uint256 _initialCollateral,
uint256 _mintingMaximum,
uint256 _initPeriodSeconds,
uint256 _expirationSeconds,
uint64 _challengeSeconds,
uint32 _annualInterestPPM,
uint256 _liqPrice,
uint32 _reservePPM
) public returns (address) {
require(_annualInterestPPM <= 1000000);
require(CHALLENGER_REWARD <= _reservePPM && _reservePPM <= 1000000);
require(IERC20(_collateralAddress).decimals() <= 24); // leaves 12 digits for price
require(_initialCollateral >= _minCollateral, "must start with min col");
require(_minCollateral * _liqPrice >= 5000 ether * 10 ** 18); // must start with at least 5000 ZCHF worth of collateral
IPosition pos = IPosition(
POSITION_FACTORY.createNewPosition(
msg.sender,
address(zchf),
_collateralAddress,
_minCollateral,
_mintingMaximum,
_initPeriodSeconds,
_expirationSeconds,
_challengeSeconds,
_annualInterestPPM,
_liqPrice,
_reservePPM
)
);
zchf.registerPosition(address(pos));
zchf.collectProfits(msg.sender, OPENING_FEE);
IERC20(_collateralAddress).transferFrom(msg.sender, address(pos), _initialCollateral);
emit PositionOpened(msg.sender, address(pos), address(zchf), _collateralAddress, _liqPrice);
return address(pos);
}
/**
* @notice Clones an existing position and immediately tries to mint the specified amount using the given collateral.
* @dev This needs an allowance to be set on the collateral contract such that the minting hub can get the collateral.
*/
function clone(
address position,
uint256 _initialCollateral,
uint256 _initialMint,
uint256 expiration
) public validPos(position) returns (address) {
IPosition existing = IPosition(position);
require(expiration <= IPosition(existing.original()).expiration());
existing.reduceLimitForClone(_initialMint);
address pos = POSITION_FACTORY.clonePosition(position);
zchf.registerPosition(pos);
IPosition(pos).initializeClone(msg.sender, existing.price(), _initialCollateral, _initialMint, expiration);
existing.collateral().transferFrom(msg.sender, pos, _initialCollateral);
emit PositionOpened(
msg.sender,
address(pos),
address(zchf),
address(IPosition(pos).collateral()),
IPosition(pos).price()
);
return address(pos);
}
/**
* @notice Launch a challenge (Dutch auction) on a position
* @param _positionAddr address of the position we want to challenge
* @param _collateralAmount amount of the collateral we want to challenge
* @param expectedPrice position.price() to guard against the minter fruntrunning with a price change
* @return index of the challenge in challenge-array
*/
function challenge(
address _positionAddr,
uint256 _collateralAmount,
uint256 expectedPrice
) external validPos(_positionAddr) returns (uint256) {
IPosition position = IPosition(_positionAddr);
if (position.price() != expectedPrice) revert UnexpectedPrice();
IERC20(position.collateral()).transferFrom(msg.sender, address(this), _collateralAmount);
uint256 pos = challenges.length;
challenges.push(Challenge(msg.sender, uint64(block.timestamp), position, _collateralAmount));
position.notifyChallengeStarted(_collateralAmount);
emit ChallengeStarted(msg.sender, address(position), _collateralAmount, pos);
return pos;
}
/**
* @notice Post a bid in ZCHF given an open challenge.
*
* @dev In case that the collateral cannot be transfered back to the challenger (i.e. because the collateral token
* has a blacklist and the challenger is on it), it is possible to postpone the return of the collateral.
*
* @param _challengeNumber index of the challenge as broadcast in the event
* @param size how much of the collateral the caller wants to bid for at most
* (automatically reduced to the available amount)
* @param postponeCollateralReturn To postpone the return of the collateral to the challenger. Usually false.
*/
function bid(uint32 _challengeNumber, uint256 size, bool postponeCollateralReturn) external {
Challenge memory _challenge = challenges[_challengeNumber];
(uint256 liqPrice, uint64 phase1, uint64 phase2) = _challenge.position.challengeData(_challenge.start);
size = _challenge.size < size ? _challenge.size : size; // cannot bid for more than the size of the challenge
if (block.timestamp <= _challenge.start + phase1) {
_avertChallenge(_challenge, _challengeNumber, liqPrice, size);
emit ChallengeAverted(address(_challenge.position), _challengeNumber, size);
} else {
_returnChallengerCollateral(_challenge, _challengeNumber, size, postponeCollateralReturn);
(uint256 transferredCollateral, uint256 offer) = _finishChallenge(
_challenge,
liqPrice,
phase1,
phase2,
size
);
emit ChallengeSucceeded(address(_challenge.position), _challengeNumber, offer, transferredCollateral, size);
}
}
function _finishChallenge(
Challenge memory _challenge,
uint256 liqPrice,
uint64 phase1,
uint64 phase2,
uint256 size
) internal returns (uint256, uint256) {
// Repayments depend on what was actually minted, whereas bids depend on the available collateral
(address owner, uint256 collateral, uint256 repayment, uint32 reservePPM) = _challenge
.position
.notifyChallengeSucceeded(msg.sender, size);
// No overflow possible thanks to invariant (col * price <= limit * 10**18)
// enforced in Position.setPrice and knowing that collateral <= col.
uint256 offer = (_calculatePrice(_challenge.start + phase1, phase2, liqPrice) * collateral) / 10 ** 18;
zchf.transferFrom(msg.sender, address(this), offer); // get money from bidder
uint256 reward = (offer * CHALLENGER_REWARD) / 1000_000;
zchf.transfer(_challenge.challenger, reward); // pay out the challenger reward
uint256 fundsAvailable = offer - reward; // funds available after reward
// Example: available funds are 90, repayment is 50, reserve 20%. Then 20%*(90-50)=16 are collected as profits
// and the remaining 34 are sent to the position owner. If the position owner maxed out debt before the challenge
// started and the liquidation price was 100, they would be slightly better off as they would get away with 80
// instead of 40+36 = 76 in this example.
if (fundsAvailable > repayment) {
// The excess amount is distributed between the system and the owner using the reserve ratio
// At this point, we cannot rely on the liquidation price because the challenge might have been started as a
// response to an unreasonable increase of the liquidation price, such that we have to use this heuristic
// for excess fund distribution, which make position owners that maxed out their positions slightly better
// off in comparison to those who did not.
uint256 profits = reservePPM * (fundsAvailable - repayment) / 1000_000;
zchf.collectProfits(address(this), profits);
zchf.transfer(owner, fundsAvailable - repayment - profits);
} else if (fundsAvailable < repayment) {
zchf.coverLoss(address(this), repayment - fundsAvailable); // ensure we have enough to pay everything
}
zchf.burnWithoutReserve(repayment, reservePPM); // Repay the challenged part, example: 50 ZCHF leading to 10 ZCHf in implicit profits
return (collateral, offer);
}
function _avertChallenge(Challenge memory _challenge, uint32 number, uint256 liqPrice, uint256 size) internal {
require(block.timestamp != _challenge.start); // do not allow to avert the challenge in the same transaction, see CS-ZCHF-037
if (msg.sender == _challenge.challenger) {
// allow challenger to cancel challenge without paying themselves
} else {
zchf.transferFrom(msg.sender, _challenge.challenger, (size * liqPrice) / (10 ** 18));
}
_challenge.position.notifyChallengeAverted(size);
_challenge.position.collateral().transfer(msg.sender, size);
if (size < _challenge.size) {
challenges[number].size = _challenge.size - size;
} else {
require(size == _challenge.size);
delete challenges[number];
}
}
/**
* @notice Returns 'amount' of the collateral to the challenger and reduces or deletes the relevant challenge.
*/
function _returnChallengerCollateral(
Challenge memory _challenge,
uint32 number,
uint256 amount,
bool postpone
) internal {
_returnCollateral(_challenge.position.collateral(), _challenge.challenger, amount, postpone);
if (_challenge.size == amount) {
// bid on full amount
delete challenges[number];
} else {
// bid on partial amount
challenges[number].size -= amount;
}
}
/**
* @notice Calculates the current Dutch auction price.
* @dev Starts at the full price at time 'start' and linearly goes to 0 as 'phase2' passes.
*/
function _calculatePrice(uint64 start, uint64 phase2, uint256 liqPrice) internal view returns (uint256) {
uint64 timeNow = uint64(block.timestamp);
if (timeNow <= start) {
return liqPrice;
} else if (timeNow >= start + phase2) {
return 0;
} else {
uint256 timeLeft = phase2 - (timeNow - start);
return (liqPrice / phase2) * timeLeft;
}
}
/**
* @notice Get the price per unit of the collateral for the given challenge.
* @dev The price comes with (36-collateral.decimals()) digits, such that multiplying it with the
* raw collateral amount always yields a price with 36 digits, or 18 digits after dividing by 10**18 again.
*/
function price(uint32 challengeNumber) public view returns (uint256) {
Challenge memory _challenge = challenges[challengeNumber];
if (_challenge.challenger == address(0x0)) {
return 0;
} else {
(uint256 liqPrice, uint64 phase1, uint64 phase2) = _challenge.position.challengeData(_challenge.start);
return _calculatePrice(_challenge.start + phase1, phase2, liqPrice);
}
}
/**
* @notice Challengers can call this method to withdraw collateral whose return was postponed.
*/
function returnPostponedCollateral(address collateral, address target) external {
uint256 amount = pendingReturns[collateral][msg.sender];
delete pendingReturns[collateral][msg.sender];
IERC20(collateral).transfer(target, amount);
}
function _returnCollateral(IERC20 collateral, address recipient, uint256 amount, bool postpone) internal {
if (postpone) {
// Postponing helps in case the challenger was blacklisted or otherwise cannot receive at the moment.
pendingReturns[address(collateral)][recipient] += amount;
emit PostPonedReturn(address(collateral), recipient, amount);
} else {
collateral.transfer(recipient, amount); // return the challenger's collateral
}
}
}
{
"compilationTarget": {
"contracts/MintingHub.sol": "MintingHub"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs",
"useLiteralContent": true
},
"optimizer": {
"enabled": true,
"runs": 200
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_zchf","type":"address"},{"internalType":"address","name":"_factory","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"InvalidPos","type":"error"},{"inputs":[],"name":"UnexpectedPrice","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"number","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"size","type":"uint256"}],"name":"ChallengeAverted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"challenger","type":"address"},{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"size","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"number","type":"uint256"}],"name":"ChallengeStarted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"uint256","name":"number","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"bid","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"acquiredCollateral","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"challengeSize","type":"uint256"}],"name":"ChallengeSucceeded","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"position","type":"address"},{"indexed":false,"internalType":"address","name":"zchf","type":"address"},{"indexed":false,"internalType":"address","name":"collateral","type":"address"},{"indexed":false,"internalType":"uint256","name":"price","type":"uint256"}],"name":"PositionOpened","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"collateral","type":"address"},{"indexed":true,"internalType":"address","name":"beneficiary","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"PostPonedReturn","type":"event"},{"inputs":[],"name":"CHALLENGER_REWARD","outputs":[{"internalType":"uint32","name":"","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"OPENING_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"_challengeNumber","type":"uint32"},{"internalType":"uint256","name":"size","type":"uint256"},{"internalType":"bool","name":"postponeCollateralReturn","type":"bool"}],"name":"bid","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_positionAddr","type":"address"},{"internalType":"uint256","name":"_collateralAmount","type":"uint256"},{"internalType":"uint256","name":"expectedPrice","type":"uint256"}],"name":"challenge","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"challenges","outputs":[{"internalType":"address","name":"challenger","type":"address"},{"internalType":"uint64","name":"start","type":"uint64"},{"internalType":"contract IPosition","name":"position","type":"address"},{"internalType":"uint256","name":"size","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"position","type":"address"},{"internalType":"uint256","name":"_initialCollateral","type":"uint256"},{"internalType":"uint256","name":"_initialMint","type":"uint256"},{"internalType":"uint256","name":"expiration","type":"uint256"}],"name":"clone","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralAddress","type":"address"},{"internalType":"uint256","name":"_minCollateral","type":"uint256"},{"internalType":"uint256","name":"_initialCollateral","type":"uint256"},{"internalType":"uint256","name":"_mintingMaximum","type":"uint256"},{"internalType":"uint256","name":"_initPeriodSeconds","type":"uint256"},{"internalType":"uint256","name":"_expirationSeconds","type":"uint256"},{"internalType":"uint64","name":"_challengeSeconds","type":"uint64"},{"internalType":"uint32","name":"_annualInterestPPM","type":"uint32"},{"internalType":"uint256","name":"_liqPrice","type":"uint256"},{"internalType":"uint32","name":"_reservePPM","type":"uint32"}],"name":"openPosition","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_collateralAddress","type":"address"},{"internalType":"uint256","name":"_minCollateral","type":"uint256"},{"internalType":"uint256","name":"_initialCollateral","type":"uint256"},{"internalType":"uint256","name":"_mintingMaximum","type":"uint256"},{"internalType":"uint256","name":"_expirationSeconds","type":"uint256"},{"internalType":"uint64","name":"_challengeSeconds","type":"uint64"},{"internalType":"uint32","name":"_annualInterestPPM","type":"uint32"},{"internalType":"uint256","name":"_liqPrice","type":"uint256"},{"internalType":"uint32","name":"_reservePPM","type":"uint32"}],"name":"openPositionOneWeek","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"collateral","type":"address"},{"internalType":"address","name":"owner","type":"address"}],"name":"pendingReturns","outputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint32","name":"challengeNumber","type":"uint32"}],"name":"price","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"collateral","type":"address"},{"internalType":"address","name":"target","type":"address"}],"name":"returnPostponedCollateral","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"zchf","outputs":[{"internalType":"contract IFrankencoin","name":"","type":"address"}],"stateMutability":"view","type":"function"}]