dagger-contracts/contracts/Proofs.sol

200 lines
6.3 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.8;
contract Proofs {
type ProofId is bytes32;
type EndId is bytes32;
uint256 private immutable period;
uint256 private immutable timeout;
uint8 private immutable downtime;
constructor(
uint256 __period,
uint256 __timeout,
uint8 __downtime
) {
require(block.number > 256, "Insufficient block height");
period = __period;
timeout = __timeout;
downtime = __downtime;
}
mapping(ProofId => bool) private ids;
mapping(ProofId => uint256) private starts;
mapping(EndId => uint256) private ends;
mapping(ProofId => EndId) private idEnds;
mapping(ProofId => uint256) private probabilities;
mapping(ProofId => uint256) private markers;
mapping(ProofId => uint256) private missed;
mapping(ProofId => mapping(uint256 => bool)) private received;
mapping(ProofId => mapping(uint256 => bool)) private missing;
function _period() internal view returns (uint256) {
return period;
}
function _timeout() internal view returns (uint256) {
return timeout;
}
function _end(EndId endId) internal view returns (uint256) {
uint256 end = ends[endId];
require(end > 0, "Proof ending doesn't exist");
return ends[endId];
}
function _endId(ProofId id) internal view returns (EndId) {
EndId endId = idEnds[id];
require(EndId.unwrap(endId) > 0, "endId for given id doesn't exist");
return endId;
}
function _endFromId(ProofId id) internal view returns (uint256) {
EndId endId = _endId(id);
return _end(endId);
}
function _missed(ProofId id) internal view returns (uint256) {
return missed[id];
}
function periodOf(uint256 timestamp) private view returns (uint256) {
return timestamp / period;
}
function currentPeriod() private view returns (uint256) {
return periodOf(block.timestamp);
}
/// @notice Informs the contract that proofs should be expected for id
/// @dev Requires that the id is not already in use
/// @param id identifies the proof expectation, typically a slot id
/// @param endId Identifies the id of the proof expectation ending. Typically a request id. Different from id because the proof ending is shared amongst many ids.
/// @param probability The probability that a proof should be expected
function _expectProofs(
ProofId id, // typically slot id
EndId endId, // typically request id, used so that the ending is global for all slots
uint256 probability
) internal {
require(!ids[id], "Proof id already in use");
ids[id] = true;
starts[id] = block.timestamp;
probabilities[id] = probability;
markers[id] = uint256(blockhash(block.number - 1)) % period;
idEnds[id] = endId;
}
function _unexpectProofs(
ProofId id
) internal {
require(ids[id], "Proof id not in use");
ids[id] = false;
}
function _getPointer(ProofId id, uint256 proofPeriod)
internal
view
returns (uint8)
{
uint256 blockNumber = block.number % 256;
uint256 periodNumber = proofPeriod % 256;
uint256 idOffset = uint256(ProofId.unwrap(id)) % 256;
uint256 pointer = (blockNumber + periodNumber + idOffset) % 256;
return uint8(pointer);
}
function _getPointer(ProofId id) internal view returns (uint8) {
return _getPointer(id, currentPeriod());
}
function _getChallenge(uint8 pointer) internal view returns (bytes32) {
bytes32 hash = blockhash(block.number - 1 - pointer);
assert(uint256(hash) != 0);
return keccak256(abi.encode(hash));
}
function _getChallenge(ProofId id, uint256 proofPeriod)
internal
view
returns (bytes32)
{
return _getChallenge(_getPointer(id, proofPeriod));
}
function _getChallenge(ProofId id) internal view returns (bytes32) {
return _getChallenge(id, currentPeriod());
}
function _getProofRequirement(ProofId id, uint256 proofPeriod)
internal
view
returns (bool isRequired, uint8 pointer)
{
if (proofPeriod <= periodOf(starts[id])) {
return (false, 0);
}
uint256 end = _endFromId(id);
if (proofPeriod >= periodOf(end)) {
return (false, 0);
}
pointer = _getPointer(id, proofPeriod);
bytes32 challenge = _getChallenge(pointer);
uint256 probability = (probabilities[id] * (256 - downtime)) / 256;
isRequired = ids[id] && uint256(challenge) % probability == 0;
}
function _isProofRequired(ProofId id, uint256 proofPeriod)
internal
view
returns (bool)
{
bool isRequired;
uint8 pointer;
(isRequired, pointer) = _getProofRequirement(id, proofPeriod);
return isRequired && pointer >= downtime;
}
function _isProofRequired(ProofId id) internal view returns (bool) {
return _isProofRequired(id, currentPeriod());
}
function _willProofBeRequired(ProofId id) internal view returns (bool) {
bool isRequired;
uint8 pointer;
(isRequired, pointer) = _getProofRequirement(id, currentPeriod());
return isRequired && pointer < downtime;
}
function _submitProof(ProofId id, bytes calldata proof) internal {
require(proof.length > 0, "Invalid proof"); // TODO: replace by actual check
require(!received[id][currentPeriod()], "Proof already submitted");
received[id][currentPeriod()] = true;
emit ProofSubmitted(id, proof);
}
function _markProofAsMissing(ProofId id, uint256 missedPeriod) internal {
uint256 periodEnd = (missedPeriod + 1) * period;
require(periodEnd < block.timestamp, "Period has not ended yet");
require(block.timestamp < periodEnd + timeout, "Validation timed out");
require(!received[id][missedPeriod], "Proof was submitted, not missing");
require(_isProofRequired(id, missedPeriod), "Proof was not required");
require(!missing[id][missedPeriod], "Proof already marked as missing");
missing[id][missedPeriod] = true;
missed[id] += 1;
}
/// @notice Sets the proof end time
/// @dev Can only be set once
/// @param endId the endId of the proofs to extend (typically a request id).
/// @param ending the new end time (in seconds)
function _setProofEnd(EndId endId, uint256 ending) internal {
// TODO: create type aliases for id and endId so that _end() can return
// EndId storage and we don't need to replicate the below require here
require (ends[endId] == 0 || ending < block.timestamp, "End exists or must be past");
ends[endId] = ending;
}
event ProofSubmitted(ProofId id, bytes proof);
}