Skip to content

Commit 85b87a2

Browse files
committed
chore: working on e2e scripts
1 parent 480257f commit 85b87a2

File tree

13 files changed

+664
-119
lines changed

13 files changed

+664
-119
lines changed

.gitignore

+1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Logs
22
yarn-debug.log*
33
yarn-error.log*
4+
node.log
45

56
# Dependency directories
67
node_modules/

packages/horizon/hardhat.config.ts

-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import { join } from 'path'
66
import '@nomicfoundation/hardhat-foundry'
77
import '@nomicfoundation/hardhat-toolbox'
88
import '@nomicfoundation/hardhat-ignition-ethers'
9-
import 'hardhat-storage-layout'
109
import 'hardhat-contract-sizer'
1110
import 'hardhat-secure-accounts'
1211
import { HardhatUserConfig } from 'hardhat/types'

packages/horizon/scripts/e2e

+79
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
#!/bin/bash
2+
3+
set -eo pipefail
4+
5+
# Check required env variables
6+
if [ -z "$ARBITRUM_SEPOLIA_RPC" ]; then
7+
echo "ARBITRUM_SEPOLIA_RPC environment variable is required"
8+
exit 1
9+
fi
10+
11+
if [ -z "$DEPLOYER_PRIVATE_KEY" ]; then
12+
echo "DEPLOYER_PRIVATE_KEY environment variable is required"
13+
exit 1
14+
fi
15+
16+
if [ -z "$GOVERNOR_PRIVATE_KEY" ]; then
17+
echo "GOVERNOR_PRIVATE_KEY environment variable is required"
18+
exit 1
19+
fi
20+
21+
echo "Starting e2e tests..."
22+
23+
# Check if port 8545 is in use
24+
if lsof -i:8545 > /dev/null 2>&1; then
25+
echo "Error: Port 8545 is already in use"
26+
exit 1
27+
fi
28+
29+
# Start local hardhat node forked from Arbitrum Sepolia
30+
echo "Starting local hardhat node..."
31+
npx hardhat node --fork $ARBITRUM_SEPOLIA_RPC > node.log 2>&1 &
32+
NODE_PID=$!
33+
34+
# Wait for node to start
35+
sleep 10
36+
37+
# Run migration steps
38+
echo "Running migration steps..."
39+
40+
# Step 1 - Deployer
41+
echo "Step 1 - Deployer"
42+
npx hardhat deploy:migrate --network localhost --step 1
43+
44+
# Step 2 - Governor
45+
echo "Step 2 - Governor"
46+
npx hardhat deploy:migrate --network localhost --step 2 --patch-config
47+
48+
# Step 3 - Deployer
49+
echo "Step 3 - Deployer"
50+
npx hardhat deploy:migrate --network localhost --step 3 --patch-config
51+
52+
# Step 4 - Governor
53+
echo "Step 4 - Governor"
54+
npx hardhat deploy:migrate --network localhost --step 4 --patch-config
55+
56+
# Run integration tests - During transition period
57+
echo "Running during-transition-period tests..."
58+
npx hardhat test:integration --phase during-transition --network localhost
59+
60+
# Clear thawing period
61+
echo "Clearing thawing period..."
62+
npx hardhat transition:clear-thawing
63+
64+
# Run integration tests - After transition period
65+
echo "Running after-transition-period tests..."
66+
npx hardhat test:integration --phase after-transition --network localhost
67+
68+
# Enable delegation slashing
69+
echo "Enabling delegation slashing..."
70+
npx hardhat transition:enable-delegation-slashing
71+
72+
# Run integration tests - After delegation slashing enabled
73+
echo "Running after-delegation-slashing tests..."
74+
npx hardhat test:integration --phase after-delegation-slashing --network localhost
75+
76+
# Cleanup
77+
kill $NODE_PID
78+
79+
echo "E2E tests completed successfully!"

packages/horizon/tasks/e2e.ts

+22-23
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,29 @@ import { task } from 'hardhat/config'
33
import { glob } from 'glob'
44

