Skip to content

Commit 3e91cee

Browse files
committed
Complete migration from Gelato VRF to Chainlink VRF v2.5
- Replace all Gelato VRF dependencies with Chainlink VRF v2.5 - Update contracts to use VRFConsumerBaseV2Plus and native payment model - Remove Solmate dependencies in favor of Solady for gas optimization - Update all test files to use VRFCoordinatorV2_5Mock with proper event parsing - Fix VRF requestId extraction from RandomWordsRequested event data field - Remove all legacy Gelato VRF testing patterns and dead code - Update CI/CD workflows and documentation for new dependencies - Maintain full test coverage with 288/288 tests passing Technical changes: - VRF subscription model with native ETH funding - Proper requestId extraction from event logs using abi.decode - Consistent _fulfillVRFRequest helper pattern across all tests - Updated deployment scripts for VRF v2.5 parameters - Complete removal of Gelato and Solmate library references
1 parent b8531b9 commit 3e91cee

28 files changed

Lines changed: 232 additions & 323 deletions

.github/workflows/test.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ jobs:
2121
run: |
2222
forge install --no-git \
2323
foundry-rs/forge-std@1eea5ba \
24-
transmissions11/solmate@c93f771 \
25-
gelatodigital/vrf-contracts@fdb85db \
2624
bokkypoobah/BokkyPooBahsDateTimeLibrary@1dc26f9 \
27-
vectorized/solady@v0.0.123
25+
vectorized/solady@v0.0.123 \
26+
smartcontractkit/chainlink@v2.17.0 \
27+
OpenZeppelin/openzeppelin-contracts@v4.9.6
2828
2929
- name: Run Forge fmt
3030
run: forge fmt --check

CLAUDE.md

Lines changed: 8 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
4040
- Implement role-based access control patterns for fine-grained permissions.
4141
- Use pull over push payment patterns to mitigate reentrancy and denial of service attacks.
4242
- Implement rate limiting for sensitive functions to prevent abuse.
43-
- Use Solmate's SafeTransferLib for interacting with ERC20 tokens.
43+
- Use Solady's SafeTransferLib for interacting with ERC20 tokens.
4444
- Implement proper randomness using Chainlink VRF or similar oracle solutions.
4545
- Use assembly for gas-intensive operations, but document extensively and use with caution.
4646
- If Solady has an implementation built already, use that instead of writing assembly from scratch.
@@ -80,7 +80,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co
8080

8181
## Project Overview
8282

83-
Heavy Helms is a Solidity-based on-chain game featuring combat mechanics, NFT skins, and multiple game modes. The project uses Foundry for development and testing. Gauntlet tournaments use blockhash-based randomness for security and gas efficiency, while other modes may use Gelato VRF.
83+
Heavy Helms is a Solidity-based on-chain game featuring combat mechanics, NFT skins, and multiple game modes. The project uses Foundry for development and testing. Gauntlet tournaments use blockhash-based randomness for security and gas efficiency, while other modes use Chainlink VRF.
8484

8585
## Key Commands
8686

@@ -178,8 +178,8 @@ forge snapshot # Create gas snapshots for tests
178178

179179
1. **Randomness Systems**:
180180
- **GauntletGame**: Uses blockhash-based commit-reveal for security and gas efficiency
181-
- **Other Games**: Use Gelato VRF for on-chain randomness
182-
- Mock system available for testing (`GelatoVRFAutoMock.sol`)
181+
- **Other Games**: Use Chainlink VRF for on-chain randomness
182+
- Mock system available for testing (Chainlink VRF v2.5 mocks)
183183

184184
2. **Fighter ID Ranges**:
185185
- Default Players: 1-2000 (game owned)
@@ -392,14 +392,13 @@ All tests inherit from `TestBase.sol` which provides:
392392

393393
1. **Environment Setup**:
394394
```bash
395-
forge install --no-git foundry-rs/forge-std@1eea5ba transmissions11/solmate@c93f771 gelatodigital/vrf-contracts@fdb85db
395+
forge install --no-git foundry-rs/forge-std@1eea5ba bokkypoobah/BokkyPooBahsDateTimeLibrary@1dc26f9 vectorized/solady@v0.0.123 smartcontractkit/chainlink@v2.17.0 OpenZeppelin/openzeppelin-contracts@v4.9.6
396396
```
397397

