Practise projects after Ethereum Smart Contract Programming 101

It was a great course, and I really wanted to write my own smart contract for the first time, and this is what i could produce.

My first idea was to create a smart contract that will pay my kid 0.5 ether, when he gets good grades. This project idea came from Ivan, in the course ethereum 101. Here is the code. The Ownable.sol file is the same as discussed by @filip in the course.

pragma solidity 0.5.12;

import "./Ownable.sol";

contract rewardKidFroGoodGrdaes is Ownable{
    address payable Receiver = 0xdd445B306473b01DF0aA50090DB618718C4b202F;
    uint public Grades;
    uint public balance;
    
    bool pay = false;
    string  errorMessage = "Your grades are low";
    string  successMessage = "You are awarded";
    
    event throwSuccessMessgae(address sender, string successMessage);
    event throwErrorMessgae(string errorMessage);
    
    function setGrades()private {
        if (Grades >= 8){
            pay = true;
        }
    }
    
    
    modifier costs(uint cost){
        require(msg.value >= cost);
        _;
    }
    
    function getGrades(uint grades)public onlyOwner{
        Grades = grades;
    }
    
    function rewardKiddo()public onlyOwner payable costs(0.1 ether){
        setGrades();
        require(pay == true);
        if (Receiver.send(msg.value)){
            balance += msg.value;
            emit throwSuccessMessgae(owner, successMessage);
        }
        else{
            emit throwErrorMessgae(errorMessage);
        }
    }
}

Second project is, suppose we organise a competition. There is an open window for competetors to take part. The cose is 1 wei. Based on their grades, the owner will choose a winner, and reward him as much he wish. And others can view the winner through a public struct variable. But only the winner can choose the winner. Here is the code.

pragma solidity 0.5.12;
import "./Ownable.sol";

contract Competition is Ownable{
    
    struct Competitor{
        address CompetitorAddress;
        string name;
        uint grade;
    }
    
    uint public totalCompetitors;
    Competitor public Winner;
    bool public CompetitionOver = false;
    
    // mapping (address => Competitor) private Competitors;
    Competitor[] Competitors;
    modifier costs(uint cost){
        require (msg.value >= cost);
        _;
    }
    
    function createCompetitor(string memory name, uint grade)public payable costs(1 wei){
        Competitor memory newCompetitor;
        newCompetitor.CompetitorAddress = msg.sender;
        newCompetitor.name = name;
        newCompetitor.grade = grade;
        
        insertCompetitor(newCompetitor);
    }
    
    function insertCompetitor(Competitor memory newCompetitor)private{
        Competitors.push(newCompetitor);
        totalCompetitors += 1;
    }
    
    function PredictWinner()public onlyOwner payable{
        // uint[] CompetitorsList;
        
        uint j;
        for(uint i = 0; i<totalCompetitors; i++){
            if (Competitors[j].grade <Competitors[i].grade){
                j = i;
            }
        }
        Winner = Competitors[j];
        payWinner(Winner.CompetitorAddress);
        
    }
    
    function payWinner(address CompetitorAddress)private onlyOwner{
        address(uint160(CompetitorAddress)).transfer(msg.value);
        CompetitionOver = true;
    }
    
}
3 Likes

I can’t wait to get home and check these out!

1 Like

This is amazing mate, just what i was about to start doing. I am going to do something else since you already cracked it brother. :smiley:

I need to create a contract where i pay TV Subcription to the providers ETH add. There is 3 variables. 3 month, 6 month or 12 months subscriptions.

I have to Send him a fixed amount of GBP value ETH @ Market price… depending on what legnth of Subscription i take.

In a long story i am converting customers Fiat Currency for them and sending the provider ETH. i would like to simplify it in a smart contract so i go to a simple interface and enter the data and its done.

Its a little to think about so i hope to get help along the way from @thecil and @Mauro. I have to try and figure this out bit by bit and i am determined to nail it.

Wish me luck eh!!! haha

Rob
#armature

Hey all!
After finishing the course, encouraged by Filip, I also wanted to write something on my own to get some experience and I came up with this:

A contract for creating shared accounts

The idea was for the contract to:

  1. Provide tools to create and use an indefinate number of shared accounts (make transfers, top-up, withdraw funds).
  2. Support adding and deleting users from account
  3. Keep track of all users of each account
  4. Allow for 3 different levels of user rights within account.
  5. Block non-users from using accounts.
  6. Keep track of all accounts of each individual user

