@leofcoin/standards 0.2.15 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. package/.gitattributes +2 -2
  2. package/.github/workflows/test.yml +33 -0
  3. package/.prettierrc +8 -8
  4. package/CHANGELOG.md +16 -3
  5. package/LICENSE +21 -21
  6. package/README.md +25 -23
  7. package/exports/helpers.js +1 -1
  8. package/exports/i-public-voting.js +1 -0
  9. package/exports/index.d.ts +2 -1
  10. package/exports/index.js +2 -3
  11. package/exports/meta-D7uruGOw.js +28 -0
  12. package/exports/meta.d.ts +11 -0
  13. package/exports/private-voting.js +104 -11
  14. package/exports/public-voting.js +103 -4
  15. package/exports/roles.d.ts +5 -10
  16. package/exports/roles.js +7 -8
  17. package/exports/token-receiver.d.ts +5 -7
  18. package/exports/token-receiver.js +25 -14
  19. package/exports/token.d.ts +14 -27
  20. package/exports/token.js +14 -48
  21. package/exports/types.d.ts +21 -0
  22. package/exports/voting/private-voting.d.ts +108 -11
  23. package/exports/voting/public-voting.d.ts +45 -8
  24. package/exports/voting/types.d.ts +11 -7
  25. package/package.json +9 -14
  26. package/rollup.config.js +28 -28
  27. package/src/helpers.ts +19 -19
  28. package/src/index.ts +8 -7
  29. package/src/meta.ts +31 -0
  30. package/src/roles.ts +88 -89
  31. package/src/token-receiver.ts +196 -175
  32. package/src/token.ts +162 -198
  33. package/src/types.ts +15 -0
  34. package/src/voting/interfaces/i-public-voting.ts +4 -0
  35. package/src/voting/private-voting.ts +187 -69
  36. package/src/voting/public-voting.ts +134 -14
  37. package/src/voting/types.ts +30 -24
  38. package/test/helpers.js +51 -0
  39. package/test/public-voting.js +365 -6
  40. package/test/roles.js +186 -0
  41. package/test/token.js +211 -0
  42. package/tsconfig.json +16 -12
  43. package/.changeset/README.md +0 -8
  44. package/.changeset/config.json +0 -11
  45. package/exports/contract-creator.d.ts +0 -11
  46. package/exports/contract-creator.js +0 -20
  47. package/exports/lock.d.ts +0 -37
  48. package/exports/staking.d.ts +0 -40
  49. package/exports/voting/interfaces/i-voting.d.ts +0 -5
  50. package/exports/voting/voting.d.ts +0 -38
  51. package/exports/voting-C0KVNQO3.js +0 -112
  52. package/exports/voting-xYjJlN2h.js +0 -112
  53. package/src/contract-creator.ts +0 -24
  54. package/src/decorators/time.ts +0 -9
  55. package/src/interfaces/i-token.ts +0 -9
  56. package/src/lock.ts +0 -167
  57. package/src/staking.ts +0 -166
  58. package/src/voting/interfaces/i-voting.ts +0 -5
  59. package/src/voting/voting.ts +0 -123
