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.