Skip to content

Commit

Permalink
add create shares eth fee
Browse files Browse the repository at this point in the history
  • Loading branch information
catel committed Sep 1, 2023
1 parent f3d670a commit 6ea6969
Show file tree
Hide file tree
Showing 7 changed files with 105 additions and 40 deletions.
9 changes: 1 addition & 8 deletions contracts/TrendsAirdrop.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,14 +41,7 @@ contract TrendsAirdrop {
event VestingStarted(address indexed user, uint256 amount);
event Claimed(address indexed user, uint256 amount);

constructor(
TrendsSharesV1 _trendsShare,
address _trendsToken,
bytes32 _merkleRoot,
uint256 _maxToClaim,
uint256 _vestingPeriod,
uint256 _blockPerPeriod
) {
constructor(TrendsSharesV1 _trendsShare, address _trendsToken, bytes32 _merkleRoot, uint256 _maxToClaim, uint256 _vestingPeriod, uint256 _blockPerPeriod) {
trendsToken = IERC20(_trendsToken);
merkleRoot = _merkleRoot;
trendsShare = _trendsShare;
Expand Down
22 changes: 19 additions & 3 deletions contracts/TrendsSharesV1.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ contract TrendsSharesV1 is Ownable {
using SafeERC20 for IERC20;

error ShareCreated();
error InsufficientEth();
error UnableSendDevFund();
error ShareNotExists();
error Address0();
error InAmountNotEnough();
Expand All @@ -18,7 +20,7 @@ contract TrendsSharesV1 is Ownable {
error InvalidParams();
error InvalidDeclineRatio();

event Create(address creator, bytes32 subject);
event Create(address creator, bytes32 subject, uint256 ethFee);

event Trade(
address trader,
Expand All @@ -41,11 +43,14 @@ contract TrendsSharesV1 is Ownable {

IERC20 public immutable TRENDS;
address public protocolFeeDestination;
address public devFundDestination;

uint256 public protocolFeePercent;
uint256 public creatorFeePercent;
uint256 public holderFeePercent;

uint256 public createSharesEthFee = 1 ether / uint256(1500);

// subject => (holder => balance)
mapping(bytes32 => mapping(address => uint256)) public sharesBalance;

Expand All @@ -68,14 +73,17 @@ contract TrendsSharesV1 is Ownable {
TRENDS = _trends;
}

function createShares(bytes32 subject, uint24 declineRatio) external {
function createShares(bytes32 subject, uint24 declineRatio) external payable {
if (sharesCreator[subject] != address(0)) revert ShareCreated();
if (msg.value < createSharesEthFee) revert InsufficientEth();
sharesCreator[subject] = msg.sender;
if (declineRatio * (1 ether / declineRatio) != 1 ether) revert InvalidDeclineRatio();
// Make sure declineRatio is fully divided in calculation later
sharesDeclineRatio[subject] = declineRatio;
emit Create(msg.sender, subject);
emit Create(msg.sender, subject, msg.value);
_buyShares(msg.sender, subject, 1, 0);
(bool success, ) = devFundDestination.call{value: msg.value}("");
if (!success) revert UnableSendDevFund();
}

function buyShares(address recipient, bytes32 subject, uint256 shares, uint256 maxInAmount) external {
Expand Down Expand Up @@ -203,6 +211,14 @@ contract TrendsSharesV1 is Ownable {
protocolFeeDestination = _protocolFeeDestination;
}

function setDevFundDestination(address _devFundDestination) external onlyOwner {
devFundDestination = _devFundDestination;
}

function setCreateSharesEthFee(uint256 _createSharesEthFee) external onlyOwner {
createSharesEthFee = _createSharesEthFee;
}

function setProtocolFeePercent(uint256 _protocolFeePercent) external onlyOwner {
if (_protocolFeePercent >= 1 ether) revert InvalidParams();
protocolFeePercent = _protocolFeePercent;
Expand Down
18 changes: 9 additions & 9 deletions test/TrendsAirdrop.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const {newToken, maxInAmount, initBalance, expectRevertCustomError} = require("./utils");
const {newToken, maxInAmount, initBalance, expectRevertCustomError, createSubjectFee} = require("./utils");
const TrendsAirdrop = artifacts.require("TrendsAirdrop");
const TrendsSharesV1 = artifacts.require("TrendsSharesV1");
const {keccak256} = require('ethereumjs-util');
Expand All @@ -8,7 +8,7 @@ const timeMachine = require('ganache-time-traveler');
contract("TrendsAirdrop", (accounts) => {

const declineRatio = 16000;

function _18dc(_amount) {
return (BigInt(_amount) * BigInt(10) ** BigInt(18)).toString();
}
Expand Down Expand Up @@ -72,7 +72,7 @@ contract("TrendsAirdrop", (accounts) => {

it("should allow eligible users to claim, but can't claim twice", async () => {
// Call the create room and buy shares
await trendsSharesV1.createShares(subject, declineRatio);
await trendsSharesV1.createShares(subject, declineRatio, {value: createSubjectFee});

let proof = merkleTree.getHexProof(allLeaves[1]);
await trendsAirdrop.claim(proof, airdrop[1].amount, subject, {from: user});
Expand All @@ -90,7 +90,7 @@ contract("TrendsAirdrop", (accounts) => {

it("should allow users to claim vested airdrop", async () => {
// Call the create room and buy shares
await trendsSharesV1.createShares(subject, declineRatio);
await trendsSharesV1.createShares(subject, declineRatio, {value: createSubjectFee});

let proof = merkleTree.getHexProof(allLeaves[1]);
await trendsAirdrop.claim(proof, airdrop[1].amount, subject, {from: user});
Expand All @@ -103,15 +103,15 @@ contract("TrendsAirdrop", (accounts) => {

it("should not allow ineligible users to claim", async () => {
const proof = merkleTree.getHexProof(allLeaves[1]);
await trendsSharesV1.createShares(subject, declineRatio);
await trendsSharesV1.createShares(subject, declineRatio, {value: createSubjectFee});

await expectRevertCustomError(trendsAirdrop.claim(proof, airdrop[1].amount, subject, {from: ineligibleUser}), "InvalidProof");
});


it("should handle reaching max claimable amount", async () => {
// Simulate reaching the max claimable addresses by claiming for all eligible users
await trendsSharesV1.createShares(subject, declineRatio);
await trendsSharesV1.createShares(subject, declineRatio, {value: createSubjectFee});

for (const [index, recipient] of airdrop.entries()) {
const proof = merkleTree.getHexProof(allLeaves[index]);
Expand All @@ -135,9 +135,9 @@ contract("TrendsAirdrop", (accounts) => {
it("should allow users to claim the entire vested amount, but no more than airdrop amount, after VESTING_PERIOD has ended", async () => {
// Claim the airdrop to set the vesting
let proof = merkleTree.getHexProof(allLeaves[1]);
await trendsSharesV1.createShares(subject, declineRatio);
await trendsSharesV1.createShares(subject, declineRatio, {value: createSubjectFee});

await trendsAirdrop.claim(proof, airdrop[1].amount, subject, { from: user });
await trendsAirdrop.claim(proof, airdrop[1].amount, subject, {from: user});

// Get the vested amount
const airdropAmount = (await trendsAirdrop.vesting(user)).amount;
Expand All @@ -149,7 +149,7 @@ contract("TrendsAirdrop", (accounts) => {

let balanceBeforeClaim = await trendsToken.balanceOf(user);
// Claim the vested tokens
await trendsAirdrop.claimVestedAirdrop({ from: user });
await trendsAirdrop.claimVestedAirdrop({from: user});
let balanceAfterClaim = await trendsToken.balanceOf(user);

// Verify the entire vested amount was claimed
Expand Down
13 changes: 13 additions & 0 deletions test/TrendsSharesV1.owner.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,12 @@ contract('TrendsSharesV1', function (accounts) {
await expectRevert(trendsSharesV1.setProtocolFeeDestination(acc1, {from: acc1}), onlyOwnerError);
});

it('set dev fund destination only owner', async function () {
await trendsSharesV1.setDevFundDestination(acc1, {from: developer});
expect(await trendsSharesV1.devFundDestination()).to.eq(acc1);
await expectRevert(trendsSharesV1.setDevFundDestination(acc1, {from: acc1}), onlyOwnerError);
});

it('set protocol fee percent only owner', async function () {
await trendsSharesV1.setProtocolFeePercent(1, {from: developer});
expect(await trendsSharesV1.protocolFeePercent()).to.be.bignumber.equal(new BN(1));
Expand All @@ -47,4 +53,11 @@ contract('TrendsSharesV1', function (accounts) {
await expectRevert(trendsSharesV1.setCreatorFeePercent(1, {from: acc1}), onlyOwnerError);
await expectRevertCustomError(trendsSharesV1.setCreatorFeePercent(eth_1, {from: developer}), invalidFeeError);
});

it('set create subject eth fee only owner', async function () {
await trendsSharesV1.setCreateSharesEthFee(1, {from: developer});
expect(await trendsSharesV1.createSharesEthFee()).to.be.bignumber.equal(new BN(1));
await expectRevert(trendsSharesV1.setCreateSharesEthFee(1, {from: acc1}), onlyOwnerError);
});

});
4 changes: 2 additions & 2 deletions test/TrendsSharesV1.reward.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const {
initBalance,
maxInAmount,
share1Price,
eth_1, share2Price, minOutAmount, expectRevertCustomError, subject1, share3Price, declineRatio
eth_1, share2Price, minOutAmount, expectRevertCustomError, subject1, share3Price, declineRatio, createSubjectFee
} = require("./utils");
const {expect} = require('chai');
const {BN, expectEvent} = require("@openzeppelin/test-helpers");
Expand All @@ -25,7 +25,7 @@ contract('TrendsSharesV1', function (accounts) {
trendsToken = await newToken(developer);
trendsSharesV1 = await newSharesV1(trendsToken.address, developer);
await trendsSharesV1.setHolderFeePercent(holderFeePercent, {from: developer});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});

await trendsToken.transfer(buyer1, initBalance, {from: developer});
await trendsToken.approve(trendsSharesV1.address, initBalance, {from: buyer1});
Expand Down
74 changes: 57 additions & 17 deletions test/TrendsSharesV1.shares.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
const {
newToken, newSharesV1, expectRevert, expectRevertCustomError, subject0, share1Price, maxInAmount, eth_1,
initBalance, share2Price, share3Price, subject1, minOutAmount, declineRatio
initBalance, share2Price, share3Price, subject1, minOutAmount, declineRatio, createSubjectFee
} = require("./utils");
const {expect} = require('chai');

Expand All @@ -10,6 +10,7 @@ const {
} = require('@openzeppelin/test-helpers');
const {toWei} = require("web3-utils");
const {ZERO_ADDRESS} = require("@openzeppelin/test-helpers/src/constants");
const {web3} = require("hardhat");


const protocolFeePercent = new BN(toWei('1', 'ether')).divn(100);
Expand All @@ -23,7 +24,7 @@ const totalFees = protocolFee.add(creatorFee).add(holderFee);
let trendsToken;
let trendsSharesV1;
let protocolFeeDestination;
let lpFarmingAddress;
let devFund;
let developer;
contract('TrendsSharesV1', function (accounts) {
developer = accounts[0];
Expand All @@ -32,19 +33,22 @@ contract('TrendsSharesV1', function (accounts) {
let buyer2 = accounts[4];
let buyer3 = accounts[7];
protocolFeeDestination = accounts[5];
lpFarmingAddress = accounts[6];
devFund = accounts[6];
beforeEach(async () => {
trendsToken = await newToken(developer);
trendsSharesV1 = await newSharesV1(trendsToken.address, developer);
});
describe('create shares', function () {
let createTxReceipt;
beforeEach(async () => {
createTxReceipt = await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
createTxReceipt = await trendsSharesV1.createShares(subject0, declineRatio, {
from: creator1,
value: createSubjectFee
});
});
it('create shares emit event', async function () {
expectEvent(createTxReceipt, 'Create', {
creator: creator1, subject: subject0
creator: creator1, subject: subject0, ethFee: createSubjectFee
});
expectEvent(createTxReceipt, 'Trade', {
trader: creator1,
Expand All @@ -58,31 +62,67 @@ contract('TrendsSharesV1', function (accounts) {
supply: new BN(1)
});
});

it('create shares fees to dev fund', async function () {
await trendsSharesV1.setDevFundDestination(devFund, {from: developer});
let devFundBalance = await web3.eth.getBalance(devFund);
await trendsSharesV1.createShares(subject1, declineRatio, {
from: creator1,
value: createSubjectFee
});
expect(await web3.eth.getBalance(devFund)).to.be.bignumber.equal(createSubjectFee.add(new BN(devFundBalance)));
});

it('shares supply and holder will change after create shares', async function () {
expect(await trendsSharesV1.sharesSupply(subject0)).to.be.bignumber.equal(new BN(1));
expect(await trendsSharesV1.sharesBalance(subject0, creator1)).to.be.bignumber.equal(new BN(1));
});

it('creator can create several shares', async function () {
await trendsSharesV1.createShares(subject1, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject1, declineRatio, {from: creator1, value: createSubjectFee});
});


it('fails if shares exists', async function () {
await expectRevertCustomError(trendsSharesV1.createShares(subject0, declineRatio, {from: creator1}), "ShareCreated");
await expectRevertCustomError(trendsSharesV1.createShares(subject0, declineRatio, {
from: creator1,
value: createSubjectFee
}), "ShareCreated");
});

it('fails if with insufficient eth', async function () {
await expectRevertCustomError(trendsSharesV1.createShares(subject1, declineRatio, {
from: creator1,
value: createSubjectFee.subn(1)
}), "InsufficientEth");
});

it('fails if dev fund destination incorrect', async function () {
await trendsSharesV1.setDevFundDestination(trendsSharesV1.address, {from: developer});
await expectRevertCustomError(trendsSharesV1.createShares(subject1, declineRatio, {
from: creator1,
value: createSubjectFee
}), "UnableSendDevFund");
});

it('fails if decline ratio is 0', async function () {
await expectRevert(trendsSharesV1.createShares(subject1, 0, {from: creator1}), "by zero");
await expectRevert(trendsSharesV1.createShares(subject1, 0, {
from: creator1,
value: createSubjectFee
}), "by zero");
});

it('fails if decline ratio is not divisible', async function () {
await expectRevertCustomError(trendsSharesV1.createShares(subject1, 3, {from: creator1}), "InvalidDeclineRatio");
await expectRevertCustomError(trendsSharesV1.createShares(subject1, 3, {
from: creator1,
value: createSubjectFee
}), "InvalidDeclineRatio");
});
});

describe('buy shares', function () {
beforeEach(async () => {
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});
await trendsToken.transfer(buyer1, initBalance, {from: developer});
await trendsToken.approve(trendsSharesV1.address, initBalance, {from: buyer1});
await trendsToken.transfer(buyer2, initBalance, {from: developer});
Expand Down Expand Up @@ -168,7 +208,7 @@ contract('TrendsSharesV1', function (accounts) {

describe('sell shares', function () {
beforeEach(async () => {
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});
await trendsToken.transfer(buyer1, initBalance, {from: developer});
await trendsToken.approve(trendsSharesV1.address, initBalance, {from: buyer1});
await trendsSharesV1.buyShares(buyer1, subject0, 1, maxInAmount, {from: buyer1});
Expand Down Expand Up @@ -245,7 +285,7 @@ contract('TrendsSharesV1', function (accounts) {

describe('collect fees', function () {
beforeEach(async () => {
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});
await trendsToken.transfer(buyer1, initBalance, {from: developer});
await trendsToken.approve(trendsSharesV1.address, initBalance, {from: buyer1});
await initFee();
Expand Down Expand Up @@ -332,13 +372,13 @@ contract('TrendsSharesV1', function (accounts) {
});

it('get buy price', async function () {
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});
let price = await trendsSharesV1.getBuyPrice(subject0, 2);
expect(price).to.be.bignumber.equal(share1Price.add(share2Price));
});

it('get sell price', async function () {
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});
await trendsToken.transfer(buyer1, initBalance, {from: developer});
await trendsToken.approve(trendsSharesV1.address, initBalance, {from: buyer1});
await trendsSharesV1.buyShares(buyer1, subject0, 2, maxInAmount, {from: buyer1});
Expand All @@ -347,14 +387,14 @@ contract('TrendsSharesV1', function (accounts) {
});

it('get buy price after fees', async function () {
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});
await initFee();
let price = await trendsSharesV1.getBuyPriceWithFees(subject0, 1);
expect(price).to.be.bignumber.equal(share1Price.add(totalFees));
});

it('get sell price after fees', async function () {
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});
await trendsToken.transfer(buyer1, initBalance, {from: developer});
await trendsToken.approve(trendsSharesV1.address, initBalance, {from: buyer1});
await trendsSharesV1.buyShares(buyer1, subject0, 1, maxInAmount, {from: buyer1});
Expand All @@ -364,7 +404,7 @@ contract('TrendsSharesV1', function (accounts) {
});

it('get last share sell price after fees', async function () {
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1});
await trendsSharesV1.createShares(subject0, declineRatio, {from: creator1, value: createSubjectFee});
let price = await trendsSharesV1.getSellPriceWithFees(subject0, 1);
expect(price).to.be.bignumber.equal(new BN(0));
});
Expand Down
Loading

0 comments on commit 6ea6969

Please sign in to comment.