Ethereum (ERC-721) Dog Adoption Solidity smart contract

 

Video link: https://youtu.be/mziWxy2k_pE

Git Repository: https://github.com/Ashot72/solidity-ERC-721--dogadoption-contract

This is a dog adoption Ethereum smart contract. A dog owner (someone from animal rescue organization) posts a dog (dog owner info and dog picture) for an adoption

via Ethereum smart contract. A dog owner should be certain that a possible dog adopter already owned a dog (dog adopter info and dog picture) before bringing a new dog home.

The contract is based on ERC-721 http://erc721.org/ open standard that describes how to build non-fungible or unique tokens on the Ethereum blockchain.

It is always better to start developing Ethereum Smart contracts in Remix https://remix.ethereum.org. Remix is a suite of tools to interact with the Ethereum

blockchain in order to develop, test, debug and deploy smart contracts and much more.

 

Figure 1

First, we select all ERC721 related contracts, libraries, and our contract (ballot.sol) and add them to the Browser Storage Explorer.

 

Figure 2

Files are located in the Browser Storage Explorer and JavaScript VM was selected as an Environment option. With this option transactions will be executed

In a sandbox blockchain in the browser. This means nothing will be persisted and a page reload will restart a new blockchain from scratch, the old one will

not be saved. Remix uses a private blockchain and accounts by default that you can use for testing.

 

Figure 3

The contract's compiler version is 0.4.24. The pragma ensures that the source code is only meant for compiler versions that are not older than 0.4.24. You can select another compiler

version from Select new compiler version drop-down.

 

Figure 4

For Analysis we do not change anything; just default settings with all checkboxes selected.

 

Figure 5

First, we should deploy TokenRepository contract. This contract contains the list of tokens registered by users. Only users with ERC721 token are allowed to participate in a dog adoption.

We should specify both name and symbol for TokenRepository when deploying. It can be anything, for example name Minsait Token, token MNST.

 

Figure 6

We specify 'Dog adoption name' as a name and 'Dog adoption symbol' as a symbol and deploy the contract by first account which is 0xca35b7d915458ef540ade6068dfe2f44e8fa733c.

 

Figure 7

TokenRepository Instance is created. registerToken function takes two arguments tokenId and uri.

A dog owner info and picture will be stored on IPFS (Interplanetary File System) https://ipfs.infura.io/ipfs/ as Ethereum is too heavy/expensive to store large blobs like images, video etc.

So, we store info on IPFS and set IPFS hash as uri in registerToken token. In general, we will have two hashes.

 

Figure 8

I built another small app and uploaded dog picture to IPFS. The hash is QmTsVe9FGHGL2TYK7mEadYzqs9czpp3rcrrfByFNKjrxm6 in https://ipfs.infura.io/ipfs/QmTsVe9FGHGL2TYK7mEadYzqs9czpp3rcrrfByFNKjrxm6

Someone knowing the picture hash can retrieve it. It is not your company's private database where data are stored securely. It is a decentralized and distributed storage and publicly accessible.

So, we uploaded an owner's dog (someone from animal rescue organization) picture and obtained the hash.

 

Figure 10

I also uploaded owner's info https://ipfs.infura.io/ipfs/Qmb2EUFUUaqQmiaQjKVv3jfEs2RcgZJ3fs1Hgc4B79aCYc and the hash is Qmb2EUFUUaqQmiaQjKVv3jfEs2RcgZJ3fs1Hgc4B79aCYc.

It includes owner's name, phone and info.

 

Figure 11

We are about to regiserToken. tokenId should be some random number but for simplicity we set to 1111.

 

Figure 12

Token 1111 is registered.

 

Figure 13

If try to click regiterToken twice with the same tokenId which is 1111 you will get an error. The reason is that tokens in ERC721 standard should be unique.

 

Figure 14

Let's register another token 11112 by the same account 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

 

Figure 15

Here is the transaction output.

 

Figure 16

We create third token but this time by second account which is 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c

 

Figure 16

In Remix, we have two button colors. Red color for transaction such as transform, grayish one for call such as balanceOf.

Transaction is broadcasted to the network, processed by miners, and if valid, is published on the blockchain (it costs some gas).

Call  is a local invocation of a contract function that does not broadcast or publish anything on the blockchain (it does not cost any gas and it is free to use).

You see some call functions of ERC721 open standard I highlighted.

totalSupply uint representing the total amount of tokens. There are three tokens so far. Two tokens created by first account (1111, 11112) and the last one (2222) by second account.

tokenURI uint ID of the token to query. tokenURI of 1111 is hash1 registered by first account.

Symbol token symbol. It is Dog adoption symbol

ownerOf owner address currently marked as the owner of the given token ID. Owner of token 1111 is first account 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

name token name. It is Dog adoption name.

