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

Add Fee Collector #394

Closed
wants to merge 19 commits into from
Closed
49 changes: 49 additions & 0 deletions contracts/FeeProvider/FeeCollector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/IERC721Receiver.sol";
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "./FeeDealProvider.sol";
import "./FeeLockProvider.sol";
import "./FeeTimedProvider.sol";

contract FeeCollector is IERC721Receiver, IFeeCollector {
uint256 public fee; // 1e18 = 100%
address public immutable feeCollector;
ILockDealNFT public immutable lockDealNFT;
IProvider public immutable feeDealProvider;
IProvider public immutable feeLockProvider;
IProvider public immutable feeTimedProvider;
YouStillAlive marked this conversation as resolved.
Show resolved Hide resolved
bool public feeCollected;

constructor(uint256 _fee, address _feeCollector, ILockDealNFT _lockDealNFT) {
fee = _fee;
feeCollector = _feeCollector;
lockDealNFT = _lockDealNFT;
feeDealProvider = new FeeDealProvider(IFeeCollector(this), _lockDealNFT);
feeLockProvider = new FeeLockProvider(_lockDealNFT, feeDealProvider);
feeTimedProvider = new FeeTimedProvider(_lockDealNFT, feeLockProvider);
YouStillAlive marked this conversation as resolved.
Show resolved Hide resolved
}

function onERC721Received(

Check warning on line 28 in contracts/FeeProvider/FeeCollector.sol

View check run for this annotation

Codecov / codecov/patch

contracts/FeeProvider/FeeCollector.sol#L28

Added line #L28 was not covered by tests
address provider,
address user,
uint256 poolId,
bytes calldata
) external override returns (bytes4) {
require(!feeCollected, "FeeCollectorProvider: fee already collected");
require(provider == address(lockDealNFT), "FeeCollectorProvider: wrong provider");
feeCollected = true;
uint256 amount = lockDealNFT.getWithdrawableAmount(poolId);
uint256 feeAmount = (amount * fee) / 1e18;
lockDealNFT.safeTransferFrom(address(this), address(lockDealNFT), poolId);
IERC20 token = IERC20(lockDealNFT.tokenOf(poolId));
token.transfer(feeCollector, feeAmount);
token.transfer(user, amount - feeAmount);

Check warning on line 42 in contracts/FeeProvider/FeeCollector.sol

View check run for this annotation

Codecov / codecov/patch

contracts/FeeProvider/FeeCollector.sol#L36-L42

Added lines #L36 - L42 were not covered by tests
if (lockDealNFT.ownerOf(poolId) == address(this)) {
lockDealNFT.transferFrom(address(this), user, poolId);

Check warning on line 44 in contracts/FeeProvider/FeeCollector.sol

View check run for this annotation

Codecov / codecov/patch

contracts/FeeProvider/FeeCollector.sol#L44

Added line #L44 was not covered by tests
}
feeCollected = false;
return IERC721Receiver.onERC721Received.selector;

Check warning on line 47 in contracts/FeeProvider/FeeCollector.sol

View check run for this annotation

Codecov / codecov/patch

contracts/FeeProvider/FeeCollector.sol#L46-L47

Added lines #L46 - L47 were not covered by tests
}
}
22 changes: 22 additions & 0 deletions contracts/FeeProvider/FeeDealProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../SimpleProviders/DealProvider/DealProvider.sol";
import "./IFeeCollector.sol";

contract FeeDealProvider is DealProvider {
IFeeCollector public feeCollector;

constructor(IFeeCollector _feeCollector, ILockDealNFT _lockDealNFT) DealProvider(_lockDealNFT) {
feeCollector = _feeCollector;
name = "FeeDealProvider";
}

function _withdraw(
uint256 poolId,
uint256 amount
) internal override firewallProtectedSig(0x9e2bf22c) returns (uint256 withdrawnAmount, bool isFinal) {
require(feeCollector.feeCollected(), "FeeDealProvider: fee not collected");
return super._withdraw(poolId, amount);

Check warning on line 20 in contracts/FeeProvider/FeeDealProvider.sol

View check run for this annotation

Codecov / codecov/patch

contracts/FeeProvider/FeeDealProvider.sol#L20

Added line #L20 was not covered by tests
}
}
11 changes: 11 additions & 0 deletions contracts/FeeProvider/FeeLockProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../SimpleProviders/LockProvider/LockDealProvider.sol";

