This repository was archived by the owner on Dec 3, 2025. It is now read-only.
forked from ZTX-Foundation/tuxedo
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathGameConsumer.sol
More file actions
197 lines (167 loc) · 7.15 KB
/
GameConsumer.sol
File metadata and controls
197 lines (167 loc) · 7.15 KB
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
// SPDX-License-Identifier: GPL-3.0-or-later
pragma solidity 0.8.18;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {IWETH} from "@protocol/interface/IWETH.sol";
import {Roles} from "@protocol/core/Roles.sol";
import {CoreRef} from "@protocol/refs/CoreRef.sol";
import {ERC20HoldingDeposit} from "@protocol/finance/ERC20HoldingDeposit.sol";
/// Game payment contract for users to boost gampeplay
/// Inherits CoreRef for roles and access
contract GameConsumer is CoreRef, ERC20HoldingDeposit {
using SafeERC20 for *;
using ECDSA for bytes32;
/// @notice event emitted when in payment is taken
event TakePayment(uint256 indexed jobId, uint256 amount);
/// @notice event emitted when proceeds are withdrawn
event WithdrawToCollector(address proceedsCollector, uint256 amount);
/// @notice event emitted when crafting fee is updated
event CraftingFeeUpdated(uint256 oldCraftingFee, uint256 newCraftingFee);
/// @notice event emitted when proceeds collector is updated
event ProceedsRecipientUpdated(address proceedsCollector);
/// @notice event emitted when tokens are swept
event TokensSwept(address indexed token, address proceedsCollector, uint256 amount);
/// @notice proceeds recipient
address public proceedsRecipient;
/// @notice WETH token
IWETH public immutable weth;
/// @notice store used hashes
mapping(bytes32 hash => bool isUsed) public usedHashes;
/// @notice construct the ERC20HoldingDeposit and CoreRef contract
/// @param _core address of the core contract
/// @param _token address of the payment token
/// @param _proceedsRecipient address to send all proceeds to
/// @param _weth address of the weth token
constructor(
address _core,
address _token,
address _proceedsRecipient,
address _weth
) ERC20HoldingDeposit(_core, _token) {
require(_proceedsRecipient != address(0), "GameConsumer: proceeds recipient cannot be address(0)");
weth = IWETH(_weth);
proceedsRecipient = _proceedsRecipient;
}
/// @notice speed up crafting
/// @dev pausing stops this function from being called by all external
/// functions that emit crafting or speed up events
/// @param quoteExpiry Expiry time of the quote
/// @param craftedHash Hash of the message from calldata
/// @param hash Hash of the message
/// @param signature Signature of the message
function _verifySignerAndHash(
uint256 quoteExpiry,
bytes32 craftedHash,
bytes32 hash,
bytes memory signature
) internal whenNotPaused {
require(hash == craftedHash, "GameConsumer: hash mismatch");
/// offchain will handle when the quote expires
require(quoteExpiry >= block.timestamp, "GameConsumer: timestamp expired");
require(!usedHashes[hash], "GameConsumer: hash already used");
require(
core.hasRole(Roles.GAME_CONSUMER_NOTARY_PROTOCOL_ROLE, recoverSigner(hash, signature)),
"GameConsumer: Missing GAME_CONSUMER_NOTARY Role"
);
usedHashes[hash] = true;
}
/// @notice generic take payment function
/// @param payer Address to take payment from
/// @param jobId ID of the offchain job
/// @param jobFee Amount of the job fee
/// @param paymentToken Address of the token to pay in
/// @param quoteExpiry A random number to prevent brute force ie a timestamp
/// @param hash Hash of the message
/// @param salt A random number to prevent collisions
/// @param signature Signature of the message
function takePayment(
address payer,
uint256 jobId,
uint256 jobFee,
address paymentToken,
uint256 quoteExpiry,
bytes32 hash,
uint256 salt,
bytes memory signature
) external {
/// checks and effects
_verifySignerAndHash(
quoteExpiry,
getHash(jobId, paymentToken, jobFee, quoteExpiry, salt),
hash,
signature
);
IERC20(paymentToken).safeTransferFrom(payer, address(this), jobFee);
emit TakePayment(jobId, jobFee);
}
/// @notice generic take payment in ETH function
/// @param jobId ID of the offchain job
/// @param jobFee Amount of the job fee
/// @param quoteExpiry A random number to prevent brute force ie a timestamp
/// @param hash Hash of the message
/// @param salt A random number to prevent collisions
/// @param signature Signature of the message
function takePaymentWithEth(
uint256 jobId,
uint256 jobFee,
uint256 quoteExpiry,
bytes32 hash,
uint256 salt,
bytes memory signature
) external payable {
require(msg.value == jobFee, "GameConsumer: incorrect job fee");
/// checks and effects
_verifySignerAndHash(
quoteExpiry,
getHash(jobId, address(weth), jobFee, quoteExpiry, salt),
hash,
signature
);
emit TakePayment(jobId, jobFee);
}
/// @dev Generic hashing method
/// @param jobId ID of the offchain job
/// @param paymentToken Address of the token to pay in
/// @param jobFee Amount of the job fee
/// @param hashTimestamp A time stamp to prevent expired quotes
/// @param salt A random number to prevent collisions
function getHash(
uint256 jobId,
address paymentToken,
uint256 jobFee,
uint256 hashTimestamp,
uint256 salt
) public pure returns (bytes32) {
bytes32 hash = keccak256(abi.encode(jobId, paymentToken, jobFee, hashTimestamp, salt));
return hash.toEthSignedMessageHash();
}
/// @dev Returns the address that signed a given string message
/// @param hash Signed Keccak-256 hash
function recoverSigner(bytes32 hash, bytes memory signature) public pure returns (address) {
return hash.recover(signature);
}
/// @notice set the proceeds recipient
/// @dev callable only by admin
/// @param _proceedsRecipient the proceeds recipient
function setProceedsCollector(address _proceedsRecipient) external onlyRole(Roles.ADMIN) {
proceedsRecipient = _proceedsRecipient;
emit ProceedsRecipientUpdated(_proceedsRecipient);
}
/// @notice turn raw eth into wrapped eth
function wrapEth() external {
weth.deposit{value: address(this).balance}();
}
/// @notice withdraw token proceeds to proceeds recipient
function sweepUnclaimed() external {
uint256 tokenBalance = token.balanceOf(address(this));
token.safeTransfer(proceedsRecipient, tokenBalance);
emit TokensSwept(address(token), proceedsRecipient, tokenBalance);
}
/// @notice withdraw WETH proceeds to proceeds recipient
function sweepUnclaimedWeth() external {
uint256 tokenBalance = weth.balanceOf(address(this));
IERC20(address(weth)).safeTransfer(proceedsRecipient, tokenBalance);
emit TokensSwept(address(weth), proceedsRecipient, tokenBalance);
}
}