summaryrefslogtreecommitdiff
path: root/Year_3/Blockchain/CorporateManagement.sol
diff options
context:
space:
mode:
Diffstat (limited to 'Year_3/Blockchain/CorporateManagement.sol')
-rw-r--r--Year_3/Blockchain/CorporateManagement.sol188
1 files changed, 188 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);
+ }
+}