getOwnerTokens list of owned token IDs for the specified address. Token IDs for address 0xca35b7d915458ef540ade6068dfe2f44e8fa733c are 1111, 11112

exists returns whether the specified token exists. TokenId 1111 exists.

balanceOf uint representing the amount owned by the passed address. The amount owned by first address 0xca35b7d915458ef540ade6068dfe2f44e8fa733c is two. (1111 and 11112)

 

Figure 17

Let's deploy the second contract DogAdoptionRepository. This contract allows dog adopters to be created for non-fungible tokens and participate in dog adaption.

 

Figure 18

DogAdoptionRepository instance is created. This contract does not allow direct payments (fallback function).

fallback function is called when someone just sent Ether to the contract without providing any data or if someone messed up the types so that they tried to call a function

that does not exist.

 

Figure 19

We have DogAdoptionRepository instance deployed and it is time to create a dog adoption. As a reminder, a dog adoption is created by a dog owner (someone from animal rescue organization).

We should specify a dog adoption name, pass token repository instance address, tokenId of a dog owner and endBlock (which we will discuss soon).

 

Figure 20

We specified required parameters in createDogAdoption function.

 

Figure 21

Token repository instance address is 0x692a70d2e424a56d2c6c27aa97d1a86395877b3a. We passed first account's 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

tokenId which is 1111.

 

Figure 22

We clicked ceateDogAdoption button and got a transaction error message Contract is not owner of given token.

 

Figure 23

createDogAdoption function is controlled by two modifiers; contractIsTokenOwner and beforeEnd.

Modifier is a piece of code which consist some validation kind of rules or logic defined by the developer. This piece of code is for the reusability purpose.

It is pass in while defining the function in solidity. The purpose of it is to do some validation before running that respective function. If the validation is failed then the respective function

does not execute.

 

Figure 24

The owner of tokenId passed to this modifier must be a dog repository instance. Let’s figure it out.

 

 

Figure 25

The owner of tokenId 1111 is first account 0xca35b7d915458ef540ade6068dfe2f44e8fa733c not dog repository instance 0xef55bfac4228981e850936aaf042951f7b146e41

What we have to do is to transfer the ownership of this token 1111 from 0xca35b7d915458ef540ade6068dfe2f44e8fa733c to 0xef55bfac4228981e850936aaf042951f7b146e41 address

 

Figure 26

We can do it by means of ERC721 open standard's transferFrom function to transfer the ownership of a given token ID to another address.

 

Figure 27

After translatefrom successful transaction we can see that owner of tokenId 1111 is dog repository instance 0xef55bfac4228981e850936aaf042951f7b146e41 and that instance

currently owns just one token 1111.

 

Figure 28

Let's try to create a dog adoption one more time. This time we got another error Dog adoption endBlock must be greater than the current Ethereum block number.

 

Figure 29

The last parameter of createDogAdoption is endBlock. We put 55. The current block number is 1150011. The starting block number in Remix is 1150006. It is just a block number to start from.

 

Figure 30

You should remember second modifier on createDogAdoption function (Figure 23) which was beforeEnd. Transaction failed because 55 is less than 1150011.

Time in Solidity is a bit tricky, block timestamps are set by miners, and are not necessarily safe from spoofing. A best practice is to demarcate time based on block number. We know that Ethereum blocks are

generated roughly every 15 seconds so we can infer timestamps from these numbers rather than the spoofable timestamp fields.

 

Figure 31

Let's see it on rinkeby test network https://rinkeby.etherscan.io/

The last block that was mind was 3459854 mined 25 seconds ago. The previous one was 3459853 mined 40 seconds ago (difference is 40 minus 25 = 15) and so on.

Now, we can set endBlock of createDogAdoption function knowing block generation internals.

 

Figure 32

We replaced 55 with 1160011 which is greater than 1150011. A dog adoption ends after reaching the end block unless it is approved or canceled by a dog owner.

 

Figure 33

We tried createDogAdoption one more time and succeeded. dogAdoptionsCount is 1 as we just created first dog adoption. You could also see first dog adoption info.

Now adopters can participate in created dog adoption. It is similar to dog adoption owner approach. A dog adopter should register a token, transfer the ownership to the contract and participate.

A dog owner (someone from animal rescue organization) should be certain that a possible dog adopter already owned a dog (dog adopter info and dog picture) before bringing a new dog home.

We do it the exact way as we did for a dog owner.

 

Figure 34

Hash of a dog adopter is QmTXUdjeYzHvM9UBcKeP9jABV1pR5acG8yna6uNPi7niV4 in https://ipfs.infura.io/ipfs/QmTXUdjeYzHvM9UBcKeP9jABV1pR5acG8yna6uNPi7niV4

 

Figure 35

