diff --git a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol index 0103019..8b3c80b 100644 --- a/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol +++ b/packages/zevm-app-contracts/contracts/instant-rewards/InstantRewards.sol @@ -27,6 +27,7 @@ contract InstantRewards is Ownable, Pausable, ReentrancyGuard { address public signerAddress; event Claimed(address indexed to, bytes32 indexed taskId, uint256 amount); + event Withdrawn(address indexed wallet, uint256 amount); error InvalidSigner(); error SignatureExpired(); @@ -91,6 +92,15 @@ contract InstantRewards is Ownable, Pausable, ReentrancyGuard { if (wallet == address(0)) revert InvalidAddress(); if (amount > address(this).balance) revert TransferFailed(); payable(wallet).transfer(amount); + emit Withdrawn(wallet, amount); + } + + function pause() external onlyOwner { + _pause(); + } + + function unpause() external onlyOwner { + _unpause(); } receive() external payable {} diff --git a/packages/zevm-app-contracts/test/instant-rewards/instant-rewards.ts b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards.ts index 8e2a992..5636949 100644 --- a/packages/zevm-app-contracts/test/instant-rewards/instant-rewards.ts +++ b/packages/zevm-app-contracts/test/instant-rewards/instant-rewards.ts @@ -1,6 +1,6 @@ import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { expect } from "chai"; -import { utils } from "ethers"; +import { BigNumber, utils } from "ethers"; import { ethers } from "hardhat"; import { InstantRewards } from "../../typechain-types"; @@ -15,6 +15,27 @@ describe("Instant Rewards Contract test", () => { const encodeTaskId = (taskId: string) => utils.keccak256(utils.defaultAbiCoder.encode(["string"], [taskId])); + const getClaimDataSigned = async ( + signer: SignerWithAddress, + amount: BigNumber, + sigExpiration: number, + taskId: string, + to: string + ) => { + const claimData: ClaimData = { + amount, + sigExpiration, + taskId, + to, + }; + + const signature = await getSignature(signer, claimData); + return { + ...claimData, + signature, + }; + }; + beforeEach(async () => { [owner, signer, user, ...addrs] = await ethers.getSigners(); const instantRewardsFactory = await ethers.getContractFactory("InstantRewards"); @@ -28,7 +49,7 @@ describe("Instant Rewards Contract test", () => { const currentBlock = await ethers.provider.getBlock("latest"); const sigExpiration = currentBlock.timestamp + 1000; const amount = utils.parseEther("1"); - const taskId = encodeTaskId("1/1/1"); + const taskId = encodeTaskId("WALLET/TASK/EPOC"); const to = owner.address; // transfer some funds to the contract @@ -37,23 +58,80 @@ describe("Instant Rewards Contract test", () => { value: amount, }); - const claimData: ClaimData = { - amount, - sigExpiration, - taskId, - to, - }; + const claimDataSigned = await getClaimDataSigned(signer, amount, sigExpiration, taskId, to); - const signature = await getSignature(signer, claimData); - const claimDataSigned = { - ...claimData, - signature, - }; + 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(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(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(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(signer, amount, sigExpiration, taskId, to); + + const tx = instantRewards.claim(claimDataSigned); + await expect(tx).to.revertedWith("Pausable: paused"); + }); + + 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(); @@ -65,4 +143,24 @@ describe("Instant Rewards Contract test", () => { expect(ownerAddr).to.be.eq(user.address); } }); + + it("Should withdraw by owner", 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 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)); + }); });