Skip to content

Commit 83c4367

Browse files
committed
test: added tests for concurrent requests for both chainlink and randomizer
1 parent 03749f3 commit 83c4367

File tree

7 files changed

+130
-21
lines changed

7 files changed

+130
-21
lines changed

contracts/deploy/00-chainlink-rng.ts

+4
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ const deployRng: DeployFunction = async (hre: HardhatRuntimeEnvironment) => {
3535
};
3636

3737
function getKeyHash({ gasPrice }: { gasPrice: keyof (typeof KEY_HASHES)[HomeChains.ARBITRUM_ONE] }): string {
38+
const validGasPrices = Object.keys(KEY_HASHES[HomeChains.ARBITRUM_ONE]).map(Number);
39+
if (!validGasPrices.includes(gasPrice)) {
40+
throw new Error(`Invalid gas price ${gasPrice}. Valid values are: ${validGasPrices.join(", ")}`);
41+
}
3842
if (chainId == HomeChains.HARDHAT) return KEY_HASHES[chainId][0];
3943
if (chainId == HomeChains.ARBITRUM_ONE) return KEY_HASHES[chainId][gasPrice];
4044
if (chainId == HomeChains.ARBITRUM_SEPOLIA) return KEY_HASHES[chainId][150];

contracts/src/rng/mock/ChainlinkVRFCoordinatorMock.sol

+2-2
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ contract ChainlinkVRFCoordinatorV2Mock is IVRFCoordinatorV2Plus {
3333
// ************************************* //
3434

3535
function fulfillRandomWords(uint256 _requestId, address _consumer, uint256[] memory _words) public {
36+
if (_consumer == address(0)) revert("zero address consumer");
3637
if (requests[_requestId].subId == 0) {
3738
revert("nonexistent request");
3839
}
@@ -49,9 +50,8 @@ contract ChainlinkVRFCoordinatorV2Mock is IVRFCoordinatorV2Plus {
4950

5051
bytes4 FULFILL_RANDOM_WORDS_SELECTOR = bytes4(keccak256("rawFulfillRandomWords(uint256,uint256[])"));
5152
bytes memory callReq = abi.encodeWithSelector(FULFILL_RANDOM_WORDS_SELECTOR, _requestId, _words);
52-
(bool success, ) = _consumer.call{gas: req.callbackGasLimit}(callReq);
53-
5453
delete (requests[_requestId]);
54+
(bool success, ) = _consumer.call{gas: req.callbackGasLimit}(callReq);
5555
emit RandomWordsFulfilled(_requestId, success);
5656
}
5757

contracts/src/rng/mock/RandomizerMock.sol

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ pragma solidity 0.8.24;
55
import "../RandomizerRNG.sol";
66

77
contract RandomizerMock is IRandomizer {
8-
uint256 private id;
8+
uint256 private id = 1;
99

1010
function request(uint256 callbackGasLimit) external override returns (uint256) {
1111
return id++;

contracts/test/arbitration/staking-neo.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ import {
77
KlerosCoreNeo,
88
TestERC721,
99
DisputeResolver,
10+
ChainlinkRNG,
11+
ChainlinkVRFCoordinatorV2Mock,
1012
} from "../../typechain-types";
1113
import { expect } from "chai";
1214
import { HardhatEthersSigner } from "@nomicfoundation/hardhat-ethers/signers";
@@ -41,8 +43,8 @@ describe("Staking", async () => {
4143
let pnk: PNK;
4244
let core: KlerosCoreNeo;
4345
let sortition: SortitionModuleNeo;
44-
let rng: RandomizerRNG;
45-
let randomizer: RandomizerMock;
46+
let rng: ChainlinkRNG;
47+
let vrfCoordinator: ChainlinkVRFCoordinatorV2Mock;
4648
let nft: TestERC721;
4749
let resolver: DisputeResolver;
4850
let balanceBefore: bigint;
@@ -57,8 +59,8 @@ describe("Staking", async () => {
5759
pnk = (await ethers.getContract("PNK")) as PNK;
5860
core = (await ethers.getContract("KlerosCoreNeo")) as KlerosCoreNeo;
5961
sortition = (await ethers.getContract("SortitionModuleNeo")) as SortitionModuleNeo;
60-
rng = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
61-
randomizer = (await ethers.getContract("RandomizerOracle")) as RandomizerMock;
62+
rng = (await ethers.getContract("ChainlinkRNG")) as ChainlinkRNG;
63+
vrfCoordinator = (await ethers.getContract("ChainlinkVRFCoordinator")) as ChainlinkVRFCoordinatorV2Mock;
6264
resolver = (await ethers.getContract("DisputeResolverNeo")) as DisputeResolver;
6365
nft = (await ethers.getContract("KlerosV2NeoEarlyUser")) as TestERC721;
6466

@@ -108,7 +110,7 @@ describe("Staking", async () => {
108110
await network.provider.send("evm_mine");
109111
}
110112

111-
await randomizer.relay(rng.target, 0, ethers.randomBytes(32));
113+
await vrfCoordinator.fulfillRandomWords(1, rng.target, []);
112114
await sortition.passPhase(); // Generating -> Drawing
113115
expect(await sortition.phase()).to.be.equal(2); // Drawing
114116

contracts/test/arbitration/staking.ts

+7-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { ethers, getNamedAccounts, network, deployments } from "hardhat";
2-
import { PNK, KlerosCore, SortitionModule, RandomizerRNG, RandomizerMock } from "../../typechain-types";
2+
import { PNK, KlerosCore, SortitionModule, ChainlinkRNG, ChainlinkVRFCoordinatorV2Mock } from "../../typechain-types";
33
import { expect } from "chai";
44

55
/* eslint-disable no-unused-vars */
@@ -18,8 +18,8 @@ describe("Staking", async () => {
1818
let pnk: PNK;
1919
let core: KlerosCore;
2020
let sortition: SortitionModule;
21-
let rng: RandomizerRNG;
22-
let randomizer: RandomizerMock;
21+
let rng: ChainlinkRNG;
22+
let vrfCoordinator: ChainlinkVRFCoordinatorV2Mock;
2323

2424
const deploy = async () => {
2525
({ deployer } = await getNamedAccounts());
@@ -30,8 +30,8 @@ describe("Staking", async () => {
3030
pnk = (await ethers.getContract("PNK")) as PNK;
3131
core = (await ethers.getContract("KlerosCore")) as KlerosCore;
3232
sortition = (await ethers.getContract("SortitionModule")) as SortitionModule;
33-
rng = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
34-
randomizer = (await ethers.getContract("RandomizerOracle")) as RandomizerMock;
33+
rng = (await ethers.getContract("ChainlinkRNG")) as ChainlinkRNG;
34+
vrfCoordinator = (await ethers.getContract("ChainlinkVRFCoordinator")) as ChainlinkVRFCoordinatorV2Mock;
3535
};
3636

3737
describe("When outside the Staking phase", async () => {
@@ -63,7 +63,7 @@ describe("Staking", async () => {
6363
};
6464

6565
const reachStakingPhaseAfterDrawing = async () => {
66-
await randomizer.relay(rng.target, 0, ethers.randomBytes(32));
66+
await vrfCoordinator.fulfillRandomWords(1, rng.target, []);
6767
await sortition.passPhase(); // Generating -> Drawing
6868
await core.draw(0, 5000);
6969
await sortition.passPhase(); // Drawing -> Staking
@@ -404,7 +404,7 @@ describe("Staking", async () => {
404404
for (let index = 0; index < lookahead; index++) {
405405
await network.provider.send("evm_mine");
406406
}
407-
await randomizer.relay(rng.target, 0, ethers.randomBytes(32));
407+
await vrfCoordinator.fulfillRandomWords(1, rng.target, []);
408408
await sortition.passPhase(); // Generating -> Drawing
409409

410410
await core.draw(0, 5000);

contracts/test/integration/index.ts

+7-5
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,8 @@ import {
1212
RandomizerRNG,
1313
RandomizerMock,
1414
SortitionModule,
15+
ChainlinkRNG,
16+
ChainlinkVRFCoordinatorV2Mock,
1517
} from "../../typechain-types";
1618

1719
/* eslint-disable no-unused-vars */
@@ -39,8 +41,8 @@ describe("Integration tests", async () => {
3941
}
4042

4143
let deployer: string;
42-
let rng: RandomizerRNG;
43-
let randomizer: RandomizerMock;
44+
let rng: ChainlinkRNG;
45+
let vrfCoordinator: ChainlinkVRFCoordinatorV2Mock;
4446
let disputeKit: DisputeKitClassic;
4547
let pnk: PNK;
4648
let core: KlerosCore;
@@ -56,8 +58,8 @@ describe("Integration tests", async () => {
5658
fallbackToGlobal: true,
5759
keepExistingDeployments: false,
5860
});
59-
rng = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
60-
randomizer = (await ethers.getContract("RandomizerOracle")) as RandomizerMock;
61+
rng = (await ethers.getContract("ChainlinkRNG")) as ChainlinkRNG;
62+
vrfCoordinator = (await ethers.getContract("ChainlinkVRFCoordinator")) as ChainlinkVRFCoordinatorV2Mock;
6163
disputeKit = (await ethers.getContract("DisputeKitClassic")) as DisputeKitClassic;
6264
pnk = (await ethers.getContract("PNK")) as PNK;
6365
core = (await ethers.getContract("KlerosCore")) as KlerosCore;
@@ -162,7 +164,7 @@ describe("Integration tests", async () => {
162164
await mineBlocks(ethers.getNumber(await sortitionModule.rngLookahead())); // Wait for finality
163165
expect(await sortitionModule.phase()).to.equal(Phase.generating);
164166
console.log("KC phase: %d", await sortitionModule.phase());
165-
await randomizer.relay(rng.target, 0, ethers.randomBytes(32));
167+
await vrfCoordinator.fulfillRandomWords(1, rng.target, []);
166168
await sortitionModule.passPhase(); // Generating -> Drawing
167169
expect(await sortitionModule.phase()).to.equal(Phase.drawing);
168170
console.log("KC phase: %d", await sortitionModule.phase());

contracts/test/rng/index.ts

+102-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
import { expect } from "chai";
22
import { deployments, ethers, network } from "hardhat";
3-
import { IncrementalNG, BlockHashRNG, ChainlinkRNG, ChainlinkVRFCoordinatorV2Mock } from "../../typechain-types";
3+
import {
4+
IncrementalNG,
5+
BlockHashRNG,
6+
ChainlinkRNG,
7+
ChainlinkVRFCoordinatorV2Mock,
8+
RandomizerRNG,
9+
RandomizerMock,
10+
} from "../../typechain-types";
411

512
const initialNg = 424242;
613
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
@@ -75,4 +82,98 @@ describe("ChainlinkRNG", async () => {
7582
const rn = await rng.receiveRandomness(0);
7683
expect(rn).to.equal(expectedRn);
7784
});
85+
86+
it("Should return only the last random number when multiple requests are made", async () => {
87+
// First request
88+
let tx = await rng.requestRandomness(0);
89+
const requestId1 = 1;
90+
await expect(tx).to.emit(rng, "RequestSent").withArgs(requestId1);
91+
92+
// Second request
93+
tx = await rng.requestRandomness(0);
94+
const requestId2 = 2;
95+
await expect(tx).to.emit(rng, "RequestSent").withArgs(requestId2);
96+
97+
// Generate expected random numbers
98+
const expectedRn1 = BigInt(
99+
ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256"], [requestId1, 0]))
100+
);
101+
const expectedRn2 = BigInt(
102+
ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256"], [requestId2, 0]))
103+
);
104+
expect(expectedRn1).to.not.equal(expectedRn2, "Random numbers should be different");
105+
106+
// Fulfill first request
107+
tx = await vrfCoordinator.fulfillRandomWords(requestId1, rng.target, []);
108+
await expect(tx).to.emit(rng, "RequestFulfilled").withArgs(requestId1, expectedRn1);
109+
110+
// Fulfill second request
111+
tx = await vrfCoordinator.fulfillRandomWords(requestId2, rng.target, []);
112+
await expect(tx).to.emit(rng, "RequestFulfilled").withArgs(requestId2, expectedRn2);
113+
114+
// Should return only the last random number
115+
const rn = await rng.receiveRandomness(0);
116+
expect(rn).to.equal(expectedRn2);
117+
});
118+
});
119+
120+
describe("RandomizerRNG", async () => {
121+
let rng: RandomizerRNG;
122+
let randomizer: RandomizerMock;
123+
124+
beforeEach("Setup", async () => {
125+
await deployments.fixture(["RandomizerRNG"], {
126+
fallbackToGlobal: true,
127+
keepExistingDeployments: false,
128+
});
129+
rng = (await ethers.getContract("RandomizerRNG")) as RandomizerRNG;
130+
randomizer = (await ethers.getContract("RandomizerOracle")) as RandomizerMock;
131+
});
132+
133+
it("Should return a non-zero random number", async () => {
134+
const randomBytes = ethers.randomBytes(32);
135+
const expectedRn = BigInt(ethers.hexlify(randomBytes));
136+
const requestId = 1;
137+
138+
let tx = await rng.requestRandomness(0);
139+
await expect(tx).to.emit(rng, "RequestSent").withArgs(requestId);
140+
141+
tx = await randomizer.relay(rng.target, requestId, randomBytes);
142+
await expect(tx).to.emit(rng, "RequestFulfilled").withArgs(requestId, expectedRn);
143+
144+
const rn = await rng.receiveRandomness(0);
145+
expect(rn).to.equal(expectedRn);
146+
});
147+
148+
it("Should return only the last random number when multiple requests are made", async () => {
149+
// First request
150+
let tx = await rng.requestRandomness(0);
151+
const requestId1 = 1;
152+
await expect(tx).to.emit(rng, "RequestSent").withArgs(requestId1);
153+
154+
// Second request
155+
tx = await rng.requestRandomness(0);
156+
const requestId2 = 2;
157+
await expect(tx).to.emit(rng, "RequestSent").withArgs(requestId2);
158+
159+
// Generate random bytes and expected numbers for both requests
160+
const randomBytes1 = ethers.randomBytes(32);
161+
const randomBytes2 = ethers.randomBytes(32);
162+
const expectedRn1 = BigInt(ethers.hexlify(randomBytes1));
163+
const expectedRn2 = BigInt(ethers.hexlify(randomBytes2));
164+
165+
expect(expectedRn1).to.not.equal(expectedRn2, "Random numbers should be different");
166+
167+
// Fulfill first request
168+
tx = await randomizer.relay(rng.target, requestId1, randomBytes1);
169+
await expect(tx).to.emit(rng, "RequestFulfilled").withArgs(requestId1, expectedRn1);
170+
171+
// Fulfill second request
172+
tx = await randomizer.relay(rng.target, requestId2, randomBytes2);
173+
await expect(tx).to.emit(rng, "RequestFulfilled").withArgs(requestId2, expectedRn2);
174+
175+
// Should return only the last random number
176+
const rn = await rng.receiveRandomness(0);
177+
expect(rn).to.equal(expectedRn2);
178+
});
78179
});

0 commit comments

Comments
 (0)