Hello Crypto Community!
-After taking the course “Ethereum Smart Contract Programming 101” and hearing Filip on the end of the course saying that the best way to learn is actually to build your own project, I decided to give it a try and make my first project in Solidity.
- Why I built it and what it is designed to do?
-The idea is very simple. Contract is designed from the trustee as incentive for the child to have better grades in school in order to get a “prize”.
- Who the different users are?
-The users accessing the contract are :
- The trustee ( the guardian of the child and the contract creator).
- Child ( this is the user that trustee is registering and it can be as many children as trustee inputs).
- Professor ( this is the user that is being registered by the trustee and for a specific child).
-
How specific parts of the code work ? (I included this in the comments in the contract itself).
-
What problems I had while building it, how I resolved them, and what new coding techniques/solutions I’ve learned:
-
First contract that I created had few lines and I was thinking that this is so easy … I didn’t understand at first why people are saying that coding is so difficult as it seemed easy for me, but only after reaching to @jon_m I understood that my code is interesting as an idea but very far from being a secure Smart Contract and high quality code.
-
Only after I listened and implemented some of the suggestions to develop this idea further it started to look like a more better designed and more structured code and Smart Contract.
-
I used the knowledge from the course that we learned and implemented a lot of the same knowledge in this code. I used to spend hours trying to access different parts of my code and trying to make them work, but still any time that I really couldn’t find a way out I asked for help and once again BIG THANK YOU to @jon_m for helping me out. Withouth your help I am pretty sure this code would not be developed nearly as much.
-I had some issues with implementing “Grades” instance in the “Child” struct
-And how to access some parts in the structs using specific addresses as the mapping keys. But with a bit of help I got to understand it and that we can create and design this ourselves.
- What features I changed, and decided to do differently, while building it, and why?
- The only things I did differently from the beginning of the contract are :
-I took out Professor struct and mapping and implemented all of the data in the Child struct to keep the code cleaner as it was unnecessary to have so many structs and mapping due to being able to access everything in the way it is now in the code.
-Instead of professor registering himself I limited this to trustee only modifier
-And instead of having to call selfDestruct() function to withdraw some funds I decided to implement withdrawFunds() function for the trustee to be able to withdraw any funds in the contract that are currently available.
(it is really difficult to remember all of the things from the beginning of the contract that I have decided to go differently with, so I just mentioned the ones that I know for sure, but I hope that you will forgive me ) .
- How I would like to develop it further?
- I would like to implement some features with time locks, and removing allocateFunds() function so that in this way when specific time or date passes (the end of the school year) the funds are being distributed automatically and not having to be done from the trustee. In this way I think the contract would gain more decentralization.
- And I would like to build a front-end in Javascript for this to create a Dapp (in this way in the contract in the lockInTheFunds() function it could be set that the amount is supposed to be in Ether or any other amount).
All comments, ideas and suggestions for improvement and other functionality of the contract are more than welcome, please… Feel free to express your thoughts and leave a feedback as everything is about all of us working together and collaborating with one another!
One more time a huge THANK YOU to @jon_m , withouth whose help I would not be able to bring this contract nearly as far as we have.
P.S (only a small issue the contract has is if the same trustee calls 2 times in a row lockInTheFunds() function , contract balance gets updated, but funds for wihdrawall don’t. The funds would have to be withdrawn using selfDestruct() function). Once again, any comments, ideas and suggestions for improvements are more than welcome.
This is the first peace of the code and a separate contract (Ownable) .
pragma solidity 0.5.12;
contract Ownable {
address public trustee;
uint public fundsAvailable;
constructor () public {
trustee = msg.sender;
}
modifier trusteeOnly {
require(trustee == msg.sender);
_;
}
}
and the second peace.
import "./Ownable.sol";
pragma solidity 0.5.12;
contract Assignment is Ownable{
struct Child {
string name; //(this is the child's name)
uint basic; //(basic funds locked up for each child)
uint bonus; //(bonus locked up for each child)
string professorName; //(professor's name)
uint professorBonus; //(bonus locked up for the teacher to be paid on assignment of child’s grades)
address payable professor; //(teacher responsible for giving each child their grades)
uint requiredGrade; //(grade required for each child to receive their basic payment)
uint bonusGrade; // (grade required for each child to receive their bonus payment)
Grade grades; //(created instance of the Grade struct added to Child struct so that it can be accessed through children mapping)
}
struct Grade{
uint math;
uint english;
uint history;
uint geography;
uint physics;
uint total;
}
uint public contractBalance; //(keeps track of the total funds held in the contract)
mapping (address => Child) internal children; //(mapping which will for a certain address give us the data we are looking for)
function registerChild(string memory name, address childsAddress) public trusteeOnly {
Child memory newChild;
newChild.name = name;
children[childsAddress] = newChild;
assert(
keccak256(
abi.encodePacked( //(registerChild function is adding a child to our mapping using childsAddress
children[childsAddress].name // as a parameter that I have set as the key to point to our specific child)
))
==
keccak256(
abi.encodePacked(
newChild.name
)
)
);
}
function lockInTheFunds(uint basicAmount, uint bonusAmount, uint professorBonusAmount,uint requiredGrade, uint bonusGrade, address childsAddress) public trusteeOnly payable {
require(msg.value == basicAmount + bonusAmount + professorBonusAmount, "The value must be equal as the total of basicAmount + bonusAmount + professorBonusAmount");
contractBalance += msg.value;
children[childsAddress].basic = basicAmount;
children[childsAddress].bonus = bonusAmount;
children[childsAddress].professorBonus = professorBonusAmount; //(lockInTheFunds function allows the trustee to pay funds into the contract and allocate specific amounts to the child’s basic
children[childsAddress].requiredGrade = requiredGrade; // and bonus payments, and the teacher’s bonus payment.
children[childsAddress].bonusGrade = bonusGrade; // These amounts are then locked in until the child receives their grades.)
}
function registerProfessor(string memory professorName, address payable professorAddress, address childsAddress) public trusteeOnly {
children[childsAddress].professorName = professorName; // (registerProfessor function is assigning professorName and professorAddress
children[childsAddress].professor = professorAddress; // values to a specific childsAddress in the children mapping)
}
function insertGrades(address childsAddress, uint math, uint english, uint history,uint geography, uint physics) public {
require(children[childsAddress].professor == msg.sender);
Grade memory newGrades;
newGrades.math = math;
newGrades.english = english; //(insertGrades function can be only called by the professor that is assigned to
newGrades.history = history; // a specific Child and this is going to determine weather or not the Child
newGrades.geography = geography; // is going to be rewarded or not and with how much value)
newGrades.physics = physics;
newGrades.total = newGrades.math + newGrades.english + newGrades.history + newGrades.geography + newGrades.physics;
children[childsAddress].grades = newGrades; //(in this way we assign a child’s grades to the grades property mapped to their address in the children mapping.)
}
function allocateFunds(address payable childsAddress) public trusteeOnly payable{
Child storage child = children[childsAddress];
if (child.grades.total >= child.bonusGrade) {
childsAddress.transfer(child.basic);
childsAddress.transfer(child.bonus);
child.professor.transfer(child.professorBonus); //( allocateFunds function can only be called by the trustee, and it will automatically
contractBalance -= child.basic; // do the following if/else statements to check for a set of instructions given by the
contractBalance -= child.bonus; // trustee and distribute the funds accordingly.
contractBalance -= child.professorBonus; // in this way I have decided to incentivise the professor for doing
child.basic = 0; // his part of the job, by giving him a bonus)
child.bonus = 0;
child.professorBonus = 0;
}
else if (child.grades.total >= child.requiredGrade) {
childsAddress.transfer(child.basic);
child.professor.transfer(child.professorBonus);
contractBalance -= child.basic;
contractBalance -= child.professorBonus;
fundsAvailable += child.bonus;
child.basic = 0;
child.bonus = 0;
child.professorBonus = 0;
}
else {
child.professor.transfer(child.professorBonus);
contractBalance -= child.professorBonus;
fundsAvailable += child.basic;
fundsAvailable += child.bonus;
child.basic = 0;
child.bonus = 0;
child.professorBonus = 0;
}
}
function getChild(address childsAddress) public view returns(string memory childName, uint basic, uint bonus, address payable professorAddress, string memory professorName, uint requiredGrade, uint bonusGrade) {
return(children[childsAddress].name,
children[childsAddress].basic,
children[childsAddress].bonus,
children[childsAddress].professor, //(getChild function can be accessed by anyone who has childsAddress, by calling the function
children[childsAddress].professorName, // he or she can see all the data listed in this function)
children[childsAddress].requiredGrade,
children[childsAddress].bonusGrade);
}
function getProfessor(address childsAddress) public view returns(uint professorBonus, string memory name){
return(children[childsAddress].professorBonus,
children[childsAddress].professorName); //(same as the getChild function, by entering childsAddress we can get the data of the
} // professor that was assigned to that Child)
function getGrades(address childsAddress) public view returns(uint math,uint english, uint history,uint geography, uint physics, uint total){
return(children[childsAddress].grades.math,
children[childsAddress].grades.english,
children[childsAddress].grades.history, //(by entering childsAddress in this getGrades function we will get all of the grades
children[childsAddress].grades.geography, // for this Child)
children[childsAddress].grades.physics,
children[childsAddress].grades.total
);
}
function withdrawFunds(uint amount) public trusteeOnly returns(uint) {
require(amount <= fundsAvailable, "The amount needs to be less than or equal to funds available to transfer (fundsAvailable)");
fundsAvailable -= amount;
contractBalance -= amount; // (this withdrawFunds function is for the trustee to be able to withdraw any surplus
msg.sender.transfer(amount); // funds that might be available and not (or no longer) locked in for any child or professor)
return fundsAvailable;
}
function destroy() public trusteeOnly{
selfdestruct(msg.sender); // (And here we have destroy function which in the end trustee can call to selfdestruct the contract
} // and transfer any remaining funds inside of the contract to his address)
}
Please, let me know what you think!