@leofcoin/standards 0.1.5 → 0.1.7

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.
@@ -0,0 +1,2 @@
1
+ export declare const restoreBalances: (balances: any) => {};
2
+ export declare const restoreApprovals: (approvals: any) => {};
@@ -0,0 +1,20 @@
1
+ // when state is stored it get encoded as a string to so we need to reformat balances back to BigNumbers
2
+ const restoreBalances = (balances) => {
3
+ const _balances = {};
4
+ for (const address in balances) {
5
+ _balances[address] = BigNumber['from'](balances[address]);
6
+ }
7
+ return _balances;
8
+ };
9
+ const restoreApprovals = (approvals) => {
10
+ const _approvals = {};
11
+ for (const owner in approvals) {
12
+ _approvals[owner] = {};
13
+ for (const operator in approvals[owner]) {
14
+ _approvals[owner][operator] = BigNumber['from'](approvals[owner][operator]);
15
+ }
16
+ }
17
+ return _approvals;
18
+ };
19
+
20
+ export { restoreApprovals, restoreBalances };
@@ -1,2 +1,4 @@
1
1
  export { default as Token } from './token.js';
2
2
  export { default as Roles } from './roles.js';
3
+ export { default as Voting } from './voting.js';
4
+ export * from './helpers.js';
package/exports/index.js CHANGED
@@ -1,2 +1,4 @@
1
1
  export { default as Token } from './token.js';
2
2
  export { default as Roles } from './roles.js';
3
+ export { default as Voting } from './voting.js';
4
+ export { restoreApprovals, restoreBalances } from './helpers.js';
@@ -3,11 +3,15 @@ export declare type TokenState = {
3
3
  roles: {
4
4
  [index: string]: address[];
5
5
  };
6
- holders: number;
6
+ holders: BigNumberish;
7
7
  balances: {
8
- [index: string]: BigNumberish;
8
+ [address: address]: BigNumberish;
9
+ };
10
+ approvals: {
11
+ [owner: address]: {
12
+ [operator: address]: BigNumberish;
13
+ };
9
14
  };
10
- approvals: {};
11
15
  totalSupply: BigNumberish;
12
16
  };
