@d20dao/vrf-sdk 0.3.4 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +25 -15
- package/API.md +342 -209
- package/BUILD-MANIFEST.json +18 -16
- package/CHANGELOG.md +44 -0
- package/PROTOCOL-PROVENANCE.json +9 -8
- package/README.md +129 -108
- package/abi/EpochEntropy.json +372 -26
- package/dist/abi.d.ts +292 -24
- package/dist/abi.js +1 -1
- package/dist/epoch.d.ts +42 -78
- package/dist/epoch.js +121 -27
- package/dist/index.d.ts +4 -2
- package/dist/index.js +2 -1
- package/dist/sources.js +2 -0
- package/dist/templates.d.ts +22 -0
- package/dist/templates.js +220 -0
- package/examples/DiceConsumer.sol +37 -29
- package/examples/LootDropConsumer.sol +60 -0
- package/examples/RaffleConsumer.sol +71 -0
- package/package.json +2 -1
- package/contracts/examples/MiningRandomnessConsumer.sol +0 -59
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
// SPDX-License-Identifier: MIT
|
|
2
|
+
pragma solidity 0.8.28;
|
|
3
|
+
|
|
4
|
+
import {D20VRFConsumer} from "@d20dao/vrf-sdk/contracts/D20VRFConsumer.sol";
|
|
5
|
+
import {ID20VRF} from "@d20dao/vrf-sdk/contracts/interfaces/ID20VRF.sol";
|
|
6
|
+
import {RandomnessMapping} from "@d20dao/vrf-sdk/contracts/libraries/RandomnessMapping.sol";
|
|
7
|
+
|
|
8
|
+
/// @notice One winner drawn from a list of entrants. Swap ChooseOne for ChooseMany to draw several.
|
|
9
|
+
contract RaffleConsumer is D20VRFConsumer {
|
|
10
|
+
uint32 private constant CALLBACK_GAS = 100_000;
|
|
11
|
+
|
|
12
|
+
address[] public entrants;
|
|
13
|
+
/// The outstanding or served request: zero before the first draw, and zero again once an expired one was refunded.
|
|
14
|
+
uint256 public drawId;
|
|
15
|
+
bytes32 public word;
|
|
16
|
+
bool public closed; bool public drawn;
|
|
17
|
+
|
|
18
|
+
error AlreadyClosed();
|
|
19
|
+
error NoEntrants();
|
|
20
|
+
error RaffleFull();
|
|
21
|
+
error AlreadyDrawn();
|
|
22
|
+
error UnexpectedCallback();
|
|
23
|
+
error NotDrawn();
|
|
24
|
+
|
|
25
|
+
constructor(address coordinator) D20VRFConsumer(coordinator) {}
|
|
26
|
+
|
|
27
|
+
function enter() external {
|
|
28
|
+
if (closed) revert AlreadyClosed();
|
|
29
|
+
// ChooseOne answers with an index, and the coordinator maps at most 256 items.
|
|
30
|
+
if (entrants.length == 256) revert RaffleFull();
|
|
31
|
+
entrants.push(msg.sender);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function draw() external payable {
|
|
35
|
+
// A raffle that can be drawn twice is not a raffle: one served request, one winner, and no second
|
|
36
|
+
// request while one is outstanding or after one was served.
|
|
37
|
+
if (drawId != 0) revert AlreadyDrawn();
|
|
38
|
+
if (entrants.length == 0) revert NoEntrants();
|
|
39
|
+
closed = true;
|
|
40
|
+
// The answer is an index, so the list must be frozen before the request goes out; hashing it into the
|
|
41
|
+
// client seed publishes that promise, because RandomnessRequested logs the seed. msg.value is
|
|
42
|
+
// forwarded whole: the coordinator keeps exactly its quote, reverts IncorrectFee when that is more
|
|
43
|
+
// than arrived, and credits the surplus to msg.sender as refund credit to withdraw later.
|
|
44
|
+
drawId = ID20VRF(vrfCoordinator).requestMappedRandomness{value: msg.value}(
|
|
45
|
+
keccak256(abi.encode(entrants)), CALLBACK_GAS, msg.sender,
|
|
46
|
+
RandomnessMapping.Spec(RandomnessMapping.Operation.ChooseOne, 0, 0, 1, uint32(entrants.length))
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function _fulfillRandomness(uint256 requestId, bytes32 randomness) internal override {
|
|
51
|
+
// Only the one request this raffle made, and only once: a failed delivery is retried by anyone with
|
|
52
|
+
// retryCallback, and ids this contract never used are not its business.
|
|
53
|
+
if (requestId != drawId || drawn) revert UnexpectedCallback();
|
|
54
|
+
// Store and stop, so the callback cannot run out of its budget. Paying the prize belongs in a
|
|
55
|
+
// separate transaction anyone can send once `drawn` is true.
|
|
56
|
+
word = randomness;
|
|
57
|
+
drawn = true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// If no proof is accepted within 60 seconds the request expires and anyone may call refundRequest(drawId)
|
|
61
|
+
/// on the coordinator. That returns the fee to whoever sent draw() and then calls this hook: the list stays
|
|
62
|
+
/// frozen and draw() may be sent again, so an expired request never leaves the raffle stuck.
|
|
63
|
+
function _onRefund(uint256 requestId) internal override {
|
|
64
|
+
if (requestId == drawId && !drawn) drawId = 0;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function winner() external view returns (address) {
|
|
68
|
+
if (!drawn) revert NotDrawn();
|
|
69
|
+
return entrants[ID20VRF(vrfCoordinator).getMappedResult(drawId)[0]];
|
|
70
|
+
}
|
|
71
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d20dao/vrf-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "D20DAO verifiable randomness: Solidity consumer helpers, contract ABIs and public proof replay",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public",
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"examples/*.sol",
|
|
41
41
|
"AGENTS.md",
|
|
42
42
|
"API.md",
|
|
43
|
+
"CHANGELOG.md",
|
|
43
44
|
"LICENSE",
|
|
44
45
|
"THIRD_PARTY_NOTICES.md",
|
|
45
46
|
"notices/*",
|
|
@@ -1,59 +0,0 @@
|
|
|
1
|
-
// SPDX-License-Identifier: MIT
|
|
2
|
-
pragma solidity 0.8.28;
|
|
3
|
-
|
|
4
|
-
import {D20VRFConsumer} from "../D20VRFConsumer.sol";
|
|
5
|
-
import {ID20VRF} from "../interfaces/ID20VRF.sol";
|
|
6
|
-
|
|
7
|
-
/// @notice Integration building block, not a replacement for the game's mining/economy contracts.
|
|
8
|
-
/// @dev The actual game validates PoW, locks payment and immutable claim attributes BEFORE
|
|
9
|
-
/// calling _requestForClaim inside that same transaction. No user reveal/contribution call.
|
|
10
|
-
abstract contract MiningRandomnessConsumer is D20VRFConsumer {
|
|
11
|
-
struct ClaimRandomness { uint256 requestId; bytes32 randomness; bool requested; bool ready; }
|
|
12
|
-
mapping(uint256 => ClaimRandomness) public claimRandomness;
|
|
13
|
-
mapping(uint256 => uint256) public requestToClaim;
|
|
14
|
-
mapping(uint256 => bool) private knownRequest;
|
|
15
|
-
event ClaimRandomnessRequested(uint256 indexed claimId, uint256 indexed requestId);
|
|
16
|
-
event ClaimReady(uint256 indexed claimId, uint256 indexed requestId, bytes32 randomness);
|
|
17
|
-
error ClaimAlreadyRequested();
|
|
18
|
-
error UnexpectedCallback();
|
|
19
|
-
error ClaimNotReady();
|
|
20
|
-
error InvalidCandidate();
|
|
21
|
-
|
|
22
|
-
constructor(address coordinator) D20VRFConsumer(coordinator) {}
|
|
23
|
-
|
|
24
|
-
function _requestForClaim(uint256 claimId, bytes32 lockedWork, uint32 callbackGasLimit, address refundAddress)
|
|
25
|
-
internal returns (uint256 requestId)
|
|
26
|
-
{
|
|
27
|
-
if (claimRandomness[claimId].requested) revert ClaimAlreadyRequested();
|
|
28
|
-
claimRandomness[claimId].requested = true;
|
|
29
|
-
ID20VRF rng = ID20VRF(vrfCoordinator);
|
|
30
|
-
// The same-transaction quote is exact; only off-chain senders need a buffer.
|
|
31
|
-
requestId = rng.requestRandomness{value: rng.quoteFee(callbackGasLimit)}(
|
|
32
|
-
keccak256(abi.encode(claimId, lockedWork)), callbackGasLimit, refundAddress
|
|
33
|
-
);
|
|
34
|
-
claimRandomness[claimId].requestId = requestId;
|
|
35
|
-
requestToClaim[requestId] = claimId;
|
|
36
|
-
knownRequest[requestId] = true;
|
|
37
|
-
emit ClaimRandomnessRequested(claimId, requestId);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
function _fulfillRandomness(uint256 requestId, bytes32 randomness) internal override {
|
|
41
|
-
if (!knownRequest[requestId]) revert UnexpectedCallback();
|
|
42
|
-
uint256 claimId = requestToClaim[requestId];
|
|
43
|
-
ClaimRandomness storage claim = claimRandomness[claimId];
|
|
44
|
-
if (claim.ready) revert UnexpectedCallback();
|
|
45
|
-
claim.randomness = randomness;
|
|
46
|
-
claim.ready = true;
|
|
47
|
-
emit ClaimReady(claimId, requestId, randomness);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
function candidateSeed(uint256 claimId, uint8 candidate) public view returns (bytes32) {
|
|
51
|
-
ClaimRandomness storage claim = claimRandomness[claimId];
|
|
52
|
-
if (!claim.ready) revert ClaimNotReady();
|
|
53
|
-
if (candidate >= 3) revert InvalidCandidate();
|
|
54
|
-
return keccak256(abi.encode(
|
|
55
|
-
keccak256("VRF_D20DAO_CARD_V1"), block.chainid, address(this),
|
|
56
|
-
claimId, claim.requestId, claim.randomness, candidate
|
|
57
|
-
));
|
|
58
|
-
}
|
|
59
|
-
}
|