Ethereum Tutorials

SMART CONTRACTS

"Duke of Ether!" Contract (part 2)

The Ownable parent contract

It is time to create a new (still not final, plus no events - see the corresponding chapter, and for integration with ShareHolder contract, which is absolutely not necessary here) version of our "Duke of Ether". First of all, we need to move out the functionality that is used by more than just Duke contract. In our case, it is going to be everything related to "owner" of the contract and the way we manage it.

The reason is simple: if we ALWAYS use same set of functions (like "isOwner"), then we want to have a STANDARD block of well-tested code, rather than inventing the wheel over and over again. (I am going to talk about other "standard" building blocks later in this book)

The following contract manages the ownership, and can be used as a parent of your contract, if you need the functionality:

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

}

The contract stores the owner's address (performed in a constructor), it creates the "onlyOwner" modifier that restricts access to certain functions to only the owner's address, it allows the owner (and only the owner) to transfer the ownership to another address, finally, it allows to check if a particular address is the owner.

Walk through the code

Note that we are going to revisit this code once again when talking about maximizing the profit.

pragma solidity ^0.4.11;

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

}

// ------

contract DukeOfEther
{
	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; 
	}
}

Let us examine the code.

The member variable m_strNickName is used in so called events:

				
string m_strNickName = "";    
...
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);

Events are functions that an HTML page (having access to web3.js library and Geth) can use. It is called from the contract's code every time you want to notify your Web page that something happened in a contract. For example:

In contract you broadcast it:
function becomeDuke(string strNickName) payable
{
	...
	updateDukeStatus(strNickName, m_addrCurrentDuke, m_nCurrentDukePaid, getMinNextBet(), now);
}

In HTML (in JavaScrip code) you catch it:

var updateDukeStatusEvent = contract.updateDukeStatus({_from: web3.eth.coinbase});
updateDukeStatusEvent.watch(function(err, result) 
{
	if(err) 
	{
		document.getElementById("error_message").style.display = "block";
		document.getElementById("error_message").text = err;
		return;
	}

	document.getElementById("error_message").style.display = "none";
			
	console.log(result.args);
			
	var rawDate = new Date(result.args.date * 1000).toISOString();
	var date = rawDate.slice(0, 10) + " " + rawDate.slice(11, 19);
	nNextMinBet = result.args.nMinNextBet.shift(-18).toNumber();
			
	fill_duke_info(result.args.strNickName, nNextMinBet, 
		result.args.addrCurrentDuke, result.args.nCurrentDukePaid.shift(-18).toNumber(), date);
});

I am going to discuss events later on.

We store few more variables holding the status of a contract:

		
uint m_nDukeDate = 0;
address m_addrCurrentDuke = 0;
uint m_nCurrentDukePaid = 0;
uint m_nDukeOwnersMoney = 0;

Note that these variables are initialized, simply to make the code neat. In Solidity, an uninitialized variable is assigned zero, so it is not really necessary.

The constructor

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

... performs the "real" initializing.

The becomeDuke() function looks more complex now:

				
	function becomeDuke(string strNickName) payable
	{
		// Making sure enough funds transfered
		if(msg.value < getMinNextBet())
			throw;

		// Share of the profit a contract owner receives
		uint nOwnersFee = msg.value / 100;	// 1%
		m_nDukeOwnersMoney += nOwnersFee;
		
		// Share of the profit tha previous Duke receives
		uint nPrevDukeReceived = msg.value - nOwnersFee;
		
		// Add info about the prev. Duke to the history (see chapter about events)
		updateDukeHistory(m_strNickName, m_addrCurrentDuke, m_nCurrentDukePaid, nPrevDukeReceived, m_nDukeDate);

		// Send money to an old Duke
		m_addrCurrentDuke.transfer(nPrevDukeReceived);
		
		// if transfer fails, still put a new Duke on the Throne - it is safe to ignore potential errors
		m_addrCurrentDuke = msg.sender;
		m_nCurrentDukePaid = msg.value;
		m_nDukeDate = now;
		m_strNickName = strNickName;

		// Call another event holding new Duke's info
		updateDukeStatus(strNickName, m_addrCurrentDuke, m_nCurrentDukePaid, getMinNextBet(), now);
	}

Withdraw function: see if you can have an intentional error here. Note, that there are many contracts out there with this sorts of errors as it is... well... profitable.

	
function withdrawDukeOwnersMoney() onlyOwner
{
	m_addrOwner.transfer(m_nDukeOwnersMoney);
}

Constant functions returning the status of a contract

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

There are reasons this contract is not final. First, the code has (intentional) errors. Second, it will only work once (one chain of Dukes, ending when the bet is too high), then the last Duke's money will be stuck forever as no one will bet more. Which is bad both for Dukes and for us as contract owners. I am going to address this issue later. Third, we need to promote that contract somehow which, too, will be discussed later.







(C) snowcron.com, all rights reserved

Please read the disclaimer