//draft of p3d margin trading for long positions //by nightman //@dev can you save on gas by using existing contract data and pointing new at the address? //@dev think about possible attack vectors like people transferring tokens to the proxy contracts pragma solidity 0.4.25; //P3D INTERFACE contract HourglassInterface { function stakingRequirement() pure public returns(uint256) {} function buyPrice() pure public returns(uint256) {} function balanceOf(address) pure public returns(uint256) {} //address parameter is user function dividendsOf(address) pure public returns(uint256) {} //address parameter is user function calculateEthereumReceived(uint256) pure public returns(uint256) {} //uint parameter is tokens function buy(address) public payable returns(uint256) {} //address parameter is referral function reinvest() pure public {} function withdraw() pure public {} function exit() pure public {} } //PRIMARY CONTRACT contract MarginTradingP3D { using SafeMath for *; //STORAGE address public admin; uint256 public adminBalance; address public p3d = 0xB3775fB83F7D12A36E0475aBdD1FCA35c091efBe; uint32 public loanOfferId; uint32 public activeLoanId; uint256 public liquidationMargin = 5; //positions are required to maintain >105% of initial loan or risk liquidation (dynamic with interest) mapping (uint256 => LoanOffer) public loanOffers; mapping (uint256 => ActiveLoan) internal activeLoans; //nested structs can't display, see view function mapping (address => uint256[]) public offersByAddress; mapping (address => uint256) public userBalance; mapping (address => bool) public proxyWhitelist; mapping (uint256 => address) public activeProxies; //CONSTRUCTOR constructor() public payable{ require(msg.value >= calcMasternodeCost(), "you must give enough for a masternode"); admin = msg.sender; adminBuyProxy(); } //STRUCTS struct LoanOffer { //tightly packed bits to save gas uint32 id; //max 4294967295 uint32 dailyInterestRate; //max 4294967295, precise to two decimals (eg 387 is 3.78% per day) uint64 duration; //max 18446744073709551615 uint128 amount; //max uint128 is greater than total ETH supply address lender; bool filled; } struct ActiveLoan { //tightly packed bits to save gas uint32 id; uint64 started; uint64 expiry; uint96 liquidationPrice; //max uint96 is greater than total ETH supply bool closed; address borrower; address proxy; LoanOffer loanOffer; } //MODIFIERS modifier onlyAdmin() { require (admin == msg.sender, "you must be the admin"); _; } modifier onlyWhitelist() { require (proxyWhitelist[msg.sender] == true, "only whitelisted contracts allowed"); _; } //EVENTS event logNewLoanOffer( uint32 indexed id_, uint32 dailyInterestRate_, uint64 duration_, uint128 amount_, address indexed lender_, string message_ ); event logLoanOfferRemoved( uint32 indexed id_, address indexed lender_, string message_ ); event logUserWithdrawal( uint256 amount_, address indexed user_, string message_ ); event logAdminWithdrawal( uint256 amount_, address indexed user_, string message_ ); event logDonation( uint256 amount_, address indexed sender_, string message_ ); event logAdminBuy( uint256 amount_, address indexed sender_, string message_ ); event logAdminReinvest( address indexed sender_, string message_ ); event logAdminProxyWithdrawal( address indexed sender_, string message_ ); event logAdminExit( address indexed sender_, string message_ ); event logLoanTaken( uint256 indexed activeLoanId_, uint256 loanOfferId_, uint256 started_, uint256 expiry_, uint256 liquidationPrice_, address indexed borrower_, address indexed proxy_, string message_ ); event logUserReinvest( address indexed borrower_, address indexed proxy_, uint256 indexed activeLoanId_, string message_ ); event logUserExit( address indexed borrower_, address indexed proxy_, uint256 indexed activeLoanId_, uint256 fundsReceived_, string message_ ); event logLiquidation( address indexed liquidator_, address indexed proxy_, uint256 indexed activeLoanId_, uint256 fundsReceived_, string message_ ); //ADMIN FUNCTIONS //admin withdraw function adminWithdraw() external onlyAdmin() { require(adminBalance > 0, "you must have a balance to withdraw"); //update accounting uint256 balance = adminBalance; adminBalance = 0; //transfer admin.transfer(balance); emit logAdminWithdrawal(balance, admin, "the admin just withdrew"); } //admin buy ins to ensure there is enough for a masternode function adminBuyProxy() public payable onlyAdmin() { HourglassInterface h = HourglassInterface(p3d); h.buy.value(msg.value)(address(this)); emit logAdminBuy(msg.value, msg.sender, "the admin just bought"); } function adminReinvestProxy() external onlyAdmin() { HourglassInterface h = HourglassInterface(p3d); h.reinvest(); emit logAdminReinvest(msg.sender, "the admin just reinvested"); } function adminWithdrawProxy() external onlyAdmin() { HourglassInterface h = HourglassInterface(p3d); h.withdraw(); emit logAdminProxyWithdrawal(msg.sender, "the admin just withdrew via proxy"); } function adminExitProxy() external onlyAdmin() { HourglassInterface h = HourglassInterface(p3d); h.exit(); emit logAdminExit(msg.sender, "the admin just exited"); } //USER FUNCTIONS //takes in money from masternodes or random ether sent to contract function() external payable { require(msg.value > 0, "fallback function only accepts ether"); //log balance adminBalance += msg.value; emit logDonation(msg.value, msg.sender, "thanks for the donation"); } //called by lenders to list loans function listLoan(uint32 _dailyInterestRate, uint64 _duration, uint128 _amount) external payable { require(_amount == msg.value && _amount > 0, "you must provide the capital for your loan offer"); require(_dailyInterestRate >= 0 && _dailyInterestRate < 4294967295, "the interest rate must be less than the max 32-bit number"); require(_duration >= 86400 && _duration < 18446744073709551615, "the duration must be longer than one day and less than the max 64-bit number"); //increment id (first loan starts at 1) loanOfferId++; //instantiate struct in storage LoanOffer storage l = loanOffers[loanOfferId]; //update values l.id = loanOfferId; l.dailyInterestRate = _dailyInterestRate; l.duration = _duration; l.amount = _amount; l.lender = msg.sender; l.filled = false; //update accounting offersByAddress[msg.sender].push(loanOfferId); emit logNewLoanOffer(loanOfferId, _dailyInterestRate, _duration, _amount, msg.sender, "new loan offer"); } //called by lenders to remove open listings function removeListing(uint32 _id) external { require (msg.sender == loanOffers[_id].lender, "you must be the lender of this loan"); require (loanOffers[_id].filled == false, "the loan offer must be open"); //update struct tracking loanOffers[_id].filled = true; uint256 balance = loanOffers[_id].amount; loanOffers[_id].amount = 0; //return to user balance userBalance[msg.sender] += balance; emit logLoanOfferRemoved(_id, msg.sender, "loan offer removed"); } //user withdraw function userWithdraw() external { require (userBalance[msg.sender] > 0, "you must have a balance to withdraw"); //update accounting uint256 balance = userBalance[msg.sender]; userBalance[msg.sender] = 0; //transfer msg.sender.transfer(balance); emit logUserWithdrawal(balance, msg.sender, "a user withdrew their balance"); } //called by borrower to take on an open loan offer function takeLoan(uint32 _id) external payable returns(uint32 _activeLoanId){ require (msg.value >= loanOffers[_id].amount, "you must provide sufficient collateral"); //2x leverage require (loanOffers[_id].filled == false, "the loan offer must be open"); //update loan offer loanOffers[_id].filled = true; //increment id activeLoanId++; //update accounting uint256 position = msg.value + loanOffers[_id].amount; //launch new contract ProxyBuyer p = (new ProxyBuyer).value(position)(activeLoanId, msg.sender); proxyWhitelist[p] = true; activeProxies[activeLoanId] = p; //update active loan struct ActiveLoan storage a = activeLoans[activeLoanId]; a.id = activeLoanId; a.started = uint64(now); a.expiry = uint64(now + loanOffers[_id].duration); a.liquidationPrice = uint96(msg.value.add(msg.value.mul(liquidationMargin).div(100))); a.closed = false; a.borrower = msg.sender; a.proxy = p; a.loanOffer = loanOffers[_id]; //@dev not sure if this is correct way to do nested structs (i may need to set each value) return activeLoanId; emit logLoanTaken(activeLoanId, _id, now, a.expiry, a.liquidationPrice, msg.sender, p, "a loan was taken out"); } //can be called by borrower to reinvest function reinvestPosition(uint32 _activeLoanId) external { require (msg.sender == activeLoans[_activeLoanId].borrower, "you must own this position"); require (activeLoans[_activeLoanId].closed == false, "the position must be active"); require (isPositionAtRisk(_activeLoanId) == false, "the position cannot be at risk"); ProxyBuyer p = ProxyBuyer(activeProxies[_activeLoanId]); p.reinvestProxy(); emit logUserReinvest(msg.sender, p, _activeLoanId, "a user reinvested"); } //can be called by borrower to close position function exitPosition(uint32 _activeLoanId) external { require (msg.sender == activeLoans[_activeLoanId].borrower, "you must own this position"); require (activeLoans[_activeLoanId].closed == false, "the position must be active"); ProxyBuyer p = ProxyBuyer(activeProxies[_activeLoanId]); p.exitProxyClose(msg.sender); } //can be called by liquidators to dissolve risky or expired positions function liquidatePosition(uint32 _activeLoanId) external { require (isPositionAtRisk(_activeLoanId) == true || now > activeLoans[_activeLoanId].expiry, "the position must be at risk or expired"); ProxyBuyer p = ProxyBuyer(activeProxies[_activeLoanId]); p.exitProxyLiquidate(msg.sender); } //log liquidated contract return function handleProxyReturn(uint32 _activeLoanId, address _user) public payable onlyWhitelist() { //update struct accounting activeLoans[_activeLoanId].closed = true; uint256 fundsReceived = msg.value; //calculate what lender is owed uint256 daysActive = (now - activeLoans[_activeLoanId].started).div(86400); //interest added once per day uint256 interestOwed = daysActive.mul(activeLoans[_activeLoanId].loanOffer.dailyInterestRate).div(100); uint256 lenderOwed = interestOwed + activeLoans[_activeLoanId].loanOffer.amount; if (_user == activeLoans[_activeLoanId].borrower) { uint256 borrowerProfit; //return funds to lender if (fundsReceived > lenderOwed) { borrowerProfit = fundsReceived.sub(lenderOwed); userBalance[activeLoans[_activeLoanId].loanOffer.lender] += lenderOwed; userBalance[activeLoans[_activeLoanId].borrower] += borrowerProfit; emit logUserExit(_user, activeProxies[_activeLoanId], _activeLoanId, fundsReceived, "a user closed a position and received funds"); } else { userBalance[activeLoans[_activeLoanId].loanOffer.lender] += fundsReceived; emit logUserExit(_user, activeProxies[_activeLoanId], _activeLoanId, fundsReceived, "a user closed a position and did not receive funds"); } } else { uint256 liquidatorProfit = activeLoans[_activeLoanId].liquidationPrice.sub(activeLoans[_activeLoanId].loanOffer.amount); uint256 lenderProfit = fundsReceived.sub(liquidatorProfit); userBalance[activeLoans[_activeLoanId].loanOffer.lender] += lenderProfit; userBalance[_user] += liquidatorProfit; emit logLiquidation(_user, activeProxies[_activeLoanId], _activeLoanId, fundsReceived, "a position was liquidated"); } } //VIEW FUNCTIONS //view active loans without nested struct function viewActiveLoan(uint32 _activeLoanId) public view returns( uint32 _id, uint64 _expiry, uint256 _liquidationPrice, bool _closed, address _borrower, address _proxy, uint256 _loanOfferID ) { return ( activeLoans[_activeLoanId].id, activeLoans[_activeLoanId].expiry, checkLiquidationPrice(_activeLoanId), activeLoans[_activeLoanId].closed, activeLoans[_activeLoanId].borrower, activeLoans[_activeLoanId].proxy, activeLoans[_activeLoanId].loanOffer.id ); } //check if the position is at risk function isPositionAtRisk(uint32 _activeLoanId) public view returns(bool _isAtRisk) { if (checkPositionValue(_activeLoanId) <= checkLiquidationPrice(_activeLoanId)) { return true; } else { return false; } } //check current value of a position function checkPositionValue(uint32 _activeLoanId) public view returns(uint256 _valueInWei) { address address_ = activeProxies[_activeLoanId]; HourglassInterface h = HourglassInterface(p3d); uint256 tokens = h.balanceOf(address_); return h.dividendsOf(address_) + h.calculateEthereumReceived(tokens); } //check tokens held by position function checkPositionTokens(uint32 _activeLoanId) external view returns(uint256 _tokens) { address address_ = activeProxies[_activeLoanId]; HourglassInterface h = HourglassInterface(p3d); return h.balanceOf(address_); } //check liquidation price function checkLiquidationPrice(uint32 _activeLoanId) public view returns(uint256 _price) { uint256 daysActive = (now - activeLoans[_activeLoanId].started).div(86400); //interest added once per day uint256 interestOwed = daysActive.mul(activeLoans[_activeLoanId].loanOffer.dailyInterestRate).div(100); return activeLoans[_activeLoanId].liquidationPrice + interestOwed; } function calcMasternodeCost() public view returns(uint256 _masternodePrice) { HourglassInterface h = HourglassInterface(p3d); return h.stakingRequirement().mul(h.buyPrice().div(10**18)); } } //MAIN CONTRACT INTERFACE FOR PROXY contract MarginTradingInterface { function handleProxyReturn(uint32, address) public payable {} } //PROXY CONTRACT contract ProxyBuyer is HourglassInterface, MarginTradingInterface { uint32 public id; address public p3d = 0xB3775fB83F7D12A36E0475aBdD1FCA35c091efBe; address public mainContract; address public user; address public lastCaller; constructor(uint32 _id, address _user) public payable { mainContract = msg.sender; user = _user; id = _id; buyProxy(); } modifier onlyMainContract() { require (msg.sender == mainContract, "only the main contract can call a proxy"); _; } function buyProxy() public payable onlyMainContract() { HourglassInterface h = HourglassInterface(p3d); //@dev can these be instantiated in the constructor? h.buy.value(msg.value)(mainContract); } function reinvestProxy() external view onlyMainContract() { HourglassInterface h = HourglassInterface(p3d); h.reinvest(); } function exitProxyClose(address _caller) external onlyMainContract() { HourglassInterface h = HourglassInterface(p3d); h.exit(); lastCaller = _caller; } function exitProxyLiquidate(address _caller) external onlyMainContract() { HourglassInterface h = HourglassInterface(p3d); h.exit(); lastCaller = _caller; } function forwardFunds() internal { MarginTradingInterface m = MarginTradingInterface(mainContract); m.handleProxyReturn.value(address(this).balance)(id, lastCaller); } function() public payable { if (msg.sender == p3d) { forwardFunds(); } else { mainContract.transfer(msg.value); } } } //LIBRARIES library SafeMath { function mul(uint256 a, uint256 b) internal pure returns (uint256) { if (a == 0) { return 0; } uint256 c = a * b; require(c / a == b, "the SafeMath multiplication check failed"); return c; } function div(uint256 a, uint256 b) internal pure returns (uint256) { require(b > 0, "the SafeMath division check failed"); uint256 c = a / b; return c; } function sub(uint256 a, uint256 b) internal pure returns (uint256) { require(b <= a, "the SafeMath subtraction check failed"); return a - b; } function add(uint256 a, uint256 b) internal pure returns (uint256) { uint256 c = a + b; require(c >= a, "the SafeMath addition check failed"); return c; } function mod(uint256 a, uint256 b) internal pure returns (uint256) { require(b != 0, "the SafeMath modulo check failed"); return a % b; } }
0.4.25