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

SimpleBuilder #312

Merged
merged 13 commits into from
Sep 20, 2023
79 changes: 79 additions & 0 deletions contracts/Builders/SimpleBuilder.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../interfaces/ISimpleProvider.sol";
import "../interfaces/ILockDealNFT.sol";
import "@openzeppelin/contracts/utils/introspection/ERC165Checker.sol";
import "@openzeppelin/contracts/token/ERC721/utils/ERC721Holder.sol";

/// @title SimpleBuilder contract
/// @notice This contract is used to create mass lock deals(NFTs)
contract SimpleBuilder is ERC721Holder {
ILockDealNFT public lockDealNFT;

constructor(ILockDealNFT _nft) {
lockDealNFT = _nft;
}

struct UserPool {
address user;
uint256 amount;
}

/// @notice Build mass pools
/// @param addressParams[0] - Provider address
/// @param addressParams[1] - Token address
/// @param userPools - Array of user pools
/// @param params - Array of params. May be empty if this is DealProvider
function buildMassPools(
address[] calldata addressParams,
UserPool[] calldata userPools,
uint256[] calldata params
) external {
uint256 length = userPools.length;
require(
ERC165Checker.supportsInterface(addressParams[0], type(ISimpleProvider).interfaceId),
"invalid provider type"
);
ISimpleProvider provider = ISimpleProvider(addressParams[0]);
address token = addressParams[1];
require(token != address(0x0), "invalid token address");
require(length > 1, "invalid userPools length");
uint256 totalAmount = _calcTotalAmount(userPools);
// one time transfer for deacrease number transactions
uint256 poolId = lockDealNFT.mintAndTransfer(address(this), token, msg.sender, totalAmount, provider);
for (uint256 i = 0; i < length; ++i) {
_createNewNFT(provider, poolId, userPools[i], params);
}
}

function _createNewNFT(
ISimpleProvider provider,
Lomet marked this conversation as resolved.
Show resolved Hide resolved
uint256 tokenPoolId,
UserPool calldata userData,
uint256[] calldata params
) internal {
require(userData.amount > 0, "invalid user amount");
require(userData.user != address(0x0), "invalid user address");
uint256 poolId = lockDealNFT.mintForProvider(userData.user, provider);
provider.registerPool(poolId, _concatParams(userData.amount, params));
lockDealNFT.copyVaultId(tokenPoolId, poolId);
}

function _calcTotalAmount(UserPool[] calldata userParams) internal pure returns (uint256 totalAmount) {
uint256 length = userParams.length;
for (uint256 i = 0; i < length; ++i) {
totalAmount += userParams[i].amount;
}
}

///@dev if params is empty, then return [amount]
function _concatParams(uint amount, uint256[] calldata params) internal pure returns (uint256[] memory result) {
uint256 length = params.length;
result = new uint256[](length + 1);
result[0] = amount;
for (uint256 i = 0; i < length; ++i) {
result[i + 1] = params[i];
}
}
}
1 change: 1 addition & 0 deletions contracts/SimpleProviders/Provider/BasicProvider.sol
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ abstract contract BasicProvider is ProviderModifiers, ISimpleProvider, ERC165 {
return
interfaceId == Refundble._INTERFACE_ID_REFUNDABLE ||
interfaceId == Bundable._INTERFACE_ID_BUNDABLE ||
interfaceId == type(ISimpleProvider).interfaceId ||
super.supportsInterface(interfaceId);
}
}
156 changes: 156 additions & 0 deletions test/SimpleBuilder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
import { MockVaultManager } from '../typechain-types';
import { DealProvider } from '../typechain-types';
import { LockDealNFT } from '../typechain-types';
import { LockDealProvider } from '../typechain-types';
import { TimedDealProvider } from '../typechain-types';
import { SimpleBuilder } from '../typechain-types';
import { deployed, token } from './helper';
import { time } from '@nomicfoundation/hardhat-network-helpers';
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
import { expect } from 'chai';
import { BigNumber } from 'ethers';
import { ethers } from 'hardhat';

