From 7f27fe114a12fc3964f6b6d842e89b429586ec9e Mon Sep 17 00:00:00 2001 From: Fatel <> Date: Sat, 27 Jan 2024 18:56:23 +0800 Subject: [PATCH] add rewardDistributor-test.js --- contracts/OPZap.sol | 16 +- contracts/common/{IxOLE.sol => ISOLE.sol} | 2 +- contracts/linkup/LinkUp.sol | 24 +-- .../mocks/{MockXOLE.sol => MockSOLE.sol} | 4 +- contracts/share/ShareHelper.sol | 2 - test/linkUp-test.js | 34 ++-- test/rewardDistributor-test.js | 179 ++++++++++++++++++ test/share/StageShare.sign.js | 128 ++++++------- test/share/shareUtil.js | 118 ++++++------ test/zap-test.js | 27 ++- 10 files changed, 355 insertions(+), 179 deletions(-) rename contracts/common/{IxOLE.sol => ISOLE.sol} (96%) rename contracts/mocks/{MockXOLE.sol => MockSOLE.sol} (98%) create mode 100644 test/rewardDistributor-test.js diff --git a/contracts/OPZap.sol b/contracts/OPZap.sol index dbb8d24..ad092aa 100644 --- a/contracts/OPZap.sol +++ b/contracts/OPZap.sol @@ -7,7 +7,7 @@ import {IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import {IStageShare} from "./share/IStageShare.sol"; import {IUniV2ClassPair} from "./common/IUniV2ClassPair.sol"; import {Erc20Utils} from "./common/Erc20Utils.sol"; -import {IxOLE} from "./common/IxOLE.sol"; +import {ISOLE} from "./common/ISOLE.sol"; import {IWETH} from "./common/IWETH.sol"; contract OPZap is IOPZapV1 { @@ -19,15 +19,15 @@ contract OPZap is IOPZapV1 { IERC20 public immutable OLE; // Address of the OLE token IWETH public immutable WETH; // Native token of the blockchain (e.g., ETH on Ethereum) IUniV2ClassPair public immutable OLE_ETH; // Address of the token pair for liquidity : OLE/ETH - address public immutable XOLE; // Address of the OpenLeverage XOLE token + address public immutable SOLE; // Address of the OpenLeverage SOLE token IStageShare public immutable STAGE; // Address of the OpenLeverage Stage share contract uint256 public immutable DEX_FEES; // 0.3% dex fees (e.g., 20 means 0.2%) - constructor(IERC20 _ole, IWETH _weth, IUniV2ClassPair _pair, uint256 _dexFee, address _xole, IStageShare _stageShare) { + constructor(IERC20 _ole, IWETH _weth, IUniV2ClassPair _pair, uint256 _dexFee, address _sole, IStageShare _stageShare) { OLE = _ole; WETH = _weth; OLE_ETH = _pair; DEX_FEES = _dexFee; - XOLE = _xole; + SOLE = _sole; STAGE = _stageShare; } @@ -42,16 +42,16 @@ contract OPZap is IOPZapV1 { WETH.deposit{value: msg.value}(); uint256 lpReturn = _addLpByETH(msg.value); if (lpReturn < minLpReturn) revert InsufficientLpReturn(); - OLE_ETH.safeApprove(XOLE, lpReturn); - IxOLE(XOLE).create_lock_for(msg.sender, lpReturn, unlockTime); + OLE_ETH.safeApprove(SOLE, lpReturn); + ISOLE(SOLE).create_lock_for(msg.sender, lpReturn, unlockTime); } function increaseXoleByETH(uint256 minLpReturn) external payable override { WETH.deposit{value: msg.value}(); uint256 lpReturn = _addLpByETH(msg.value); if (lpReturn < minLpReturn) revert InsufficientLpReturn(); - OLE_ETH.safeApprove(XOLE, lpReturn); - IxOLE(XOLE).increase_amount_for(msg.sender, lpReturn); + OLE_ETH.safeApprove(SOLE, lpReturn); + ISOLE(SOLE).increase_amount_for(msg.sender, lpReturn); } function buySharesByETH(uint256 stageId, uint256 shares, uint256 timestamp, bytes memory signature) external payable override { diff --git a/contracts/common/IxOLE.sol b/contracts/common/ISOLE.sol similarity index 96% rename from contracts/common/IxOLE.sol rename to contracts/common/ISOLE.sol index 0242ebf..598dca5 100644 --- a/contracts/common/IxOLE.sol +++ b/contracts/common/ISOLE.sol @@ -1,7 +1,7 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.21; -interface IxOLE { +interface ISOLE { function create_lock_for(address to, uint256 _value, uint256 _unlock_time) external; function increase_amount_for(address to, uint256 _value) external; diff --git a/contracts/linkup/LinkUp.sol b/contracts/linkup/LinkUp.sol index bdc1140..85d279f 100644 --- a/contracts/linkup/LinkUp.sol +++ b/contracts/linkup/LinkUp.sol @@ -2,7 +2,7 @@ pragma solidity ^0.8.21; import "@openzeppelin/contracts/access/Ownable.sol"; -import "../common/IxOLE.sol"; +import "../common/ISOLE.sol"; import "../IOPZapV1.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Erc20Utils} from "../common/Erc20Utils.sol"; @@ -10,10 +10,10 @@ import {Erc20Utils} from "../common/Erc20Utils.sol"; contract LinkUp is Ownable { using Erc20Utils for IERC20; - IxOLE public immutable xOLEToken; // xOLE Token, immutable + ISOLE public immutable sOLEToken; // sOLE Token, immutable IERC20 public immutable OLE; // Address of the OLE token IOPZapV1 public immutable ZAP; - uint256 public constant MIN_XOLE_BALANCE = 100 * 10 ** 18; // Example: 100 xOLE (adjust as needed) + uint256 public constant MIN_SOLE_BALANCE = 100 * 10 ** 18; // Example: 100 sOLE (adjust as needed) address public signerAddress; uint256 public joinFee = 0.0015 ether; mapping(address => address) public inviterOf; @@ -35,9 +35,9 @@ contract LinkUp is Ownable { error InvalidNewSignerAddress(); error InvalidSignatureLength(); - constructor(address signer, address _xOLETokenAddress, IERC20 _ole, IOPZapV1 _zap) Ownable(msg.sender) { + constructor(address signer, address _sOLETokenAddress, IERC20 _ole, IOPZapV1 _zap) Ownable(msg.sender) { signerAddress = signer; - xOLEToken = IxOLE(_xOLETokenAddress); // Set the xOLE Token address here + sOLEToken = ISOLE(_sOLETokenAddress); // Set the sOLE Token address here OLE = _ole; ZAP = _zap; } @@ -55,24 +55,24 @@ contract LinkUp is Ownable { uint256 secondTierInviterFeePercent = 0; uint256 protocolFeePercent = 100; - // Check xOLE balances of inviters - bool directInviterOwnsXOLE = xOLEToken.balanceOf(inviter) >= MIN_XOLE_BALANCE; - bool secondTierInviterOwnsXOLE = false; + // Check sOLE balances of inviters + bool directInviterOwnsSOLE = sOLEToken.balanceOf(inviter) >= MIN_SOLE_BALANCE; + bool secondTierInviterOwnsSOLE = false; address secondTierInviter = inviterOf[inviter]; if (secondTierInviter != address(0)) { - secondTierInviterOwnsXOLE = xOLEToken.balanceOf(secondTierInviter) >= MIN_XOLE_BALANCE; + secondTierInviterOwnsSOLE = sOLEToken.balanceOf(secondTierInviter) >= MIN_SOLE_BALANCE; } // Calculate fee distribution percent - if (directInviterOwnsXOLE && secondTierInviterOwnsXOLE) { + if (directInviterOwnsSOLE && secondTierInviterOwnsSOLE) { directInviterFeePercent = 75; secondTierInviterFeePercent = 25; protocolFeePercent = 0; - } else if (directInviterOwnsXOLE) { + } else if (directInviterOwnsSOLE) { directInviterFeePercent = 80; secondTierInviterFeePercent = 15; protocolFeePercent = 5; - } else if (secondTierInviterOwnsXOLE) { + } else if (secondTierInviterOwnsSOLE) { directInviterFeePercent = 65; secondTierInviterFeePercent = 30; protocolFeePercent = 5; diff --git a/contracts/mocks/MockXOLE.sol b/contracts/mocks/MockSOLE.sol similarity index 98% rename from contracts/mocks/MockXOLE.sol rename to contracts/mocks/MockSOLE.sol index 05836e5..184bd0f 100644 --- a/contracts/mocks/MockXOLE.sol +++ b/contracts/mocks/MockSOLE.sol @@ -1,11 +1,11 @@ // SPDX-License-Identifier: BUSL-1.1 pragma solidity 0.8.21; -import {IxOLE} from "../common/IxOLE.sol"; +import {ISOLE} from "../common/ISOLE.sol"; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {Erc20Utils} from "../common/Erc20Utils.sol"; -contract MockXOLE is IxOLE { +contract MockSOLE is ISOLE { using Erc20Utils for IERC20; uint256 constant WEEK = 7 * 86400; // all future times are rounded by week diff --git a/contracts/share/ShareHelper.sol b/contracts/share/ShareHelper.sol index d531c32..aafc744 100644 --- a/contracts/share/ShareHelper.sol +++ b/contracts/share/ShareHelper.sol @@ -4,7 +4,6 @@ pragma solidity 0.8.21; import {IStageShare} from "./IStageShare.sol"; contract ShareHelper { - constructor() {} function getRewards(IStageShare stageShare, address holder, uint256[] memory stageIds) external view returns (uint256[] memory rewards) { @@ -16,5 +15,4 @@ contract ShareHelper { rewards[i] = stageShare.getRewards(queryIds, holder); } } - } diff --git a/test/linkUp-test.js b/test/linkUp-test.js index 19f5563..8448041 100644 --- a/test/linkUp-test.js +++ b/test/linkUp-test.js @@ -8,7 +8,7 @@ describe("LinkUp Contract", function() { let LinkUp; let linkUp; let MockXOLE; - let mockxOLE; + let mocksOLE; let owner; let addr1; let addr2; @@ -23,20 +23,20 @@ describe("LinkUp Contract", function() { let zapCtr = await (await ethers.getContractFactory("MockZap")).deploy(oleCtr, wethCtr); await oleCtr.mint(await zapCtr.getAddress(), ethers.parseEther("10000")); LinkUp = await ethers.getContractFactory("LinkUp"); - MockXOLE = await ethers.getContractFactory("MockXOLE"); - mockxOLE = await MockXOLE.deploy(ZERO_ADDRESS); + MockXOLE = await ethers.getContractFactory("MockSOLE"); + mocksOLE = await MockXOLE.deploy(ZERO_ADDRESS); [owner, addr1, addr2, ...addrs] = await ethers.getSigners(); signer = addrs[9]; - linkUp = await LinkUp.connect(owner).deploy(signer, mockxOLE, oleCtr, zapCtr); + linkUp = await LinkUp.connect(owner).deploy(signer, mocksOLE, oleCtr, zapCtr); }); async function setupXOLEBalances(directInviterHasXOLE, secondTierInviterHasXOLE) { if (directInviterHasXOLE) { - await mockxOLE.mint(addr1.address, ethers.parseEther("100")); // Direct inviter has xOLE + await mocksOLE.mint(addr1.address, ethers.parseEther("100")); // Direct inviter has sOLE } // else do not mint to simulate no balance if (secondTierInviterHasXOLE) { - await mockxOLE.mint(addr2.address, ethers.parseEther("100")); // Second-tier inviter has xOLE + await mocksOLE.mint(addr2.address, ethers.parseEther("100")); // Second-tier inviter has sOLE } // else do not mint to simulate no balance } @@ -101,16 +101,16 @@ describe("LinkUp Contract", function() { .to.be.revertedWithCustomError(linkUp, "AlreadyJoined"); }); - it("Should distribute fees correctly when only the direct inviter owns xOLE", async function() { - // addr1 is the direct inviter and owns enough xOLE tokens - // addr2 is the second-tier inviter and does not own enough xOLE tokens + it("Should distribute fees correctly when only the direct inviter owns sOLE", async function() { + // addr1 is the direct inviter and owns enough sOLE tokens + // addr2 is the second-tier inviter and does not own enough sOLE tokens // Sign inviter address const inviter = addr1.address; // Direct inviter const secondTierInviter = addr2.address; // Second-tier inviter const invitee = addrs[3].address; // New user being invited - // Ensure addr2 has no xOLE to affect the distribution + // Ensure addr2 has no sOLE to affect the distribution await setupXOLEBalances(true, false); // Generate signatures @@ -139,9 +139,9 @@ describe("LinkUp Contract", function() { ); }); - it("Should distribute fees correctly when only the second-tier inviter owns xOLE", async function() { - // Setup: addr2 is the direct inviter and does not own enough xOLE tokens - // addr1 is the second-tier inviter and owns enough xOLE tokens + it("Should distribute fees correctly when only the second-tier inviter owns sOLE", async function() { + // Setup: addr2 is the direct inviter and does not own enough sOLE tokens + // addr1 is the second-tier inviter and owns enough sOLE tokens // Sign inviter address const inviter = addr1.address; // Direct inviter const secondTierInviter = addr2.address; // Second-tier inviter @@ -175,8 +175,8 @@ describe("LinkUp Contract", function() { ); }); - it("Should distribute fees correctly when both inviters own xOLE", async function() { - // Setup: Both addr1 and addr2 own enough xOLE tokens + it("Should distribute fees correctly when both inviters own sOLE", async function() { + // Setup: Both addr1 and addr2 own enough sOLE tokens const inviter = addr1.address; // Direct inviter const secondTierInviter = addr2.address; // Second-tier inviter const invitee = addrs[3].address; // New user being invited @@ -209,8 +209,8 @@ describe("LinkUp Contract", function() { ); }); - it("Should distribute fees correctly when neither inviter owns xOLE", async function() { - // Setup: Neither addr1 nor addr2 owns enough xOLE tokens + it("Should distribute fees correctly when neither inviter owns sOLE", async function() { + // Setup: Neither addr1 nor addr2 owns enough sOLE tokens const inviter = addr1.address; // Direct inviter const secondTierInviter = addr2.address; // Second-tier inviter const invitee = addrs[3].address; // New user being invited diff --git a/test/rewardDistributor-test.js b/test/rewardDistributor-test.js new file mode 100644 index 0000000..2d4b10f --- /dev/null +++ b/test/rewardDistributor-test.js @@ -0,0 +1,179 @@ +const { expect } = require("chai"); +const { ethers, web3 } = require("hardhat"); +const { ZERO_ADDRESS } = require("@openzeppelin/test-helpers/src/constants"); +const { hexStringToArray } = require("./util/EtheUtil"); +const { time } = require("@nomicfoundation/hardhat-network-helpers"); + +const VEST_DURATION = 60 * 60 * 24 * 7; +describe("RewardDistributor Contract", function() { + let oleCtr; + let rewardCtr; + let deployer; + let signer; + let acc1; + let acc2; + let ts; + beforeEach(async function() { + oleCtr = await (await ethers.getContractFactory("MockToken")).deploy("OLE", "OLE", ethers.parseEther("1000000000")); + [deployer, signer, acc1, acc2, ...addrs] = await ethers.getSigners(); + rewardCtr = await (await ethers.getContractFactory("RewardDistributor")).deploy(oleCtr, signer, VEST_DURATION); + ts = (await ethers.provider.getBlock("latest")).timestamp; + }); + + describe("deployment", function() { + it("constructor initializes ", async function() { + expect(await rewardCtr.OLE()).to.equal(await oleCtr.getAddress()); + expect(await rewardCtr.signerAddress()).to.equal(signer); + expect(await rewardCtr.vestDuration()).to.equal(VEST_DURATION); + expect(await rewardCtr.owner()).to.equal(deployer); + }); + }); + + describe("authentication", function() { + it("set signer address only owner", async function() { + await expect(rewardCtr.connect(acc1).setSignerAddress(acc1)) + .to.revertedWithCustomError(rewardCtr, "OwnableUnauthorizedAccount"); + await rewardCtr.setSignerAddress(acc1); + expect(await rewardCtr.signerAddress()).to.equal(acc1); + }); + it("fails if signer address is zero address", async function() { + await expect(rewardCtr.setSignerAddress(ZERO_ADDRESS)) + .to.revertedWithCustomError(rewardCtr, "InvalidAddress"); + }); + it("set vest duration only owner", async function() { + let newVestDuration = 1; + await expect(rewardCtr.connect(acc1).setVestDuration(newVestDuration)) + .to.revertedWithCustomError(rewardCtr, "OwnableUnauthorizedAccount"); + await rewardCtr.setVestDuration(newVestDuration); + expect(await rewardCtr.vestDuration()).to.equal(newVestDuration); + }); + it("fails if vest duration is 0", async function() { + await expect(rewardCtr.setVestDuration(0)) + .to.revertedWithCustomError(rewardCtr, "InvalidDuration"); + }); + }); + + describe("vests", function() { + let epochIds = [1]; + let amount = ethers.parseEther("100"); + it("emit event", async function() { + let vestId = getVestId(acc1.address, amount, epochIds); + let tx = rewardCtr.connect(acc1).vests(epochIds, amount, await signer.signMessage(hexStringToArray(vestId))); + await expect(tx) + .to.emit(rewardCtr, "VestStarted") + .withArgs(vestId, acc1.address, amount, ts + 1, ts + 1 + VEST_DURATION + ); + let rewardStruct = await rewardCtr.rewards(acc1, vestId); + expect(rewardStruct.total).to.equal(amount); + expect(rewardStruct.withdrawn).to.equal(0); + expect(rewardStruct.startTime).to.equal(ts + 1); + expect(rewardStruct.endTime).to.equal(ts + 1 + VEST_DURATION); + }); + it("vest success with 1 epoch", async function() { + let vestId = getVestId(acc1.address, amount, epochIds); + await rewardCtr.connect(acc1).vests(epochIds, amount, await signer.signMessage(hexStringToArray(vestId))); + + }); + it("vest success with 96 epochs", async function() { + let epochIds = Array.from({ length: 96 }, (value, index) => index + 1); + let vestId = getVestId(acc1.address, amount, epochIds); + await rewardCtr.connect(acc1).vests(epochIds, amount, await signer.signMessage(hexStringToArray(vestId))); + }); + it("fails if epochIds is empty", async function() { + let vestId = getVestId(acc1.address, amount, []); + await expect(rewardCtr.connect(acc1).vests([], amount, await signer.signMessage(hexStringToArray(vestId)))) + .to.revertedWithCustomError(rewardCtr, "InvalidParams"); + }); + it("fails if epoch was vested", async function() { + let vestId = getVestId(acc1.address, amount, epochIds); + await rewardCtr.connect(acc1).vests(epochIds, amount, await signer.signMessage(hexStringToArray(vestId))); + await expect(rewardCtr.connect(acc1).vests(epochIds, amount, await signer.signMessage(hexStringToArray(vestId)))) + .to.revertedWithCustomError(rewardCtr, "AlreadyVested"); + }); + it("fails if signature is invalid", async function() { + let vestId = getVestId(acc1.address, amount, epochIds); + await expect(rewardCtr.connect(acc1).vests(epochIds, amount, await acc1.signMessage(hexStringToArray(vestId)))) + .to.revertedWithCustomError(rewardCtr, "InvalidSignature"); + }); + }); + + describe("withdraws", function() { + let epochIds = [1]; + let amount = ethers.parseEther("100"); + let releaseablePerSecond = ethers.parseEther("100") / BigInt(VEST_DURATION); + let vest1Id; + beforeEach(async function() { + vest1Id = getVestId(acc1.address, amount, epochIds); + await oleCtr.transfer(rewardCtr, ethers.parseEther("10000")); + await rewardCtr.connect(acc1).vests(epochIds, amount, await signer.signMessage(hexStringToArray(vest1Id))); + }); + + it("emit event", async function() { + let tx = rewardCtr.connect(acc1).withdraws([vest1Id]); + await expect(tx) + .to.emit(rewardCtr, "Withdrawn") + .withArgs(vest1Id, acc1.address, releaseablePerSecond + ); + }); + + it("withdraw success with 1 vest", async function() { + await rewardCtr.connect(acc1).withdraws([vest1Id]); + expect(await oleCtr.balanceOf(acc1)).to.equal(releaseablePerSecond); + let rewardStruct = await rewardCtr.rewards(acc1, vest1Id); + expect(rewardStruct.total).to.equal(amount); + expect(rewardStruct.withdrawn).to.equal(releaseablePerSecond); + }); + + it("withdraw success with 2 vest", async function() { + let vest2Id = getVestId(acc1.address, amount, [2]); + await rewardCtr.connect(acc1).vests([2], amount, await signer.signMessage(hexStringToArray(vest2Id))); + await rewardCtr.connect(acc1).withdraws([vest1Id, vest2Id]); + expect(await oleCtr.balanceOf(acc1)).to.equal(releaseablePerSecond * BigInt(3)); + }); + + it("withdraw 2 times", async function() { + await rewardCtr.connect(acc1).withdraws([vest1Id]); + await rewardCtr.connect(acc1).withdraws([vest1Id]); + expect(await oleCtr.balanceOf(acc1)).to.equal(releaseablePerSecond * BigInt(2)); + expect(await rewardCtr.getWithdrawable([vest1Id], acc1)).to.equal(0); + }); + + it("withdraw 3 times", async function() { + await rewardCtr.connect(acc1).withdraws([vest1Id]); + await rewardCtr.connect(acc1).withdraws([vest1Id]); + await rewardCtr.connect(acc1).withdraws([vest1Id]); + let rewardStruct = await rewardCtr.rewards(acc1, vest1Id); + expect(await oleCtr.balanceOf(acc1)).to.equal(rewardStruct.withdrawn); + expect(await rewardCtr.getWithdrawable([vest1Id], acc1)).to.equal(0); + }); + + it("withdraw after ended", async function() { + await rewardCtr.connect(acc1).withdraws([vest1Id]); + let rewardStruct = await rewardCtr.rewards(acc1, vest1Id); + await time.increaseTo(rewardStruct.endTime); + await rewardCtr.connect(acc1).withdraws([vest1Id]); + rewardStruct = await rewardCtr.rewards(acc1, vest1Id); + expect(amount).to.equal(rewardStruct.withdrawn); + expect(await oleCtr.balanceOf(acc1)).to.equal(rewardStruct.withdrawn); + expect(await rewardCtr.getWithdrawable([vest1Id], acc1)).to.equal(0); + }); + + it("fails if user reward is 0", async function() { + await expect(rewardCtr.connect(acc2).withdraws([vest1Id])) + .to.revertedWithCustomError(rewardCtr, "NoReward"); + }); + it("fails if withdrawable reward is 0", async function() { + let rewardStruct = await rewardCtr.rewards(acc1, vest1Id); + await time.increaseTo(rewardStruct.endTime); + await rewardCtr.connect(acc1).withdraws([vest1Id]); + await expect(rewardCtr.connect(acc1).withdraws([vest1Id])) + .to.revertedWithCustomError(rewardCtr, "InvalidWithdrawn"); + }); + }); +}); + +function getVestId(user, amount, epochIds) { + return ethers.solidityPackedKeccak256( + ["address", "uint256", "uint256[]"], [user, amount, epochIds] + ); +} diff --git a/test/share/StageShare.sign.js b/test/share/StageShare.sign.js index c4f5427..fbf7f00 100644 --- a/test/share/StageShare.sign.js +++ b/test/share/StageShare.sign.js @@ -1,75 +1,75 @@ const { - newOLE, - newStageWithOle, - price1, invalidSignatureError,expiredSignatureError + newOLE, + newStageWithOle, + price1, invalidSignatureError, expiredSignatureError } = require("./shareUtil"); -const {BN, expectEvent, expectRevert} = require("@openzeppelin/test-helpers"); -const {web3, ethers } = require("hardhat"); -const {expect} = require("chai"); +const { BN, expectEvent, expectRevert } = require("@openzeppelin/test-helpers"); +const { web3, ethers } = require("hardhat"); +const { expect } = require("chai"); const { hexStringToArray } = require("../util/EtheUtil"); -contract('StageShare', function (accounts) { - let shareCtr; - let oleCtr; - let stageId = new BN(1); - let owner = accounts[0]; - let trader = accounts[1]; - let maxInAmount = web3.utils.toWei('10000'); - let validDuration = 300; - let timestamp; - let issuer; - let invalidIssuer; +contract("StageShare", function(accounts) { + let shareCtr; + let oleCtr; + let stageId = new BN(1); + let owner = accounts[0]; + let trader = accounts[1]; + let maxInAmount = web3.utils.toWei("10000"); + let validDuration = 300; + let timestamp; + let issuer; + let invalidIssuer; - beforeEach(async () => { - oleCtr = await newOLE(owner); - [issuer, invalidIssuer] = await ethers.getSigners(); - shareCtr = await newStageWithOle(oleCtr.address, issuer.address, owner); - timestamp = Math.floor(Date.now() / 1000); - await shareCtr.createStage({from: owner}); - await oleCtr.mint(trader, maxInAmount); - await oleCtr.approve(shareCtr.address, maxInAmount, {from: trader}); - }); + beforeEach(async () => { + oleCtr = await newOLE(owner); + [issuer, invalidIssuer] = await ethers.getSigners(); + shareCtr = await newStageWithOle(oleCtr.address, issuer.address, owner); + timestamp = (await web3.eth.getBlock("latest")).timestamp; + await shareCtr.createStage({ from: owner }); + await oleCtr.mint(trader, maxInAmount); + await oleCtr.approve(shareCtr.address, maxInAmount, { from: trader }); + }); - it('should successfully verify a valid signature', async () => { - let validSignature = await sign(trader, timestamp, issuer); - await shareCtr.buyShares(stageId, new BN(1), price1, timestamp, validSignature, {from: trader}); - }); + it("should successfully verify a valid signature", async () => { + let validSignature = await sign(trader, timestamp, issuer); + await shareCtr.buyShares(stageId, new BN(1), price1, timestamp, validSignature, { from: trader }); + }); - it('should fail when signature content is inconsistent', async () => { - let validSignature = await sign(accounts[2], timestamp, issuer); - await expectRevert( - shareCtr.buyShares(stageId, new BN(1), price1, timestamp, validSignature, {from: trader}), - invalidSignatureError - ); - validSignature = await sign(trader, timestamp - 1, issuer); - await expectRevert( - shareCtr.buyShares(stageId, new BN(1), price1, timestamp, validSignature, {from: trader}), - invalidSignatureError - ); - }); + it("should fail when signature content is inconsistent", async () => { + let validSignature = await sign(accounts[2], timestamp, issuer); + await expectRevert( + shareCtr.buyShares(stageId, new BN(1), price1, timestamp, validSignature, { from: trader }), + invalidSignatureError + ); + validSignature = await sign(trader, timestamp - 1, issuer); + await expectRevert( + shareCtr.buyShares(stageId, new BN(1), price1, timestamp, validSignature, { from: trader }), + invalidSignatureError + ); + }); - it('should fail when signature is expired', async () => { - let newTimestamp = timestamp - 6000; // 100 minutes ago - let expiredSignature = await sign(trader, newTimestamp, issuer); - await expectRevert( - shareCtr.buyShares(stageId, new BN(1), price1, newTimestamp, expiredSignature, {from: trader}), - expiredSignatureError - ); - }); + it("should fail when signature is expired", async () => { + let newTimestamp = timestamp - 6000; // 100 minutes ago + let expiredSignature = await sign(trader, newTimestamp, issuer); + await expectRevert( + shareCtr.buyShares(stageId, new BN(1), price1, newTimestamp, expiredSignature, { from: trader }), + expiredSignatureError + ); + }); - it('should fail when owner address is inconsistent', async () => { - let validSignature = await sign(trader, timestamp, invalidIssuer); - await expectRevert( - shareCtr.buyShares(stageId, new BN(1), price1, timestamp, validSignature, {from: trader}), - invalidSignatureError - ); - }); + it("should fail when owner address is inconsistent", async () => { + let validSignature = await sign(trader, timestamp, invalidIssuer); + await expectRevert( + shareCtr.buyShares(stageId, new BN(1), price1, timestamp, validSignature, { from: trader }), + invalidSignatureError + ); + }); - async function sign(user, timestamp, issuer) { - let sign; - await issuer.signMessage(hexStringToArray(ethers.solidityPackedKeccak256(['address', 'uint256'], [user, timestamp]))).then(result => { - sign = result; - }); - return sign; - } + async function sign(user, timestamp, issuer) { + let sign; + await issuer.signMessage(hexStringToArray(ethers.solidityPackedKeccak256(["address", "uint256"], [user, timestamp]))).then(result => { + sign = result; + }); + return sign; + } }); \ No newline at end of file diff --git a/test/share/shareUtil.js b/test/share/shareUtil.js index b08b0c0..396e829 100644 --- a/test/share/shareUtil.js +++ b/test/share/shareUtil.js @@ -1,80 +1,80 @@ -const {ZERO_ADDRESS} = require("@openzeppelin/test-helpers/src/constants"); -const {web3} = require("hardhat"); -const {toBN, hexStringToArray} = require("../util/EtheUtil"); +const { ZERO_ADDRESS } = require("@openzeppelin/test-helpers/src/constants"); +const { web3 } = require("hardhat"); +const { toBN, hexStringToArray } = require("../util/EtheUtil"); const StageShare = artifacts.require("StageShare"); const MockToken = artifacts.require("MockToken"); -const { AbiCoder,ethers } = require('ethers'); +const { AbiCoder, ethers } = require("ethers"); -let notOwnerError = 'OwnableUnauthorizedAccount'; -let invalidParamError = 'InvalidParam'; -let zeroAddressError = 'ZeroAddress'; -let zeroAmountError = 'ZeroAmount'; -let stageNotExistsError = 'StageNotExists'; -let insufficientInAmountError = 'InsufficientInAmount'; -let insufficientOutAmountError = 'InsufficientOutAmount'; -let insufficientSharesError = 'InsufficientShares'; -let arithmeticError = 'Arithmetic'; -let noRewardsError = 'NoRewards'; -let notSellLastShareError = 'CannotSellLastShare'; -let invalidSignatureError = 'InvalidSignature'; -let expiredSignatureError = 'Signature is expired'; -let K = toBN(web3.utils.toWei('1.1', 'ether')); -let B = toBN(web3.utils.toWei('100', 'ether')); +let notOwnerError = "OwnableUnauthorizedAccount"; +let invalidParamError = "InvalidParam"; +let zeroAddressError = "ZeroAddress"; +let zeroAmountError = "ZeroAmount"; +let stageNotExistsError = "StageNotExists"; +let insufficientInAmountError = "InsufficientInAmount"; +let insufficientOutAmountError = "InsufficientOutAmount"; +let insufficientSharesError = "InsufficientShares"; +let arithmeticError = "Arithmetic"; +let noRewardsError = "NoRewards"; +let notSellLastShareError = "CannotSellLastShare"; +let invalidSignatureError = "InvalidSignature"; +let expiredSignatureError = "Signature is expired"; +let K = toBN(web3.utils.toWei("1.1", "ether")); +let B = toBN(web3.utils.toWei("100", "ether")); let price1 = K.add(B); let price2 = toBN(2).mul(K).add(B); let price3 = toBN(3).mul(K).add(B); let FEE_DENOMINATOR = toBN(10000); const newStageShare = async (owner) => { - return await StageShare.new(ZERO_ADDRESS, ZERO_ADDRESS, 0, 0, 0, {from: owner}); -} + return await StageShare.new(ZERO_ADDRESS, ZERO_ADDRESS, 0, 0, 0, { from: owner }); +}; const newOLE = async (owner) => { - return await MockToken.new("ole", "ole", toBN(web3.utils.toWei('1000000000', 'ether')), {from: owner}); -} + return await MockToken.new("ole", "ole", toBN(web3.utils.toWei("1000000000", "ether")), { from: owner }); +}; const newStageShareWithOle = async (ole, issuer, owner) => { - return await StageShare.new(ole, issuer, 3000, K, B, {from: owner}); -} + return await StageShare.new(ole, issuer, 3000, K, B, { from: owner }); +}; const getSign = async (signer, trader) => { - let timestamp = Math.floor(Date.now() / 1000); - let sign; - await signer.signMessage(hexStringToArray(ethers.solidityPackedKeccak256(['address', 'uint256'], [trader, timestamp]))).then(result => { - sign = result; - }); - return [sign, timestamp]; -} + let timestamp = (await web3.eth.getBlock("latest")).timestamp; + let sign; + await signer.signMessage(hexStringToArray(ethers.solidityPackedKeccak256(["address", "uint256"], [trader, timestamp]))).then(result => { + sign = result; + }); + return [sign, timestamp]; +}; const clearOleBalance = async (ole, account) => { - await ole.burn(account, await ole.balanceOf(account)); -} + await ole.burn(account, await ole.balanceOf(account)); +}; module.exports = { - newStage: newStageShare, - newOLE, - getSign, - newStageWithOle: newStageShareWithOle, - clearOleBalance, - K, - B, - notOwnerError, - invalidParamError, - zeroAddressError, - price1, - price2, - price3, - zeroAmountError, - stageNotExistsError, - insufficientInAmountError, - FEE_DENOMINATOR, - notSellLastShareError, - insufficientOutAmountError, - insufficientSharesError, - arithmeticError, - noRewardsError, - invalidSignatureError, - expiredSignatureError -} + newStage: newStageShare, + newOLE, + getSign, + newStageWithOle: newStageShareWithOle, + clearOleBalance, + K, + B, + notOwnerError, + invalidParamError, + zeroAddressError, + price1, + price2, + price3, + zeroAmountError, + stageNotExistsError, + insufficientInAmountError, + FEE_DENOMINATOR, + notSellLastShareError, + insufficientOutAmountError, + insufficientSharesError, + arithmeticError, + noRewardsError, + invalidSignatureError, + expiredSignatureError +}; diff --git a/test/zap-test.js b/test/zap-test.js index ad7397f..fdcc1c4 100644 --- a/test/zap-test.js +++ b/test/zap-test.js @@ -10,7 +10,7 @@ describe("Zap Contract", function() { let oleCtr; let wethCtr; let oleEthCtr; - let xoleCtr; + let soleCtr; let stageShareCtr; let zapCtr; let deployer; @@ -21,10 +21,10 @@ describe("Zap Contract", function() { wethCtr = await (await ethers.getContractFactory("MockWETH")).deploy(); oleEthCtr = await (await ethers.getContractFactory("MockPancakePair")).deploy(oleCtr, wethCtr, ethers.parseEther("100000"), ethers.parseEther("1")); - xoleCtr = await (await ethers.getContractFactory("MockXOLE")).deploy(oleEthCtr); + soleCtr = await (await ethers.getContractFactory("MockSOLE")).deploy(oleEthCtr); stageShareCtr = await (await ethers.getContractFactory("MockStageShare")).deploy(oleCtr, K, B); zapCtr = await (await ethers.getContractFactory("OPZap")).deploy(oleCtr, wethCtr, - oleEthCtr, DEX_FEES, await xoleCtr, await stageShareCtr); + oleEthCtr, DEX_FEES, soleCtr, stageShareCtr); [deployer, acc1, ...addrs] = await ethers.getSigners(); ts = (await ethers.provider.getBlock("latest")).timestamp; }); @@ -35,9 +35,8 @@ describe("Zap Contract", function() { expect(await zapCtr.WETH()).to.equal(await wethCtr.getAddress()); expect(await zapCtr.OLE_ETH()).to.equal(await oleEthCtr.getAddress()); expect(await zapCtr.DEX_FEES()).to.equal(DEX_FEES); - expect(await zapCtr.XOLE()).to.equal(await xoleCtr.getAddress()); + expect(await zapCtr.SOLE()).to.equal(await soleCtr.getAddress()); expect(await zapCtr.STAGE()).to.equal(await stageShareCtr.getAddress()); - expect(await zapCtr.owner()).to.equal(deployer); }); }); @@ -58,7 +57,7 @@ describe("Zap Contract", function() { }); }); - describe("create xole by eth", function() { + describe("create sole by eth", function() { it("fails if lp return less than minimum", async function() { await expect(zapCtr.createXoleByETH(ethers.parseEther("10000000"), 0, { value: ethers.parseEther("0.1") })) @@ -71,39 +70,39 @@ describe("Zap Contract", function() { .to.revertedWith("Can only lock until time in the future"); }); - it("create xole for 0.1eth", async function() { + it("create sole for 0.1eth", async function() { await zapCtr.createXoleByETH(0, ts + WEEK_4, { value: ethers.parseEther("0.1") }); - expect(await xoleCtr.balanceOf(deployer)).to.equal(ethers.parseEther("15.402586869280504685")); + expect(await soleCtr.balanceOf(deployer)).to.equal(ethers.parseEther("15.402586869280504685")); }); - it("create xole for 0.00002eth", async function() { + it("create sole for 0.00002eth", async function() { await zapCtr.createXoleByETH(0, ts + WEEK_4, { value: ethers.parseEther("0.00002") }); - expect(await xoleCtr.balanceOf(deployer)).to.equal(ethers.parseEther("0.003158301646320516")); + expect(await soleCtr.balanceOf(deployer)).to.equal(ethers.parseEther("0.003158301646320516")); expect(await wethCtr.balanceOf(zapCtr)).to.equal(ethers.parseEther("0.000000000015376133")); }); }); - describe("increase xole by eth", function() { + describe("increase sole by eth", function() { it("fails if lp return less than minimum", async function() { await expect(zapCtr.increaseXoleByETH(ethers.parseEther("10000000"), { value: ethers.parseEther("0.1") })) .to.revertedWithCustomError(zapCtr, "InsufficientLpReturn"); }); - it("fails if xole no existing", async function() { + it("fails if sole no existing", async function() { await expect(zapCtr.increaseXoleByETH(0, { value: ethers.parseEther("0.1") })) .to.revertedWith("No existing lock found"); }); - it("increase xole for 0.1eth", async function() { + it("increase sole for 0.1eth", async function() { await zapCtr.createXoleByETH(0, ts + WEEK_4, { value: ethers.parseEther("0.1") }); await zapCtr.increaseXoleByETH(0, { value: ethers.parseEther("0.1") }); - expect(await xoleCtr.balanceOf(deployer)).to.equal(ethers.parseEther("30.121411716967329357")); + expect(await soleCtr.balanceOf(deployer)).to.equal(ethers.parseEther("30.121411716967329357")); }); });