contract FeeLockProvider is LockDealProvider {
constructor(ILockDealNFT _lockDealNFT, IProvider _provider) LockDealProvider(_lockDealNFT, address(_provider)) {
require(keccak256(bytes(_provider.name())) == keccak256(bytes("FeeDealProvider")), "invalid provider");
name = "FeeLockProvider";
}
}
11 changes: 11 additions & 0 deletions contracts/FeeProvider/FeeTimedProvider.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../SimpleProviders/TimedDealProvider/TimedDealProvider.sol";

contract FeeTimedProvider is TimedDealProvider {
constructor(ILockDealNFT lockDealNFT, IProvider _provider) TimedDealProvider(lockDealNFT, address(_provider)) {
require(keccak256(bytes(_provider.name())) == keccak256(bytes("FeeLockProvider")), "invalid provider");
name = "FeeTimedProvider";
}
}
7 changes: 7 additions & 0 deletions contracts/FeeProvider/IFeeCollector.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface IFeeCollector {
// Function to get the value of feeCollected
function feeCollected() external view returns (bool);
}
2 changes: 1 addition & 1 deletion contracts/SimpleProviders/DealProvider/DealProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ contract DealProvider is DealProviderState, BasicProvider {
function _withdraw(
uint256 poolId,
uint256 amount
) internal override firewallProtectedSig(0x9e2bf22c) returns (uint256 withdrawnAmount, bool isFinal) {
) internal virtual override firewallProtectedSig(0x9e2bf22c) returns (uint256 withdrawnAmount, bool isFinal) {
if (poolIdToAmount[poolId] >= amount) {
poolIdToAmount[poolId] -= amount;
withdrawnAmount = amount;
Expand Down
61 changes: 61 additions & 0 deletions test/FeeCollector/FeeDealProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { FeeDealProvider, LockDealNFT, FeeCollector } from '../../typechain-types';
import { MockVaultManager } from '../../typechain-types';
import { deployed, token, MAX_RATIO } from '../helper';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { BigNumber, Bytes, constants } from 'ethers';

Check notice on line 6 in test/FeeCollector/FeeDealProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeDealProvider.ts#L6

'constants' is defined but never used. (@typescript-eslint/no-unused-vars)
import { ethers } from 'hardhat';

describe('Fee Deal Provider', function () {
let lockDealNFT: LockDealNFT;
let feeDealProvider: FeeDealProvider;
let mockVaultManager: MockVaultManager;
let feeCollector: FeeCollector;
let poolId: number;
let receiver: SignerWithAddress;
let newOwner: SignerWithAddress;

Check notice on line 16 in test/FeeCollector/FeeDealProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeDealProvider.ts#L16

'newOwner' is assigned a value but never used. (@typescript-eslint/no-unused-vars)
let addresses: string[];
let params: [number];
let vaultId: BigNumber;
const name: string = 'FeeDealProvider';
const amount = 10000;
const fee = '100';
const signature: Bytes = ethers.utils.toUtf8Bytes('signature');
const ratio = MAX_RATIO.div(2); // half of the amount

Check notice on line 24 in test/FeeCollector/FeeDealProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeDealProvider.ts#L24

'ratio' is assigned a value but never used. (@typescript-eslint/no-unused-vars)

before(async () => {
[receiver, newOwner] = await ethers.getSigners();
mockVaultManager = await deployed('MockVaultManager');
lockDealNFT = await deployed('LockDealNFT', mockVaultManager.address, '');
feeCollector = await deployed('FeeCollector', fee, receiver.address, lockDealNFT.address);
const feeDealProviderAddress = await feeCollector.feeDealProvider();
feeDealProvider = await ethers.getContractAt('FeeDealProvider', feeDealProviderAddress);
await lockDealNFT.setApprovedContract(feeDealProvider.address, true);
});

beforeEach(async () => {
poolId = (await lockDealNFT.totalSupply()).toNumber();
params = [amount];
addresses = [receiver.address, token];
});

it("should return fee deal provider's name", async () => {
expect(await feeDealProvider.name()).to.equal(name);
});

it('should create a new fee deal', async () => {
await feeDealProvider.createNewPool(addresses, params, signature);
vaultId = await mockVaultManager.Id();
const data = await lockDealNFT.getData(poolId);
expect(data).to.deep.equal([feeDealProvider.address, name, poolId, vaultId, receiver.address, token, params]);
});

it('should revert withdraw from LockDealNFT', async () => {
await feeDealProvider.createNewPool(addresses, params, signature);
await expect(
lockDealNFT
.connect(receiver)
['safeTransferFrom(address,address,uint256)'](receiver.address, lockDealNFT.address, poolId),
).to.be.revertedWith('FeeDealProvider: fee not collected');
});
});
65 changes: 65 additions & 0 deletions test/FeeCollector/FeeLockProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { FeeLockProvider, LockDealNFT, FeeCollector } from '../../typechain-types';
import { MockVaultManager } from '../../typechain-types';
import { deployed, token, MAX_RATIO } from '../helper';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { time } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
import { BigNumber, Bytes, constants } from 'ethers';

Check notice on line 7 in test/FeeCollector/FeeLockProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeLockProvider.ts#L7

'constants' is defined but never used. (@typescript-eslint/no-unused-vars)
import { ethers } from 'hardhat';

describe('Fee Lock Provider', function () {
let lockDealNFT: LockDealNFT;
let feeLockProvider: FeeLockProvider;
let mockVaultManager: MockVaultManager;
let feeCollector: FeeCollector;
let poolId: number;
let receiver: SignerWithAddress;
let newOwner: SignerWithAddress;

Check notice on line 17 in test/FeeCollector/FeeLockProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeLockProvider.ts#L17

'newOwner' is assigned a value but never used. (@typescript-eslint/no-unused-vars)
let startTime: number;
let addresses: string[];
let params: [number, number];
let vaultId: BigNumber;
const name: string = 'FeeLockProvider';
const amount = 10000;
const fee = '100';
const signature: Bytes = ethers.utils.toUtf8Bytes('signature');
const ratio = MAX_RATIO.div(2); // half of the amount

Check notice on line 26 in test/FeeCollector/FeeLockProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeLockProvider.ts#L26

'ratio' is assigned a value but never used. (@typescript-eslint/no-unused-vars)

before(async () => {
[receiver, newOwner] = await ethers.getSigners();
mockVaultManager = await deployed('MockVaultManager');
lockDealNFT = await deployed('LockDealNFT', mockVaultManager.address, '');
feeCollector = await deployed('FeeCollector', fee, receiver.address, lockDealNFT.address);
const feeLockProviderAddress = await feeCollector.feeLockProvider();
feeLockProvider = await ethers.getContractAt('FeeLockProvider', feeLockProviderAddress);
await lockDealNFT.setApprovedContract(feeLockProvider.address, true);
});

beforeEach(async () => {
startTime = (await time.latest()) + 100;
params = [amount, startTime];
addresses = [receiver.address, token];
poolId = (await lockDealNFT.totalSupply()).toNumber();
vaultId = await mockVaultManager.Id();
});

it("should return fee lock provider's name", async () => {
expect(await feeLockProvider.name()).to.equal(name);
});

it('should create a new fee lock deal', async () => {
await feeLockProvider.createNewPool(addresses, params, signature);
vaultId = await mockVaultManager.Id();
const data = await lockDealNFT.getData(poolId);
expect(data).to.deep.equal([feeLockProvider.address, name, poolId, vaultId, receiver.address, token, params]);
});

it('should revert withdraw from LockDealNFT', async () => {
await feeLockProvider.createNewPool(addresses, params, signature);
await expect(
lockDealNFT
.connect(receiver)
['safeTransferFrom(address,address,uint256)'](receiver.address, lockDealNFT.address, poolId),
).to.be.revertedWith('FeeDealProvider: fee not collected');
});
});
80 changes: 80 additions & 0 deletions test/FeeCollector/FeeTimedProvider.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { FeeTimedProvider, LockDealNFT, FeeCollector } from '../../typechain-types';
import { MockVaultManager } from '../../typechain-types';
import { deployed, token, MAX_RATIO } from '../helper';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { time } from '@nomicfoundation/hardhat-network-helpers';
import { expect } from 'chai';
import { BigNumber, Bytes, constants } from 'ethers';

Check notice on line 7 in test/FeeCollector/FeeTimedProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeTimedProvider.ts#L7

'constants' is defined but never used. (@typescript-eslint/no-unused-vars)
import { ethers } from 'hardhat';

describe('Fee Timed Provider', function () {
let lockDealNFT: LockDealNFT;
let feeTimeProvider: FeeTimedProvider;
let mockVaultManager: MockVaultManager;
let feeCollector: FeeCollector;
let poolId: number;
let receiver: SignerWithAddress;
let newOwner: SignerWithAddress;

Check notice on line 17 in test/FeeCollector/FeeTimedProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeTimedProvider.ts#L17

'newOwner' is assigned a value but never used. (@typescript-eslint/no-unused-vars)
let addresses: string[];
let params: [number, number, number];
let vaultId: BigNumber;
const name: string = 'FeeTimedProvider';
const amount = 10000;
const fee = '100';
const ONE_DAY = 86400;
let startTime: number, finishTime: number;
let halfTime: number;

Check notice on line 26 in test/FeeCollector/FeeTimedProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeTimedProvider.ts#L26

'halfTime' is assigned a value but never used. (@typescript-eslint/no-unused-vars)
const signature: Bytes = ethers.utils.toUtf8Bytes('signature');
const ratio = MAX_RATIO.div(2); // half of the amount

Check notice on line 28 in test/FeeCollector/FeeTimedProvider.ts

View check run for this annotation

codefactor.io / CodeFactor

test/FeeCollector/FeeTimedProvider.ts#L28

'ratio' is assigned a value but never used. (@typescript-eslint/no-unused-vars)

before(async () => {
[receiver, newOwner] = await ethers.getSigners();
mockVaultManager = await deployed('MockVaultManager');
lockDealNFT = await deployed('LockDealNFT', mockVaultManager.address, '');
feeCollector = await deployed('FeeCollector', fee, receiver.address, lockDealNFT.address);
const feeTimeProviderAddress = await feeCollector.feeTimedProvider();
feeTimeProvider = await ethers.getContractAt('FeeTimedProvider', feeTimeProviderAddress);
const feeLockProvider = await feeCollector.feeLockProvider();
await lockDealNFT.setApprovedContract(feeLockProvider, true);
await lockDealNFT.setApprovedContract(feeTimeProvider.address, true);
});

beforeEach(async () => {
startTime = (await time.latest()) + ONE_DAY; // plus 1 day
finishTime = startTime + 7 * ONE_DAY; // plus 7 days from `startTime`
params = [amount, startTime, finishTime];
poolId = (await lockDealNFT.totalSupply()).toNumber();
addresses = [receiver.address, token];
vaultId = await mockVaultManager.Id();
halfTime = (finishTime - startTime) / 2;
});

it("should return fee timed provider's name", async () => {
expect(await feeTimeProvider.name()).to.equal(name);
});

it('should create a new fee timed deal', async () => {
await feeTimeProvider.createNewPool(addresses, params, signature);
vaultId = await mockVaultManager.Id();
const data = await lockDealNFT.getData(poolId);
const expectedParams = [amount, startTime, finishTime, amount];
expect(data).to.deep.equal([
feeTimeProvider.address,
name,
poolId,
vaultId,
receiver.address,
token,
expectedParams,
]);
});

it('should revert withdraw from LockDealNFT', async () => {
await feeTimeProvider.createNewPool(addresses, params, signature);
await expect(
lockDealNFT
.connect(receiver)
['safeTransferFrom(address,address,uint256)'](receiver.address, lockDealNFT.address, poolId),
).to.be.revertedWith('FeeDealProvider: fee not collected');
});
});
Loading