-
Notifications
You must be signed in to change notification settings - Fork 1
/
CorporateManagement.sol
241 lines (216 loc) · 9.24 KB
/
CorporateManagement.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
/*
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;
- 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;
- 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;
- 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 "./CorporateManagementSpecs.sol";
contract CorporateManagement is CorporateManagementSpecs {
struct Associate {
bool accepted;
uint256 share;
}
struct Proposal {
ProposalCategory category;
string description;
address associate;
address[] voters;
bool pending;
}
mapping(address => Associate) public associates;
mapping(uint256 => Proposal) public proposals;
uint256 public immutable minShare;
address payable immutable founder;
uint256 public totalShares;
bool private dissoluted;
uint256 private nProposals;
modifier isAssociate() {
require(
this.isAssociated(msg.sender),
"You must be an associate to perform this operation"
);
_;
}
modifier isNotDissoluted() {
require(
!dissoluted,
"The association has been dissoluted. You can still recover your share"
);
_;
}
modifier isFirstVote(uint256 proposalId) {
Proposal storage p = proposals[proposalId];
require(p.pending, "The proposal has ended or is invalid");
for (uint256 i = 0; i < p.voters.length; ++i)
if (p.voters[i] == msg.sender)
revert("You have already voted for this proposal");
_;
}
/* constructor(uint minimumAssociatingShare) payable */
constructor(uint256 minimumAssociatingShare) payable {
require(
minimumAssociatingShare > 0,
"The minimumAssociatingShare must be greater than 0"
);
require(
msg.value >= minimumAssociatingShare,
"The initial share must be greater or equal to the minShare provided"
);
founder = payable(msg.sender);
minShare = minimumAssociatingShare;
associates[msg.sender] = Associate(true, msg.value);
totalShares = msg.value;
dissoluted = false;
nProposals = 0;
emit AcceptedAssociate(msg.sender);
}
function depositFunds() external payable override isNotDissoluted {
Associate storage a = associates[msg.sender];
// New Associate proposal
if (a.share < minShare) {
require(
msg.value >= minShare,
"The operation requires a payment greater or equal to the minShare"
);
associates[msg.sender] = Associate(false, msg.value);
proposals[nProposals] = Proposal(
ProposalCategory.NewAssociationAcceptance,
"",
msg.sender,
new address[](0),
true
);
emit NewAssociateCandidate(nProposals, msg.sender);
nProposals++;
// Update share of old associate
} else {
a.share += msg.value;
}
if (this.isAssociated(msg.sender)) totalShares += msg.value;
}
function voteProposal(uint256 proposalId)
external
override
isAssociate
isNotDissoluted
isFirstVote(proposalId)
{
proposals[proposalId].voters.push(msg.sender);
uint256 voteShares = getVoteShares(proposalId);
checkProposal(proposalId, voteShares);
}
function depositGenericProposal(string calldata description)
external
override
isAssociate
isNotDissoluted
{
proposals[nProposals] = Proposal(
ProposalCategory.Generic,
description,
address(0),
new address[](0),
true
);
emit NewGenericProposal(nProposals, description);
nProposals++;
}
function depositDissolutionProposal()
external
override
isAssociate
isNotDissoluted
{
proposals[nProposals] = Proposal(
ProposalCategory.CorporateDissolution,
"",
address(0),
new address[](0),
true
);
emit NewDissolutionProposal(nProposals);
nProposals++;
}
function requestShareRefunding() external override {
uint256 share = associates[msg.sender].share;
assert(address(this).balance >= share);
payable(msg.sender).transfer(share);
if (this.isAssociated(msg.sender)) totalShares -= share;
if (address(this).balance == 0) selfdestruct(founder);
associates[msg.sender].share = 0;
associates[msg.sender].accepted = false;
}
function isAssociated(address id) external view override returns (bool) {
return associates[id].share >= minShare && associates[id].accepted;
}
function isDissoluted() external view override returns (bool) {
return dissoluted;
}
function getVoteShares(uint256 proposalId)
internal
view
returns (uint256 voteShares)
{
Proposal storage p = proposals[proposalId];
for (uint256 i = 0; i < p.voters.length; ++i)
voteShares += associates[p.voters[i]].share;
}
function checkProposal(uint256 proposalId, uint256 voteShares) internal {
Proposal storage p = proposals[proposalId];
ProposalCategory category = p.category;
if (
category == ProposalCategory.NewAssociationAcceptance &&
voteShares > totalShares / 2
) {
p.pending = false;
associates[p.associate].accepted = true;
totalShares += associates[p.associate].share;
emit AcceptedAssociate(p.associate);
} else if (
category == ProposalCategory.Generic && voteShares > totalShares / 2
) {
p.pending = false;
emit AcceptedGenericProposal(p.description);
} else if (
category == ProposalCategory.CorporateDissolution &&
voteShares == totalShares
) {
p.pending = false;
dissoluted = true;
emit AcceptedCorporateDissolution();
}
}
}