Tutorial 2: Rainbow Coin

KenKKenK Posts: 44Member admin
edited December 2014 in Education
This tutorial currently works with the VM shared with the community here. If you are currently using the newest build of the client (POC7) please check the bottom of the tutorial for the new code.

In this tutorial we are going to take what we learnt in the previous tutorial and build upon it to create a contract which can hold several different tokens - seven in all. In doing so we will learn about structuring a contracts data storage.

The sub-currency is one of the simplest contract examples to learn - in a few lines of code you are able to produce a crypto-currency with all the functionality of an alt-coin imbued with the trust of ethereum's consensus and cryptographic security. We are going to take this and build on top of it - but first let us go through the previous example in a bit more detail.

Once again we will be using a private chain to build on so first kill your blockchain, restart your client and select private chain from the debug menu.

The original Sub-Currency

The code for a sub-currency is below:
def init():
  self.storage[msg.sender] = 10000
def code():
  to = msg.data[0]
  from = msg.sender
  value = msg.data[1]
  if self.storage[from] >= value:
    self.storage[from] = self.storage[from]  - value
    self.storage[to] = self.storage[to] + value
Its operation is explained in detail in the previous tutorial but I will just briefly go over a few things again.

The first method named ‘init’ is executed when you submit the contract to the blockchain, it is the area where you can set the initial state of the self.storage, and it is only run once. In this example it has created a tuple with a key equal to the contract-senders public address and a value of 10,000.

The ‘code’ method executes whenever someone sends a transaction to the contract - in this case it allows the transaction sender to move coins from one account to another by modifying the state of self.storage in a limited way, if certain conditions are met:
if self.storage[from] >= value:
    self.storage[from] = self.storage[from]  - value
    self.storage[to] = self.storage[to] + value
The first line checks if the senders address is a key in self.storage, and if the value in that tuple is greater than or equal to the value specified in the senders transaction. If it evaluates ‘true’ then the next two lines of the code will make changes to the self.storage[from] tuple and the self.storage[to] tuple.

This is a common operation in code execution - when a transaction is received the contract will perform a series of conditional operations on the information provided by the transaction and decide if/how the state should be modified.

Typical transactions include the following fields: msg.sender, msg.value, a function id, multiple arguments, Gas, and Gas price. Msg.sender and Msg.value are the public address associated with the private key of the sender and the value of ether sent in the transaction respectively. This makes them very important fields for dapp developers as they allow them to write contracts which can: verify who a sender is, and verify a certain amount of ether has been included in the transaction. This is confirmed by the ethereum network ‘under the hood’ and you can trust the information in these fields to have been ‘proven’ by the network.

Arguments are passed to function in a 32 byte array which is sent along with the transaction. As opposed to msg.sender or msg.value - the transaction sender can put anything they want in there - so in general every operation to alter a contracts.storage should involve at least a conditional check on the values in either the msg.sender or the msg.value fields.

As I mentioned, if the conditional statement in the above code is true then the self.storage can be modified. self.storage is a key-value tuple with 256 bit length. This means the maximum number of possible keys is 2^256 (~~ 0.0012 × the number of atoms in the visible universe!), more space than could ever possibly be filled by humanity. In the example sub-currency contract, account addresses are used as keys to store the number of coins held by account holders. Accounts addresses are strings 160 bits in length; As 2^160
def init():
	self.storage[msg.sender] = 10000
	self.storage[2**161] = “Ken Coin”
def code():
	to = msg.data[0]
	from = msg.sender
	value = msg.data[1]
	if self.storage[from] >= value:
		self.storage[from] = self.storage[from] - value
		self.storage[to] = self.storage[to] + value
I have decided to add a small metadata tag to my contract with the string “Ken’s Coin” at self.storage[2**161], which is not a valid public key - this is a pretty innocuous change but it introduces a problem; The only conditional in our contract is that the self.storage[msg.sender] is greater than or equal to msg.data[1]. If that conditional is met the transaction sender can change the value of any key value pair he specifies by sending coins to it - this includes my metadata tag at self.storage[2^161], potentially allowing someone to vandalise my metadata tag by sending coins to it.

In order to avoid problems like this it is important to tightly define where a transaction can and cannot alter a state:
def init():
	self.storage[msg.sender] = 10000
	self.storage[2**161] = “Ken Coin”
def code():
	to = msg.data[0]
	from = msg.sender
	value = msg.data[1]
	if self.storage[from] >= value and to <= 2**160:
		self.storage[from] = self.storage[from] - value
		self.storage[to] = self.storage[to] + value
This amendment keeps transactions from altering self.storage with keys greater than or equal to 2^160 - which is the max length of valid addresses. It is important that you ‘ringfence’ certain key values in self.storage to avoid accidental or intentional collisions as you add more data. self.storage has more than enough available keys to allow you to do this.

Building a table from self.storage

You can think of the subcurrency example above as using the contracts data storage as a spreadsheet with one column and 2^160 rows - most of them empty. Whenever a transaction moves coins from one account to another we find the row equal to the account number and change the value in the cell next to it.

