pragma solidity 0.4.25;
pragma experimental ABIEncoderV2;
pragma experimental "v0.5.0";

/**
 * @title SafeMath
 * @dev Math operations with safety checks that revert on error
 * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/d17ae0b806b2f8e69b291284bbf30321640609e3/contracts/math/SafeMath.sol
 */
library SafeMath {
	int256 constant private INT256_MIN = -2**255;

	/**
	* @dev Multiplies two unsigned integers, reverts on overflow.
	*/
	function mul(uint256 a, uint256 b) internal pure returns (uint256) {
		// Gas optimization: this is cheaper than requiring 'a' not being zero, but the benefit is lost if 'b' is also tested.
		// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
		if (a == 0) {
			return 0;
		}

		uint256 c = a * b;
		require(c / a == b);

		return c;
	}

	/**
	* @dev Multiplies two signed integers, reverts on overflow.
	*/
	function mul(int256 a, int256 b) internal pure returns (int256) {
		// Gas optimization: this is cheaper than requiring 'a' not being zero, but the benefit is lost if 'b' is also tested.
		// See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522
		if (a == 0) {
			return 0;
		}

		require(!(a == -1 && b == INT256_MIN)); // This is the only case of overflow not detected by the check below

		int256 c = a * b;
		require(c / a == b);

		return c;
	}

	/**
	* @dev Integer division of two unsigned integers truncating the quotient, reverts on division by zero.
	*/
	function div(uint256 a, uint256 b) internal pure returns (uint256) {
		// Solidity only automatically asserts when dividing by 0
		require(b > 0);
		uint256 c = a / b;
		// assert(a == b * c + a % b); // There is no case in which this doesn't hold

		return c;
	}

	/**
	* @dev Integer division of two signed integers truncating the quotient, reverts on division by zero.
	*/
	function div(int256 a, int256 b) internal pure returns (int256) {
		require(b != 0); // Solidity only automatically asserts when dividing by 0
		require(!(b == -1 && a == INT256_MIN)); // This is the only case of overflow

		int256 c = a / b;

		return c;
	}

	/**
	* @dev Subtracts two unsigned integers, reverts on overflow (i.e. if subtrahend is greater than minuend).
	*/
	function sub(uint256 a, uint256 b) internal pure returns (uint256) {
		require(b <= a);
		uint256 c = a - b;

		return c;
	}

	/**
	* @dev Subtracts two signed integers, reverts on overflow.
	*/
	function sub(int256 a, int256 b) internal pure returns (int256) {
		int256 c = a - b;
		require((b >= 0 && c <= a) || (b < 0 && c > a));

		return c;
	}

	/**
	* @dev Adds two unsigned integers, reverts on overflow.
	*/
	function add(uint256 a, uint256 b) internal pure returns (uint256) {
		uint256 c = a + b;
		require(c >= a);

		return c;
	}

	/**
	* @dev Adds two signed integers, reverts on overflow.
	*/
	function add(int256 a, int256 b) internal pure returns (int256) {
		int256 c = a + b;
		require((b >= 0 && c >= a) || (b < 0 && c < a));

		return c;
	}

	/**
	* @dev Divides two unsigned integers and returns the remainder (unsigned integer modulo), reverts when dividing by zero.
	*/
	function mod(uint256 a, uint256 b) internal pure returns (uint256) {
		require(b != 0);
		return a % b;
	}
}

/**
* @title Convenience and rounding functions when dealing with numbers already factored by 10**18 or 10**27
* @dev Math operations with safety checks that throw on error
* https://github.com/dapphub/ds-math/blob/87bef2f67b043819b7195ce6df3058bd3c321107/src/math.sol
*/
library SafeMathFixedPoint {
	using SafeMath for uint256;

	function mul27(uint256 x, uint256 y) internal pure returns (uint256 z) {
		z = x.mul(y).add(5 * 10**26).div(10**27);
	}
	function mul18(uint256 x, uint256 y) internal pure returns (uint256 z) {
		z = x.mul(y).add(5 * 10**17).div(10**18);
	}

	function div18(uint256 x, uint256 y) internal pure returns (uint256 z) {
		z = x.mul(10**18).add(y.div(2)).div(y);
	}
	function div27(uint256 x, uint256 y) internal pure returns (uint256 z) {
		z = x.mul(10**27).add(y.div(2)).div(y);
	}
}

