From 5715168ed3f787cb381a3af79415406e81eb98db Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Thu, 31 Oct 2024 12:17:54 -0300 Subject: [PATCH 1/5] feat: 1 smart contract per activity --- .../instant-rewards/InstantRewards.sol | 4 +- .../instant-rewards/InstantRewardsFactory.sol | 21 ++ .../instant-rewards/InstantRewardsV2.sol | 54 +++ .../instant-rewards-v2-compatibility.ts | 337 ++++++++++++++++++ 4 files changed, 414 insertions(+), 2 deletions(-) create mode 100644 packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol create mode 100644 packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsV2.sol create mode 100644 packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2-compatibility.ts diff --git a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol index f06efe6..9b022a2 100644 --- a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol +++ b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol @@ -52,7 +52,7 @@ contract InstantRewards is Ownable2Step, Pausable, ReentrancyGuard, EIP712 { if (block.timestamp > claimData.sigExpiration) revert SignatureExpired(); } - function claim(ClaimData memory claimData) external nonReentrant whenNotPaused { + function claim(ClaimData memory claimData) public virtual nonReentrant whenNotPaused { claimData.to = msg.sender; _verify(claimData); @@ -72,7 +72,7 @@ contract InstantRewards is Ownable2Step, Pausable, ReentrancyGuard, EIP712 { emit SignerUpdated(signerAddress_); } - function withdraw(address wallet, uint256 amount) external onlyOwner { + function withdraw(address wallet, uint256 amount) public virtual onlyOwner { if (wallet == address(0)) revert InvalidAddress(); if (amount > address(this).balance) revert TransferFailed(); (bool success, ) = wallet.call{value: amount}(""); diff --git a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol new file mode 100644 index 0000000..1674b28 --- /dev/null +++ b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol @@ -0,0 +1,21 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "@openzeppelin/contracts/access/Ownable2Step.sol"; +import "./InstantRewardsV2.sol"; + +contract InstantRewardsFactory is Ownable2Step { + event InstantRewardsCreated(address indexed instantRewards, address indexed owner); + + function createInstantRewards( + address signerAddress, + uint256 start, + uint256 end, + string memory name + ) external returns (address) { + InstantRewardsV2 instantRewards = new InstantRewardsV2(signerAddress, owner(), start, end, name); + instantRewards.transferOwnership(owner()); + emit InstantRewardsCreated(address(instantRewards), owner()); + return address(instantRewards); + } +} diff --git a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsV2.sol b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsV2.sol new file mode 100644 index 0000000..8750088 --- /dev/null +++ b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsV2.sol @@ -0,0 +1,54 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import "./InstantRewards.sol"; + +contract InstantRewardsV2 is InstantRewards { + string public name; + + uint256 public start; + uint256 public end; + + event TimeframeUpdated(uint256 start, uint256 end); + + error InvalidTimeframe(); + error InstantRewardNotActive(); + error InstantRewardStillActive(); + + constructor( + address signerAddress_, + address owner, + uint256 start_, + uint256 end_, + string memory name_ + ) InstantRewards(signerAddress_, owner) { + if (signerAddress_ == address(0)) revert InvalidAddress(); + if (start_ > end_) revert InvalidTimeframe(); + start = start_; + end = end_; + name = name_; + } + + function isActive() public view returns (bool) { + return block.timestamp >= start && block.timestamp <= end; + } + + function setTimeframe(uint256 start_, uint256 end_) external onlyOwner { + if (start_ > end_) revert InvalidTimeframe(); + if (start_ < block.timestamp || end_ < block.timestamp) revert InvalidTimeframe(); + if (isActive()) revert InstantRewardStillActive(); + start = start_; + end = end_; + emit TimeframeUpdated(start_, end_); + } + + function claim(ClaimData memory claimData) public override { + if (!isActive()) revert InstantRewardNotActive(); + super.claim(claimData); + } + + function withdraw(address wallet, uint256 amount) public override { + if (isActive()) revert InstantRewardStillActive(); + super.withdraw(wallet, amount); + } +} diff --git a/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2-compatibility.ts b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2-compatibility.ts new file mode 100644 index 0000000..bfc21ff --- /dev/null +++ b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2-compatibility.ts @@ -0,0 +1,337 @@ +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 { InstantRewardsV2 } from "../../typechain-types"; +import { ClaimData, getSignature } from "./test.helpers"; + +const HARDHAT_CHAIN_ID = 1337; + +describe("Instant Rewards Contract test", () => { + let instantRewards: InstantRewardsV2, + owner: SignerWithAddress, + signer: SignerWithAddress, + user: SignerWithAddress, + addrs: SignerWithAddress[]; + + const encodeTaskId = (taskId: string) => utils.keccak256(utils.defaultAbiCoder.encode(["string"], [taskId])); + + const getClaimDataSigned = async ( + chainId: number, + verifyingContract: string, + signer: SignerWithAddress, + amount: BigNumber, + sigExpiration: number, + taskId: string, + to: string + ) => { + const claimData: ClaimData = { + amount, + sigExpiration, + taskId, + to, + }; + + const signature = await getSignature(chainId, verifyingContract, signer, claimData); + return { + ...claimData, + signature, + }; + }; + + beforeEach(async () => { + [owner, signer, user, ...addrs] = await ethers.getSigners(); + const instantRewardsFactory = await ethers.getContractFactory("InstantRewardsV2"); + + const start = (await ethers.provider.getBlock("latest")).timestamp + 1; + const end = start + 1000; + + instantRewards = await instantRewardsFactory.deploy(signer.address, owner.address, start, end, "Instant Rewards"); + + await instantRewards.deployed(); + }); + + it("Should claim", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigExpiration = currentBlock.timestamp + 1000; + const amount = utils.parseEther("1"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); + const to = owner.address; + + // transfer some funds to the contract + await owner.sendTransaction({ + to: instantRewards.address, + value: amount, + }); + + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration, + taskId, + to + ); + + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.emit(instantRewards, "Claimed").withArgs(owner.address, taskId, amount); + }); + + it("Should claim if pause and unpause", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigExpiration = currentBlock.timestamp + 1000; + const amount = utils.parseEther("1"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); + const to = owner.address; + + await instantRewards.pause(); + await instantRewards.unpause(); + + // transfer some funds to the contract + await owner.sendTransaction({ + to: instantRewards.address, + value: amount, + }); + + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration, + taskId, + to + ); + + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.emit(instantRewards, "Claimed").withArgs(owner.address, taskId, amount); + }); + + it("Should revert if try to claim on behalf of somebody else", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigExpiration = currentBlock.timestamp + 1000; + const amount = utils.parseEther("1"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); + const to = user.address; + + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration, + taskId, + to + ); + + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.revertedWith("InvalidSigner"); + }); + + it("Should revert if try to claim with an expired signature", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigExpiration = currentBlock.timestamp - 1000; + const amount = utils.parseEther("1"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); + const to = owner.address; + + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration, + taskId, + to + ); + + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.revertedWith("SignatureExpired"); + }); + + it("Should revert if try to claim when contract it's paused", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigExpiration = currentBlock.timestamp + 1000; + const amount = utils.parseEther("1"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); + const to = owner.address; + + await instantRewards.pause(); + + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration, + taskId, + to + ); + + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.revertedWith("Pausable: paused"); + }); + + it("Should revert if try to claim twice with same signature", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigExpiration = currentBlock.timestamp + 1000; + const amount = utils.parseEther("1"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); + const to = owner.address; + + // transfer some funds to the contract + await owner.sendTransaction({ + to: instantRewards.address, + value: amount, + }); + + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration, + taskId, + to + ); + + instantRewards.claim(claimDataSigned); + + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.revertedWith("TaskAlreadyClaimed"); + }); + + it("Should revert if try to claim same task with another signature", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigExpiration = currentBlock.timestamp + 1000; + const amount = utils.parseEther("1"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); + const to = owner.address; + + // transfer some funds to the contract + await owner.sendTransaction({ + to: instantRewards.address, + value: amount, + }); + + { + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration, + taskId, + to + ); + instantRewards.claim(claimDataSigned); + } + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount.add(parseEther("1")), + sigExpiration, + taskId, + to + ); + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.revertedWith("TaskAlreadyClaimed"); + }); + + it("Should revert if try to claim with an old valid signature if a new one was used", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const sigExpiration = currentBlock.timestamp + 1000; + const amount = utils.parseEther("2"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); + const to = owner.address; + + // transfer some funds to the contract + await owner.sendTransaction({ + to: instantRewards.address, + value: amount, + }); + + const claimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration, + taskId, + to + ); + + const newClaimDataSigned = await getClaimDataSigned( + HARDHAT_CHAIN_ID, + instantRewards.address, + signer, + amount, + sigExpiration + 1000, + taskId, + to + ); + + instantRewards.claim(newClaimDataSigned); + + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.revertedWith("TaskAlreadyClaimed"); + }); + + it("Should revert if not owner try to pause", async () => { + const tx = instantRewards.connect(user).pause(); + await expect(tx).to.revertedWith("Ownable: caller is not the owner"); + }); + + it("Should transfer ownership", async () => { + { + const ownerAddr = await instantRewards.owner(); + expect(ownerAddr).to.be.eq(owner.address); + } + await instantRewards.transferOwnership(user.address); + await instantRewards.connect(user).acceptOwnership(); + { + const ownerAddr = await instantRewards.owner(); + expect(ownerAddr).to.be.eq(user.address); + } + }); + + it("Should withdraw by owner", async () => { + await ethers.provider.send("evm_increaseTime", [7200]); // Fast forward 2 hours to ensure voting delay is over + await ethers.provider.send("evm_mine", []); // Mine the next block + + const amount = utils.parseEther("2"); + const amountToWithdraw = utils.parseEther("1"); + // transfer some funds to the contract + await owner.sendTransaction({ + to: instantRewards.address, + value: amount, + }); + + const userBalanceBefore = await ethers.provider.getBalance(user.address); + + const tx = instantRewards.withdraw(user.address, amountToWithdraw); + await expect(tx).to.emit(instantRewards, "Withdrawn").withArgs(user.address, amountToWithdraw); + + const balanceOfContract = await ethers.provider.getBalance(instantRewards.address); + expect(balanceOfContract).to.be.eq(amount.sub(amountToWithdraw)); + const balanceOfUser = await ethers.provider.getBalance(user.address); + expect(balanceOfUser).to.be.eq(userBalanceBefore.add(amountToWithdraw)); + }); + + it("Should fail if try to withdraw an active IR", async () => { + const amount = utils.parseEther("2"); + const amountToWithdraw = utils.parseEther("1"); + // transfer some funds to the contract + await owner.sendTransaction({ + to: instantRewards.address, + value: amount, + }); + + const tx = instantRewards.withdraw(user.address, amountToWithdraw); + await expect(tx).to.revertedWith("InstantRewardStillActive"); + }); +}); From ae9aad8c39842cb7187717045329d2f3b33484df Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Wed, 6 Nov 2024 14:08:27 -0300 Subject: [PATCH 2/5] add test --- .../instant-rewards/InstantRewardsFactory.sol | 14 +++ .../instant-rewards/InstantRewardsV2.sol | 2 +- .../instant-rewards-v2-compatibility.ts | 8 +- .../instant-rewards/instant-rewards-v2.ts | 94 +++++++++++++++++++ 4 files changed, 113 insertions(+), 5 deletions(-) create mode 100644 packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2.ts diff --git a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol index 1674b28..0c69f89 100644 --- a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol +++ b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol @@ -5,14 +5,28 @@ import "@openzeppelin/contracts/access/Ownable2Step.sol"; import "./InstantRewardsV2.sol"; contract InstantRewardsFactory is Ownable2Step { + bool public allowPublicCreation = false; + + error AccessDenied(); event InstantRewardsCreated(address indexed instantRewards, address indexed owner); + constructor(address owner) Ownable() { + transferOwnership(owner); + } + + function setAllowPublicCreation(bool allowPublicCreation_) external onlyOwner { + allowPublicCreation = allowPublicCreation_; + } + function createInstantRewards( address signerAddress, uint256 start, uint256 end, string memory name ) external returns (address) { + bool isOwner = owner() == msg.sender; + if (!allowPublicCreation && !isOwner) revert AccessDenied(); + InstantRewardsV2 instantRewards = new InstantRewardsV2(signerAddress, owner(), start, end, name); instantRewards.transferOwnership(owner()); emit InstantRewardsCreated(address(instantRewards), owner()); diff --git a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsV2.sol b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsV2.sol index 8750088..145eeeb 100644 --- a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsV2.sol +++ b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsV2.sol @@ -47,7 +47,7 @@ contract InstantRewardsV2 is InstantRewards { super.claim(claimData); } - function withdraw(address wallet, uint256 amount) public override { + function withdraw(address wallet, uint256 amount) public override onlyOwner { if (isActive()) revert InstantRewardStillActive(); super.withdraw(wallet, amount); } diff --git a/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2-compatibility.ts b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2-compatibility.ts index bfc21ff..7ac1ff1 100644 --- a/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2-compatibility.ts +++ b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2-compatibility.ts @@ -9,7 +9,7 @@ import { ClaimData, getSignature } from "./test.helpers"; const HARDHAT_CHAIN_ID = 1337; -describe("Instant Rewards Contract test", () => { +describe("Instant Rewards V2 Contract Compatibility test", () => { let instantRewards: InstantRewardsV2, owner: SignerWithAddress, signer: SignerWithAddress, @@ -198,7 +198,7 @@ describe("Instant Rewards Contract test", () => { to ); - instantRewards.claim(claimDataSigned); + await instantRewards.claim(claimDataSigned); const tx = instantRewards.claim(claimDataSigned); await expect(tx).to.revertedWith("TaskAlreadyClaimed"); @@ -227,7 +227,7 @@ describe("Instant Rewards Contract test", () => { taskId, to ); - instantRewards.claim(claimDataSigned); + await instantRewards.claim(claimDataSigned); } const claimDataSigned = await getClaimDataSigned( HARDHAT_CHAIN_ID, @@ -275,7 +275,7 @@ describe("Instant Rewards Contract test", () => { to ); - instantRewards.claim(newClaimDataSigned); + await instantRewards.claim(newClaimDataSigned); const tx = instantRewards.claim(claimDataSigned); await expect(tx).to.revertedWith("TaskAlreadyClaimed"); diff --git a/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2.ts b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2.ts new file mode 100644 index 0000000..06965cb --- /dev/null +++ b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2.ts @@ -0,0 +1,94 @@ +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 { InstantRewardsFactory, InstantRewardsV2 } from "../../typechain-types"; +import { ClaimData, getSignature } from "./test.helpers"; + +const HARDHAT_CHAIN_ID = 1337; + +describe("Instant Rewards Contract test", () => { + let instantRewardsFactory: InstantRewardsFactory, + deployer: SignerWithAddress, + owner: SignerWithAddress, + signer: SignerWithAddress, + user: SignerWithAddress, + addrs: SignerWithAddress[]; + + const encodeTaskId = (taskId: string) => utils.keccak256(utils.defaultAbiCoder.encode(["string"], [taskId])); + + const getClaimDataSigned = async ( + chainId: number, + verifyingContract: string, + signer: SignerWithAddress, + amount: BigNumber, + sigExpiration: number, + taskId: string, + to: string + ) => { + const claimData: ClaimData = { + amount, + sigExpiration, + taskId, + to, + }; + + const signature = await getSignature(chainId, verifyingContract, signer, claimData); + return { + ...claimData, + signature, + }; + }; + + beforeEach(async () => { + [deployer, owner, signer, user, ...addrs] = await ethers.getSigners(); + const instantRewardsFactoryF = await ethers.getContractFactory("InstantRewardsFactory"); + instantRewardsFactory = (await instantRewardsFactoryF.deploy(owner.address)) as InstantRewardsFactory; + await instantRewardsFactory.deployed(); + await instantRewardsFactory.connect(owner).acceptOwnership(); + }); + + it("Should deploy an IR instance", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const start = currentBlock.timestamp + 1000; + const end = start + 1000; + const name = "Instant Rewards"; + const tx = instantRewardsFactory.connect(owner).createInstantRewards(signer.address, start, end, name); + await expect(tx).to.emit(instantRewardsFactory, "InstantRewardsCreated"); + + const events = await instantRewardsFactory.queryFilter("InstantRewardsCreated"); + const address = events[0].args?.instantRewards; + expect(address).to.be.not.undefined; + + const instantRewards = (await ethers.getContractAt("InstantRewardsV2", address)) as InstantRewardsV2; + expect(await instantRewards.signerAddress()).to.be.eq(signer.address); + expect(await instantRewards.start()).to.be.eq(start); + expect(await instantRewards.end()).to.be.eq(end); + expect(await instantRewards.name()).to.be.eq(name); + + await instantRewards.connect(owner).acceptOwnership(); + expect(await instantRewards.owner()).to.be.eq(owner.address); + }); + + it("Should revert if not owner try to deploy", async () => { + const currentBlock = await ethers.provider.getBlock("latest"); + const start = currentBlock.timestamp + 1000; + const end = start + 1000; + const name = "Instant Rewards"; + const tx = instantRewardsFactory.createInstantRewards(signer.address, start, end, name); + await expect(tx).to.revertedWith("AccessDenied"); + }); + + it("Should deploy an IR instance with any wallet if it's open", async () => { + await instantRewardsFactory.connect(owner).setAllowPublicCreation(true); + + const currentBlock = await ethers.provider.getBlock("latest"); + const start = currentBlock.timestamp + 1000; + const end = start + 1000; + const name = "Instant Rewards"; + const tx = instantRewardsFactory.createInstantRewards(signer.address, start, end, name); + await expect(tx).to.emit(instantRewardsFactory, "InstantRewardsCreated"); + }); +}); From 188c1361f709e83c4ebd2b0038a083e46c1aba7a Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Wed, 6 Nov 2024 14:10:53 -0300 Subject: [PATCH 3/5] remove unused code --- .../instant-rewards/instant-rewards-v2.ts | 30 ------------------- 1 file changed, 30 deletions(-) diff --git a/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2.ts b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2.ts index 06965cb..41009c6 100644 --- a/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2.ts +++ b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards-v2.ts @@ -1,13 +1,8 @@ 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 { InstantRewardsFactory, InstantRewardsV2 } from "../../typechain-types"; -import { ClaimData, getSignature } from "./test.helpers"; - -const HARDHAT_CHAIN_ID = 1337; describe("Instant Rewards Contract test", () => { let instantRewardsFactory: InstantRewardsFactory, @@ -17,31 +12,6 @@ describe("Instant Rewards Contract test", () => { user: SignerWithAddress, addrs: SignerWithAddress[]; - const encodeTaskId = (taskId: string) => utils.keccak256(utils.defaultAbiCoder.encode(["string"], [taskId])); - - const getClaimDataSigned = async ( - chainId: number, - verifyingContract: string, - signer: SignerWithAddress, - amount: BigNumber, - sigExpiration: number, - taskId: string, - to: string - ) => { - const claimData: ClaimData = { - amount, - sigExpiration, - taskId, - to, - }; - - const signature = await getSignature(chainId, verifyingContract, signer, claimData); - return { - ...claimData, - signature, - }; - }; - beforeEach(async () => { [deployer, owner, signer, user, ...addrs] = await ethers.getSigners(); const instantRewardsFactoryF = await ethers.getContractFactory("InstantRewardsFactory"); From 98dd76c122ff5dc0c65ba51fa302b611be78f1a9 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Wed, 6 Nov 2024 14:12:47 -0300 Subject: [PATCH 4/5] add validations --- .../instant-rewards/InstantRewardsFactory.sol | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol index 0c69f89..3a0560f 100644 --- a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol +++ b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewardsFactory.sol @@ -8,6 +8,11 @@ contract InstantRewardsFactory is Ownable2Step { bool public allowPublicCreation = false; error AccessDenied(); + error InvalidSignerAddress(); + error EmptyName(); + error StartTimeInPast(); + error EndTimeBeforeStart(); + event InstantRewardsCreated(address indexed instantRewards, address indexed owner); constructor(address owner) Ownable() { @@ -24,6 +29,11 @@ contract InstantRewardsFactory is Ownable2Step { uint256 end, string memory name ) external returns (address) { + if (signerAddress == address(0)) revert InvalidSignerAddress(); + if (bytes(name).length == 0) revert EmptyName(); + if (start < block.timestamp) revert StartTimeInPast(); + if (end <= start) revert EndTimeBeforeStart(); + bool isOwner = owner() == msg.sender; if (!allowPublicCreation && !isOwner) revert AccessDenied(); From b9381a97a501333903a925da5aacdff46343f943 Mon Sep 17 00:00:00 2001 From: Andres Aiello Date: Fri, 8 Nov 2024 12:02:54 -0300 Subject: [PATCH 5/5] deploy to testnet --- .../zevm-app-contracts/data/addresses.json | 2 +- .../scripts/instant-rewards/deploy-v2.ts | 37 +++++++++++++++++++ 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 packages/zevm-app-contracts/scripts/instant-rewards/deploy-v2.ts diff --git a/packages/zevm-app-contracts/data/addresses.json b/packages/zevm-app-contracts/data/addresses.json index 2e3e46a..acfec2f 100644 --- a/packages/zevm-app-contracts/data/addresses.json +++ b/packages/zevm-app-contracts/data/addresses.json @@ -8,7 +8,7 @@ "invitationManager": "0x3649C03C472B698213926543456E9c21081e529d", "withdrawERC20": "0xa349B9367cc54b47CAb8D09A95836AE8b4D1d84E", "ZetaXP": "0x5c25b6f4D2b7a550a80561d3Bf274C953aC8be7d", - "InstantRewards": "0x10DfEd4ba9b8F6a1c998E829FfC0325D533c80E3", + "InstantRewards": "0xd91164c9671C5A2ee1C95fa34A95C9141dA691D4", "ProofOfLiveness": "0x981EB6fD19717Faf293Fba0cBD05C6Ac97b8C808", "TimelockController": "0x44139C2150c11c25f517B8a8F974b59C82aEe709", "ZetaXPGov": "0x854032d484aE21acC34F36324E55A8080F21Af12", diff --git a/packages/zevm-app-contracts/scripts/instant-rewards/deploy-v2.ts b/packages/zevm-app-contracts/scripts/instant-rewards/deploy-v2.ts new file mode 100644 index 0000000..67319f3 --- /dev/null +++ b/packages/zevm-app-contracts/scripts/instant-rewards/deploy-v2.ts @@ -0,0 +1,37 @@ +import { isProtocolNetworkName } from "@zetachain/protocol-contracts"; +import { ethers, network } from "hardhat"; + +import { InstantRewardsFactory__factory } from "../../typechain-types"; +import { saveAddress } from "../address.helpers"; +import { verifyContract } from "../explorer.helpers"; + +const networkName = network.name; + +const owner = "0x1d24d94520B94B26351f6573de5ef9731c48531A"; + +const deployInstantRewards = async () => { + if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); + + const InstantRewardsFactory = (await ethers.getContractFactory( + "InstantRewardsFactory" + )) as InstantRewardsFactory__factory; + const InstantRewards = await InstantRewardsFactory.deploy(owner); + + await InstantRewards.deployed(); + + console.log("InstantRewards deployed to:", InstantRewards.address); + + saveAddress("InstantRewards", InstantRewards.address, networkName); + + await verifyContract(InstantRewards.address, [owner]); +}; + +const main = async () => { + if (!isProtocolNetworkName(networkName)) throw new Error("Invalid network name"); + await deployInstantRewards(); +}; + +main().catch((error) => { + console.error(error); + process.exit(1); +});