From 552a00dfb8da53ae91b8546a7a747aa89138d4ed Mon Sep 17 00:00:00 2001 From: udkreddySomish Date: Tue, 5 Jan 2021 22:57:41 +0530 Subject: [PATCH 1/9] Added option to provide round id while settling the market --- contracts/AllMarketsV2.sol | 100 +++++++ contracts/MarketUtilityV3.sol | 54 ++++ contracts/interfaces/IMarketUtility.sol | 6 + contracts/mock/MockConfig.sol | 4 +- test/26_settleMarketV2.test.js | 363 ++++++++++++++++++++++++ 5 files changed, 525 insertions(+), 2 deletions(-) create mode 100644 contracts/AllMarketsV2.sol create mode 100644 contracts/MarketUtilityV3.sol create mode 100644 test/26_settleMarketV2.test.js diff --git a/contracts/AllMarketsV2.sol b/contracts/AllMarketsV2.sol new file mode 100644 index 000000000..d2b84ff62 --- /dev/null +++ b/contracts/AllMarketsV2.sol @@ -0,0 +1,100 @@ +/* Copyright (C) 2020 PlotX.io + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ */ + +pragma solidity 0.5.7; + +import "./AllMarkets.sol"; + +contract AllMarketsV2 is AllMarkets { + + /** + * @dev Changes the master address and update it's instance + */ + function setMasterAddress() public { + } + + /** + * @dev Start the initial market and set initial variables. + */ + function addInitialMarketTypesAndStart(address _marketCreationRewards,address _ethAddress, address _marketUtility, uint32 _marketStartTime, address _ethFeed, address _btcFeed) external { + } + + /** + * @dev Create the market. + * @param _marketCurrencyIndex The index of market currency feed + * @param _marketTypeIndex The time duration of market. + */ + function createMarket(uint32 _marketCurrencyIndex,uint32 _marketTypeIndex) public payable { + } + + /** + * @dev Create the market and settle the prenultimate market. + * @param _marketCurrencyIndex The index of market currency feed + * @param _marketTypeIndex The time duration of market. + * @param _roundId The chainlink round id of the penultimate market, with the round updated time closest to market settlement time + */ + function createMarketAndSettle(uint32 _marketCurrencyIndex,uint32 _marketTypeIndex, uint80 _roundId) public { + uint256 gasProvided = gasleft(); + require(!marketCreationPaused && !marketTypeArray[_marketTypeIndex].paused); + _closePreviousMarketV2( _marketTypeIndex, _marketCurrencyIndex, _roundId); + marketUtility.update(); + uint32 _startTime = calculateStartTimeForMarket(_marketCurrencyIndex, _marketTypeIndex); + (uint64 _minValue, uint64 _maxValue) = marketUtility.calculateOptionRange(marketTypeArray[_marketTypeIndex].optionRangePerc, marketCurrencies[_marketCurrencyIndex].decimals, marketCurrencies[_marketCurrencyIndex].roundOfToNearest, marketCurrencies[_marketCurrencyIndex].marketFeed); + uint64 _marketIndex = uint64(marketBasicData.length); + marketBasicData.push(MarketBasicData(_marketTypeIndex,_marketCurrencyIndex,_startTime, marketTypeArray[_marketTypeIndex].predictionTime,_minValue,_maxValue)); + marketDataExtended[_marketIndex].ethCommission = commissionPercGlobal.ethCommission; + marketDataExtended[_marketIndex].plotCommission = commissionPercGlobal.plotCommission; + (marketCreationData[_marketTypeIndex][_marketCurrencyIndex].penultimateMarket, marketCreationData[_marketTypeIndex][_marketCurrencyIndex].latestMarket) = + (marketCreationData[_marketTypeIndex][_marketCurrencyIndex].latestMarket, _marketIndex); + emit MarketQuestion(_marketIndex, marketCurrencies[_marketCurrencyIndex].currencyName, _marketTypeIndex, _startTime, marketTypeArray[_marketTypeIndex].predictionTime, _minValue, _maxValue); + marketCreationRewards.calculateMarketCreationIncentive(msg.sender, gasProvided - gasleft(), _marketIndex); + } + + /** + * @dev Internal function to settle the previous market + */ + function _closePreviousMarket(uint64 _marketTypeIndex, uint64 _marketCurrencyIndex) internal { + } + + /** + * @dev Internal function to settle the previous market + */ + function _closePreviousMarketV2(uint64 _marketTypeIndex, uint64 _marketCurrencyIndex, uint80 _roundId) internal { + uint64 currentMarket = marketCreationData[_marketTypeIndex][_marketCurrencyIndex].latestMarket; + if(currentMarket != 0 && _roundId > 0) { + require(marketStatus(currentMarket) >= PredictionStatus.InSettlement); + uint64 penultimateMarket = marketCreationData[_marketTypeIndex][_marketCurrencyIndex].penultimateMarket; + if(penultimateMarket > 0 && now >= marketSettleTime(penultimateMarket)) { + settleMarketByRoundId(penultimateMarket, _roundId); + } + } + } + + /** + * @dev Settle the market, setting the winning option + */ + function settleMarket(uint256 _marketId) public { + } + + /** + * @dev Settle the market, setting the winning option + */ + function settleMarketByRoundId(uint256 _marketId, uint80 _roundId) public { + if(marketStatus(_marketId) == PredictionStatus.InSettlement) { + (uint256 _value, uint256 _roundIdUsed) = marketUtility.getSettlemetPriceByRoundId(marketCurrencies[marketBasicData[_marketId].currency].marketFeed, marketSettleTime(_marketId), _roundId); + _postResult(_value, _roundIdUsed, _marketId); + } + } +} \ No newline at end of file diff --git a/contracts/MarketUtilityV3.sol b/contracts/MarketUtilityV3.sol new file mode 100644 index 000000000..e90a04e02 --- /dev/null +++ b/contracts/MarketUtilityV3.sol @@ -0,0 +1,54 @@ +/* Copyright (C) 2020 PlotX.io + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ */ + +pragma solidity 0.5.7; + +import "./MarketUtilityV2.sol"; + +contract MarketUtilityV3 is MarketUtilityV2 { + /** + * @dev Get price of provided feed address + * @param _currencyFeedAddress Feed Address of currency on which market options are based on + * @return Current price of the market currency + **/ + function getSettlemetPriceByRoundId( + address _currencyFeedAddress, + uint256 _settleTime, + uint80 _roundId + ) public view returns (uint256 latestAnswer, uint256 roundId) { + uint80 roundIdToCheck; + uint256 currentRoundTime; + int256 currentRoundAnswer; + (roundIdToCheck, currentRoundAnswer, , currentRoundTime, )= IChainLinkOracle(_currencyFeedAddress).latestRoundData(); + if(roundIdToCheck == _roundId) { + if(currentRoundTime <= _settleTime) { + return (uint256(currentRoundAnswer), roundIdToCheck); + } + } else { + (roundIdToCheck, currentRoundAnswer, , currentRoundTime, )= IChainLinkOracle(_currencyFeedAddress).getRoundData(_roundId + 1); + require(currentRoundTime > _settleTime); + roundIdToCheck = _roundId + 1; + } + while(currentRoundTime > _settleTime) { + roundIdToCheck--; + (roundIdToCheck, currentRoundAnswer, , currentRoundTime, )= IChainLinkOracle(_currencyFeedAddress).getRoundData(roundIdToCheck); + if(currentRoundTime <= _settleTime) { + break; + } + } + return + (uint256(currentRoundAnswer), roundIdToCheck); + } +} \ No newline at end of file diff --git a/contracts/interfaces/IMarketUtility.sol b/contracts/interfaces/IMarketUtility.sol index bb7bf8978..05d974a02 100644 --- a/contracts/interfaces/IMarketUtility.sol +++ b/contracts/interfaces/IMarketUtility.sol @@ -82,4 +82,10 @@ contract IMarketUtility { address _currencyFeedAddress, uint256 _settleTime ) public view returns (uint256 latestAnswer, uint256 roundId); + + function getSettlemetPriceByRoundId( + address _currencyFeedAddress, + uint256 _settleTime, + uint80 _roundId + ) public view returns (uint256 latestAnswer, uint256 roundId); } diff --git a/contracts/mock/MockConfig.sol b/contracts/mock/MockConfig.sol index 51c4c0071..6b282e38d 100644 --- a/contracts/mock/MockConfig.sol +++ b/contracts/mock/MockConfig.sol @@ -1,8 +1,8 @@ pragma solidity 0.5.7; -import "../MarketUtilityV2.sol"; +import "../MarketUtilityV3.sol"; -contract MockConfig is MarketUtilityV2 { +contract MockConfig is MarketUtilityV3 { uint public priceOfToken; bool public mockFlag; diff --git a/test/26_settleMarketV2.test.js b/test/26_settleMarketV2.test.js new file mode 100644 index 000000000..c2fde045d --- /dev/null +++ b/test/26_settleMarketV2.test.js @@ -0,0 +1,363 @@ +const { assert } = require("chai"); +const OwnedUpgradeabilityProxy = artifacts.require("OwnedUpgradeabilityProxy"); +const Market = artifacts.require("MockMarket"); +const Plotus = artifacts.require("MarketRegistry"); +const Master = artifacts.require("Master"); +const MemberRoles = artifacts.require("MemberRoles"); +const PlotusToken = artifacts.require("MockPLOT"); +const MockWeth = artifacts.require("MockWeth"); +const MockConfig = artifacts.require("MockConfig"); //mock +const Governance = artifacts.require("GovernanceV2"); +const AllMarkets = artifacts.require("MockAllMarkets"); +const AllMarketsV2 = artifacts.require("AllMarketsV2"); +const MockchainLink = artifacts.require('MockChainLinkAggregator'); +const MockUniswapRouter = artifacts.require("MockUniswapRouter"); +const MockUniswapV2Pair = artifacts.require("MockUniswapV2Pair"); +const MockUniswapFactory = artifacts.require("MockUniswapFactory"); +const TokenController = artifacts.require("MockTokenController"); +const DummyTokenMock2 = artifacts.require("SampleERC"); + +const web3 = Market.web3; +const ethAddress = "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE"; +const increaseTime = require("./utils/increaseTime.js").increaseTime; +const assertRevert = require("./utils/assertRevert").assertRevert; +const latestTime = require("./utils/latestTime").latestTime; +const encode = require("./utils/encoder.js").encode; +const encode1 = require("./utils/encoder.js").encode1; +const gvProposal = require("./utils/gvProposal.js").gvProposalWithIncentiveViaTokenHolder; +const { toHex, toWei, toChecksumAddress } = require("./utils/ethTools"); +const to8Power = (number) => String(parseFloat(number) * 1e8); + +// Multiplier Sheet + +contract("26_SettleMarketV2. AllMarket", async function([ + ab1, + ab2, + ab3, + ab4, + mem1, + mem2, + mem3, + mem4, + mem5, + mem6, + mem7, + mem8, + mem9, + mem10, + notMember, + dr1, + dr2, + dr3, + user11, + user12, + user13, +]) { + let masterInstance, + plotusToken, + mockMarketConfig, + MockUniswapRouterInstance, + tokenControllerAdd, + tokenController, + plotusNewAddress, + plotusNewInstance, + governance, + mockUniswapV2Pair, + mockUniswapFactory, + weth, + allMarkets, chainlinkAggregator; + before(async () => { + masterInstance = await OwnedUpgradeabilityProxy.deployed(); + masterInstance = await Master.at(masterInstance.address); + plotusToken = await PlotusToken.deployed(); + chainlinkAggregator = await MockchainLink.deployed(); + tokenControllerAdd = await masterInstance.getLatestAddress(web3.utils.toHex("TC")); + tokenController = await TokenController.at(tokenControllerAdd); + plotusNewAddress = await masterInstance.getLatestAddress(web3.utils.toHex("PL")); + let memberRoles = await masterInstance.getLatestAddress(web3.utils.toHex("MR")); + memberRoles = await MemberRoles.at(memberRoles); + governance = await masterInstance.getLatestAddress(web3.utils.toHex("GV")); + governance = await Governance.at(governance); + MockUniswapRouterInstance = await MockUniswapRouter.deployed(); + mockUniswapFactory = await MockUniswapFactory.deployed(); + plotusNewInstance = await Plotus.at(plotusNewAddress); + mockMarketConfig = await plotusNewInstance.marketUtility(); + mockMarketConfig = await MockConfig.at(mockMarketConfig); + weth = await MockWeth.deployed(); + await mockMarketConfig.setWeth(weth.address); + let newUtility = await MockConfig.new(); + let actionHash = encode( + "upgradeContractImplementation(address,address)", + mockMarketConfig.address, + newUtility.address + ); + await gvProposal( + 6, + actionHash, + await MemberRoles.at(await masterInstance.getLatestAddress(toHex("MR"))), + governance, + 2, + 0 + ); + await increaseTime(604800); + mockUniswapV2Pair = await MockUniswapV2Pair.new(); + await mockUniswapV2Pair.initialize(plotusToken.address, weth.address); + await weth.deposit({ from: user11, value: toWei(10) }); + await weth.transfer(mockUniswapV2Pair.address, toWei(10), { from: user11 }); + await plotusToken.transfer(mockUniswapV2Pair.address, toWei(1000)); + initialPLOTPrice = 1000 / 10; + initialEthPrice = 10 / 1000; + await mockUniswapFactory.setPair(mockUniswapV2Pair.address); + await mockUniswapV2Pair.sync(); + newUtility = await MockConfig.new(); + actionHash = encode( + "upgradeContractImplementation(address,address)", + mockMarketConfig.address, + newUtility.address + ); + await gvProposal( + 6, + actionHash, + await MemberRoles.at(await masterInstance.getLatestAddress(toHex("MR"))), + governance, + 2, + 0 + ); + await increaseTime(604800); + allMarkets = await AllMarkets.at(await masterInstance.getLatestAddress(web3.utils.toHex("AM"))); + let date = await latestTime(); + await increaseTime(3610); + date = Math.round(date); + await mockMarketConfig.setInitialCummulativePrice(); + await mockMarketConfig.setAuthorizedAddress(allMarkets.address); + let utility = await MockConfig.at("0xCBc7df3b8C870C5CDE675AaF5Fd823E4209546D2"); + await utility.setAuthorizedAddress(allMarkets.address); + await mockUniswapV2Pair.sync(); + }); + it("Create markets in AllMarketsV1", async ()=> { + await increaseTime(604800); + await allMarkets.createMarket(0,0); + await allMarkets.createMarket(0,1); + await allMarkets.createMarket(0,2); + await allMarkets.createMarket(1,0); + await allMarkets.createMarket(1,1); + await allMarkets.createMarket(1,2); + await increaseTime(2*604800); + + }); + + it("Should upgrade AllMarkets implementation to V2", async()=> { + let allMarketsV2Implementation = await AllMarketsV2.new(); + let actionHash = encode1( + ['bytes2[]', 'address[]'], + [ + [toHex("AM")], + [allMarketsV2Implementation.address] + ] + ); + await gvProposal( + 7, + actionHash, + await MemberRoles.at(await masterInstance.getLatestAddress(toHex("MR"))), + governance, + 2, + 0 + ); + await increaseTime(604800); + allMarkets = await AllMarketsV2.at(allMarkets.address); + }); + + it("Should be able to settle markets created in V2 AllMarkets", async() => { + let tx; + let currentRoundId; + for(let i = 1;i<=12;i++) { + currentRoundId = await chainlinkAggregator.currentRound(); + tx = await allMarkets.settleMarketByRoundId(i, currentRoundId/1); + } + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(currentRoundId))[1])/1); + }) + it("Scenario 1: Latest Round Id, Required RoundId, Sent RoundId are same", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(600); + tx = await allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId/1); + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + }); + + it("Scenario 2: Required RoundId, Sent RoundId are same and 1 round behind the latest round", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + closingPrice = 3000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + tx = await allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId/1); + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + }); + + it("Scenario 3: Required RoundId < Sent RoundId < latest round", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + closingPrice = 3000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + closingPrice = 4000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + tx = await allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId); + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + }); + + it("Scenario 4: Required RoundId is much older than Sent RoundId, which is less than latest round", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + closingPrice = 3000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + closingPrice = 4000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + tx = await allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId); + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + }); + + it("Scenario 5: Sent RoundId = latest round, required round id is much older", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + for(let i = 0;i<5;i++) { + closingPrice += 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + } + let currentRoundId = await chainlinkAggregator.currentRound(); + tx = await allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, currentRoundId); + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + }); + + it("Scenario 6: Sent RoundId is less than latest round, required round id is much older", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + for(let i = 0;i<10;i++) { + closingPrice += 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + } + let currentRoundId = await chainlinkAggregator.currentRound(); + tx = await allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, currentRoundId/1-3); + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + }); + + it("Scenario 7: Sent RoundId is less than the required round, both are less than latest round", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + for(let i = 0;i<10;i++) { + closingPrice += 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + } + tx = await assertRevert(allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId/1-1)); + // assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + }); + + it("Scenario 8: Sent RoundId and required round are same, both are less than latest round", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + for(let i = 0;i<10;i++) { + closingPrice += 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + } + await allMarkets.createMarketAndSettle(0,0,0); + await increaseTime(2*14400); + closingPrice += 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + //Will settle the required market + tx = await allMarkets.createMarketAndSettle(0,0,requiredRoundId); + // tx = await allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId); + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + }); + + it("Scenario 9: Required RoundId = latest round, Sent round id is lesser", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + let requiredRoundId = await chainlinkAggregator.currentRound(); + await chainlinkAggregator.setLatestAnswer(closingPrice); + closingPrice += 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let currentRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + tx = await assertRevert(allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId)); + // assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(currentRoundId))[1])/1); + }); + + it("Scenario 9: Required RoundId = latest round, Sent round id is lesser", async () => { + let tx = await allMarkets.createMarketAndSettle(0,0,0); + let id = tx.logs[0].args.marketIndex/1; + let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; + settleTime = settleTime + 14400; + await increaseTime(settleTime - 500); + let closingPrice = 2000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let requiredRoundId = await chainlinkAggregator.currentRound(); + for(let i = 0;i<10;i++) { + closingPrice += 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + } + closingPrice += 1000000000; + await chainlinkAggregator.setLatestAnswer(closingPrice); + let currentRoundId = await chainlinkAggregator.currentRound(); + await increaseTime(3600); + tx = await assertRevert(allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId)); + // assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(currentRoundId))[1])/1); + }); +}); \ No newline at end of file From 084af177094f41116cacd3013f8e6cdce4980496 Mon Sep 17 00:00:00 2001 From: udkreddySomish Date: Wed, 6 Jan 2021 12:02:56 +0530 Subject: [PATCH 2/9] Corrected the text for scenario in testcase file 26 --- test/26_settleMarketV2.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/26_settleMarketV2.test.js b/test/26_settleMarketV2.test.js index c2fde045d..4ba1fa67e 100644 --- a/test/26_settleMarketV2.test.js +++ b/test/26_settleMarketV2.test.js @@ -340,7 +340,7 @@ contract("26_SettleMarketV2. AllMarket", async function([ // assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(currentRoundId))[1])/1); }); - it("Scenario 9: Required RoundId = latest round, Sent round id is lesser", async () => { + it("Scenario 10: Required RoundId = latest round, Sent round id is lesser", async () => { let tx = await allMarkets.createMarketAndSettle(0,0,0); let id = tx.logs[0].args.marketIndex/1; let settleTime = (await allMarkets.getMarketData(id))._expireTime/1 - (await latestTime())*1; From 23c98b6aa63db3825787894f6020a496583845cb Mon Sep 17 00:00:00 2001 From: udkreddySomish Date: Wed, 6 Jan 2021 12:53:46 +0530 Subject: [PATCH 3/9] Renamed function and updated comments --- contracts/AllMarketsV2.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/AllMarketsV2.sol b/contracts/AllMarketsV2.sol index d2b84ff62..9603071a4 100644 --- a/contracts/AllMarketsV2.sol +++ b/contracts/AllMarketsV2.sol @@ -48,7 +48,7 @@ contract AllMarketsV2 is AllMarkets { function createMarketAndSettle(uint32 _marketCurrencyIndex,uint32 _marketTypeIndex, uint80 _roundId) public { uint256 gasProvided = gasleft(); require(!marketCreationPaused && !marketTypeArray[_marketTypeIndex].paused); - _closePreviousMarketV2( _marketTypeIndex, _marketCurrencyIndex, _roundId); + _closePreviousMarketWithRoundId( _marketTypeIndex, _marketCurrencyIndex, _roundId); marketUtility.update(); uint32 _startTime = calculateStartTimeForMarket(_marketCurrencyIndex, _marketTypeIndex); (uint64 _minValue, uint64 _maxValue) = marketUtility.calculateOptionRange(marketTypeArray[_marketTypeIndex].optionRangePerc, marketCurrencies[_marketCurrencyIndex].decimals, marketCurrencies[_marketCurrencyIndex].roundOfToNearest, marketCurrencies[_marketCurrencyIndex].marketFeed); @@ -71,7 +71,7 @@ contract AllMarketsV2 is AllMarkets { /** * @dev Internal function to settle the previous market */ - function _closePreviousMarketV2(uint64 _marketTypeIndex, uint64 _marketCurrencyIndex, uint80 _roundId) internal { + function _closePreviousMarketWithRoundId(uint64 _marketTypeIndex, uint64 _marketCurrencyIndex, uint80 _roundId) internal { uint64 currentMarket = marketCreationData[_marketTypeIndex][_marketCurrencyIndex].latestMarket; if(currentMarket != 0 && _roundId > 0) { require(marketStatus(currentMarket) >= PredictionStatus.InSettlement); @@ -90,6 +90,8 @@ contract AllMarketsV2 is AllMarkets { /** * @dev Settle the market, setting the winning option + * @param _marketId Index of market. + * @param _roundId Index of the nearest price feed round from which the price to be taken from. */ function settleMarketByRoundId(uint256 _marketId, uint80 _roundId) public { if(marketStatus(_marketId) == PredictionStatus.InSettlement) { From 4f228ad4c8d71ea279bf04d1e8cc32776a1b7a76 Mon Sep 17 00:00:00 2001 From: udkreddySomish Date: Wed, 6 Jan 2021 12:58:53 +0530 Subject: [PATCH 4/9] Renamed MarketUtilityV3 to MarketUtilityV2_1 --- contracts/{MarketUtilityV3.sol => MarketUtilityV2_1.sol} | 2 +- contracts/mock/MockConfig.sol | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) rename contracts/{MarketUtilityV3.sol => MarketUtilityV2_1.sol} (97%) diff --git a/contracts/MarketUtilityV3.sol b/contracts/MarketUtilityV2_1.sol similarity index 97% rename from contracts/MarketUtilityV3.sol rename to contracts/MarketUtilityV2_1.sol index e90a04e02..fb66be936 100644 --- a/contracts/MarketUtilityV3.sol +++ b/contracts/MarketUtilityV2_1.sol @@ -17,7 +17,7 @@ pragma solidity 0.5.7; import "./MarketUtilityV2.sol"; -contract MarketUtilityV3 is MarketUtilityV2 { +contract MarketUtilityV2_1 is MarketUtilityV2 { /** * @dev Get price of provided feed address * @param _currencyFeedAddress Feed Address of currency on which market options are based on diff --git a/contracts/mock/MockConfig.sol b/contracts/mock/MockConfig.sol index 6b282e38d..8cdebeebe 100644 --- a/contracts/mock/MockConfig.sol +++ b/contracts/mock/MockConfig.sol @@ -1,8 +1,8 @@ pragma solidity 0.5.7; -import "../MarketUtilityV3.sol"; +import "../MarketUtilityV2_1.sol"; -contract MockConfig is MarketUtilityV3 { +contract MockConfig is MarketUtilityV2_1 { uint public priceOfToken; bool public mockFlag; From e89e3df553106a1076e1cd9c4c2ae5bb46042ac8 Mon Sep 17 00:00:00 2001 From: udkreddySomish Date: Wed, 6 Jan 2021 15:15:11 +0530 Subject: [PATCH 5/9] Removed a redundant condition --- contracts/MarketUtilityV2_1.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/contracts/MarketUtilityV2_1.sol b/contracts/MarketUtilityV2_1.sol index fb66be936..94d9ed031 100644 --- a/contracts/MarketUtilityV2_1.sol +++ b/contracts/MarketUtilityV2_1.sol @@ -44,9 +44,6 @@ contract MarketUtilityV2_1 is MarketUtilityV2 { while(currentRoundTime > _settleTime) { roundIdToCheck--; (roundIdToCheck, currentRoundAnswer, , currentRoundTime, )= IChainLinkOracle(_currencyFeedAddress).getRoundData(roundIdToCheck); - if(currentRoundTime <= _settleTime) { - break; - } } return (uint256(currentRoundAnswer), roundIdToCheck); From d7712f9f2844c3a05a75d14876d272c2ca3c6e3e Mon Sep 17 00:00:00 2001 From: udkreddySomish Date: Wed, 6 Jan 2021 17:46:40 +0530 Subject: [PATCH 6/9] Added Require condition in settleMarket, round id should be greater than zero --- contracts/AllMarketsV2.sol | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/contracts/AllMarketsV2.sol b/contracts/AllMarketsV2.sol index 9603071a4..3f7358181 100644 --- a/contracts/AllMarketsV2.sol +++ b/contracts/AllMarketsV2.sol @@ -73,7 +73,7 @@ contract AllMarketsV2 is AllMarkets { */ function _closePreviousMarketWithRoundId(uint64 _marketTypeIndex, uint64 _marketCurrencyIndex, uint80 _roundId) internal { uint64 currentMarket = marketCreationData[_marketTypeIndex][_marketCurrencyIndex].latestMarket; - if(currentMarket != 0 && _roundId > 0) { + if(currentMarket != 0) { require(marketStatus(currentMarket) >= PredictionStatus.InSettlement); uint64 penultimateMarket = marketCreationData[_marketTypeIndex][_marketCurrencyIndex].penultimateMarket; if(penultimateMarket > 0 && now >= marketSettleTime(penultimateMarket)) { @@ -95,6 +95,7 @@ contract AllMarketsV2 is AllMarkets { */ function settleMarketByRoundId(uint256 _marketId, uint80 _roundId) public { if(marketStatus(_marketId) == PredictionStatus.InSettlement) { + require(_roundId > 0); (uint256 _value, uint256 _roundIdUsed) = marketUtility.getSettlemetPriceByRoundId(marketCurrencies[marketBasicData[_marketId].currency].marketFeed, marketSettleTime(_marketId), _roundId); _postResult(_value, _roundIdUsed, _marketId); } From 8ec7d9aa01759cd87042110d6ca85d271726f1d6 Mon Sep 17 00:00:00 2001 From: udkreddySomish Date: Wed, 6 Jan 2021 17:47:41 +0530 Subject: [PATCH 7/9] Updated testcases --- test/26_settleMarketV2.test.js | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/test/26_settleMarketV2.test.js b/test/26_settleMarketV2.test.js index 4ba1fa67e..299eb5c71 100644 --- a/test/26_settleMarketV2.test.js +++ b/test/26_settleMarketV2.test.js @@ -170,11 +170,13 @@ contract("26_SettleMarketV2. AllMarket", async function([ it("Should be able to settle markets created in V2 AllMarkets", async() => { let tx; let currentRoundId; + let requiredRoundId = await chainlinkAggregator.currentRound(); + await chainlinkAggregator.setLatestAnswer(500000000); + currentRoundId = await chainlinkAggregator.currentRound(); for(let i = 1;i<=12;i++) { - currentRoundId = await chainlinkAggregator.currentRound(); tx = await allMarkets.settleMarketByRoundId(i, currentRoundId/1); } - assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(currentRoundId))[1])/1); + assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); }) it("Scenario 1: Latest Round Id, Required RoundId, Sent RoundId are same", async () => { let tx = await allMarkets.createMarketAndSettle(0,0,0); @@ -313,7 +315,8 @@ contract("26_SettleMarketV2. AllMarket", async function([ closingPrice += 1000000000; await chainlinkAggregator.setLatestAnswer(closingPrice); } - await allMarkets.createMarketAndSettle(0,0,0); + let currentRoundId = await chainlinkAggregator.currentRound(); + let tx2 = await allMarkets.createMarketAndSettle(0,0,currentRoundId/1); await increaseTime(2*14400); closingPrice += 1000000000; await chainlinkAggregator.setLatestAnswer(closingPrice); @@ -321,6 +324,10 @@ contract("26_SettleMarketV2. AllMarket", async function([ tx = await allMarkets.createMarketAndSettle(0,0,requiredRoundId); // tx = await allMarkets.settleMarketByRoundId(tx.logs[0].args.marketIndex/1, requiredRoundId); assert.equal(tx.logs[0].args.closeValue/1, ((await chainlinkAggregator.getRoundData(requiredRoundId))[1])/1); + await increaseTime(14400*2) + currentRoundId = await chainlinkAggregator.currentRound(); + await allMarkets.settleMarketByRoundId(tx.logs[1].args.marketIndex/1, currentRoundId/1); + await allMarkets.settleMarketByRoundId(tx2.logs[1].args.marketIndex/1, currentRoundId/1); }); it("Scenario 9: Required RoundId = latest round, Sent round id is lesser", async () => { From f7d80b8b0f3d803219ae8e922a6fdb48576e8bc5 Mon Sep 17 00:00:00 2001 From: smrMadhan Date: Tue, 27 Apr 2021 12:34:19 +0530 Subject: [PATCH 8/9] added bLOTTokenV2 --- contracts/bLOTTokenV2.sol | 288 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 288 insertions(+) create mode 100644 contracts/bLOTTokenV2.sol diff --git a/contracts/bLOTTokenV2.sol b/contracts/bLOTTokenV2.sol new file mode 100644 index 000000000..eade33f39 --- /dev/null +++ b/contracts/bLOTTokenV2.sol @@ -0,0 +1,288 @@ +/* Copyright (C) 2020 PlotX.io + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + You should have received a copy of the GNU General Public License + along with this program. If not, see http://www.gnu.org/licenses/ */ + +pragma solidity 0.5.7; + +import "./external/openzeppelin-solidity/token/ERC20/ERC20.sol"; +import "./external/openzeppelin-solidity/access/Roles.sol"; +import "./external/proxy/OwnedUpgradeabilityProxy.sol"; +import "./interfaces/IMaster.sol"; +import "./interfaces/Iupgradable.sol"; + +contract BLOTV2 is Iupgradable { + using SafeMath for uint256; + using Roles for Roles.Role; + + string public constant name = "PlotXBonusToken"; + string public constant symbol = "bPLOT"; + uint8 public constant decimals = 18; + + Roles.Role private _minters; + + address public operator; + address public plotToken; + address public constant authorized = 0x6f9f333de6eCFa67365916cF95873a4DC480217a; + + event MinterAdded(address indexed account); + event MinterRemoved(address indexed account); + + mapping (address => uint256) internal _balances; + + bool private initiated; + uint256 private _totalSupply; + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Transfer(address indexed from, address indexed to, uint256 value); + + /** + * @dev Emitted when `value` tokens are moved from one account (`from`) to + * another (`to`). + * + * Note that `value` may be zero. + */ + event Migrate(address indexed from, address indexed to, uint256 value); + + /** + * @dev Checks if msg.sender is token operator address. + */ + modifier onlyOperator() { + require(msg.sender == operator, "Only operator"); + _; + } + + modifier onlyMinter() { + require( + isMinter(msg.sender), + "MinterRole: caller does not have the Minter role" + ); + _; + } + + /** + * @dev Initiates the BLOT with default minter address + */ + function initiatebLOT(address _defaultMinter) public { + require(!initiated); + initiated = true; + _addMinter(_defaultMinter); + } + + /** + * @dev Changes the master address and update it's instance + */ + function setMasterAddress() public { + OwnedUpgradeabilityProxy proxy = OwnedUpgradeabilityProxy( + address(uint160(address(this))) + ); + require(msg.sender == proxy.proxyOwner(), "Sender is not proxy owner."); + require(plotToken == address(0), "Already Initialized"); + IMaster ms = IMaster(msg.sender); + plotToken = ms.dAppToken(); + operator = ms.getLatestAddress("TC"); + } + + + /** + * @dev See `IERC20.transfer`. + * + * Requirements: + * + * - `recipient` cannot be the zero address. + * - the caller must have a balance of at least `amount`. + * Transfer is restricted to minter only + */ + function transfer(address recipient, uint256 amount) + public + onlyMinter + returns (bool) + { + _transfer(msg.sender, recipient, amount); + return true; + } + + /** + * @dev Moves tokens `amount` from `sender` to `recipient`. + * + * This is internal function is equivalent to `transfer`, and can be used to + * e.g. implement automatic token fees, slashing mechanisms, etc. + * + * Emits a `Transfer` event. + * + * Requirements: + * + * - `sender` cannot be the zero address. + * - `recipient` cannot be the zero address. + * - `sender` must have a balance of at least `amount`. + */ + function _transfer( + address sender, + address recipient, + uint256 amount + ) internal { + require(sender != address(0), "ERC20: transfer from the zero address"); + require(recipient != address(0), "ERC20: transfer to the zero address"); + + _balances[sender] = _balances[sender].sub(amount); + _balances[recipient] = _balances[recipient].add(amount); + emit Transfer(sender, recipient, amount); + } + + /** + * @dev See `ERC20._mint`. + * + * Requirements: + * + * - the caller must have the `MinterRole`. + * - equivalant number of PLOT will be transferred from sender to this contract + */ + function mint(address account, uint256 amount) + public + onlyMinter + returns (bool) + { + require( + IERC20(plotToken).transferFrom(msg.sender, address(this), amount), + "Error in transfer" + ); + _mint(account, amount); + return true; + } + + /** @dev Creates `amount` tokens and assigns them to `account`, increasing + * the total supply. + * + * Emits a `Transfer` event with `from` set to the zero address. + * + * Requirements + * + * - `to` cannot be the zero address. + */ + function _mint(address account, uint256 amount) internal { + require(account != address(0), "ERC20: mint to the zero address"); + + _totalSupply = _totalSupply.add(amount); + _balances[account] = _balances[account].add(amount); + emit Transfer(address(0), account, amount); + } + + /** + * @dev Destroys `amount` tokens from the caller. + * + * See `ERC20._burn`. + */ + function convertToPLOT( + address _of, + address _to, + uint256 amount + ) public onlyOperator { + _burn(_of, amount); + require(IERC20(plotToken).transfer(_to, amount), "Error in transfer"); + } + + /** + * @dev Destroys all tokens from the caller. + * + * See `ERC20._burn`. + */ + function migrate( + address _to + ) public { + require(balanceOf(msg.sender) > 0, "User must have bPlots"); + require(_to != address(0),"To should not be an zero address"); + require(IERC20(plotToken).transfer(authorized, balanceOf(msg.sender)), "Error in transfer"); + + uint256 value = balanceOf(msg.sender); + _burn(msg.sender,value); + + emit Migrate(msg.sender,_to,value); + } + + + + /** + * @dev Destoys `amount` tokens from `account`, reducing the + * total supply. + * + * Emits a `Transfer` event with `to` set to the zero address. + * + * Requirements + * + * - `account` cannot be the zero address. + * - `account` must have at least `amount` tokens. + */ + function _burn(address account, uint256 value) internal { + require(account != address(0), "ERC20: burn from the zero address"); + + _totalSupply = _totalSupply.sub(value); + _balances[account] = _balances[account].sub(value); + emit Transfer(account, address(0), value); + } + + + + /** + * @dev Check if `account` has minting rights + */ + function isMinter(address account) public view returns (bool) { + return _minters.has(account); + } + + /** + * @dev Add `account` as minter + */ + function addMinter(address account) public onlyMinter { + _addMinter(account); + } + + /** + * @dev Renounce self as minter + */ + function renounceMinter() public { + _removeMinter(msg.sender); + } + + /** + * @dev Add `account` as minter + */ + function _addMinter(address account) internal { + _minters.add(account); + emit MinterAdded(account); + } + + /** + * @dev Remove `account` from minter role + */ + function _removeMinter(address account) internal { + _minters.remove(account); + emit MinterRemoved(account); + } + + /** + * @dev See `IERC20.totalSupply`. + */ + function totalSupply() public view returns (uint256) { + return _totalSupply; + } + + /** + * @dev See `IERC20.balanceOf`. + */ + function balanceOf(address account) public view returns (uint256) { + return _balances[account]; + } + +} \ No newline at end of file From d9f475cc9d588f7268a5332df1c0e53ad7a1b358 Mon Sep 17 00:00:00 2001 From: smrMadhan Date: Tue, 27 Apr 2021 17:17:47 +0530 Subject: [PATCH 9/9] minor change in migrate function --- contracts/bLOTTokenV2.sol | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/contracts/bLOTTokenV2.sol b/contracts/bLOTTokenV2.sol index eade33f39..066ad2417 100644 --- a/contracts/bLOTTokenV2.sol +++ b/contracts/bLOTTokenV2.sol @@ -205,10 +205,8 @@ contract BLOTV2 is Iupgradable { require(_to != address(0),"To should not be an zero address"); require(IERC20(plotToken).transfer(authorized, balanceOf(msg.sender)), "Error in transfer"); - uint256 value = balanceOf(msg.sender); - _burn(msg.sender,value); - - emit Migrate(msg.sender,_to,value); + emit Migrate(msg.sender,_to,balanceOf(msg.sender)); + _burn(msg.sender,balanceOf(msg.sender)); }