A dog adopter info https://ipfs.infura.io/ipfs/Qmb3W7No6m89aKYQTnx7TwGN81Juz89hG6T3CasZsMa5BX  and hash is Qmb3W7No6m89aKYQTnx7TwGN81Juz89hG6T3CasZsMa5BX.

It happened that this adopter already adopted a dog before.

 

Figure 36

addDogAdopter function requires three parameters; dogAdoptionId which is 0, token registration address and an adopter tokenId. There are some restrictions to addDogAdopter function.

A dog owner should transfer ownership to the contract before participating contractIsTokenOwner. Dog adoption owner cannot be a dog adopter isNotOwner. In order words a dog owner should not adopt his dog

which is senseless. The last restriction is that a dog adoption should be active.

 

Figure 37

A dog adoption is active if it is neither canceled nor approved be the owner and it has not yet ended.

 

Figure 38

 

We specified tokenId 2222 in addDogAdopter function. The owner of this token is second account with 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c address.

 

Figure 39

 

Dog adopters can also send some Wei (money) to a dog owner while adding themselves as a dog adopter. I want to emphasize that it is not a required at all and this contract is not a bidding contract.

A dog owner will not select an adopter based on ether sent to them. The important thing for a dog owner is to find a dog adopter who will take care of a dog. It is up to a dog adopter to send some wei to an owner to support

the animal rescue organization. For demo purposes I selected 5 ether. So, a dog adopter 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c is going to add himself as a dog adopter by sending 5 ether which will go

to the animal rescue organization if selected as a winner.

 

 

Figure 40

We got the same transaction error as we forgot to transfer ownership of 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c to dog adoption address.

 

Figure 41

We ran transferFrom function successfully. tokenId 2222 is owned by dog adopter instance. Now dog adopter instance owns two tokens 1111 and 2222.

 

Figure 42

Transaction run successfully and 5 ether was sent to contract.

 

Figure 43

Account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c owns 94 ether instead of 99 (Figure 39). 5 ether was sent.

 

Figure 44

 

The important thing to take into consideration is that money did not go to dog owner directly but dog adoption contract. You see that the current balance of the contract is 5 ether.

 

Figure 45

Wei is the default unit to maintain funds in smart contract. You can go to https://etherconverter.online/ to see conversion in different units.

 

Figure 46

First, we get dogAdopterAddressesCount for the specified dog adoption instance - dogAdoptionId. The count is 1 as we have just one dog adopter. Then we get the dog adopter info by passing dog adoption id which is 0 and

dog adopter position which is zero based and is 0 as well.

 

Figure 47

Let ' add another dog adopter to dog adoption contract. This time we selected third account 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db, registered token 3333

and transformed the ownership to dog adoption instance.

 

Figure 48

Owner of tokenId is dog adoption instance and it owns three tokens 1111, 2222, 3333

 

Figure 49

For this dog adopter we specify 10 ether.

 

Figure 50

We added second dog adopter. The contract balance went to 15 ether.

 

Figure 51

Account 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db owns 99 minus 10 = 89 ether now.

 

Figure 52

Dog adopters count for the specified dog adoption instance is 2 now. We can retrieve second dog adopter info similarly.

 

Figure 53

Now, the dog owner (who created dog adoption instance) is going to approve second account who was the first adopter sending 5 ether to the contract. Transaction error telling that

Message sender is not dog adoption owner.

 

Figure 54

The reason is that we ran approveDogAdopter function from second account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c who was not dog adoption instance creator.

We should switch to first account 0xca35b7d915458ef540ade6068dfe2f44e8fa733c who owns 99 ether at this moment and run approveDogAdopter again.

 

Figure 55

Transaction passed successfully and the contract balance went from 15 ether to 10.

 

Figure 56

Dog owner balance increased from 99 to 104. 5 ether went to his account.

 

Figure 57

First account 0xca35b7d915458ef540ade6068dfe2f44e8fa733c owns only token 11112

 

Figure 58

Second account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c which was chosen as a winner by dog owner now owns two tokens 2222, 1111. Token 2222 was initially created by the

same account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c then transferred its ownership to dog adoption instance 0xef55bfac4228981e850936aaf042951f7b146e41. After he was chosen

as a winner, dog adoption contract 0xef55bfac4228981e850936aaf042951f7b146e41 transferred the ownership back to account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c.

In other words, the winner got his token back. Winner also got token 1111 which was initially created by dog owner 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

and also transferred that token to dog adaption instance. After he was chosen as a winner the dog adoption instance transferred the ownership of that token 1111 to winner account

0x14723a09acff6d2a60dcdf7aa4aff308fddc160c. In other words, dog owner chose second account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c as a winner, received 5 ether,

and passes token 1111 created by himself to winner account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c via dog adoption contract.

 