@@ -1,14 +1,134 @@
1
- import ContractCreator, { ContractCreatorState } from '../contract-creator.js'
2
- import { VotingState, VoteResult } from './types.js'
3
- import Voting from './voting.js'
4
-
5
- export declare interface PublicVotingState extends VotingState, ContractCreatorState {}
6
-
7
- /**
8
- * allows everybody that has a balance greater or equeal then/to tokenAmountToReceive to vote
9
- */
10
- export default class PublicVoting extends Voting {
11
- constructor(state: PublicVotingState) {
12
- super(state)
13
- }
14
- }
1
+ import Meta from '../meta.js'
2
+ import { VotingState, VoteResult } from './types.js'
3
+
4
+ /**
5
+ * allows everybody that has a balance greater or equal to tokenAmountToReceive to vote
6
+ */
7
+ export default class PublicVoting extends Meta {
8
+ #votes: VotingState['votes']
9
+ #votingDisabled: boolean
10
+ #votingDuration: number = 172800000
11
+
12
+ constructor(state: VotingState) {
13
+ super(state)
14
+ if (state) {
15
+ this.#votes = state.votes
16
+ this.#votingDisabled = state.votingDisabled
17
+ }
18
+ }
19
+
20
+ get votes() {
21
+ return { ...this.#votes }
22
+ }
23
+
24
+ get votingDuration() {
25
+ return this.#votingDuration
26
+ }
27
+
28
+ get votingDisabled() {
29
+ return this.#votingDisabled
30
+ }
31
+
32
+ /**
33
+ *
34
+ */
35
+ get state() {
36
+ return {
37
+ ...super.state,
38
+ votes: this.#votes,
39
+ votingDisabled: this.#votingDisabled,
40
+ votingDuration: this.#votingDuration
41
+ }
42
+ }
43
+
44
+ get inProgress() {
45
+ return Object.entries(this.#votes)
46
+ .filter(([id, vote]) => !vote.finished)
47
+ .map(([id, vote]) => {
48
+ return { ...vote, id }
49
+ })
50
+ }
51
+ /**
52
+ * create vote
53
+ * @param {string} vote
54
+ * @param {string} description
55
+ * @param {number} endTime
56
+ * @param {string} method function to run when agree amount is bigger
57
+ */
58
+
59
+ createVote(
60
+ title: string,
61
+ description: string,
62
+ endTime: EpochTimeStamp,
63
+ method: string,
64
+ args: any[] = []
65
+ ) {
66
+ if (!this.#canVote()) throw new Error(`Not allowed to create a vote`)
67
+ const id = crypto.randomUUID()
68
+ this.#votes[id] = {
69
+ title,
70
+ description,
71
+ method,
72
+ endTime,
73
+ args
74
+ }
75
+ }
76
+
77
+ #canVote() {
78
+ // @ts-expect-error
79
+ return this._canVote?.()
80
+ }
81
+
82
+ #beforeVote() {
83
+ // @ts-expect-error
84
+ return this._beforeVote?.()
85
+ }
86
+
87
+ #endVoting(voteId) {
88
+ let agree = Object.values(this.#votes[voteId].results).filter(
89
+ (result) => result === 1
90
+ )
91
+ let disagree = Object.values(this.#votes[voteId].results).filter(
92
+ (result) => result === 0
93
+ )
94
+ if (agree.length > disagree.length && this.#votes[voteId].enoughVotes)
95
+ this[this.#votes[voteId].method](...this.#votes[voteId].args)
96
+ this.#votes[voteId].finished = true
97
+ }
98
+
99
+ async vote(voteId: string, vote: VoteResult) {
100
+ vote = Number(vote) as VoteResult
101
+ if (vote !== 0 && vote !== 0.5 && vote !== 1)
102
+ throw new Error(`invalid vote value ${vote}`)
103
+ if (!this.#votes[voteId]) throw new Error(`Nothing found for ${voteId}`)
104
+ const ended = new Date().getTime() > this.#votes[voteId].endTime
105
+ if (ended && !this.#votes[voteId].finished) this.#endVoting(voteId)
106
+ if (ended) throw new Error('voting already ended')
107
+ if (!this.#canVote()) throw new Error(`Not allowed to vote`)
108
+ await this.#beforeVote()
109
+ this.#votes[voteId][msg.sender] = vote
110
+ }
111
+
112
+ #disableVoting() {
113
+ this.#votingDisabled = true
114
+ }
115
+
116
+ disableVoting() {
117
+ if (!this.#canVote()) throw new Error('not a allowed')
118
+ else {
119
+ this.createVote(
120
+ `disable voting`,
121
+ `Warning this disables all voting features forever`,
122
+ new Date().getTime() + this.#votingDuration,
123
+ '#disableVoting',
124
+ []
125
+ )
126
+ }
127
+ }
128
+
129
+ _sync() {
130
+ for (const vote of this.inProgress) {
131
+ if (vote.endTime < new Date().getTime()) this.#endVoting(vote.id)
132
+ }
133
+ }
134
+ }
@@ -1,24 +1,30 @@
1
- export type VoteResult = 0 | 0.5 | 1
2
-
3
- export type Vote = {
4
- title: string
5
- method: string
6
- args: any[]
7
- description: string
8
- endTime: EpochTimeStamp
9
- results?: { [address: string]: VoteResult }
10
- finished?: boolean
11
- enoughVotes?: boolean
12
- }
13
-
14
- export interface VotingState {
15
- votes: {
16
- [id: string]: Vote
17
- }
18
- votingDisabled: boolean
19
- votingDuration: number
20
- }
21
-
22
- export interface VoteView extends Vote {
23
- id: string
24
- }
1
+ import { MetaState } from '../types.js'
2
+
3
+ export interface VotingState extends MetaState {
4
+ votes: {
5
+ [id: string]: Vote
6
+ }
7
+ votingDisabled: boolean
8
+ votingDuration: number
9
+ }
10
+
11
+ export interface PrivateVotingState extends VotingState {
12
+ voters: address[]
13
+ }
14
+
15
+ export type VoteResult = 0 | 0.5 | 1
16
+
17
+ export type Vote = {
18
+ title: string
19
+ method: string
20
+ args: any[]
21
+ description: string
22
+ endTime: EpochTimeStamp
23
+ results?: { [address: string]: VoteResult }
24
+ finished?: boolean
25
+ enoughVotes?: boolean
26
+ }
27
+
28
+ export interface VoteView extends Vote {
29
+ id: string
30
+ }
@@ -0,0 +1,51 @@
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import { restoreBalances, restoreApprovals } from './../exports/helpers.js'
4
+
5
+ test('Helpers - restoreBalances converts string balances to BigInt', () => {
6
+ const balances = {
7
+ '0x1': '1000',
8
+ '0x2': '2000',
9
+ '0x3': '5000'
10
+ }
11
+
12
+ const restored = restoreBalances(balances)
13
+
14
+ assert.equal(restored['0x1'], 1000n)
15
+ assert.equal(restored['0x2'], 2000n)
16
+ assert.equal(restored['0x3'], 5000n)
17
+ })
18
+
19
+ test('Helpers - restoreBalances handles empty balances', () => {
20
+ const balances = {}
21
+
22
+ const restored = restoreBalances(balances)
23
+
24
+ assert.deepEqual(restored, {})
25
+ })
26
+
27
+ test('Helpers - restoreApprovals converts nested string approvals to BigInt', () => {
28
+ const approvals = {
29
+ '0x1': {
30
+ '0x2': '500',
31
+ '0x3': '1000'
32
+ },
33
+ '0x2': {
34
+ '0x3': '250'
35
+ }
36
+ }
37
+
38
+ const restored = restoreApprovals(approvals)
39
+
40
+ assert.equal(restored['0x1']['0x2'], 500n)
41
+ assert.equal(restored['0x1']['0x3'], 1000n)
42
+ assert.equal(restored['0x2']['0x3'], 250n)
43
+ })
44
+
45
+ test('Helpers - restoreApprovals handles empty approvals', () => {
46
+ const approvals = {}
47
+
48
+ const restored = restoreApprovals(approvals)
49
+
50
+ assert.deepEqual(restored, {})
51
+ })
@@ -1,6 +1,365 @@
1
- import Voting from './../exports/public-voting.js'
2
-
3
- const voting = new Voting('0x0', '1000')
4
-
5
- console.log(voting)
6
- console.log(voting.votingDuration)
1
+ import { test } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import Voting from './../exports/public-voting.js'
4
+
5
+ test('PublicVoting - constructor initializes with correct state', () => {
6
+ const state = {
7
+ votes: {},
8
+ votingDisabled: false,
9
+ votingDuration: 172800000
10
+ }
11
+ const voting = new Voting(state)
12
+
13
+ assert.deepEqual(voting.votes, {})
14
+ assert.equal(voting.votingDisabled, false)
15
+ assert.equal(voting.votingDuration, 172800000)
16
+ })
17
+
18
+ test('PublicVoting - votingDuration getter returns correct value', () => {
19
+ const state = {
20
+ votes: {},
21
+ votingDisabled: false
22
+ }
23
+ const voting = new Voting(state)
24
+
25
+ assert.equal(voting.votingDuration, 172800000)
26
+ })
27
+
28
+ test('PublicVoting - votingDisabled getter returns correct value', () => {
29
+ const state = {
30
+ votes: {},
31
+ votingDisabled: true
32
+ }
33
+ const voting = new Voting(state)
34
+
35
+ assert.equal(voting.votingDisabled, true)
36
+ })
37
+
38
+ test('PublicVoting - inProgress returns empty array when no votes', () => {
39
+ const state = {
40
+ votes: {},
41
+ votingDisabled: false
42
+ }
43
+ const voting = new Voting(state)
44
+
45
+ assert.deepEqual(voting.inProgress, [])
46
+ })
47
+
48
+ test('PublicVoting - votes getter returns existing votes', () => {
49
+ const voteId = '123e4567-e89b-12d3-a456-426614174000'
50
+ const state = {
51
+ creator: '0x1234567890123456789012345678901234567890',
52
+ votes: {
53
+ [voteId]: {
54
+ title: 'Test Vote',
55
+ description: 'A test vote',
56
+ method: 'testMethod',
57
+ endTime: Date.now() + 100000,
58
+ args: [],
59
+ results: {},
60
+ finished: false
61
+ }
62
+ },
63
+ votingDisabled: false
64
+ }
65
+ const voting = new Voting(state)
66
+ const votes = voting.votes
67
+
68
+ assert.equal(Object.keys(votes).length, 1)
69
+ assert.equal(votes[voteId].title, 'Test Vote')
70
+ assert.equal(votes[voteId].description, 'A test vote')
71
+ assert.equal(votes[voteId].method, 'testMethod')
72
+ })
73
+
74
+ test('PublicVoting - inProgress returns only unfinished votes', () => {
75
+ const activeVoteId = '123e4567-e89b-12d3-a456-426614174000'
76
+ const finishedVoteId = '987e6543-e21b-12d3-a456-426614174001'
77
+ const state = {
78
+ creator: '0x1234567890123456789012345678901234567890',
79
+ votes: {
80
+ [activeVoteId]: {
81
+ title: 'Active Vote',
82
+ description: 'An active vote',
83
+ method: 'testMethod',
84
+ endTime: Date.now() + 100000,
85
+ args: [],
86
+ finished: false
87
+ },
88
+ [finishedVoteId]: {
89
+ title: 'Finished Vote',
90
+ description: 'A finished vote',
91
+ method: 'testMethod',
92
+ endTime: Date.now() - 100000,
93
+ args: [],
94
+ finished: true
95
+ }
96
+ },
97
+ votingDisabled: false
98
+ }
99
+ const voting = new Voting(state)
100
+ const inProgress = voting.inProgress
101
+
102
+ assert.equal(inProgress.length, 1)
103
+ assert.equal(inProgress[0].title, 'Active Vote')
104
+ assert.equal(inProgress[0].id, activeVoteId)
105
+ })
106
+
107
+ test('PublicVoting - vote throws error for invalid vote value', async () => {
108
+ const voteId = '123e4567-e89b-12d3-a456-426614174000'
109
+ const state = {
110
+ creator: '0x1234567890123456789012345678901234567890',
111
+ votes: {
112
+ [voteId]: {
113
+ title: 'Test Vote',
114
+ description: 'A test vote',
115
+ method: 'testMethod',
116
+ endTime: Date.now() + 100000,
117
+ args: [],
118
+ results: {},
119
+ finished: false
120
+ }
121
+ },
122
+ votingDisabled: false
123
+ }
124
+ const voting = new Voting(state)
125
+
126
+ await assert.rejects(async () => voting.vote(voteId, 2), /invalid vote value/)
127
+ })
128
+
129
+ test('PublicVoting - vote throws error for non-existent vote', async () => {
130
+ const state = {
131
+ creator: '0x1234567890123456789012345678901234567890',
132
+ votes: {},
133
+ votingDisabled: false
134
+ }
135
+ const voting = new Voting(state)
136
+
137
+ await assert.rejects(
138
+ async () => voting.vote('non-existent-id', 1),
139
+ /Nothing found for/
140
+ )
141
+ })
142
+
143
+ test('PublicVoting - vote throws error for ended vote', async () => {
144
+ const voteId = '123e4567-e89b-12d3-a456-426614174000'
145
+ const state = {
146
+ creator: '0x1234567890123456789012345678901234567890',
147
+ votes: {
148
+ [voteId]: {
149
+ title: 'Expired Vote',
150
+ description: 'An expired vote',
151
+ method: 'testMethod',
152
+ endTime: Date.now() - 100000, // Already ended
153
+ args: [],
154
+ results: {},
155
+ finished: false
156
+ }
157
+ },
158
+ votingDisabled: false
159
+ }
160
+ const voting = new Voting(state)
161
+
162
+ await assert.rejects(
163
+ async () => voting.vote(voteId, 1),
164
+ /voting already ended/
165
+ )
166
+ })
167
+
168
+ test('PublicVoting - vote ending marks vote as finished when time expires', () => {
169
+ const voteId = '123e4567-e89b-12d3-a456-426614174000'
170
+ const state = {
171
+ creator: '0x1234567890123456789012345678901234567890',
172
+ votes: {
173
+ [voteId]: {
174
+ title: 'Expired Vote',
175
+ description: 'A vote that should end',
176
+ method: 'testMethod',
177
+ endTime: Date.now() - 100000, // Already ended
178
+ args: [],
179
+ results: {
180
+ '0x1': 0, // disagree
181
+ '0x2': 0, // disagree
182
+ '0x3': 1 // agree
183
+ },
184
+ finished: false,
185
+ enoughVotes: true
186
+ }
187
+ },
188
+ votingDisabled: false
189
+ }
190
+ const voting = new Voting(state)
191
+
192
+ // Before sync, should be in progress
193
+ assert.equal(voting.inProgress.length, 1)
194
+
195
+ // Run sync to end expired vote
196
+ voting._sync()
197
+
198
+ // After sync, should not be in progress anymore
199
+ assert.equal(voting.inProgress.length, 0)
200
+ })
201
+
202
+ test('PublicVoting - _sync ends all expired in-progress votes', () => {
203
+ const expiredVoteId1 = '123e4567-e89b-12d3-a456-426614174000'
204
+ const expiredVoteId2 = '123e4567-e89b-12d3-a456-426614174001'
205
+ const activeVoteId = '123e4567-e89b-12d3-a456-426614174002'
206
+
207
+ const state = {
208
+ creator: '0x1234567890123456789012345678901234567890',
209
+ votes: {
210
+ [expiredVoteId1]: {
211
+ title: 'Expired Vote 1',
212
+ description: 'First expired vote',
213
+ method: 'testMethod',
214
+ endTime: Date.now() - 50000,
215
+ args: [],
216
+ results: { '0x1': 1, '0x2': 0 },
217
+ finished: false
218
+ },
219
+ [expiredVoteId2]: {
220
+ title: 'Expired Vote 2',
221
+ description: 'Second expired vote',
222
+ method: 'testMethod',
223
+ endTime: Date.now() - 100000,
224
+ args: [],
225
+ results: { '0x1': 0, '0x2': 0 },
226
+ finished: false
227
+ },
228
+ [activeVoteId]: {
229
+ title: 'Active Vote',
230
+ description: 'Still active vote',
231
+ method: 'testMethod',
232
+ endTime: Date.now() + 100000,
233
+ args: [],
234
+ results: {},
235
+ finished: false
236
+ }
237
+ },
238
+ votingDisabled: false
239
+ }
240
+ const voting = new Voting(state)
241
+
242
+ // Before sync, should have 3 in-progress votes
243
+ assert.equal(voting.inProgress.length, 3)
244
+
245
+ // Run sync to end expired votes
246
+ voting._sync()
247
+
248
+ // After sync, only the active vote should remain in progress
249
+ const inProgress = voting.inProgress
250
+ assert.equal(inProgress.length, 1)
251
+ assert.equal(inProgress[0].id, activeVoteId)
252
+
253
+ // Check that expired votes are marked as finished
254
+ const votes = voting.votes
255
+ assert.equal(votes[expiredVoteId1].finished, true)
256
+ assert.equal(votes[expiredVoteId2].finished, true)
257
+ assert.equal(votes[activeVoteId].finished, false)
258
+ })
259
+
260
+ test('PublicVoting - vote ending with more agrees than disagrees', () => {
261
+ const voteId = '123e4567-e89b-12d3-a456-426614174000'
262
+ const state = {
263
+ creator: '0x1234567890123456789012345678901234567890',
264
+ votes: {
265
+ [voteId]: {
266
+ title: 'Vote with Majority Agree',
267
+ description: 'Should pass with majority',
268
+ method: 'testMethod',
269
+ endTime: Date.now() - 1000,
270
+ args: ['arg1', 'arg2'],
271
+ results: {
272
+ '0x1': 1, // agree
273
+ '0x2': 1, // agree
274
+ '0x3': 1, // agree
275
+ '0x4': 0, // disagree
276
+ '0x5': 0 // disagree
277
+ },
278
+ finished: false,
279
+ enoughVotes: false // Not enough votes to execute method
280
+ }
281
+ },
282
+ votingDisabled: false
283
+ }
284
+ const voting = new Voting(state)
285
+
286
+ // Before sync, vote should be in progress
287
+ assert.equal(voting.inProgress.length, 1)
288
+
289
+ // Run sync to trigger vote ending
290
+ voting._sync()
291
+
292
+ // After sync, vote should not be in progress
293
+ assert.equal(voting.inProgress.length, 0)
294
+ })
295
+
296
+ test('PublicVoting - vote ending with more disagrees than agrees', async () => {
297
+ const voteId = '123e4567-e89b-12d3-a456-426614174000'
298
+ const state = {
299
+ creator: '0x1234567890123456789012345678901234567890',
300
+ votes: {
301
+ [voteId]: {
302
+ title: 'Vote with Majority Disagree',
303
+ description: 'Should not pass',
304
+ method: 'testMethod',
305
+ endTime: Date.now() - 1000,
306
+ args: [],
307
+ results: {
308
+ '0x1': 0, // disagree
309
+ '0x2': 0, // disagree
310
+ '0x3': 0, // disagree
311
+ '0x4': 1, // agree
312
+ '0x5': 1 // agree
313
+ },
314
+ finished: false,
315
+ enoughVotes: true
316
+ }
317
+ },
318
+ votingDisabled: false
319
+ }
320
+ const voting = new Voting(state)
321
+
322
+ // Trigger vote ending
323
+ try {
324
+ await voting.vote(voteId, 1)
325
+ } catch (err) {
326
+ // Expected to throw "voting already ended"
327
+ }
328
+
329
+ // Verify vote is finished even though it didn't pass
330
+ assert.equal(voting.votes[voteId].finished, true)
331
+ })
332
+
333
+ test('PublicVoting - vote ending without enoughVotes flag does not execute method', async () => {
334
+ const voteId = '123e4567-e89b-12d3-a456-426614174000'
335
+ const state = {
336
+ creator: '0x1234567890123456789012345678901234567890',
337
+ votes: {
338
+ [voteId]: {
339
+ title: 'Vote Without Enough Votes',
340
+ description: 'Has majority but not enough votes',
341
+ method: 'testMethod',
342
+ endTime: Date.now() - 1000,
343
+ args: [],
344
+ results: {
345
+ '0x1': 1, // agree
346
+ '0x2': 0 // disagree
347
+ },
348
+ finished: false,
349
+ enoughVotes: false // Not enough votes to execute
350
+ }
351
+ },
352
+ votingDisabled: false
353
+ }
354
+ const voting = new Voting(state)
355
+
356
+ // Trigger vote ending
357
+ try {
358
+ await voting.vote(voteId, 1)
359
+ } catch (err) {
360
+ // Expected to throw "voting already ended"
361
+ }
362
+
363
+ // Vote should be marked as finished
364
+ assert.equal(voting.votes[voteId].finished, true)
365
+ })