Note: Contract uses the functionality from Ownable.sol and Destroyable.sol as they were provided in the course materials (link to github repository).

For clarity of reading I divided the functionality in 4 parts:

1. Creatable

Here I defined:

  • structs for Account and user's Rights
  • function to create an account and give it a unique ID
  • mappings of each ID to its corresponding account
  • mapping of each user's address to all of his/her accounts
import "./Destroyable.sol";

pragma solidity 0.5.12;

contract Creatable is Destroyable {
    
    struct Account{
        bool created;
        string name;
        address[] users;
        mapping(address => Rights) userRights;    // mapping of each user's rights
        uint saldo;
    }
    
    struct Rights{          // struct containing user rights to:
        bool user;          // make transactions and withdraw funds
        bool manager;       // add and delete users
        bool creator;       // change user rights
    }
    
    mapping(address => bytes32[]) internal accountIds;          // mapping of all account of each individual user
    mapping(bytes32 => Account) internal accounts;              // mapping of each account to its id
    
    event accountCreated(bytes32 accId, address creator);
    
    
    // create an account with one user (creator) and balance equal to value sent with function
    function createAccount( string memory name) public payable {
        
        address creator = msg.sender;
        bytes32 id =  keccak256(  abi.encodePacked( name, creator )  );     // create account ID
        
        // verify if this is account already created
        require(!accounts[id].created, "You already have an account with that name");      
        
        // create temp Account 
        Account memory newAccount;                                          
        newAccount.created = true;
        
        newAccount.name = name;                                         // set account name
        
        address[] memory allUsers = new address[](1);                   // insert creator's address into users array
        allUsers[0] = creator;
        newAccount.users = allUsers;
        
        newAccount.saldo = msg.value;                                   // set balance equal to value sent
        
        accounts[id] = newAccount;                                      // insert account to "accounts" mapping by ID
        accounts[id].userRights[creator] = Rights(true,true,true);      // give all rights to creator
        accountIds[creator].push(id);                                   // insert account ID into user's array of IDs
        
        // assert if account was inserted correctly
        assert(
            keccak256(  
                abi.encodePacked(
                    newAccount.created,
                    newAccount.name,
                    newAccount.saldo
                )
            ) 
            ==
            keccak256(  
                abi.encodePacked(
                    accounts[id].created,
                    accounts[id].name,
                    accounts[id].saldo
                )
            )
        );
        
        emit accountCreated(id,creator);
    }
}

2. Functional

This contract defines inspection functionality by providing:

  • modifiers to verify if input indexes are correct
  • modifiers to check user's rights
  • functions to inspect account IDs, account users, their addresses and rights
import "./Creatable.sol";
pragma solidity 0.5.12;

contract Functional is Creatable{
    
    // check if the given accIndex is within the "accountIDs" array
    modifier checkAccIndex(uint accIndex){
        require(accIndex < accountIds[ msg.sender ].length, "You do not have that many accounts");
        _;
    }
    
    // check if the given userIndex is within the account's "users" array
    modifier checkUserIndex(bytes32 accId, uint userIndex){
        require(userIndex < accounts[ accId ].users.length, "There isn't that many users in account");
        _;
    }
    
    modifier onlyUser(bytes32 accId){
        require(accounts[ accId ].userRights[msg.sender].user, "You don't have rights to do this");
        _;
    }
    
    modifier onlyManager(bytes32 accId){
        require(accounts[ accId ].userRights[msg.sender].manager, "You don't have rights to do this");
        _;
    }
    
    modifier onlyCreator(bytes32 accId){
        require(accounts[ accId ].userRights[msg.sender].creator, "You don't have rights to do this");
        _;
    }
    
    // check if the newUser address is already added to the account
    modifier noUser(bytes32 accId, address newUser){
        require(!accounts[accId].userRights[newUser].user, "User already added");
        _;
    }
    
    // returns biggest index of sender's "accountIds" array
    function myAccounIdMaxIndex() public view checkAccIndex(0) returns(uint){
        return( accountIds[ msg.sender ].length -1 );
    }
    
    // return ID of senders account with chosen index
    function myAccountId( uint accIndex ) public view checkAccIndex(accIndex) returns( bytes32 ) {
        return( accountIds[ msg.sender ][ accIndex ] );
    }
    
    // returns biggest index of 
    function maxUserIndex( bytes32 accId ) public view onlyUser(accId) returns(uint){
        return( accounts[ accId ].users.length -1 );
    }
    
    // return address of chosen user of one of your accounts
    function userAddress( bytes32 accId, uint userIndex ) public view onlyUser(accId) checkUserIndex(accId, userIndex) returns( address user ){
        return( accounts[ accId ].users[ userIndex ] );
    }
    
    // return user rights
    function userRights(bytes32 accId, address user) public view onlyUser(accId) returns (bool, bool, bool){
        return(
            accounts[accId].userRights[user].user, 
            accounts[accId].userRights[user].manager,
            accounts[accId].userRights[user].creator
            );
    }
}

