Go up to the CCC HW page (md) | view tabbed version
I need a new gradebook! Because Canvas and my favorite spreadsheet programs are just not doing the job anymore. So I’ve decided to keep everybody’s grades in a public blockchain. Your task is to implement this gradebook for me.
Admittedly, keeping a gradebook of private grades on a public blockchain is not the most realistic use of smart contracts. But it will introduce you to the concepts involved in developing more complicated smart contracts. And there are many very similar applications that would only require a few tweaks to the gradebook contract. For example, there are organizations that coordinate through blockchains, and they have to keep some information on members; while not grades, it involves the same concepts.
The gradebook will need to have the following functionalities:
Writing this homework will require completion of the following assignments:
You will also need to be familiar with the Ethereum slide set and the Solidity slide set.
In addition to your source code, you will submit an edited version of gradebook.py (src).
Any changes to this page will be put here for easy reference. Typo fixes and minor clarifications are not listed here. So far there aren’t any significant changes to report.
Formally, your contract will need to be named Gradebook
, and saved in a file named Gradebook.sol
. It will need to implement the IGradebook.sol (src) interface, which is as follows. NOTE: the interface file itself has many more details and specifications in the comments; most of the comments were stripped from what is shown below.
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity ^0.8.24;
// See the actual IGradebook.sol file, linked to above, for much more detailed comments
interface IGradebook {
event assignmentCreationEvent (uint indexed _id);
event gradeEntryEvent (uint indexed _id);
// The following six methods are done for you automatically -- as long as
// you make the appropriate variable public, then Solidity will create
// the getter function for you
function tas(address ta) external returns (bool);
function max_scores(uint id) external returns (uint);
function assignment_names(uint id) external returns (string memory);
function scores(uint id, string memory userid) external returns (uint);
function num_assignments() external returns (uint);
function instructor() external returns (address);
// The following five functions are ones you must implement
function designateTA(address ta) external;
function addAssignment(string memory name, uint max_score) external returns (uint);
function addGrade(string memory student, uint assignment, uint score) external;
function getAverage(string memory student) external view returns (uint);
function requestTAAccess() external;
// The implementation for the following is provided in the HW description
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}
supportsInterface()
functionWe will see the use of supportsInterface()
in a future lecture and in a future assignment. For now, you should use this exact implementation:
function supportsInterface(bytes4 interfaceId) external pure returns (bool) {
return interfaceId == type(IGradebook).interfaceId || interfaceId == 0x01ffc9a7;
}
IGradebook.sol
is as follows, can can be copied by clicking on:
[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_id","type":"uint256"}],"name":"assignmentCreationEvent","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"uint256","name":"_id","type":"uint256"}],"name":"gradeEntryEvent","type":"event"},{"inputs":[{"internalType":"string","name":"name","type":"string"},{"internalType":"uint256","name":"max_score","type":"uint256"}],"name":"addAssignment","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"student","type":"string"},{"internalType":"uint256","name":"assignment","type":"uint256"},{"internalType":"uint256","name":"score","type":"uint256"}],"name":"addGrade","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"assignment_names","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ta","type":"address"}],"name":"designateTA","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"student","type":"string"}],"name":"getAverage","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"instructor","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"}],"name":"max_scores","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"num_assignments","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"requestTAAccess","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"id","type":"uint256"},{"internalType":"string","name":"userid","type":"string"}],"name":"scores","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes4","name":"interfaceId","type":"bytes4"}],"name":"supportsInterface","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"ta","type":"address"}],"name":"tas","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"}]
require()
instructor
field to msg.sender
contract Gradebook is IGradebook {
override
qualifier, since they are overriding what is specified in the IGradebook
interface. It’s good practice to put that qualifier in there even if the compiler does not explicitly require it.supportsInterface()
function should be exactly as is specified aboverequestTAAccess()
will grant anybody who calls it TA access; it’s not realistic in a contract deployed on a public blockchain, but we need it to test your code (the details are in the IGradebook.sol comments for that function)The first six methods (after the two events) in the IGradebook.sol (src) interface are getter functions. As long as you set the visibility of the field in the contract as public
, then the getter method is created for you, as discussed in the lecture slides. For example, for the getter function function num_assignments() external returns (uint)
, the appropriate field declaration would be uint public override num_assignments;
. The lecture slide set explains this a bit more.
The two events, listed at the top of the interface, should be emitted at the appropriate time. The addAssignment()
function should emit the assignmentCreationEvent()
event upon successful completion, and the addGrade()
function should emit the gradeEntryEvent()
event upon successful completion. It’s a good idea to emit the events after any require()
calls! It is often the case (but not required) that the event emission is done at the very end of the function.
Note that Remix may complain if an Ethernet address is not checksummed. Remix will provide, in the error, the checksummed address – you can use that value (cut-and-paste it into your code) instead to silence this warning. You can also use ethsum.netlify.app to checksum an Ethernet address.
Also note that compilation warnings will appear to Gradescope as a compilation error. Thus, you will have to remove them by the time you submit your assignment.
You will invariably run into issues testing and debugging your code. We have a few tips and tricks.
Use require()
. A lot. And be sure to use the two-parameter version of require()
.
Keep in mind the Solidity testing & debugging ideas from the lecture slides
Once you have tested it in the Javascript deployment environment, you will want to test it on our private blockchain, as described in the dApp introduction assignment. Remember that, in Remix, when you initiate a transaction (orange button) – rather than a call (blue button) – you can then view it on the explorer (once it is mined into the blockchain and the explorer updates).
Don’t be afraid to deploy it multiple times to the course blockchain – that’s what it is there for. A dozen deployments is fine, but if you start approaching a hundred or so, we are going to wonder what is going on. You will only submit the most recent (and – presumably – fully working) deployment when you submit the assignment.
In Remix you are going to end up calling a bunch of functions to initialize your smart contract for testing – for example, to create a few assignments, add some scores, etc. Once you know those functions work properly, you can put them in the constructor, as such. This will save you time, but BE SURE to remove those lines once you are finished testing it and prior to submission. For example:
designateTA(0x0123456789abcdef0123456789abcdef01234567);
addAssignment("HW1",10);
addAssignment("HW2",10);
addGrade("mst3k",0,5);
addGrade("mst3k",1,10);
Another option is to put those calls in a separate function called setup()
(or similar). This way, with one click, all of your contract setup will occur, and you don’t have to pollute the constructor.
Once done, you will need to deploy your CoruseGradebook smart contract to our private Ethereum blockchain. Save the contract address, as you will need to submit that, below. It’s okay if you deploy it multiple times (for testing, debugging, errors, etc.). Just submit the address of the last one you deployed.
On the deployed contract, you do not need to designate anybody as a TA – we are going to make ourselves a TA in your gradebook via the requestTAAccess()
function, so make sure that works properly.
I’ve deployed a gradebook with your (fake) grades. The address for that smart contract is on the Canvas landing page. You will need to find out your overall average as well as a few other items of information. Your scores are kept by your UVA userid. These scores are fake, and were randomly generated, so don’t feel bad if your score(s) are low.
There are two ways you can access the gradebook on the blockchain. One is through Remix, like was done in the dApp introduction assignment (md) – you load the IGradebook.sol interface, and then enter the address of the deployed Gradebook contract into the ‘At Address’ text box in the deployment window. The other way is through geth, as in the live coding example in class – the geth commands start about 8 slides down in that slide column. For this you will also need the ABI. You can compile the IGradebook.sol interface in Remix, and then copy the ABI – after you compile it, the copy ABI link is at the very bottom of the compilation pane. Note that you may have to reformat that ABI a bit – what you copy is on many lines, and you may have to reformat it to one line.
The steps to access the gradebook via Remix are:
The steps to access the gradebook via a geth terminal are (adapted from here):
var addr = "0xffffffffffffffffffffffffffffffffffffffff";
, but with the real contract address on the Canvas landing pagevar abi = [...];
, but with the real ABI of IGradebook from the Canvas landing page. Do not put this in quotes, and do not put this in extra square brackets!var interface = eth.contract(abi);
var contract = interface.at(addr);
view
or pure
function via: contract.function.call()
; parameters, if any, go in the parenthesis of call()
personal.unlockAccount(eth.coinbase,"password",0)
contract.addAlias.sendTransaction("mst3k", "Your Name", {from:eth.coinbase, gas:1000000})
{from:eth.coinbase, gas:1000000}
should stay the sameThe information you need to obtain is:
1234
is returned by the function, then you should enter 12.34
into gradebook.py.You will need to fill in the various values from this assignment into the gradebook.py (src) file. That file clearly indicates all the values that need to be filled in. That file, along with your Solidity source code, are the only two files that must be submitted. The sanity_checks
dictionary is intended to be a checklist to ensure that you perform the various other aspects to ensure this assignment is fully submitted.
There are two forms of submission for this assignment; you must do both.
Submission 1: You must deploy your smart contract to our private Ethereum blockchain – this was probably done above. It’s fine if you deploy it a few times because you were testing it, messed something up, or whatever. But the final deployment should not have any other transactions to the deployed contract.
Submission 2: You should submit your Gradebook.sol
file, as well as your gradebook.py
file, and ONLY those two files, to Gradescope. All your Solidity code should be in that first file, and you should specifically import the IGradebook interface. That interface file will be placed in the same directory on Gradescope when you submit. NOTE: Gradescope cannot fully test this assignment, as it does not have access to the private blockchain. So it can only do a few sanity tests (correct files submitted, successful compilation, valid values in auction.py, etc.).