Skip to content

Commit

Permalink
feat: new enrollment contract
Browse files Browse the repository at this point in the history
  • Loading branch information
andresaiello committed Nov 5, 2024
1 parent a857666 commit 764cb2d
Show file tree
Hide file tree
Showing 7 changed files with 320 additions and 5 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"statusBarItem.hoverBackground": "#71C87D"
},
"editor.codeActionsOnSave": {
"source.fixAll.eslint": true
"source.fixAll.eslint": "explicit"
},
"[solidity]": {
"editor.tabSize": 4,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// SPDX-License-Identifier: MIT
pragma solidity 0.8.7;
pragma solidity ^0.8.20;

contract InvitationManager {
/* An ECDSA signature. */
Expand Down Expand Up @@ -69,7 +69,7 @@ contract InvitationManager {
return userVerificationTimestamps[userAddress];
}

function _verifySignature(address inviter, uint256 expiration, Signature calldata signature) private pure {
function _verifySignature(address inviter, uint256 expiration, Signature calldata signature) internal pure {
bytes32 payloadHash = keccak256(abi.encode(inviter, expiration));
bytes32 messageHash = keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", payloadHash));

Expand Down
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


function markAsVerifiedWithSignature(VerifyData memory data) external {
_verify(data);
_markAsVerified(data.to);
}
}
5 changes: 3 additions & 2 deletions packages/zevm-app-contracts/data/addresses.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"zevm": {
"zeta_testnet": {
"disperse": "0x23ce409Ea60c3d75827d04D9db3d52F3af62e44d",
"disperse": "0x049893Bd0fC4923FC1B1136Ef2ac996C55D4942C",
"rewardDistributorFactory": "0xB9dc665610CF5109cE23aBBdaAc315B41FA094c1",
"zetaSwap": "0xA8168Dc495Ed61E70f5c1941e2860050AB902cEF",
"zetaSwapBtcInbound": "0x358E2cfC0E16444Ba7D3164Bbeeb6bEA7472c559",
Expand All @@ -11,7 +11,8 @@
"InstantRewards": "0x10DfEd4ba9b8F6a1c998E829FfC0325D533c80E3",
"ProofOfLiveness": "0x981EB6fD19717Faf293Fba0cBD05C6Ac97b8C808",
"TimelockController": "0x44139C2150c11c25f517B8a8F974b59C82aEe709",
"ZetaXPGov": "0x854032d484aE21acC34F36324E55A8080F21Af12"
"ZetaXPGov": "0x854032d484aE21acC34F36324E55A8080F21Af12",
"invitationManagerV2": "0xb80f6360194Dd6B47B80bd8730b3dBb05a39e723"
},
"zeta_mainnet": {
"disperse": "0x23ce409Ea60c3d75827d04D9db3d52F3af62e44d",
Expand Down
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 packages/zevm-app-contracts/test/zeta-points/InvitationManagerV2.ts
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");
});
});
});
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;
};

0 comments on commit 764cb2d

Please sign in to comment.