3. Manageable
This part includes:

  • functions to add and delete a user from account
  • function to modify user rights
import "./Functional.sol";
pragma solidity 0.5.12;

contract Manageable is Functional{
    
    event userAdded(bytes32 accId, address newUser);
    event userDeleted(bytes32 accId, address deletedUser);
    event userRightsChanged(bytes32 accId, address user, bool manager, bool owner);
    
    // add a new user to your account
    // new user will receive user rights by default
    function addUser( bytes32 accId, address newUser ) 
        public onlyManager(accId) noUser(accId, newUser) {
            
       uint usersCount = accounts[ accId ].users.length;
        
        accounts[ accId ].users.push( newUser );            // add new user to account
        accounts[ accId ].userRights[newUser].user = true;  // mark new user as created
        accountIds[ newUser ].push( accId );                // add account to user's "accountIds" array 
        
        assert( accounts[ accId ].users.length == usersCount + 1 );
        emit userAdded(accId, newUser);
    }
    
    
    // delete user from your account
    // removes also the account from user's "accountIds" array
    function deleteUser( bytes32 accId, uint userIndex ) 
        public onlyManager(accId) checkUserIndex(accId, userIndex) {
            
        require(userIndex > 0, "You cannot remove creator");
        
        uint usersCount = accounts[accId].users.length;             // get user count
        address deletedUser = accounts[accId].users[userIndex];     // get deleted user address
        
        delete accounts[accId].users[userIndex];                    // delete user from account
        
        if(userIndex < usersCount -1){                              // move all to the left to fill gap
            for (uint i=userIndex; i<usersCount-1; i++){
                accounts[accId].users[i] = accounts[accId].users[i+1];
            }
            assert(accounts[accId].users[userIndex] != deletedUser);
        }
        
        accounts[accId].users.length--;                             // decrease length
        assert(accounts[accId].users[usersCount-2] != deletedUser);
        
        delete accounts[accId].userRights[deletedUser];             // delete user rights
        
        deleteAccFromUser(deletedUser, accId);                      // delete account from user's accIds array
        
        emit userDeleted(accId, deletedUser);
    }
    
    // remove account from user's "accountIds" array
    function deleteAccFromUser(address deletedUser, bytes32 accId) internal {
        
        uint userAccCount = accountIds[deletedUser].length;         // get length of user's accIds array
        
        for (uint i=0; i < userAccCount; i++){                      // find index of the account
            if (accountIds[deletedUser][i] == accId){               
                
                if (userAccCount==1){                               // if it is the only user's account
                    delete accountIds[deletedUser];                 // delete user from mapping
                    
                } else {                                                
                    delete accountIds[deletedUser][i];              // delete account from user
                    
                    
                    // move all to the left to fill gap
                    if(i < userAccCount -1){                        
                        for (uint j=i; j<userAccCount -1; j++){
                            accountIds[deletedUser][j] = accountIds[deletedUser][j+1];
                        }
                        assert(accountIds[deletedUser][i] != accId);
                    }
                    
                    
                    // update array length
                    accountIds[deletedUser].length--;               
                    assert(accountIds[deletedUser][ userAccCount-2 ] != accId);
                    
                    break; 
                    
                }
            }
        }
    }
    
    
    // modify user's "manager" and "owner" rights values
    function changeUserRights(bytes32 accId, uint userIndex, bool manager, bool creator) 
    public onlyCreator(accId) {
        
        address user = userAddress(accId, userIndex);
        
        accounts[ accId ].userRights[user].manager = manager;
        accounts[ accId ].userRights[user].creator = creator;
        
        emit userRightsChanged(accId, user, manager, creator);
    }
    
}

