@leofcoin/contracts 0.1.14 → 0.1.15

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.
@@ -1,92 +1,93 @@
1
- import { Roles, TokenReceiver } from '@leofcoin/standards'
2
- import { TokenReceiverState } from '@leofcoin/standards/token-receiver'
3
-
4
- type registry = {
5
- name?: {
6
- address: address
7
- owner: address
8
- }
9
- }
10
-
11
- export interface NameServiceState extends TokenReceiverState {
12
- registry: registry
13
- }
14
-
15
- export default class NameService extends TokenReceiver {
16
- /**
17
- * string
18
- */
19
- #name: string = 'LeofcoinNameService'
20
- /**
21
- * Object => string
22
- */
23
- #registry: registry = {}
24
-
25
- get name(): string {
26
- return this.#name
27
- }
28
-
29
- get registry(): {} {
30
- return { ...this.#registry }
31
- }
32
-
33
- get state(): NameServiceState {
34
- return {
35
- ...super.state,
36
- registry: this.#registry
37
- }
38
- }
39
-
40
- // TODO: control with contract
41
- constructor(
42
- factoryAddress: address,
43
- tokenToReceive: address,
44
- validatorAddress: address,
45
- tokenAmountToReceive: bigint,
46
- state: NameServiceState
47
- ) {
48
- super(tokenToReceive, tokenAmountToReceive, true, state as TokenReceiverState)
49
- if (state) {
50
- this.#registry = state.registry
51
- } else {
52
- this.#registry['LeofcoinContractFactory'] = {
53
- owner: msg.sender,
54
- address: factoryAddress
55
- }
56
-
57
- this.#registry['LeofcoinToken'] = {
58
- owner: msg.sender,
59
- address: tokenToReceive
60
- }
61
-
62
- this.#registry['LeofcoinValidators'] = {
63
- owner: msg.sender,
64
- address: validatorAddress
65
- }
66
- }
67
- }
68
-
69
- async purchaseName(name: string | number, address: any) {
70
- await this._canPay()
71
- await this._payTokenToReceive()
72
-
73
- this.#registry[name] = {
74
- owner: msg.sender,
75
- address
76
- }
77
- }
78
-
79
- lookup(name: string | number) {
80
- return this.#registry[name]
81
- }
82
-
83
- transferOwnership(name: string | number, to: any) {
84
- if (msg.sender !== this.#registry[name].owner) throw new Error('not allowed')
85
- this.#registry[name].owner = to
86
- }
87
-
88
- changeAddress(name: string | number, address: any) {
89
- if (msg.sender !== this.#registry[name].owner) throw new Error('not allowed')
90
- this.#registry[name].address = address
91
- }
92
- }
1
+ import { TokenReceiver } from '@leofcoin/standards'
2
+ import { TokenReceiverState } from '@leofcoin/standards/token-receiver'
3
+
4
+ type registry = {
5
+ name?: {
6
+ address: address
7
+ owner: address
8
+ }
9
+ }
10
+
11
+ export interface NameServiceState extends TokenReceiverState {
12
+ registry: registry
13
+ }
14
+
15
+ export default class NameService extends TokenReceiver {
16
+ /**
17
+ * string
18
+ */
19
+ #name: string = 'LeofcoinNameService'
20
+ /**
21
+ * Object => string
22
+ */
23
+ #registry: registry = {}
24
+
25
+ get name(): string {
26
+ return this.#name
27
+ }
28
+
29
+ get registry(): {} {
30
+ return { ...this.#registry }
31
+ }
32
+
33
+ get state(): NameServiceState {
34
+ const baseState = super.state as TokenReceiverState
35
+ return {
36
+ ...baseState,
37
+ registry: this.#registry
38
+ }
39
+ }
40
+
41
+ // TODO: control with contract
42
+ constructor(
43
+ factoryAddress: address,
44
+ tokenToReceive: address,
45
+ validatorAddress: address,
46
+ tokenAmountToReceive: bigint,
47
+ state: NameServiceState
48
+ ) {
49
+ super(tokenToReceive, tokenAmountToReceive, true, state as TokenReceiverState)
50
+ if (state) {
51
+ this.#registry = state.registry
52
+ } else {
53
+ this.#registry['LeofcoinContractFactory'] = {
54
+ owner: msg.sender,
55
+ address: msg.contract
56
+ }
57
+
58
+ this.#registry['LeofcoinToken'] = {
59
+ owner: msg.sender,
60
+ address: tokenToReceive
61
+ }
62
+
63
+ this.#registry['LeofcoinValidators'] = {
64
+ owner: msg.sender,
65
+ address: validatorAddress
66
+ }
67
+ }
68
+ }
69
+
70
+ async purchaseName(name: string | number, address: any) {
71
+ if (this.#registry[name]) throw new Error('name already registered')
72
+ await this._payTokenToReceive()
73
+
74
+ this.#registry[name] = {
75
+ owner: msg.sender,
76
+ address
77
+ }
78
+ }
79
+
80
+ lookup(name: string | number) {
81
+ return this.#registry[name]
82
+ }
83
+
84
+ transferOwnership(name: string | number, to: any) {
85
+ if (msg.sender !== this.#registry[name].owner) throw new Error('not allowed')
86
+ this.#registry[name].owner = to
87
+ }
88
+
89
+ changeAddress(name: string | number, address: any) {
90
+ if (msg.sender !== this.#registry[name].owner) throw new Error('not allowed')
91
+ this.#registry[name].address = address
92
+ }
93
+ }
@@ -1,8 +1,13 @@
1
- import { IToken } from '@leofcoin/standards/interfaces/i-token'
2
- import Token, { TokenState } from '@leofcoin/standards/token.js'
3
-
4
- export default class Leofcoin extends Token implements IToken {
5
- constructor(state: TokenState) {
6
- super('Leofcoin', 'LFC', 18, state)
7
- }
8
- }
1
+ import Token, { TokenState } from '@leofcoin/standards/token.js'
2
+
3
+ export default class Leofcoin extends Token {
4
+ constructor(state: TokenState) {
5
+ super('Leofcoin', 'LFC', 18, state)
6
+ }
7
+
8
+ burn(from: address, amount: bigint) {
9
+ // Prevent balance underflow when burning
10
+ if (this.balanceOf(from) < amount) throw new Error('amount exceeds balance')
11
+ return super.burn(from, amount)
12
+ }
13
+ }
@@ -1,8 +1,7 @@
1
- import { IToken } from '@leofcoin/standards/interfaces/i-token'
2
- import Token, { TokenState } from '@leofcoin/standards/token.js'
3
-
4
- export default class Power extends Token implements IToken {
5
- constructor(state: TokenState) {
6
- super('Power', 'PWR', 18, state)
7
- }
8
- }
1
+ import Token, { TokenState } from '@leofcoin/standards/token.js'
2
+
3
+ export default class Power extends Token {
4
+ constructor(state: TokenState) {
5
+ super('Power', 'PWR', 18, state)
6
+ }
7
+ }
package/src/validators.ts CHANGED
@@ -1,147 +1,148 @@
1
- import { lottery } from 'lucky-numbers'
2
- import Roles, { RolesState } from '@leofcoin/standards/roles.js'
3
-
4
- export declare interface ValidatorsState extends RolesState {
5
- balances: { [address: string]: bigint }
6
- minimumBalance: bigint
7
- currency: address
8
- validators: address[]
9
- currentValidator: address
10
- }
11
-
12
- export default class Validators extends Roles {
13
- /**
14
- * string
15
- */
16
- #name = 'LeofcoinValidators'
17
- /**
18
- * Object => string(address) => Object
19
- */
20
- #validators: address[] = []
21
-
22
- #currentValidator: address
23
-
24
- #currency: address
25
-
26
- #minimumBalance: bigint = BigInt(50_000)
27
-
28
- #balances: { [address: address]: bigint } = {}
29
-
30
- get state() {
31
- return {
32
- ...super.state,
33
- balances: this.#balances,
34
- minimumBalance: this.#minimumBalance,
35
- currency: this.#currency,
36
- validators: this.#validators,
37
- currentValidator: this.#currentValidator
38
- }
39
- }
40
-
41
- constructor(tokenAddress: address, state: ValidatorsState) {
42
- super(state?.roles)
43
- if (state) {
44
- this.#minimumBalance = BigInt(state.minimumBalance)
45
- this.#currency = state.currency
46
- this.#validators = state.validators
47
- this.#balances = state.balances
48
- this.#currentValidator = state.currentValidator
49
- } else {
50
- this.#currency = tokenAddress
51
- this.#validators.push(msg.sender)
52
- this.#currentValidator = msg.sender
53
- }
54
- }
55
-
56
- get currentValidator() {
57
- return this.#currentValidator
58
- }
59
-
60
- get name() {
61
- return this.#name
62
- }
63
-
64
- get currency() {
65
- return this.#currency
66
- }
67
-
68
- get validators() {
69
- return this.#validators
70
- }
71
-
72
- get totalValidators() {
73
- return this.#validators.length
74
- }
75
-
76
- get minimumBalance() {
77
- return this.#minimumBalance
78
- }
79
-
80
- changeCurrency(currency) {
81
- if (!this.hasRole(msg.sender, 'OWNER')) throw new Error('not an owner')
82
- this.#currency = currency
83
- }
84
-
85
- has(validator) {
86
- return this.#validators.includes(validator)
87
- }
88
-
89
- #isAllowed(address) {
90
- if (msg.sender !== address && !this.hasRole(msg.sender, 'OWNER'))
91
- throw new Error('sender is not the validator or owner')
92
- return true
93
- }
94
-
95
- async addValidator(validator: address) {
96
- this.#isAllowed(validator)
97
- if (this.has(validator)) throw new Error('validator already exists')
98
-
99
- const balance = await msg.staticCall(this.currency, 'balanceOf', [validator])
100
-
101
- if (this.minimumBalance > balance)
102
- throw new Error(`balance to low! got: ${balance} need: ${this.#minimumBalance}`)
103
-
104
- await msg.call(this.currency, 'transfer', [validator, msg.contract, this.#minimumBalance])
105
-
106
- this.#balances[validator] = this.#minimumBalance
107
- this.#validators.push(validator)
108
- }
109
-
110
- async removeValidator(validator) {
111
- this.#isAllowed(validator)
112
- if (!this.has(validator)) throw new Error('validator not found')
113
- await msg.call(this.currency, 'transfer', [msg.contract, validator, this.#minimumBalance])
114
- delete this.#balances[validator]
115
- this.#validators.splice(this.#validators.indexOf(validator), 1)
116
- }
117
-
118
- shuffleValidator() {
119
- // todo introduce voting mechanism
120
- // select amount of peers to vote & pass when 2/3 select the same peer
121
- // this.vote
122
- // todo only ids should be accessable
123
- const _peers = state.peers
124
- const peers = _peers
125
- // only validators make a chance
126
- .filter((peer) => this.#validators.includes(peer[0]))
127
- // add up the bytes
128
- .map((peer) => {
129
- peer[1].totalBytes = peer[1].bw.up + peer[1].bw.down
130
- return peer
131
- })
132
- .sort((a, b) => b[1].totalBytes - a[1].totalBytes)
133
- // only return 128 best participating max
134
- .splice(0, _peers.length > 128 ? 128 : _peers.length)
135
-
136
- const luckyNumber = lottery(1, peers.length - 1)
137
-
138
- let nextValidator = peers[luckyNumber[0]][0]
139
- // redraw when the validator is the same
140
- // but keep the net alive when only one validator is found
141
- if (this.#currentValidator === nextValidator && peers.length !== 1) {
142
- const luckyNumber = lottery(1, peers.length - 1)
143
- nextValidator = peers[luckyNumber[0]][0]
144
- }
145
- this.#currentValidator = nextValidator
146
- }
147
- }
1
+ import { lottery } from 'lucky-numbers'
2
+ import Roles from '@leofcoin/standards/roles.js'
3
+ import type { RolesState } from '@leofcoin/standards/interfaces.js'
4
+
5
+ export declare interface ValidatorsState extends RolesState {
6
+ balances: { [address: string]: bigint }
7
+ minimumBalance: bigint
8
+ currency: address
9
+ validators: address[]
10
+ currentValidator: address
11
+ }
12
+
13
+ export default class Validators extends Roles {
14
+ /**
15
+ * string
16
+ */
17
+ #name = 'LeofcoinValidators'
18
+ /**
19
+ * Object => string(address) => Object
20
+ */
21
+ #validators: address[] = []
22
+
23
+ #currentValidator: address
24
+
25
+ #currency: address
26
+
27
+ #minimumBalance: bigint = BigInt(50_000)
28
+
29
+ #balances: { [address: address]: bigint } = {}
30
+
31
+ get state() {
32
+ return {
33
+ ...super.state,
34
+ balances: this.#balances,
35
+ minimumBalance: this.#minimumBalance,
36
+ currency: this.#currency,
37
+ validators: this.#validators,
38
+ currentValidator: this.#currentValidator
39
+ }
40
+ }
41
+
42
+ constructor(tokenAddress: address, state: ValidatorsState) {
43
+ super(state)
44
+ if (state) {
45
+ this.#minimumBalance = BigInt(state.minimumBalance)
46
+ this.#currency = state.currency
47
+ this.#validators = state.validators
48
+ this.#balances = state.balances
49
+ this.#currentValidator = state.currentValidator
50
+ } else {
51
+ this.#currency = tokenAddress
52
+ this.#validators.push(msg.sender)
53
+ this.#currentValidator = msg.sender
54
+ }
55
+ }
56
+
57
+ get currentValidator() {
58
+ return this.#currentValidator
59
+ }
60
+
61
+ get name() {
62
+ return this.#name
63
+ }
64
+
65
+ get currency() {
66
+ return this.#currency
67
+ }
68
+
69
+ get validators() {
70
+ return this.#validators
71
+ }
72
+
73
+ get totalValidators() {
74
+ return this.#validators.length
75
+ }
76
+
77
+ get minimumBalance() {
78
+ return this.#minimumBalance
79
+ }
80
+
81
+ changeCurrency(currency) {
82
+ if (!this.hasRole(msg.sender, 'OWNER')) throw new Error('not an owner')
83
+ this.#currency = currency
84
+ }
85
+
86
+ has(validator) {
87
+ return this.#validators.includes(validator)
88
+ }
89
+
90
+ #isAllowed(address) {
91
+ if (msg.sender !== address && !this.hasRole(msg.sender, 'OWNER'))
92
+ throw new Error('sender is not the validator or owner')
93
+ return true
94
+ }
95
+
96
+ async addValidator(validator: address) {
97
+ this.#isAllowed(validator)
98
+ if (this.has(validator)) throw new Error('validator already exists')
99
+
100
+ const balance = await msg.staticCall(this.currency, 'balanceOf', [validator])
101
+
102
+ if (this.minimumBalance > balance)
103
+ throw new Error(`balance to low! got: ${balance} need: ${this.#minimumBalance}`)
104
+
105
+ await msg.call(this.currency, 'transfer', [validator, msg.contract, this.#minimumBalance])
106
+
107
+ this.#balances[validator] = this.#minimumBalance
108
+ this.#validators.push(validator)
109
+ }
110
+
111
+ async removeValidator(validator) {
112
+ this.#isAllowed(validator)
113
+ if (!this.has(validator)) throw new Error('validator not found')
114
+ await msg.call(this.currency, 'transfer', [msg.contract, validator, this.#minimumBalance])
115
+ delete this.#balances[validator]
116
+ this.#validators.splice(this.#validators.indexOf(validator), 1)
117
+ }
118
+
119
+ shuffleValidator() {
120
+ // todo introduce voting mechanism
121
+ // select amount of peers to vote & pass when 2/3 select the same peer
122
+ // this.vote
123
+ // todo only ids should be accessable
124
+ const _peers = state.peers
125
+ const peers = _peers
126
+ // only validators make a chance
127
+ .filter((peer) => this.#validators.includes(peer[0]))
128
+ // add up the bytes
129
+ .map((peer) => {
130
+ peer[1].totalBytes = peer[1].bw.up + peer[1].bw.down
131
+ return peer
132
+ })
133
+ .sort((a, b) => b[1].totalBytes - a[1].totalBytes)
134
+ // only return 128 best participating max
135
+ .splice(0, _peers.length > 128 ? 128 : _peers.length)
136
+
137
+ const luckyNumber = lottery(1, peers.length - 1)
138
+
139
+ let nextValidator = peers[luckyNumber[0]][0]
140
+ // redraw when the validator is the same
141
+ // but keep the net alive when only one validator is found
142
+ if (this.#currentValidator === nextValidator && peers.length !== 1) {
143
+ const luckyNumber = lottery(1, peers.length - 1)
144
+ nextValidator = peers[luckyNumber[0]][0]
145
+ }
146
+ this.#currentValidator = nextValidator
147
+ }
148
+ }
package/test/factory.js CHANGED
@@ -1,66 +1,65 @@
1
- import Factory from './../exports/factory.js'
2
- import { expect } from 'chai'
3
-
4
- describe('Factory', () => {
5
- let factory
6
-
7
- beforeEach(() => {
8
- global.msg = {
9
- sender: '0xOwnerAddress',
10
- contract: '0xContractAddress',
11
- staticCall: async (currency, method, args) => {
12
- console.log(currency, method, args)
13
- if (method === 'balanceOf') {
14
- return BigInt(100000)
15
- } else if (method === '_isContractCreator') {
16
- return true
17
- }
18
- },
19
- call: async (currency, method, args) => {
20
- console.log(currency, method, args)
21
-
22
- if (method === 'balanceOf') {
23
- return BigInt(100000)
24
- } else if (method === '_isContractCreator') {
25
- return true
26
- }
27
- }
28
- }
29
- factory = new Factory('0xTokenAddress', BigInt(1000))
30
- })
31
-
32
- it('should throw an error if balance is too low', async () => {
33
- factory._canPay = () => false
34
- try {
35
- await factory.registerContract('0xContractAddress')
36
- } catch (error) {
37
- expect(error.message).to.equal("can't register, balance to low")
38
- }
39
- })
40
-
41
- it('should throw an error if address is not a contract creator', async () => {
42
- factory._canPay = () => true
43
- factory.isCreator = async () => false
44
- try {
45
- await factory.registerContract('0xContractAddress')
46
- } catch (error) {
47
- expect(error.message).to.equal("You don't own that contract")
48
- }
49
- })
50
-
51
- it('should throw an error if contract is already registered', async () => {
52
- await factory.registerContract('0xContractAddress')
53
- try {
54
- await factory.registerContract('0xContractAddress')
55
- } catch (error) {
56
- expect(error.message).to.equal('already registered')
57
- }
58
- })
59
-
60
- it('should successfully register a contract', async () => {
61
- await factory.registerContract('0xContractAddress')
62
-
63
- expect(factory.totalContracts).to.equal(BigInt(1))
64
- expect(factory.contracts).to.include('0xContractAddress')
65
- })
66
- })
1
+ import { describe, it, beforeEach } from 'node:test'
2
+ import assert from 'node:assert/strict'
3
+ import Factory from './../exports/factory.js'
4
+
5
+ describe('Factory', () => {
6
+ let factory
7
+
8
+ beforeEach(() => {
9
+ global.msg = {
10
+ sender: '0xOwnerAddress',
11
+ contract: '0xContractAddress',
12
+ staticCall: async (currency, method, args) => {
13
+ if (method === 'balanceOf') {
14
+ return BigInt(100000)
15
+ } else if (method === 'creator') {
16
+ return true
17
+ }
18
+ },
19
+ call: async (currency, method, args) => {
20
+ if (method === 'balanceOf') {
21
+ return BigInt(100000)
22
+ } else if (method === 'creator') {
23
+ return true
24
+ }
25
+ }
26
+ }
27
+ factory = new Factory('0xTokenAddress', BigInt(1000))
28
+ })
29
+
30
+ it('should throw an error if address is not a contract creator', async () => {
31
+ factory.isCreator = async () => false
32
+ await assert.rejects(async () => await factory.registerContract('0xContractAddress'), {
33
+ message: "You don't own that contract"
34
+ })
35
+ })
36
+
37
+ it('should throw an error if balance is too low', async () => {
38
+ global.msg.staticCall = async (currency, method, args) => {
39
+ if (method === 'balanceOf') {
40
+ return BigInt(100) // Balance too low
41
+ } else if (method === 'creator') {
42
+ return true
43
+ } else if (method === 'transfer') {
44
+ throw new Error('amount exceeds balance')
45
+ }
46
+ }
47
+ await assert.rejects(async () => await factory.registerContract('0xContractAddress'), {
48
+ message: 'amount exceeds balance'
49
+ })
50
+ })
51
+
52
+ it('should throw an error if contract is already registered', async () => {
53
+ await factory.registerContract('0xContractAddress')
54
+ await assert.rejects(async () => await factory.registerContract('0xContractAddress'), {
55
+ message: 'already registered'
56
+ })
57
+ })
58
+
59
+ it('should successfully register a contract', async () => {
60
+ await factory.registerContract('0xContractAddress')
61
+
62
+ assert.equal(factory.totalContracts, BigInt(1))
63
+ assert.ok(factory.contracts.includes('0xContractAddress'))
64
+ })
65
+ })