Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dispenser progression #5

Merged
merged 5 commits into from
Mar 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 11 additions & 7 deletions contracts/DispenserProvider.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/cryptography/ECDSA.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "hardhat/console.sol";
import "./DispenserView.sol";

contract DispenserProvider is DispenserView {
Expand Down Expand Up @@ -70,13 +69,16 @@ contract DispenserProvider is DispenserView {
bytes memory signature
) external validProviderId(poolId) {
require(
lockDealNFT.isApprovedForAll(owner, address(this)),
"DispenserProvider: Owner has not approved the DispenserProvider"
msg.sender == owner ||
lockDealNFT.getApproved(poolId) == msg.sender ||
lockDealNFT.isApprovedForAll(owner, msg.sender),
"DispenserProvider: Caller is not approved"
);
require(
validUntil >= block.timestamp,
"DispenserProvider: Invalid validUntil"
);
require(!isTaken[poolId][owner], "DispenserProvider: Tokens already taken");
// Check the signature
bytes memory dataToCheck = abi.encodePacked(
poolId,
Expand All @@ -89,13 +91,14 @@ contract DispenserProvider is DispenserView {
"DispenserProvider: Invalid signature"
);
_createSimpleNFTs(poolId, owner, data);
isTaken[poolId][owner] = true;
}

function _encodeBuilder(
Builder[] calldata builder
) internal pure returns (bytes memory data) {
for (uint256 i = 0; i < builder.length; ++i) {
data = abi.encodePacked(data, abi.encode(builder));
data = abi.encodePacked(data, address(builder[i].simpleProvider), builder[i].params);
}
}

Expand All @@ -108,17 +111,18 @@ contract DispenserProvider is DispenserView {
uint256 poolId = lockDealNFT.mintForProvider(owner, data[i].simpleProvider);
data[i].simpleProvider.registerPool(poolId, data[i].params);
lockDealNFT.cloneVaultId(poolId, tokenPoolId);
_withdrawIfAvaliable(data[i].simpleProvider, poolId, owner);
leftAmount[tokenPoolId] -= data[i].params[0];
_withdrawIfAvailable(data[i].simpleProvider, poolId, owner);
}
}

function _withdrawIfAvaliable(
function _withdrawIfAvailable(
ISimpleProvider provider,
uint256 poolId,
address owner
) internal {
if (provider.getWithdrawableAmount(poolId) > 0) {
lockDealNFT.safeTransferFrom(address(this), owner, poolId);
lockDealNFT.safeTransferFrom(owner, address(lockDealNFT), poolId);
}
}

Expand Down
2 changes: 1 addition & 1 deletion contracts/DispenserState.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "@poolzfinance/poolz-helper-v2/contracts/interfaces/IVaultManager.sol";
import "@poolzfinance/poolz-helper-v2/contracts/interfaces/ISimpleProvider.sol";

contract DispenserState {
mapping(uint256 => mapping(address => bool)) public isAvailable;
mapping(uint256 => mapping(address => bool)) public isTaken;
mapping(uint256 => uint256) public leftAmount;

struct Builder {
Expand Down
29 changes: 28 additions & 1 deletion contracts/mock/MockVaultManager.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,31 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@poolzfinance/lockdeal-nft/contracts/mock/MockVaultManager.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";

contract MockVaultManager {
mapping(address => uint) public tokenToVaultId;
mapping(uint256 => address) vaultIdtoToken;
bool public transfers = true;
uint256 public Id = 0;

function safeDeposit(address _tokenAddress, uint _amount, address from, bytes memory signature) external returns (uint vaultId) {
require(keccak256(abi.encodePacked(signature)) == keccak256(abi.encodePacked("signature")), "wrong signature");
IERC20(_tokenAddress).transferFrom(from, address(this), _amount);
vaultId = _depositByToken(_tokenAddress);
}

function _depositByToken(address _tokenAddress) internal returns (uint vaultId) {
vaultId = ++Id;
vaultIdtoToken[vaultId] = _tokenAddress;
tokenToVaultId[_tokenAddress] = vaultId;
}

function withdrawByVaultId(uint _vaultId, address _to, uint _amount) external {
IERC20(vaultIdtoToken[_vaultId]).transfer(_to, _amount);
}

function vaultIdToTokenAddress(uint _vaultId) external view returns (address) {
return vaultIdtoToken[_vaultId];
}
}
81 changes: 63 additions & 18 deletions test/DispenserProvider.ts
Original file line number Diff line number Diff line change
@@ -1,31 +1,34 @@
import { DispenserProvider } from "../typechain-types/contracts/DispenserProvider"
import { LockDealNFT } from "../typechain-types/@poolzfinance/lockdeal-nft/contracts/LockDealNFT/LockDealNFT"
import { DealProvider } from "../typechain-types/@poolzfinance/lockdeal-nft/contracts/SimpleProviders/DealProvider/DealProvider"
import { MockVaultManager as VaultManager } from "../typechain-types/@poolzfinance/lockdeal-nft/contracts/mock/MockVaultManager"
import { MockVaultManager as VaultManager } from "../typechain-types/contracts/mock/MockVaultManager"
import { LockDealProvider } from "../typechain-types/@poolzfinance/lockdeal-nft/contracts/SimpleProviders/LockProvider/LockDealProvider"
import { ERC20Token } from "../typechain-types/@poolzfinance/poolz-helper-v2/contracts/token/ERC20Token"
import { TimedDealProvider } from "../typechain-types/@poolzfinance/lockdeal-nft/contracts/SimpleProviders/TimedDealProvider/TimedDealProvider"
import { DispenserState } from "../typechain-types/contracts/DispenserProvider"
import { time } from "@nomicfoundation/hardhat-network-helpers"
import { expect } from "chai"
import { Bytes, constants } from "ethers"
import { createSignature } from "./helper"
import { Bytes, constants, BigNumber } from "ethers"
import { ethers } from "hardhat"
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"

describe("DispenserProvider", function () {
describe("Dispenser Provider tests", function () {
let owner: SignerWithAddress
let user: SignerWithAddress
let signer: SignerWithAddress
let dispenserProvider: DispenserProvider
let token: ERC20Token
let lockDealNFT: LockDealNFT
let dealProvider: DealProvider
let userData: DispenserState.BuilderStruct[]
let userData: DispenserState.BuilderStruct
let usersData: DispenserState.BuilderStruct[]
let lockProvider: LockDealProvider
let timedProvider: TimedDealProvider
let vaultManager: VaultManager
let packedData: string
let poolId: number
let validTime: BigNumber
const builderType = ["uint256", "uint256", "address", "tuple(address,uint256[])[]"]
const creationSignature: Bytes = ethers.utils.toUtf8Bytes("signature")
const amount = ethers.utils.parseUnits("10", 18)
Expand All @@ -45,7 +48,6 @@ describe("DispenserProvider", function () {
lockProvider = await LockDealProvider.deploy(lockDealNFT.address, dealProvider.address)
const TimedDealProvider = await ethers.getContractFactory("TimedDealProvider")
timedProvider = await TimedDealProvider.deploy(lockDealNFT.address, lockProvider.address)

await lockDealNFT.setApprovedContract(dealProvider.address, true)
await lockDealNFT.setApprovedContract(lockProvider.address, true)
await lockDealNFT.setApprovedContract(timedProvider.address, true)
Expand All @@ -57,8 +59,11 @@ describe("DispenserProvider", function () {
const ERC20Token = await ethers.getContractFactory("ERC20Token")
token = await ERC20Token.deploy("Test", "TST")
poolId = (await lockDealNFT.totalSupply()).toNumber()
await token.approve(vaultManager.address, amount)
await dispenserProvider.connect(owner).deposit(signer.address, token.address, amount, creationSignature)
const validTime = ethers.BigNumber.from((await time.latest()) + ONE_DAY)
validTime = ethers.BigNumber.from((await time.latest()) + ONE_DAY)
userData = { simpleProvider: lockProvider.address, params: [amount.div(2), validTime] }
usersData = [{ simpleProvider: lockProvider.address, params: [amount.div(2), validTime] }]
packedData = ethers.utils.defaultAbiCoder.encode(builderType, [
poolId,
validTime,
Expand All @@ -67,11 +72,6 @@ describe("DispenserProvider", function () {
])
})

async function createSignature(signer: SignerWithAddress, data: string[]) {
const packedData = ethers.utils.defaultAbiCoder.encode(builderType, data)
return await signer.signMessage(ethers.utils.arrayify(packedData))
}

it("should return name of contract", async () => {
expect(await dispenserProvider.name()).to.equal("DispenserProvider")
})
Expand All @@ -80,13 +80,50 @@ describe("DispenserProvider", function () {
expect(await dispenserProvider.leftAmount(poolId)).to.equal(amount)
})

it("should transfer dealProvider nft", async () => {
const validTime = ethers.BigNumber.from((await time.latest()) + ONE_DAY)
userData = [{ simpleProvider: dealProvider.address, params: [amount] }]
const builderData = [[dealProvider.address, [amount]]]
const data = [poolId, validTime, user.address, builderData]
const signature = await createSignature(signer, data)
//await dispenserProvider.connect(user).createLock(poolId, validTime, user.address, userData, signature)
it("should deacrease leftAmount after lock", async () => {
const signatureData = [poolId, validTime, user.address, userData]
const signature = await createSignature(signer, signatureData)
await dispenserProvider.connect(user).createLock(poolId, validTime, user.address, usersData, signature)
expect(await dispenserProvider.leftAmount(poolId)).to.equal(amount.div(2))
})

it("should transfer if available", async () => {
userData = { simpleProvider: dealProvider.address, params: [amount] }
usersData = [{ simpleProvider: dealProvider.address, params: [amount] }]
const signatureData = [poolId, validTime, user.address, userData]
const signature = await createSignature(signer, signatureData)
const beforeBalance = await token.balanceOf(user.address)
await dispenserProvider.connect(user).createLock(poolId, validTime, user.address, usersData, signature)
// check if user has tokens after the transfer
expect(await token.balanceOf(user.address)).to.equal(beforeBalance.add(amount))
})

it("should create lock if approved for all", async () => {
await lockDealNFT.connect(user).setApprovalForAll(owner.address, true)
const signatureData = [poolId, validTime, user.address, userData]
const signature = await createSignature(signer, signatureData)
await expect(
dispenserProvider.connect(owner).createLock(poolId, validTime, user.address, usersData, signature)
).to.not.reverted
await lockDealNFT.connect(user).setApprovalForAll(owner.address, false)
})

it("should create lock if approved poolId", async () => {
await lockDealNFT.connect(signer).approve(owner.address, poolId)
const signatureData = [poolId, validTime, user.address, userData]
const signature = await createSignature(signer, signatureData)
await expect(
dispenserProvider.connect(owner).createLock(poolId, validTime, user.address, usersData, signature)
).to.not.reverted
})

it("should revert double creation", async () => {
const signatureData = [poolId, validTime, user.address, userData]
const signature = await createSignature(signer, signatureData)
await dispenserProvider.connect(user).createLock(poolId, validTime, user.address, usersData, signature)
await expect(
dispenserProvider.connect(user).createLock(poolId, validTime, user.address, usersData, signature)
).to.be.revertedWith("DispenserProvider: Tokens already taken")
})

it("should revert invalid signer address", async () => {
Expand All @@ -95,6 +132,14 @@ describe("DispenserProvider", function () {
).to.be.revertedWith("DispenserProvider: Invalid signer address")
})

it("should revert if sender is invalid", async () => {
const signatureData = [poolId, validTime, user.address, userData]
const signature = await createSignature(signer, signatureData)
await expect(
dispenserProvider.connect(owner).createLock(poolId, validTime, user.address, usersData, signature)
).to.be.revertedWith("DispenserProvider: Caller is not approved")
})

it("should revert invalid signer address", async () => {
await expect(
dispenserProvider.connect(owner).deposit(owner.address, constants.AddressZero, amount, creationSignature)
Expand Down
26 changes: 26 additions & 0 deletions test/helper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"
import { ethers } from "hardhat"

export async function createSignature(signer: SignerWithAddress, data: any[]): Promise<string> {
const types: string[] = []
const values: any[] = []
for (const element of data) {
if (typeof element === "string") {
types.push("address")
values.push(element)
} else if (typeof element === "object" && Array.isArray(element)) {
types.push("uint256[]")
values.push(element)
} else if (typeof element === "number" || ethers.BigNumber.isBigNumber(element)) {
types.push("uint256")
values.push(element)
} else if (typeof element === "object" && !Array.isArray(element)) {
types.push("address")
values.push(element.simpleProvider)
types.push("uint256[]")
values.push(element.params)
}
}
const packedData = ethers.utils.solidityKeccak256(types, values)
return signer.signMessage(ethers.utils.arrayify(packedData))
}