/**
 * @title ERC20Basic
 * @dev Simpler version of ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/179
 * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20Basic.sol
 */
contract ERC20Basic {
	function totalSupply() public view returns (uint256);
	function balanceOf(address who) public view returns (uint256);
	function transfer(address to, uint256 value) public returns (bool);
	event Transfer(address indexed from, address indexed to, uint256 value);
}

/**
 * @title ERC20 interface
 * @dev see https://github.com/ethereum/EIPs/issues/20
 * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/token/ERC20/ERC20.sol
 */
contract ERC20 is ERC20Basic {
	function allowance(address owner, address spender) public view returns (uint256);
	function transferFrom(address from, address to, uint256 value) public returns (bool);
	function approve(address spender, uint256 value) public returns (bool);
	event Approval(address indexed owner, address indexed spender, uint256 value);
}

/**
 * @title Ownable
 * @dev The Ownable contract has an owner address, and provides basic authorization control
 * functions, this simplifies the implementation of "user permissions".
 * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Ownable.sol
 */
contract Ownable {
	address public owner;

	event OwnershipRenounced(address indexed previousOwner);
	event OwnershipTransferred(
		address indexed previousOwner,
		address indexed newOwner
	);

	/**
	 * @dev The Ownable constructor sets the original `owner` of the contract to the sender
	 * account.
	 */
	constructor() public {
		owner = msg.sender;
	}

	/**
	 * @dev Throws if called by any account other than the owner.
	 */
	modifier onlyOwner() {
		require(msg.sender == owner);
		_;
	}

	/**
	 * @dev Allows the current owner to transfer control of the contract to a newOwner.
	 * @param newOwner The address to transfer ownership to.
	 */
	function transferOwnership(address newOwner) public onlyOwner {
		require(newOwner != address(0));
		emit OwnershipTransferred(owner, newOwner);
		owner = newOwner;
	}

	/**
	 * @dev Allows the current owner to relinquish control of the contract.
	 */
	function renounceOwnership() public onlyOwner {
		emit OwnershipRenounced(owner);
		owner = address(0);
	}
}

/**
 * @title Claimable
 * @dev Extension for the Ownable contract, where the ownership needs to be claimed.
 * This allows the new owner to accept the transfer.
 * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/ownership/Claimable.sol
 */
contract Claimable is Ownable {
	address public pendingOwner;

	/**
	 * @dev Modifier throws if called by any account other than the pendingOwner.
	 */
	modifier onlyPendingOwner() {
		require(msg.sender == pendingOwner);
		_;
	}

	/**
	 * @dev Allows the current owner to set the pendingOwner address.
	 * @param newOwner The address to transfer ownership to.
	 */
	function transferOwnership(address newOwner) onlyOwner public {
		pendingOwner = newOwner;
	}

	/**
	 * @dev Allows the pendingOwner address to finalize the transfer.
	 */
	function claimOwnership() onlyPendingOwner public {
		emit OwnershipTransferred(owner, pendingOwner);
		owner = pendingOwner;
		pendingOwner = address(0);
	}
}

/**
 * @title Pausable
 * @dev Base contract which allows children to implement an emergency stop mechanism.
 * https://github.com/OpenZeppelin/openzeppelin-solidity/blob/master/contracts/lifecycle/Pausable.sol
 */
