-
Notifications
You must be signed in to change notification settings - Fork 230
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
0247c3b
commit 8ef87b0
Showing
3 changed files
with
138 additions
and
21 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,12 +1,14 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "hardhat/console.sol"; | ||
|
||
contract ProofOfLiveness { | ||
uint256 constant PROOF_PERIOD = 24 hours; | ||
uint256 constant LAST_PERIODS_LENGTH = 5; | ||
|
||
// Mapping to track the last time the user proved liveness | ||
mapping(address => uint256) public lastProofTimestamp; | ||
// Mapping to track the proof history for each user (last 5 proof timestamps) | ||
mapping(address => uint256[LAST_PERIODS_LENGTH]) public proofHistory; | ||
|
||
// Custom error for when a user has already proved liveness within the last PROOF_PERIOD | ||
error ProofWithinLast24Hours(uint256 lastProofTime); | ||
|
@@ -17,47 +19,63 @@ contract ProofOfLiveness { | |
// The function to prove liveness, can only be called once every PROOF_PERIOD | ||
function proveLiveness() external { | ||
uint256 currentTime = block.timestamp; | ||
uint256 lastProofTime = lastProofTimestamp[msg.sender]; | ||
uint256 lastProofTime = proofHistory[msg.sender][0]; // The most recent proof timestamp is always stored in the first position | ||
|
||
// Check if the user has proved liveness within the last PROOF_PERIOD | ||
if (currentTime < lastProofTime + PROOF_PERIOD) { | ||
revert ProofWithinLast24Hours(lastProofTime); | ||
} | ||
|
||
// Update the last proof timestamp for the user | ||
lastProofTimestamp[msg.sender] = currentTime; | ||
// Shift the proof history and add the new timestamp | ||
_updateProofHistory(msg.sender, currentTime); | ||
|
||
// Emit an event to track the liveness proof | ||
emit LivenessProved(msg.sender, currentTime); | ||
} | ||
Check notice Code scanning / Slither Block timestamp Low
ProofOfLiveness.proveLiveness() uses timestamp for comparisons
Dangerous comparisons: - currentTime < lastProofTime + PROOF_PERIOD |
||
|
||
// Helper function to check if a user can prove liveness (returns true if PROOF_PERIOD have passed) | ||
// Helper function to check if a user can prove liveness (returns true if PROOF_PERIOD has passed) | ||
function canProveLiveness(address user) external view returns (bool) { | ||
uint256 currentTime = block.timestamp; | ||
return currentTime >= lastProofTimestamp[user] + PROOF_PERIOD; | ||
return currentTime >= proofHistory[user][0] + PROOF_PERIOD; | ||
} | ||
|
||
// View function to return the liveness proof status for the last LAST_PERIODS_LENGTH periods (each PROOF_PERIOD long) | ||
function getLastPeriodsStatus(address user) external view returns (bool[LAST_PERIODS_LENGTH] memory) { | ||
uint256 currentTime = block.timestamp; | ||
uint256 lastProofTime = lastProofTimestamp[user]; | ||
|
||
bool[LAST_PERIODS_LENGTH] memory proofStatus; | ||
uint256 periodDuration = PROOF_PERIOD; | ||
|
||
for (uint256 i = 0; i < LAST_PERIODS_LENGTH; i++) { | ||
// Calculate the start of the period (going back i * PROOF_PERIOD) | ||
uint256 periodStart = currentTime - (i * periodDuration); | ||
uint256 periodEnd = periodStart - periodDuration; | ||
|
||
// If the last proof timestamp falls within this period, mark it as true | ||
if (lastProofTime >= periodEnd && lastProofTime < periodStart) { | ||
proofStatus[i] = true; | ||
} else { | ||
proofStatus[i] = false; | ||
} | ||
// Calculate the end of the period (going back i * PROOF_PERIOD) | ||
uint256 periodEnd = currentTime - (i * PROOF_PERIOD); | ||
uint256 periodStart = periodEnd - PROOF_PERIOD - 1; | ||
// If the proof timestamp falls within this period, mark it as true | ||
proofStatus[i] = hasProofedAt(user, periodStart, periodEnd); | ||
} | ||
|
||
return proofStatus; | ||
} | ||
|
||
function hasProofedAt(address user, uint256 periodStart, uint256 periodEnd) public view returns (bool) { | ||
for (uint256 i = 0; i < LAST_PERIODS_LENGTH; i++) { | ||
if (proofHistory[user][i] >= periodStart && proofHistory[user][i] < periodEnd) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
Check notice Code scanning / Slither Block timestamp Low
ProofOfLiveness.hasProofedAt(address,uint256,uint256) uses timestamp for comparisons
Dangerous comparisons: - proofHistory[user][i] >= periodStart && proofHistory[user][i] < periodEnd |
||
|
||
function getProofHistory(address user) external view returns (uint256[LAST_PERIODS_LENGTH] memory) { | ||
return proofHistory[user]; | ||
} | ||
|
||
// Internal function to update the user's proof history by shifting timestamps and adding the new proof | ||
function _updateProofHistory(address user, uint256 newProofTimestamp) internal { | ||
// Shift the history to the right | ||
for (uint256 i = LAST_PERIODS_LENGTH - 1; i > 0; i--) { | ||
proofHistory[user][i] = proofHistory[user][i - 1]; | ||
} | ||
|
||
// Add the new timestamp in the first position | ||
proofHistory[user][0] = newProofTimestamp; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
99 changes: 99 additions & 0 deletions
99
packages/zevm-app-contracts/test/proof-of-liveness/proof-of-liveness.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,99 @@ | ||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | ||
import { expect } from "chai"; | ||
import { BigNumber, utils } from "ethers"; | ||
import { parseEther } from "ethers/lib/utils"; | ||
import { ethers } from "hardhat"; | ||
|
||
import { ProofOfLiveness } from "../../typechain-types"; | ||
|
||
const HARDHAT_CHAIN_ID = 1337; | ||
|
||
describe("Proof Of Liveness Contract test", () => { | ||
let proofOfLiveness: ProofOfLiveness, | ||
owner: SignerWithAddress, | ||
signer: SignerWithAddress, | ||
user: SignerWithAddress, | ||
addrs: SignerWithAddress[]; | ||
|
||
beforeEach(async () => { | ||
[owner, signer, user, ...addrs] = await ethers.getSigners(); | ||
const ProofOfLivenessFactory = await ethers.getContractFactory("ProofOfLiveness"); | ||
|
||
proofOfLiveness = await ProofOfLivenessFactory.deploy(); | ||
|
||
await proofOfLiveness.deployed(); | ||
}); | ||
|
||
it("Should proof", async () => { | ||
const tx = await proofOfLiveness.proveLiveness(); | ||
|
||
const receipt = await tx.wait(); | ||
const blockTimestamp = (await ethers.provider.getBlock(receipt.blockNumber)).timestamp; | ||
|
||
await expect(tx).to.emit(proofOfLiveness, "LivenessProved").withArgs(owner.address, blockTimestamp); | ||
}); | ||
|
||
it("Should proof 5 times every 24 hours and return correct view values", async () => { | ||
const PROOF_PERIOD = 24 * 60 * 60; // 24 hours in seconds | ||
|
||
// Prove liveness 5 times | ||
for (let i = 0; i < 5; i++) { | ||
// Call the proveLiveness function | ||
const tx = await proofOfLiveness.proveLiveness(); | ||
await tx.wait(); | ||
|
||
// Increase the time by 24 hours in the EVM | ||
await ethers.provider.send("evm_increaseTime", [PROOF_PERIOD]); | ||
await ethers.provider.send("evm_mine", []); // Mine a new block to apply the time change | ||
} | ||
|
||
// Now check the getLastPeriodsStatus for the owner | ||
const periodsStatus = await proofOfLiveness.getLastPeriodsStatus(owner.address); | ||
|
||
// We expect that all 5 periods should return true | ||
expect(periodsStatus).to.deep.equal([true, true, true, true, true]); | ||
}); | ||
|
||
it("Should proof 5 times every 24 hours and return correct view values if one day is missing", async () => { | ||
const PROOF_PERIOD = 24 * 60 * 60; // 24 hours in seconds | ||
|
||
// Prove liveness 5 times | ||
for (let i = 0; i < 5; i++) { | ||
// Call the proveLiveness function if day is not 3 | ||
if (i !== 3) { | ||
const tx = await proofOfLiveness.proveLiveness(); | ||
await tx.wait(); | ||
} | ||
|
||
// Increase the time by 24 hours in the EVM | ||
await ethers.provider.send("evm_increaseTime", [PROOF_PERIOD]); | ||
await ethers.provider.send("evm_mine", []); // Mine a new block to apply the time change | ||
} | ||
|
||
// Now check the getLastPeriodsStatus for the owner | ||
const periodsStatus = await proofOfLiveness.getLastPeriodsStatus(owner.address); | ||
|
||
// We expect that all 5 periods should return true but 3 | ||
expect(periodsStatus).to.deep.equal([true, false, true, true, true]); | ||
}); | ||
|
||
it("Should proof view return if only one day was proof", async () => { | ||
const tx = await proofOfLiveness.proveLiveness(); | ||
await tx.wait(); | ||
await ethers.provider.send("evm_mine", []); // Mine a new block to apply the time change | ||
|
||
// Now check the getLastPeriodsStatus for the owner | ||
const periodsStatus = await proofOfLiveness.getLastPeriodsStatus(owner.address); | ||
|
||
expect(periodsStatus).to.deep.equal([true, false, false, false, false]); | ||
}); | ||
|
||
it("Should revert if trying to prove twice in less than 24 hours", async () => { | ||
// Prove liveness for the first time | ||
await proofOfLiveness.proveLiveness(); | ||
|
||
const tx = proofOfLiveness.proveLiveness(); | ||
|
||
await expect(tx).to.be.revertedWith("ProofWithinLast24Hours"); | ||
}); | ||
}); |