describe('Simple Builder tests', function () {
let lockProvider: LockDealProvider;
let dealProvider: DealProvider;
let mockVaultManager: MockVaultManager;
let timedProvider: TimedDealProvider;
let simpleBuilder: SimpleBuilder;
let lockDealNFT: LockDealNFT;
let userPools: { user: string; amount: string }[];
let addressParams: [string, string];
let projectOwner: SignerWithAddress;
let startTime: BigNumber, finishTime: BigNumber;
const amount = ethers.utils.parseEther('100').toString();
const ONE_DAY = 86400;

// helper functions
async function _createUsers(amount: string, userCount: string) {
const pools = [];
const length = parseInt(userCount);
// Create signers
for (let i = 0; i < length; i++) {
const privateKey = ethers.Wallet.createRandom().privateKey;
const signer = new ethers.Wallet(privateKey);
const user = signer.address;
pools.push({ user: user, amount: amount });
}
return pools;
}

async function _testMassPoolsData(provider: string, amount: string, userCount: string, params: BigNumber[]) {
userPools = await _createUsers(amount, userCount);
const lastPoolId = (await lockDealNFT.totalSupply()).toNumber();
await simpleBuilder.connect(projectOwner).buildMassPools(addressParams, userPools, params);
await _logGasPrice(params);
let k = 0;
params.splice(0, 0, ethers.BigNumber.from(amount));
if (provider == timedProvider.address) {
params.push(ethers.BigNumber.from(amount));
}
for (let i = lastPoolId + 1; i < userPools.length + lastPoolId; i++) {
const userData = await lockDealNFT.getData(i);
expect(userData.provider).to.equal(provider);
expect(userData.poolId).to.equal(i);
expect(userData.owner).to.equal(userPools[k++].user);
expect(userData.token).to.equal(token);
expect(userData.params).to.deep.equal(params);
}
}

async function _logGasPrice(params: BigNumber[]) {
const tx = await simpleBuilder.connect(projectOwner).buildMassPools(addressParams, userPools, params);
const txReceipt = await tx.wait();
const gasUsed = txReceipt.gasUsed;
const GREEN_TEXT = '\x1b[32m';
console.log(`${GREEN_TEXT}Gas Used: ${gasUsed.toString()}`);
console.log(`Price per one pool: ${gasUsed.div(userPools.length)}`);
}

function _createProviderParams(provider: string) {
addressParams[0] = provider;
return provider == dealProvider.address
? []
: provider == lockProvider.address
? [finishTime]
: [startTime, finishTime];
}

before(async () => {
[projectOwner] = await ethers.getSigners();
mockVaultManager = await deployed('MockVaultManager');
const baseURI = 'https://nft.poolz.finance/test/metadata/';
lockDealNFT = await deployed('LockDealNFT', mockVaultManager.address, baseURI);
dealProvider = await deployed('DealProvider', lockDealNFT.address);
lockProvider = await deployed('LockDealProvider', lockDealNFT.address, dealProvider.address);
timedProvider = await deployed('TimedDealProvider', lockDealNFT.address, lockProvider.address);
simpleBuilder = await deployed('SimpleBuilder', lockDealNFT.address);
await lockDealNFT.setApprovedProvider(lockProvider.address, true);
await lockDealNFT.setApprovedProvider(dealProvider.address, true);
await lockDealNFT.setApprovedProvider(timedProvider.address, true);
await lockDealNFT.setApprovedProvider(lockDealNFT.address, true);
await lockDealNFT.setApprovedProvider(simpleBuilder.address, true);
});

beforeEach(async () => {
userPools = await _createUsers(amount, '4');
addressParams = [timedProvider.address, token];
startTime = ethers.BigNumber.from((await time.latest()) + ONE_DAY); // plus 1 day
finishTime = startTime.add(7 * ONE_DAY); // plus 7 days from `startTime`
});

it('should create 10 dealProvider pools', async () => {
const userCount = '10';
const params = _createProviderParams(dealProvider.address);
await _testMassPoolsData(dealProvider.address, amount, userCount, params);
});

it('should create 50 dealProvider pools', async () => {
const userCount = '50';
const params = _createProviderParams(dealProvider.address);
await _testMassPoolsData(dealProvider.address, amount, userCount, params);
});

it('should create 100 dealProvider pools', async () => {
const userCount = '100';
const params = _createProviderParams(dealProvider.address);
await _testMassPoolsData(dealProvider.address, amount, userCount, params);
});

it('should create 10 lockProvider pools', async () => {
const userCount = '10';
const params = _createProviderParams(lockProvider.address);
await _testMassPoolsData(lockProvider.address, amount, userCount, params);
});

it('should create 50 lockProvider pools', async () => {
const userCount = '50';
const params = _createProviderParams(lockProvider.address);
await _testMassPoolsData(lockProvider.address, amount, userCount, params);
});

it('should create 100 lockProvider pools', async () => {
const userCount = '100';
const params = _createProviderParams(lockProvider.address);
await _testMassPoolsData(lockProvider.address, amount, userCount, params);
});

it('should create 10 timedProvider pools', async () => {
const userCount = '10';
const params = _createProviderParams(timedProvider.address);
await _testMassPoolsData(timedProvider.address, amount, userCount, params);
});

it('should create 50 timedProvider pools', async () => {
const userCount = '50';
const params = _createProviderParams(timedProvider.address);
await _testMassPoolsData(timedProvider.address, amount, userCount, params);
});

it('should create 100 timedProvider pools', async () => {
const userCount = '100';
const params = _createProviderParams(timedProvider.address);
await _testMassPoolsData(timedProvider.address, amount, userCount, params);
});
});