55
task('test:integration', 'Runs all integration tests')
6-
.addOptionalParam('deployType', 'Chose between deploy or migrate. If not specified, skips deployment.')
6+
.addParam(
7+
'phase',
8+
'Test phase to run: "during-transition", "after-transition", "after-delegation-slashing"',
9+
)
710
.setAction(async (taskArgs, hre) => {
8-
// Require hardhat or localhost network
9-
if (hre.network.name !== 'hardhat' && hre.network.name !== 'localhost') {
10-
throw new Error('Integration tests can only be run on the hardhat or localhost network')
11-
}
11+
// Get test files for each phase
12+
const duringTransitionPeriodFiles = await glob('test/integration/during-transition-period/**/*.{js,ts}')
13+
const afterTransitionPeriodFiles = await glob('test/integration/after-transition-period/**/*.{js,ts}')
14+
const afterDelegationSlashingEnabledFiles = await glob('test/integration/after-delegation-slashing-enabled/**/*.{js,ts}')
1215

13-
// Handle deployment if mode is specified
14-
if (taskArgs.deployType) {
15-
switch (taskArgs.deployType.toLowerCase()) {
16-
case 'deploy':
17-
await hre.run('deploy:protocol')
18-
break
19-
default:
20-
throw new Error('Invalid mode. Must be either deploy or migrate')
21-
}
16+
switch (taskArgs.phase) {
17+
case 'during-transition':
18+
await hre.run(TASK_TEST, { testFiles: duringTransitionPeriodFiles })
19+
break
20+
case 'after-transition':
21+
await hre.run(TASK_TEST, { testFiles: afterTransitionPeriodFiles })
22+
break
23+
case 'after-delegation-slashing':
24+
await hre.run(TASK_TEST, { testFiles: afterDelegationSlashingEnabledFiles })
25+
break
26+
default:
27+
throw new Error(
28+
'Invalid phase. Must be "during-transition", "after-transition", "after-delegation-slashing", or "all"',
29+
)
2230
}
23-
24-
const testFiles = await glob('test/integration/**/*.{js,ts}')
25-
26-
// Initialize graph config if not exists
27-
hre.config.graph = hre.config.graph || {}
28-
hre.config.graph.deployments = hre.config.graph.deployments || {}
29-
hre.config.graph.deployments.horizon = './addresses-local.json'
30-
31-
await hre.run(TASK_TEST, { testFiles: testFiles })
3231
})

packages/horizon/tasks/transitions.ts

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { task } from 'hardhat/config'
2+
3+
task('transition:clear-thawing', 'Clears the thawing period in HorizonStaking')
4+
.setAction(async (_, hre) => {
5+
const [governor] = await hre.ethers.getSigners()
6+
const horizonStaking = hre.graph().horizon!.contracts.HorizonStaking
7+
8+
console.log('Clearing thawing period...')
9+
const tx = await horizonStaking.connect(governor).clearThawingPeriod()
10+
await tx.wait()
11+
console.log('Thawing period cleared')
12+
})
13+
14+
task('transition:enable-delegation-slashing', 'Enables delegation slashing in HorizonStaking')
15+
.setAction(async (_, hre) => {
16+
const [governor] = await hre.ethers.getSigners()
17+
const horizonStaking = hre.graph().horizon!.contracts.HorizonStaking
18+
19+
console.log('Enabling delegation slashing...')
20+
const tx = await horizonStaking.connect(governor).setDelegationSlashingEnabled()
21+
await tx.wait()
22+
console.log('Delegation slashing enabled')
23+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,159 @@
1+
import hre from 'hardhat'
2+
import { ethers } from 'hardhat'
3+
import { expect } from 'chai'
4+
import { HorizonStaking, IGraphToken } from '../../../typechain-types'
5+
import { SignerWithAddress } from '@nomicfoundation/hardhat-ethers/signers'
6+
7+
import {
8+
addToDelegationPool,
9+
createProvision,
10+
delegate,
11+
slash,
12+
stake,
13+
} from '../shared/staking'
14+
15+
describe('Add to delegation pool', () => {
16+
let horizonStaking: HorizonStaking
17+
let graphToken: IGraphToken
18+
let serviceProvider: SignerWithAddress
19+
let delegator: SignerWithAddress
20+
let signer: SignerWithAddress
21+
let verifier: string
22+
23+
const maxVerifierCut = 1000000
24+
const thawingPeriod = 2419200 // 28 days
25+
const tokens = ethers.parseEther('100000')
26+
const delegationTokens = ethers.parseEther('1000')
27+
28+
before(async () => {
29+
const graph = hre.graph()
30+
31+
horizonStaking = graph.horizon!.contracts.HorizonStaking
32+
graphToken = graph.horizon!.contracts.L2GraphToken as unknown as IGraphToken
33+
34+
[serviceProvider, delegator, signer] = await ethers.getSigners()
35+
verifier = await ethers.Wallet.createRandom().getAddress()
36+
37+
// Service provider stake
38+
await stake({
39+
horizonStaking,
40+
graphToken,
41+
serviceProvider,
42+
tokens,
43+
})
44+
45+
// Create provision
46+
const provisionTokens = ethers.parseEther('1000')
47+
await createProvision({
48+
horizonStaking,
49+
serviceProvider,
50+
verifier,
51+
tokens: provisionTokens,
52+
maxVerifierCut,
53+
thawingPeriod,
54+
})
55+
56+
// Send funds to delegator and signer
57+
await graphToken.connect(serviceProvider).transfer(delegator.address, tokens)
58+
await graphToken.connect(serviceProvider).transfer(signer.address, tokens)
59+
60+
// Initialize delegation pool
61+
await delegate({
62+
horizonStaking,
63+
graphToken,
64+
delegator,
65+
serviceProvider,
66+
verifier,
67+
tokens: delegationTokens,
68+
minSharesOut: 0n,
69+
})
70+
})
71+
72+
it('should recover delegation pool from invalid state by adding tokens', async () => {
73+
// Setup a new verifier
74+
const newVerifier = ethers.Wallet.createRandom().connect(ethers.provider)
75+
// Send eth to new verifier to cover gas fees
76+
await serviceProvider.sendTransaction({
77+
to: newVerifier.address,
78+
value: ethers.parseEther('0.1'),
79+
})
80+
81+
// Create a provision for the new verifier
82+
const newVerifierProvisionTokens = ethers.parseEther('1000')
83+
await createProvision({
84+
horizonStaking,
85+
serviceProvider,
86+
verifier: newVerifier.address,
87+
tokens: newVerifierProvisionTokens,
88+
maxVerifierCut,
89+
thawingPeriod,
90+
})
91+
92+
// Initialize delegation pool
93+
const initialDelegation = ethers.parseEther('1000')
94+
await delegate({
95+
horizonStaking,
96+
graphToken,
97+
delegator,
98+
serviceProvider,
99+
verifier: newVerifier.address,
100+
tokens: initialDelegation,
101+
minSharesOut: 0n,
102+
})
103+
104+
const poolBefore = await horizonStaking.getDelegationPool(serviceProvider.address, newVerifier.address)
105+
106+
// Slash entire provision (service provider tokens + delegation pool tokens)
107+
const slashTokens = newVerifierProvisionTokens + initialDelegation
108+
const tokensVerifier = newVerifierProvisionTokens / 2n
109+
await slash({
110+
horizonStaking,
111+
verifier: newVerifier,
112+
serviceProvider,
113+
tokens: slashTokens,
114+
tokensVerifier,
115+
verifierDestination: newVerifier.address,
116+
})
117+
118+
// Delegating should revert since pool.tokens == 0 and pool.shares != 0
119+
const delegateTokens = ethers.parseEther('500')
120+
await expect(
121+
delegate({
122+
horizonStaking,
123+
graphToken,
124+
delegator,
125+
serviceProvider,
126+
verifier: newVerifier.address,
127+
tokens: delegateTokens,
128+
minSharesOut: 0n,
129+
}),
130+
).to.be.revertedWithCustomError(horizonStaking, 'HorizonStakingInvalidDelegationPoolState')
131+
132+
// Add tokens to the delegation pool to recover the pool
133+
const recoverPoolTokens = ethers.parseEther('500')
134+
await addToDelegationPool({
135+
horizonStaking,
136+
graphToken,
137+
signer,
138+
serviceProvider,
139+
verifier: newVerifier.address,
140+
tokens: recoverPoolTokens,
141+
})
142+
143+
// Verify delegation pool is recovered
144+
const poolAfter = await horizonStaking.getDelegationPool(serviceProvider.address, newVerifier.address)
145+
expect(poolAfter.tokens).to.equal(recoverPoolTokens, 'Pool tokens should be recovered')
146+
expect(poolAfter.shares).to.equal(poolBefore.shares, 'Pool shares should remain the same')
147+
148+
// Delegation should now succeed
149+
await delegate({
150+
horizonStaking,
151+
graphToken,
152+
delegator,
153+
serviceProvider,
154+
verifier: newVerifier.address,
155+
tokens: delegateTokens,
156+
minSharesOut: 0n,
157+
})
158+
})
159+
})

0 commit comments

Comments
 (0)