diff --git a/contracts/XOLE.sol b/contracts/XOLE.sol index 0819f43..1d85e01 100644 --- a/contracts/XOLE.sol +++ b/contracts/XOLE.sol @@ -24,6 +24,8 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent using SignedSafeMath128 for int128; using DexData for bytes; + IERC20 public shareToken; + /* We cannot really do block numbers per se b/c slope is per time, not per block and per block could be fairly bad b/c Ethereum changes blocktimes. What we can do is to extrapolate ***At functions @@ -52,30 +54,75 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent dexAgg = _dexAgg; } + /*** Admin Functions ***/ + function setDevFundRatio(uint newRatio) external override onlyAdmin { + require(newRatio <= 10000); + devFundRatio = newRatio; + } + + function setDev(address newDev) external override onlyAdmin { + require(newDev != address(0), "0x"); + dev = newDev; + } + function setDexAgg(DexAggregatorInterface newDexAgg) external override onlyAdmin { dexAgg = newDexAgg; } - // Fees sharing functions ===== + function setShareToken(address _shareToken) external override onlyAdmin { + require(_shareToken != address(0), "0x"); + require(devFund == 0 && claimableTokenAmountInternal() == 0, 'Withdraw fund firstly'); + shareToken = IERC20(_shareToken); + } + // Fees sharing functions ===== function withdrawDevFund() external override { require(msg.sender == dev, "Dev only"); require(devFund != 0, "No fund to withdraw"); uint toSend = devFund; devFund = 0; - oleToken.transfer(dev, toSend); + shareToken.safeTransfer(dev, toSend); + } + + function withdrawCommunityFund(address to) external override onlyAdmin { + require(to != address(0), "0x"); + uint claimable = claimableTokenAmountInternal(); + require(claimable != 0, "No fund to withdraw"); + withdrewReward = withdrewReward.add(claimable); + shareToken.safeTransfer(to, claimable); + emit RewardPaid(to, claimable); + } + + function shareableTokenAmount() external override view returns (uint256){ + return shareableTokenAmountInternal(); + } + + function shareableTokenAmountInternal() internal view returns (uint256 shareableAmount){ + uint claimable = claimableTokenAmountInternal(); + if (address(shareToken) == address(oleToken)) { + shareableAmount = shareToken.balanceOf(address(this)).sub(totalLocked).sub(claimable).sub(devFund); + } else { + shareableAmount = shareToken.balanceOf(address(this)).sub(claimable).sub(devFund); + } + } + + function claimableTokenAmount() external override view returns (uint256){ + return claimableTokenAmountInternal(); + } + + function claimableTokenAmountInternal() internal view returns (uint256 claimableAmount){ + claimableAmount = totalRewarded.sub(withdrewReward); } /// @dev swap feeCollected to reward token function convertToSharingToken(uint amount, uint minBuyAmount, bytes memory dexData) external override onlyAdminOrDeveloper() { - require(totalSupply > 0, "Can't share without locked OLE"); address fromToken; address toToken; - // If no swapping, then assuming OLE reward distribution + // If no swapping, then assuming shareToken reward distribution if (dexData.length == 0) { - fromToken = address(oleToken); + fromToken = address(shareToken); } - // Not OLE + // Not shareToken else { if (dexData.isUniV2Class()) { address[] memory path = dexData.toUniV2Path(); @@ -87,26 +134,29 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent toToken = path[path.length - 1].tokenB; } } - uint newReward; + // If fromToken is ole, check amount if (fromToken == address(oleToken)) { - uint claimable = totalRewarded.sub(withdrewReward); - uint toShare = oleToken.balanceOf(address(this)).sub(claimable).sub(totalLocked).sub(devFund); - require(toShare >= amount, 'Exceed OLE balance'); - newReward = toShare; - } else { + require(oleToken.balanceOf(address(this)).sub(totalLocked) >= amount, 'Exceed OLE balance'); + } + uint newReward; + if (fromToken == address(shareToken)) { + uint toShare = shareableTokenAmountInternal(); + require(toShare >= amount, 'Exceed share token balance'); + newReward = amount; + } + else { require(IERC20(fromToken).balanceOf(address(this)) >= amount, "Exceed available balance"); (IERC20(fromToken)).safeApprove(address(dexAgg), 0); (IERC20(fromToken)).safeApprove(address(dexAgg), amount); newReward = dexAgg.sellMul(amount, minBuyAmount, dexData); + require(newReward > 0, 'New reward is 0'); } - //fromToken or toToken equal OLE ,update reward - if (fromToken == address(oleToken) || toToken == address(oleToken)) { + //fromToken or toToken equal shareToken ,update reward + if (fromToken == address(shareToken) || toToken == address(shareToken)) { uint newDevFund = newReward.mul(devFundRatio).div(10000); newReward = newReward.sub(newDevFund); devFund = devFund.add(newDevFund); totalRewarded = totalRewarded.add(newReward); - lastUpdateTime = block.timestamp; - rewardPerTokenStored = rewardPerToken(newReward); emit RewardAdded(fromToken, amount, newReward); } else { emit RewardConvert(fromToken, toToken, amount, newReward); @@ -114,97 +164,6 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent } - /// @notice calculate the amount of token reward - function earned(address account) external override view returns (uint) { - return earnedInternal(account); - } - - function earnedInternal(address account) internal view returns (uint) { - return (balances[account]) - .mul(rewardPerToken(0).sub(userRewardPerTokenPaid[account])) - .div(1e18) - .add(rewards[account]); - } - - function rewardPerToken(uint newReward) internal view returns (uint) { - if (totalSupply == 0) { - return rewardPerTokenStored; - } - - if (block.timestamp == lastUpdateTime) { - return rewardPerTokenStored.add(newReward - .mul(1e18) - .div(totalSupply)); - } else { - return rewardPerTokenStored; - } - } - - /// @notice transfer rewarded ole to msg.sender - function withdrawReward() external override { - uint reward = getReward(); - oleToken.safeTransfer(msg.sender, reward); - emit RewardPaid(msg.sender, reward); - } - - function getReward() internal updateReward(msg.sender) returns (uint) { - uint reward = rewards[msg.sender]; - if (reward > 0) { - rewards[msg.sender] = 0; - withdrewReward = withdrewReward.add(reward); - } - return reward; - } - - modifier updateReward(address account) { - rewardPerTokenStored = rewardPerToken(0); - rewards[account] = earnedInternal(account); - userRewardPerTokenPaid[account] = rewardPerTokenStored; - _; - } - - /*** Admin Functions ***/ - function setDevFundRatio(uint newRatio) external override onlyAdmin { - require(newRatio <= 10000); - devFundRatio = newRatio; - } - - function setDev(address newDev) external override onlyAdmin { - dev = newDev; - } - - - function _mint(address account, uint amount) internal { - totalSupply = totalSupply.add(amount); - balances[account] = balances[account].add(amount); - emit Transfer(address(0), account, amount); - if (delegates[account] == address(0)) { - delegates[account] = account; - } - _moveDelegates(address(0), delegates[account], amount); - _updateTotalSupplyCheckPoints(); - } - - function _burn(address account) internal { - uint burnAmount = balances[account]; - totalSupply = totalSupply.sub(burnAmount); - balances[account] = 0; - emit Transfer(account, address(0), burnAmount); - _moveDelegates(delegates[account], address(0), burnAmount); - _updateTotalSupplyCheckPoints(); - } - - function _updateTotalSupplyCheckPoints() internal { - uint32 blockNumber = safe32(block.number, "block number exceeds 32 bits"); - if (totalSupplyNumCheckpoints > 0 && totalSupplyCheckpoints[totalSupplyNumCheckpoints - 1].fromBlock == blockNumber) { - totalSupplyCheckpoints[totalSupplyNumCheckpoints - 1].votes = totalSupply; - } - else { - totalSupplyCheckpoints[totalSupplyNumCheckpoints] = Checkpoint(blockNumber, totalSupply); - totalSupplyNumCheckpoints = totalSupplyNumCheckpoints + 1; - } - - } function balanceOf(address addr) external view override returns (uint256){ return balances[addr]; @@ -257,7 +216,7 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent _deposit_for(msg.sender, _value, unlock_time, _locked, CREATE_LOCK_TYPE); } - + /// @notice Deposit `_value` additional tokens for `msg.sender` /// without modifying the unlock time /// @param _value Amount of tokens to deposit and add to the lock @@ -289,7 +248,7 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent /// @param unlock_time New time when to unlock the tokens, or 0 if unchanged /// @param _locked Previous locked amount / timestamp /// @param _type For event only. - function _deposit_for(address _addr, uint256 _value, uint256 unlock_time, LockedBalance memory _locked, int128 _type) internal updateReward(_addr) { + function _deposit_for(address _addr, uint256 _value, uint256 unlock_time, LockedBalance memory _locked, int128 _type) internal { uint256 locked_before = totalLocked; totalLocked = locked_before.add(_value); // Adding to existing lock, or if a lock is expired - creating a new one @@ -320,9 +279,41 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent emit Deposit(_addr, _value, _locked.end, _type, block.timestamp); } + function _mint(address account, uint amount) internal { + totalSupply = totalSupply.add(amount); + balances[account] = balances[account].add(amount); + emit Transfer(address(0), account, amount); + if (delegates[account] == address(0)) { + delegates[account] = account; + } + _moveDelegates(address(0), delegates[account], amount); + _updateTotalSupplyCheckPoints(); + } + + function _burn(address account) internal { + uint burnAmount = balances[account]; + totalSupply = totalSupply.sub(burnAmount); + balances[account] = 0; + emit Transfer(account, address(0), burnAmount); + _moveDelegates(delegates[account], address(0), burnAmount); + _updateTotalSupplyCheckPoints(); + } + + function _updateTotalSupplyCheckPoints() internal { + uint32 blockNumber = safe32(block.number, "block number exceeds 32 bits"); + if (totalSupplyNumCheckpoints > 0 && totalSupplyCheckpoints[totalSupplyNumCheckpoints - 1].fromBlock == blockNumber) { + totalSupplyCheckpoints[totalSupplyNumCheckpoints - 1].votes = totalSupply; + } + else { + totalSupplyCheckpoints[totalSupplyNumCheckpoints] = Checkpoint(blockNumber, totalSupply); + totalSupplyNumCheckpoints = totalSupplyNumCheckpoints + 1; + } + } + + /// @notice Withdraw all tokens for `msg.sender` /// @dev Only possible if the lock has expired - function withdraw() external override nonReentrant() updateReward(msg.sender) { + function withdraw() external override nonReentrant() { LockedBalance memory _locked = locked[msg.sender]; require(_locked.amount > 0, "Nothing to withdraw"); require(block.timestamp >= _locked.end, "The lock didn't expire"); @@ -331,11 +322,9 @@ contract XOLE is DelegateInterface, Adminable, XOLEInterface, XOLEStorage, Reent _locked.end = 0; _locked.amount = 0; locked[msg.sender] = _locked; - uint reward = getReward(); - require(IERC20(oleToken).transfer(msg.sender, value.add(reward))); + oleToken.safeTransfer(msg.sender, value); _burn(msg.sender); emit Withdraw(msg.sender, value, block.timestamp); - emit RewardPaid(msg.sender, reward); } diff --git a/contracts/XOLEInterface.sol b/contracts/XOLEInterface.sol index fdafa7d..57b60b3 100644 --- a/contracts/XOLEInterface.sol +++ b/contracts/XOLEInterface.sol @@ -56,6 +56,7 @@ contract XOLEStorage { uint public devFundRatio; // ex. 5000 => 50% // user => reward + // useless mapping(address => uint256) public rewards; // useless @@ -66,10 +67,13 @@ contract XOLEStorage { uint public withdrewReward; + // useless uint public lastUpdateTime; + // useless uint public rewardPerTokenStored; + // useless mapping(address => uint256) public userRewardPerTokenPaid; @@ -109,8 +113,14 @@ contract XOLEStorage { event RewardAdded(address fromToken, uint convertAmount, uint reward); + event RewardConvert(address fromToken, address toToken, uint convertAmount, uint returnAmount); + event RewardPaid ( + address paidTo, + uint256 amount + ); + event Transfer(address indexed from, address indexed to, uint256 value); event Deposit ( @@ -132,17 +142,12 @@ contract XOLEStorage { uint256 supply ); - event RewardPaid ( - address paidTo, - uint256 amount - ); - event FailedDelegateBySig( address indexed delegatee, - uint indexed nonce, + uint indexed nonce, uint expiry, - uint8 v, - bytes32 r, + uint8 v, + bytes32 r, bytes32 s ); } @@ -150,22 +155,27 @@ contract XOLEStorage { interface XOLEInterface { - function convertToSharingToken(uint amount, uint minBuyAmount, bytes memory data) external; + function shareableTokenAmount() external view returns (uint256); - function withdrawDevFund() external; + function claimableTokenAmount() external view returns (uint256); - function earned(address account) external view returns (uint); + function convertToSharingToken(uint amount, uint minBuyAmount, bytes memory data) external; - function withdrawReward() external; + function withdrawDevFund() external; /*** Admin Functions ***/ + function withdrawCommunityFund(address to) external; + function setDevFundRatio(uint newRatio) external; function setDev(address newDev) external; function setDexAgg(DexAggregatorInterface newDexAgg) external; + function setShareToken(address _shareToken) external; + + // xOLE functions function create_lock(uint256 _value, uint256 _unlock_time) external; diff --git a/test/FeesShareTest.js b/test/FeesShareTest.js index 33aa499..e80f3ed 100644 --- a/test/FeesShareTest.js +++ b/test/FeesShareTest.js @@ -40,6 +40,8 @@ contract("XOLE", async accounts => { let john = accounts[1]; let tom = accounts[2]; let dev = accounts[7]; + let communityAcc = accounts[8]; + let daiOLEDexData; let usdtOLEDexData; let daiUsdtDexData; @@ -61,7 +63,7 @@ contract("XOLE", async accounts => { let pair = await MockUniswapV2Pair.new(usdt.address, dai.address, toWei(10000), toWei(10000)); let oleUsdtPair = await MockUniswapV2Pair.new(usdt.address, ole.address, toWei(100000), toWei(100000)); let oleDaiPair = await MockUniswapV2Pair.new(dai.address, ole.address, toWei(100000), toWei(100000)); - daiOLEDexData = Uni2DexData + addressToBytes(dai.address) + addressToBytes(ole.address); + daiOLEDexData = Uni2DexData + addressToBytes(dai.address) + addressToBytes(ole.address); usdtOLEDexData = Uni2DexData + addressToBytes(usdt.address) + addressToBytes(ole.address); daiUsdtDexData = Uni2DexData + addressToBytes(dai.address) + addressToBytes(usdt.address); @@ -84,7 +86,7 @@ contract("XOLE", async accounts => { assert.equal(await pair.token0(), await gotPair.token0()); assert.equal(await pair.token1(), await gotPair.token1()); xole = await utils.createXOLE(ole.address, admin, dev, dexAgg.address); - + await xole.setShareToken(ole.address); m.log("Created xOLE", last8(xole.address)); await utils.mint(usdt, xole.address, 10000); @@ -139,7 +141,7 @@ contract("XOLE", async accounts => { m.log("totalRewarded:", await xole.totalRewarded()); m.log("withdrewReward:", await xole.withdrewReward()); m.log("devFund:", await xole.devFund()); - await assertThrows(xole.convertToSharingToken(toWei(1), 0, '0x'), 'Exceed OLE balance'); + await assertThrows(xole.convertToSharingToken(toWei(1), 0, '0x'), 'Exceed share token balance'); }) @@ -159,24 +161,24 @@ contract("XOLE", async accounts => { m.log("xOLE devFund:", await xole.devFund()); assert.equal('493579017198530649425', (await xole.devFund()).toString()); + m.log("xOLE withdrewReward:", await xole.withdrewReward()); assert.equal('0', (await xole.withdrewReward()).toString()); m.log("xole.totalSupply", (await xole.totalSupply()).toString()); m.log("xole.balanceOf", (await xole.balanceOf(admin)).toString()); - m.log("earned:", (await xole.earned(admin)).toString()); - m.log("rewardPerTokenStored before:", (await xole.rewardPerTokenStored()).toString()); - // withdraw ole reward - await xole.withdrawReward(); - m.log("rewardPerTokenStored after:", (await xole.rewardPerTokenStored()).toString()); - assert.equal('493579017198530640000', (await xole.withdrewReward()).toString()); - assert.equal('493579017198530640000', (await ole.balanceOf(admin)).toString()); + assert.equal('0', (await xole.rewardPerTokenStored()).toString()); + // withdraw devFund + await xole.withdrawDevFund({from: dev}); + assert.equal('493579017198530649425', (await ole.balanceOf(dev)).toString()); + // withdraw communityFund + await xole.withdrawCommunityFund(communityAcc); + assert.equal('493579017198530649425', (await ole.balanceOf(communityAcc)).toString()); + assert.equal('493579017198530649425', (await xole.withdrewReward()).toString()); //add sharingToken Reward 2000 await usdt.mint(xole.address, toWei(2000)); //sharing 1000 await xole.convertToSharingToken(toWei(1000), 0, usdtOLEDexData); - assert.equal('987158034397061298850', (await xole.totalRewarded()).toString()); - //Exceed available balance await assertThrows(xole.convertToSharingToken(toWei(20001), 0, usdtOLEDexData), 'Exceed available balance'); @@ -212,7 +214,7 @@ contract("XOLE", async accounts => { let lastbk = await web3.eth.getBlock('latest'); await xole.create_lock(toWei(10000), lastbk.timestamp + WEEK); assert.equal('10000000000000000000000', (await usdt.balanceOf(xole.address)).toString()); - await xole.convertToSharingToken(toWei(1000), 0, "0x01" + "000000" + "03" + addressToBytes(dai.address) + addressToBytes(usdt.address) + addressToBytes(ole.address)); + await xole.convertToSharingToken(toWei(1000), 0, "0x01" + "000000" + "03" + addressToBytes(dai.address) + addressToBytes(usdt.address) + addressToBytes(ole.address)); m.log("xOLE USDT balance:", await usdt.balanceOf(xole.address)); assert.equal('10000000000000000000000', (await usdt.balanceOf(xole.address)).toString()); @@ -250,9 +252,6 @@ contract("XOLE", async accounts => { await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '498495030004550854', await xole.devFund()); assertPrint("Total to share:", '498495030004550855', await xole.totalRewarded()); - assertPrint("John earned:", '246682021973748000', await xole.earned(john)); - //Tom earned more 2.08% than John - assertPrint("Tom earned:", '251813008030801958', await xole.earned(tom)); }) it("John Deposit for 1 weeks, Tom 2 weeks increase amount yet", async () => { @@ -278,9 +277,6 @@ contract("XOLE", async accounts => { await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '498495030004550854', await xole.devFund()); assertPrint("Total to share:", '498495030004550855', await xole.totalRewarded()); - assertPrint("John earned:", '163892369149313000', await xole.earned(john)); - //Tom earned more 104.16% than John - assertPrint("Tom earned:", '334602660855237420', await xole.earned(tom)); }) it("John Deposit for 1 weeks, Tom 2 weeks increase unlock time to 4 weeks", async () => { @@ -306,9 +302,6 @@ contract("XOLE", async accounts => { await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '498495030004550854', await xole.devFund()); assertPrint("Total to share:", '498495030004550855', await xole.totalRewarded()); - assertPrint("John earned:", '241706279094526000', await xole.earned(john)); - //Tom earned more 6.24% than John - assertPrint("Tom earned:", '256788750910024422', await xole.earned(tom)); }) it("John Deposit for 1 weeks, Tom 2 weeks redraw, share again", async () => { @@ -328,9 +321,6 @@ contract("XOLE", async accounts => { step("New reward 1"); await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); - assertPrint("John earned:", '246682021973748000', await xole.earned(john)); - //Tom earned more 2.08% than John - assertPrint("Tom earned:", '251813008030801958', await xole.earned(tom)); m.log("Tom balance=", (await xole.balanceOf(tom)).toString()); m.log("John balance=", (await xole.balanceOf(john)).toString()); @@ -347,9 +337,6 @@ contract("XOLE", async accounts => { assertPrint("Tom Extra Token:", 0, await xole.balanceOf(tom)); await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); - //241706279094526000+498495030004550855 - assertPrint("John earned:", '745167097231345500', await xole.earned(john)); - assertPrint("Tom earned:", '0', await xole.earned(tom)); }) it("John and Tom stakes, Tom stakes more, shares fees", async () => { m.log("process.env.FASTMODE", process.env.FASTMODE); @@ -377,10 +364,6 @@ contract("XOLE", async accounts => { await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '498495030004550854', await xole.devFund()); assertPrint("Total to share:", '498495030004550855', await xole.totalRewarded()); - assertPrint("John earned:", '311559393752844000', await xole.earned(john)); - assertPrint("Tom earned:", '186935636251706400', await xole.earned(tom)); - assertPrint("Total of John and Tom", '498495030004550400', - (await xole.earned(john)).add(await xole.earned(tom))); step("Tom stake more 200"); await ole.approve(xole.address, toWei(200), {from: tom}); @@ -392,43 +375,31 @@ contract("XOLE", async accounts => { step("New reward 1"); await xole.convertToSharingToken(toWei(1), 0, daiOLEDexData); assertPrint("Dev Fund:", '996980105262148814', await xole.devFund()); - assertPrint("John earned:", '560801931381642500', await xole.earned(john)); - assertPrint("Tom earned:", '436178173880504900', await xole.earned(tom)); // Block time insensitive step("Advancing block time ..."); timeMachine.advanceTimeAndBlock(1000); assertPrint("Dev Fund:", '996980105262148814', await xole.devFund()); - assertPrint("John earned:", '560801931381642500', await xole.earned(john)); - assertPrint("Tom earned:", '436178173880504900', await xole.earned(tom)); step("John stack more, but earning should not change because no new reward"); await ole.approve(xole.address, toWei(1000), {from: john}); await xole.increase_amount(toWei(1000), {from: john}); assertPrint("Total staked:", toWei(2000), await xole.totalLocked()); assertPrint("Dev Fund:", '996980105262148814', await xole.devFund()); - assertPrint("John earned:", '560801931381642500', await xole.earned(john)); - assertPrint("Tom earned:", '436178173880504900', await xole.earned(tom)); step("New reward 200"); await xole.convertToSharingToken(toWei(200), 0, daiOLEDexData); assertPrint("Dev Fund:", '100494603912584309258', await xole.devFund()); - assertPrint("John earned:", '75184019786873262500', await xole.earned(john)); - assertPrint("Tom earned:", '25310584125711044900', await xole.earned(tom)); await advanceBlockAndSetTime(lastbk.timestamp + 3 * WEEK); step("John exits, but earning should not change because no new reward"); await xole.withdraw({from: john}); assertPrint("Total staked:", toWei(500), await xole.totalLocked()); assertPrint("Dev Fund:", '100494603912584309258', await xole.devFund()); - assertPrint("John earned:", '0', await xole.earned(john)); - assertPrint("Tom earned:", '25310584125711044900', await xole.earned(tom)); step("New reward 100"); await xole.convertToSharingToken(toWei(100), 0, daiOLEDexData); assertPrint("Dev Fund:", '150094767100146587308', await xole.devFund()); - assertPrint("John earned:", '0', await xole.earned(john)); - assertPrint("Tom earned:", '74910747313273322900', await xole.earned(tom)); step("Tom exit, and more reward"); await xole.withdraw({from: tom}); @@ -437,23 +408,136 @@ contract("XOLE", async accounts => { await ole.approve(xole.address, toWei(1000), {from: john}); lastbk = await web3.eth.getBlock('latest'); await xole.create_lock(toWei(1000), lastbk.timestamp + WEEK, {from: john}); - assertPrint("John earned:", '0', await xole.earned(john)); step("New reward 100"); await xole.convertToSharingToken(toWei(100), 0, daiOLEDexData); assertPrint("Dev Fund:", '199596275059873518079', await xole.devFund()); - assertPrint("John earned:", '49501507959726930000', await xole.earned(john)); await advanceMultipleBlocksAndTime(10); lastbk = await web3.eth.getBlock('latest'); await xole.increase_unlock_time(lastbk.timestamp + 2 * WEEK, {from: john}); assertPrint("Dev Fund:", '199596275059873518079', await xole.devFund()); - assertPrint("John earned:", '49501507959726930000', await xole.earned(john)); step("New reward 100"); await xole.convertToSharingToken(toWei(100), 0, daiOLEDexData); assertPrint("Dev Fund:", '248999421985512891445', await xole.devFund()); - assertPrint("John earned:", '98904654885366303000', await xole.earned(john)); + + }) + + it("Convert dexData is 0x ", async () => { + await dai.mint(xole.address, toWei(1000)); + await xole.setShareToken(dai.address); + assert.equal('1000000000000000000000', (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + + await xole.convertToSharingToken(toWei(500), 0, '0x'); + + m.log("xOLE dai balance:", await dai.balanceOf(xole.address)); + assert.equal('1000000000000000000000', (await dai.balanceOf(xole.address)).toString()); + + + m.log("xOLE totalRewarded:", await xole.totalRewarded()); + assert.equal('250000000000000000000', (await xole.totalRewarded()).toString()); + + m.log("xOLE devFund:", await xole.devFund()); + assert.equal('250000000000000000000', (await xole.devFund()).toString()); + + assert.equal('500000000000000000000', (await xole.shareableTokenAmount()).toString()); + assert.equal('250000000000000000000', (await xole.claimableTokenAmount()).toString()); + + + await dai.mint(xole.address, toWei(1000)); + + assert.equal('1500000000000000000000', (await xole.shareableTokenAmount()).toString()); + assert.equal('250000000000000000000', (await xole.claimableTokenAmount()).toString()); + + // withdraw devFund + await xole.withdrawDevFund({from: dev}); + assert.equal('250000000000000000000', (await dai.balanceOf(dev)).toString()); + + await xole.convertToSharingToken(toWei(1500), 0, '0x'); + + assert.equal('1750000000000000000000', (await dai.balanceOf(xole.address)).toString()); + assert.equal('1000000000000000000000', (await xole.totalRewarded()).toString()); + assert.equal('750000000000000000000', (await xole.devFund()).toString()); + + await assertThrows(xole.convertToSharingToken(toWei(1), 0, '0x'), 'Exceed share token balance'); + + // withdraw devFund + await xole.withdrawDevFund({from: dev}); + assert.equal('1000000000000000000000', (await dai.balanceOf(dev)).toString()); + // withdraw communityFund + await xole.withdrawCommunityFund(communityAcc); + assert.equal('1000000000000000000000', (await dai.balanceOf(communityAcc)).toString()); + + assert.equal('0', (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + }) + + it("Convert minBuy limit test", async () => { + await dai.mint(xole.address, toWei(1000)); + await ole.mint(admin, toWei(10000)); + await ole.approve(xole.address, toWei(10000)); + let lastbk = await web3.eth.getBlock('latest'); + await xole.create_lock(toWei(10000), lastbk.timestamp + WEEK); + assert.equal('10000000000000000000000', (await usdt.balanceOf(xole.address)).toString()); + await assertThrows(xole.convertToSharingToken(toWei(1000), '10906610893880149131582', daiUsdtDexData), 'buy amount less than min'); + + await assertThrows(xole.convertToSharingToken(toWei(1000), "895794058774498675512", + "0x01" + "000000" + "03" + addressToBytes(dai.address) + addressToBytes(usdt.address) + addressToBytes(ole.address)), 'buy amount less than min'); + + }) + + it("Convert shareToken=oleToken test", async () => { + await ole.mint(admin, toWei(10000)); + await ole.approve(xole.address, toWei(10000)); + let lastbk = await web3.eth.getBlock('latest'); + await xole.create_lock(toWei(1), lastbk.timestamp + WEEK); + + assert.equal('0', (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + await ole.mint(xole.address, toWei(10000)); + assert.equal("10000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + // increase not effect shareableAmount + await xole.increase_amount(toWei(1)); + assert.equal("10000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + // convert dai to ole + await dai.mint(xole.address, toWei(1000)); + await xole.convertToSharingToken(toWei(1000), '0', daiOLEDexData); + assert.equal("10000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('493579017198530649425', (await xole.claimableTokenAmount()).toString()); + assert.equal('493579017198530649425', (await xole.devFund()).toString()); + // convert usdt to ole + await usdt.mint(xole.address, toWei(1000)); + await xole.convertToSharingToken(toWei(1000), '0', usdtOLEDexData); + assert.equal("10000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('987158034397061298850', (await xole.claimableTokenAmount()).toString()); + assert.equal('987158034397061298850', (await xole.devFund()).toString()); + // convert ole + await xole.convertToSharingToken(toWei(1000), '0', '0x'); + assert.equal("9000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('1487158034397061298850', (await xole.claimableTokenAmount()).toString()); + assert.equal('1487158034397061298850', (await xole.devFund()).toString()); + // withdrawCommunityFund + await xole.withdrawCommunityFund(communityAcc); + assert.equal('1487158034397061298850', (await ole.balanceOf(communityAcc)).toString()); + assert.equal("9000000000000000000000", (await xole.shareableTokenAmount()).toString()); + assert.equal('0', (await xole.claimableTokenAmount()).toString()); + assert.equal('1487158034397061298850', (await xole.devFund()).toString()); + // user withdraw ole, no effect + lastbk = await web3.eth.getBlock('latest'); + end = lastbk.timestamp + WEEK * 3; + await advanceBlockAndSetTime(end + 60 * 60 * 24); + await xole.withdraw({from: admin}); + assert.equal('0', (await xole.balanceOf(admin)).toString()); + assert.equal('10000000000000000000000', (await ole.balanceOf(admin)).toString()); + // convert all + await xole.convertToSharingToken(toWei(9000), '0', '0x'); + assert.equal("0", (await xole.shareableTokenAmount()).toString()); + assert.equal('4500000000000000000000', (await xole.claimableTokenAmount()).toString()); + assert.equal('5987158034397061298850', (await xole.devFund()).toString()); }) @@ -465,6 +549,57 @@ contract("XOLE", async accounts => { web3.eth.abi.encodeParameters(['uint256'], [1]), 0) assert.equal(1, await xole0.devFundRatio()); await assertThrows(xole0.setDevFundRatio(1), 'caller must be admin'); + }) + + it("Admin setDexAgg test", async () => { + let newDexAgg = accounts[3]; + let timeLock = await utils.createTimelock(admin); + let xole0 = await utils.createXOLE(ole.address, timeLock.address, dev, dexAgg.address, accounts[0]); + await timeLock.executeTransaction(xole0.address, 0, 'setDexAgg(address)', + web3.eth.abi.encodeParameters(['address'], [newDexAgg]), 0) + assert.equal(newDexAgg, await xole0.dexAgg()); + await assertThrows(xole0.setDexAgg(newDexAgg), 'caller must be admin'); + }) + + it("Admin setShareToken test", async () => { + let shareToken = ole.address; + let timeLock = await utils.createTimelock(admin); + let xole0 = await utils.createXOLE(ole.address, timeLock.address, dev, dexAgg.address, accounts[0]); + await timeLock.executeTransaction(xole0.address, 0, 'setShareToken(address)', + web3.eth.abi.encodeParameters(['address'], [shareToken]), 0) + assert.equal(shareToken, await xole0.shareToken()); + await assertThrows(xole0.setShareToken(shareToken), 'caller must be admin'); + + await ole.mint(xole0.address, toWei(10000)); + await xole0.convertToSharingToken(toWei(10000), 0, '0x'); + // Withdraw fund firstly + await assertThrows(timeLock.executeTransaction(xole0.address, 0, 'setShareToken(address)', + web3.eth.abi.encodeParameters(['address'], [shareToken]), 0), 'Transaction execution reverted'); + + }) + it("Admin withdrawCommunityFund test", async () => { + let to = accounts[7]; + let timeLock = await utils.createTimelock(admin); + let xole0 = await utils.createXOLE(ole.address, timeLock.address, dev, dexAgg.address, accounts[0]); + + await assertThrows(xole0.withdrawCommunityFund(to), 'caller must be admin'); + let claimableTokenAmount = await xole0.claimableTokenAmount(); + assert.equal(0, claimableTokenAmount); + // to is 0x address + await assertThrows(timeLock.executeTransaction(xole0.address, 0, 'withdrawCommunityFund(address)', + web3.eth.abi.encodeParameters(['address'], ["0x0000000000000000000000000000000000000000"]), 0, {from: admin}), 'Transaction execution reverted'); + // fund is 0 + await assertThrows(timeLock.executeTransaction(xole0.address, 0, 'withdrawCommunityFund(address)', + web3.eth.abi.encodeParameters(['address'], [to]), 0, {from: admin}), 'Transaction execution reverted'); + + await timeLock.executeTransaction(xole0.address, 0, 'setShareToken(address)', + web3.eth.abi.encodeParameters(['address'], [ole.address]), 0); + + await ole.mint(xole0.address, toWei(10000)); + await xole0.convertToSharingToken(toWei(10000), 0, '0x'); + + await timeLock.executeTransaction(xole0.address, 0, 'withdrawCommunityFund(address)', + web3.eth.abi.encodeParameters(['address'], [to]), 0, {from: admin}); }) @@ -476,7 +611,6 @@ contract("XOLE", async accounts => { web3.eth.abi.encodeParameters(['address'], [newDev]), 0) assert.equal(newDev, await xole0.dev()); await assertThrows(xole0.setDev(newDev), 'caller must be admin'); - }) it("Admin convertToSharingToken test", async () => { diff --git a/test/xOLETest.js b/test/xOLELockTest.js similarity index 98% rename from test/xOLETest.js rename to test/xOLELockTest.js index 9748cdc..1442ce4 100644 --- a/test/xOLETest.js +++ b/test/xOLELockTest.js @@ -136,6 +136,7 @@ contract("xOLE", async accounts => { // m.log("Alice withdraw"); await xole.withdraw({from: alice}); + approxAssertPrint("Alice's balance of ole", _1000, await ole.balanceOf(alice)); approxAssertPrint("xOLE Total supply", "1020800000000000000000", await xole.totalSupply()); assertPrint("Alice's balance of xOLE", "0", await xole.balanceOf(alice));