Skip to content

Commit

Permalink
fix: fix contract & update unit test
Browse files Browse the repository at this point in the history
  • Loading branch information
thongxuan committed Jan 3, 2025
1 parent ab8c3e4 commit 33466d8
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 40 deletions.
15 changes: 9 additions & 6 deletions contracts/reward/RewardRegistry.sol
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,11 @@ pragma solidity ^0.8.0;
import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/utils/Create2.sol";
import "hardhat/console.sol";

import "../utils/Data.sol";
import "../payment/PaymentConfigRegistry.sol";
import "./RewardVault.sol";

bytes32 constant REWARD = keccak256(abi.encode("REWARD"));

contract RewardRegistry is IRewardRegistry, OwnableUpgradeable {
//-- TYPE DEFINITION
using EnumerableSet for EnumerableSet.AddressSet;
Expand All @@ -37,6 +35,7 @@ contract RewardRegistry is IRewardRegistry, OwnableUpgradeable {
//-- ERRORS
error NotRewardVault(address caller);
error InvalidData();
error AlreadyClaimed();

function initialize(address registry) public initializer {
__Ownable_init();
Expand Down Expand Up @@ -128,6 +127,10 @@ contract RewardRegistry is IRewardRegistry, OwnableUpgradeable {
uint256[] calldata counts,
bytes calldata signature
) external {
if (claimed[claimId]) {
revert AlreadyClaimed();
}

uint256 rewardLength = rewardIds.length;
uint256 countLength = counts.length;

Expand All @@ -138,15 +141,15 @@ contract RewardRegistry is IRewardRegistry, OwnableUpgradeable {
address sender = _msgSender();

bytes32[] memory payload = new bytes32[](
rewardLength + countLength + 1
rewardLength + countLength + 2
);

payload[0] = REWARD;
payload[0] = stringToId("REWARD");
payload[1] = claimId;

for (uint256 i = 0; i < rewardLength; ) {
payload[2 + i] = rewardIds[i];
payload[2 + i * 2] = bytes32(counts[i]);
payload[2 + i + rewardLength] = bytes32(counts[i]);

unchecked {
++i;
Expand Down
1 change: 0 additions & 1 deletion contracts/reward/RewardVault.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/structs/EnumerableSet.sol";
import "hardhat/console.sol";

import "../utils/Vault.sol";

Expand Down
4 changes: 2 additions & 2 deletions scripts/deploy-proxy-create2.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import { ethers } from 'hardhat';
import assert from 'assert';

import { toId } from "../test/utils";
import { stringToBytes32 } from "../test/utils";

const { FACTORY, NAME, SALT, ARGS } = process.env;

async function main() {
assert.ok(FACTORY && SALT && NAME);

const salt = toId(SALT);
const salt = stringToBytes32(SALT);
const genericFactory = await ethers.getContractAt("GenericFactory", FACTORY);

const ToBeDeployedFactory = await ethers.getContractFactory(NAME);
Expand Down
12 changes: 6 additions & 6 deletions test/LemonadeStakePaymentV1.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import BigNumber from "bignumber.js";
import { Contract, ContractTransactionResponse } from 'ethers';
import { ethers, upgrades } from 'hardhat';

import { toId } from "./utils";
import { stringToBytes32 } from "./utils";

import { createSignature, deployAccessRegistry, deployConfigRegistry, getBalances, mintERC20 } from "./helper";

Expand All @@ -22,7 +22,7 @@ const deployStake = async (signer: SignerWithAddress) => {
return { configRegistry, stakePayment };
}

const salt = toId("SALT");
const salt = stringToBytes32("SALT");

const register = async (ppm: bigint) => {
const [signer, signer2] = await ethers.getSigners();
Expand Down Expand Up @@ -138,7 +138,7 @@ async function testWith(currencyResolver: () => Promise<string>) {
const expectedRefund = BigInt(amount * ppm / 1000000);

//-- generate refund signature
const signature = await createSignature(signer, ["STAKE_REFUND", paymentId]);
const signature = await createSignature(signer, ["STAKE_REFUND", paymentId].map(stringToBytes32));

const { balanceBefore, balanceAfter, fee } = await getBalances(
signer2.address,
Expand Down Expand Up @@ -166,7 +166,7 @@ async function testWith(currencyResolver: () => Promise<string>) {
const { paymentId } = await stake(vault, configRegistry, stakePayment, "3", currency);

//-- generate refund signature
const signature = await createSignature(signer, ["STAKE_REFUND", paymentId]);
const signature = await createSignature(signer, ["STAKE_REFUND", paymentId].map(stringToBytes32));

await stakePayment.connect(signer2).refund(paymentId, signature);
await assert.rejects(stakePayment.connect(signer2).refund(paymentId, signature));
Expand All @@ -183,7 +183,7 @@ async function testWith(currencyResolver: () => Promise<string>) {
const expectedSlashAmount = BigInt(stake1.amount + stake2.amount);

//-- generate slash signature
const signature = await createSignature(signer, ["STAKE_SLASH", stake1.paymentId, stake2.paymentId]);
const signature = await createSignature(signer, ["STAKE_SLASH", stake1.paymentId, stake2.paymentId].map(stringToBytes32));

//-- signer 2 is expecting the slash amount
const { balanceBefore, balanceAfter } = await getBalances(
Expand Down Expand Up @@ -214,7 +214,7 @@ async function testWith(currencyResolver: () => Promise<string>) {

const { paymentId } = await stake(vault, configRegistry, stakePayment, "5", currency);

const signature = await createSignature(signer, ["STAKE_SLASH", paymentId]);
const signature = await createSignature(signer, ["STAKE_SLASH", paymentId].map(stringToBytes32));

await stakePayment.connect(signer).slash(
vault,
Expand Down
119 changes: 100 additions & 19 deletions test/LemonadeRewardRegistryV1.ts → test/RewardRegistryV1.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import assert from 'assert';
import { expect } from 'chai';
import { ContractTransactionResponse } from 'ethers';
import { ethers, upgrades } from 'hardhat';

import { toId } from "./utils";
import { numberToBytes32, stringToBytes32 } from "./utils";

import { deployAccessRegistry, deployConfigRegistry, getBalances, mintERC20 } from "./helper";
import { createSignature, deployAccessRegistry, deployConfigRegistry, getBalances, mintERC20 } from "./helper";

const deployRewardRegistry = async () => {
const [signer] = await ethers.getSigners();
Expand All @@ -23,7 +24,7 @@ const deployRewardRegistry = async () => {
return { rewardRegistry };
}

const salt = toId("SALT");
const salt = stringToBytes32("SALT");

const register = async (owners: SignerWithAddress[]) => {
const { rewardRegistry } = await deployRewardRegistry();
Expand Down Expand Up @@ -143,6 +144,82 @@ async function testWith(currencyResolver: () => Promise<string>) {
});
}

const setRewards = async () => {
const [signer1, signer2] = await ethers.getSigners();
const { vaults: [vault1, vault2], rewardRegistry } = await register([signer1, signer2]);

const currency = await mintERC20(signer1, signer1.address, "TEST", "TST", ethers.parseEther("1"));

const reward1 = stringToBytes32("TICKET:type1");
const reward2 = stringToBytes32("TICKET:type2");

const rewardVault1 = await ethers.getContractAt("RewardVault", vault1);
const rewardVault2 = await ethers.getContractAt("RewardVault", vault2);

const amount1 = 1000000000000n
const amount2 = 3000000000000n

const currencyContract = await ethers.getContractAt('IERC20', currency);

await Promise.all([
//-- set rewards
rewardVault1.connect(signer1).setRewards(
[reward1, reward1],
[[ethers.ZeroAddress, amount1], [currency, amount1]],
),
rewardVault2.connect(signer2).setRewards(
[reward1, reward2],
[[ethers.ZeroAddress, amount2], [currency, 1000000000n]],
),
//-- funds the vaults
signer1.sendTransaction({ to: vault1, value: ethers.parseEther("1") }),
signer1.sendTransaction({ to: vault2, value: ethers.parseEther("1") }),
currencyContract.connect(signer1).transfer(vault1, ethers.parseEther("1")),
].map((thenable) => thenable.then((tx) => tx.wait())));

return {
reward1, reward2, amount1, amount2,
signer1, signer2, currencyContract, rewardRegistry,
};
}

const claimRefunds = async (args: Awaited<ReturnType<typeof setRewards>>) => {
const {
reward1,
amount1, amount2,
signer1, signer2,
currencyContract, rewardRegistry,
} = args;

const claimId = "ticket_purchase:1";
const rewardIds = [reward1];
const counts = [1n];

const signature = await createSignature(
signer1,
[...["REWARD", claimId].map(stringToBytes32), ...rewardIds, ...counts.map(numberToBytes32)],
);

const erc20BalanceBefore = await currencyContract.balanceOf(signer2.address);

const { balanceBefore, balanceAfter, fee } = await getBalances(signer2.address, ethers.ZeroAddress, async () => {
const tx = await rewardRegistry
.connect(signer2)
.claimRewards(stringToBytes32(claimId), rewardIds, counts, signature);

const receipt = await tx.wait();

assert.ok(receipt);

return receipt;
});

const erc20BalanceAfter = await currencyContract.balanceOf(signer2.address);

assert.ok(erc20BalanceBefore === 0n && erc20BalanceAfter === amount1);
assert.ok(balanceAfter === balanceBefore + amount1 + amount2 - fee);
}

describe('LemonadeRewardV1', function () {
it('should allow create vault', async () => {
const [signer] = await ethers.getSigners();
Expand All @@ -158,30 +235,34 @@ describe('LemonadeRewardV1', function () {
});

it('should allow setRewards', async () => {
const [signer1, signer2] = await ethers.getSigners();
const { vaults: [vault1, vault2], rewardRegistry } = await register([signer1, signer2]);
const { rewardRegistry, reward1, reward2 } = await setRewards();

const currency = await mintERC20(signer1, signer2.address, "TEST", "TST", 1000000000000n);
const rewards = await rewardRegistry.checkRewards([reward1, reward2]);

const reward1 = toId("TICKET:type1");
const reward2 = toId("TICKET:type2");
assert.ok(rewards.length === 2);

const rewardVault1 = await ethers.getContractAt("RewardVault", vault1);
const rewardVault2 = await ethers.getContractAt("RewardVault", vault2);
const [r1, r2] = rewards;

await rewardVault1.connect(signer1).setRewards(
[reward1],
[[ethers.ZeroAddress, 1000000000]],
assert.ok(
r1.length === 2 //-- both vault1 & vault2
&& r2.length === 1 //-- only vault1
&& r1[0][1].length === 2 //-- vault1 has 2 reward settings for reward1
&& r1[1][1].length === 1 //-- vault2 has 1 reward settings for reward1
);
});

await rewardVault2.connect(signer2).setRewards(
[reward1, reward2],
[[ethers.ZeroAddress, 1000000000], [currency, 1000000000]],
);
it('should allow claimRewards', async () => {
const args = await setRewards();

const rewards = await rewardRegistry.checkRewards([reward1, reward2]);
await claimRefunds(args);
});

it('should prevent claimRewards with same claimId', async () => {
const args = await setRewards();

await claimRefunds(args);

console.log("rewaeds", rewards);
await expect(claimRefunds(args)).to.revertedWithCustomError(args.rewardRegistry, "AlreadyClaimed");
});

describe('Native currency', function () {
Expand Down
6 changes: 1 addition & 5 deletions test/helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,6 @@ import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers";
import { TransactionReceipt } from "ethers";
import { ethers, upgrades } from "hardhat";

import { toId } from "./utils";

export async function mintERC20(signer: SignerWithAddress, destination: string, name: string, symbol: string, amount: bigint) {
const MyERC20Token = await ethers.getContractFactory("ERC20Mint", signer);

Expand Down Expand Up @@ -46,9 +44,7 @@ export async function getBalances(wallet: string, currency: string, op: () => Pr
return { balanceBefore, balanceAfter, fee: isNative ? receipt.gasPrice * receipt.gasUsed : 0n };
}

export function createSignature(signer: SignerWithAddress, args: string[]) {
const data = args.map(toId);

export function createSignature(signer: SignerWithAddress, data: any[]) {
let encoded = "0x";

for (let i = 0; i < data.length; i++) {
Expand Down
6 changes: 5 additions & 1 deletion test/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,10 @@ export async function expectEmittedEventWithArgs(contract: Contract, tx: Contrac
}
}

export function toId(value: string) {
export function stringToBytes32(value: string) {
return ethers.keccak256(ethers.AbiCoder.defaultAbiCoder().encode(["string"], [value]));
}

export function numberToBytes32(value: bigint) {
return ethers.zeroPadValue(ethers.toBeHex(value), 32);
}

0 comments on commit 33466d8

Please sign in to comment.