-
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.
feat: Create proof of liveness smart contract (#190)
* Create proof of liveness smart contract * refactor to constants * add test * remove unused code * deploy to mainnet
- Loading branch information
1 parent
9e35108
commit b7ae18a
Showing
4 changed files
with
211 additions
and
2 deletions.
There are no files selected for viewing
81 changes: 81 additions & 0 deletions
81
packages/zevm-app-contracts/contracts/proof-of-liveness/ProofOfLiveness.sol
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,81 @@ | ||
// 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 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); | ||
|
||
// Event to log when liveness is proved | ||
event LivenessProved(address indexed user, uint256 proofTimestamp); | ||
|
||
// The function to prove liveness, can only be called once every PROOF_PERIOD | ||
function proveLiveness() external { | ||
uint256 currentTime = block.timestamp; | ||
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); | ||
} | ||
|
||
// 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); | ||
} | ||
|
||
// 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 >= 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; | ||
bool[LAST_PERIODS_LENGTH] memory proofStatus; | ||
|
||
for (uint256 i = 0; i < LAST_PERIODS_LENGTH; i++) { | ||
// 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; | ||
} | ||
|
||
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
33 changes: 33 additions & 0 deletions
33
packages/zevm-app-contracts/scripts/proof-of-liveness/deploy.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,33 @@ | ||
import { isProtocolNetworkName } from "@zetachain/protocol-contracts"; | ||
import { ethers, network } from "hardhat"; | ||
|
||
import { ProofOfLiveness__factory } from "../../typechain-types"; | ||
import { saveAddress } from "../address.helpers"; | ||
import { verifyContract } from "../explorer.helpers"; | ||
|
||
const networkName = network.name; | ||
|
||
const deployProofOfLiveness = async () => { | ||
if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); | ||
|
||
const ProofOfLivenessFactory = (await ethers.getContractFactory("ProofOfLiveness")) as ProofOfLiveness__factory; | ||
const ProofOfLiveness = await ProofOfLivenessFactory.deploy(); | ||
|
||
await ProofOfLiveness.deployed(); | ||
|
||
console.log("ProofOfLiveness deployed to:", ProofOfLiveness.address); | ||
|
||
saveAddress("ProofOfLiveness", ProofOfLiveness.address, networkName); | ||
|
||
await verifyContract(ProofOfLiveness.address, []); | ||
}; | ||
|
||
const main = async () => { | ||
if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); | ||
await deployProofOfLiveness(); | ||
}; | ||
|
||
main().catch((error) => { | ||
console.error(error); | ||
process.exit(1); | ||
}); |
93 changes: 93 additions & 0 deletions
93
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,93 @@ | ||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | ||
import { expect } from "chai"; | ||
import { ethers } from "hardhat"; | ||
|
||
import { ProofOfLiveness } from "../../typechain-types"; | ||
|
||
const PROOF_PERIOD = 24 * 60 * 60; // 24 hours in seconds | ||
|
||
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 () => { | ||
// 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 () => { | ||
// 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"); | ||
}); | ||
}); |