Skip to content

Commit 2866a7a

Browse files
committed
test: chainlink vrf testing with coordinator mock
1 parent b0fba7f commit 2866a7a

File tree

3 files changed

+212
-5
lines changed

3 files changed

+212
-5
lines changed

contracts/deploy/00-chainlink-rng.ts

+3-3
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
3131
const SUBSCRIPTION_ID = {
3232
[HomeChains.ARBITRUM_ONE]: "66240499937595191069677958665918759554657443303079118766000192000140992834352",
3333
[HomeChains.ARBITRUM_SEPOLIA]: "38502597312983100069991953687934627561654236680431968938019951490339399569548",
34-
[HomeChains.HARDHAT]: "0x0000000000000000000000000000000000000000000000000000000000000000",
34+
[HomeChains.HARDHAT]: "0x0000000000000000000000000000000000000000000000000000000000000001",
3535
};
3636

3737
function getKeyHash({ gasPrice }: { gasPrice: keyof (typeof KEY_HASHES)[HomeChains.ARBITRUM_ONE] }): string {
@@ -43,7 +43,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
4343

4444
const ChainlinkVRFCoordinator = await getContractOrDeploy(hre, "ChainlinkVRFCoordinator", {
4545
from: deployer,
46-
contract: "ChainlinkVRFCoordinator",
46+
contract: "ChainlinkVRFCoordinatorV2Mock",
4747
args: [],
4848
log: true,
4949
});
@@ -57,7 +57,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
5757
from: deployer,
5858
args: [
5959
deployer,
60-
deployer,
60+
deployer, // For testing only, it should be the SortitionModule
6161
ChainlinkVRFCoordinator.target,
6262
keyHash,
6363
subscriptionId,
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity 0.8.24;
3+
4+
import {IVRFCoordinatorV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
5+
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
6+
7+
/// @title A mock for testing code that relies on VRFCoordinatorV2.
8+
contract ChainlinkVRFCoordinatorV2Mock is IVRFCoordinatorV2Plus {
9+
// ************************************* //
10+
// * Storage * //
11+
// ************************************* //
12+
13+
uint256 nextRequestId = 1;
14+
mapping(uint256 requestID => VRFV2PlusClient.RandomWordsRequest request) requests;
15+
16+
// ************************************* //
17+
// * Events * //
18+
// ************************************* //
19+
20+
event RandomWordsRequested(
21+
bytes32 indexed keyHash,
22+
uint256 requestId,
23+
uint256 indexed subId,
24+
uint16 minimumRequestConfirmations,
25+
uint32 callbackGasLimit,
26+
uint32 numWords,
27+
address indexed sender
28+
);
29+
event RandomWordsFulfilled(uint256 indexed requestId, bool success);
30+
31+
// ************************************* //
32+
// * Function Modifiers * //
33+
// ************************************* //
34+
35+
function fulfillRandomWords(uint256 _requestId, address _consumer, uint256[] memory _words) public {
36+
if (requests[_requestId].subId == 0) {
37+
revert("nonexistent request");
38+
}
39+
VRFV2PlusClient.RandomWordsRequest memory req = requests[_requestId];
40+
41+
if (_words.length == 0) {
42+
_words = new uint256[](req.numWords);
43+
for (uint256 i = 0; i < req.numWords; i++) {
44+
_words[i] = uint256(keccak256(abi.encode(_requestId, i)));
45+
}
46+
} else if (_words.length != req.numWords) {
47+
revert InvalidRandomWords();
48+
}
49+
50+
bytes4 FULFILL_RANDOM_WORDS_SELECTOR = bytes4(keccak256("rawFulfillRandomWords(uint256,uint256[])"));
51+
bytes memory callReq = abi.encodeWithSelector(FULFILL_RANDOM_WORDS_SELECTOR, _requestId, _words);
52+
(bool success, ) = _consumer.call{gas: req.callbackGasLimit}(callReq);
53+
54+
delete (requests[_requestId]);
55+
emit RandomWordsFulfilled(_requestId, success);
56+
}
57+
58+
function requestRandomWords(VRFV2PlusClient.RandomWordsRequest calldata req) external returns (uint256) {
59+
uint256 requestId = nextRequestId++;
60+
61+
requests[requestId] = req;
62+
63+
emit RandomWordsRequested(
64+
req.keyHash,
65+
requestId,
66+
req.subId,
67+
req.requestConfirmations,
68+
req.callbackGasLimit,
69+
req.numWords,
70+
msg.sender
71+
);
72+
return requestId;
73+
}
74+
75+
// ************************************* //
76+
// * Public Views * //
77+
// ************************************* //
78+
79+
function fundSubscription(uint256, uint96) public pure {
80+
revert("not implemented");
81+
}
82+
83+
function createSubscription() external pure returns (uint256) {
84+
revert("not implemented");
85+
}
86+
87+
function fundSubscriptionWithNative(uint256) external payable {
88+
revert("not implemented");
89+
}
90+
91+
function getActiveSubscriptionIds(uint256, uint256) external pure returns (uint256[] memory) {
92+
revert("not implemented");
93+
}
94+
95+
function getSubscription(uint256) external pure returns (uint96, uint96, uint64, address, address[] memory) {
96+
revert("not implemented");
97+
}
98+
99+
function cancelSubscription(uint256, address) external pure {
100+
revert("not implemented");
101+
}
102+
103+
function addConsumer(uint256, address) external pure {
104+
revert("not implemented");
105+
}
106+
107+
function removeConsumer(uint256, address) external pure {
108+
revert("not implemented");
109+
}
110+
111+
function getRequestConfig() external pure returns (uint16, uint32, bytes32[] memory) {
112+
return (3, 2000000, new bytes32[](0));
113+
}
114+
115+
function getConfig()
116+
external
117+
pure
118+
returns (
119+
uint16 minimumRequestConfirmations,
120+
uint32 maxGasLimit,
121+
uint32 stalenessSeconds,
122+
uint32 gasAfterPaymentCalculation
123+
)
124+
{
125+
return (4, 2_500_000, 2_700, 33285);
126+
}
127+
128+
function getFeeConfig()
129+
external
130+
pure
131+
returns (
132+
uint32 fulfillmentFlatFeeLinkPPMTier1,
133+
uint32 fulfillmentFlatFeeLinkPPMTier2,
134+
uint32 fulfillmentFlatFeeLinkPPMTier3,
135+
uint32 fulfillmentFlatFeeLinkPPMTier4,
136+
uint32 fulfillmentFlatFeeLinkPPMTier5,
137+
uint24 reqsForTier2,
138+
uint24 reqsForTier3,
139+
uint24 reqsForTier4,
140+
uint24 reqsForTier5
141+
)
142+
{
143+
return (
144+
100000, // 0.1 LINK
145+
100000, // 0.1 LINK
146+
100000, // 0.1 LINK
147+
100000, // 0.1 LINK
148+
100000, // 0.1 LINK
149+
0,
150+
0,
151+
0,
152+
0
153+
);
154+
}
155+
156+
function getFallbackWeiPerUnitLink() external pure returns (int256) {
157+
return 4000000000000000; // 0.004 Ether
158+
}
159+
160+
function requestSubscriptionOwnerTransfer(uint256, address) external pure {
161+
revert("not implemented");
162+
}
163+
164+
function acceptSubscriptionOwnerTransfer(uint256) external pure {
165+
revert("not implemented");
166+
}
167+
168+
function pendingRequestExists(uint256) public pure returns (bool) {
169+
revert("not implemented");
170+
}
171+
172+
// ************************************* //
173+
// * Errors * //
174+
// ************************************* //
175+
176+
error InvalidRandomWords();
177+
}

contracts/test/rng/index.ts

+32-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { expect } from "chai";
2-
import { ethers, network } from "hardhat";
3-
import { IncrementalNG, BlockHashRNG } from "../../typechain-types";
2+
import { deployments, ethers, network } from "hardhat";
3+
import { IncrementalNG, BlockHashRNG, ChainlinkRNG, ChainlinkVRFCoordinatorV2Mock } from "../../typechain-types";
44

55
const initialNg = 424242;
66
const abiCoder = ethers.AbiCoder.defaultAbiCoder();
@@ -46,3 +46,33 @@ describe("BlockHashRNG", async () => {
4646
expect(rn).to.equal(0);
4747
});
4848
});
49+
50+
describe("ChainlinkRNG", async () => {
51+
let rng: ChainlinkRNG;
52+
let vrfCoordinator: ChainlinkVRFCoordinatorV2Mock;
53+
54+
beforeEach("Setup", async () => {
55+
await deployments.fixture(["ChainlinkRNG"], {
56+
fallbackToGlobal: true,
57+
keepExistingDeployments: false,
58+
});
59+
rng = (await ethers.getContract("ChainlinkRNG")) as ChainlinkRNG;
60+
vrfCoordinator = (await ethers.getContract("ChainlinkVRFCoordinator")) as ChainlinkVRFCoordinatorV2Mock;
61+
});
62+
63+
it("Should return a non-zero random number", async () => {
64+
const requestId = 1;
65+
const expectedRn = BigInt(
66+
ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(["uint256", "uint256"], [requestId, 0]))
67+
);
68+
69+
let tx = await rng.requestRandomness(0);
70+
await expect(tx).to.emit(rng, "RequestSent").withArgs(requestId);
71+
72+
tx = await vrfCoordinator.fulfillRandomWords(requestId, rng.target, []);
73+
await expect(tx).to.emit(rng, "RequestFulfilled").withArgs(requestId, expectedRn);
74+
75+
const rn = await rng.receiveRandomness(0);
76+
expect(rn).to.equal(expectedRn);
77+
});
78+
});

0 commit comments

Comments
 (0)