4. AccountManager

This contract defines funds-handling functionality:

  • modifiers for transfer functions
  • top-up function
  • external (to addresses) and internal (to other accounts within contract) transfer functions
  • withdraw function
  • function to check account balance
import "./Manageable.sol";
pragma solidity 0.5.12;
 
contract AccountManager is Manageable{
    
    event toppedUp(bytes32 accId, uint amount);
    event transferSuccessful(bytes32 senderId, address receiver, uint amount);
    event internalTransferSuccessful(bytes32 senderId, bytes32 receiverId, uint amount);
    event withdrawalSuccessful(bytes32 senderId, address receiver, uint amount);
    
    modifier nonZeroValue(uint value){
        require(value > 0, "Non-zero value required");
        _;
    }
    
    modifier balanceCheck(bytes32 accId, uint toTransfer){
        require(toTransfer <= accounts[ accId ].saldo, "Insufficient funds");
        _;
    }
    
    function topUp( bytes32 accId ) public payable nonZeroValue(msg.value){
        accounts[ accId ].saldo += msg.value;
        
        emit toppedUp(accId, msg.value);
    }
    
    function transfer(bytes32 accId, address payable receiver, uint toTransfer) 
        public onlyUser(accId) nonZeroValue(toTransfer) balanceCheck( accId, toTransfer){
            
        uint oldBalance = accounts[ accId ].saldo;
        accounts[ accId ].saldo = oldBalance - toTransfer;
        receiver.transfer(toTransfer);
        
        emit transferSuccessful(accId,receiver, toTransfer);
    }
    
    function internalTransfer(bytes32 senderAccId, bytes32 receiverAccId, uint toTransfer) 
        public onlyUser(senderAccId) nonZeroValue(toTransfer) balanceCheck( senderAccId, toTransfer){
        
        uint balanceSum = accounts[ senderAccId ].saldo + accounts[ receiverAccId ].saldo;
        accounts[ senderAccId ].saldo -= toTransfer;
        accounts[ receiverAccId ].saldo += toTransfer;
        
        assert( balanceSum == accounts[ senderAccId ].saldo + accounts[ receiverAccId ].saldo );
        emit internalTransferSuccessful(senderAccId, receiverAccId, toTransfer);
    }
    
    function withdraw(bytes32 accId, uint toTransfer) 
        public onlyUser(accId) nonZeroValue(toTransfer) balanceCheck( accId, toTransfer) {
        
        uint oldBalance = accounts[ accId ].saldo;
        accounts[ accId ].saldo = oldBalance - toTransfer;
        msg.sender.transfer(toTransfer);
        
        emit withdrawalSuccessful(accId, msg.sender, toTransfer);
    }
    
    function accountBalance( bytes32 accId ) public view onlyUser(accId) returns(uint)  {
        return( accounts[ accId ].saldo );
    }
    
}

Problems

The biggest struggle throughout the whole process was for me creating the deleteUser function.
The function had to not only delete users from account but also the account from the user (as all user addresses are mapped to the array of account IDs). This plus accounting for the edge cases and not leaving behind gaps in the arrays or duplicated values made it time consuming to make it work.
Here I learned to appreciate the assert() statements, as they turned out to be a great help in identifying bugs.

Contract interactions

The contract is deployed on Ropsten testnet on the following address:

0xd5f414294e5D6B12A1fF62952Ca583BDe27513b2

One way to interact with it is to copy the code from this link and paste it into a new file in Remix. Then go to Deploy & run transaction tab, choose Injected Web3 environment, AccountManager contract and paste the contract address into the At Address box and click that button. Contract will appear under Deployed contracts.

Up front I am sorry that the destroy() functions sends the whole contract balance back to me. Next step in improving could definitely be to send back all account balances to account owners on destroying the contract.

Hope this gives you some help or motivation and I will appreciate any feedback.
Good luck to everybody :kissing_heart:

3 Likes

Now this looks very great sir, i will be trying it later, but apparently your code looks.

Amazing work! Keep it like that sir! :clap:

If you have any more questions, please let us know so we can help you! :slight_smile:

Carlos Z.

1 Like

Hey @Rob_McCourt,