contract Pausable is Ownable {
	event Pause();
	event Unpause();

	bool public paused = false;


	/**
	 * @dev Modifier to make a function callable only when the contract is not paused.
	 */
	modifier whenNotPaused() {
		require(!paused);
		_;
	}

	/**
	 * @dev Modifier to make a function callable only when the contract is paused.
	 */
	modifier whenPaused() {
		require(paused);
		_;
	}

	/**
	 * @dev called by the owner to pause, triggers stopped state
	 */
	function pause() onlyOwner whenNotPaused public {
		paused = true;
		emit Pause();
	}

	/**
	 * @dev called by the owner to unpause, returns to normal state
	 */
	function unpause() onlyOwner whenPaused public {
		paused = false;
		emit Unpause();
	}
}

contract Dai is ERC20 {

}

contract Weth is ERC20 {
	function deposit() public payable;
	function withdraw(uint wad) public;
}

contract Mkr is ERC20 {

}

contract Peth is ERC20 {

}

contract MatchingMarket {
	function getBuyAmount(ERC20 tokenToBuy, ERC20 tokenToPay, uint256 amountToPay) external view returns(uint256 amountBought);
	function getPayAmount(ERC20 tokenToPay, ERC20 tokenToBuy, uint amountToBuy) public constant returns (uint amountPaid);
	function getBestOffer(ERC20 sell_gem, ERC20 buy_gem) public constant returns(uint offerId);
	function getWorseOffer(uint id) public constant returns(uint offerId);
	function getOffer(uint id) public constant returns (uint pay_amt, ERC20 pay_gem, uint buy_amt, ERC20 buy_gem);
	function sellAllAmount(ERC20 pay_gem, uint pay_amt, ERC20 buy_gem, uint min_fill_amount) public returns (uint fill_amt);
	function buyAllAmount(ERC20 buy_gem, uint buy_amt, ERC20 pay_gem, uint max_fill_amount) public returns (uint fill_amt);
}

contract DSValue {
	function read() external view returns(bytes32);
}

contract Maker {
	function sai() external view returns(Dai);
	function gem() external view returns(Weth);
	function gov() external view returns(Mkr);
	function skr() external view returns(Peth);
	function pip() external view returns(DSValue);
	function pep() external view returns(DSValue);

	// Join-Exit Spread
	 uint256 public gap;

	struct Cup {
		// CDP owner
		address lad;
		// Locked collateral (in SKR)
		uint256 ink;
		// Outstanding normalised debt (tax only)
		uint256 art;
		// Outstanding normalised debt
		uint256 ire;
	}

	uint256 public cupi;
	mapping (bytes32 => Cup) public cups;

	function lad(bytes32 cup) public view returns (address);
	function per() public view returns (uint ray);
	function tab(bytes32 cup) public returns (uint);
	function ink(bytes32 cup) public returns (uint);
	function rap(bytes32 cup) public returns (uint);
	function chi() public returns (uint);

	function open() public returns (bytes32 cup);
	function give(bytes32 cup, address guy) public;
	function lock(bytes32 cup, uint wad) public;
	function free(bytes32 cup, uint wad) public;
	function draw(bytes32 cup, uint wad) public;
	function join(uint wad) public;
	function exit(uint wad) public;
	function wipe(bytes32 cup, uint wad) public;
}

contract DSProxy {
	// Technically from DSAuth
	address public owner;

	function execute(address _target, bytes _data) public payable returns (bytes32 response);
}

contract ProxyRegistry {
	mapping(address => DSProxy) public proxies;
	function build(address owner) public returns (DSProxy proxy);
}

