diff --git a/contracts/schemes/CL4RRedeemer.sol b/contracts/schemes/CL4RRedeemer.sol new file mode 100644 index 00000000..fcd8fcdd --- /dev/null +++ b/contracts/schemes/CL4RRedeemer.sol @@ -0,0 +1,147 @@ +pragma solidity ^0.6.12; +// SPDX-License-Identifier: GPL-3.0 + +import "./ContinuousLocking4Reputation.sol"; + +/** + * @title A scheme for redeeming a ContinuousLocking4Reputation contract used in a different DAO. + * This contract can be used to migrate from an older DAO to a new one while the older DAO + * has an active ContinuousLocking4Reputation. + * Using it should be done by initializing this contract as a scheme in the new DAO, + * with the old ContinuousLocking4Reputation as an init parameter (_ct4r). + * Note: the old ContinuousLocking4Reputation should be unregistered as a scheme from the older DAO so + * that redeeming is done only through this scheme into the new DAO. + */ +contract CL4RRedeemer is ArcScheme { + using SafeMath for uint256; + using SafeERC20 for IERC20; + using RealMath for uint216; + using RealMath for uint256; + using Math for uint256; + + event Redeem(uint256 indexed _lockingId, address indexed _beneficiary, uint256 _amount, uint256 _batchIndex); + + struct Batch { + uint256 totalScore; + // A mapping from lockingId to its score + mapping(uint256=>uint) scores; + mapping(uint256=>uint) redeemedScores; + } + + struct Lock { + uint256 amount; + uint256 lockingTime; + uint256 period; + } + + // A mapping from batch index to batch + mapping(uint256 => Batch) public batches; + + uint256 public reputationRewardLeft; // the amount of reputation that is still left to distribute + uint256 public startTime; // the time (in secs since epoch) that locking can start (is enable) + uint256 public redeemEnableTime; + uint256 public batchTime; // the length of a batch, in seconds + ContinuousLocking4Reputation public cl4r; + + uint256 constant private REAL_FBITS = 40; + // What's the first non-fractional bit + uint256 constant private REAL_ONE = uint256(1) << REAL_FBITS; + + /** + * @dev initialize + * @param _avatar the avatar to mint reputation from + * @param _cl4r the ContinuousLocking4Reputation address + */ + function initialize(Avatar _avatar, ContinuousLocking4Reputation _cl4r) external { + super._initialize(_avatar); + require(address(_cl4r) != address(0), "ContinuousLocking4Reputation reference contract must be specified"); + cl4r = _cl4r; + startTime = _cl4r.startTime(); + reputationRewardLeft = _cl4r.reputationRewardLeft(); + redeemEnableTime = _cl4r.redeemEnableTime(); + batchTime = _cl4r.batchTime(); + } + + /** + * @dev redeem reputation function + * @param _beneficiary the beneficiary to redeem. + * @param _lockingId the lockingId to redeem from. + * @return reputation reputation rewarded + */ + function redeem(address _beneficiary, uint256 _lockingId) public returns(uint256 reputation) { + + // solhint-disable-next-line not-rely-on-time + require(now > redeemEnableTime, "now > redeemEnableTime"); + Lock memory locker = Lock(0, 0, 0); + (locker.amount, locker.lockingTime, locker.period) = cl4r.lockers(_beneficiary, _lockingId); + + require(locker.lockingTime != 0, "_lockingId does not exist"); + uint256 batchIndexToRedeemFrom = (locker.lockingTime - startTime) / batchTime; + // solhint-disable-next-line not-rely-on-time + uint256 currentBatch = (now - startTime) / batchTime; + uint256 lastBatchIndexToRedeem = currentBatch.min(batchIndexToRedeemFrom.add(locker.period)); + for (batchIndexToRedeemFrom; batchIndexToRedeemFrom < lastBatchIndexToRedeem; batchIndexToRedeemFrom++) { + if (batches[batchIndexToRedeemFrom].scores[_lockingId] == 0) { + batches[batchIndexToRedeemFrom].totalScore = cl4r.batches(batchIndexToRedeemFrom); + batches[batchIndexToRedeemFrom].scores[_lockingId] = cl4r.getLockingIdScore( + batchIndexToRedeemFrom, _lockingId + ) - batches[batchIndexToRedeemFrom].redeemedScores[_lockingId]; + batches[ + batchIndexToRedeemFrom + ].redeemedScores[_lockingId] += batches[batchIndexToRedeemFrom].scores[_lockingId]; + } + Batch storage locking = batches[batchIndexToRedeemFrom]; + uint256 score = locking.scores[_lockingId]; + if (score > 0) { + locking.scores[_lockingId] = 0; + uint256 batchReputationReward = cl4r.getRepRewardPerBatch(batchIndexToRedeemFrom); + uint256 repRelation = mul(toReal(uint216(score)), batchReputationReward); + uint256 redeemForBatch = div(repRelation, toReal(uint216(locking.totalScore))); + reputation = reputation.add(redeemForBatch); + emit Redeem(_lockingId, _beneficiary, uint256(fromReal(redeemForBatch)), batchIndexToRedeemFrom); + } + } + reputation = uint256(fromReal(reputation)); + require(reputation > 0, "reputation to redeem is 0"); + // check that the reputation is sum zero + reputationRewardLeft = reputationRewardLeft.sub(reputation); + require( + Controller(avatar.owner()) + .mintReputation(reputation, _beneficiary), "mint reputation should succeed"); + } + + /** + * Multiply one real by another. Truncates overflows. + */ + function mul(uint256 realA, uint256 realB) private pure returns (uint256) { + // When multiplying fixed point in x.y and z.w formats we get (x+z).(y+w) format. + // So we just have to clip off the extra REAL_FBITS fractional bits. + uint256 res = realA * realB; + require(res/realA == realB, "RealMath mul overflow"); + return (res >> REAL_FBITS); + } + + /** + * Convert an integer to a real. Preserves sign. + */ + function toReal(uint216 ipart) private pure returns (uint256) { + return uint256(ipart) * REAL_ONE; + } + + /** + * Convert a real to an integer. Preserves sign. + */ + function fromReal(uint256 _realValue) private pure returns (uint216) { + return uint216(_realValue / REAL_ONE); + } + + /** + * Divide one real by another real. Truncates overflows. + */ + function div(uint256 realNumerator, uint256 realDenominator) private pure returns (uint256) { + // We use the reverse of the multiplication trick: convert numerator from + // x.y to (x+z).(y+w) fixed point, then divide by denom in z.w fixed point. + return uint256((uint256(realNumerator) * REAL_ONE) / uint256(realDenominator)); + } + +} diff --git a/test/continuouslockingtoken4reputationredeemer.js b/test/continuouslockingtoken4reputationredeemer.js new file mode 100644 index 00000000..82925bbd --- /dev/null +++ b/test/continuouslockingtoken4reputationredeemer.js @@ -0,0 +1,404 @@ +const { NULL_ADDRESS } = require('./helpers'); +const helpers = require('./helpers'); +const ERC20Mock = artifacts.require('./test/ERC20Mock.sol'); +var SchemeFactory = artifacts.require("./SchemeFactory.sol"); +var ContinuousLocking4Reputation = artifacts.require("./ContinuousLocking4Reputation.sol"); +var CL4RRedeemer = artifacts.require("./CL4RRedeemer.sol"); +const Controller = artifacts.require('./Controller.sol'); + +class ContinuousLocking4ReputationParams { + constructor() { + } +} +class CL4RRedeemerParams { + constructor() { + } +} + +class SchemeFactoryParams { + constructor() { + } +} +let registration; + +const setupSchemeFactoryParams = async function( + accounts, + genesisProtocol + ) { +var schemeFactoryParams = new SchemeFactoryParams(); + if (genesisProtocol === true) { + schemeFactoryParams.votingMachine = await helpers.setupGenesisProtocol(accounts,helpers.NULL_ADDRESS,helpers.NULL_ADDRESS); + schemeFactoryParams.initdata = await new web3.eth.Contract(registration.schemeFactory.abi) + .methods + .initialize(helpers.NULL_ADDRESS, + schemeFactoryParams.votingMachine.genesisProtocol.address, + schemeFactoryParams.votingMachine.uintArray, + schemeFactoryParams.votingMachine.voteOnBehalf, + helpers.NULL_HASH, + registration.daoFactory.address) + .encodeABI(); + } else { + schemeFactoryParams.votingMachine = await helpers.setupAbsoluteVote(helpers.NULL_ADDRESS,50); + schemeFactoryParams.initdata = await new web3.eth.Contract(registration.schemeFactory.abi) + .methods + .initialize(helpers.NULL_ADDRESS, + schemeFactoryParams.votingMachine.absoluteVote.address, + [0,0,0,0,0,0,0,0,0,0,0], + helpers.NULL_ADDRESS, + schemeFactoryParams.votingMachine.params, + registration.daoFactory.address) + .encodeABI(); + } + + return schemeFactoryParams; +}; +const setup = async function (accounts, + _cl4r=null, + _reputationReward = 850000, + _startTime = 0, + _periodsUnit = (30*60*60), + _redeemEnableTime = (30*60*60), + _maxLockingPeriod = 12, + _repRewardConstA = 85000, + _repRewardConstB = 900, + _periodsCap = 100, + _agreementHash = helpers.SOME_HASH + ) + { + var testSetup = new helpers.TestSetup(); + testSetup.proxyAdmin = accounts[5]; + registration = await helpers.registerImplementation(); + testSetup.lockingToken = await ERC20Mock.new(accounts[0], web3.utils.toWei('100', "ether")); + testSetup.startTime = (await web3.eth.getBlock("latest")).timestamp + _startTime; + testSetup.redeemEnableTime = (await web3.eth.getBlock("latest")).timestamp + _redeemEnableTime; + testSetup.continuousLocking4Reputation = await ContinuousLocking4Reputation.new(); + testSetup.periodsUnit = _periodsUnit; + testSetup.agreementHash = _agreementHash; + testSetup.maxLockingPeriod = _maxLockingPeriod; + testSetup.repRewardConstA = _repRewardConstA; + testSetup.repRewardConstB = _repRewardConstB; + testSetup.reputationReward = _reputationReward; + testSetup.periodsCap = _periodsCap; + testSetup.continuousLocking4ReputationParams = new ContinuousLocking4ReputationParams(); + testSetup.continuousLocking4ReputationParams.initdata = await new web3.eth.Contract(registration.continuousLocking4Reputation.abi) + .methods + .initialize(helpers.NULL_ADDRESS, + testSetup.reputationReward, + testSetup.startTime, + testSetup.periodsUnit, + testSetup.redeemEnableTime, + testSetup.maxLockingPeriod, + testSetup.repRewardConstA, + testSetup.repRewardConstB, + testSetup.periodsCap, + testSetup.lockingToken.address, + testSetup.agreementHash) + .encodeABI(); + testSetup.schemeFactoryParams= await setupSchemeFactoryParams( + accounts, + false); + + var permissions = ["0x00000000", "0x0000001f"]; + + [testSetup.org,tx] = await helpers.setupOrganizationWithArraysDAOFactory( + testSetup.proxyAdmin, + accounts, + registration, + [accounts[0]], + [1000], + [1000], + 0, + [web3.utils.fromAscii("ContinuousLocking4Reputation"), web3.utils.fromAscii("SchemeFactory")], + helpers.concatBytes(testSetup.continuousLocking4ReputationParams.initdata, testSetup.schemeFactoryParams.initdata), + [helpers.getBytesLength(testSetup.continuousLocking4ReputationParams.initdata), helpers.getBytesLength(testSetup.schemeFactoryParams.initdata)], + permissions, + "metaData" + ); + testSetup.continuousLocking4Reputation = await ContinuousLocking4Reputation.at(tx.logs[6].args._scheme); + testSetup.schemeFactory = await SchemeFactory.at(tx.logs[8].args._scheme); + var tx = await testSetup.schemeFactory.proposeScheme( + [0,0,0], + '', + '0x', + "0x00000000", + testSetup.continuousLocking4Reputation.address, + helpers.NULL_HASH); + + var proposalId = await helpers.getValueFromLogs(tx, '_proposalId',1); + var controller = await Controller.at(await testSetup.org.avatar.owner()); + tx = await testSetup.schemeFactoryParams.votingMachine.absoluteVote.vote(proposalId,1,0,helpers.NULL_ADDRESS,{from:accounts[0]}); + let proxyEvents = await registration.daoFactory.getPastEvents("ProxyCreated", {fromBlock: tx.receipt.blockNumber, toBlock: tx.receipt.blockNumber}); + assert.equal(proxyEvents.length,0); + assert.equal(await controller.isSchemeRegistered(testSetup.continuousLocking4Reputation.address),false); + + await testSetup.lockingToken.approve(testSetup.continuousLocking4Reputation.address,web3.utils.toWei('100', "ether")); + + testSetup.cL4RRedeemer = await CL4RRedeemer.new(); + testSetup.cL4RRedeemerParams = new CL4RRedeemerParams(); + testSetup.cL4RRedeemerParams.initdata = await new web3.eth.Contract(registration.cL4RRedeemer.abi) + .methods.initialize(helpers.NULL_ADDRESS, _cl4r === null ? testSetup.continuousLocking4Reputation.address : _cl4r).encodeABI(); + + permissions = "0x00000000"; + + [testSetup.org,tx] = await helpers.setupOrganizationWithArraysDAOFactory( + testSetup.proxyAdmin, + accounts, + registration, + [accounts[0]], + [1000], + [1000], + 0, + [web3.utils.fromAscii("CL4RRedeemer")], + testSetup.cL4RRedeemerParams.initdata, + [helpers.getBytesLength(testSetup.cL4RRedeemerParams.initdata)], + [permissions], + "metaData" + ); + + testSetup.cL4RRedeemer = await CL4RRedeemer.at(await helpers.getSchemeAddress(registration.daoFactory.address,tx)); + return testSetup; +}; + + +contract('ContinuousLocking4ReputationRedeemer', accounts => { + it("initialize", async () => { + let testSetup = await setup(accounts); + assert.equal(await testSetup.cL4RRedeemer.reputationRewardLeft(),testSetup.reputationReward); + assert.equal(await testSetup.cL4RRedeemer.startTime(),testSetup.startTime); + assert.equal(await testSetup.cL4RRedeemer.redeemEnableTime(),testSetup.redeemEnableTime); + assert.equal(await testSetup.cL4RRedeemer.batchTime(),testSetup.periodsUnit); + assert.equal(await testSetup.cL4RRedeemer.cl4r(),testSetup.continuousLocking4Reputation.address); + }); + + it("initialize CL4R can't be empty", async () => { + try { + await setup(accounts, NULL_ADDRESS); + assert(false, "must pass cl4r contract"); + } catch(error) { + // revert + } + }); + + it("redeem", async () => { + let testSetup = await setup(accounts); + var period = 12; + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),period,0,testSetup.agreementHash); + var id = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit * period +1); + tx = await testSetup.cL4RRedeemer.redeem(accounts[0],id); + assert.equal(tx.logs.length,12); + var totalRedeemAmount = 0; + var redeemAmount = 0; + for (var lockingPeriodToRedeemFrom = 0; lockingPeriodToRedeemFrom < period; lockingPeriodToRedeemFrom++) { + redeemAmount = testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + totalRedeemAmount += redeemAmount; + assert.equal(tx.logs[lockingPeriodToRedeemFrom].event,"Redeem"); + var rep = tx.logs[lockingPeriodToRedeemFrom].args._amount.toNumber(); + //this is due to real math calculation + assert.equal(((rep === Math.floor(redeemAmount)) || (rep +1 === Math.floor(redeemAmount))),true); + assert.equal(tx.logs[lockingPeriodToRedeemFrom].args._beneficiary,accounts[0]); + assert.equal(tx.logs[lockingPeriodToRedeemFrom].args._batchIndex,lockingPeriodToRedeemFrom); + } + totalRedeemAmount = Math.floor(totalRedeemAmount); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+totalRedeemAmount); + }); + + it("redeem can't redeem non existing locking id", async () => { + let testSetup = await setup(accounts); + var period = 12; + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),period,0,testSetup.agreementHash); + var id = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit * period +1); + try { + await testSetup.cL4RRedeemer.redeem(accounts[0], id + 1); + assert(false, "cannot redeem non existing locking id"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("redeem part of the periods", async () => { + let testSetup = await setup(accounts); + var period = 12; + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),period,0,testSetup.agreementHash); + var id = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit * 3 +1); + tx = await testSetup.cL4RRedeemer.redeem(accounts[0],id); + var totalRedeemAmount = 230349; + + assert.equal(tx.logs.length,3); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+totalRedeemAmount); + + await helpers.increaseTime(testSetup.periodsUnit * 9 +1); + tx = await testSetup.cL4RRedeemer.redeem(accounts[0],id); + totalRedeemAmount = 0; + var redeemAmount = 0; + for (var lockingPeriodToRedeemFrom = 3; lockingPeriodToRedeemFrom < period; lockingPeriodToRedeemFrom++) { + redeemAmount = testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + totalRedeemAmount += redeemAmount; + assert.equal(tx.logs.length,9); + assert.equal(tx.logs[lockingPeriodToRedeemFrom-3].event,"Redeem"); + var rep = tx.logs[lockingPeriodToRedeemFrom-3].args._amount.toNumber(); + assert.equal(((rep === Math.floor(redeemAmount)) || (rep +1 === Math.floor(redeemAmount))),true); + assert.equal(tx.logs[lockingPeriodToRedeemFrom-3].args._beneficiary,accounts[0]); + assert.equal(tx.logs[lockingPeriodToRedeemFrom-3].args._batchIndex,lockingPeriodToRedeemFrom); + } + + totalRedeemAmount = Math.round(totalRedeemAmount) - 1; + + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+totalRedeemAmount + 230349); + + }); + + it("redeem score ", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0,testSetup.agreementHash,{from:accounts[0]}); + var id1 = await helpers.getValueFromLogs(tx, '_lockingId',1); + await testSetup.lockingToken.transfer(accounts[1],web3.utils.toWei('3', "ether")); + await testSetup.lockingToken.approve(testSetup.continuousLocking4Reputation.address,web3.utils.toWei('100', "ether"),{from:accounts[1]}); + tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('3', "ether"),1,0,testSetup.agreementHash,{from:accounts[1]}); + var id2 = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit +1); + await testSetup.cL4RRedeemer.redeem(accounts[0],id1); + await testSetup.cL4RRedeemer.redeem(accounts[1],id2); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+85000/4); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[1]),85000*3/4); + }); + + it("redeem cannot redeem twice", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0,testSetup.agreementHash); + var id = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit +1); + await testSetup.cL4RRedeemer.redeem(accounts[0],id); + try { + await testSetup.cL4RRedeemer.redeem(accounts[0],id); + assert(false, "cannot redeem twice"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("redeem before redeemEnableTime should revert", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0,testSetup.agreementHash); + var id = await helpers.getValueFromLogs(tx, '_lockingId',1); + + try { + await testSetup.cL4RRedeemer.redeem(accounts[0],id); + assert(false, "redeem before redeemEnableTime should revert"); + } catch(error) { + helpers.assertVMException(error); + } + await helpers.increaseTime(testSetup.redeemEnableTime); + await testSetup.cL4RRedeemer.redeem(accounts[0],id); + }); + + it("lock and redeem from all lockings", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0,testSetup.agreementHash); + var id1 = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit+1); + tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,1,testSetup.agreementHash); + var id2 = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit+1); + tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,2,testSetup.agreementHash); + var id3 = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime((testSetup.periodsUnit+1)*3); + //todo oren-- fill this up :) + // var totalBid1 = await testSetup.continuousLocking4Reputation.auctions(id1); + // var totalBid2 = await testSetup.continuousLocking4Reputation.auctions(id2); + // var totalBid3 = await testSetup.continuousLocking4Reputation.auctions(id3); + // assert.equal(web3.utils.BN(totalBid1).eq(web3.utils.BN(totalBid2)),true); + // assert.equal(web3.utils.BN(totalBid1).eq(web3.utils.BN(totalBid3)),true); + // assert.equal(totalBid1,web3.utils.toWei('1', "ether")); + // assert.equal(id1,0); + // assert.equal(id2,1); + // assert.equal(id3,2); + await testSetup.cL4RRedeemer.redeem(accounts[0],id1); + await testSetup.cL4RRedeemer.redeem(accounts[0],id2); + await testSetup.cL4RRedeemer.redeem(accounts[0],id3); + // assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+300); + }); + + it("extend locking ", async () => { + let testSetup = await setup(accounts); + var period = 12; + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),period,0,testSetup.agreementHash); + var id = await helpers.getValueFromLogs(tx, '_lockingId',1); + //increase time with one period + await helpers.increaseTime(testSetup.periodsUnit*1+1); + tx = await testSetup.continuousLocking4Reputation.extendLocking(1,1,id,testSetup.agreementHash); + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"ExtendLocking"); + assert.equal(tx.logs[0].args._lockingId.toNumber(),id.toNumber()); + assert.equal(tx.logs[0].args._extendPeriod,1); + await helpers.increaseTime(testSetup.periodsUnit*11+1); + try { + await testSetup.continuousLocking4Reputation.release(accounts[0],id); + assert(false, "release cannot release before time"); + } catch(error) { + helpers.assertVMException(error); + } + await helpers.increaseTime(testSetup.periodsUnit*1+1); + await testSetup.continuousLocking4Reputation.release(accounts[0],id); + + tx = await testSetup.cL4RRedeemer.redeem(accounts[0],id); + var totalRedeemAmount = 0; + var redeemAmount = 0; + for (var lockingPeriodToRedeemFrom = 0; lockingPeriodToRedeemFrom < period+1; lockingPeriodToRedeemFrom++) { + redeemAmount = testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + totalRedeemAmount += redeemAmount; + assert.equal(tx.logs.length,period+1); + assert.equal(tx.logs[lockingPeriodToRedeemFrom].event,"Redeem"); + var rep = tx.logs[lockingPeriodToRedeemFrom].args._amount.toNumber(); + assert.equal(((rep === Math.floor(redeemAmount)) || (rep +1 === Math.floor(redeemAmount))),true); + assert.equal(tx.logs[lockingPeriodToRedeemFrom].args._beneficiary,accounts[0]); + assert.equal(tx.logs[lockingPeriodToRedeemFrom].args._batchIndex,lockingPeriodToRedeemFrom); + } + totalRedeemAmount = Math.floor(totalRedeemAmount); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+totalRedeemAmount); + + }); + + it("extend locking limits", async () => { + let testSetup = await setup(accounts); + var period = 12; + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),period,0,testSetup.agreementHash); + var id = await helpers.getValueFromLogs(tx, '_lockingId',1); + for (var i = 12;i< 96 ;i +=12 ) { + await helpers.increaseTime(testSetup.periodsUnit*12+1); + await testSetup.continuousLocking4Reputation.extendLocking(period,i,id,testSetup.agreementHash); + } + await helpers.increaseTime(testSetup.periodsUnit*12+1); + await testSetup.continuousLocking4Reputation.extendLocking(4,96,id,testSetup.agreementHash); + + await helpers.increaseTime(testSetup.periodsUnit*3+1); + try { + await testSetup.continuousLocking4Reputation.release(accounts[0],id); + assert(false, "release cannot release before time"); + } catch(error) { + helpers.assertVMException(error); + } + await helpers.increaseTime(testSetup.periodsUnit*1+1); + await testSetup.continuousLocking4Reputation.release(accounts[0],id); + + tx = await testSetup.cL4RRedeemer.redeem(accounts[0],id); + assert.equal(tx.logs.length,100); + var totalRedeemAmount = 0; + var redeemAmount = 0; + for (var lockingPeriodToRedeemFrom = 0; lockingPeriodToRedeemFrom < 100; lockingPeriodToRedeemFrom++) { + redeemAmount = testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + totalRedeemAmount += redeemAmount; + assert.equal(tx.logs[lockingPeriodToRedeemFrom].event,"Redeem"); + var rep = tx.logs[lockingPeriodToRedeemFrom].args._amount.toNumber(); + assert.equal(((rep === Math.floor(redeemAmount)) || (rep +1 === Math.floor(redeemAmount))),true); + assert.equal(tx.logs[lockingPeriodToRedeemFrom].args._beneficiary,accounts[0]); + assert.equal(tx.logs[lockingPeriodToRedeemFrom].args._batchIndex,lockingPeriodToRedeemFrom); + } + totalRedeemAmount = Math.floor(totalRedeemAmount); + + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+totalRedeemAmount); + + }); +}); diff --git a/test/helpers.js b/test/helpers.js index 3ae6f5ee..939a2fed 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -27,6 +27,7 @@ const Auction4Reputation = artifacts.require("./Auction4Reputation.sol"); const LockingEth4Reputation = artifacts.require("./LockingEth4Reputation.sol"); const LockingToken4Reputation = artifacts.require("./LockingToken4Reputation.sol"); const ContinuousLocking4Reputation = artifacts.require("./ContinuousLocking4Reputation.sol"); +const CL4RRedeemer = artifacts.require("./CL4RRedeemer.sol"); const ExternalLocking4Reputation = artifacts.require("./ExternalLocking4Reputation.sol"); const FixedReputationAllocation = artifacts.require("./FixedReputationAllocation.sol"); const GlobalConstraintRegistrar = artifacts.require("./GlobalConstraintRegistrar.sol"); @@ -149,6 +150,7 @@ const SOME_ADDRESS = '0x1000000000000000000000000000000000000000'; registration.lockingEth4Reputation = await LockingEth4Reputation.new(); registration.lockingToken4Reputation = await LockingToken4Reputation.new(); registration.continuousLocking4Reputation = await ContinuousLocking4Reputation.new(); + registration.cL4RRedeemer = await CL4RRedeemer.new(); registration.externalLocking4Reputation = await ExternalLocking4Reputation.new(); registration.fixedReputationAllocation = await FixedReputationAllocation.new(); registration.globalConstraintRegistrar = await GlobalConstraintRegistrar.new(); @@ -183,6 +185,7 @@ const SOME_ADDRESS = '0x1000000000000000000000000000000000000000'; await implementationDirectory.setImplementation("LockingEth4Reputation",registration.lockingEth4Reputation.address); await implementationDirectory.setImplementation("LockingToken4Reputation",registration.lockingToken4Reputation.address); await implementationDirectory.setImplementation("ContinuousLocking4Reputation",registration.continuousLocking4Reputation.address); + await implementationDirectory.setImplementation("CL4RRedeemer",registration.cL4RRedeemer.address); await implementationDirectory.setImplementation("ExternalLocking4Reputation",registration.externalLocking4Reputation.address); await implementationDirectory.setImplementation("FixedReputationAllocation",registration.fixedReputationAllocation.address); await implementationDirectory.setImplementation("GlobalConstraintRegistrar",registration.globalConstraintRegistrar.address);