How are you getting on with your project idea?
I’ve read your idea, but to achieve what you want I think you will need to use oracles, because the GBP payments will be made using off-chain systems, and the current ETH/GBP exchange rate will need to come from an off-chain data provider/API. Using oracles to bring external data into your smart contract will be introduced in the Ethereum Smart Contract Programming 201 course.
Maybe you could start developing the smart contract for the ETH subscription payments for the different periods. Then you could add on the external data feeds (from oracles) as part of a 201 project…
Good luck :muscle: and let us know how you’re getting on.

1 Like

Hello mate, i am currently working out Phase 1 of my Betting Project. I am just following along old lessons and chipping away at it currently. I was hoping to complete this project then move on to the “Personal Idea” after…

I am struggling a little with setting up Payable function so that some one who connects their metamask then has the ability to bet on my Dapp… I just don’t know where to begin. There is some good projects already been done on the forum and they are really useful. Some folks are using Ownable contracts etc so im looking at that now.

Rob.

1 Like

Hi @Rob_McCourt,
If you mean the coin flipping project in Solidity 201, then, yes, that’s a good idea to finish working on that, and also the oracles, and then you should be in a much better position to tackle your own personal project after.

If you find yourself struggling for too long with the payable function, then ask for some help in the discussion topic for that project and the teaching assistant who is covering that course is sure to give you a hand.

yeah @dan-i has been on hand with me on it. I have DMd him a link to my repository on Github with a read me file with some key things i don’t have a clue how to create so just waiting to see his reply.

I am really enjoying Solidity mate and its absolutely mind blowingly difficult for someone like me who has never coded in his life. But i know what i am trying to achieve its just the basic functionality i am struggling with. If i can get to the end of this by doing it myself then i know i can really go forward confidently and know the right questions to be asking in future, I just need my hand held a little this time haha.

Cheers for checking in today pal !

Top man

3 Likes

Yeh, it is a real challenge, but glad to hear you’re really enjoying it and staying so positive :muscle: @dan-i is definitely the man to help you out and hold your hand when needed! :grinning:

3 Likes

Great to hear, let me know how that works out.

Will be exciting to see other transactions than just mine on it on etherscan :sweat_smile:

3 Likes

current HTML File for the Dapp… I need to fix where my HEADS & TALES Buttons are as i dont know how to position them…

And i suppose its just fixing the code i have…

Please feel free to have a look at my Github so far.

https://github.com/Highlander-maker/CoinFlipDapp

ill crack on tomorrow …

2 Likes

@jon_m

Hey Jon.

I finished the Smart Contract Programming 101 and wanted to write some contracts for practice.
Here is a basic one I wrote where people can apply for Membership into a Bourbon Drinking Club, and once approved, they can pay their membership dues.
Anything you would like to suggest I do differently?
Does everything look to be correctly done?

This is the first piece of coding that I have ever done in any language without someone’s help in some way.
I appreciate the feedback. Cheers!

pragma solidity 0.7.5;
pragma abicoder v2;

contract Application {

address private owner;
uint balance;

constructor (){
    owner = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4;
}

modifier onlyOwner {               
    require( msg.sender == owner);   
    _;   
}


struct Member {
    string name;
    address memberAddress;
    bool approved;
    bool paid;
    uint id;
}

Member[] members;

// anyone can apply for membership
function applyForMember(string memory _name, address _memberAddress) public {
    members.push(Member(_name, _memberAddress, false, false, members.length));
    
}


// returns list of all applied-for members, approved or not
function getMembers() public view returns(Member[] memory){
    return members;
}

// owner can approve the member application
function approveMember(uint _id) public onlyOwner {
    require(_id < members.length);
    require(members[_id].approved == false);
    
    members[_id].approved = true;
    
}

// only member can pay their membership dues owed
function payMemberDues(uint _id, uint amount) public payable {
    require(_id < members.length);
    require(msg.sender == members[_id].memberAddress);
    require(members[_id].approved == true);
    require(members[_id].paid == false);
    require(amount == 3000000, "Payment amount incorrect, must be 3 000 000; please add correct amount");
    
    balance += amount;
    
    members[_id].paid = true;
}

// get the contract balance
function getBalance() public onlyOwner view returns (uint){
    return balance;         
}

}

Good information thanks for sharing
HP Field Support Engineer

It is an interesting topic in Thanks for sharing.

168vegus