Ethereum Tutorials

"ShareHolder" Contract

Full text of the contract (See additional comments in code). Please note, that this is a simplified version for the tutorial purposes only. To see a full version of the code, check it on etherscan site or in a "bounty" section of this site.

				
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; 
	}
}







Visibility is very important for this site. If you like it please link from your page to this URL or share info using Social Buttons below.
(C) snowcron.com, all rights reserved

Please read the disclaimer