Now imagine you wish to add an extra column or columns to your spreadsheet - perhaps you want to add a name to each account. This might seem difficult using a dictionary like self.storage - but in fact it is quite simple.
Account number Number Of Coins Name of account
21ce9a2f8474a91268208862da395a6bf21f7784 500 “Ken”
If we wanted to store the above information in the contracts storage we would run the following two operations:
self.storage[21ce9a2f8474a91268208862da395a6bf21f7784] = 500
This is exactly the same as the operation we have seen before in our subcurrency example above, however;
self.storage[2**160 + 21ce9a2f8474a91268208862da395a6bf21f7784] = “Ken”
This is new - I have added the value 2**160 to the address to create a new key value pair storing my account name. 2**160 is the total number of possible rows in our table(remember the maximum length of an account is 160 bits) and by adding this to our address we have moved to an unused area of the self.storage to place our second column. If we wished to add a third column:
Account number Number Of Coins Name of accountColour
21ce9a2f8474a91268208862da395a6bf21f7784 500 “Ken”"Red"
self.storage[2 x (2**160) + 21ce9a2f8474a91268208862da395a6bf21f7784] = “Red”
This shifts the key by another 2**160 rows and assigns the value “Red” - this is how key values are calculated for cells - whenever you add a new column you offset the value by the number of rows in the array multiplied by the column number.

If we think in terms of tables then the Key value for use in the self.storage can be defined by the formula below:

Key = row number + (total number of rows x column number) + constant

Where the ‘constant’ is a number used to choose where in the self.storage we wish to position the array (typically 0). This formula is used for both storing the array and for later recalling the information stored.

The example below is a five by five array containing values:
Column 0 Column 1 Column 2 Column 3 Column 4
Row 0 17 3 32432 4333 2343
Row 1 7 87 687 32 322
Row 2 53 47 22 56 8
Row 3 3 4 88 54 889
Row 4 22 5 3434 53345 5443
Using the formula above we can calculate the keys for each cell and store the array in self.storage.

The top left cell is in row 0 column zero and has the value 17, therefore:

Key = 0 + (5 x 0) + 0 = 0
self.storage[0] = 17
Correspondingly the bottom right cell is in row 4 column 4 and has the value 5443, therefore:

Key = 4 + (5 x 4) + 0 = 24
self.storage[24] =5443
Post edited by KenK on

