@leofcoin/standards 0.1.7 → 0.2.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.
- package/.changeset/README.md +8 -0
- package/.changeset/config.json +11 -0
- package/CHANGELOG.md +7 -0
- package/README.md +6 -4
- package/exports/index.d.ts +3 -1
- package/exports/index.js +3 -1
- package/exports/private-voting.js +133 -0
- package/exports/public-voting.js +104 -0
- package/exports/token-receiver.d.ts +47 -0
- package/exports/token-receiver.js +128 -0
- package/exports/token.d.ts +6 -0
- package/exports/token.js +6 -0
- package/exports/voting/interfaces/public-voting.d.ts +4 -0
- package/exports/voting/private-voting.d.ts +32 -0
- package/exports/voting/public-voting.d.ts +47 -0
- package/exports/voting/types.d.ts +23 -0
- package/package.json +66 -56
- package/rollup.config.js +10 -3
- package/src/index.ts +4 -1
- package/src/token-receiver.ts +176 -0
- package/src/token.ts +8 -0
- package/src/voting/interfaces/public-voting.ts +4 -0
- package/src/{voting.ts → voting/private-voting.ts} +7 -25
- package/src/voting/public-voting.ts +116 -0
- package/src/voting/types.ts +24 -0
- package/test/public-voting.js +6 -0
- package/tsconfig.json +5 -3
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changesets
|
|
2
|
+
|
|
3
|
+
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
|
4
|
+
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
|
5
|
+
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
|
6
|
+
|
|
7
|
+
We have a quick list of common questions to get you started engaging with this project in
|
|
8
|
+
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
|
3
|
+
"changelog": "@changesets/cli/changelog",
|
|
4
|
+
"commit": false,
|
|
5
|
+
"fixed": [],
|
|
6
|
+
"linked": [],
|
|
7
|
+
"access": "public",
|
|
8
|
+
"baseBranch": "main",
|
|
9
|
+
"updateInternalDependencies": "patch",
|
|
10
|
+
"ignore": []
|
|
11
|
+
}
|
package/CHANGELOG.md
ADDED
package/README.md
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
# standards
|
|
2
|
+
|
|
2
3
|
> Contract standards
|
|
3
4
|
|
|
4
5
|
standards used in leofcoin chain kinda like erc20's but the native coin included
|
|
5
6
|
|
|
6
7
|
## install
|
|
8
|
+
|
|
7
9
|
```sh
|
|
8
10
|
npm i @leofcoin/standards
|
|
9
11
|
```
|
|
10
12
|
|
|
11
13
|
## usage
|
|
14
|
+
|
|
12
15
|
```js
|
|
13
|
-
import {Token} from '@leofcoin/standards'
|
|
16
|
+
import { Token } from '@leofcoin/standards'
|
|
14
17
|
|
|
15
18
|
class myCoolToken extends Token {
|
|
16
|
-
constructor() {
|
|
17
|
-
super('myCoolToken', 'MCT', 18, state
|
|
19
|
+
constructor(state) {
|
|
20
|
+
super('myCoolToken', 'MCT', 18, state)
|
|
18
21
|
}
|
|
19
22
|
}
|
|
20
23
|
```
|
|
21
|
-
|
package/exports/index.d.ts
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { default as Token } from './token.js';
|
|
2
2
|
export { default as Roles } from './roles.js';
|
|
3
|
-
export { default as
|
|
3
|
+
export { default as TokenReceiver } from './token-receiver.js';
|
|
4
|
+
export { default as PublicVoting } from './voting/public-voting.js';
|
|
5
|
+
export { default as PrivateVoting } from './voting/private-voting.js';
|
|
4
6
|
export * from './helpers.js';
|
package/exports/index.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
export { default as Token } from './token.js';
|
|
2
2
|
export { default as Roles } from './roles.js';
|
|
3
|
-
export { default as
|
|
3
|
+
export { default as TokenReceiver } from './token-receiver.js';
|
|
4
|
+
export { default as PublicVoting } from './public-voting.js';
|
|
5
|
+
export { default as PrivateVoting } from './private-voting.js';
|
|
4
6
|
export { restoreApprovals, restoreBalances } from './helpers.js';
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
class PrivateVoting {
|
|
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 { PrivateVoting as default };
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* allows everybody that has a balance greater or equeal then/to tokenAmountToReceive to vote
|
|
3
|
+
*/
|
|
4
|
+
class PublicVoting {
|
|
5
|
+
#votes;
|
|
6
|
+
#votingDisabled;
|
|
7
|
+
#votingDuration = 172800000;
|
|
8
|
+
constructor(state) {
|
|
9
|
+
if (state) {
|
|
10
|
+
this.#votes = state.votes;
|
|
11
|
+
this.#votingDisabled = state.votingDisabled;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
get votes() {
|
|
15
|
+
return { ...this.#votes };
|
|
16
|
+
}
|
|
17
|
+
get votingDuration() {
|
|
18
|
+
return this.#votingDuration;
|
|
19
|
+
}
|
|
20
|
+
get votingDisabled() {
|
|
21
|
+
return this.#votingDisabled;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
*
|
|
25
|
+
*/
|
|
26
|
+
get state() {
|
|
27
|
+
return { votes: this.#votes, votingDisabled: this.#votingDisabled, votingDuration: this.#votingDuration };
|
|
28
|
+
}
|
|
29
|
+
get inProgress() {
|
|
30
|
+
return Object.entries(this.#votes)
|
|
31
|
+
.filter(([id, vote]) => !vote.finished)
|
|
32
|
+
.map(([id, vote]) => {
|
|
33
|
+
return { ...vote, id };
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* create vote
|
|
38
|
+
* @param {string} vote
|
|
39
|
+
* @param {string} description
|
|
40
|
+
* @param {number} endTime
|
|
41
|
+
* @param {string} method function to run when agree amount is bigger
|
|
42
|
+
*/
|
|
43
|
+
createVote(title, description, endTime, method, args = []) {
|
|
44
|
+
if (!this.#canVote())
|
|
45
|
+
throw new Error(`Not allowed to create a vote`);
|
|
46
|
+
const id = crypto.randomUUID();
|
|
47
|
+
this.#votes[id] = {
|
|
48
|
+
title,
|
|
49
|
+
description,
|
|
50
|
+
method,
|
|
51
|
+
endTime,
|
|
52
|
+
args
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
#canVote() {
|
|
56
|
+
// @ts-expect-error
|
|
57
|
+
return this._canVote?.();
|
|
58
|
+
}
|
|
59
|
+
#beforeVote() {
|
|
60
|
+
// @ts-expect-error
|
|
61
|
+
return this._beforeVote?.();
|
|
62
|
+
}
|
|
63
|
+
#endVoting(voteId) {
|
|
64
|
+
let agree = Object.values(this.#votes[voteId].results).filter((result) => result === 1);
|
|
65
|
+
let disagree = Object.values(this.#votes[voteId].results).filter((result) => result === 0);
|
|
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
|
+
async 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())
|
|
82
|
+
throw new Error(`Not allowed to vote`);
|
|
83
|
+
await this.#beforeVote();
|
|
84
|
+
this.#votes[voteId][msg.sender] = vote;
|
|
85
|
+
}
|
|
86
|
+
#disableVoting() {
|
|
87
|
+
this.#votingDisabled = true;
|
|
88
|
+
}
|
|
89
|
+
disableVoting() {
|
|
90
|
+
if (!this.#canVote())
|
|
91
|
+
throw new Error('not a allowed');
|
|
92
|
+
else {
|
|
93
|
+
this.createVote(`disable voting`, `Warning this disables all voting features forever`, new Date().getTime() + this.#votingDuration, '#disableVoting', []);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
_sync() {
|
|
97
|
+
for (const vote of this.inProgress) {
|
|
98
|
+
if (vote.endTime < new Date().getTime())
|
|
99
|
+
this.#endVoting(vote.id);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
export { PublicVoting as default };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { IPublicVoting } from './voting/interfaces/public-voting.js';
|
|
2
|
+
import PublicVoting from './voting/public-voting.js';
|
|
3
|
+
import { VotingState } from './voting/types.js';
|
|
4
|
+
export interface TokenReceiverState extends VotingState {
|
|
5
|
+
tokenToReceive: address;
|
|
6
|
+
tokenReceiver: address;
|
|
7
|
+
tokenAmountToReceive: typeof BigNumber;
|
|
8
|
+
voteType: 'burn' | 'transfer';
|
|
9
|
+
}
|
|
10
|
+
export default class TokenReceiver extends PublicVoting implements IPublicVoting {
|
|
11
|
+
#private;
|
|
12
|
+
constructor(tokenToReceive: address, tokenAmountToReceive: typeof BigNumber, burns: boolean, state?: TokenReceiverState);
|
|
13
|
+
get tokenToReceive(): string;
|
|
14
|
+
get tokenAmountToReceive(): import("@ethersproject/bignumber").BigNumber;
|
|
15
|
+
get tokenReceiver(): string;
|
|
16
|
+
get state(): {
|
|
17
|
+
tokenReceiver: string;
|
|
18
|
+
tokenToReceive: string;
|
|
19
|
+
tokenAmountToReceive: import("@ethersproject/bignumber").BigNumber;
|
|
20
|
+
voteType: "burn" | "transfer";
|
|
21
|
+
votes: {
|
|
22
|
+
[id: string]: import("./voting/types.js").Vote;
|
|
23
|
+
};
|
|
24
|
+
votingDisabled: boolean;
|
|
25
|
+
votingDuration: number;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* check if sender can pay
|
|
29
|
+
* @returns {boolean} promise
|
|
30
|
+
*/
|
|
31
|
+
_canVote(): Promise<boolean>;
|
|
32
|
+
_beforeVote(): Promise<any>;
|
|
33
|
+
/**
|
|
34
|
+
* check if sender can pay
|
|
35
|
+
* @returns {boolean} promise
|
|
36
|
+
*/
|
|
37
|
+
_payTokenToReceive(): Promise<boolean>;
|
|
38
|
+
/**
|
|
39
|
+
* check if sender can pay
|
|
40
|
+
* @returns {boolean} promise
|
|
41
|
+
*/
|
|
42
|
+
_burnTokenToReceive(): Promise<boolean>;
|
|
43
|
+
changeVoteType(type: TokenReceiverState['voteType']): Promise<void>;
|
|
44
|
+
getTokensOut(amount: typeof BigNumber, receiver: address): void;
|
|
45
|
+
changeTokenAmountToReceive(): void;
|
|
46
|
+
changeTokenToReceive(): Promise<void>;
|
|
47
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import PublicVoting from './public-voting.js';
|
|
2
|
+
|
|
3
|
+
class TokenReceiver extends PublicVoting {
|
|
4
|
+
#tokenToReceive;
|
|
5
|
+
#tokenAmountToReceive;
|
|
6
|
+
#tokenReceiver;
|
|
7
|
+
#voteType = 'transfer';
|
|
8
|
+
constructor(tokenToReceive, tokenAmountToReceive, burns, state) {
|
|
9
|
+
super(state);
|
|
10
|
+
if (state) {
|
|
11
|
+
this.#tokenReceiver = state.tokenReceiver;
|
|
12
|
+
this.#tokenToReceive = state.tokenToReceive;
|
|
13
|
+
this.#tokenAmountToReceive = BigNumber['from'](state.tokenAmountToReceive);
|
|
14
|
+
this.#voteType = state.voteType;
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
this.#tokenReceiver = msg.contract;
|
|
18
|
+
this.#tokenToReceive = tokenToReceive;
|
|
19
|
+
this.#tokenAmountToReceive = BigNumber['from'](tokenAmountToReceive);
|
|
20
|
+
if (burns)
|
|
21
|
+
this.#voteType = 'burn';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
get tokenToReceive() {
|
|
25
|
+
return this.#tokenToReceive;
|
|
26
|
+
}
|
|
27
|
+
get tokenAmountToReceive() {
|
|
28
|
+
return this.#tokenAmountToReceive;
|
|
29
|
+
}
|
|
30
|
+
get tokenReceiver() {
|
|
31
|
+
return this.#tokenReceiver;
|
|
32
|
+
}
|
|
33
|
+
get state() {
|
|
34
|
+
return {
|
|
35
|
+
...super.state,
|
|
36
|
+
tokenReceiver: this.#tokenReceiver,
|
|
37
|
+
tokenToReceive: this.#tokenToReceive,
|
|
38
|
+
tokenAmountToReceive: this.#tokenAmountToReceive,
|
|
39
|
+
voteType: this.#voteType
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
async #canVote() {
|
|
43
|
+
const amount = (await msg.staticCall(this.#tokenToReceive, 'balanceOf', [msg.sender]));
|
|
44
|
+
return amount.gte(this.#tokenAmountToReceive);
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* check if sender can pay
|
|
48
|
+
* @returns {boolean} promise
|
|
49
|
+
*/
|
|
50
|
+
async _canVote() {
|
|
51
|
+
return this.#canVote();
|
|
52
|
+
}
|
|
53
|
+
async #beforeVote() {
|
|
54
|
+
if (this.#voteType === 'burn')
|
|
55
|
+
return msg.staticCall(this.tokenToReceive, 'burn', [this.tokenAmountToReceive]);
|
|
56
|
+
return msg.staticCall(this.tokenToReceive, 'transfer', [msg.sender, this.tokenReceiver, this.tokenAmountToReceive]);
|
|
57
|
+
}
|
|
58
|
+
async _beforeVote() {
|
|
59
|
+
await this.#beforeVote();
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* check if sender can pay
|
|
63
|
+
* @returns {boolean} promise
|
|
64
|
+
*/
|
|
65
|
+
async _payTokenToReceive() {
|
|
66
|
+
return msg.staticCall(this.#tokenToReceive, 'transfer', [
|
|
67
|
+
msg.sender,
|
|
68
|
+
this.#tokenReceiver,
|
|
69
|
+
this.#tokenAmountToReceive
|
|
70
|
+
]);
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* check if sender can pay
|
|
74
|
+
* @returns {boolean} promise
|
|
75
|
+
*/
|
|
76
|
+
async _burnTokenToReceive() {
|
|
77
|
+
return msg.staticCall(this.#tokenToReceive, 'burn', [this.#tokenAmountToReceive]);
|
|
78
|
+
}
|
|
79
|
+
#changeTokenToReceive(address) {
|
|
80
|
+
this.#tokenToReceive = address;
|
|
81
|
+
}
|
|
82
|
+
#changeTokenAmountToReceive(amount) {
|
|
83
|
+
this.#tokenAmountToReceive = amount;
|
|
84
|
+
}
|
|
85
|
+
#changeVoteType(type) {
|
|
86
|
+
this.#voteType = type;
|
|
87
|
+
}
|
|
88
|
+
#getTokensOut(amount, receiver) {
|
|
89
|
+
return msg.call(this.#tokenReceiver, 'transfer', [this.#tokenReceiver, receiver, amount]);
|
|
90
|
+
}
|
|
91
|
+
async changeVoteType(type) {
|
|
92
|
+
if (!this.#canVote())
|
|
93
|
+
throw new Error('not a allowed');
|
|
94
|
+
if (this.#voteType === 'transfer' && (await this.#balance()).gt(0))
|
|
95
|
+
throw new Error('get tokens out first or they be lost forever');
|
|
96
|
+
else {
|
|
97
|
+
this.createVote(`change the token amount to receive`, `set tokenAmountToReceive`, new Date().getTime() + this.votingDuration, '#changeVoteType', [type]);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
getTokensOut(amount, receiver) {
|
|
101
|
+
if (!this.#canVote())
|
|
102
|
+
throw new Error('not a allowed');
|
|
103
|
+
else {
|
|
104
|
+
this.createVote(`withdraw all tokens`, `withdraw all tokens to ${receiver}`, new Date().getTime() + this.votingDuration, '#getTokensOut', [amount, receiver]);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
changeTokenAmountToReceive() {
|
|
108
|
+
if (!this.#canVote())
|
|
109
|
+
throw new Error('not a allowed');
|
|
110
|
+
else {
|
|
111
|
+
this.createVote(`change the token amount to receive`, `set tokenAmountToReceive`, new Date().getTime() + this.votingDuration, '#changeTokenAmountToReceive', []);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
#balance() {
|
|
115
|
+
return msg.staticCall(this.#tokenToReceive, 'balanceOf', [this.#tokenReceiver]);
|
|
116
|
+
}
|
|
117
|
+
async changeTokenToReceive() {
|
|
118
|
+
if (!this.#canVote())
|
|
119
|
+
throw new Error('not a allowed');
|
|
120
|
+
if (!(await this.#balance()).eq(0) && this.#voteType === 'transfer')
|
|
121
|
+
throw new Error('get tokens out first or they be lost forever');
|
|
122
|
+
else {
|
|
123
|
+
this.createVote(`change the token to receive`, `set tokenToReceive to a new address`, new Date().getTime() + this.votingDuration, '#changeTokenToReceive', []);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
export { TokenReceiver as default };
|
package/exports/token.d.ts
CHANGED
|
@@ -26,9 +26,15 @@ export default class Token extends Roles {
|
|
|
26
26
|
get symbol(): string;
|
|
27
27
|
get holders(): {};
|
|
28
28
|
get balances(): {};
|
|
29
|
+
get approvals(): {
|
|
30
|
+
[owner: string]: {
|
|
31
|
+
[operator: string]: import("@ethersproject/bignumber").BigNumber;
|
|
32
|
+
};
|
|
33
|
+
};
|
|
29
34
|
get decimals(): number;
|
|
30
35
|
mint(to: address, amount: BigNumberish): void;
|
|
31
36
|
burn(from: address, amount: BigNumberish): void;
|
|
37
|
+
balance(): any;
|
|
32
38
|
balanceOf(address: address): BigNumberish;
|
|
33
39
|
setApproval(operator: address, amount: BigNumberish): void;
|
|
34
40
|
approved(owner: address, operator: address, amount: BigNumberish): boolean;
|
package/exports/token.js
CHANGED
|
@@ -73,6 +73,9 @@ class Token extends Roles {
|
|
|
73
73
|
get balances() {
|
|
74
74
|
return { ...this.#balances };
|
|
75
75
|
}
|
|
76
|
+
get approvals() {
|
|
77
|
+
return this.#approvals;
|
|
78
|
+
}
|
|
76
79
|
get decimals() {
|
|
77
80
|
return this.#decimals;
|
|
78
81
|
}
|
|
@@ -110,6 +113,9 @@ class Token extends Roles {
|
|
|
110
113
|
this.#balances[address] = this.#balances[address].sub(amount);
|
|
111
114
|
this.#updateHolders(address, previousBalance);
|
|
112
115
|
}
|
|
116
|
+
balance() {
|
|
117
|
+
return this.#balances[msg.sender];
|
|
118
|
+
}
|
|
113
119
|
balanceOf(address) {
|
|
114
120
|
return this.#balances[address];
|
|
115
121
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { VoteResult, VoteView, VotingState } from './types.js';
|
|
2
|
+
export interface PrivateVotingState extends VotingState {
|
|
3
|
+
voters: any;
|
|
4
|
+
}
|
|
5
|
+
export default class PrivateVoting {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(state: PrivateVotingState);
|
|
8
|
+
get votes(): {
|
|
9
|
+
[x: string]: import("./types.js").Vote;
|
|
10
|
+
};
|
|
11
|
+
get voters(): any;
|
|
12
|
+
get votingDisabled(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
get state(): {};
|
|
17
|
+
get inProgress(): VoteView[];
|
|
18
|
+
/**
|
|
19
|
+
* create vote
|
|
20
|
+
* @param {string} vote
|
|
21
|
+
* @param {string} description
|
|
22
|
+
* @param {number} endTime
|
|
23
|
+
* @param {string} method function to run when agree amount is bigger
|
|
24
|
+
*/
|
|
25
|
+
createVote(title: string, description: string, endTime: EpochTimeStamp, method: string, args?: any[]): void;
|
|
26
|
+
canVote(address: address): any;
|
|
27
|
+
vote(voteId: string, vote: VoteResult): void;
|
|
28
|
+
disableVoting(): void;
|
|
29
|
+
grantVotingPower(address: address, voteId: string): void;
|
|
30
|
+
revokeVotingPower(address: address, voteId: string): void;
|
|
31
|
+
sync(): void;
|
|
32
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { VotingState, VoteResult } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* allows everybody that has a balance greater or equeal then/to tokenAmountToReceive to vote
|
|
4
|
+
*/
|
|
5
|
+
export default class PublicVoting {
|
|
6
|
+
#private;
|
|
7
|
+
constructor(state: VotingState);
|
|
8
|
+
get votes(): {
|
|
9
|
+
[x: string]: import("./types.js").Vote;
|
|
10
|
+
};
|
|
11
|
+
get votingDuration(): number;
|
|
12
|
+
get votingDisabled(): boolean;
|
|
13
|
+
/**
|
|
14
|
+
*
|
|
15
|
+
*/
|
|
16
|
+
get state(): {
|
|
17
|
+
votes: {
|
|
18
|
+
[id: string]: import("./types.js").Vote;
|
|
19
|
+
};
|
|
20
|
+
votingDisabled: boolean;
|
|
21
|
+
votingDuration: number;
|
|
22
|
+
};
|
|
23
|
+
get inProgress(): {
|
|
24
|
+
id: string;
|
|
25
|
+
title: string;
|
|
26
|
+
method: string;
|
|
27
|
+
args: any[];
|
|
28
|
+
description: string;
|
|
29
|
+
endTime: number;
|
|
30
|
+
results?: {
|
|
31
|
+
[address: string]: VoteResult;
|
|
32
|
+
};
|
|
33
|
+
finished?: boolean;
|
|
34
|
+
enoughVotes?: boolean;
|
|
35
|
+
}[];
|
|
36
|
+
/**
|
|
37
|
+
* create vote
|
|
38
|
+
* @param {string} vote
|
|
39
|
+
* @param {string} description
|
|
40
|
+
* @param {number} endTime
|
|
41
|
+
* @param {string} method function to run when agree amount is bigger
|
|
42
|
+
*/
|
|
43
|
+
createVote(title: string, description: string, endTime: EpochTimeStamp, method: string, args?: any[]): void;
|
|
44
|
+
vote(voteId: string, vote: VoteResult): Promise<void>;
|
|
45
|
+
disableVoting(): void;
|
|
46
|
+
_sync(): void;
|
|
47
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
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: string]: VoteResult;
|
|
10
|
+
};
|
|
11
|
+
finished?: boolean;
|
|
12
|
+
enoughVotes?: boolean;
|
|
13
|
+
};
|
|
14
|
+
export interface VotingState {
|
|
15
|
+
votes: {
|
|
16
|
+
[id: string]: Vote;
|
|
17
|
+
};
|
|
18
|
+
votingDisabled: boolean;
|
|
19
|
+
votingDuration: number;
|
|
20
|
+
}
|
|
21
|
+
export interface VoteView extends Vote {
|
|
22
|
+
id: string;
|
|
23
|
+
}
|
package/package.json
CHANGED
|
@@ -1,56 +1,66 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@leofcoin/standards",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Contract standards",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"exports": {
|
|
7
|
-
".": {
|
|
8
|
-
"import": "./exports/index.js",
|
|
9
|
-
"types": "./exports/index.d.ts"
|
|
10
|
-
},
|
|
11
|
-
"./token": {
|
|
12
|
-
"import": "./exports/token.js",
|
|
13
|
-
"types": "./exports/token.d.ts"
|
|
14
|
-
},
|
|
15
|
-
"./roles": {
|
|
16
|
-
"import": "./exports/roles.js",
|
|
17
|
-
"types": "./exports/roles.d.ts"
|
|
18
|
-
},
|
|
19
|
-
"./voting": {
|
|
20
|
-
"import": "./exports/voting.js",
|
|
21
|
-
"types": "./exports/voting.d.ts"
|
|
22
|
-
},
|
|
23
|
-
"./
|
|
24
|
-
"import": "./exports/
|
|
25
|
-
"types": "./exports/
|
|
26
|
-
},
|
|
27
|
-
"./token
|
|
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
|
-
|
|
1
|
+
{
|
|
2
|
+
"name": "@leofcoin/standards",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Contract standards",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"exports": {
|
|
7
|
+
".": {
|
|
8
|
+
"import": "./exports/index.js",
|
|
9
|
+
"types": "./exports/index.d.ts"
|
|
10
|
+
},
|
|
11
|
+
"./token": {
|
|
12
|
+
"import": "./exports/token.js",
|
|
13
|
+
"types": "./exports/token.d.ts"
|
|
14
|
+
},
|
|
15
|
+
"./roles": {
|
|
16
|
+
"import": "./exports/roles.js",
|
|
17
|
+
"types": "./exports/roles.d.ts"
|
|
18
|
+
},
|
|
19
|
+
"./public-voting": {
|
|
20
|
+
"import": "./exports/public-voting.js",
|
|
21
|
+
"types": "./exports/public-voting.d.ts"
|
|
22
|
+
},
|
|
23
|
+
"./private-voting": {
|
|
24
|
+
"import": "./exports/private-voting.js",
|
|
25
|
+
"types": "./exports/private-voting.d.ts"
|
|
26
|
+
},
|
|
27
|
+
"./token-receiver": {
|
|
28
|
+
"import": "./exports/token-receiver.js",
|
|
29
|
+
"types": "./exports/token-receiver.d.ts"
|
|
30
|
+
},
|
|
31
|
+
"./helpers": {
|
|
32
|
+
"import": "./exports/helpers.js",
|
|
33
|
+
"types": "./exports/helpers.d.ts"
|
|
34
|
+
},
|
|
35
|
+
"./token.js": "./exports/token.js",
|
|
36
|
+
"./roles.js": "./exports/roles.js",
|
|
37
|
+
"./public-voting.js": "./exports/public-voting.js",
|
|
38
|
+
"./private-voting.js": "./exports/private-voting.js",
|
|
39
|
+
"./helpers.js": "./exports/helpers.js"
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "rollup -c",
|
|
43
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
44
|
+
},
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "git+https://github.com/ArteonToken/standards.git"
|
|
48
|
+
},
|
|
49
|
+
"keywords": [],
|
|
50
|
+
"author": "",
|
|
51
|
+
"license": "MIT",
|
|
52
|
+
"bugs": {
|
|
53
|
+
"url": "https://github.com/ArteonToken/standards/issues"
|
|
54
|
+
},
|
|
55
|
+
"homepage": "https://github.com/ArteonToken/standards#readme",
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@leofcoin/global-types": "^1.0.0",
|
|
58
|
+
"@rollup/plugin-typescript": "^11.1.6",
|
|
59
|
+
"rollup": "^4.12.0",
|
|
60
|
+
"tslib": "^2.5.0"
|
|
61
|
+
},
|
|
62
|
+
"dependencies": {
|
|
63
|
+
"@changesets/cli": "^2.27.1",
|
|
64
|
+
"@ethersproject/bignumber": "^5.7.0"
|
|
65
|
+
}
|
|
66
|
+
}
|
package/rollup.config.js
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import typescript from '@rollup/plugin-typescript'
|
|
2
|
-
import tsConfig from './tsconfig.json' assert { type: 'json' }
|
|
3
2
|
import { execSync } from 'child_process'
|
|
4
3
|
|
|
5
4
|
// const templates = (await readdir('./src/templates')).map(path => join('./src/templates', path))
|
|
@@ -10,11 +9,19 @@ const clean = () => {
|
|
|
10
9
|
|
|
11
10
|
export default [
|
|
12
11
|
{
|
|
13
|
-
input: [
|
|
12
|
+
input: [
|
|
13
|
+
'src/index.ts',
|
|
14
|
+
'src/token.ts',
|
|
15
|
+
'src/roles.ts',
|
|
16
|
+
'src/voting/public-voting.ts',
|
|
17
|
+
'src/voting/private-voting.ts',
|
|
18
|
+
'src/helpers.ts',
|
|
19
|
+
'src/token-receiver.ts'
|
|
20
|
+
],
|
|
14
21
|
output: {
|
|
15
22
|
dir: './exports',
|
|
16
23
|
format: 'es'
|
|
17
24
|
},
|
|
18
|
-
plugins: [typescript(
|
|
25
|
+
plugins: [typescript()]
|
|
19
26
|
}
|
|
20
27
|
]
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
export { default as Token } from './token.js'
|
|
2
2
|
export { default as Roles } from './roles.js'
|
|
3
|
-
export { default as
|
|
3
|
+
export { default as TokenReceiver } from './token-receiver.js'
|
|
4
|
+
export { default as PublicVoting } from './voting/public-voting.js'
|
|
5
|
+
export { default as PrivateVoting } from './voting/private-voting.js'
|
|
6
|
+
|
|
4
7
|
export * from './helpers.js'
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { IPublicVoting } from './voting/interfaces/public-voting.js'
|
|
2
|
+
import PublicVoting from './voting/public-voting.js'
|
|
3
|
+
import { VotingState } from './voting/types.js'
|
|
4
|
+
|
|
5
|
+
export interface TokenReceiverState extends VotingState {
|
|
6
|
+
tokenToReceive: address
|
|
7
|
+
tokenReceiver: address
|
|
8
|
+
tokenAmountToReceive: typeof BigNumber
|
|
9
|
+
voteType: 'burn' | 'transfer'
|
|
10
|
+
}
|
|
11
|
+
export default class TokenReceiver extends PublicVoting implements IPublicVoting {
|
|
12
|
+
#tokenToReceive: address
|
|
13
|
+
#tokenAmountToReceive: typeof BigNumber
|
|
14
|
+
#tokenReceiver: address
|
|
15
|
+
#voteType: TokenReceiverState['voteType'] = 'transfer'
|
|
16
|
+
|
|
17
|
+
constructor(
|
|
18
|
+
tokenToReceive: address,
|
|
19
|
+
tokenAmountToReceive: typeof BigNumber,
|
|
20
|
+
burns: boolean,
|
|
21
|
+
state?: TokenReceiverState
|
|
22
|
+
) {
|
|
23
|
+
super(state)
|
|
24
|
+
if (state) {
|
|
25
|
+
this.#tokenReceiver = state.tokenReceiver
|
|
26
|
+
this.#tokenToReceive = state.tokenToReceive
|
|
27
|
+
this.#tokenAmountToReceive = BigNumber['from'](state.tokenAmountToReceive)
|
|
28
|
+
this.#voteType = state.voteType
|
|
29
|
+
} else {
|
|
30
|
+
this.#tokenReceiver = msg.contract
|
|
31
|
+
this.#tokenToReceive = tokenToReceive
|
|
32
|
+
this.#tokenAmountToReceive = BigNumber['from'](tokenAmountToReceive)
|
|
33
|
+
if (burns) this.#voteType = 'burn'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get tokenToReceive() {
|
|
38
|
+
return this.#tokenToReceive
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
get tokenAmountToReceive() {
|
|
42
|
+
return this.#tokenAmountToReceive
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
get tokenReceiver() {
|
|
46
|
+
return this.#tokenReceiver
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
get state() {
|
|
50
|
+
return {
|
|
51
|
+
...super.state,
|
|
52
|
+
tokenReceiver: this.#tokenReceiver,
|
|
53
|
+
tokenToReceive: this.#tokenToReceive,
|
|
54
|
+
tokenAmountToReceive: this.#tokenAmountToReceive,
|
|
55
|
+
voteType: this.#voteType
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
async #canVote() {
|
|
60
|
+
const amount = (await msg.staticCall(this.#tokenToReceive, 'balanceOf', [msg.sender])) as typeof BigNumber
|
|
61
|
+
return amount.gte(this.#tokenAmountToReceive)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* check if sender can pay
|
|
66
|
+
* @returns {boolean} promise
|
|
67
|
+
*/
|
|
68
|
+
async _canVote(): Promise<boolean> {
|
|
69
|
+
return this.#canVote()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
async #beforeVote(): Promise<any> {
|
|
73
|
+
if (this.#voteType === 'burn') return msg.staticCall(this.tokenToReceive, 'burn', [this.tokenAmountToReceive])
|
|
74
|
+
return msg.staticCall(this.tokenToReceive, 'transfer', [msg.sender, this.tokenReceiver, this.tokenAmountToReceive])
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
async _beforeVote(): Promise<any> {
|
|
78
|
+
await this.#beforeVote()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* check if sender can pay
|
|
83
|
+
* @returns {boolean} promise
|
|
84
|
+
*/
|
|
85
|
+
async _payTokenToReceive(): Promise<boolean> {
|
|
86
|
+
return msg.staticCall(this.#tokenToReceive, 'transfer', [
|
|
87
|
+
msg.sender,
|
|
88
|
+
this.#tokenReceiver,
|
|
89
|
+
this.#tokenAmountToReceive
|
|
90
|
+
])
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* check if sender can pay
|
|
95
|
+
* @returns {boolean} promise
|
|
96
|
+
*/
|
|
97
|
+
async _burnTokenToReceive(): Promise<boolean> {
|
|
98
|
+
return msg.staticCall(this.#tokenToReceive, 'burn', [this.#tokenAmountToReceive])
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
#changeTokenToReceive(address: address) {
|
|
102
|
+
this.#tokenToReceive = address
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
#changeTokenAmountToReceive(amount: typeof BigNumber) {
|
|
106
|
+
this.#tokenAmountToReceive = amount
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#changeVoteType(type: TokenReceiverState['voteType']) {
|
|
110
|
+
this.#voteType = type
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#getTokensOut(amount: typeof BigNumber, receiver: address) {
|
|
114
|
+
return msg.call(this.#tokenReceiver, 'transfer', [this.#tokenReceiver, receiver, amount])
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async changeVoteType(type: TokenReceiverState['voteType']) {
|
|
118
|
+
if (!this.#canVote()) throw new Error('not a allowed')
|
|
119
|
+
if (this.#voteType === 'transfer' && (await this.#balance()).gt(0))
|
|
120
|
+
throw new Error('get tokens out first or they be lost forever')
|
|
121
|
+
else {
|
|
122
|
+
this.createVote(
|
|
123
|
+
`change the token amount to receive`,
|
|
124
|
+
`set tokenAmountToReceive`,
|
|
125
|
+
new Date().getTime() + this.votingDuration,
|
|
126
|
+
'#changeVoteType',
|
|
127
|
+
[type]
|
|
128
|
+
)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
getTokensOut(amount: typeof BigNumber, receiver: address) {
|
|
133
|
+
if (!this.#canVote()) throw new Error('not a allowed')
|
|
134
|
+
else {
|
|
135
|
+
this.createVote(
|
|
136
|
+
`withdraw all tokens`,
|
|
137
|
+
`withdraw all tokens to ${receiver}`,
|
|
138
|
+
new Date().getTime() + this.votingDuration,
|
|
139
|
+
'#getTokensOut',
|
|
140
|
+
[amount, receiver]
|
|
141
|
+
)
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
changeTokenAmountToReceive() {
|
|
146
|
+
if (!this.#canVote()) throw new Error('not a allowed')
|
|
147
|
+
else {
|
|
148
|
+
this.createVote(
|
|
149
|
+
`change the token amount to receive`,
|
|
150
|
+
`set tokenAmountToReceive`,
|
|
151
|
+
new Date().getTime() + this.votingDuration,
|
|
152
|
+
'#changeTokenAmountToReceive',
|
|
153
|
+
[]
|
|
154
|
+
)
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
#balance(): Promise<typeof BigNumber> {
|
|
159
|
+
return msg.staticCall(this.#tokenToReceive, 'balanceOf', [this.#tokenReceiver])
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async changeTokenToReceive() {
|
|
163
|
+
if (!this.#canVote()) throw new Error('not a allowed')
|
|
164
|
+
if (!(await this.#balance()).eq(0) && this.#voteType === 'transfer')
|
|
165
|
+
throw new Error('get tokens out first or they be lost forever')
|
|
166
|
+
else {
|
|
167
|
+
this.createVote(
|
|
168
|
+
`change the token to receive`,
|
|
169
|
+
`set tokenToReceive to a new address`,
|
|
170
|
+
new Date().getTime() + this.votingDuration,
|
|
171
|
+
'#changeTokenToReceive',
|
|
172
|
+
[]
|
|
173
|
+
)
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
package/src/token.ts
CHANGED
|
@@ -90,6 +90,10 @@ export default class Token extends Roles {
|
|
|
90
90
|
return { ...this.#balances }
|
|
91
91
|
}
|
|
92
92
|
|
|
93
|
+
get approvals() {
|
|
94
|
+
return this.#approvals
|
|
95
|
+
}
|
|
96
|
+
|
|
93
97
|
get decimals() {
|
|
94
98
|
return this.#decimals
|
|
95
99
|
}
|
|
@@ -132,6 +136,10 @@ export default class Token extends Roles {
|
|
|
132
136
|
this.#updateHolders(address, previousBalance)
|
|
133
137
|
}
|
|
134
138
|
|
|
139
|
+
balance() {
|
|
140
|
+
return this.#balances[msg.sender]
|
|
141
|
+
}
|
|
142
|
+
|
|
135
143
|
balanceOf(address: address): BigNumberish {
|
|
136
144
|
return this.#balances[address]
|
|
137
145
|
}
|
|
@@ -1,33 +1,15 @@
|
|
|
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
|
-
}
|
|
1
|
+
import { VoteResult, VoteView, VotingState } from './types.js'
|
|
13
2
|
|
|
14
|
-
export
|
|
15
|
-
voters
|
|
16
|
-
votes: {
|
|
17
|
-
[id: string]: Vote
|
|
18
|
-
}
|
|
19
|
-
votingDisabled: boolean
|
|
20
|
-
}
|
|
21
|
-
export interface VoteView extends Vote {
|
|
22
|
-
id: string
|
|
3
|
+
export interface PrivateVotingState extends VotingState {
|
|
4
|
+
voters
|
|
23
5
|
}
|
|
24
6
|
|
|
25
|
-
export default class
|
|
26
|
-
#voters:
|
|
27
|
-
#votes:
|
|
7
|
+
export default class PrivateVoting {
|
|
8
|
+
#voters: PrivateVotingState['voters']
|
|
9
|
+
#votes: PrivateVotingState['votes']
|
|
28
10
|
#votingDisabled: boolean
|
|
29
11
|
|
|
30
|
-
constructor(state:
|
|
12
|
+
constructor(state: PrivateVotingState) {
|
|
31
13
|
if (state) {
|
|
32
14
|
this.#voters = state.voters
|
|
33
15
|
this.#votes = state.votes
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { VotingState, VoteResult } from './types.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* allows everybody that has a balance greater or equeal then/to tokenAmountToReceive to vote
|
|
5
|
+
*/
|
|
6
|
+
export default class PublicVoting {
|
|
7
|
+
#votes: VotingState['votes']
|
|
8
|
+
#votingDisabled: boolean
|
|
9
|
+
#votingDuration: number = 172800000
|
|
10
|
+
|
|
11
|
+
constructor(state: VotingState) {
|
|
12
|
+
if (state) {
|
|
13
|
+
this.#votes = state.votes
|
|
14
|
+
this.#votingDisabled = state.votingDisabled
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get votes() {
|
|
19
|
+
return { ...this.#votes }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get votingDuration() {
|
|
23
|
+
return this.#votingDuration
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
get votingDisabled() {
|
|
27
|
+
return this.#votingDisabled
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
*
|
|
32
|
+
*/
|
|
33
|
+
get state() {
|
|
34
|
+
return { votes: this.#votes, votingDisabled: this.#votingDisabled, votingDuration: this.#votingDuration }
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get inProgress() {
|
|
38
|
+
return Object.entries(this.#votes)
|
|
39
|
+
.filter(([id, vote]) => !vote.finished)
|
|
40
|
+
.map(([id, vote]) => {
|
|
41
|
+
return { ...vote, id }
|
|
42
|
+
})
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* create vote
|
|
46
|
+
* @param {string} vote
|
|
47
|
+
* @param {string} description
|
|
48
|
+
* @param {number} endTime
|
|
49
|
+
* @param {string} method function to run when agree amount is bigger
|
|
50
|
+
*/
|
|
51
|
+
|
|
52
|
+
createVote(title: string, description: string, endTime: EpochTimeStamp, method: string, args: any[] = []) {
|
|
53
|
+
if (!this.#canVote()) throw new Error(`Not allowed to create a vote`)
|
|
54
|
+
const id = crypto.randomUUID()
|
|
55
|
+
this.#votes[id] = {
|
|
56
|
+
title,
|
|
57
|
+
description,
|
|
58
|
+
method,
|
|
59
|
+
endTime,
|
|
60
|
+
args
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
#canVote() {
|
|
65
|
+
// @ts-expect-error
|
|
66
|
+
return this._canVote?.()
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
#beforeVote() {
|
|
70
|
+
// @ts-expect-error
|
|
71
|
+
return this._beforeVote?.()
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
#endVoting(voteId) {
|
|
75
|
+
let agree = Object.values(this.#votes[voteId].results).filter((result) => result === 1)
|
|
76
|
+
let disagree = Object.values(this.#votes[voteId].results).filter((result) => result === 0)
|
|
77
|
+
if (agree.length > disagree.length && this.#votes[voteId].enoughVotes)
|
|
78
|
+
this[this.#votes[voteId].method](...this.#votes[voteId].args)
|
|
79
|
+
this.#votes[voteId].finished = true
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
async vote(voteId: string, vote: VoteResult) {
|
|
83
|
+
vote = Number(vote) as VoteResult
|
|
84
|
+
if (vote !== 0 && vote !== 0.5 && vote !== 1) throw new Error(`invalid vote value ${vote}`)
|
|
85
|
+
if (!this.#votes[voteId]) throw new Error(`Nothing found for ${voteId}`)
|
|
86
|
+
const ended = new Date().getTime() > this.#votes[voteId].endTime
|
|
87
|
+
if (ended && !this.#votes[voteId].finished) this.#endVoting(voteId)
|
|
88
|
+
if (ended) throw new Error('voting already ended')
|
|
89
|
+
if (!this.#canVote()) throw new Error(`Not allowed to vote`)
|
|
90
|
+
await this.#beforeVote()
|
|
91
|
+
this.#votes[voteId][msg.sender] = vote
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
#disableVoting() {
|
|
95
|
+
this.#votingDisabled = true
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
disableVoting() {
|
|
99
|
+
if (!this.#canVote()) throw new Error('not a allowed')
|
|
100
|
+
else {
|
|
101
|
+
this.createVote(
|
|
102
|
+
`disable voting`,
|
|
103
|
+
`Warning this disables all voting features forever`,
|
|
104
|
+
new Date().getTime() + this.#votingDuration,
|
|
105
|
+
'#disableVoting',
|
|
106
|
+
[]
|
|
107
|
+
)
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
_sync() {
|
|
112
|
+
for (const vote of this.inProgress) {
|
|
113
|
+
if (vote.endTime < new Date().getTime()) this.#endVoting(vote.id)
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
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
|
+
}
|
package/tsconfig.json
CHANGED
|
@@ -3,12 +3,14 @@
|
|
|
3
3
|
"module": "NodeNext",
|
|
4
4
|
"target": "es2022",
|
|
5
5
|
"outDir": "./exports",
|
|
6
|
-
"moduleResolution":"NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
7
|
"declaration": true,
|
|
8
8
|
"declarationDir": "./exports"
|
|
9
9
|
},
|
|
10
10
|
"include": [
|
|
11
11
|
"./src/*",
|
|
12
|
-
"./node_modules/@leofcoin/global-types/*"
|
|
12
|
+
"./node_modules/@leofcoin/global-types/*",
|
|
13
|
+
"src/voting/private-voting.ts",
|
|
14
|
+
"src/voting/public-voting.ts"
|
|
13
15
|
]
|
|
14
|
-
}
|
|
16
|
+
}
|