13
17
  export default class Token extends Roles {
package/exports/token.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { restoreBalances, restoreApprovals } from './helpers.js';
1
2
  import Roles from './roles.js';
2
3
 
3
4
  class Token extends Roles {
@@ -31,9 +32,9 @@ class Token extends Roles {
31
32
  throw new Error(`symbol undefined`);
32
33
  super(state?.roles);
33
34
  if (state) {
35
+ this.#balances = restoreBalances(state.balances);
36
+ this.#approvals = restoreApprovals(state.approvals);
34
37
  this.#holders = BigNumber['from'](state.holders);
35
- this.#balances = BigNumber['from'](state.balances);
36
- this.#approvals = BigNumber['from'](state.approvals);
37
38
  this.#totalSupply = BigNumber['from'](state.totalSupply);
38
39
  }
39
40
  else {
@@ -116,7 +117,7 @@ class Token extends Roles {
116
117
  const owner = msg.sender;
117
118
  if (!this.#approvals[owner])
118
119
  this.#approvals[owner] = {};
119
- this.#approvals[owner][operator] = amount;
120
+ this.#approvals[owner][operator] = BigNumber['from'](amount);
120
121
  }
121
122
  approved(owner, operator, amount) {
122
123
  return this.#approvals[owner][operator] === amount;
@@ -0,0 +1,134 @@
1
+ export type VoteResult = 0 | 0.5 | 1;
2
+ export type Vote = {
3
+ title: string;
4
+ method: string;
5
+ args: any[];
6
+ description: string;
7
+ endTime: EpochTimeStamp;
8
+ results?: {
9
+ [address: address]: VoteResult;
10
+ };
11
+ finished?: boolean;
12
+ enoughVotes?: boolean;
13
+ };
14
+ export type VotingState = {
15
+ voters: address[];
16
+ votes: {
17
+ [id: string]: Vote;
18
+ };
19
+ votingDisabled: boolean;
20
+ };
21
+ export interface VoteView extends Vote {
22
+ id: string;
23
+ }
24
+ export default class Voting {
25
+ #private;
26
+ constructor(state: VotingState);
27
+ get votes(): {
28
+ [x: string]: Vote;
29
+ };
30
+ get voters(): {
31
+ [x: number]: string;
32
+ length: number;
33
+ toString(): string;
34
+ toLocaleString(): string;
35
+ pop(): string;
36
+ push(...items: string[]): number;
37
+ concat(...items: ConcatArray<string>[]): string[];
38
+ concat(...items: (string | ConcatArray<string>)[]): string[];
39
+ join(separator?: string): string;
40
+ reverse(): string[];
41
+ shift(): string;
42
+ slice(start?: number, end?: number): string[];
43
+ sort(compareFn?: (a: string, b: string) => number): string[];
44
+ splice(start: number, deleteCount?: number): string[];
45
+ splice(start: number, deleteCount: number, ...items: string[]): string[];
46
+ unshift(...items: string[]): number;
47
+ indexOf(searchElement: string, fromIndex?: number): number;
48
+ lastIndexOf(searchElement: string, fromIndex?: number): number;
49
+ every<S extends string>(predicate: (value: string, index: number, array: string[]) => value is S, thisArg?: any): this is S[];
50
+ every(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
51
+ some(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): boolean;
52
+ forEach(callbackfn: (value: string, index: number, array: string[]) => void, thisArg?: any): void;
53
+ map<U>(callbackfn: (value: string, index: number, array: string[]) => U, thisArg?: any): U[];
54
+ filter<S_1 extends string>(predicate: (value: string, index: number, array: string[]) => value is S_1, thisArg?: any): S_1[];
55
+ filter(predicate: (value: string, index: number, array: string[]) => unknown, thisArg?: any): string[];
56
+ reduce(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string;
57
+ reduce(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string;
58
+ reduce<U_1>(callbackfn: (previousValue: U_1, currentValue: string, currentIndex: number, array: string[]) => U_1, initialValue: U_1): U_1;
59
+ reduceRight(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string): string;
60
+ reduceRight(callbackfn: (previousValue: string, currentValue: string, currentIndex: number, array: string[]) => string, initialValue: string): string;
61
+ reduceRight<U_2>(callbackfn: (previousValue: U_2, currentValue: string, currentIndex: number, array: string[]) => U_2, initialValue: U_2): U_2;
62
+ find<S_2 extends string>(predicate: (value: string, index: number, obj: string[]) => value is S_2, thisArg?: any): S_2;
63
+ find(predicate: (value: string, index: number, obj: string[]) => unknown, thisArg?: any): string;
64
+ findIndex(predicate: (value: string, index: number, obj: string[]) => unknown, thisArg?: any): number;
65
+ fill(value: string, start?: number, end?: number): string[];
66
+ copyWithin(target: number, start: number, end?: number): string[];
67
+ entries(): IterableIterator<[number, string]>;
68
+ keys(): IterableIterator<number>;
69
+ values(): IterableIterator<string>;
70
+ includes(searchElement: string, fromIndex?: number): boolean;
71
+ flatMap<U_3, This = undefined>(callback: (this: This, value: string, index: number, array: string[]) => U_3 | readonly U_3[], thisArg?: This): U_3[];
72
+ flat<A, D extends number = 1>(this: A, depth?: D): FlatArray<A, D>[];
73
+ at(index: number): string;
74
+ [Symbol.iterator](): IterableIterator<string>;
75
+ [Symbol.unscopables]: {
76
+ [x: number]: boolean;
77
+ length?: boolean;
78
+ toString?: boolean;
79
+ toLocaleString?: boolean;
80
+ pop?: boolean;
81
+ push?: boolean;
82
+ concat?: boolean;
83
+ join?: boolean;
84
+ reverse?: boolean;
85
+ shift?: boolean;
86
+ slice?: boolean;
87
+ sort?: boolean;
88
+ splice?: boolean;
89
+ unshift?: boolean;
90
+ indexOf?: boolean;
91
+ lastIndexOf?: boolean;
92
+ every?: boolean;
93
+ some?: boolean;
94
+ forEach?: boolean;
95
+ map?: boolean;
96
+ filter?: boolean;
97
+ reduce?: boolean;
98
+ reduceRight?: boolean;
99
+ find?: boolean;
100
+ findIndex?: boolean;
101
+ fill?: boolean;
102
+ copyWithin?: boolean;
103
+ entries?: boolean;
104
+ keys?: boolean;
105
+ values?: boolean;
106
+ includes?: boolean;
107
+ flatMap?: boolean;
108
+ flat?: boolean;
109
+ at?: boolean;
110
+ [Symbol.iterator]?: boolean;
111
+ readonly [Symbol.unscopables]?: boolean;
112
+ };
113
+ };
114
+ get votingDisabled(): boolean;
115
+ /**
116
+ *
117
+ */
118
+ get state(): {};
119
+ get inProgress(): VoteView[];
120
+ /**
121
+ * create vote
122
+ * @param {string} vote
123
+ * @param {string} description
124
+ * @param {number} endTime
125
+ * @param {string} method function to run when agree amount is bigger
126
+ */
127
+ createVote(title: string, description: string, endTime: EpochTimeStamp, method: string, args?: any[]): void;
128
+ canVote(address: address): boolean;
129
+ vote(voteId: string, vote: VoteResult): void;
130
+ disableVoting(): void;
131
+ grantVotingPower(address: address, voteId: string): void;
132
+ revokeVotingPower(address: address, voteId: string): void;
133
+ sync(): void;
134
+ }
@@ -0,0 +1,133 @@
1
+ class Voting {
2
+ #voters;
3
+ #votes;
4
+ #votingDisabled;
5
+ constructor(state) {
6
+ if (state) {
7
+ this.#voters = state.voters;
8
+ this.#votes = state.votes;
9
+ this.#votingDisabled = state.votingDisabled;
10
+ }
11
+ else {
12
+ this.#voters = [msg.sender];
13
+ }
14
+ }
15
+ get votes() {
16
+ return { ...this.#votes };
17
+ }
18
+ get voters() {
19
+ return { ...this.#voters };
20
+ }
21
+ get votingDisabled() {
22
+ return this.#votingDisabled;
23
+ }
24
+ /**
25
+ *
26
+ */
27
+ get state() {
28
+ return { voters: this.#voters, votes: this.#votes, votingDisabled: this.#votingDisabled };
29
+ }
30
+ get inProgress() {
31
+ return Object.entries(this.#votes)
32
+ .filter(([id, vote]) => !vote.finished)
33
+ .map(([id, vote]) => {
34
+ return { ...vote, id };
35
+ });
36
+ }
37
+ /**
38
+ * create vote
39
+ * @param {string} vote
40
+ * @param {string} description
41
+ * @param {number} endTime
42
+ * @param {string} method function to run when agree amount is bigger
43
+ */
44
+ createVote(title, description, endTime, method, args = []) {
45
+ if (!this.canVote(msg.sender))
46
+ throw new Error(`Not allowed to create a vote`);
47
+ const id = crypto.randomUUID();
48
+ this.#votes[id] = {
49
+ title,
50
+ description,
51
+ method,
52
+ endTime,
53
+ args
54
+ };
55
+ }
56
+ canVote(address) {
57
+ return this.#voters.includes(address);
58
+ }
59
+ #enoughVotes(id) {
60
+ return this.#voters.length - 2 <= Object.keys(this.#votes[id]).length;
61
+ }
62
+ #endVoting(voteId) {
63
+ let agree = Object.values(this.#votes[voteId].results).filter((result) => result === 1);
64
+ let disagree = Object.values(this.#votes[voteId].results).filter((result) => result === 0);
65
+ this.#votes[voteId].enoughVotes = this.#enoughVotes(voteId);
66
+ if (agree.length > disagree.length && this.#votes[voteId].enoughVotes)
67
+ this[this.#votes[voteId].method](...this.#votes[voteId].args);
68
+ this.#votes[voteId].finished = true;
69
+ }
70
+ vote(voteId, vote) {
71
+ vote = Number(vote);
72
+ if (vote !== 0 && vote !== 0.5 && vote !== 1)
73
+ throw new Error(`invalid vote value ${vote}`);
74
+ if (!this.#votes[voteId])
75
+ throw new Error(`Nothing found for ${voteId}`);
76
+ const ended = new Date().getTime() > this.#votes[voteId].endTime;
77
+ if (ended && !this.#votes[voteId].finished)
78
+ this.#endVoting(voteId);
79
+ if (ended)
80
+ throw new Error('voting already ended');
81
+ if (!this.canVote(msg.sender))
82
+ throw new Error(`Not allowed to vote`);
83
+ this.#votes[voteId][msg.sender] = vote;
84
+ if (this.#enoughVotes(voteId)) {
85
+ this.#endVoting(voteId);
86
+ }
87
+ }
88
+ #disableVoting() {
89
+ this.#votingDisabled = true;
90
+ this.#voters = [];
91
+ }
92
+ #grantVotingPower(address) {
93
+ this.#voters.push(address);
94
+ }
95
+ #revokeVotingPower(address) {
96
+ this.#voters.splice(this.#voters.indexOf(address));
97
+ }
98
+ disableVoting() {
99
+ if (!this.canVote(msg.sender))
100
+ throw new Error('not a allowed');
101
+ if (this.#voters.length === 1)
102
+ this.#disableVoting();
103
+ else {
104
+ this.createVote(`disable voting`, `Warning this disables all voting features forever`, new Date().getTime() + 172800000, '#disableVoting', []);
105
+ }
106
+ }
107
+ grantVotingPower(address, voteId) {
108
+ if (this.#voters.length === 1 && this.canVote(msg.sender))
109
+ this.#grantVotingPower(address);
110
+ else {
111
+ this.createVote(`grant voting power to ${address}`, `Should we grant ${address} voting power?`, new Date().getTime() + 172800000, '#grantVotingPower', [address]);
112
+ }
113
+ }
114
+ revokeVotingPower(address, voteId) {
115
+ if (!this.canVote(msg.sender))
116
+ throw new Error('not a allowed to vote');
117
+ if (this.#voters.length === 1 && address === msg.sender && !this.#votingDisabled)
118
+ throw new Error('only one voter left, disable voting before making this contract voteless');
119
+ if (this.#voters.length === 1)
120
+ this.#revokeVotingPower(address);
121
+ else {
122
+ this.createVote(`revoke voting power for ${address}`, `Should we revoke ${address} it's voting power?`, new Date().getTime() + 172800000, '#revokeVotingPower', [address]);
123
+ }
124
+ }
125
+ sync() {
126
+ for (const vote of this.inProgress) {
127
+ if (vote.endTime < new Date().getTime())
128
+ this.#endVoting(vote.id);
129
+ }
130
+ }
131
+ }
132
+
133
+ export { Voting as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@leofcoin/standards",
3
- "version": "0.1.5",
3
+ "version": "0.1.7",
4
4
  "description": "Contract standards",
5
5
  "type": "module",
6
6
  "exports": {
@@ -16,8 +16,18 @@
16
16
  "import": "./exports/roles.js",
17
17
  "types": "./exports/roles.d.ts"
18
18
  },
19
+ "./voting": {
20
+ "import": "./exports/voting.js",
21
+ "types": "./exports/voting.d.ts"
22
+ },
23
+ "./helpers": {
24
+ "import": "./exports/helpers.js",
25
+ "types": "./exports/helpers.d.ts"
26
+ },
19
27
  "./token.js": "./exports/token.js",
20
- "./roles.js": "./exports/roles.js"
28
+ "./roles.js": "./exports/roles.js",
29
+ "./voting.js": "./exports/voting.js",
30
+ "./helpers.js": "./exports/helpers.js"
21
31
  },
22
32
  "scripts": {
23
33
  "build": "rollup -c",
package/rollup.config.js CHANGED
@@ -1,23 +1,20 @@
1
1
  import typescript from '@rollup/plugin-typescript'
2
- import tsConfig from './tsconfig.json' assert { type: 'json'}
2
+ import tsConfig from './tsconfig.json' assert { type: 'json' }
3
3
  import { execSync } from 'child_process'
4
4
 
5
-
6
-
7
-
8
5
  // const templates = (await readdir('./src/templates')).map(path => join('./src/templates', path))
9
6
  const clean = () => {
10
7
  execSync('rm -rf www/*.js')
11
8
  return
12
9
  }
13
10
 
14
- export default [{
15
- input: ['src/index.ts', 'src/token.ts', 'src/roles.ts'],
16
- output: {
17
- dir: './exports',
18
- format: 'es'
19
- },
20
- plugins: [
21
- typescript(tsConfig)
22
- ]
23
- }]
11
+ export default [
12
+ {
13
+ input: ['src/index.ts', 'src/token.ts', 'src/roles.ts', 'src/voting.ts', 'src/helpers.ts'],
14
+ output: {
15
+ dir: './exports',
16
+ format: 'es'
17
+ },
18
+ plugins: [typescript(tsConfig)]
19
+ }
20
+ ]
package/src/helpers.ts ADDED
@@ -0,0 +1,19 @@
1
+ // when state is stored it get encoded as a string to so we need to reformat balances back to BigNumbers
2
+ export const restoreBalances = (balances) => {
3
+ const _balances = {}
4
+ for (const address in balances) {
5
+ _balances[address] = BigNumber['from'](balances[address])
6
+ }
7
+ return _balances
8
+ }
9
+
10
+ export const restoreApprovals = (approvals) => {
11
+ const _approvals = {}
12
+ for (const owner in approvals) {
13
+ _approvals[owner] = {}
14
+ for (const operator in approvals[owner]) {
15
+ _approvals[owner][operator] = BigNumber['from'](approvals[owner][operator])
16
+ }
17
+ }
18
+ return _approvals
19
+ }
package/src/index.ts CHANGED
@@ -1,2 +1,4 @@
1
1
  export { default as Token } from './token.js'
2
2
  export { default as Roles } from './roles.js'
3
+ export { default as Voting } from './voting.js'
4
+ export * from './helpers.js'
package/src/token.ts CHANGED
@@ -1,10 +1,11 @@
1
+ import { restoreApprovals, restoreBalances } from './helpers.js'
1
2
  import Roles from './roles.js'
2
3
 
3
4
  export declare type TokenState = {
4
5
  roles: { [index: string]: address[] }
5
- holders: number
6
- balances: { [index: string]: BigNumberish }
7
- approvals: {}
6
+ holders: BigNumberish
7
+ balances: { [address: address]: BigNumberish }
8
+ approvals: { [owner: address]: { [operator: address]: BigNumberish } }
8
9
  totalSupply: BigNumberish
9
10
  }
10
11
 
@@ -28,11 +29,11 @@ export default class Token extends Roles {
28
29
  /**
29
30
  * Object => Object => uint
30
31
  */
31
- #approvals = {}
32
+ #approvals: { [owner: string]: { [operator: string]: typeof BigNumber } } = {}
32
33
 
33
34
  #decimals = 18
34
35
 
35
- #totalSupply = BigNumber['from'](0)
36
+ #totalSupply: typeof BigNumber = BigNumber['from'](0)
36
37
 
37
38
  // this.#privateField2 = 1
38
39
  constructor(name: string, symbol: string, decimals: number = 18, state?: TokenState) {
@@ -42,9 +43,9 @@ export default class Token extends Roles {
42
43
  super(state?.roles)
43
44
 
44
45
  if (state) {
46
+ this.#balances = restoreBalances(state.balances)
47
+ this.#approvals = restoreApprovals(state.approvals)
45
48
  this.#holders = BigNumber['from'](state.holders)
46
- this.#balances = BigNumber['from'](state.balances)
47
- this.#approvals = BigNumber['from'](state.approvals)
48
49
  this.#totalSupply = BigNumber['from'](state.totalSupply)
49
50
  } else {
50
51
  this.#name = name
@@ -138,7 +139,7 @@ export default class Token extends Roles {
138
139
  setApproval(operator: address, amount: BigNumberish) {
139
140
  const owner = msg.sender
140
141
  if (!this.#approvals[owner]) this.#approvals[owner] = {}
141
- this.#approvals[owner][operator] = amount
142
+ this.#approvals[owner][operator] = BigNumber['from'](amount)
142
143
  }
143
144
 
144
145
  approved(owner: address, operator: address, amount: BigNumberish): boolean {
package/src/voting.ts ADDED
@@ -0,0 +1,178 @@
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: address]: VoteResult }
10
+ finished?: boolean
11
+ enoughVotes?: boolean
12
+ }
13
+
14
+ export type VotingState = {
15
+ voters: address[]
16
+ votes: {
17
+ [id: string]: Vote
18
+ }
19
+ votingDisabled: boolean
20
+ }
21
+ export interface VoteView extends Vote {
22
+ id: string
23
+ }
24
+
25
+ export default class Voting {
26
+ #voters: VotingState['voters']
27
+ #votes: VotingState['votes']
28
+ #votingDisabled: boolean
29
+
30
+ constructor(state: VotingState) {
31
+ if (state) {
32
+ this.#voters = state.voters
33
+ this.#votes = state.votes
34
+ this.#votingDisabled = state.votingDisabled
35
+ } else {
36
+ this.#voters = [msg.sender]
37
+ }
38
+ }
39
+
40
+ get votes() {
41
+ return { ...this.#votes }
42
+ }
43
+
44
+ get voters() {
45
+ return { ...this.#voters }
46
+ }
47
+
48
+ get votingDisabled() {
49
+ return this.#votingDisabled
50
+ }
51
+
52
+ /**
53
+ *
54
+ */
55
+ get state(): {} {
56
+ return { voters: this.#voters, votes: this.#votes, votingDisabled: this.#votingDisabled }
57
+ }
58
+
59
+ get inProgress(): VoteView[] {
60
+ return Object.entries(this.#votes)
61
+ .filter(([id, vote]) => !vote.finished)
62
+ .map(([id, vote]) => {
63
+ return { ...vote, id }
64
+ })
65
+ }
66
+ /**
67
+ * create vote
68
+ * @param {string} vote
69
+ * @param {string} description
70
+ * @param {number} endTime
71
+ * @param {string} method function to run when agree amount is bigger
72
+ */
73
+
74
+ createVote(title: string, description: string, endTime: EpochTimeStamp, method: string, args: any[] = []) {
75
+ if (!this.canVote(msg.sender)) throw new Error(`Not allowed to create a vote`)
76
+ const id = crypto.randomUUID()
77
+ this.#votes[id] = {
78
+ title,
79
+ description,
80
+ method,
81
+ endTime,
82
+ args
83
+ }
84
+ }
85
+
86
+ canVote(address: address) {
87
+ return this.#voters.includes(address)
88
+ }
89
+
90
+ #enoughVotes(id) {
91
+ return this.#voters.length - 2 <= Object.keys(this.#votes[id]).length
92
+ }
93
+
94
+ #endVoting(voteId) {
95
+ let agree = Object.values(this.#votes[voteId].results).filter((result) => result === 1)
96
+ let disagree = Object.values(this.#votes[voteId].results).filter((result) => result === 0)
97
+ this.#votes[voteId].enoughVotes = this.#enoughVotes(voteId)
98
+ if (agree.length > disagree.length && this.#votes[voteId].enoughVotes)
99
+ this[this.#votes[voteId].method](...this.#votes[voteId].args)
100
+ this.#votes[voteId].finished = true
101
+ }
102
+
103
+ vote(voteId: string, vote: VoteResult) {
104
+ vote = Number(vote) as VoteResult
105
+ if (vote !== 0 && vote !== 0.5 && vote !== 1) throw new Error(`invalid vote value ${vote}`)
106
+ if (!this.#votes[voteId]) throw new Error(`Nothing found for ${voteId}`)
107
+ const ended = new Date().getTime() > this.#votes[voteId].endTime
108
+ if (ended && !this.#votes[voteId].finished) this.#endVoting(voteId)
109
+ if (ended) throw new Error('voting already ended')
110
+ if (!this.canVote(msg.sender)) throw new Error(`Not allowed to vote`)
111
+ this.#votes[voteId][msg.sender] = vote
112
+ if (this.#enoughVotes(voteId)) {
113
+ this.#endVoting(voteId)
114
+ }
115
+ }
116
+
117
+ #disableVoting() {
118
+ this.#votingDisabled = true
119
+ this.#voters = []
120
+ }
121
+
122
+ #grantVotingPower(address) {
123
+ this.#voters.push(address)
124
+ }
125
+
126
+ #revokeVotingPower(address) {
127
+ this.#voters.splice(this.#voters.indexOf(address))
128
+ }
129
+
130
+ disableVoting() {
131
+ if (!this.canVote(msg.sender)) throw new Error('not a allowed')
132
+ if (this.#voters.length === 1) this.#disableVoting()
133
+ else {
134
+ this.createVote(
135
+ `disable voting`,
136
+ `Warning this disables all voting features forever`,
137
+ new Date().getTime() + 172800000,
138
+ '#disableVoting',
139
+ []
140
+ )
141
+ }
142
+ }
143
+
144
+ grantVotingPower(address: address, voteId: string) {
145
+ if (this.#voters.length === 1 && this.canVote(msg.sender)) this.#grantVotingPower(address)
146
+ else {
147
+ this.createVote(
148
+ `grant voting power to ${address}`,
149
+ `Should we grant ${address} voting power?`,
150
+ new Date().getTime() + 172800000,
151
+ '#grantVotingPower',
152
+ [address]
153
+ )
154
+ }
155
+ }
156
+
157
+ revokeVotingPower(address: address, voteId: string) {
158
+ if (!this.canVote(msg.sender)) throw new Error('not a allowed to vote')
159
+ if (this.#voters.length === 1 && address === msg.sender && !this.#votingDisabled)
160
+ throw new Error('only one voter left, disable voting before making this contract voteless')
161
+ if (this.#voters.length === 1) this.#revokeVotingPower(address)
162
+ else {
163
+ this.createVote(
164
+ `revoke voting power for ${address}`,
165
+ `Should we revoke ${address} it's voting power?`,
166
+ new Date().getTime() + 172800000,
167
+ '#revokeVotingPower',
168
+ [address]
169
+ )
170
+ }
171
+ }
172
+
173
+ sync() {
174
+ for (const vote of this.inProgress) {
175
+ if (vote.endTime < new Date().getTime()) this.#endVoting(vote.id)
176
+ }
177
+ }
178
+ }