Figure 59

Dog adoption instance owns just token 3333. That is the token registered by third account 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db.

 

Figure 60

If dog owner tries to execute approveDogAdopter function second time then an error Dog adoption is either canceled or approved or ended will be raised. In our case dog owner already approved it.

 

Figure 61

We called dogAdoptions function and could see that dog adoption instance is approved by owner 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

 

Figure 62

We also can see that first chosen dog adopter, which is second account 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c is the winner. You see amount 5 ether.

This only shows that 0x14723a09acff6d2a60dcdf7aa4aff308fddc160c account sent 5 ether to dog adoption contract. It is not a balance.

 

Figure 63

Second dog adopter, which is third account 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db is not a winner. Note, that this dog's adopter ether is still belongs to dog adoption contract.

Dog adoption contract owns 10 ether (Figure 60) and that money was sent by second dog adopter 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db.

As this dog adoption instance is closed via approving which means that a winner was selected then other dog adopters should also somehow get their money back. Not only they should get their ether

but also tokens that transferred to dog adoption contract.

 

Figure 64

There is withdraw function. Withdraw may not be the best function name as the function not only withdraws a dog adaptor ether but also transferring ownership back to dog adopters.

We should recall that dog adopters ware not forced to send ether to contract as it was not required. But dog adapters should still call withdraw function to get their tokens back. That is the

reason that withdraw is not the best name for this function. There are two modifiers attach to this function; dog owner cannot call this function as he is not a dog adopter but owner (isNotOwner) and

the adoption instance should not be active (isNotActive), meaning that dog adoption instance should be either canceled, approved or ended.

 

Figure 65

Second dog adopter, third account 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db calls withdraw function passing dog adoption instance Id which is 0. Dog adoption instance balance

is zero now because 10 ether was sent back to owner when withdraw function was executed.

 

Figure 66

Second dog adopter balance is increased from 89 ether to 99 obtaining 10 ether (compare Figure 51)

 

Figure 67

Second dog adopter, third account 0x4b0897b0513fdc7c541b6d9d7e929c4e5364d2db got his token 3333 back.

 

Figure 68

Dog adoption instance owns no taken anymore. So, second dog adopter received both his 10 ether and token back from the dog adoption instance.

 

Figure 69

Second dog adopter is refunded and he is not the winner.

 

Figure 70

Let's create another dog adoption this time by forth account 0x583031d1113ad414f02576bd6afabfb302140225

 

Figure 71

Account 0x583031d1113ad414f02576bd6afabfb302140225 registered token 4444 and that account owns the token so far.

 

Figure 72

We transferred ownership from 0x583031d1113ad414f02576bd6afabfb302140225 to dog adoption instance and that instance owns token 4444 now.

 

Figure 73

Second dog adoption instance was created by 0x583031d1113ad414f02576bd6afabfb302140225. There are two dog adoption instances.

 

Figure 74

This is second dog adoption instance which is neither approved nor canceled nor ended. In other words, it is active as opposed to first one which was already approved.

 

Figure 75

New token 5555 is registered by account 0xdd870fa1b7c4700f2bd7f44238821c26f7392148 and then that token transferred to dog adoption account.

0xdd870fa1b7c4700f2bd7f44238821c26f7392148 is going to be a new dog adopter.

 

Figure 76

Dog adoption contract owns two tokens 4444 and 5555.

 

Figure 77

The adopter is going to send 20 ether.

 

 

Figure 78

Dog adopter was added to second dog adoption which id is 1. Now contract owns 20 ether sent by 0xdd870fa1b7c4700f2bd7f44238821c26f7392148

 

 

Figure 79

Dog adopter account ' s balance went from 99 ether to 79 as 20 ether was sent to contract.

 

Figure 80

Newly created dog adoption instance which id is 1 has one dog adopter who sent 20 ether to that instance.

 

Figure 81

Now, the owner decided to cancel newly created dog adoption.

 

Figure 82

We got an error Message sender is not dog adoption owner while canceling the dog adoption cancelDogAdoption.

 

Figure 83

The reason is that the contract was cancelled by dog adopter 0xdd870fa1b7c4700f2bd7f44238821c26f7392148 not owner 0x583031d1113ad414f02576bd6afabfb302140225

 

Figure 84

Second dog adoption was canceled.

 

Figure 85

Dog adaptor 0xdd870fa1b7c4700f2bd7f44238821c26f7392148 withdrew his ether form contract and the contract balance went to 0.

 

Figure 86

Dog adopter got his money back from the contract.

 

Figure 87

The dog adopter also got his token back 5555.

 

Figure 88

Dog adoption contract owns no token.

 

Figure 89

Dog owner received his token back.

 

Figure 90

You can debug a transaction by clicking Debug button and see Solidity Locals, State etc.