-
Notifications
You must be signed in to change notification settings - Fork 0
/
RaffleHookV2.sol
269 lines (216 loc) · 8.76 KB
/
RaffleHookV2.sol
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
// ---- NOT THIS VERSION ---- //
// SPDX-License-Identifier: MIT
pragma solidity 0.8.26;
import {BaseHook} from "v4-periphery/src/base/hooks/BaseHook.sol";
import {ERC20} from "solmate/src/tokens/ERC20.sol";
import {CurrencyLibrary, Currency} from "v4-core/types/Currency.sol";
import {CurrencySettler} from "@uniswap/v4-core/test/utils/CurrencySettler.sol";
import {PoolKey} from "v4-core/types/PoolKey.sol";
import {BalanceDeltaLibrary, BalanceDelta} from "v4-core/types/BalanceDelta.sol";
import {IPoolManager} from "v4-core/interfaces/IPoolManager.sol";
import {PoolId, PoolIdLibrary} from "v4-core/types/PoolId.sol";
import {Hooks} from "v4-core/libraries/Hooks.sol";
import {BeforeSwapDelta, toBeforeSwapDelta} from "v4-core/types/BeforeSwapDelta.sol";
contract RaffleHookV2 is BaseHook {
// Use CurrencyLibrary and BalanceDeltaLibrary
// to add some helper functions over the Currency and BalanceDelta
// data types
using CurrencySettler for Currency;
using BalanceDeltaLibrary for BalanceDelta;
int128 public constant COMMISSION_RATE = 1000;
uint256 constant batchSize = 50000; // every 50K ticket issued will reset the batch
struct Prize {
uint256 prizeAmount;
bool claimed;
Ticket winningTicket;
}
struct Ticket {
uint256 raffleBatch;
uint256 lowerNumber;
uint256 upperNumber;
}
struct Batch {
uint256 currentBatch;
uint256 currentBatchTicketNumber;
}
// pool raffle status
mapping(PoolId poolId => Batch raffleBatch) public raffleBatchByPool;
// winners, and allocated prize
mapping(PoolId poolId =>
mapping(uint256 raffeBatch => Prize prize))
public winnersTicketList;
mapping(PoolId poolId =>
mapping(uint256 raffeBatch =>
mapping(address participants => Ticket ticket)))
public ticketList;
// recording the unallocated portion of Pot
mapping(PoolId poolId => uint256 unallocatedPot) public RafflePot;
// Amount of points someone gets for referring someone else
uint256 public constant POINTS_FOR_REFERRAL = 500 * 10 ** 18;
// Callback Data for winner claiming prize
struct CallbackData {
uint256 amount;
Currency currency0;
address sender;
}
// Initialize BaseHook and ERC20
constructor(
IPoolManager _manager,
string memory _name,
string memory _symbol
) BaseHook(_manager) {}
// Set up hook permissions to return `true`
// for the two hook functions we are using
function getHookPermissions()
public
pure
override
returns (Hooks.Permissions memory)
{
return
Hooks.Permissions({
beforeInitialize: false,
afterInitialize: false,
beforeAddLiquidity: false,
beforeRemoveLiquidity: false,
afterAddLiquidity: false,
afterRemoveLiquidity: false,
beforeSwap: true,
afterSwap: true,
beforeDonate: false,
afterDonate: false,
beforeSwapReturnDelta: true,
afterSwapReturnDelta: false,
afterAddLiquidityReturnDelta: false,
afterRemoveLiquidityReturnDelta: false
});
}
function beforeSwap(
address,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
bytes calldata
) external override returns (bytes4, BeforeSwapDelta, uint24) {
int128 amountInOutPositive = params.amountSpecified > 0
? int128( params.amountSpecified)
: int128(-params.amountSpecified);
// taking a cut from token0
int128 commissionAmount = (amountInOutPositive * COMMISSION_RATE) / 10000;
// in current version, we handle selling ETH only
if(params.zeroForOne){
BeforeSwapDelta beforeSwapDelta = toBeforeSwapDelta(commissionAmount, 0);
// creating a debit of token0 to PM as commission.
key.currency0.take(
poolManager,
address(this),
uint256(int256(commissionAmount)),
true
);
return (this.beforeSwap.selector, beforeSwapDelta, 0);
} else {
BeforeSwapDelta beforeSwapDelta = toBeforeSwapDelta(0, 0);
return (this.beforeSwap.selector, beforeSwapDelta, 0);
}
}
function afterSwap(
address sender,
PoolKey calldata key,
IPoolManager.SwapParams calldata params,
BalanceDelta,
bytes calldata
) external override returns (bytes4, int128) {
// issue tickets for trader
// in later version, number of tickets will be issued based on trading volume.
uint256 TICKET_ISSUABLE = 5;
Ticket storage tix;
tix.raffleBatch = raffleBatchByPool[key.toId()].currentBatch;
tix.lowerNumber = raffleBatchByPool[key.toId()].currentBatchTicketNumber+1;
tix.upperNumber = raffleBatchByPool[key.toId()].currentBatchTicketNumber + TICKET_ISSUABLE;
ticketList[key.toId()][tix.raffleBatch][sender] = tix;
// update raffer pool
raffleBatchByPool[key.toId()].currentBatchTicketNumber + TICKET_ISSUABLE;
}
// when user claim the prize
function claimPrizeByWinner(
address sender,
PoolKey calldata poolKey
) external {
// check if sender is a winner
for(uint batch = 0; batch < raffleBatchByPool[poolKey.toId()].currentBatch ; batch++){
Ticket storage tix = ticketList[poolKey.toId()][batch][sender];
// if msg.sender has got some ticket in this batch
if(tix.raffleBatch >= 0){
Prize storage prize = winnersTicketList[poolKey.toId()][batch];
if(!prize.claimed){
if(tix.lowerNumber <= prize.winningTicket.lowerNumber || prize.winningTicket.upperNumber <= tix.upperNumber)
{
// it's a win!
// unlock the vault and send out prize
poolManager.unlock(
abi.encode(
CallbackData(
prize.prizeAmount,
poolKey.currency0,
msg.sender
)
)
);
// update prize record
prize.claimed = true;
winnersTicketList[poolKey.toId()][batch] = prize;
}
}
}
}
}
function _unlockCallback(
bytes calldata data
) internal override returns (bytes memory){
CallbackData memory callbackData = abi.decode(data, CallbackData);
// burn the claim token and settle transfer to winner (80% only)
callbackData.currency0.settle(
poolManager,
callbackData.sender,
callbackData.amount * 4 / 5,
false
);
// transfer token to winner, hook keeps 20%
callbackData.currency0.settle(
poolManager,
address(this),
callbackData.amount * 1 / 5,
false
);
}
// draw the winner of the current batch
// expect to be ran in conjob and will be using Chainlink randomizer
function drawWinnerForPool(
address sender,
PoolKey calldata poolKey
) external {
// check if Batchsize > currentBatchTicketNumber, if so, draw the winner
Batch storage batch = raffleBatchByPool[poolKey.toId()];
if(batch.currentBatchTicketNumber >= batchSize){
// draw the winner
uint winnerNumber = getRandomNumber(0, batch.currentBatchTicketNumber);
Ticket storage winnerTicket;
winnerTicket.raffleBatch = batch.currentBatch;
winnerTicket.lowerNumber = winnerTicket.upperNumber = winnerNumber;
Prize storage prize;
prize.prizeAmount = RafflePot[poolKey.toId()]; // get the unallocated of the current batch
prize.claim = false;
prize.winningTicket = winnerTicket;
winnersTicketList[poolKey.toId()][batch.currentBatch] = prize;
// update the unallocated claim token in pot
RafflePot[poolKey.toId()] -= prize.prizeAmount;
// reset the raffle
batch.currentBatch++;
batch.currentBatchTicketNumber = 0;
raffleBatchByPool[poolKey.toId()] = batch;
}
}
// dummy function for randomizer on ChainLink later
function getRandomNumber(uint start, uint end) internal {
return 100;
}
}