398398
2. **Configuration** (`.env` file):
399399
```
400400
RPC_URL=<YOUR RPC URL>
401401
PK=<YOUR PRIVATE KEY>
402-
GELATO_VRF_OPERATOR=<VRF OPERATOR>
403402
```
404403

405404
3. **Deployment Order**:
@@ -422,7 +421,7 @@ All tests inherit from `TestBase.sol` which provides:
422421
## Important Notes
423422

424423
- **Via IR**: Enabled in foundry.toml for optimization
425-
- **Solmate**: Primary dependency for gas-optimized contracts
424+
- **Solady**: Primary dependency for gas-optimized contracts
426425
- **Testing**: Extensive test coverage expected, use `-vv` for debugging
427426
- **Gas Optimization**: Critical due to on-chain game nature
428427

@@ -505,7 +504,7 @@ All tests inherit from `TestBase.sol` which provides:
505504
506505
## Dependencies Management
507506
508-
- Use Solmate (transmissions11/solmate) as a primary source of gas-optimized dependencies.
507+
- Use Solady (vectorized/solady) as a primary source of gas-optimized dependencies.
509508
- Use Solady (vectorized/solady) for even more aggressive gas optimization when needed.
510509
- Ensure that any libraries used are installed with forge, and remappings are set.
511510
- Place remappings in `foundry.toml` instead of a `remappings.txt` file.
@@ -691,7 +690,7 @@ externalCall();
691690

692691
**VRF/Randomness Patterns:**
693692
- GauntletGame: Blockhash-based commit-reveal
694-
- Other Games: Gelato VRF
693+
- Other Games: Chainlink VRF
695694
- Clear phase management for multi-step processes
696695

697696
**Registry Pattern:**

README.md

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,6 @@
2929
```
3030
RPC_URL=<YOUR RPC URL> // Optional - will use forked chain for entropy during testing
3131
PK=<YOUR PRIVATE KEY> // Only needed for deployment + scripts
32-
GELATO_VRF_OPERATOR=<YOUR GELATO VRF OPERATOR> // Only needed for deployment
3332
```
3433

3534
## Usage Scripts
@@ -64,7 +63,7 @@ cd auto_battler_game_contracts
6463
2. Install dependencies:
6564

6665
```bash
67-
forge install --no-git foundry-rs/forge-std@1eea5ba transmissions11/solmate@c93f771 gelatodigital/vrf-contracts@fdb85db bokkypoobah/BokkyPooBahsDateTimeLibrary@1dc26f9 vectorized/solady@v0.0.123
66+
forge install --no-git foundry-rs/forge-std@1eea5ba bokkypoobah/BokkyPooBahsDateTimeLibrary@1dc26f9 vectorized/solady@v0.0.123 smartcontractkit/chainlink@v2.17.0 OpenZeppelin/openzeppelin-contracts@v4.9.6
6867
```
6968

7069
3. Deploy GameEngine contract _(add --broadcast to send tx)_
@@ -145,7 +144,7 @@ forge script script/deploy/PracticeGameDeploy.s.sol --sig "run(address,address,a
145144
13. Deploy DuelGame _(add --broadcast to send tx)_
146145

147146
```bash
148-
forge script script/deploy/DuelGameDeploy.s.sol --sig "run(address,address)" <GAME_ENGINE_ADDRESS> <PLAYER_CONTRACT_ADDRESS>
147+
forge script script/deploy/DuelGameDeploy.s.sol --sig "run(address,address,address,address,uint256,bytes32)" <GAME_ENGINE_ADDRESS> <PLAYER_CONTRACT_ADDRESS> <PLAYER_TICKETS_ADDRESS> <VRF_COORDINATOR> <SUBSCRIPTION_ID> <KEY_HASH>
149148
```
150149

