Skip to content

Instantly share code, notes, and snippets.

@cronokirby
Last active November 24, 2021 18:03
Show Gist options
  • Save cronokirby/9ae094faf74fd94305596dd4db2277f1 to your computer and use it in GitHub Desktop.
Save cronokirby/9ae094faf74fd94305596dd4db2277f1 to your computer and use it in GitHub Desktop.
Created using remix-ide: Realtime Ethereum Contract Compiler and Runtime. Load this file by pasting this gists URL or ID at https://remix.ethereum.org/#version=soljson-v0.8.7+commit.e28d00a7.js&optimize=false&runs=200&gist=9ae094faf74fd94305596dd4db2277f1

CS-234 Solidity Homework (Part 1)

In the first part of this homework, you will work on a few Ethereum smart contracts that are written in Solidity to learn the basics about interacting with smart contracts. For now, you are going to work individually. In the second part of the homework, you will be interacting with your classmates on the same contract.

To interact with these contracts, we will be using a sandbox: https://remix.ethereum.org. Remix allows you to load a project from different sources. In case you do not see the contracts named 1_Hello.sol, 2_Quadratic_Voting.sol and 3_Multi_Voting.sol, you can use the Gist button on the homepage (under Load from) to load the contracts from this Gist, using the URL of this page.

1: Hello Contract

This contract can be found in contracts/1_Hello.sol. This is just a simple contract that stores a single number that anyone can modify. The first thing we will do is compile the contract. The compiler can be accessed via the second item of the left toolbar (looks like an S). With the contract open, there should be a button to compile it.

After clicking on the button to compile, you can open the deployment tab by clicking on the third item on the same toolbar (it is right under the S-shaped compiler icon). Here, you can select the 1_Hello.sol contract, and then deploy it. This will deploy the contract in a sandbox so that we can interact with it.

After deploying it, you should see it appear under the Deployed Contracts section, which is below the Deploy button. After expanding it, you should see two buttons, corresponding to the different functions in the contract. The first function lets you modify the number stored in the contract, and the second function lets you get the current value of the number. Try playing around with these functions, and then look at how they are implemented in the contract.

2: Quadratic Voting

This second contract is in contracts/2_Quadratic_Voting.sol. This one is a bit more complicated to implement, but the basic idea is simple. You have a collection of ice cream flavors you can vote for: nothing, mint, chocolate, and vanilla. Currently, nothing is in the lead with 2 votes. The other flavors have no votes so far. Your goal is to make your favorite flavor win, by giving it 3 votes.

To start with, you can compile and deploy the contract like you did in the previous question.

To vote, you will need to spend tokens. You can get your initial tokens by calling the mintTokens function. This will give you 9 tokens to start with. If you call the function a second time, it will fail, since you have already minted your tokens. You can check how many tokens you have with the myTokens function.

You can use the voteFor function to vote for a specific proposal. If you vote twice for the same proposal, notice that you only have 5 tokens remaining. This is because this contract uses quadratic voting. To provide N votes for the same proposal, you need to spend N^2 tokens.

Thankfully, if you change your mind, you can reset your votes with resetMyVotes.

You can see how many times you have voted for a specific proposal with the myVotes function. You can also see which proposal is winning with the winner function.

Your goal is to bring your favorite flavor into the lead, by giving it at least 3 votes, more than the 2 votes that nothing currently has. With the 9 tokens you are given to start with, is this possible?

3: Sybil Problems

This contract is in contracts/3_Multi_Voting.sol. The contract functions in the same way as the last contract, except that nothing now starts with 4 votes, instead of 2. You still only have 9 tokens to start with. Can you make your favorite flavor win now?

Hint: Notice the Account field at the top of "Deploy & Run Transactions" section.

4: Questions

To test your understanding of the homework, please answer the following questions:

  1. If you have voted n - 1 times for a proposal, how many tokens do you need to spend to vote one more time on the same proposal?
  2. (a) If instead of making a single proposal of the three win, you want to vote as many times for the proposals as possible, what is the best strategy? (b) How many votes can you give to each of the three proposals if you use the strategy in (a)? (c) Could you vote more times if you had the same number of tokens but there were more than three proposals?
  3. What would be an approach to address the Sybil problem introduced in part 3?

5: What to submit?

You are going to upload on Moodle a PDF document with your answers to the questions in part 4. Please carefully label your answers with the corresponding question numbers (e.g., 2.a.). Please do not forget to put your name and SCIPER in the PDF. Your PDF file should be named as follows: <YOUR_SCIPER>.pdf.

// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract Hello {
uint256 number;
function store(uint256 num) public {
number = num;
}
function retrieve() public view returns (uint256){
return number;
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract Flavors {
mapping(string => uint32) proposalIndices;
uint32 numProposals = 4;
// The tokens we've given to each participant
mapping(address => uint32) tokens;
mapping(address => bool) tokensMinted;
// The votes that each proposal has gotten
mapping(uint32 => uint32) proposals;
mapping(address => mapping(uint32 => uint32)) votesByFor;
constructor() {
proposalIndices["nothing"] = 0;
proposalIndices["mint"] = 1;
proposalIndices["chocolate"] = 2;
proposalIndices["vanilla"] = 3;
proposals[0] = 2;
}
function mintTokens() public {
require(!tokensMinted[msg.sender], "can't mint tokens twice");
tokens[msg.sender] = 9;
tokensMinted[msg.sender] = true;
}
function myTokens() public view returns(uint32) {
return tokens[msg.sender];
}
function voteFor(string memory proposal) public {
// Will be Nothing if not present
uint32 i = proposalIndices[proposal];
require(
tokens[msg.sender] >= 1,
"You need to have a token to vote with"
);
uint32 n = votesByFor[msg.sender][i] + 1;
// This makes it so that for N votes, we need to have spent a total of N^2 tokens
uint32 required = 2 * n - 1;
require(tokens[msg.sender] >= required, "Insufficient tokens");
tokens[msg.sender] -= required;
votesByFor[msg.sender][i] = n;
proposals[i] += 1;
}
// This will take away all votes that the user has allocated so far
function resetMyVotes() public {
for (uint32 i = 0; i < numProposals; ++i) {
uint32 n = votesByFor[msg.sender][i];
tokens[msg.sender] += n * n;
proposals[i] -= n;
votesByFor[msg.sender][i] = 0;
}
}
function myVotes(string memory proposal) public view returns(uint32 votes) {
uint32 i = proposalIndices[proposal];
return votesByFor[msg.sender][i];
}
function getVotes(string memory proposal) public view returns(uint32 votes) {
return proposals[proposalIndices[proposal]];
}
function winner() public view returns (string memory) {
uint32 maxI = 0;
uint32 max = proposals[0];
for (uint32 i = 1; i < numProposals; i++) {
if (proposals[i] > max) {
max = proposals[i];
maxI = i;
}
}
if (maxI == 1) {
return "mint";
}
if (maxI == 2) {
return "chocolate";
}
if (maxI == 3) {
return "vanilla";
}
return "nothing";
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
contract MultiFlavors {
mapping(string => uint32) proposalIndices;
uint32 numProposals = 4;
// The tokens we've given to each participant
mapping(address => uint32) tokens;
mapping(address => bool) tokensMinted;
// The votes that each proposal has gotten
mapping(uint32 => uint32) proposals;
mapping(address => mapping(uint32 => uint32)) votesByFor;
constructor() {
proposalIndices["nothing"] = 0;
proposalIndices["mint"] = 1;
proposalIndices["chocolate"] = 2;
proposalIndices["vanilla"] = 3;
proposals[0] = 4;
}
function mintTokens() public {
require(!tokensMinted[msg.sender], "can't mint tokens twice");
tokens[msg.sender] = 9;
tokensMinted[msg.sender] = true;
}
function myTokens() public view returns(uint32) {
return tokens[msg.sender];
}
function voteFor(string memory proposal) public {
// Will be Nothing if not present
uint32 i = proposalIndices[proposal];
require(
tokens[msg.sender] >= 1,
"You need to have a token to vote with"
);
uint32 n = votesByFor[msg.sender][i] + 1;
// This makes it so that for N votes, we need to have spent a total of N^2 tokens
uint32 required = 2 * n - 1;
require(tokens[msg.sender] >= required, "Insufficient tokens");
tokens[msg.sender] -= required;
votesByFor[msg.sender][i] = n;
proposals[i] += 1;
}
// This will take away all votes that the user has allocated so far
function resetMyVotes() public {
for (uint32 i = 0; i < numProposals; ++i) {
uint32 n = votesByFor[msg.sender][i];
tokens[msg.sender] += n * n;
proposals[i] -= n;
votesByFor[msg.sender][i] = 0;
}
}
function myVotes(string memory proposal) public view returns(uint32 votes) {
uint32 i = proposalIndices[proposal];
return votesByFor[msg.sender][i];
}
function getVotes(string memory proposal) public view returns(uint32 votes) {
return proposals[proposalIndices[proposal]];
}
function winner() public view returns (string memory) {
uint32 maxI = 0;
uint32 max = proposals[0];
for (uint32 i = 1; i < numProposals; i++) {
if (proposals[i] > max) {
max = proposals[i];
maxI = i;
}
}
if (maxI == 1) {
return "mint";
}
if (maxI == 2) {
return "chocolate";
}
if (maxI == 3) {
return "vanilla";
}
return "nothing";
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment