Ethereum Tutorials

SMART CONTRACTS

"ShareHolder" Contract

Full text of the contract (See additional comments in code):

				
pragma solidity ^0.4.11;

// A simple implementation of the ownership contract.
contract Ownable 
{
	address m_addrOwner;

	function Ownable() 	
	{ 
		m_addrOwner = msg.sender; 
	}

	modifier onlyOwner() 
	{
		if (msg.sender != m_addrOwner) 
		{
			throw;
		}
    	_;
	}

	// ---

	function transferOwnership(address newOwner) onlyOwner 
	{
		m_addrOwner = newOwner;
	}

	// ---

	function isOwner() constant returns (bool bIsOwner) { return (m_addrOwner == msg.sender); }

}

// --- ShareHolder contract receives part of profit of the contract it is
// --- attached to (in our case it is "Duke of Ether") and distributes it 
// --- among shere holders. Shares can be bought and sold back to this contract,
// --- price of the share is dynamically based on the contract's profit, but
// --- can never drop lower than the price at which the share was originally
// --- purchased.

contract ShareHolder is Ownable
{
	// Note that m_deployedAtBlock is used both in "ShareHolder" and in "Duke". If we are going to attach 
	// more than one contract to ShareHolder, it is possible, that each contract starts at its own time,
	// therefore, we might need to implement separate variables for each contract.
	uint m_deployedAtBlock = 0;					// Initial block of a contract, used in logging/reporting

	uint m_nTimeStarted;						// Timestamp when a contract was created

	// We count weeks from the moment contract was deployed. Weeks are used in profit EMA calculation,
	// each week represents the next point on the profit's smoothed chart, with smoothing period of one
	// year (50 weeks). So m_nCurrentWeek changes from 0 to 49 and then becomes 0 again.
	uint m_nCurrentWeek = 0;					// 0 - 49, one year
	
	// m_nCurrentWeekTotal is another counter for weeks, unlike m_nCurrentWeek, it changes from 0 to
	// infinity, it holds weeks from the moment contract was deployed.
	// Formulas: 
	// m_nCurrentWeekTotal = (m_nCurrentProfitDay / 7);
	// uint nCurrentWeek = m_nCurrentWeekTotal % 50;
	uint m_nCurrentWeekTotal = 0;				// 0 - ..., all years, used in EMA 
	
	uint[50] m_arrWeeksProfit;					// Profit for 50 last weeks

	// --- Config. constants ---
	uint m_nTradeComission = 1;					// 1% - comission for buying / selling shares
	uint m_nProfitComission = 5;				// 5% - comission on profit a contract sends (5% of, say, 4% original contract pays...)
	
	uint m_nTotalShares = 10000;				// Total amount of shares to be issued
	
	// this amount is being increased by m_nTotalShares / m_nDailySupplyFraction per day
	uint m_nSharesAvailable = 0;				// Available for sale at a particular moment
	uint m_nDailySupplyFraction = 100;			// 1/100 of m_shareHolders[m_addrOwner] amount is added daily to m_nSharesAvailable
	
	uint m_nShareMinPrice = 1 finney;			// Minimum (initial) "share" price
	
	// We consider demand to be too high if all available shares are sold
	uint m_nPriceOnDemandIncrease = 10;			// If demand is too high, increase the price 10% over calculated one
	uint m_nPriceOnDemandTotalIncrease = 0;		// We keep total (cumulative) price increase here
	
	uint m_nShareHoldersMoney = 0;				// Shareholders' share of profit received from the attached contract.
	uint m_numberOfSharesCurrentlyInPosession = 0;

	// We have two types of events: profit from the contract arrives or shares are being bought/sold
	// Both events are being recorded together with the day they occure. If the day ends and new day
	// begins, we need to increase day number. However, before doing it, we have to trigger profit event -
	// one of two types. So to avoid extra work, I chose to have two counters instead of one.
	uint m_nCurrentProfitDay = 0;               // Used as index in m_dailyProfit
	uint m_nCurrentSharesDay = 0;               // Used to adjust avail. shares
	
	mapping (uint => uint) m_dailyProfit;		// Cumulative timestamp - daily profit. Day starts at m_nTimeStarted + 24h*n
	mapping (address => uint) m_shareHolders;	// Shareholders' money, in shares 

	event tradeEvent(string strType, address addr, uint nAmount, uint nShares);
	event errorMessage(string strMessage);
	event profitEvent(uint nCurrentDay, uint nDailyProfit, uint nShareHoldersMoney, uint numberOfSharesCurrentlyInPosession, uint nTokenPrice);
	
	function ShareHolder()
	{
		m_nTimeStarted = now;
		m_deployedAtBlock = block.number;
		m_nSharesAvailable = m_nTotalShares / m_nDailySupplyFraction;
		m_shareHolders[msg.sender] = m_nTotalShares;	// Initially, all shares are owned by contract creator
	}

	// --- Handling 50 weeks of profit ---
	
	function adjustCurrentWeek() internal
	{
		m_nCurrentWeekTotal = (m_nCurrentProfitDay / 7);
		uint nCurrentWeek = m_nCurrentWeekTotal % 50;

		if(nCurrentWeek != m_nCurrentWeek)						// Next week started
		{
			m_arrWeeksProfit[m_nCurrentWeek] = getNextWeekEMA();
			
			if(nCurrentWeek >= 50)
				m_nCurrentWeek = 0;
			else
				m_nCurrentWeek = nCurrentWeek; 
		
			m_arrWeeksProfit[m_nCurrentWeek] = 0;
		}
	}
	
	// --- Exponential Moving Average ---
	
	function getNextWeekEMA() internal returns (uint d)
	{
		uint dMa;
		uint nPeriod = 50;	// 50 weeks in a year
		if(m_nCurrentWeekTotal == 0)
			dMa = m_arrWeeksProfit[m_nCurrentWeek];
		else if(nPeriod >= m_nCurrentWeekTotal)
			nPeriod = m_nCurrentWeekTotal;
		else
		{
			uint dLastValue = 0;
			for(uint i = 0; i < m_nCurrentWeek; i++)
				dLastValue += m_arrWeeksProfit[i];
				
			for(i = m_nCurrentWeek + 1; i < nPeriod; i++)
				dLastValue += m_arrWeeksProfit[i];
			
			dLastValue /= nPeriod;

			dMa = m_arrWeeksProfit[m_nCurrentWeek] * (2.0 / (nPeriod + 1)) + dLastValue * (1 - 2.0 / (nPeriod + 1));
		}
		
		return dMa;
	}	
	
	// --- End of Handling 50 weeks of profit ---

	function() payable 
	{ 
		// You are not allowed to send money to the fallback. If you do... sorry.
	}

	// ---

	function shareHolderInvest() payable				// Buy "shares" at current price
	{
		if(msg.sender == m_addrOwner)
		{
			errorMessage("Owner can not invest");
			throw;
		}
		
		if(msg.value <= 0)
		{
			errorMessage("Insufficiend funds");
			throw;
		}	
		
		uint nAmountAfterComission = (100 - m_nTradeComission) * msg.value / 100;
		uint nShares = nAmountAfterComission / getSharePrice();
		if(nShares == 0)
		{
			errorMessage("Insufficiend funds");
			throw;
		}	

		uint nCurrentDay = (now - m_nTimeStarted) / 86400;

		uint nDailySupply = m_nTotalShares / m_nDailySupplyFraction;
		uint nSupplyForPeriod = nDailySupply * (nCurrentDay - m_nCurrentSharesDay);

		if(m_nCurrentSharesDay < nCurrentDay && m_nSharesAvailable + nSupplyForPeriod <= m_shareHolders[m_addrOwner])
		{
			if(m_nSharesAvailable == 0)
				m_nPriceOnDemandTotalIncrease += m_nPriceOnDemandIncrease;
			else if(m_nPriceOnDemandTotalIncrease > 0)
			{
				if(m_nPriceOnDemandTotalIncrease < m_nPriceOnDemandIncrease)
					m_nPriceOnDemandTotalIncrease = 0;
				else	
					m_nPriceOnDemandTotalIncrease -= m_nPriceOnDemandIncrease;
			}		
				
			m_nSharesAvailable += nSupplyForPeriod;
			m_nCurrentSharesDay = nCurrentDay;
		}	
		
		if(nShares > m_nSharesAvailable || nShares > m_shareHolders[m_addrOwner])
		{
			errorMessage("Request exceeded max. number of shares available");
			throw;
		}

		m_shareHolders[msg.sender] += nShares;
		m_shareHolders[m_addrOwner] -= nShares;
		m_nSharesAvailable -= nShares;
		m_numberOfSharesCurrentlyInPosession += nShares;
		
		// ---
		
		tradeEvent("buy", msg.sender, msg.value, nShares);
	}

	// ---

	function shareHolderWithdraw(uint nShares) 
	{
		if(msg.sender == m_addrOwner)
		{
			errorMessage("Owner can not withdraw");
			throw;
		}	
	
		if(nShares <= 0 || nShares > m_shareHolders[msg.sender])
			throw;

		m_shareHolders[msg.sender] -= nShares;
		m_shareHolders[m_addrOwner] += nShares;
		m_nSharesAvailable += nShares;
		m_numberOfSharesCurrentlyInPosession -= nShares;
		
		uint nWithdrawAmount = (100 - m_nTradeComission) * nShares * getSharePrice() / 100;
		
		msg.sender.transfer(nWithdrawAmount); 

		tradeEvent("sell", msg.sender, nWithdrawAmount, nShares);
   	}
	
	// ---

	function addToShareHoldersProfit(uint nProfit) internal
	{
		nProfit -= m_nProfitComission * nProfit / 100;

		// What happens here: We only add profit to shares that DO NOT belong to owner
		// The rest goes to owner (as he holds the rest of shares)
		m_nShareHoldersMoney += m_numberOfSharesCurrentlyInPosession * nProfit / m_nTotalShares;

		uint nCurrentDay = (now - m_nTimeStarted) / 86400; // (3600 * 24) = 86400
		if(m_nCurrentProfitDay < nCurrentDay)
		{
			profitEvent(m_nCurrentProfitDay, m_dailyProfit[m_nCurrentProfitDay], m_nShareHoldersMoney, m_numberOfSharesCurrentlyInPosession, getSharePrice());
			
			adjustCurrentWeek();
			
			m_nCurrentProfitDay = nCurrentDay;
			
			adjustCurrentWeek();
		}

		m_dailyProfit[nCurrentDay] += nProfit;
		m_arrWeeksProfit[m_nCurrentWeek] += nProfit;
	}	

	// ---
	
	function withdrawOwner() onlyOwner
	{
		// Find an intentional error!
		m_addrOwner.transfer(this.balance - m_nShareHoldersMoney);
	}

	// ---

	function getSharePrice() constant returns (uint nPrice) 
	{ 
		uint nAssetsPrice = 0;
	
		if(m_nShareHoldersMoney != 0 && m_numberOfSharesCurrentlyInPosession != 0) 
			nAssetsPrice = m_nShareHoldersMoney / m_numberOfSharesCurrentlyInPosession;
			
		uint nWeek = m_nCurrentWeek;
		if(m_nCurrentWeekTotal != 0)
		{
			if(nWeek == 0)
				nWeek = 49;
			else
				nWeek -= 1;
		}	
		uint nProfitPrice = 50 * m_arrWeeksProfit[nWeek];
			
		var nSharePriceCalculated = (nAssetsPrice + 5 * nProfitPrice) / m_nTotalShares;
		if(nSharePriceCalculated < m_nShareMinPrice)
			nSharePriceCalculated = m_nShareMinPrice;
			
		return nSharePriceCalculated;
	}
	
	// ---

	function getCurrentDay() constant returns (uint nProfit) { return m_nCurrentProfitDay ; }
	function getDailyProfit() constant returns (uint nProfit) { return m_dailyProfit[m_nCurrentProfitDay] ; }
	function getShareHolderBalance(address addr) constant returns (uint nBalance) { return m_shareHolders[addr] * getSharePrice(); }
	function getShareHolderShares(address addr) constant returns (uint nShares) { return m_shareHolders[addr]; }
	function getCumulativeShareHoldersProfit() constant returns (uint nProfit) { return m_nShareHoldersMoney; }
	function getNumberOfSharesCurrentlyInPosession() constant returns (uint nShares) { return m_numberOfSharesCurrentlyInPosession; }
	function getOwnersMoney() constant returns (uint nAmount) { return this.balance - m_nShareHoldersMoney; }
	function getInitBlock() constant returns (uint nInitBlock) { return m_deployedAtBlock; }
	function getTimeStarted() constant returns (uint nTime) { return m_nTimeStarted; }
	function getTradeComission() constant returns(uint nTradeComission) { return m_nTradeComission; }
}