151150
14. Deploy GauntletGame _(add --broadcast to send tx)_
@@ -154,10 +153,13 @@ forge script script/deploy/DuelGameDeploy.s.sol --sig "run(address,address)" <GA
154153
forge script script/deploy/GauntletGameDeploy.s.sol --sig "run(address,address,address)" <GAME_ENGINE_ADDRESS> <PLAYER_CONTRACT_ADDRESS> <DEFAULT_PLAYER_CONTRACT_ADDRESS>
155154
```
156155

157-
15. Setup VRF
156+
15. Setup Chainlink VRF
158157

159158
```bash
160-
Use Gelato dashboard to add VRF tasks for Player + Duel Game + Gauntlet Game contracts
159+
# 1. Create a Chainlink VRF subscription on your target network
160+
# 2. Fund the subscription with LINK tokens or native currency
161+
# 3. Add the deployed Player contract as a consumer to your subscription
162+
# 4. Note the VRF Coordinator address, subscription ID, and key hash for deployments
161163
```
162164

163165
## Architecture Changes - Contract Size Optimization

foundry.toml

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,6 @@ libs = ["lib"]
1212
# Remappings
1313
remappings = [
1414
"forge-std/=lib/forge-std/src/",
15-
"solmate/=lib/solmate/",
16-
"vrf-contracts/=lib/vrf-contracts/",
1715
"BokkyPooBahsDateTimeLibrary/=lib/BokkyPooBahsDateTimeLibrary/contracts/",
1816
"solady/=lib/solady/src/",
1917
"@chainlink/contracts/=lib/chainlink-evm/contracts/",

script/deploy/DuelGameDeploy.s.sol

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,8 @@ contract DuelGameDeployScript is Script {
3838
vm.startBroadcast(deployerPrivateKey);
3939

4040
// Deploy DuelGame
41-
DuelGame duelGame = new DuelGame(
42-
gameEngineAddr,
43-
playerAddr,
44-
vrfCoordinator,
45-
subscriptionId,
46-
keyHash,
47-
playerTicketsAddr
48-
);
41+
DuelGame duelGame =
42+
new DuelGame(gameEngineAddr, playerAddr, vrfCoordinator, subscriptionId, keyHash, playerTicketsAddr);
4943

5044
// Whitelist DuelGame in Player contract
5145
Player playerContract = Player(playerAddr);

script/game/player/CreatePlayer.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ contract CreatePlayerScript is Script {
3030
// Request player creation with 0.001 ETH fee
3131
uint256 requestId = player.requestCreatePlayer{value: 0.001 ether}(isFemale);
3232
console2.log("Player creation requested with ID:", requestId);
33-
console2.log("Waiting for VRF fulfillment by Gelato operator...");
33+
console2.log("Waiting for VRF fulfillment by Chainlink oracle...");
3434
vm.stopBroadcast();
3535
}
3636
}

src/fighters/DefaultPlayer.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ pragma solidity ^0.8.13;
1010
//==============================================================//
1111
// IMPORTS //
1212
//==============================================================//
13-
import "solmate/src/auth/Owned.sol";
13+
import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
1414
import "./Fighter.sol";
1515
import "../interfaces/fighters/IPlayer.sol";
1616
import "../interfaces/fighters/IDefaultPlayer.sol";
@@ -41,7 +41,7 @@ error InvalidDefaultPlayerSkinType(uint32 skinIndex);
4141
/// @title Default Player Contract for Heavy Helms
4242
/// @notice Manages default player characters for the game
4343
/// @dev Default players are pre-created game characters (IDs 1-2000)
44-
contract DefaultPlayer is IDefaultPlayer, Owned, Fighter {
44+
contract DefaultPlayer is IDefaultPlayer, ConfirmedOwner, Fighter {
4545
//==============================================================//
4646
// STATE VARIABLES //
4747
//==============================================================//
@@ -99,7 +99,7 @@ contract DefaultPlayer is IDefaultPlayer, Owned, Fighter {
9999
/// @param nameRegistryAddress Address of the name registry contract
100100
/// @dev Reverts with BadZeroAddress if any address is zero
101101
constructor(address skinRegistryAddress, address nameRegistryAddress)
102-
Owned(msg.sender)
102+
ConfirmedOwner(msg.sender)
103103
Fighter(skinRegistryAddress)
104104
{
105105
if (nameRegistryAddress == address(0)) {

src/fighters/Monster.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ pragma solidity ^0.8.13;
1212
//==============================================================//
1313
import "../interfaces/fighters/IMonster.sol";
1414
import "../interfaces/fighters/registries/names/IMonsterNameRegistry.sol";
15-
import "solmate/src/auth/Owned.sol";
15+
import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
1616
import "./Fighter.sol";
1717

1818
//==============================================================//
@@ -34,7 +34,7 @@ error BadZeroAddress();
3434
/// @title Monster Contract for Heavy Helms
3535
/// @notice Manages monster characters for the game
3636
/// @dev Monsters are system-controlled characters (IDs 2001-10000)
37-
contract Monster is IMonster, Owned, Fighter {
37+
contract Monster is IMonster, ConfirmedOwner, Fighter {
3838
//==============================================================//
3939
// STATE VARIABLES //
4040
//==============================================================//
@@ -111,7 +111,7 @@ contract Monster is IMonster, Owned, Fighter {
111111
/// @param nameRegistryAddress Address of the name registry contract
112112
/// @dev Reverts with BadZeroAddress if name registry address is zero
113113
constructor(address skinRegistryAddress, address nameRegistryAddress)
114-
Owned(msg.sender)
114+
ConfirmedOwner(msg.sender)
115115
Fighter(skinRegistryAddress)
116116
{
117117
if (nameRegistryAddress == address(0)) {

src/fighters/Player.sol

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,8 @@ pragma solidity ^0.8.13;
1111
// IMPORTS //
1212
//==============================================================//
1313
// External imports
14-
import "solmate/src/utils/SafeTransferLib.sol";
14+
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
15+
import {ERC20} from "solady/tokens/ERC20.sol";
1516
import {VRFConsumerBaseV2Plus} from "@chainlink/contracts/src/v0.8/vrf/dev/VRFConsumerBaseV2Plus.sol";
1617
import {VRFV2PlusClient} from "@chainlink/contracts/src/v0.8/vrf/dev/libraries/VRFV2PlusClient.sol";
1718
// Internal imports
@@ -1069,7 +1070,7 @@ contract Player is IPlayer, VRFConsumerBaseV2Plus, Fighter {
10691070
/// @param token The address of the ERC20 token to recover
10701071
/// @param amount The amount of tokens to recover
10711072
function recoverERC20(address token, uint256 amount) external onlyOwner {
1072-
SafeTransferLib.safeTransfer(ERC20(token), owner(), amount);
1073+
SafeTransferLib.safeTransfer(token, owner(), amount);
10731074
}
10741075

10751076
/// @notice Emergency function to clear pending VRF requests for an address

src/fighters/registries/names/MonsterNameRegistry.sol

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ pragma solidity ^0.8.13;
1111
// IMPORTS //
1212
//==============================================================//
1313
import "../../../interfaces/fighters/registries/names/IMonsterNameRegistry.sol";
14-
import "solmate/src/auth/Owned.sol";
14+
import {ConfirmedOwner} from "@chainlink/contracts/src/v0.8/shared/access/ConfirmedOwner.sol";
1515

1616
//==============================================================//
1717
// CUSTOM ERRORS //
@@ -32,7 +32,7 @@ error InvalidNameLength();
3232
/// @title Monster Name Registry for Heavy Helms
3333
/// @notice Manages monster name collections for the game
3434
/// @dev Stores names for monsters with index 0 reserved for nameless monsters
35-
contract MonsterNameRegistry is IMonsterNameRegistry, Owned {
35+
contract MonsterNameRegistry is IMonsterNameRegistry, ConfirmedOwner {
3636
//==============================================================//
3737
// STATE VARIABLES //
3838
//==============================================================//
@@ -78,7 +78,7 @@ contract MonsterNameRegistry is IMonsterNameRegistry, Owned {
7878
//==============================================================//
7979
/// @notice Initializes the Monster Name Registry
8080
/// @dev Creates an empty name at index 0 for nameless monsters
81-
constructor() Owned(msg.sender) {
81+
constructor() ConfirmedOwner(msg.sender) {
8282
// Add empty name at index 0 for "nameless" monsters
8383
monsterNames.push("");
8484
}

0 commit comments

Comments
 (0)