From df800f05416aa11bd04501b6dd771307d05dc8b7 Mon Sep 17 00:00:00 2001 From: Santo Cariotti Date: Mon, 20 Jun 2022 14:53:17 +0200 Subject: Add exercises --- Year_3/Blockchain/CorporateManagement.sol | 188 ++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 Year_3/Blockchain/CorporateManagement.sol (limited to 'Year_3/Blockchain/CorporateManagement.sol') 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); + } +} -- cgit v1.2.3-18-g5258