@xyo-network/chain-sdk 1.7.8 → 1.7.9
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/package.json +31 -23
- package/src/e2e/spec/ChainHeadSelector.spec.ts +61 -0
- package/src/e2e/spec/EvmBlockRewardService.spec.ts +180 -0
- package/src/e2e/spec/EvmChainService.spec.ts +131 -0
- package/src/e2e/spec/XyoBlockRewardService.spec.ts +77 -0
- package/src/e2e/spec/send-xl1.spec.ts +98 -0
- package/src/spec/RequiredBalance.spec.ts +44 -0
- package/xy.config.ts +0 -11
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "http://json.schemastore.org/package.json",
|
|
3
3
|
"name": "@xyo-network/chain-sdk",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.9",
|
|
5
5
|
"description": "XYO Layer One SDK",
|
|
6
6
|
"homepage": "https://xylabs.com",
|
|
7
7
|
"bugs": {
|
|
@@ -24,21 +24,29 @@
|
|
|
24
24
|
".": {
|
|
25
25
|
"browser": {
|
|
26
26
|
"types": "./dist/browser/index-browser.d.ts",
|
|
27
|
+
"source": "./src/index-browser.ts",
|
|
27
28
|
"default": "./dist/browser/index-browser.mjs"
|
|
28
29
|
},
|
|
29
30
|
"node": {
|
|
30
31
|
"types": "./dist/node/index-node.d.ts",
|
|
32
|
+
"source": "./src/index-node.ts",
|
|
31
33
|
"default": "./dist/node/index-node.mjs"
|
|
32
34
|
},
|
|
33
35
|
"neutral": {
|
|
34
36
|
"types": "./dist/neutral/index-shared.d.ts",
|
|
37
|
+
"source": "./src/index-shared.ts",
|
|
35
38
|
"default": "./dist/neutral/index-shared.mjs"
|
|
36
39
|
}
|
|
37
40
|
},
|
|
38
41
|
"./package.json": "./package.json"
|
|
39
42
|
},
|
|
40
43
|
"module": "./dist/neutral/index-shared.mjs",
|
|
44
|
+
"source": "./src/index-shared.ts",
|
|
41
45
|
"types": "./dist/neutral/index-shared.d.ts",
|
|
46
|
+
"files": [
|
|
47
|
+
"dist",
|
|
48
|
+
"src"
|
|
49
|
+
],
|
|
42
50
|
"workspaces": [
|
|
43
51
|
"packages/*"
|
|
44
52
|
],
|
|
@@ -52,36 +60,36 @@
|
|
|
52
60
|
"@xylabs/assert": "^4.13.21",
|
|
53
61
|
"@xylabs/delay": "^4.13.21",
|
|
54
62
|
"@xylabs/hex": "^4.13.21",
|
|
55
|
-
"@xyo-network/account": "^4.1.
|
|
56
|
-
"@xyo-network/account-model": "^4.1.
|
|
57
|
-
"@xyo-network/chain-ethereum": "^1.7.
|
|
58
|
-
"@xyo-network/chain-modules": "^1.7.
|
|
59
|
-
"@xyo-network/chain-orchestration": "^1.7.
|
|
60
|
-
"@xyo-network/chain-protocol": "^1.7.
|
|
61
|
-
"@xyo-network/chain-services": "^1.7.
|
|
62
|
-
"@xyo-network/chain-utils": "^1.7.
|
|
63
|
-
"@xyo-network/chain-validation": "^1.7.
|
|
64
|
-
"@xyo-network/payload-builder": "^4.1.
|
|
63
|
+
"@xyo-network/account": "^4.1.6",
|
|
64
|
+
"@xyo-network/account-model": "^4.1.6",
|
|
65
|
+
"@xyo-network/chain-ethereum": "^1.7.9",
|
|
66
|
+
"@xyo-network/chain-modules": "^1.7.9",
|
|
67
|
+
"@xyo-network/chain-orchestration": "^1.7.9",
|
|
68
|
+
"@xyo-network/chain-protocol": "^1.7.9",
|
|
69
|
+
"@xyo-network/chain-services": "^1.7.9",
|
|
70
|
+
"@xyo-network/chain-utils": "^1.7.9",
|
|
71
|
+
"@xyo-network/chain-validation": "^1.7.9",
|
|
72
|
+
"@xyo-network/payload-builder": "^4.1.6",
|
|
65
73
|
"@xyo-network/typechain": "^3.5.4",
|
|
66
|
-
"@xyo-network/wallet-model": "^4.1.
|
|
67
|
-
"@xyo-network/xl1-protocol": "^1.7.
|
|
68
|
-
"@xyo-network/xl1-protocol-sdk": "^1.7.
|
|
74
|
+
"@xyo-network/wallet-model": "^4.1.6",
|
|
75
|
+
"@xyo-network/xl1-protocol": "^1.7.10",
|
|
76
|
+
"@xyo-network/xl1-protocol-sdk": "^1.7.9",
|
|
69
77
|
"ethers": "6.15.0"
|
|
70
78
|
},
|
|
71
79
|
"devDependencies": {
|
|
72
|
-
"@types/node": "^24.0.
|
|
80
|
+
"@types/node": "^24.0.15",
|
|
73
81
|
"@xylabs/decimal-precision": "^4.13.21",
|
|
74
82
|
"@xylabs/delay": "^4.13.21",
|
|
75
83
|
"@xylabs/ts-scripts-yarn3": "^7.0.0",
|
|
76
84
|
"@xylabs/tsconfig": "^7.0.0",
|
|
77
|
-
"@xyo-network/account": "^4.1.
|
|
78
|
-
"@xyo-network/account-model": "^4.1.
|
|
79
|
-
"@xyo-network/archivist-memory": "^4.1.
|
|
80
|
-
"@xyo-network/archivist-model": "^4.1.
|
|
81
|
-
"@xyo-network/boundwitness-builder": "^4.1.
|
|
82
|
-
"@xyo-network/chain-analyze": "^1.7.
|
|
83
|
-
"@xyo-network/payload-model": "^4.1.
|
|
84
|
-
"@xyo-network/wallet": "^4.1.
|
|
85
|
+
"@xyo-network/account": "^4.1.6",
|
|
86
|
+
"@xyo-network/account-model": "^4.1.6",
|
|
87
|
+
"@xyo-network/archivist-memory": "^4.1.6",
|
|
88
|
+
"@xyo-network/archivist-model": "^4.1.6",
|
|
89
|
+
"@xyo-network/boundwitness-builder": "^4.1.6",
|
|
90
|
+
"@xyo-network/chain-analyze": "^1.7.9",
|
|
91
|
+
"@xyo-network/payload-model": "^4.1.6",
|
|
92
|
+
"@xyo-network/wallet": "^4.1.6",
|
|
85
93
|
"eslint": "^9.31.0",
|
|
86
94
|
"knip": "^5.62.0",
|
|
87
95
|
"typescript": "^5.8.3",
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { Account } from '@xyo-network/account'
|
|
2
|
+
import type { AccountInstance } from '@xyo-network/account-model'
|
|
3
|
+
import { MemoryArchivist } from '@xyo-network/archivist-memory'
|
|
4
|
+
import { ChainHeadSelector } from '@xyo-network/chain-analyze'
|
|
5
|
+
import { buildRandomChain } from '@xyo-network/chain-protocol'
|
|
6
|
+
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
7
|
+
import type { HydratedBlock } from '@xyo-network/xl1-protocol'
|
|
8
|
+
import { flattenHydratedBlock } from '@xyo-network/xl1-protocol-sdk'
|
|
9
|
+
import {
|
|
10
|
+
beforeEach,
|
|
11
|
+
describe, expect, it,
|
|
12
|
+
} from 'vitest'
|
|
13
|
+
|
|
14
|
+
describe('ChainHeadSelector', () => {
|
|
15
|
+
let SampleBlockChain: HydratedBlock[]
|
|
16
|
+
let SampleGenesisOnlyBlockChain: HydratedBlock[]
|
|
17
|
+
let producer: AccountInstance
|
|
18
|
+
let archivist: MemoryArchivist
|
|
19
|
+
beforeEach (async () => {
|
|
20
|
+
producer = await Account.random()
|
|
21
|
+
archivist = await MemoryArchivist.create({ account: 'random' })
|
|
22
|
+
SampleBlockChain = (await buildRandomChain(producer, 10))
|
|
23
|
+
SampleGenesisOnlyBlockChain = await buildRandomChain(producer, 1)
|
|
24
|
+
})
|
|
25
|
+
it('no producers', async () => {
|
|
26
|
+
expect(SampleBlockChain.length).toEqual(10)
|
|
27
|
+
await archivist.insert(SampleBlockChain.flatMap(block => flattenHydratedBlock(block)))
|
|
28
|
+
const selector = new ChainHeadSelector({ chainArchivist: archivist })
|
|
29
|
+
const bestHead = await selector.findBestHead()
|
|
30
|
+
expect(bestHead).toBeDefined()
|
|
31
|
+
expect(bestHead?.previous).toBeDefined()
|
|
32
|
+
})
|
|
33
|
+
it('only one producer', async () => {
|
|
34
|
+
await archivist.insert(SampleBlockChain.flatMap(block => flattenHydratedBlock(block)))
|
|
35
|
+
const selector = new ChainHeadSelector({ chainArchivist: archivist, activeProducers: [producer.address] })
|
|
36
|
+
const bestHead = await selector.findBestHead()
|
|
37
|
+
expect(bestHead).toBeDefined()
|
|
38
|
+
expect(bestHead?.previous === null).toBeFalsy()
|
|
39
|
+
})
|
|
40
|
+
it('only genesis block', async () => {
|
|
41
|
+
await archivist.insert(SampleGenesisOnlyBlockChain.flatMap(block => flattenHydratedBlock(block)))
|
|
42
|
+
const selector = new ChainHeadSelector({ chainArchivist: archivist, activeProducers: [producer.address] })
|
|
43
|
+
const bestHead = await selector.findBestHead()
|
|
44
|
+
expect(bestHead).toBeDefined()
|
|
45
|
+
expect(bestHead?.previous).toBeNull()
|
|
46
|
+
})
|
|
47
|
+
it('with finalization', async () => {
|
|
48
|
+
await archivist.insert(SampleBlockChain.flatMap(block => flattenHydratedBlock(block)))
|
|
49
|
+
const selector = new ChainHeadSelector({ chainArchivist: archivist, activeProducers: [producer.address] })
|
|
50
|
+
const bestHead = await selector.findBestHead()
|
|
51
|
+
if (bestHead) {
|
|
52
|
+
await selector.finalizeBlock(await PayloadBuilder.hash(bestHead))
|
|
53
|
+
}
|
|
54
|
+
const bestHead2 = await selector.findBestHead()
|
|
55
|
+
expect(bestHead).toBeDefined()
|
|
56
|
+
expect(bestHead2).toBeDefined()
|
|
57
|
+
if (bestHead && bestHead2) {
|
|
58
|
+
expect(await PayloadBuilder.hash(bestHead)).toBe(await PayloadBuilder.hash(bestHead2))
|
|
59
|
+
}
|
|
60
|
+
})
|
|
61
|
+
})
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { assertEx } from '@xylabs/assert'
|
|
4
|
+
import { toFixedPoint } from '@xylabs/decimal-precision'
|
|
5
|
+
import { delay } from '@xylabs/delay'
|
|
6
|
+
import { Account } from '@xyo-network/account'
|
|
7
|
+
import {
|
|
8
|
+
createChainContract, createChainRewardsContract, getDefaultGasConfig,
|
|
9
|
+
} from '@xyo-network/chain-ethereum'
|
|
10
|
+
import type { EvmBlockRewardServiceParams } from '@xyo-network/chain-services'
|
|
11
|
+
import { EvmBlockRewardService, EvmChainService } from '@xyo-network/chain-services'
|
|
12
|
+
import { HDWallet } from '@xyo-network/wallet'
|
|
13
|
+
import type { WalletInstance } from '@xyo-network/wallet-model'
|
|
14
|
+
import { getDefaultConfig } from '@xyo-network/xl1-protocol-sdk'
|
|
15
|
+
import type { ContractRunner } from 'ethers/providers'
|
|
16
|
+
import { JsonRpcProvider } from 'ethers/providers'
|
|
17
|
+
import { Wallet } from 'ethers/wallet'
|
|
18
|
+
import {
|
|
19
|
+
afterAll,
|
|
20
|
+
beforeAll,
|
|
21
|
+
describe, expect, it,
|
|
22
|
+
} from 'vitest'
|
|
23
|
+
|
|
24
|
+
import { startGanache, stopGanache } from '../../test/index.ts'
|
|
25
|
+
|
|
26
|
+
const ganachePort = 8565
|
|
27
|
+
const testPhrase = 'edit hill neutral usage share kite knee abandon slim open lottery abandon'
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line max-statements
|
|
30
|
+
describe('EvmBlockRewardService', () => {
|
|
31
|
+
const genesisReward = toFixedPoint(20_000_000_000n)
|
|
32
|
+
const initialStepReward = toFixedPoint(1000n)
|
|
33
|
+
const stepSize = 1_000_000n
|
|
34
|
+
const minRewardPerBlock = toFixedPoint(30n)
|
|
35
|
+
const config = getDefaultConfig()
|
|
36
|
+
|
|
37
|
+
let service: EvmBlockRewardService
|
|
38
|
+
let accountPerson: WalletInstance
|
|
39
|
+
let accountPerson2: WalletInstance
|
|
40
|
+
let walletPerson: WalletInstance
|
|
41
|
+
let ganacheProcess: ChildProcess
|
|
42
|
+
let ethWalletPerson: Wallet
|
|
43
|
+
let ethWalletPerson2: Wallet
|
|
44
|
+
let provider: JsonRpcProvider
|
|
45
|
+
let provider2: JsonRpcProvider
|
|
46
|
+
|
|
47
|
+
beforeAll(async () => {
|
|
48
|
+
ganacheProcess = await startGanache(ganachePort, testPhrase)
|
|
49
|
+
walletPerson = await HDWallet.fromPhrase(testPhrase, "m/44'/60'/0'/0")
|
|
50
|
+
accountPerson = await walletPerson.derivePath("m/44'/60'/0'/0/0")
|
|
51
|
+
accountPerson2 = await walletPerson.derivePath("m/44'/60'/0'/0/1")
|
|
52
|
+
provider = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
53
|
+
ethWalletPerson = new Wallet(accountPerson.privateKey, provider)
|
|
54
|
+
provider2 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
55
|
+
ethWalletPerson2 = new Wallet(accountPerson2.privateKey, provider2)
|
|
56
|
+
const rewardsContract = await createChainRewardsContract(ethWalletPerson, genesisReward, getDefaultGasConfig())
|
|
57
|
+
const tokenAddress = '0x01'
|
|
58
|
+
const genesisBlockHash = '0x01'
|
|
59
|
+
const stakeContract = await createChainContract(ethWalletPerson2, tokenAddress, genesisBlockHash, rewardsContract, 1n, getDefaultGasConfig())
|
|
60
|
+
const id = stakeContract
|
|
61
|
+
const account = await Account.random()
|
|
62
|
+
const privateKey = assertEx(account.private?.hex, () => 'Error: Account does not have a private key')
|
|
63
|
+
const runner: ContractRunner = new Wallet(privateKey, provider)
|
|
64
|
+
|
|
65
|
+
const chainService = await EvmChainService.create({ id, runner })
|
|
66
|
+
const params: EvmBlockRewardServiceParams = {
|
|
67
|
+
account,
|
|
68
|
+
chainService,
|
|
69
|
+
config,
|
|
70
|
+
name: 'EvmBlockRewardService',
|
|
71
|
+
provider,
|
|
72
|
+
}
|
|
73
|
+
service = await EvmBlockRewardService.create(params)
|
|
74
|
+
}, 40_000)
|
|
75
|
+
|
|
76
|
+
afterAll(async () => {
|
|
77
|
+
await delay(1000)
|
|
78
|
+
stopGanache(ganacheProcess)
|
|
79
|
+
}, 20_000)
|
|
80
|
+
|
|
81
|
+
it('blocks 0 and 1', async () => {
|
|
82
|
+
expect(await service.getRewardForBlock(0n)).toBe(genesisReward)
|
|
83
|
+
expect(await service.getRewardForBlock(10n)).toBe(initialStepReward)
|
|
84
|
+
})
|
|
85
|
+
|
|
86
|
+
it('last block in step 1', async () => {
|
|
87
|
+
expect(await service.getRewardForBlock(stepSize - 1n)).toBe(initialStepReward)
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
it('first blocks in step 2', async () => {
|
|
91
|
+
expect(await service.getRewardForBlock(stepSize)).toBe(950_000_000_000_000_000_000n)
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
it('last blocks in step 2', async () => {
|
|
95
|
+
expect(await service.getRewardForBlock(stepSize * 2n - 1n)).toBe(950_000_000_000_000_000_000n)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('first blocks in step 3', async () => {
|
|
99
|
+
expect(await service.getRewardForBlock(stepSize * 2n)).toBe(902_000_000_000_000_000_000n)
|
|
100
|
+
})
|
|
101
|
+
|
|
102
|
+
it('first blocks in step 5', async () => {
|
|
103
|
+
expect(await service.getRewardForBlock(stepSize * 4n)).toBe(813_000_000_000_000_000_000n)
|
|
104
|
+
expect(await service.getRewardForBlock(stepSize * 4n + 1n)).toBe(813_000_000_000_000_000_000n)
|
|
105
|
+
})
|
|
106
|
+
|
|
107
|
+
it('last blocks in step 5', async () => {
|
|
108
|
+
expect(await service.getRewardForBlock(stepSize * 5n - 1n)).toBe(813_000_000_000_000_000_000n)
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
it('first blocks in step 10', async () => {
|
|
112
|
+
expect(await service.getRewardForBlock(stepSize * 9n)).toBe(627_000_000_000_000_000_000n)
|
|
113
|
+
expect(await service.getRewardForBlock(stepSize * 9n + 1n)).toBe(627_000_000_000_000_000_000n)
|
|
114
|
+
})
|
|
115
|
+
|
|
116
|
+
it('last blocks in step 10', async () => {
|
|
117
|
+
expect(await service.getRewardForBlock(stepSize * 10n - 1n)).toBe(627_000_000_000_000_000_000n)
|
|
118
|
+
})
|
|
119
|
+
|
|
120
|
+
it('first blocks in step 20', async () => {
|
|
121
|
+
expect(await service.getRewardForBlock(stepSize * 19n)).toBe(372_000_000_000_000_000_000n)
|
|
122
|
+
expect(await service.getRewardForBlock(stepSize * 19n + 1n)).toBe(372_000_000_000_000_000_000n)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('last blocks in step 20', async () => {
|
|
126
|
+
expect(await service.getRewardForBlock(stepSize * 20n - 1n)).toBe(372_000_000_000_000_000_000n)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
it('first blocks in step 30', async () => {
|
|
130
|
+
expect(await service.getRewardForBlock(stepSize * 29n)).toBe(219_000_000_000_000_000_000n)
|
|
131
|
+
expect(await service.getRewardForBlock(stepSize * 29n + 1n)).toBe(219_000_000_000_000_000_000n)
|
|
132
|
+
})
|
|
133
|
+
|
|
134
|
+
it('last blocks in step 30', async () => {
|
|
135
|
+
expect(await service.getRewardForBlock(stepSize * 30n - 1n)).toBe(219_000_000_000_000_000_000n)
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
it('first blocks in step 40', async () => {
|
|
139
|
+
expect(await service.getRewardForBlock(stepSize * 39n)).toBe(128_000_000_000_000_000_000n)
|
|
140
|
+
expect(await service.getRewardForBlock(stepSize * 39n + 1n)).toBe(128_000_000_000_000_000_000n)
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
it('last blocks in step 40', async () => {
|
|
144
|
+
expect(await service.getRewardForBlock(stepSize * 40n - 1n)).toBe(128_000_000_000_000_000_000n)
|
|
145
|
+
})
|
|
146
|
+
|
|
147
|
+
it('first blocks in step 44', async () => {
|
|
148
|
+
expect(await service.getRewardForBlock(stepSize * 43n)).toBe(102_000_000_000_000_000_000n)
|
|
149
|
+
expect(await service.getRewardForBlock(stepSize * 43n + 1n)).toBe(102_000_000_000_000_000_000n)
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('last blocks in step 44', async () => {
|
|
153
|
+
expect(await service.getRewardForBlock(stepSize * 44n - 1n)).toBe(102_000_000_000_000_000_000n)
|
|
154
|
+
})
|
|
155
|
+
|
|
156
|
+
it('first blocks in step 64', async () => {
|
|
157
|
+
expect(await service.getRewardForBlock(stepSize * 63n)).toBe(31_000_000_000_000_000_000n)
|
|
158
|
+
expect(await service.getRewardForBlock(stepSize * 63n + 1n)).toBe(31_000_000_000_000_000_000n)
|
|
159
|
+
})
|
|
160
|
+
|
|
161
|
+
it('first blocks in step 68', async () => {
|
|
162
|
+
expect(await service.getRewardForBlock(stepSize * 64n)).toBe(minRewardPerBlock)
|
|
163
|
+
expect(await service.getRewardForBlock(stepSize * 64n + 1n)).toBe(minRewardPerBlock)
|
|
164
|
+
})
|
|
165
|
+
|
|
166
|
+
it('first blocks in step 80', async () => {
|
|
167
|
+
expect(await service.getRewardForBlock(stepSize * 80n)).toBe(minRewardPerBlock)
|
|
168
|
+
expect(await service.getRewardForBlock(stepSize * 80n + 1n)).toBe(minRewardPerBlock)
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it('first blocks in step 200', async () => {
|
|
172
|
+
expect(await service.getRewardForBlock(stepSize * 200n)).toBe(minRewardPerBlock)
|
|
173
|
+
expect(await service.getRewardForBlock(stepSize * 200n + 1n)).toBe(minRewardPerBlock)
|
|
174
|
+
})
|
|
175
|
+
|
|
176
|
+
it('first blocks in step 2000', async () => {
|
|
177
|
+
expect(await service.getRewardForBlock(stepSize * 2000n)).toBe(minRewardPerBlock)
|
|
178
|
+
expect(await service.getRewardForBlock(stepSize * 2000n + 1n)).toBe(minRewardPerBlock)
|
|
179
|
+
})
|
|
180
|
+
})
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { ChildProcess } from 'node:child_process'
|
|
2
|
+
|
|
3
|
+
import { assertEx } from '@xylabs/assert'
|
|
4
|
+
import { delay } from '@xylabs/delay'
|
|
5
|
+
import { asAddress } from '@xylabs/hex'
|
|
6
|
+
import type { AccountInstance } from '@xyo-network/account-model'
|
|
7
|
+
import { createChain, toEthAddress } from '@xyo-network/chain-ethereum'
|
|
8
|
+
import { EvmChainService } from '@xyo-network/chain-services'
|
|
9
|
+
import { XYO_ZERO_ADDRESS } from '@xyo-network/chain-utils'
|
|
10
|
+
import type { BurnableErc20 } from '@xyo-network/typechain'
|
|
11
|
+
import { BurnableErc20__factory as Erc20Factory, StakedXyoChain__factory as StakedXyoChainFactory } from '@xyo-network/typechain'
|
|
12
|
+
import { HDWallet } from '@xyo-network/wallet'
|
|
13
|
+
import type { WalletInstance } from '@xyo-network/wallet-model'
|
|
14
|
+
import type { ContractRunner } from 'ethers/providers'
|
|
15
|
+
import { JsonRpcProvider } from 'ethers/providers'
|
|
16
|
+
import { parseUnits } from 'ethers/utils'
|
|
17
|
+
import { Wallet } from 'ethers/wallet'
|
|
18
|
+
import {
|
|
19
|
+
afterAll, beforeAll, describe, expect, it,
|
|
20
|
+
} from 'vitest'
|
|
21
|
+
|
|
22
|
+
import { startGanache, stopGanache } from '../../test/index.ts'
|
|
23
|
+
|
|
24
|
+
const ganachePort = 8545
|
|
25
|
+
const testPhrase = 'edit hill neutral usage share kite knee abandon slim open lottery abandon'
|
|
26
|
+
|
|
27
|
+
const gasConfig = () => {
|
|
28
|
+
return {
|
|
29
|
+
gasLimit: 2_000_000, // Set the gas limit
|
|
30
|
+
gasPrice: parseUnits('100', 'gwei'),
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
describe('EvmContractChainStake', () => {
|
|
35
|
+
let account0: WalletInstance
|
|
36
|
+
let account1: WalletInstance
|
|
37
|
+
let walletPerson: WalletInstance
|
|
38
|
+
let initialProducerAccount: AccountInstance
|
|
39
|
+
let ganacheProcess: ChildProcess
|
|
40
|
+
let ethWalletPerson0: Wallet
|
|
41
|
+
let ethWalletPerson1: Wallet
|
|
42
|
+
let provider0: JsonRpcProvider
|
|
43
|
+
let provider1: JsonRpcProvider
|
|
44
|
+
|
|
45
|
+
beforeAll(async () => {
|
|
46
|
+
ganacheProcess = await startGanache(ganachePort, testPhrase)
|
|
47
|
+
walletPerson = await HDWallet.fromPhrase(testPhrase, "m/44'/60'/0'/0")
|
|
48
|
+
initialProducerAccount = await HDWallet.fromPhrase(testPhrase, "m/44'/60'/1'/0")
|
|
49
|
+
account0 = await walletPerson.derivePath("m/44'/60'/0'/0/0")
|
|
50
|
+
account1 = await walletPerson.derivePath("m/44'/60'/0'/0/1")
|
|
51
|
+
provider0 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
52
|
+
ethWalletPerson0 = new Wallet(account0.privateKey, provider0)
|
|
53
|
+
provider1 = new JsonRpcProvider(`http://127.0.0.1:${ganachePort}`, 1337)
|
|
54
|
+
ethWalletPerson1 = new Wallet(account1.privateKey, provider1)
|
|
55
|
+
}, 20_000)
|
|
56
|
+
|
|
57
|
+
afterAll(async () => {
|
|
58
|
+
await delay(1000)
|
|
59
|
+
stopGanache(ganacheProcess)
|
|
60
|
+
}, 20_000)
|
|
61
|
+
|
|
62
|
+
describe.sequential('Create Fresh Chain', () => {
|
|
63
|
+
let erc20Contract: BurnableErc20
|
|
64
|
+
let erc20Address: string
|
|
65
|
+
let erc20ContractPerson0: BurnableErc20
|
|
66
|
+
let xyoChainContractAddress: string
|
|
67
|
+
let erc20ContractPerson1: BurnableErc20
|
|
68
|
+
|
|
69
|
+
it('Create Erc20', async () => {
|
|
70
|
+
const erc20 = new Erc20Factory(ethWalletPerson0)
|
|
71
|
+
erc20Contract = await (await erc20.deploy('Test Token', 'TST', parseUnits('1000000', 18), gasConfig())).waitForDeployment()
|
|
72
|
+
erc20Address = await erc20Contract.getAddress()
|
|
73
|
+
expect(erc20Address).toBeDefined()
|
|
74
|
+
erc20ContractPerson0 = Erc20Factory.connect(erc20Address, ethWalletPerson0)
|
|
75
|
+
const balance = await erc20ContractPerson0.balanceOf(ethWalletPerson0.address)
|
|
76
|
+
expect(balance).toBe(parseUnits('1000000', 18))
|
|
77
|
+
})
|
|
78
|
+
|
|
79
|
+
it('Transfer Tokens', async () => {
|
|
80
|
+
const transfer = await (await erc20ContractPerson0.transfer(ethWalletPerson1.address, parseUnits('1000', 18), gasConfig())).wait()
|
|
81
|
+
expect(transfer).toBeDefined()
|
|
82
|
+
erc20ContractPerson1 = Erc20Factory.connect(erc20Address, ethWalletPerson1)
|
|
83
|
+
const balance = await erc20ContractPerson1.balanceOf(ethWalletPerson1.address)
|
|
84
|
+
expect(balance).toBe(parseUnits('1000', 18))
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('Create Chain Contract', async () => {
|
|
88
|
+
// Create Chain Contract
|
|
89
|
+
const result = await (createChain(
|
|
90
|
+
ethWalletPerson0, // signer
|
|
91
|
+
toEthAddress(erc20Address), // stakingTokenAddress
|
|
92
|
+
initialProducerAccount, // initialProducer
|
|
93
|
+
null,
|
|
94
|
+
0n,
|
|
95
|
+
XYO_ZERO_ADDRESS,
|
|
96
|
+
50_000n,
|
|
97
|
+
gasConfig(),
|
|
98
|
+
))
|
|
99
|
+
|
|
100
|
+
xyoChainContractAddress = toEthAddress(result[0])
|
|
101
|
+
|
|
102
|
+
const xyoChainPerson0 = StakedXyoChainFactory.connect(xyoChainContractAddress)
|
|
103
|
+
expect(xyoChainPerson0).toBeDefined()
|
|
104
|
+
const xyoChainPerson1 = StakedXyoChainFactory.connect(xyoChainContractAddress, ethWalletPerson1)
|
|
105
|
+
const xyoChainPerson1Address = await xyoChainPerson1.getAddress()
|
|
106
|
+
expect(xyoChainPerson1Address).toBeDefined()
|
|
107
|
+
}, 1_000_000)
|
|
108
|
+
|
|
109
|
+
it('Approve Stake Chain Address', async () => {
|
|
110
|
+
const approveResultTx = await (await erc20ContractPerson1.approve(xyoChainContractAddress, parseUnits('100', 18), gasConfig())).wait()
|
|
111
|
+
expect(approveResultTx).toBeDefined()
|
|
112
|
+
})
|
|
113
|
+
|
|
114
|
+
it('Stake Chain Contract', async () => {
|
|
115
|
+
const address = assertEx(asAddress(xyoChainContractAddress))
|
|
116
|
+
const xyoChainPerson1 = await EvmChainService.create({
|
|
117
|
+
id: address,
|
|
118
|
+
runner: ethWalletPerson1 as ContractRunner,
|
|
119
|
+
})
|
|
120
|
+
const stakeResult = await xyoChainPerson1.addStake(await ethWalletPerson1.getAddress(), parseUnits('50', 18))
|
|
121
|
+
expect(stakeResult).toBeTruthy()
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
it('Read Stake', async () => {
|
|
125
|
+
const address = assertEx(asAddress(xyoChainContractAddress))
|
|
126
|
+
const xyoChainPerson0 = await EvmChainService.create({ id: address, runner: ethWalletPerson0 as ContractRunner })
|
|
127
|
+
const stakeByAddressResult = await xyoChainPerson0.activeByAddressStaked(await ethWalletPerson1.getAddress())
|
|
128
|
+
expect(stakeByAddressResult).toBe(parseUnits('50', 18))
|
|
129
|
+
})
|
|
130
|
+
}, 10_000)
|
|
131
|
+
}, 10_000)
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { toFixedPoint } from '@xylabs/decimal-precision'
|
|
2
|
+
import { MemoryBlockRewardService } from '@xyo-network/chain-services'
|
|
3
|
+
import {
|
|
4
|
+
describe, expect, it,
|
|
5
|
+
} from 'vitest'
|
|
6
|
+
|
|
7
|
+
describe('XyoBlockRewardService', async () => {
|
|
8
|
+
const creatorReward = toFixedPoint(20_000_000_000n)
|
|
9
|
+
const initialStepReward = toFixedPoint(3000n)
|
|
10
|
+
const stepSize = 1_000_000n
|
|
11
|
+
const minRewardPerBlock = toFixedPoint(30n)
|
|
12
|
+
const service = await MemoryBlockRewardService.create({
|
|
13
|
+
initialStepReward, stepSize, creatorReward, minRewardPerBlock,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
it('blocks 0 and 1', async () => {
|
|
17
|
+
expect(await service.getRewardForBlock(0n)).toBe(creatorReward)
|
|
18
|
+
expect(await service.getRewardForBlock(10n)).toBe(initialStepReward)
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
it('last block in step 1', async () => {
|
|
22
|
+
expect(await service.getRewardForBlock(stepSize - 1n)).toBe(initialStepReward)
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
it('first blocks in step 2', async () => {
|
|
26
|
+
expect(await service.getRewardForBlock(stepSize)).toBe(2_700_000_000_000_000_000_000n)
|
|
27
|
+
})
|
|
28
|
+
|
|
29
|
+
it('last blocks in step 2', async () => {
|
|
30
|
+
expect(await service.getRewardForBlock(stepSize * 2n - 1n)).toBe(2_700_000_000_000_000_000_000n)
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('first blocks in step 3', async () => {
|
|
34
|
+
expect(await service.getRewardForBlock(stepSize * 2n)).toBe(2_430_000_000_000_000_000_000n)
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('first blocks in step 5', async () => {
|
|
38
|
+
expect(await service.getRewardForBlock(stepSize * 4n)).toBe(1_968_300_000_000_000_000_000n)
|
|
39
|
+
expect(await service.getRewardForBlock(stepSize * 4n + 1n)).toBe(1_968_300_000_000_000_000_000n)
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
it('last blocks in step 5', async () => {
|
|
43
|
+
expect(await service.getRewardForBlock(stepSize * 5n - 1n)).toBe(1_968_300_000_000_000_000_000n)
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('first blocks in step 10', async () => {
|
|
47
|
+
expect(await service.getRewardForBlock(stepSize * 9n)).toBe(1_162_261_467_000_000_000_000n)
|
|
48
|
+
expect(await service.getRewardForBlock(stepSize * 9n + 1n)).toBe(1_162_261_467_000_000_000_000n)
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('last blocks in step 10', async () => {
|
|
52
|
+
expect(await service.getRewardForBlock(stepSize * 10n - 1n)).toBe(1_162_261_467_000_000_000_000n)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('first blocks in step 40', async () => {
|
|
56
|
+
expect(await service.getRewardForBlock(stepSize * 39n)).toBe(49_269_609_804_781_974_438n)
|
|
57
|
+
expect(await service.getRewardForBlock(stepSize * 39n + 1n)).toBe(49_269_609_804_781_974_438n)
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('last blocks in step 40', async () => {
|
|
61
|
+
expect(await service.getRewardForBlock(stepSize * 40n - 1n)).toBe(49_269_609_804_781_974_438n)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('first blocks in step 44', async () => {
|
|
65
|
+
expect(await service.getRewardForBlock(stepSize * 43n)).toBe(32_325_790_992_917_453_429n)
|
|
66
|
+
expect(await service.getRewardForBlock(stepSize * 43n + 1n)).toBe(32_325_790_992_917_453_429n)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
it('last blocks in step 44', async () => {
|
|
70
|
+
expect(await service.getRewardForBlock(stepSize * 44n - 1n)).toBe(32_325_790_992_917_453_429n)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
it('first blocks in step 54', async () => {
|
|
74
|
+
expect(await service.getRewardForBlock(stepSize * 53n)).toBe(minRewardPerBlock)
|
|
75
|
+
expect(await service.getRewardForBlock(stepSize * 53n + 1n)).toBe(minRewardPerBlock)
|
|
76
|
+
})
|
|
77
|
+
})
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { assertEx } from '@xylabs/assert'
|
|
2
|
+
import { toFixedPoint } from '@xylabs/decimal-precision'
|
|
3
|
+
import type { Address, Hash } from '@xylabs/hex'
|
|
4
|
+
import { toHex } from '@xylabs/hex'
|
|
5
|
+
import { Account } from '@xyo-network/account'
|
|
6
|
+
import type { AccountInstance } from '@xyo-network/account-model'
|
|
7
|
+
import { MemoryArchivist } from '@xyo-network/archivist-memory'
|
|
8
|
+
import type { ArchivistInstance } from '@xyo-network/archivist-model'
|
|
9
|
+
import { BoundWitnessBuilder } from '@xyo-network/boundwitness-builder'
|
|
10
|
+
import {
|
|
11
|
+
analyzeChain, BalanceAnalyzer, isChainSummaryBalances,
|
|
12
|
+
} from '@xyo-network/chain-analyze'
|
|
13
|
+
import {
|
|
14
|
+
buildGenesisBlock, buildNextBlock, buildRandomTransaction,
|
|
15
|
+
} from '@xyo-network/chain-protocol'
|
|
16
|
+
import { PayloadBuilder } from '@xyo-network/payload-builder'
|
|
17
|
+
import type { Payload, WithStorageMeta } from '@xyo-network/payload-model'
|
|
18
|
+
import type { BlockBoundWitness, Transfer } from '@xyo-network/xl1-protocol'
|
|
19
|
+
import { TransferSchema } from '@xyo-network/xl1-protocol'
|
|
20
|
+
import { flattenHydratedBlock } from '@xyo-network/xl1-protocol-sdk'
|
|
21
|
+
import {
|
|
22
|
+
beforeAll,
|
|
23
|
+
describe, expect, it,
|
|
24
|
+
} from 'vitest'
|
|
25
|
+
|
|
26
|
+
const balancesFromHead = async (head: Hash, archivist: ArchivistInstance) => {
|
|
27
|
+
const { balances } = assertEx((await analyzeChain(
|
|
28
|
+
archivist,
|
|
29
|
+
[new BalanceAnalyzer()],
|
|
30
|
+
head,
|
|
31
|
+
)).find(isChainSummaryBalances), () => 'ChainSummaryBalances not found')
|
|
32
|
+
return balances
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
describe.sequential('Send XL1', () => {
|
|
36
|
+
let archivist: ArchivistInstance
|
|
37
|
+
let chainId: Address
|
|
38
|
+
let previousBlock: WithStorageMeta<BlockBoundWitness>
|
|
39
|
+
let txAccount1: AccountInstance
|
|
40
|
+
let txAccount2: AccountInstance
|
|
41
|
+
let blockAccount1: AccountInstance
|
|
42
|
+
let blockAccount2: AccountInstance
|
|
43
|
+
beforeAll(async () => {
|
|
44
|
+
// make a fake chainId using a random address
|
|
45
|
+
chainId = (await Account.random()).address
|
|
46
|
+
archivist = await MemoryArchivist.create({ account: 'random' })
|
|
47
|
+
})
|
|
48
|
+
it('Get Initial Balances', async () => {
|
|
49
|
+
blockAccount1 = await Account.random()
|
|
50
|
+
txAccount1 = await Account.random()
|
|
51
|
+
const firstHydratedTx = await buildRandomTransaction(chainId, undefined, txAccount1)
|
|
52
|
+
const firstBlock = await buildGenesisBlock(chainId, [firstHydratedTx], [], [blockAccount1], blockAccount1.address, toFixedPoint(3000n))
|
|
53
|
+
expect(firstBlock[0].block).toEqual(0)
|
|
54
|
+
await archivist.insert(flattenHydratedBlock(firstBlock))
|
|
55
|
+
previousBlock = firstBlock[0]
|
|
56
|
+
const balances = await balancesFromHead(previousBlock._hash, archivist)
|
|
57
|
+
expect(balances[blockAccount1.address]).toEqual({ positive: toHex(toFixedPoint(3000n)) })
|
|
58
|
+
}, 10_000)
|
|
59
|
+
it('Do a transfer', async () => {
|
|
60
|
+
blockAccount2 = await Account.random()
|
|
61
|
+
const receiveAccount = await Account.random()
|
|
62
|
+
txAccount2 = await Account.random()
|
|
63
|
+
const transferPayload: Transfer = {
|
|
64
|
+
from: txAccount2.address,
|
|
65
|
+
epoch: Date.now(),
|
|
66
|
+
schema: TransferSchema,
|
|
67
|
+
transfers: { [receiveAccount.address]: toHex(toFixedPoint(1n)) },
|
|
68
|
+
}
|
|
69
|
+
const [innerBw] = await new BoundWitnessBuilder().payload({ schema: 'network.xyo.test', nonce: 2 } as Payload).signer(await Account.random()).build()
|
|
70
|
+
const secondHydratedTx = await buildRandomTransaction(chainId, [transferPayload, innerBw], txAccount2)
|
|
71
|
+
const secondBlock = await buildNextBlock(previousBlock, [secondHydratedTx], [], [blockAccount2], blockAccount2.address, toFixedPoint(3000n))
|
|
72
|
+
expect(secondBlock[0].block).toEqual(1)
|
|
73
|
+
await archivist.insert([...flattenHydratedBlock(secondBlock)])
|
|
74
|
+
previousBlock = secondBlock[0]
|
|
75
|
+
const balanceChanges = await balancesFromHead(await PayloadBuilder.hash(secondBlock[0]), archivist)
|
|
76
|
+
expect(balanceChanges[blockAccount1.address]).toEqual({ positive: toHex(toFixedPoint(3000n)) })
|
|
77
|
+
expect(balanceChanges[blockAccount2.address]).toEqual({ positive: toHex(toFixedPoint(3000n)) })
|
|
78
|
+
expect(balanceChanges[receiveAccount.address]).toEqual({ positive: toHex(toFixedPoint(1n)) })
|
|
79
|
+
}, 10_000)
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
/*
|
|
83
|
+
|
|
84
|
+
# Send XL1
|
|
85
|
+
|
|
86
|
+
The signer of a boundwitness that contains a transfer payload will have their balance reduced by the amount of the transfer,
|
|
87
|
+
and the receiver will have their balance increased by the amount of the transfer.
|
|
88
|
+
|
|
89
|
+
The gas that is specified in the transfer payload will be deducted from the signer's balance and will be added to the block builder's balance.
|
|
90
|
+
|
|
91
|
+
The goal is for the block builder to only have to validate the transfers that are directly in the transactions that are being added to the block.
|
|
92
|
+
This means that the builder will only accept transfers from accounts that have matching vouchers, and will only accept transfers that are valid.
|
|
93
|
+
|
|
94
|
+
If an unknown account wants to send xl1 to another account, they must get a voucher from a bank that will allow them to send the xl1.
|
|
95
|
+
|
|
96
|
+
Do we need two payloads for this, or do we need one payload that has a list of transfers?
|
|
97
|
+
|
|
98
|
+
*/
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { Account } from '@xyo-network/account'
|
|
2
|
+
import type { AccountInstance } from '@xyo-network/account-model'
|
|
3
|
+
import { MemoryArchivist } from '@xyo-network/archivist-memory'
|
|
4
|
+
import { buildRandomChain } from '@xyo-network/chain-protocol'
|
|
5
|
+
import { accountBalanceServiceFromArchivist } from '@xyo-network/chain-services'
|
|
6
|
+
import { RequiredBalanceBlockStateValidator } from '@xyo-network/chain-validation'
|
|
7
|
+
import { flattenHydratedBlock } from '@xyo-network/xl1-protocol-sdk'
|
|
8
|
+
import {
|
|
9
|
+
beforeEach,
|
|
10
|
+
describe, expect, it,
|
|
11
|
+
} from 'vitest'
|
|
12
|
+
|
|
13
|
+
describe('RequiredBalanceBlockStateValidator', () => {
|
|
14
|
+
let blockProducer: AccountInstance
|
|
15
|
+
let archivist: MemoryArchivist
|
|
16
|
+
const chainId = 'a82920051db4fcbb804463440dd45e03f72442fd'
|
|
17
|
+
beforeEach(async () => {
|
|
18
|
+
archivist = await MemoryArchivist.create({ account: 'random' })
|
|
19
|
+
blockProducer = await Account.random()
|
|
20
|
+
})
|
|
21
|
+
it('does not validate block level transfers', async () => {
|
|
22
|
+
expect(await Promise.resolve()).toBeUndefined()
|
|
23
|
+
const chain = await buildRandomChain(blockProducer, 2)
|
|
24
|
+
await archivist.insert(flattenHydratedBlock(chain[0]))
|
|
25
|
+
const hydratedBlock = chain[1]
|
|
26
|
+
const accountBalance = await accountBalanceServiceFromArchivist(archivist)
|
|
27
|
+
|
|
28
|
+
// Mock the account balance service to return a balance of 1 ETH for the block producer
|
|
29
|
+
const result = await RequiredBalanceBlockStateValidator(hydratedBlock, chainId, { accountBalance })
|
|
30
|
+
expect(result).toEqual([])
|
|
31
|
+
})
|
|
32
|
+
|
|
33
|
+
it('returns no errors when required balances are met', async () => {
|
|
34
|
+
expect(await Promise.resolve()).toBeUndefined()
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('returns an error when required balance is not met', async () => {
|
|
38
|
+
expect(await Promise.resolve()).toBeUndefined()
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
it('returns error when block has no previous hash (first block)', async () => {
|
|
42
|
+
expect(await Promise.resolve()).toBeUndefined()
|
|
43
|
+
})
|
|
44
|
+
})
|
package/xy.config.ts
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
import type { XyTsupConfig } from '@xylabs/ts-scripts-yarn3'
|
|
2
|
-
const config: XyTsupConfig = {
|
|
3
|
-
compile: {
|
|
4
|
-
entryMode: 'custom',
|
|
5
|
-
browser: { src: { entry: ['index-browser.ts'] } },
|
|
6
|
-
node: { src: { entry: ['index-node.ts'] } },
|
|
7
|
-
neutral: { src: { entry: ['index-shared.ts'] } },
|
|
8
|
-
},
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export default config
|