-
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
a857666
commit 764cb2d
Showing
7 changed files
with
320 additions
and
5 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
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
66 changes: 66 additions & 0 deletions
66
packages/zevm-app-contracts/contracts/zeta-points/InvitationManagerV2.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,66 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.20; | ||
|
||
import "@openzeppelin/contracts/utils/cryptography/EIP712.sol"; | ||
import {SignatureChecker} from "@openzeppelin/contracts/utils/cryptography/SignatureChecker.sol"; | ||
|
||
import "./InvitationManager.sol"; | ||
|
||
contract InvitationManagerV2 is EIP712 { | ||
bytes32 private constant VERIFY_TYPEHASH = keccak256("Verify(address to,uint256 signatureExpiration)"); | ||
|
||
InvitationManager public immutable invitationManager; | ||
|
||
struct VerifyData { | ||
address to; | ||
bytes signature; | ||
uint256 signatureExpiration; | ||
} | ||
|
||
// Records the timestamp when a particular user gets verified. | ||
mapping(address => uint256) public userVerificationTimestamps; | ||
|
||
error UserAlreadyVerified(); | ||
error SignatureExpired(); | ||
error InvalidSigner(); | ||
|
||
event UserVerified(address indexed userAddress, uint256 verifiedAt, uint256 unix_timestamp); | ||
|
||
constructor(InvitationManager _invitationManager) EIP712("InvitationManagerV2", "1") { | ||
invitationManager = _invitationManager; | ||
} | ||
|
||
function _markAsVerified(address user) internal { | ||
// Check if the user is already verified | ||
if (hasBeenVerified(user)) revert UserAlreadyVerified(); | ||
|
||
userVerificationTimestamps[user] = block.timestamp; | ||
emit UserVerified(user, block.timestamp, block.timestamp); | ||
} | ||
|
||
function markAsVerified() external { | ||
_markAsVerified(msg.sender); | ||
} | ||
|
||
function hasBeenVerified(address userAddress) public view returns (bool) { | ||
if (userVerificationTimestamps[userAddress] > 0) return true; | ||
if (address(invitationManager) != address(0) && invitationManager.hasBeenVerified(userAddress)) return true; | ||
return false; | ||
} | ||
Check notice Code scanning / Slither Block timestamp Low
InvitationManagerV2.hasBeenVerified(address) uses timestamp for comparisons
Dangerous comparisons: - userVerificationTimestamps[userAddress] > 0 |
||
|
||
function _verify(VerifyData memory claimData) private view { | ||
bytes32 structHash = keccak256(abi.encode(VERIFY_TYPEHASH, claimData.to, claimData.signatureExpiration)); | ||
bytes32 constructedHash = _hashTypedDataV4(structHash); | ||
|
||
if (!SignatureChecker.isValidSignatureNow(claimData.to, constructedHash, claimData.signature)) { | ||
revert InvalidSigner(); | ||
} | ||
|
||
if (block.timestamp > claimData.signatureExpiration) revert SignatureExpired(); | ||
} | ||
Check notice Code scanning / Slither Block timestamp Low
InvitationManagerV2._verify(InvitationManagerV2.VerifyData) uses timestamp for comparisons
Dangerous comparisons: - block.timestamp > claimData.signatureExpiration |
||
|
||
function markAsVerifiedWithSignature(VerifyData memory data) external { | ||
_verify(data); | ||
_markAsVerified(data.to); | ||
} | ||
} |
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/zeta-points/deploy-invitationV2.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 { InvitationManagerV2__factory } from "../../typechain-types"; | ||
import { getZEVMAppAddress, saveAddress } from "../address.helpers"; | ||
import { verifyContract } from "../explorer.helpers"; | ||
|
||
const networkName = network.name; | ||
|
||
const invitationManager = async () => { | ||
if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); | ||
|
||
const invitationManagerV1 = getZEVMAppAddress("invitationManager", networkName); | ||
|
||
const InvitationManagerFactory = (await ethers.getContractFactory( | ||
"InvitationManagerV2" | ||
)) as InvitationManagerV2__factory; | ||
const invitationManager = await InvitationManagerFactory.deploy(invitationManagerV1); | ||
await invitationManager.deployed(); | ||
console.log("InvitationManagerV2 deployed to:", invitationManager.address); | ||
saveAddress("invitationManagerV2", invitationManager.address, networkName); | ||
await verifyContract(invitationManager.address, [invitationManagerV1]); | ||
}; | ||
|
||
const main = async () => { | ||
if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); | ||
await invitationManager(); | ||
}; | ||
|
||
main().catch((error) => { | ||
console.error(error); | ||
process.exit(1); | ||
}); |
175 changes: 175 additions & 0 deletions
175
packages/zevm-app-contracts/test/zeta-points/InvitationManagerV2.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,175 @@ | ||
import { expect, use } from "chai"; | ||
import { solidity } from "ethereum-waffle"; | ||
use(solidity); | ||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | ||
import { ethers } from "hardhat"; | ||
|
||
import { InvitationManager, InvitationManagerV2 } from "../../typechain-types"; | ||
import { EnrollmentSigned, getEnrollmentSignature } from "./invitationManager.helpers"; | ||
|
||
const HARDHAT_CHAIN_ID = 1337; | ||
|
||
describe("InvitationManagerV2 Contract test", () => { | ||
let invitationManager: InvitationManager, | ||
invitationManagerV2: InvitationManagerV2, | ||
signer: SignerWithAddress, | ||
user: SignerWithAddress, | ||
addrs: SignerWithAddress[]; | ||
|
||
beforeEach(async () => { | ||
[signer, user, ...addrs] = await ethers.getSigners(); | ||
const InvitationManager = await ethers.getContractFactory("InvitationManager"); | ||
//@ts-ignore | ||
invitationManager = await InvitationManager.deploy(); | ||
|
||
const InvitationManagerV2 = await ethers.getContractFactory("InvitationManagerV2"); | ||
//@ts-ignore | ||
invitationManagerV2 = await InvitationManagerV2.deploy(invitationManager.address); | ||
}); | ||
|
||
const getTomorrowTimestamp = async () => { | ||
const block = await ethers.provider.getBlock("latest"); | ||
const now = block.timestamp; | ||
const tomorrow = now + 24 * 60 * 60; | ||
return tomorrow; | ||
}; | ||
|
||
describe("True", () => { | ||
it("Should be true", async () => { | ||
expect(true).to.equal(true); | ||
}); | ||
}); | ||
|
||
describe("Invitations test", () => { | ||
it("Should do enrollment", async () => { | ||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(false); | ||
} | ||
|
||
await invitationManagerV2.connect(user).markAsVerified(); | ||
|
||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(true); | ||
} | ||
}); | ||
|
||
it("Should execute enrollement with from other", async () => { | ||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(false); | ||
} | ||
|
||
const signatureExpiration = await getTomorrowTimestamp(); | ||
const signature = await getEnrollmentSignature( | ||
HARDHAT_CHAIN_ID, | ||
invitationManagerV2.address, | ||
user, | ||
signatureExpiration, | ||
user.address | ||
); | ||
const enrollementParams: EnrollmentSigned = { | ||
signature, | ||
signatureExpiration, | ||
to: user.address, | ||
} as EnrollmentSigned; | ||
|
||
await invitationManagerV2.markAsVerifiedWithSignature(enrollementParams); | ||
|
||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(true); | ||
} | ||
}); | ||
|
||
it("Should check if user was enroll in previus version", async () => { | ||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(false); | ||
} | ||
|
||
await invitationManager.connect(user).markAsVerified(); | ||
|
||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(true); | ||
} | ||
}); | ||
|
||
it("Should fail if try to enroll somebody else", async () => { | ||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(false); | ||
} | ||
|
||
const signatureExpiration = await getTomorrowTimestamp(); | ||
const signature = await getEnrollmentSignature( | ||
HARDHAT_CHAIN_ID, | ||
invitationManagerV2.address, | ||
user, | ||
signatureExpiration, | ||
user.address | ||
); | ||
const enrollementParams: EnrollmentSigned = { | ||
signature, | ||
signatureExpiration, | ||
to: signer.address, | ||
} as EnrollmentSigned; | ||
|
||
const tx = invitationManagerV2.markAsVerifiedWithSignature(enrollementParams); | ||
await expect(tx).to.be.revertedWith("InvalidSigner"); | ||
}); | ||
|
||
it("Should fail if try to enroll and was already enrolled", async () => { | ||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(false); | ||
} | ||
|
||
const signatureExpiration = await getTomorrowTimestamp(); | ||
const signature = await getEnrollmentSignature( | ||
HARDHAT_CHAIN_ID, | ||
invitationManagerV2.address, | ||
user, | ||
signatureExpiration, | ||
user.address | ||
); | ||
const enrollementParams: EnrollmentSigned = { | ||
signature, | ||
signatureExpiration, | ||
to: user.address, | ||
} as EnrollmentSigned; | ||
|
||
await invitationManagerV2.markAsVerifiedWithSignature(enrollementParams); | ||
|
||
const tx = invitationManagerV2.markAsVerifiedWithSignature(enrollementParams); | ||
await expect(tx).to.be.revertedWith("UserAlreadyVerified"); | ||
}); | ||
|
||
it("Should fail if try to enroll and was enrolled with previus contract", async () => { | ||
{ | ||
const hasBeenVerifiedBefore = await invitationManagerV2.hasBeenVerified(user.address); | ||
await expect(hasBeenVerifiedBefore).to.be.eq(false); | ||
} | ||
|
||
await invitationManager.connect(user).markAsVerified(); | ||
const signatureExpiration = await getTomorrowTimestamp(); | ||
const signature = await getEnrollmentSignature( | ||
HARDHAT_CHAIN_ID, | ||
invitationManagerV2.address, | ||
user, | ||
signatureExpiration, | ||
user.address | ||
); | ||
const enrollementParams: EnrollmentSigned = { | ||
signature, | ||
signatureExpiration, | ||
to: user.address, | ||
} as EnrollmentSigned; | ||
|
||
const tx = invitationManagerV2.markAsVerifiedWithSignature(enrollementParams); | ||
await expect(tx).to.be.revertedWith("UserAlreadyVerified"); | ||
}); | ||
}); | ||
}); |
40 changes: 40 additions & 0 deletions
40
packages/zevm-app-contracts/test/zeta-points/invitationManager.helpers.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,40 @@ | ||
import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; | ||
|
||
export interface Enrollment { | ||
to: string; | ||
} | ||
|
||
export interface EnrollmentSigned extends Enrollment { | ||
signature: string; | ||
signatureExpiration: number; | ||
} | ||
|
||
export const getEnrollmentSignature = async ( | ||
chainId: number, | ||
verifyingContract: string, | ||
signer: SignerWithAddress, | ||
signatureExpiration: number, | ||
to: string | ||
) => { | ||
const domain = { | ||
chainId: chainId, | ||
name: "InvitationManagerV2", | ||
verifyingContract: verifyingContract, | ||
version: "1", | ||
}; | ||
|
||
const types = { | ||
Verify: [ | ||
{ name: "to", type: "address" }, | ||
{ name: "signatureExpiration", type: "uint256" }, | ||
], | ||
}; | ||
|
||
const value = { | ||
signatureExpiration, | ||
to, | ||
}; | ||
// Signing the data | ||
const signature = await signer._signTypedData(domain, types, value); | ||
return signature; | ||
}; |