// ------

contract DukeOfEther is ShareHolder
{
	string m_strNickName = "";    
	uint m_nDukeDate = 0;
	address m_addrCurrentDuke;
	uint m_nCurrentDukePaid;			// Cost current Duke paid
	uint m_nDukeOwnersMoney = 0;

	function DukeOfEther() ShareHolder()
	{
		m_addrCurrentDuke = msg.sender;
		m_nCurrentDukePaid = 0;	
		m_nDukeDate = now;
		m_strNickName = "Vacant";
	}

	event updateDukeStatus(string strNickName, address indexed addrCurrentDuke, uint nCurrentDukePaid, uint nMinNextBet, uint date);
	event updateDukeHistory(string strNickName, address indexed addrCurrentDuke, uint nCurrentDukePaid, uint nMinNextBet, uint date);

	// ---

	function becomeDuke(string strNickName) payable
	{
		if(msg.value < getMinNextBet())
			throw;

		uint nFee = msg.value / 25;	// 4%
		addToShareHoldersProfit(nFee);
		
		uint nOwnersFee = msg.value / 100;	// 1%
		m_nDukeOwnersMoney += nOwnersFee;

		uint nPrevDukeReceived = msg.value - nFee - nOwnersFee;
		// Add info about the prev. Duke to the history
		updateDukeHistory(m_strNickName, m_addrCurrentDuke, m_nCurrentDukePaid, nPrevDukeReceived, m_nDukeDate);

		m_addrCurrentDuke.transfer(nPrevDukeReceived);
		m_addrCurrentDuke = msg.sender;
		m_nCurrentDukePaid = msg.value;

		m_nDukeDate = now;
		m_strNickName = strNickName;

		updateDukeStatus(strNickName, m_addrCurrentDuke, m_nCurrentDukePaid, getMinNextBet(), now);
	}
	
	// ---
	
	function withdrawDukeOwnersMoney() onlyOwner
	{
		m_addrOwner.transfer(m_nDukeOwnersMoney);
	}
	
	// ---
	
	function getDukeNickName() constant returns (string date) { return m_strNickName; }
	function getDukeDate() constant returns (uint date) { return m_nDukeDate; }
	function isDuke() constant returns (bool bIsDuke) { return (m_addrCurrentDuke == msg.sender); }
	function getCurrentDuke() constant returns (address addr) { return m_addrCurrentDuke; }
	function getCurrentDukePaid() constant returns (uint nPaid) { return m_nCurrentDukePaid; }
	function getMinNextBet() constant returns (uint nNextBet) 
	{
		if(m_nCurrentDukePaid == 0)
			return 1 finney;

		return  15 * m_nCurrentDukePaid / 10; 
	}
}







(C) snowcron.com, all rights reserved

Please read the disclaimer