Skip to content

Commit 6153e6d

Browse files
committed
initial commit
0 parents  commit 6153e6d

11 files changed

+49324
-0
lines changed

contracts/Raffle.sol

+172
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
// raffle contract
2+
// entering lottery by paying
3+
// pick a true random winner
4+
// winner to be selected every x minutes --> comp automated
5+
// chainlink oracle --> randomness, automated execution (chainlink keepers)
6+
7+
// SPDX-License-Identifier: MIT
8+
9+
pragma solidity ^0.8.7;
10+
11+
import "@chainlink/contracts/src/v0.8/VRFConsumerBaseV2.sol";
12+
import "@chainlink/contracts/src/v0.8/interfaces/VRFCoordinatorV2Interface.sol";
13+
import "@chainlink/contracts/src/v0.8/AutomationCompatible.sol";
14+
15+
error Raffle__NotEnoughETH();
16+
error Raffle__TransferFailed();
17+
error Raffle__NotOpen();
18+
error Raffle__UpkeepNotNeeded(uint256 currentBalance, uint256 numPlayers, uint256 raffleState);
19+
20+
/** @title A sample Raffle contract
21+
* @author Micle Platon
22+
* @notice This contract is for creating an untamperable decentralized smart contract
23+
* @dev This implements Chainlin VRF V2 and Chainlink KEEPERS
24+
*/
25+
26+
contract Raffle is VRFConsumerBaseV2, AutomationCompatibleInterface {
27+
/* Type declarations */
28+
enum RaffleState {
29+
OPEN,
30+
CALCULATING
31+
} // uint256 0 = OPEN, 1 = CALCULATING
32+
33+
/* State variables */
34+
VRFCoordinatorV2Interface private immutable i_vrfCoordinator;
35+
uint64 private immutable i_subscriptionId;
36+
bytes32 private immutable i_gasLane;
37+
uint32 private immutable i_callbackGasLimit;
38+
uint16 private constant REQUEST_CONFIRMATIONS = 3;
39+
uint32 private constant NUM_WORDS = 1;
40+
41+
// Lottery variables
42+
uint256 private immutable i_interval;
43+
uint256 private immutable i_entranceFee;
44+
uint256 private s_lastTimeStamp;
45+
address private s_recentWinner;
46+
address payable[] private s_players;
47+
RaffleState private s_raffleState;
48+
49+
/* Events */
50+
event RequestedRaffleWinner(uint256 indexed requestId);
51+
event RaffleEnter(address indexed player);
52+
event WinnerPicked(address indexed winner);
53+
54+
/* Functions */
55+
constructor(
56+
address vrfCoordinatorV2, // contract --> need mocks for this
57+
uint64 subscriptionId,
58+
bytes32 gasLane,
59+
uint256 interval,
60+
uint256 entranceFee,
61+
uint32 callbackGasLimit
62+
) VRFConsumerBaseV2(vrfCoordinatorV2) {
63+
i_vrfCoordinator = VRFCoordinatorV2Interface(vrfCoordinatorV2);
64+
i_gasLane = gasLane;
65+
i_interval = interval;
66+
i_subscriptionId = subscriptionId;
67+
i_entranceFee = entranceFee;
68+
s_raffleState = RaffleState.OPEN;
69+
s_lastTimeStamp = block.timestamp;
70+
i_callbackGasLimit = callbackGasLimit;
71+
}
72+
73+
function enterRaffle() public payable {
74+
// instead of this: (msg.value > i_entranceFee, "Not enough ETH!")
75+
if (msg.value < i_entranceFee) revert Raffle__NotEnoughETH();
76+
77+
if (s_raffleState != RaffleState.OPEN) revert Raffle__NotOpen();
78+
s_players.push(payable(msg.sender));
79+
emit RaffleEnter(msg.sender);
80+
}
81+
82+
/**
83+
* @dev This is the function that the CHainlink Keeper nodes call
84+
* they look for the 'upkeepneeded' to return true
85+
* the following should be true in order to return true
86+
* 1. our time interval should have passed
87+
* 2. lottery should have at least 1 player an have some ETH
88+
* 3. our subscription is funded with LINK
89+
* 4. lottery should be in an "open" state --> enums to close #
90+
*/
91+
function checkUpkeep(
92+
bytes memory /*checkData*/
93+
)
94+
public
95+
override
96+
returns (
97+
bool upkeepNeeded,
98+
bytes memory /* performData */
99+
)
100+
{
101+
bool isOpen = (RaffleState.OPEN == s_raffleState);
102+
bool timePassed = ((block.timestamp - s_lastTimeStamp) > i_interval);
103+
bool hasPlayers = (s_players.length > 0);
104+
bool hasBalance = address(this).balance > 0;
105+
upkeepNeeded = (isOpen && timePassed && hasPlayers && hasBalance);
106+
}
107+
108+
function performUpkeep(
109+
bytes calldata /* performData*/
110+
) external override {
111+
(bool upkeepNeeded, ) = checkUpkeep("");
112+
if (!upkeepNeeded) {
113+
revert Raffle__UpkeepNotNeeded(
114+
address(this).balance,
115+
s_players.length,
116+
uint256(s_raffleState)
117+
);
118+
}
119+
120+
s_raffleState = RaffleState.CALCULATING;
121+
uint256 requestId = i_vrfCoordinator.requestRandomWords(
122+
i_gasLane,
123+
i_subscriptionId,
124+
REQUEST_CONFIRMATIONS,
125+
i_callbackGasLimit, // SETS a cap for the gas price the function can run
126+
NUM_WORDS
127+
);
128+
emit RequestedRaffleWinner(requestId);
129+
}
130+
131+
function fulfillRandomWords(
132+
uint256, /* requestId */
133+
uint256[] memory randomWords
134+
) internal override {
135+
uint256 indexOfWinner = randomWords[0] % s_players.length;
136+
address payable recentWinner = s_players[indexOfWinner];
137+
s_recentWinner = recentWinner;
138+
s_raffleState = RaffleState.OPEN;
139+
s_players = new address payable[](0);
140+
s_lastTimeStamp = block.timestamp;
141+
(bool success, ) = recentWinner.call{value: address(this).balance}("");
142+
if (!success) {
143+
revert Raffle__TransferFailed();
144+
}
145+
emit WinnerPicked(recentWinner);
146+
}
147+
148+
/* views / pure functions */
149+
function getEntranceFee() public view returns (uint256) {
150+
return i_entranceFee;
151+
}
152+
153+
function getPlayer(uint256 index) public view returns (address) {
154+
return s_players[index];
155+
}
156+
157+
function getRecentWinner() public view returns (address) {
158+
return s_recentWinner;
159+
}
160+
161+
function getRaffleState() public view returns (RaffleState) {
162+
return s_raffleState;
163+
}
164+
165+
function getNumWords() public pure returns (uint256) {
166+
return NUM_WORDS;
167+
}
168+
169+
function getInterval() public view returns (uint256) {
170+
return i_interval;
171+
}
172+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
// SPDX-License-Identifier: MIT
2+
pragma solidity ^0.8.7;
3+
4+
import "@chainlink/contracts/src/v0.8/mocks/VRFCoordinatorV2Mock.sol";

depencies.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
yarn add --dev @nomiclabs/hardhat-ethers@npm:hardhat-deploy-ethers ethers @nomiclabs/hardhat-etherscan @nomiclabs/hardhat-waffle chai ethereum-waffle hardhat hardhat-contract-sizer hardhat-deploy hardhat-gas-reporter prettier prettier-plugin-solidity solhint solidity-coverage dotenv
2+
3+
copy the line above in a new project by default

deploy/00-deploy-mocks.js

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
const { developmentChains } = require("../helper-hardhat-config")
2+
const { network } = require("hardhat")
3+
4+
const BASE_FEE = ethers.utils.parseEther("0.25") // 0.25 is the premium. It costs 0.25 LINK per request.
5+
const GAS_PRICE_LINK = 1e9 // calculated value based on the gas price of the chain
6+
7+
// chainlink nodes pay the gas fees to give us randomness & do external execution
8+
9+
module.exports = async ({ getNamedAccounts, deployments }) => {
10+
const { deploy, log } = deployments
11+
const { deployer } = await getNamedAccounts()
12+
const args = [BASE_FEE, GAS_PRICE_LINK]
13+
14+
if (developmentChains.includes(network.name)) {
15+
log("Local network detected! Deploying mocks...")
16+
// deploy mock!
17+
await deploy("VRFCoordinatorV2Mock", {
18+
from: deployer,
19+
log: true,
20+
args: args,
21+
})
22+
log("Mocks Deployed!")
23+
log("----------------------------------------------------")
24+
}
25+
}
26+
27+
module.exports.tags = ["all", "mocks"]

deploy/01-deploy-raffle.js

+57
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
const { network, ethers } = require("hardhat")
2+
const { developmentChains, networkConfig } = require("../helper-hardhat-config")
3+
const { verify } = require("../utils/verify")
4+
5+
const VRF_SUB_FUND_AMOUNT = ethers.utils.parseEther("2")
6+
7+
module.exports = async function ({ getNamedAccounts, deployments }) {
8+
const { deploy, log } = deployments
9+
const { deployer } = await getNamedAccounts()
10+
const chainId = network.config.chainId
11+
let VRFCoordinatorV2Address, subscriptionId, VRFCoordinatorV2Mock
12+
13+
if (developmentChains.includes(network.name)) {
14+
VRFCoordinatorV2Mock = await ethers.getContract("VRFCoordinatorV2Mock")
15+
VRFCoordinatorV2Address = VRFCoordinatorV2Mock.address
16+
const transactionResponse = await VRFCoordinatorV2Mock.createSubscription()
17+
const transactionReceipt = await transactionResponse.wait(1)
18+
subscriptionId = transactionReceipt.events[0].args.subId
19+
// fund subcribtion
20+
// usually you need link token on real network
21+
await VRFCoordinatorV2Mock.fundSubscription(subscriptionId, VRF_SUB_FUND_AMOUNT)
22+
} else {
23+
VRFCoordinatorV2Address = networkConfig[chainId]["vrfCoordinatorV2"]
24+
subscriptionId = networkConfig[chainId]["subscriptionId"]
25+
}
26+
27+
const entranceFee = networkConfig[chainId]["entranceFee"]
28+
const gasLane = networkConfig[chainId]["gasLane"]
29+
const callbackGasLimit = networkConfig[chainId]["callbackGasLimit"]
30+
const interval = networkConfig[chainId]["interval"]
31+
const args = [
32+
VRFCoordinatorV2Address,
33+
entranceFee,
34+
gasLane,
35+
subscriptionId,
36+
callbackGasLimit,
37+
interval,
38+
]
39+
const raffle = await deploy("Raffle", {
40+
from: deployer,
41+
args: args,
42+
log: true,
43+
waitConfirmations: network.config.blockConfirmations || 1,
44+
})
45+
if (developmentChains.includes(network.name)) {
46+
await VRFCoordinatorV2Mock.addConsumer(subscriptionId, raffle.address)
47+
log(">consumer added<")
48+
}
49+
50+
if (!developmentChains.includes(network.name) && process.env.ETHERSCAN_API_KEY) {
51+
log("Verifying...")
52+
await verify(raffle.address, args)
53+
}
54+
log("--------------------------")
55+
}
56+
57+
module.exports.tags = ["all", "raffle"]

hardhat.config.js

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
require("@nomiclabs/hardhat-waffle")
2+
require("@nomiclabs/hardhat-etherscan")
3+
require("hardhat-deploy")
4+
require("solidity-coverage")
5+
require("hardhat-gas-reporter")
6+
require("hardhat-contract-sizer")
7+
require("dotenv").config()
8+
9+
const COINMARKETCAP_API_KEY = process.env.COINMARKETCAP_API_KEY
10+
const GOERLI_RPC_URL =
11+
process.env.GOERLI_RPC_URL || "https://eth-mainnet.alchemyapi.io/v2/your-api-key"
12+
const PRIVATE_KEY =
13+
process.env.PRIVATE_KEY || "0x11ee3108a03081fe260ecdc106554d09d9d1209bcafd46942b10e02943effc4a"
14+
const ETHERSCAN_API_KEY = process.env.ETHERSCAN_API_KEY
15+
16+
/** @type import('hardhat/config').HardhatUserConfig */
17+
module.exports = {
18+
defaultNetwork: "hardhat",
19+
networks: {
20+
hardhat: {
21+
chainId: 31337,
22+
blockConfirmations: 1,
23+
},
24+
goerli: {
25+
chainId: 5,
26+
blockConfirmations: 6,
27+
url: GOERLI_RPC_URL,
28+
accounts: [PRIVATE_KEY],
29+
},
30+
},
31+
gasReporter: {
32+
enabled: false,
33+
currency: "USD",
34+
outputFile: "gas-report.txt",
35+
noColors: true,
36+
coinmarketcap: COINMARKETCAP_API_KEY,
37+
},
38+
solidity: "0.8.17",
39+
namedAccounts: {
40+
deployer: {
41+
default: 0,
42+
1: 0,
43+
},
44+
player: {
45+
default: 1,
46+
},
47+
},
48+
}

helper-hardhat-config.js

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
const { ethers } = require("hardhat")
2+
3+
const networkConfig = {
4+
default: {
5+
name: "hardhat",
6+
interval: "3",
7+
},
8+
5: {
9+
name: "goerli",
10+
vrfCoordinatorV2: "0x2Ca8E0C643bDe4C2E08ab1fA0da3401AdAD7734D",
11+
entranceFee: ethers.utils.parseEther("0.01"),
12+
gasLane: "0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15",
13+
subscriptionId: "0",
14+
callbackGasLimit: "5000000",
15+
interval: "3",
16+
},
17+
31337: {
18+
name: "localhost",
19+
entranceFee: ethers.utils.parseEther("0.01"),
20+
gasLane: "0x79d3d8832d904592c0bf9818b621522c988bb8b0c05cdc3b15aea1b6e8db0c15",
21+
interval: "3",
22+
subscriptionId: "0",
23+
callbackGasLimit: "5000000",
24+
},
25+
}
26+
27+
const developmentChains = ["hardhat", "localhost"]
28+
29+
module.exports = {
30+
networkConfig,
31+
developmentChains,
32+
}

0 commit comments

Comments
 (0)