Comments

  • KenKKenK Posts: 44Member admin
    edited December 2014
    The full list of self.storage values calculated is below, try calculating one yourself:
    Key Value
    self.storage[0] 1
    self.storage[1] 7
    self.storage[2] 53
    self.storage[3] 3
    self.storage[4] 22
    self.storage[5] 3
    self.storage[6] 87
    self.storage[7] 47
    self.storage[8] 4
    self.storage[9] 5
    self.storage[10] 32432
    self.storage[11] 687
    self.storage[12] 22
    self.storage[13] 88
    self.storage[14] 3434
    self.storage[15] 4333
    self.storage[16] 32
    self.storage[17] 56
    self.storage[18] 54
    self.storage[19] 53345
    self.storage[20] 2343
    self.storage[21] 322
    self.storage[22] 8
    self.storage[23] 889
    self.storage[24] 5443
    As you can see this 5 by 5 array occupies the first 25 entries in self.storage - if we wanted to add a further array (for example 3 by 3) we would use the same Key value formula for writing and reading to storage, but this time set the constant to 25 so as not to have key values which collide with our previous array:

    Key value = row number +(total number of rows x column number) + 25
    23 78 7
    33 9 9
    67 44 4
    Is entered into our self.storage as:
    self.storage[25] 23
    self.storage[26] 33
    self.storage[27] 67
    self.storage[28] 78
    self.storage[29] 9
    self.storage[30] 44
    self.storage[31] 7
    self.storage[32] 9
    self.storage[33] 4
    Using this method it is possible to add as many arrays as you like as long as the total size of all the arrays does not exceed 2**256.

    It is important to note that the formula above is only one option for storing tabular information into self.storage - there are many equally good alternatives - this one however is rather simple to use and understand and is sufficient to build our new multi coin sub-currency contract.

    Rainbow-Coin contract

    This contract is similar to the subcurrency example except that it contains 7 different coins in one contract: Red, Orange, Yellow, Green, Blue, Indigo and Violet. These coins will be held in seperate accounts, within the self.storage.

    Starting with our original subcurrency example:
    def init():
    	self.storage[msg.sender] = 10000
    def code():
    	to = msg.data[0]
    	from = msg.sender
    	value = msg.data[1]
    	if self.storage[from] >= value and to <= 2**160:
    		self.storage[from] = self.storage[from] - value
    		self.storage[to] = self.storage[to] + value
    Firstly we want to initialise the contract with a certain number of coins under control of the msg.sender. We plan to store the number of coins in an array of size 2^160 x 7 inside self.storage.

    We use the formula from above:

    Key value = row number +(total number of rows x column number) +constant

    Where row number = account number, total number of rows = 2^160, column number = 0-6(representing the 7 colours), and the constant = 0.

    So our initial state of:
    RedOrangeYellowGreen Blue Indigo Violet
    self.storage[msg.sender] 10000100001000010000100001000010000
    Should be:
    def init():
    	self.storage[msg.sender + (0 * 2**160)] = 10000 #Red
    	self.storage[msg.sender + (1 * 2**160)] = 10000 #Orange
    	self.storage[msg.sender + (2 * 2**160)] = 10000 #Yellow
    	self.storage[msg.sender + (3 * 2**160)] = 10000 #Green
    	self.storage[msg.sender + (4 * 2**160)] = 10000 #Blue
    	self.storage[msg.sender + (5 * 2**160)] = 10000 #Indigo
    	self.storage[msg.sender + (6 * 2**160)] = 10000 #Violet
    


    We now have 10,000 coins of each colour in self.storage - next we need to specify the code which allows coins to be moved to other accounts:
    def code():
    	to = msg.data[0]
    	from = msg.sender
    	value = msg.data[1]
    	coin = msg.data[2]
    	if self.storage[from + (coin * 2**160)] >= value and to <= 2**160:
        	self.storage[from + (coin * 2**160)] = self.storage[from +(coin * 2**160)]  - value
        	self.storage[to + (coin * 2**160)] = self.storage[to+ (coin * 2**160)] + value
    The changes made should make sense if you have followed the previous section. Firstly we have a new variable from our transaction called “coin” this is a number between 0-6 which represents which colour coin the transaction is for. Our if statement uses this “coin” variable and msg.sender to read from the self.storage where the msg.sender’s coin balance is listed, and if it finds the sender has sufficient balance of that coin it transfers the value to the ‘to’ address’ balance.

    Try submitting the full contract to the ethereum network yourself:
    def init():
    	self.storage[msg.sender + (0 * 2**160)] = 10000 #Red
    	self.storage[msg.sender + (1 * 2**160)] = 10000 #Orange
    	self.storage[msg.sender + (2 * 2**160)] = 10000 #Yellow
    	self.storage[msg.sender + (3 * 2**160)] = 10000 #Green
    	self.storage[msg.sender + (4 * 2**160)] = 10000 #Blue
    	self.storage[msg.sender + (5 * 2**160)] = 10000 #Indigo
    	self.storage[msg.sender + (6 * 2**160)] = 10000 #Violet
    def code():
    	to = msg.data[0]
    	from = msg.sender
    	value = msg.data[1]
    	coin = msg.data[2]
    	if self.storage[from + (coin * 2**160)] >= value and to <= 2**160:
    		self.storage[from + (coin * 2**160)] = self.storage[from +(coin * 2**160)]  - value
    		self.storage[to + (coin * 2**160)] = self.storage[to+ (coin * 2**160)] + value
    If everything worked correctly your submitting address should now have 10,000 of each coin in the contract - you can check this manually in the contracts window of the Alethzero browser. In order to send coins between addresses you can send a transaction to the contract using the Alethzero transact pane specifying the coin on the third line:




    And that is it! We now have multiple sub-currencies in one contract; a very simple demonstration of how to organise the storage of data inside contracts. If you have any questions or comments please leave a reply below and I will try and get back to you.

    POC7 code:
    def init():
    	self.storage[msg.sender + (0 * 2**160)] = 10000 #Red
    	self.storage[msg.sender + (1 * 2**160)] = 10000 #Orange
    	self.storage[msg.sender + (2 * 2**160)] = 10000 #Yellow
    	self.storage[msg.sender + (3 * 2**160)] = 10000 #Green
    	self.storage[msg.sender + (4 * 2**160)] = 10000 #Blue
    	self.storage[msg.sender + (5 * 2**160)] = 10000 #Indigo
    	self.storage[msg.sender + (6 * 2**160)] = 10000 #Violet
    def send(to, value, coin):
    	from = msg.sender
    	if value <= self.storage[from + (coin * 2**160)] and to <= 2**160:
    		self.storage[to + (coin * 2**160)] += value
    		self.storage[from + (coin * 2**160)] -= value
    Post edited by KenK on
  • o0ragman0oo0ragman0o Posts: 1,251Member, Moderator mod
    edited December 2014
    As hinted at by the PoC7 code adjustment in Tutorial 1, this PoC7 code works for me for Tutorial 2:
    def send(to, value, coin):
    from = msg.sender
    if value <= self.storage[from + (coin * 2**160)] and to <= 2**160:
    self.storage[to + (coin * 2**160)] += value
    self.storage[from + (coin * 2**160)] -= value
    Calling e.g.:
    $0x00
    0xda186c837585316cf92e52a380ef6fde24c44cdd
    500
    0x02
  • stevexstevex Posts: 4Member
    I pasted in the PoC code into AlethZero but just get a parse error. Alethzero is today's build (Ubuntu package) - PoC8?
Sign In or Register to comment.