contract LiquidLong is Ownable, Claimable, Pausable {
	using SafeMath for uint256;
	using SafeMathFixedPoint for uint256;

	uint256 public providerFeePerEth;

	MatchingMarket public matchingMarket;
	Maker public maker;
	Dai public dai;
	Weth public weth;
	Peth public peth;
	Mkr public mkr;

	ProxyRegistry public proxyRegistry;

	struct CDP {
		uint256 id;
		uint256 debtInAttodai;
		uint256 lockedAttoeth;
		address owner;
		bool userOwned;
	}

	event NewCup(address user, uint256 cup);
	event CloseCup(address user, uint256 cup);

	constructor(MatchingMarket _matchingMarket, Maker _maker, ProxyRegistry _proxyRegistry) public payable {
		providerFeePerEth = 0.01 ether;

		matchingMarket = _matchingMarket;
		maker = _maker;
		dai = maker.sai();
		weth = maker.gem();
		peth = maker.skr();
		mkr = maker.gov();

		// MatchingMarket buy/sell
		dai.approve(address(_matchingMarket), uint256(-1));
		weth.approve(address(_matchingMarket), uint256(-1));
		// Wipe
		dai.approve(address(_maker), uint256(-1));
		mkr.approve(address(_maker), uint256(-1));
		// Join
		weth.approve(address(_maker), uint256(-1));
		// Lock
		peth.approve(address(_maker), uint256(-1));

		proxyRegistry = _proxyRegistry;

		if (msg.value > 0) {
			weth.deposit.value(msg.value)();
		}
	}

	// Receive ETH from WETH withdraw
	function () external payable {
	}

	function wethDeposit() public payable {
		weth.deposit.value(msg.value)();
	}

	function wethWithdraw(uint256 _amount) public onlyOwner {
		weth.withdraw(_amount);
		owner.transfer(_amount);
	}

	function attowethBalance() public view returns (uint256 _attoweth) {
		return weth.balanceOf(address(this));
	}

	function ethWithdraw() public onlyOwner {
		uint256 _amount = address(this).balance;
		owner.transfer(_amount);
	}

	function transferTokens(ERC20 _token) public onlyOwner {
		_token.transfer(owner, _token.balanceOf(this));
	}

	function ethPriceInUsd() public view returns (uint256 _attousd) {
		return uint256(maker.pip().read());
	}

	function estimateDaiSaleProceeds(uint256 _attodaiToSell) public view returns (uint256 _daiPaid, uint256 _wethBought) {
		return getPayPriceAndAmount(dai, weth, _attodaiToSell);
	}

	// buy/pay are from the perspective of the taker/caller (MatchingMarket contracts use buy/pay terminology from perspective of the maker)
	function getPayPriceAndAmount(ERC20 _payGem, ERC20 _buyGem, uint256 _payDesiredAmount) public view returns (uint256 _paidAmount, uint256 _boughtAmount) {
		uint256 _offerId = matchingMarket.getBestOffer(_buyGem, _payGem);
		while (_offerId != 0) {
			uint256 _payRemaining = _payDesiredAmount.sub(_paidAmount);
			(uint256 _buyAvailableInOffer,  , uint256 _payAvailableInOffer,) = matchingMarket.getOffer(_offerId);
			if (_payRemaining <= _payAvailableInOffer) {
				uint256 _buyRemaining = _payRemaining.mul(_buyAvailableInOffer).div(_payAvailableInOffer);
				_paidAmount = _paidAmount.add(_payRemaining);
				_boughtAmount = _boughtAmount.add(_buyRemaining);
				break;
			}
			_paidAmount = _paidAmount.add(_payAvailableInOffer);
			_boughtAmount = _boughtAmount.add(_buyAvailableInOffer);
			_offerId = matchingMarket.getWorseOffer(_offerId);
		}
		return (_paidAmount, _boughtAmount);
	}

	function estimateDaiPurchaseCosts(uint256 _attodaiToBuy) public view returns (uint256 _wethPaid, uint256 _daiBought) {
		return getBuyPriceAndAmount(weth, dai, _attodaiToBuy);
	}

	// buy/pay are from the perspective of the taker/caller (MatchingMarket contracts use buy/pay terminology from perspective of the maker)
	function getBuyPriceAndAmount(ERC20 _payGem, ERC20 _buyGem, uint256 _buyDesiredAmount) public view returns (uint256 _paidAmount, uint256 _boughtAmount) {
		uint256 _offerId = matchingMarket.getBestOffer(_buyGem, _payGem);
		while (_offerId != 0) {
			uint256 _buyRemaining = _buyDesiredAmount.sub(_boughtAmount);
			(uint256 _buyAvailableInOffer, , uint256 _payAvailableInOffer,) = matchingMarket.getOffer(_offerId);
			if (_buyRemaining <= _buyAvailableInOffer) {
				// TODO: verify this logic is correct
				uint256 _payRemaining = _buyRemaining.mul(_payAvailableInOffer).div(_buyAvailableInOffer);
				_paidAmount = _paidAmount.add(_payRemaining);
				_boughtAmount = _boughtAmount.add(_buyRemaining);
				break;
			}
			_paidAmount = _paidAmount.add(_payAvailableInOffer);
			_boughtAmount = _boughtAmount.add(_buyAvailableInOffer);
			_offerId = matchingMarket.getWorseOffer(_offerId);
		}
		return (_paidAmount, _boughtAmount);
	}

	modifier wethBalanceIncreased() {
		uint256 _startingAttowethBalance = weth.balanceOf(this);
		_;
		require(weth.balanceOf(this) > _startingAttowethBalance);
	}

	// TODO: change affiliate fee to be 50% of service fee, no parameter needed
	function openCdp(uint256 _leverage, uint256 _leverageSizeInAttoeth, uint256 _allowedFeeInAttoeth, address _affiliateAddress) public payable wethBalanceIncreased returns (bytes32 _cdpId) {
		require(_leverage >= 100 && _leverage <= 300);
		uint256 _lockedInCdpInAttoeth = _leverageSizeInAttoeth.mul(_leverage).div(100);
		uint256 _loanInAttoeth = _lockedInCdpInAttoeth.sub(_leverageSizeInAttoeth);
		uint256 _feeInAttoeth = _loanInAttoeth.mul18(providerFeePerEth);
		require(_feeInAttoeth <= _allowedFeeInAttoeth);
		uint256 _drawInAttodai = _loanInAttoeth.mul18(uint256(maker.pip().read()));
		uint256 _attopethLockedInCdp = _lockedInCdpInAttoeth.div27(maker.per());

		// Convert all incoming eth to weth (we will pay back later if too much)
		weth.deposit.value(msg.value)();
		// Open CDP
		_cdpId = maker.open();
		// Convert WETH into PETH
		maker.join(_attopethLockedInCdp);
		// Store PETH in CDP
		maker.lock(_cdpId, _attopethLockedInCdp);

		// This could be 0 if the user has requested a leverage of exactly 100 (1x).
		// There's no reason to draw 0 dai, try to sell it, or to pay out affiliate
		// Drawn dai is used to satisfy the _loanInAttoeth. 0 DAI means 0 loan.
		if (_drawInAttodai > 0) {
			// Withdraw DAI from CDP
			maker.draw(_cdpId, _drawInAttodai);
			// Sell DAI for WETH
			sellDai(_drawInAttodai, _lockedInCdpInAttoeth, _feeInAttoeth);
			// Pay provider fee
			if (_affiliateAddress != address(0)) {
				// Fee charged is constant. If affiliate provided, split fee with affiliate
				// Don't bother sending eth to owner, the owner has all weth anyway
				weth.transfer(_affiliateAddress, _feeInAttoeth.div(2));
			}
		}
		emit NewCup(msg.sender, uint256(_cdpId));

		giveCdpToProxy(msg.sender, _cdpId);
	}

	function giveCdpToProxy(address _ownerOfProxy, bytes32 _cdpId) private {
		DSProxy _proxy = proxyRegistry.proxies(_ownerOfProxy);
		if (_proxy == DSProxy(0) || _proxy.owner() != _ownerOfProxy) {
			_proxy = proxyRegistry.build(_ownerOfProxy);
		}
		// Send the CDP to the owner's proxy instead of directly to owner
		maker.give(_cdpId, _proxy);
	}

	// extracted function to mitigate stack depth issues
	function sellDai(uint256 _drawInAttodai, uint256 _lockedInCdpInAttoeth, uint256 _feeInAttoeth) private {
		uint256 _wethBoughtInAttoweth = matchingMarket.sellAllAmount(dai, _drawInAttodai, weth, 0);
		// SafeMath failure below catches not enough eth provided
		uint256 _refundDue = msg.value.add(_wethBoughtInAttoweth).sub(_lockedInCdpInAttoeth).sub(_feeInAttoeth);
		if (_refundDue > 0) {
			weth.withdraw(_refundDue);
			require(msg.sender.call.value(_refundDue)());
		}
	}

	// closeCdp is intended to be a delegate call that executes as a user's DSProxy
	function closeCdp(LiquidLong _liquidLong, uint256 _cdpId, uint256 _minimumValueInAttoeth, address _affiliateAddress) external returns (uint256 _payoutOwnerInAttoeth) {
		address _owner = DSProxy(this).owner();
		uint256 _startingAttoethBalance = _owner.balance;

		// This is delegated, we cannot use storage
		Maker _maker = _liquidLong.maker();

		// if the CDP is already empty, early return (this allows this method to be called off-chain to check estimated payout and not fail for empty CDPs)
		uint256 _lockedPethInAttopeth = _maker.ink(bytes32(_cdpId));
		if (_lockedPethInAttopeth == 0) return 0;

		_maker.give(bytes32(_cdpId), _liquidLong);
		_payoutOwnerInAttoeth = _liquidLong.closeGiftedCdp(bytes32(_cdpId), _minimumValueInAttoeth, _owner, _affiliateAddress);

		require(_maker.lad(bytes32(_cdpId)) == address(this));
		require(_owner.balance > _startingAttoethBalance);
		return _payoutOwnerInAttoeth;
	}

	// Close cdp that was just received as part of the same transaction
	function closeGiftedCdp(bytes32 _cdpId, uint256 _minimumValueInAttoeth, address _recipient, address _affiliateAddress) external wethBalanceIncreased returns (uint256 _payoutOwnerInAttoeth) {
		require(_recipient != address(0));
		uint256 _lockedPethInAttopeth = maker.ink(_cdpId);
		uint256 _debtInAttodai = maker.tab(_cdpId);

		// Calculate what we need to claim out of the CDP in Weth
		uint256 _lockedWethInAttoweth = _lockedPethInAttopeth.div27(maker.per());

		// Buy DAI and wipe the entire CDP
		// Pass in _lockedWethInAttoweth as "max fill amount". If buying DAI costs more in eth than the entire CDP has locked up, revert (we will fail later anyway)
		uint256 _wethSoldInAttoweth = matchingMarket.buyAllAmount(dai, _debtInAttodai, weth, _lockedWethInAttoweth);
		uint256 _providerFeeInAttoeth = _wethSoldInAttoweth.mul18(providerFeePerEth);

		// Calculating governance fee is difficult and gas-intense. Just look up how wiping impacts balance
		// Then convert that difference into weth, the only asset we charge in. This will require loading up
		// mkr periodically
		uint256 _mkrBalanceBeforeInAttomkr = mkr.balanceOf(this);
		maker.wipe(_cdpId, _debtInAttodai);
		uint256 _mkrBurnedInAttomkr = _mkrBalanceBeforeInAttomkr.sub(mkr.balanceOf(this));
		uint256 _ethValueOfBurnedMkrInAttoeth = _mkrBurnedInAttomkr.mul(uint256(maker.pep().read())) // converts Mkr to DAI
			.div(uint256(maker.pip().read())); // converts DAI to ETH

		// Relying on safe-math to revert a situation where LiquidLong would lose weth
		_payoutOwnerInAttoeth = _lockedWethInAttoweth.sub(_wethSoldInAttoweth).sub(_providerFeeInAttoeth).sub(_ethValueOfBurnedMkrInAttoeth);

		// Ensure remaining peth in CDP is greater than the value they requested as minimum value
		require(_payoutOwnerInAttoeth >= _minimumValueInAttoeth);

		// Pull that value from the CDP, convert it back to WETH for next time
		// We rely on "free" reverting the transaction if there is not enough peth to profitably close CDP
		maker.free(_cdpId, _lockedPethInAttopeth);
		maker.exit(_lockedPethInAttopeth);

		// DSProxy (or other proxy?) will have issued this request, send it back to the proxy contract. CDP is empty and valueless
		maker.give(_cdpId, msg.sender);

		weth.withdraw(_payoutOwnerInAttoeth);
		if (_affiliateAddress != address(0)) {
			// Fee charged is constant. If affiliate provided, split fee with affiliate
			// Don't bother sending eth to owner, the owner has all weth anyway
			weth.transfer(_affiliateAddress, _providerFeeInAttoeth.div(2));
		}
		require(_recipient.call.value(_payoutOwnerInAttoeth)());
		emit CloseCup(msg.sender, uint256(_cdpId));
	}

	// Retrieve CDPs by EFFECTIVE owner, which address owns the DSProxy which owns the CDPs
	function getCdps(address _owner, uint32 _offset, uint32 _pageSize) public returns (CDP[] _cdps) {
		// resolve a owner to a proxy, then query by that proxy
		DSProxy _cdpProxy = proxyRegistry.proxies(_owner);
		require(_cdpProxy != address(0));
		return getCdpsByAddresses(_owner, _cdpProxy, _offset, _pageSize);
	}

	// Retrieve CDPs by TRUE owner, as registered in Maker
	function getCdpsByAddresses(address _owner, address _proxy, uint32 _offset, uint32 _pageSize) public returns (CDP[] _cdps) {
		_cdps = new CDP[](getCdpCountByOwnerAndProxy(_owner, _proxy, _offset, _pageSize));
		uint256 _cdpCount = cdpCount();
		uint32 _matchCount = 0;
		for (uint32 _i = _offset; _i <= _cdpCount && _i < _offset + _pageSize; ++_i) {
			address _cdpOwner = maker.lad(bytes32(_i));
			if (_cdpOwner != _owner && _cdpOwner != _proxy) continue;
			_cdps[_matchCount] = getCdpDetailsById(_i, _owner);
			++_matchCount;
		}
		return _cdps;
	}

	function cdpCount() public view returns (uint32 _cdpCount) {
		uint256 count = maker.cupi();
		require(count < 2**32);
		return uint32(count);
	}

	function getCdpCountByOwnerAndProxy(address _owner, address _proxy, uint32 _offset, uint32 _pageSize) private view returns (uint32 _count) {
		uint256 _cdpCount = cdpCount();
		_count = 0;
		for (uint32 _i = _offset; _i <= _cdpCount && _i < _offset + _pageSize; ++_i) {
			address _cdpOwner = maker.lad(bytes32(_i));
			if (_cdpOwner != _owner && _cdpOwner != _proxy) continue;
			++_count;
		}
		return _count;
	}

	function getCdpDetailsById(uint32 _cdpId, address _owner) private returns (CDP _cdp) {
		(address _cdpOwner, uint256 _collateral,,) = maker.cups(bytes32(_cdpId));
		// this one line makes this function not `view`. tab calls chi, which calls drip which mutates state and we can't directly access _chi to bypass this
		uint256 _debtInAttodai = maker.tab(bytes32(_cdpId));
		// Adjust locked attoeth to factor in peth/weth ratio
		uint256 _lockedAttoeth = (_collateral + 1).mul27(maker.gap().mul18(maker.per()));
		_cdp = CDP({
			id: _cdpId,
			debtInAttodai: _debtInAttodai,
			lockedAttoeth: _lockedAttoeth,
			owner: _cdpOwner,
			userOwned: _cdpOwner == _owner
		});
		return _cdp;
	}
}