From 4811a7a4e9369212d14eb70b0ca13fe4134592e9 Mon Sep 17 00:00:00 2001 From: Andrew Dmytrenko Date: Wed, 20 Mar 2024 14:07:50 +0200 Subject: [PATCH] dispenser progression --- contracts/DispenserProvider.sol | 13 +++++---- contracts/mock/MockVaultManager.sol | 29 ++++++++++++++++++- test/DispenserProvider.ts | 45 +++++++++++++++++------------ test/helper.ts | 26 +++++++++++++++++ 4 files changed, 88 insertions(+), 25 deletions(-) create mode 100644 test/helper.ts diff --git a/contracts/DispenserProvider.sol b/contracts/DispenserProvider.sol index 3ddde1f..a9d3bdf 100644 --- a/contracts/DispenserProvider.sol +++ b/contracts/DispenserProvider.sol @@ -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 { @@ -69,8 +68,9 @@ contract DispenserProvider is DispenserView { Builder[] calldata data, bytes memory signature ) external validProviderId(poolId) { + require(msg.sender == owner || lockDealNFT.getApproved(poolId) == msg.sender, "DispenserProvider: Not approved"); require( - lockDealNFT.isApprovedForAll(owner, address(this)), + lockDealNFT.isApprovedForAll(owner, address(this)), "DispenserProvider: Owner has not approved the DispenserProvider" ); require( @@ -95,7 +95,7 @@ contract DispenserProvider is DispenserView { 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); } } @@ -108,17 +108,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); } } diff --git a/contracts/mock/MockVaultManager.sol b/contracts/mock/MockVaultManager.sol index 669c811..0e1aecc 100644 --- a/contracts/mock/MockVaultManager.sol +++ b/contracts/mock/MockVaultManager.sol @@ -1,4 +1,31 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.0; -import "@poolzfinance/lockdeal-nft/contracts/mock/MockVaultManager.sol"; \ No newline at end of file +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]; + } +} diff --git a/test/DispenserProvider.ts b/test/DispenserProvider.ts index 85b0cb1..f7b65a9 100644 --- a/test/DispenserProvider.ts +++ b/test/DispenserProvider.ts @@ -1,18 +1,19 @@ 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 @@ -20,12 +21,14 @@ describe("DispenserProvider", function () { 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) @@ -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) @@ -57,8 +59,9 @@ 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) packedData = ethers.utils.defaultAbiCoder.encode(builderType, [ poolId, validTime, @@ -67,11 +70,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") }) @@ -80,13 +78,24 @@ 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 () => { + userData = { simpleProvider: lockProvider.address, params: [amount.div(2), validTime] } + usersData = [{ simpleProvider: lockProvider.address, params: [amount.div(2), validTime] }] + 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 revert invalid signer address", async () => { diff --git a/test/helper.ts b/test/helper.ts new file mode 100644 index 0000000..606a024 --- /dev/null +++ b/test/helper.ts @@ -0,0 +1,26 @@ +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers" +import { ethers } from "hardhat" + +export async function createSignature(signer: SignerWithAddress, data: any[]): Promise { + 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)) +} \ No newline at end of file