summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Year_3/Blockchain/CorporateManagement.sol188
-rw-r--r--Year_3/Blockchain/RandomMoney.sol43
-rw-r--r--Year_3/Blockchain/RockPaperScissor.sol124
-rw-r--r--Year_3/Blockchain/TicTacToe.sol153
4 files changed, 508 insertions, 0 deletions
diff --git a/Year_3/Blockchain/CorporateManagement.sol b/Year_3/Blockchain/CorporateManagement.sol
new file mode 100644
index 0000000..cb74871
--- /dev/null
+++ b/Year_3/Blockchain/CorporateManagement.sol
@@ -0,0 +1,188 @@
+/*
+ Corso Blockchain e Cryptocurrencies - Compito di laboratorio del 12/07/2021
+
+ Scrivere un contratto `CorporateManagement` che implementi l'interfaccia `CorporateManagementSpecs`
+ per la gestione delle decisioni di una società sulla piattaforma Ethereum usando il linguaggio Solidity.
+
+ All'interno della società ci possono essere una serie di soci (associates): il primo è il fondatore che
+ crea il contratto stesso con relativa quota (share). Gli altri soci si possono auto-candidare depositando
+ una quota sufficiente: tale candidatura viene considerata accettata solo se accettata a maggioranza dai
+ soci attuali. Ogni socio può depositare una generica proposta, consistente in una semplice descrizione
+ testuale: essa sarà considerata accettata se votata a maggioranza dai soci. Il voto per maggioranza (50% + 1)
+ tiene conto del peso della quota in ETH depositata da ogni socio.
+ La società, su proposta di qualche socio, può essere anche sciolta purché la risoluzione venga votata
+ all'unanimità.
+
+ Di seguito alcuni dettagli sui singoli elementi obbligatori dell'interfaccia:
+ - tutti i casi illustrati sopra vengono considerti come proposte da accettare: le tipologie di proposte
+ corrispondono all'enumerazione `ProposalCategory`;
+ - il primo socio (fondatore) crea il contratto specificando la quota minima e versando, contestualmente, la
+ propria di quota sufficiente;
+
+ X - il metodo `depositFunds` può essere usato da chiunque per auto-candidarsi a socio, versando la relativa
+ sufficiente quota; il metodo può essere usato sia dai soci che da candidati-soci per aumentare la propria
+ quota;
+ X - il metodo `voteProposal`, utilizzabile solo dai soci effettivi, può essere usato per aderire, con la propria
+ quota, all'accettazione della proposta tramite l'apposito identificativo; ovviamente un socio può votare una
+ sola volta per ogni singola proposta, tale metodo dovrà anche verificare se la proposta ha raggiunto i voti
+ necessari e far scaturire i relativi effetti;
+ X - i metodi `depositGenericProposal` e `depositDissolutionProposal` permettono, unicamente ai soci, di depositare,
+ rispettivamente, una generica proposta con descrizione testuale o una proposta di scioglimento;
+ - nel caso in cui una proposta di scioglimento venga accettata all'unanimità la società va in liquidazione e pertanto
+ nessuna funzionalità avrà più effetti ad eccezione del metodo `requestShareRefunding` che permette al singolo
+ socio di riscuotere la propria quota;
+ - i predicati `isAssociated` e `isDissoluted` permettono, rispettivamente, di verificare se un indirizzo specificato
+ corrisponde ad un socio effettivo o se la società è in scioglimento;
+
+ - gli eventi di tipo `New...` dovranno essere generati quando una proposta viene creata a seguito di un'azione
+ esterna; depositare dei fondi sufficienti da parte di un non-socio rappresenta una proposta di candidatura;
+ - gli eventi di tipo `Accepted....` dovranno essere generati ogniqualvolta una relativa proposta viene accettata;
+ notare che all'atto della creazione della società il socio fondatore è implicitamente accettato.
+
+ Il contratto dovrà occuparsi di validare gli input ricevuti secondo criteri ovvi di sensatezza.
+
+*/
+
+// SPDX-License-Identifier: None
+
+pragma solidity ^0.8.0;
+import "exam.sol";
+
+contract CorporateManagement is CorporateManagementSpecs {
+ enum AssociateRole { NONE, CANDIDATE, ASSOCIATE, FOUNDER }
+ struct Associate {
+ address payable id;
+ uint share;
+ AssociateRole role;
+ }
+
+ struct Proposal {
+ ProposalCategory category;
+ address owner;
+ Associate[] votes;
+ string descr;
+ }
+
+ uint public minimumShare;
+ mapping(address => Associate) associates;
+ address[] associateIds;
+ address public immutable founder;
+
+ Proposal[] public proposals;
+
+ bool public override isDissoluted = false;
+
+ function isAssociated(address id) external view override returns (bool) {
+ if (associates[id].id == address(0)) {
+ return false;
+ }
+
+ return (associates[id].role == AssociateRole.ASSOCIATE || associates[id].role == AssociateRole.FOUNDER);
+ }
+
+
+ constructor(uint minimumAssociatingShare) payable {
+ assert(minimumAssociatingShare == msg.value);
+ minimumShare = msg.value;
+ associates[msg.sender] = Associate({
+ id: payable(msg.sender),
+ share: msg.value,
+ role: AssociateRole.FOUNDER
+ });
+
+ associateIds.push(msg.sender);
+ founder = msg.sender;
+ }
+
+ modifier NeedDissolution(bool action) {
+ if (!action) {
+ require(!isDissoluted, "Can't do this action because the corporate is dissoluting");
+ } else {
+ require(isDissoluted, "Can't do this action because the corporate is not dissoluting");
+ }
+ _;
+ }
+
+ modifier NeedAssociate {
+ if (!this.isAssociated(msg.sender)) {
+ revert("You must be an associated to vote this proposal.");
+ }
+ _;
+ }
+
+ function depositFunds() external override payable NeedDissolution(false) {
+ if (associates[msg.sender].id == address(0)) {
+ require(msg.value >= minimumShare, "The share must be at least equals to the minimum");
+ associates[msg.sender] = Associate({
+ id: payable(msg.sender),
+ share: msg.value,
+ role: AssociateRole.CANDIDATE
+ });
+ Proposal storage p = proposals.push();
+ p.category = ProposalCategory.NewAssociationAcceptance;
+ p.owner = msg.sender;
+
+ emit NewAssociateCandidate(proposals.length-1, p.owner);
+ } else {
+ associates[msg.sender].share += msg.value;
+ }
+ }
+
+
+ function depositGenericProposal(string calldata description) external override NeedDissolution(false) NeedAssociate {
+ Proposal storage p = proposals.push();
+ p.category = ProposalCategory.Generic;
+ p.owner = msg.sender;
+ p.descr = description;
+
+ emit NewGenericProposal(proposals.length-1, description);
+ }
+
+
+ function depositDissolutionProposal() external override NeedDissolution(false) NeedAssociate {
+ Proposal storage p = proposals.push();
+ p.category = ProposalCategory.CorporateDissolution;
+ p.owner = msg.sender;
+
+ emit NewDissolutionProposal(proposals.length-1);
+ }
+
+
+ function voteProposal(uint proposalId) external override NeedDissolution(false) NeedAssociate {
+ Proposal storage p = proposals[proposalId];
+ uint totalShare = 0;
+ for (uint i = 0; i < p.votes.length; ++i) {
+ totalShare += p.votes[i].share;
+ if (p.votes[i].id == msg.sender) {
+ revert("Already voted this proposal!");
+ }
+ }
+ p.votes.push(associates[msg.sender]);
+ totalShare += associates[msg.sender].share;
+
+ uint minQuote = (address(this).balance / 2) + 1;
+
+ if (p.category == ProposalCategory.CorporateDissolution && p.votes.length == associateIds.length) {
+ isDissoluted = true;
+ emit AcceptedCorporateDissolution();
+ } else if (totalShare >= minQuote) {
+ if (p.category == ProposalCategory.NewAssociationAcceptance) {
+ assert(associates[p.owner].role == AssociateRole.CANDIDATE);
+ associateIds.push(p.owner);
+ associates[p.owner].role = AssociateRole.ASSOCIATE;
+ emit AcceptedAssociate(p.owner);
+ } else if (p.category == ProposalCategory.Generic) {
+ emit AcceptedGenericProposal(p.descr);
+ }
+ }
+ }
+
+
+ function requestShareRefunding() external override NeedDissolution(true) {
+ if (associates[msg.sender].id == address(0)) {
+ revert("You're not one of the associates, can't refund");
+ }
+
+ associates[msg.sender].id.transfer(associates[msg.sender].share);
+ }
+}
diff --git a/Year_3/Blockchain/RandomMoney.sol b/Year_3/Blockchain/RandomMoney.sol
new file mode 100644
index 0000000..2a5def4
--- /dev/null
+++ b/Year_3/Blockchain/RandomMoney.sol
@@ -0,0 +1,43 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity >0.7 <0.9;
+
+contract RandomMoney {
+ uint public total;
+ uint public constant minFee = 10;
+ mapping(address => uint) public amounts;
+ address[] private members;
+
+
+ function play() public payable returns (uint) {
+ require(msg.value >= minFee, "Value is less than the required minimum");
+
+
+ amounts[msg.sender] += msg.value;
+
+ if (amounts[msg.sender] == msg.value) {
+ members.push(msg.sender);
+ }
+
+ total += msg.value;
+
+ return total;
+ }
+
+ function pay() public payable {
+ address payable account = payable(randomAddress());
+ account.transfer(total);
+ total = 0;
+ }
+
+ function randomAddress() internal view returns (address) {
+ uint8 num = uint8(
+ uint256(
+ keccak256(abi.encode(block.timestamp, block.difficulty))
+ ) % members.length
+ );
+
+ return members[num];
+ }
+}
+
diff --git a/Year_3/Blockchain/RockPaperScissor.sol b/Year_3/Blockchain/RockPaperScissor.sol
new file mode 100644
index 0000000..528bc4c
--- /dev/null
+++ b/Year_3/Blockchain/RockPaperScissor.sol
@@ -0,0 +1,124 @@
+// SPDX-License-Identifier: MIT
+
+pragma solidity ^0.8.0;
+
+import "TrustworthyRockPaperScissorsTournamentSpecs.sol";
+
+contract TrustworthyRockPaperScissorsTournament is TrustworthyRockPaperScissorsTournamentSpecs {
+ enum Move { ROCK, PAPER, SCISSOR }
+ enum Status { IN_PROGRESS, END }
+ struct PlayerInfo {
+ address payable id;
+ uint8 wins;
+ Move[] games;
+ uint fees;
+ }
+
+ mapping(Player => PlayerInfo) players;
+ uint8 target;
+ uint256 matchFee;
+ Status status;
+ Player winner;
+ address payable owner;
+
+ uint8 override public disputedMatches;
+
+ constructor(
+ address payable firstPlayer,
+ address payable secondPlayer,
+ uint8 targetWins,
+ uint256 singleMatchFee
+ ) {
+ require(firstPlayer != secondPlayer, "Players must be two different addresses.");
+ require(firstPlayer != address(0) && secondPlayer != address(0), "Players address are not valid.");
+
+ players[Player.First].id = firstPlayer;
+ players[Player.Second].id = secondPlayer;
+ target = targetWins;
+ matchFee = singleMatchFee;
+ status = Status.IN_PROGRESS;
+ disputedMatches = 0;
+ owner = payable(msg.sender);
+ }
+
+ function moveRock() external override payable {
+ move(Move.ROCK);
+ }
+
+ function movePaper() external override payable {
+ move(Move.PAPER);
+ }
+
+ function moveScissor() external override payable {
+ move(Move.SCISSOR);
+ }
+
+ modifier mustBeAPlayer() {
+ require(msg.sender == players[Player.First].id || msg.sender == players[Player.Second].id, "You're not one of the players.");
+ _;
+ }
+
+ function move(Move move_type) internal mustBeAPlayer {
+ require(status != Status.END, "Game has already finish");
+ require(msg.value >= matchFee, "You need to pass the minimum fee to play");
+
+ Player p = (msg.sender == players[Player.First].id ? Player.First : Player.Second);
+ players[p].games.push(move_type);
+ players[p].fees += msg.value;
+
+ checkGame();
+ }
+
+ function checkGame() internal {
+ uint8 n = uint8(
+ players[Player.First].games.length < players[Player.Second].games.length
+ ? players[Player.First].games.length
+ : players[Player.Second].games.length
+ );
+
+ for (uint8 i = disputedMatches; i < n; ++i) {
+ Move p1 = players[Player.First].games[i];
+ Move p2 = players[Player.Second].games[i];
+
+ // Fair play
+ if (p1 == p2) continue;
+
+ if (
+ (p1 == Move.ROCK && p2 == Move.SCISSOR) ||
+ (p1 == Move.SCISSOR && p2 == Move.PAPER) ||
+ (p1 == Move.PAPER && p2 == Move.ROCK)
+ ) {
+ players[Player.First].wins++;
+ emit MatchWonBy(Player.First, i);
+ } else {
+ players[Player.Second].wins++;
+ emit MatchWonBy(Player.Second, i);
+ }
+
+ disputedMatches++;
+
+ if (players[Player.First].wins == target) {
+ emit TournamentWonBy(Player.First);
+ winner = Player.First;
+ endGame();
+ } else if (players[Player.Second].wins == target) {
+ emit TournamentWonBy(Player.Second);
+ winner = Player.Second;
+ endGame();
+ }
+ }
+ }
+
+ function endGame() internal {
+ require(status != Status.END, "Game has already finish");
+
+ assert(players[winner].wins == target);
+
+ uint total = players[Player.First].fees + players[Player.Second].fees;
+ assert(total == address(this).balance);
+ players[winner].id.transfer(total);
+
+ status = Status.END;
+ selfdestruct(owner);
+ }
+}
diff --git a/Year_3/Blockchain/TicTacToe.sol b/Year_3/Blockchain/TicTacToe.sol
new file mode 100644
index 0000000..582e10d
--- /dev/null
+++ b/Year_3/Blockchain/TicTacToe.sol
@@ -0,0 +1,153 @@
+// SPDX-License-Identifier: None
+
+pragma solidity ^0.8.0;
+
+contract TicTacToe {
+ enum Player { X, O }
+ struct PlayerInfo {
+ address payable id;
+ Player p_type;
+ }
+ enum Cell { X, O, NONE }
+ enum GameStatus { TURN_X, TURN_O, WAIT, END }
+
+ mapping(address => PlayerInfo) players;
+ address[2] playerIds;
+ Cell[3][3] cells;
+ Player turn;
+ GameStatus status = GameStatus.WAIT;
+ uint immutable minFee;
+ uint fee;
+ uint8 moves = 9;
+
+ event Movement(address sender, Player player, Cell[3][3] cells);
+ event EndGame(address winner, uint amount);
+
+ constructor(address payable firstPlayer, address payable secondPlayer, uint _minFee) {
+ require(firstPlayer != address(0) && secondPlayer != address(0), "You must define both address");
+ require(firstPlayer != secondPlayer, "Players must be different");
+
+ players[firstPlayer] = PlayerInfo({
+ id: firstPlayer,
+ p_type: Player.X
+ });
+ playerIds[0] = firstPlayer;
+ players[secondPlayer] = PlayerInfo({
+ id: secondPlayer,
+ p_type: Player.O
+ });
+ playerIds[1] = secondPlayer;
+ minFee = _minFee;
+ turn = Player(getRandomTurn());
+
+ for (uint8 i = 0; i < 3; ++i)
+ for (uint8 j = 0; j < 3; ++j)
+ cells[i][j] = Cell.NONE;
+ }
+
+ function play(uint x, uint y) public payable {
+ require(status != GameStatus.END, "Game is over");
+ require(players[msg.sender].id != address(0), "You're not one of the players");
+ require(players[msg.sender].p_type == turn, "It's not your turn");
+ require(msg.value >= minFee, "Value is less than the min fee");
+
+ fee += msg.value;
+
+ if (cells[y][x] != Cell.NONE) {
+ revert("Can't use this cell");
+ }
+
+ cells[y][x] = (turn == Player.X ? Cell.X : Cell.O);
+ turn = Player(1 - uint(turn));
+ moves--;
+ checkGame();
+ }
+
+ function getBoard() public view returns (Cell[3][3] memory) {
+ return cells;
+ }
+
+ /*
+ -------------
+ | | | |
+ |---|---|---|
+ | | | |
+ |---|---|---|
+ | | | |
+ -------------
+ */
+
+ function checkGame() internal {
+ // It's useless to check when moves > 4
+ if (moves > 4) return;
+
+ bool result = false;
+ PlayerInfo memory winner;
+
+ if (moves == 0) {
+ emit EndGame(address(this), fee);
+ status = GameStatus.END;
+ }
+
+ // Diagonal wins
+ if (
+ (cells[0][0] == cells[1][1] && cells[0][0] == cells[2][2]) ||
+ (cells[0][2] == cells[1][1] && cells[0][2] == cells[2][0])
+ ) {
+ address addr = address(0);
+ if (cells[0][0] != Cell.NONE) {
+ addr = playerIds[uint256(cells[0][0])];
+ winner = players[addr];
+ assert(uint(winner.p_type) == uint(cells[0][0]));
+ result = true;
+ } else if (cells[0][2] != Cell.NONE) {
+ addr = playerIds[uint256(cells[0][2])];
+ winner = players[addr];
+ assert(uint(winner.p_type) == uint(cells[0][2]));
+ result = true;
+ }
+ }
+
+ if (!result) {
+ for (uint8 i = 0; i < 3; i+=3) {
+ for (uint8 j = 0; j < 3; j+=3) {
+ if (cells[i][j] == Cell.NONE) continue;
+
+ address addr = playerIds[uint256(cells[i][j])];
+
+ if (
+ (cells[i][j] == cells[i+1][j] && cells[i][j] == cells[i+2][j]) ||
+ (cells[i][j] == cells[i][j+1] && cells[i][j+1] == cells[i][j+2])
+ ) {
+ result = true;
+ }
+
+ if (result) {
+ winner = players[addr];
+ assert(uint(winner.p_type) == uint(cells[i][j]));
+ break;
+ }
+
+ }
+
+ if (result) break;
+ }
+ }
+
+ if (result && winner.id != address(0)) {
+ assert(fee == address(this).balance);
+ winner.id.transfer(fee);
+ status = GameStatus.END;
+ emit EndGame(winner.id, fee);
+ fee = 0;
+ }
+ }
+
+ function getRandomTurn() internal view returns (uint8) {
+ return uint8(
+ uint256(
+ keccak256(abi.encode(block.timestamp, block.difficulty))
+ ) % 2
+ );
+ }
+}