From 58e68c0165ddfe8bde3d11b17c039d2be0bbe62c Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Tue, 3 Sep 2019 14:10:05 +0300 Subject: [PATCH 01/13] add ContinuousLockingToken4Reputation.sol --- .../ContinuousLockingToken4Reputation.sol | 193 ++++++++++++++++++ 1 file changed, 193 insertions(+) create mode 100644 contracts/schemes/ContinuousLockingToken4Reputation.sol diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol new file mode 100644 index 00000000..bda3174a --- /dev/null +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -0,0 +1,193 @@ +pragma solidity ^0.5.4; + +import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "../controller/ControllerInterface.sol"; +import "../libs/SafeERC20.sol"; +import "./Agreement.sol"; +import { RealMath } from "@daostack/infra/contracts/libs/RealMath.sol"; + +/** + * @title A scheme for conduct ERC20 Tokens auction for reputation + */ + + +contract ContinuousLocking4Reputation is Agreement { + using SafeMath for uint256; + using SafeERC20 for address; + using RealMath for uint216; + using RealMath for uint256; + + event Redeem(bytes32 indexed _lockingId, address indexed _beneficiary, uint256 _amount); + event Release(bytes32 indexed _lockingId, address indexed _beneficiary, uint256 _amount); + event Lock(address indexed _locker, bytes32 indexed _lockingId, uint256 _amount, uint256 _period); + + struct Locking { + uint256 totalScore; + // A mapping from locker addresses to their locking score. + mapping(address=>uint256) scores; + } + + struct Locker { + uint256 amount; + uint256 releaseTime; + uint256 lockingTime; + uint256 period; + } + + // A mapping from lockers addresses their lock balances. + mapping(address => mapping(bytes32=>Locker)) public lockers; + // A mapping from locking index to locking. + mapping(uint256=>Locking) public lockings; + + Avatar public avatar; + uint256 public reputationRewardLeft; + uint256 public lockingStartTime; + uint256 public numberOfLockingPeriods; + uint256 public lockingReputationReward; + uint256 public redeemEnableTime; + uint256 public maxLockingPeriod; + uint256 public lockingPeriodsUnit; + IERC20 public token; + uint256 public lockingsCounter; // Total number of lockings + uint256 public totalLockedLeft; + uint256 repRewardConstA; + uint256 repRewardConstB; + + /** + * @dev initialize + * @param _avatar the avatar to mint reputation from + * @param _lockingReputationReward the reputation reward per auction this contract will reward + * for the token locking + * @param _lockingStartTime auctions period start time + * @param _lockingPeriodsUnit locking periods units (e.g 30 days). + * @param _redeemEnableTime redeem enable time . + * redeem reputation can be done after this time. + * @param _maxLockingPeriod - maximum number of locking periods (in _lockingPeriodsUnit units) + * @param _token the bidding token + * @param _agreementHash is a hash of agreement required to be added to the TX by participants + */ + function initialize( + Avatar _avatar, + uint256 _lockingReputationReward, + uint256 _lockingStartTime, + uint256 _lockingPeriodsUnit, + uint256 _redeemEnableTime, + uint256 _maxLockingPeriod, + IERC20 _token, + uint256 _repRewardConstA, + uint256 _repRewardConstB, + bytes32 _agreementHash ) + external + { + require(avatar == Avatar(0), "can be called only one time"); + require(_avatar != Avatar(0), "avatar cannot be zero"); + //_lockingPeriodsUnit should be greater than block interval + require(_lockingPeriodsUnit > 15, "lockingPeriod should be > 15"); + require(_redeemEnableTime >= _lockingStartTime+_lockingPeriodsUnit, + "_redeemEnableTime >= _lockingStartTime+_lockingPeriodsUnit"); + token = _token; + avatar = _avatar; + lockingStartTime = _lockingStartTime; + lockingReputationReward = _lockingReputationReward; + redeemEnableTime = _redeemEnableTime; + maxLockingPeriod = _maxLockingPeriod; + lockingPeriodsUnit = _lockingPeriodsUnit; + repRewardConstA = _repRewardConstA; + repRewardConstB = uint216(_repRewardConstB).fraction(uint216(1000)); + super.setAgreementHash(_agreementHash); + } + + /** + * @dev redeem reputation function + * @param _beneficiary the beneficiary to redeem. + * @param _lockingId the lockingId to redeem from. + * @return uint256 reputation rewarded + */ + function redeem(address _beneficiary, bytes32 _lockingId) public returns(uint256 reputation) { + // solhint-disable-next-line not-rely-on-time + require(now > redeemEnableTime, "now > redeemEnableTime"); + Locker storage locker = lockers[_beneficiary][_lockingId]; + uint256 lockingPeriodToRedeemFrom = (locker.lockingTime - lockingStartTime) / lockingPeriodsUnit; + for (lockingPeriodToRedeemFrom; lockingPeriodToRedeemFrom < locker.period; lockingPeriodToRedeemFrom) { + Locking storage locking = lockings[lockingPeriodToRedeemFrom]; + uint256 score = locking.scores[_beneficiary]; + require(score > 0, "locking score should be > 0"); + locking.scores[_beneficiary] = 0; + uint256 lockingPeriodReputationReward = repRewardConstA.mul(repRewardConstB.pow(lockingPeriodToRedeemFrom)); + uint256 repRelation = score.mul(lockingPeriodReputationReward); + reputation = reputation.add(repRelation.div(locking.totalScore)); + } + // check that the reputation is sum zero + reputationRewardLeft = reputationRewardLeft.sub(reputation); + require( + ControllerInterface(avatar.owner()) + .mintReputation(reputation, _beneficiary, address(avatar)), "mint reputation should succeed"); + emit Redeem(_lockingId, _beneficiary, reputation); + } + + /** + * @dev lock function + * @param _amount the amount to bid with + * @param _period the period to lock. in lockingPeriodsUnit. + * @param _lockingPeriodToLockIn the locking id to lock at . + * @return lockingId + */ + function lock(uint256 _amount, uint256 _period ,uint256 _lockingPeriodToLockIn, bytes32 _agreementHash) + public + onlyAgree(_agreementHash) + returns(bytes32 lockingId) + { + require(_amount > 0, "bidding amount should be > 0"); + // solhint-disable-next-line not-rely-on-time + require(now >= lockingStartTime, "bidding is enable only after bidding lockingStartTime"); + require(_period <= maxLockingPeriod, "locking period exceed the maximum allowed"); + require(_period > 0, "locking period equal to zero"); + address(token).safeTransferFrom(msg.sender, address(this), _amount); + // solhint-disable-next-line not-rely-on-time + uint256 lockingPeriodToLockIn = (now - lockingStartTime) / lockingPeriodsUnit; + require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); + uint256 i = 0; + //fill in the next lockings scores. + //todo : check limitation of _period and require that on the init function. + for (lockingPeriodToLockIn; lockingPeriodToLockIn < lockingPeriodToLockIn+_period; lockingPeriodToLockIn++) { + Locking storage locking = lockings[lockingPeriodToLockIn]; + uint256 score = (_period - i) * _amount; + i = i + 1; + locking.totalScore = locking.totalScore.add(score); + locking.scores[msg.sender] = score; + } + + lockingId = keccak256(abi.encodePacked(address(this), lockingsCounter)); + lockingsCounter = lockingsCounter.add(1); + + Locker storage locker = lockers[msg.sender][lockingId]; + locker.amount = _amount; + locker.period = _period; + // solhint-disable-next-line not-rely-on-time + locker.lockingTime = now; + + totalLockedLeft = totalLockedLeft.add(_amount); + + emit Lock(msg.sender, lockingId, _amount, _period); + } + + /** + * @dev release function + * @param _beneficiary the beneficiary for the release + * @param _lockingId the locking id to release + * @return bool + */ + function release(address _beneficiary, bytes32 _lockingId) public returns(uint256 amount) { + Locker storage locker = lockers[_beneficiary][_lockingId]; + require(locker.amount > 0, "amount should be > 0"); + amount = locker.amount; + locker.amount = 0; + // solhint-disable-next-line not-rely-on-time + require(block.timestamp > locker.lockingTime + (locker.period*lockingPeriodsUnit), + "check the lock period pass"); + totalLockedLeft = totalLockedLeft.sub(amount); + address(token).safeTransfer(_beneficiary, amount); + emit Release(_lockingId, _beneficiary, amount); + } + +} From 125a379811d8b67e0abfade56d524c43d2969307 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Tue, 3 Sep 2019 17:08:19 +0300 Subject: [PATCH 02/13] test --- .../ContinuousLockingToken4Reputation.sol | 130 +++++--- test/continuouslockingtoken4reputation.js | 282 ++++++++++++++++++ 2 files changed, 369 insertions(+), 43 deletions(-) create mode 100644 test/continuouslockingtoken4reputation.js diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index bda3174a..402faa31 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -1,4 +1,4 @@ -pragma solidity ^0.5.4; +pragma solidity ^0.5.11; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; import "../controller/ControllerInterface.sol"; @@ -19,7 +19,7 @@ contract ContinuousLocking4Reputation is Agreement { event Redeem(bytes32 indexed _lockingId, address indexed _beneficiary, uint256 _amount); event Release(bytes32 indexed _lockingId, address indexed _beneficiary, uint256 _amount); - event Lock(address indexed _locker, bytes32 indexed _lockingId, uint256 _amount, uint256 _period); + event LockToken(address indexed _locker, bytes32 indexed _lockingId, uint256 _amount, uint256 _period); struct Locking { uint256 totalScore; @@ -41,58 +41,66 @@ contract ContinuousLocking4Reputation is Agreement { Avatar public avatar; uint256 public reputationRewardLeft; - uint256 public lockingStartTime; + uint256 public startTime; uint256 public numberOfLockingPeriods; - uint256 public lockingReputationReward; uint256 public redeemEnableTime; - uint256 public maxLockingPeriod; - uint256 public lockingPeriodsUnit; + uint256 public maxLockingPeriods; + uint256 public periodsUnit; IERC20 public token; uint256 public lockingsCounter; // Total number of lockings uint256 public totalLockedLeft; - uint256 repRewardConstA; - uint256 repRewardConstB; + uint256 public repRewardConstA; + uint256 public repRewardConstB; + + 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 _lockingReputationReward the reputation reward per auction this contract will reward + * @param _reputationReward the reputation reward per auction this contract will reward * for the token locking - * @param _lockingStartTime auctions period start time - * @param _lockingPeriodsUnit locking periods units (e.g 30 days). + * @param _startTime auctions period start time + * @param _periodsUnit locking periods units (e.g 30 days). * @param _redeemEnableTime redeem enable time . * redeem reputation can be done after this time. - * @param _maxLockingPeriod - maximum number of locking periods (in _lockingPeriodsUnit units) + * @param _maxLockingPeriods - maximum number of locking periods (in _periodsUnit units) * @param _token the bidding token * @param _agreementHash is a hash of agreement required to be added to the TX by participants */ function initialize( Avatar _avatar, - uint256 _lockingReputationReward, - uint256 _lockingStartTime, - uint256 _lockingPeriodsUnit, + uint256 _reputationReward, + uint256 _startTime, + uint256 _periodsUnit, uint256 _redeemEnableTime, - uint256 _maxLockingPeriod, - IERC20 _token, + uint256 _maxLockingPeriods, uint256 _repRewardConstA, uint256 _repRewardConstB, + IERC20 _token, bytes32 _agreementHash ) external { require(avatar == Avatar(0), "can be called only one time"); require(_avatar != Avatar(0), "avatar cannot be zero"); - //_lockingPeriodsUnit should be greater than block interval - require(_lockingPeriodsUnit > 15, "lockingPeriod should be > 15"); - require(_redeemEnableTime >= _lockingStartTime+_lockingPeriodsUnit, - "_redeemEnableTime >= _lockingStartTime+_lockingPeriodsUnit"); + //_periodsUnit should be greater than block interval + require(_periodsUnit > 15, "lockingPeriod should be > 15"); + require(_maxLockingPeriods < 24, "maxLockingPeriods should be < 24"); + require(_redeemEnableTime >= _startTime+_periodsUnit, + "_redeemEnableTime >= _startTime+_periodsUnit"); token = _token; avatar = _avatar; - lockingStartTime = _lockingStartTime; - lockingReputationReward = _lockingReputationReward; + startTime = _startTime; + reputationRewardLeft = _reputationReward; redeemEnableTime = _redeemEnableTime; - maxLockingPeriod = _maxLockingPeriod; - lockingPeriodsUnit = _lockingPeriodsUnit; - repRewardConstA = _repRewardConstA; + maxLockingPeriods = _maxLockingPeriods; + periodsUnit = _periodsUnit; + require(_repRewardConstB < 1000, "_repRewardConstB should be < 1000"); + require(repRewardConstA < _reputationReward, "repRewardConstA should be < _reputationReward"); + repRewardConstA = toReal(uint216(_repRewardConstA)); repRewardConstB = uint216(_repRewardConstB).fraction(uint216(1000)); super.setAgreementHash(_agreementHash); } @@ -107,16 +115,18 @@ contract ContinuousLocking4Reputation is Agreement { // solhint-disable-next-line not-rely-on-time require(now > redeemEnableTime, "now > redeemEnableTime"); Locker storage locker = lockers[_beneficiary][_lockingId]; - uint256 lockingPeriodToRedeemFrom = (locker.lockingTime - lockingStartTime) / lockingPeriodsUnit; - for (lockingPeriodToRedeemFrom; lockingPeriodToRedeemFrom < locker.period; lockingPeriodToRedeemFrom) { + uint256 lockingPeriodToRedeemFrom = (locker.lockingTime - startTime) / periodsUnit; + for (lockingPeriodToRedeemFrom; lockingPeriodToRedeemFrom < locker.period; lockingPeriodToRedeemFrom++) { Locking storage locking = lockings[lockingPeriodToRedeemFrom]; uint256 score = locking.scores[_beneficiary]; require(score > 0, "locking score should be > 0"); locking.scores[_beneficiary] = 0; - uint256 lockingPeriodReputationReward = repRewardConstA.mul(repRewardConstB.pow(lockingPeriodToRedeemFrom)); - uint256 repRelation = score.mul(lockingPeriodReputationReward); - reputation = reputation.add(repRelation.div(locking.totalScore)); + uint256 lockingPeriodReputationReward = + mul(repRewardConstA, repRewardConstB.pow(lockingPeriodToRedeemFrom)); + uint256 repRelation = mul(toReal(uint216(score)), lockingPeriodReputationReward); + reputation = reputation.add(div(repRelation, toReal(uint216(locking.totalScore)))); } + reputation = uint256(fromReal(reputation)); // check that the reputation is sum zero reputationRewardLeft = reputationRewardLeft.sub(reputation); require( @@ -128,31 +138,31 @@ contract ContinuousLocking4Reputation is Agreement { /** * @dev lock function * @param _amount the amount to bid with - * @param _period the period to lock. in lockingPeriodsUnit. + * @param _period the period to lock. in periodsUnit. * @param _lockingPeriodToLockIn the locking id to lock at . * @return lockingId */ - function lock(uint256 _amount, uint256 _period ,uint256 _lockingPeriodToLockIn, bytes32 _agreementHash) + function lock(uint256 _amount, uint256 _period, uint256 _lockingPeriodToLockIn, bytes32 _agreementHash) public onlyAgree(_agreementHash) returns(bytes32 lockingId) { require(_amount > 0, "bidding amount should be > 0"); // solhint-disable-next-line not-rely-on-time - require(now >= lockingStartTime, "bidding is enable only after bidding lockingStartTime"); - require(_period <= maxLockingPeriod, "locking period exceed the maximum allowed"); + require(now >= startTime, "bidding is enable only after bidding startTime"); + require(_period <= maxLockingPeriods, "locking period exceed the maximum allowed"); require(_period > 0, "locking period equal to zero"); address(token).safeTransferFrom(msg.sender, address(this), _amount); // solhint-disable-next-line not-rely-on-time - uint256 lockingPeriodToLockIn = (now - lockingStartTime) / lockingPeriodsUnit; + uint256 lockingPeriodToLockIn = (now - startTime) / periodsUnit; require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); - uint256 i = 0; + uint256 j = _period; //fill in the next lockings scores. //todo : check limitation of _period and require that on the init function. - for (lockingPeriodToLockIn; lockingPeriodToLockIn < lockingPeriodToLockIn+_period; lockingPeriodToLockIn++) { - Locking storage locking = lockings[lockingPeriodToLockIn]; - uint256 score = (_period - i) * _amount; - i = i + 1; + for (int256 i = int256(lockingPeriodToLockIn + _period - 1); i >= int256(lockingPeriodToLockIn); i--) { + Locking storage locking = lockings[uint256(i)]; + uint256 score = (_period - j + 1) * _amount; + j--; locking.totalScore = locking.totalScore.add(score); locking.scores[msg.sender] = score; } @@ -168,7 +178,7 @@ contract ContinuousLocking4Reputation is Agreement { totalLockedLeft = totalLockedLeft.add(_amount); - emit Lock(msg.sender, lockingId, _amount, _period); + emit LockToken(msg.sender, lockingId, _amount, _period); } /** @@ -183,11 +193,45 @@ contract ContinuousLocking4Reputation is Agreement { amount = locker.amount; locker.amount = 0; // solhint-disable-next-line not-rely-on-time - require(block.timestamp > locker.lockingTime + (locker.period*lockingPeriodsUnit), + require(block.timestamp > locker.lockingTime + (locker.period*periodsUnit), "check the lock period pass"); totalLockedLeft = totalLockedLeft.sub(amount); address(token).safeTransfer(_beneficiary, amount); emit Release(_lockingId, _beneficiary, amount); } + /** + * 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/continuouslockingtoken4reputation.js b/test/continuouslockingtoken4reputation.js new file mode 100644 index 00000000..24321398 --- /dev/null +++ b/test/continuouslockingtoken4reputation.js @@ -0,0 +1,282 @@ +const helpers = require('./helpers'); +const DaoCreator = artifacts.require("./DaoCreator.sol"); +const ControllerCreator = artifacts.require("./ControllerCreator.sol"); +const constants = require('./constants'); +const ERC20Mock = artifacts.require('./test/ERC20Mock.sol'); +var ContinuousLocking4Reputation = artifacts.require("./ContinuousLocking4Reputation.sol"); + + +const setup = async function (accounts, + _initialize = true, + _reputationReward = 850000, + _startTime = 0, + _periodsUnit = (30*60*60), + _redeemEnableTime = (30*60*60), + _maxLockingPeriod = 12, + _repRewardConstA = 85000, + _repRewardConstB = 900, + _agreementHash = helpers.SOME_HASH + ) { + var testSetup = new helpers.TestSetup(); + testSetup.lockingToken = await ERC20Mock.new(accounts[0], web3.utils.toWei('100', "ether")); + var controllerCreator = await ControllerCreator.new({gas: constants.ARC_GAS_LIMIT}); + testSetup.daoCreator = await DaoCreator.new(controllerCreator.address,{gas:constants.ARC_GAS_LIMIT}); + + testSetup.org = await helpers.setupOrganization(testSetup.daoCreator,accounts[0],1000,1000); + 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; + if (_initialize === true ) { + await testSetup.continuousLocking4Reputation.initialize(testSetup.org.avatar.address, + testSetup.reputationReward, + testSetup.startTime, + testSetup.periodsUnit, + testSetup.redeemEnableTime, + testSetup.maxLockingPeriod, + testSetup.repRewardConstA, + testSetup.repRewardConstB, + testSetup.lockingToken.address, + testSetup.agreementHash, + {gas : constants.ARC_GAS_LIMIT}); + } + + var permissions = "0x00000000"; + await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address,[testSetup.continuousLocking4Reputation.address],[web3.utils.asciiToHex("0")],[permissions],"metaData"); + await testSetup.lockingToken.approve(testSetup.continuousLocking4Reputation.address,web3.utils.toWei('100', "ether")); + return testSetup; +}; + +contract('ContinuousLocking4Reputation', accounts => { + it("initialize", async () => { + let testSetup = await setup(accounts); + assert.equal(await testSetup.continuousLocking4Reputation.reputationRewardLeft(),testSetup.reputationReward); + assert.equal(await testSetup.continuousLocking4Reputation.startTime(),testSetup.startTime); + assert.equal(await testSetup.continuousLocking4Reputation.redeemEnableTime(),testSetup.redeemEnableTime); + assert.equal(await testSetup.continuousLocking4Reputation.token(),testSetup.lockingToken.address); + assert.equal(await testSetup.continuousLocking4Reputation.periodsUnit(),testSetup.periodsUnit); + assert.equal(await testSetup.continuousLocking4Reputation.getAgreementHash(),testSetup.agreementHash); + }); + + it("initialize periodsUnit <= 15 seconds is not allowed", async () => { + let testSetup = await setup(accounts,false); + try { + await testSetup.continuousLocking4Reputation.initialize(testSetup.org.avatar.address, + testSetup.reputationReward, + testSetup.startTime, + 1, + testSetup.redeemEnableTime, + testSetup.maxLockingPeriod, + testSetup.repRewardConstA, + testSetup.repRewardConstB, + testSetup.lockingToken.address, + testSetup.agreementHash, + {gas : constants.ARC_GAS_LIMIT}); + assert(false, "periodsUnit < 15 is not allowed"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("initialize _redeemEnableTime < _startTime+_periodsUnit is not allowed", async () => { + let testSetup = await setup(accounts,false); + try { + await testSetup.continuousLocking4Reputation.initialize(testSetup.org.avatar.address, + testSetup.reputationReward, + testSetup.startTime, + testSetup.periodsUnit, + testSetup.startTime + testSetup.periodsUnit -7, + testSetup.maxLockingPeriod, + testSetup.repRewardConstA, + testSetup.repRewardConstB, + testSetup.lockingToken.address, + testSetup.agreementHash, + {gas : constants.ARC_GAS_LIMIT}); + assert(false, "_redeemEnableTime < _startTime+_periodsUnit is not allowed"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + + it("lock", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"), 12 , 0, testSetup.agreementHash); + var id = await helpers.getValueFromLogs(tx, '_lockingId',1); + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"LockToken"); + assert.equal(tx.logs[0].args._lockingId,id); + assert.equal(tx.logs[0].args._amount,web3.utils.toWei('1', "ether")); + assert.equal(tx.logs[0].args._locker,accounts[0]); + //test the tokens moved to the wallet. + assert.equal(await testSetup.lockingToken.balanceOf(testSetup.continuousLocking4Reputation.address),web3.utils.toWei('1', "ether")); + }); + + + it("lock without initialize should fail", async () => { + let testSetup = await setup(accounts,false); + try { + await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0, testSetup.agreementHash); + assert(false, "lock without initialize should fail"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("lock with wrong agreementHash should fail", async () => { + let testSetup = await setup(accounts); + try { + await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0, helpers.NULL_HASH); + assert(false, "lock with wrong agreementHash should fail"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("lock with value == 0 should revert", async () => { + let testSetup = await setup(accounts); + try { + await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('0', "ether"),1,0,testSetup.agreementHash); + assert(false, "lock with value == 0 should revert"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("lock with period over maxLockingPeriod should revert", async () => { + let testSetup = await setup(accounts); + try { + await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('0', "ether"),testSetup.maxLockingPeriod +1 ,0,testSetup.agreementHash); + assert(false, "lock with period over maxLockingPeriod should revert"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + 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.continuousLocking4Reputation.redeem(accounts[0],id); + var redeemAmount = 0; + for (var lockingPeriodToRedeemFrom = 0; lockingPeriodToRedeemFrom < period; lockingPeriodToRedeemFrom++) { + redeemAmount += testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + } + redeemAmount = Math.floor(redeemAmount); + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"Redeem"); + assert.equal(tx.logs[0].args._amount.toNumber(),redeemAmount); + assert.equal(tx.logs[0].args._beneficiary,accounts[0]); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+redeemAmount); + }); + + 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.continuousLocking4Reputation.redeem(accounts[0],id1); + await testSetup.continuousLocking4Reputation.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.continuousLocking4Reputation.redeem(accounts[0],id); + try { + await testSetup.continuousLocking4Reputation.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.continuousLocking4Reputation.redeem(accounts[0],id); + assert(false, "redeem before redeemEnableTime should revert"); + } catch(error) { + helpers.assertVMException(error); + } + await helpers.increaseTime(testSetup.redeemEnableTime); + await testSetup.continuousLocking4Reputation.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.continuousLocking4Reputation.redeem(accounts[0],id1); + await testSetup.continuousLocking4Reputation.redeem(accounts[0],id2); + await testSetup.continuousLocking4Reputation.redeem(accounts[0],id3); + // assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+300); + }); + + it("cannot initialize twice", async () => { + let testSetup = await setup(accounts); + try { + await testSetup.continuousLocking4Reputation.initialize(testSetup.org.avatar.address, + testSetup.reputationReward, + testSetup.startTime, + testSetup.periodsUnit, + testSetup.redeemEnableTime, + testSetup.maxLockingPeriod, + testSetup.repRewardConstA, + testSetup.repRewardConstB, + testSetup.lockingToken.address, + testSetup.agreementHash, + {gas : constants.ARC_GAS_LIMIT}); + assert(false, "cannot initialize twice"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("cannot bid with wrong _lockingPeriodToLockIn", async () => { + var lockingPeriodToLockIn = 2; + let testSetup = await setup(accounts); + try { + await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,lockingPeriodToLockIn,testSetup.agreementHash); + assert(false, "cannot lock with wrong _lockingPeriodToLockIn"); + } catch(error) { + helpers.assertVMException(error); + } + }); +}); From 0e83c143d33d04a4a1265114351a4a8c3097577d Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Tue, 3 Sep 2019 17:09:54 +0300 Subject: [PATCH 03/13] lint --- contracts/schemes/ContinuousLockingToken4Reputation.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index 402faa31..22d873bf 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -10,7 +10,6 @@ import { RealMath } from "@daostack/infra/contracts/libs/RealMath.sol"; * @title A scheme for conduct ERC20 Tokens auction for reputation */ - contract ContinuousLocking4Reputation is Agreement { using SafeMath for uint256; using SafeERC20 for address; From f01df09612b697feb609504efb3a87999e48c7d9 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Sep 2019 00:39:41 +0300 Subject: [PATCH 04/13] release tests --- .../ContinuousLockingToken4Reputation.sol | 26 ++++--- test/continuouslockingtoken4reputation.js | 77 ++++++++++++++++++- 2 files changed, 92 insertions(+), 11 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index 22d873bf..4fdb8450 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -1,6 +1,7 @@ pragma solidity ^0.5.11; import "openzeppelin-solidity/contracts/math/SafeMath.sol"; +import "openzeppelin-solidity/contracts/math/Math.sol"; import "../controller/ControllerInterface.sol"; import "../libs/SafeERC20.sol"; import "./Agreement.sol"; @@ -15,6 +16,7 @@ contract ContinuousLocking4Reputation is Agreement { using SafeERC20 for address; using RealMath for uint216; using RealMath for uint256; + using Math for uint256; event Redeem(bytes32 indexed _lockingId, address indexed _beneficiary, uint256 _amount); event Release(bytes32 indexed _lockingId, address indexed _beneficiary, uint256 _amount); @@ -28,7 +30,6 @@ contract ContinuousLocking4Reputation is Agreement { struct Locker { uint256 amount; - uint256 releaseTime; uint256 lockingTime; uint256 period; } @@ -114,18 +115,23 @@ contract ContinuousLocking4Reputation is Agreement { // solhint-disable-next-line not-rely-on-time require(now > redeemEnableTime, "now > redeemEnableTime"); Locker storage locker = lockers[_beneficiary][_lockingId]; - uint256 lockingPeriodToRedeemFrom = (locker.lockingTime - startTime) / periodsUnit; - for (lockingPeriodToRedeemFrom; lockingPeriodToRedeemFrom < locker.period; lockingPeriodToRedeemFrom++) { - Locking storage locking = lockings[lockingPeriodToRedeemFrom]; + uint256 periodToRedeemFrom = (locker.lockingTime - startTime) / periodsUnit; + // solhint-disable-next-line not-rely-on-time + uint256 currentLockingPeriod = (now - startTime) / periodsUnit; + uint256 lastLockingPeriodToRedeem = currentLockingPeriod.min(periodToRedeemFrom + locker.period); + for (periodToRedeemFrom; periodToRedeemFrom < lastLockingPeriodToRedeem; periodToRedeemFrom++) { + Locking storage locking = lockings[periodToRedeemFrom]; uint256 score = locking.scores[_beneficiary]; - require(score > 0, "locking score should be > 0"); - locking.scores[_beneficiary] = 0; - uint256 lockingPeriodReputationReward = - mul(repRewardConstA, repRewardConstB.pow(lockingPeriodToRedeemFrom)); - uint256 repRelation = mul(toReal(uint216(score)), lockingPeriodReputationReward); - reputation = reputation.add(div(repRelation, toReal(uint216(locking.totalScore)))); + if (score > 0) { + locking.scores[_beneficiary] = 0; + uint256 lockingPeriodReputationReward = + mul(repRewardConstA, repRewardConstB.pow(periodToRedeemFrom)); + uint256 repRelation = mul(toReal(uint216(score)), lockingPeriodReputationReward); + reputation = reputation.add(div(repRelation, toReal(uint216(locking.totalScore)))); + } } reputation = uint256(fromReal(reputation)); + require(reputation > 0, "reputation to redeem is 0"); // check that the reputation is sum zero reputationRewardLeft = reputationRewardLeft.sub(reputation); require( diff --git a/test/continuouslockingtoken4reputation.js b/test/continuouslockingtoken4reputation.js index 24321398..6910dbfd 100644 --- a/test/continuouslockingtoken4reputation.js +++ b/test/continuouslockingtoken4reputation.js @@ -178,6 +178,42 @@ contract('ContinuousLocking4Reputation', accounts => { assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+redeemAmount); }); + 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.continuousLocking4Reputation.redeem(accounts[0],id); + var redeemAmount = 230349; + // for (var lockingPeriodToRedeemFrom = 0; lockingPeriodToRedeemFrom < 3; lockingPeriodToRedeemFrom++) { + // redeemAmount += testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + // } + + //redeemAmount = Math.round(redeemAmount); + + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"Redeem"); + assert.equal(tx.logs[0].args._amount.toNumber(),redeemAmount); + assert.equal(tx.logs[0].args._beneficiary,accounts[0]); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+redeemAmount); + + await helpers.increaseTime(testSetup.periodsUnit * 9 +1); + tx = await testSetup.continuousLocking4Reputation.redeem(accounts[0],id); + redeemAmount = 0; + for (var lockingPeriodToRedeemFrom = 3; lockingPeriodToRedeemFrom < period; lockingPeriodToRedeemFrom++) { + redeemAmount += testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + } + + redeemAmount = Math.round(redeemAmount) - 1; + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"Redeem"); + assert.equal(tx.logs[0].args._amount.toNumber(),redeemAmount); + assert.equal(tx.logs[0].args._beneficiary,accounts[0]); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+redeemAmount + 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]}); @@ -269,7 +305,7 @@ contract('ContinuousLocking4Reputation', accounts => { } }); - it("cannot bid with wrong _lockingPeriodToLockIn", async () => { + it("cannot lock with wrong _lockingPeriodToLockIn", async () => { var lockingPeriodToLockIn = 2; let testSetup = await setup(accounts); try { @@ -279,4 +315,43 @@ contract('ContinuousLocking4Reputation', accounts => { helpers.assertVMException(error); } }); + + it("release", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0,testSetup.agreementHash); + var lockingId = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit+1); + tx = await testSetup.continuousLocking4Reputation.release(accounts[0],lockingId); + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"Release"); + assert.equal(tx.logs[0].args._lockingId,lockingId); + assert.equal(tx.logs[0].args._amount,web3.utils.toWei('1', "ether")); + assert.equal(tx.logs[0].args._beneficiary,accounts[0]); + }); + + it("release before locking period should revert", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0,testSetup.agreementHash); + var lockingId = await helpers.getValueFromLogs(tx, '_lockingId',1); + try { + await testSetup.continuousLocking4Reputation.release(accounts[0],lockingId); + assert(false, "release before locking period should revert"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("release cannot release twice", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,0,testSetup.agreementHash); + var lockingId = await helpers.getValueFromLogs(tx, '_lockingId',1); + await helpers.increaseTime(testSetup.periodsUnit+1); + await testSetup.continuousLocking4Reputation.release(accounts[0],lockingId); + try { + await testSetup.continuousLocking4Reputation.release(accounts[0],lockingId); + assert(false, "release cannot release twice"); + } catch(error) { + helpers.assertVMException(error); + } + }); }); From 9debc49fedc8a3b6268c57066a2071a202816282 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Sep 2019 00:44:39 +0300 Subject: [PATCH 05/13] comments --- contracts/schemes/ContinuousLockingToken4Reputation.sol | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index 4fdb8450..e388c114 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -8,7 +8,7 @@ import "./Agreement.sol"; import { RealMath } from "@daostack/infra/contracts/libs/RealMath.sol"; /** - * @title A scheme for conduct ERC20 Tokens auction for reputation + * @title A scheme for continuous locking ERC20 Token for reputation */ contract ContinuousLocking4Reputation is Agreement { @@ -180,9 +180,7 @@ contract ContinuousLocking4Reputation is Agreement { locker.period = _period; // solhint-disable-next-line not-rely-on-time locker.lockingTime = now; - totalLockedLeft = totalLockedLeft.add(_amount); - emit LockToken(msg.sender, lockingId, _amount, _period); } From e5c1cd6a7ecda7e8a08dde99f9e16f8f660fd90e Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Sep 2019 11:48:45 +0300 Subject: [PATCH 06/13] limit periods to 100 --- .../ContinuousLockingToken4Reputation.sol | 15 ++++++-- test/continuouslockingtoken4reputation.js | 34 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index e388c114..36446a4d 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -58,6 +58,9 @@ contract ContinuousLocking4Reputation is Agreement { */ uint256 constant private REAL_ONE = uint256(1) << REAL_FBITS; + uint256 constant public MAX_PERIODS = 100; + uint256 constant public MAX_LOCKING_PERIODS = 24; + /** * @dev initialize * @param _avatar the avatar to mint reputation from @@ -88,7 +91,7 @@ contract ContinuousLocking4Reputation is Agreement { require(_avatar != Avatar(0), "avatar cannot be zero"); //_periodsUnit should be greater than block interval require(_periodsUnit > 15, "lockingPeriod should be > 15"); - require(_maxLockingPeriods < 24, "maxLockingPeriods should be < 24"); + require(_maxLockingPeriods <= MAX_LOCKING_PERIODS, "maxLockingPeriods should be <= MAX_LOCKING_PERIODS"); require(_redeemEnableTime >= _startTime+_periodsUnit, "_redeemEnableTime >= _startTime+_periodsUnit"); token = _token; @@ -124,8 +127,7 @@ contract ContinuousLocking4Reputation is Agreement { uint256 score = locking.scores[_beneficiary]; if (score > 0) { locking.scores[_beneficiary] = 0; - uint256 lockingPeriodReputationReward = - mul(repRewardConstA, repRewardConstB.pow(periodToRedeemFrom)); + uint256 lockingPeriodReputationReward = repRewardPerPeriod(periodToRedeemFrom); uint256 repRelation = mul(toReal(uint216(score)), lockingPeriodReputationReward); reputation = reputation.add(div(repRelation, toReal(uint216(locking.totalScore)))); } @@ -157,6 +159,7 @@ contract ContinuousLocking4Reputation is Agreement { require(now >= startTime, "bidding is enable only after bidding startTime"); require(_period <= maxLockingPeriods, "locking period exceed the maximum allowed"); require(_period > 0, "locking period equal to zero"); + require((_lockingPeriodToLockIn + _period) <= MAX_PERIODS, "exceed max allowed periods"); address(token).safeTransferFrom(msg.sender, address(this), _amount); // solhint-disable-next-line not-rely-on-time uint256 lockingPeriodToLockIn = (now - startTime) / periodsUnit; @@ -203,6 +206,12 @@ contract ContinuousLocking4Reputation is Agreement { emit Release(_lockingId, _beneficiary, amount); } + function repRewardPerPeriod(uint256 _periodToRedeemFrom) public view returns(uint256 repReward) { + if (_periodToRedeemFrom <= MAX_PERIODS) { + repReward = mul(repRewardConstA, repRewardConstB.pow(_periodToRedeemFrom)); + } + } + /** * Multiply one real by another. Truncates overflows. */ diff --git a/test/continuouslockingtoken4reputation.js b/test/continuouslockingtoken4reputation.js index 6910dbfd..62a678cc 100644 --- a/test/continuouslockingtoken4reputation.js +++ b/test/continuouslockingtoken4reputation.js @@ -354,4 +354,38 @@ contract('ContinuousLocking4Reputation', accounts => { helpers.assertVMException(error); } }); + + it("redeem reward limits 100 periods", async () => { + let testSetup = await setup(accounts); + var repForPeriod = await testSetup.continuousLocking4Reputation.repRewardPerPeriod(100); + var REAL_FBITS = 40; + var res = (repForPeriod.shrn(REAL_FBITS).toNumber() + (repForPeriod.maskn(REAL_FBITS)/Math.pow(2,REAL_FBITS))).toFixed(2); + assert.equal(Math.floor(res),Math.floor(testSetup.repRewardConstA* Math.pow(testSetup.repRewardConstB/1000,100))); + assert.equal(await testSetup.continuousLocking4Reputation.repRewardPerPeriod(101),0); + }); + + it("redeem limits 100 periods", async () => { + let testSetup = await setup(accounts,false); + var period = 24; + await helpers.increaseTime(testSetup.periodsUnit*90+1); + await testSetup.continuousLocking4Reputation.initialize(testSetup.org.avatar.address, + testSetup.reputationReward, + testSetup.startTime, + testSetup.periodsUnit, + testSetup.redeemEnableTime, + period, + testSetup.repRewardConstA, + testSetup.repRewardConstB, + testSetup.lockingToken.address, + testSetup.agreementHash, + {gas : constants.ARC_GAS_LIMIT}); + await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),1,90,testSetup.agreementHash); + try { + await testSetup.continuousLocking4Reputation.lock(web3.utils.toWei('1', "ether"),period,90,testSetup.agreementHash); + assert(false, "exceed max allowe periods"); + } catch(error) { + helpers.assertVMException(error); + } + }); + }); From f2bb4b3a2296fd9bbad43e6fdb3e7147899abd21 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 4 Sep 2019 15:00:34 +0300 Subject: [PATCH 07/13] comments --- .../schemes/ContinuousLockingToken4Reputation.sol | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index 36446a4d..f1adc48d 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -206,9 +206,16 @@ contract ContinuousLocking4Reputation is Agreement { emit Release(_lockingId, _beneficiary, amount); } - function repRewardPerPeriod(uint256 _periodToRedeemFrom) public view returns(uint256 repReward) { - if (_periodToRedeemFrom <= MAX_PERIODS) { - repReward = mul(repRewardConstA, repRewardConstB.pow(_periodToRedeemFrom)); + /** + * @dev repRewardPerPeriod function + * the calculation is done the following formula: + * RepReward = repRewardConstA * (repRewardConstB**_periodNumber) + * @param _periodNumber the period number to calc rep reward of + * @return repReward + */ + function repRewardPerPeriod(uint256 _periodNumber) public view returns(uint256 repReward) { + if (_periodNumber <= MAX_PERIODS) { + repReward = mul(repRewardConstA, repRewardConstB.pow(_periodNumber)); } } From 056abc8d17f04a049571cd6d8938c3b27cd23784 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Sun, 8 Sep 2019 11:32:55 +0300 Subject: [PATCH 08/13] add extendLocking function --- .../ContinuousLockingToken4Reputation.sol | 49 +++++++++- test/continuouslockingtoken4reputation.js | 97 +++++++++++++++++++ 2 files changed, 143 insertions(+), 3 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index f1adc48d..67151c08 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -21,6 +21,7 @@ contract ContinuousLocking4Reputation is Agreement { event Redeem(bytes32 indexed _lockingId, address indexed _beneficiary, uint256 _amount); event Release(bytes32 indexed _lockingId, address indexed _beneficiary, uint256 _amount); event LockToken(address indexed _locker, bytes32 indexed _lockingId, uint256 _amount, uint256 _period); + event ExtendLocking(address indexed _locker, bytes32 indexed _lockingId, uint256 _extendPeriod); struct Locking { uint256 totalScore; @@ -71,7 +72,7 @@ contract ContinuousLocking4Reputation is Agreement { * @param _redeemEnableTime redeem enable time . * redeem reputation can be done after this time. * @param _maxLockingPeriods - maximum number of locking periods (in _periodsUnit units) - * @param _token the bidding token + * @param _token the locking token * @param _agreementHash is a hash of agreement required to be added to the TX by participants */ function initialize( @@ -154,9 +155,9 @@ contract ContinuousLocking4Reputation is Agreement { onlyAgree(_agreementHash) returns(bytes32 lockingId) { - require(_amount > 0, "bidding amount should be > 0"); + require(_amount > 0, "locking amount should be > 0"); // solhint-disable-next-line not-rely-on-time - require(now >= startTime, "bidding is enable only after bidding startTime"); + require(now >= startTime, "locking is enable only after locking startTime"); require(_period <= maxLockingPeriods, "locking period exceed the maximum allowed"); require(_period > 0, "locking period equal to zero"); require((_lockingPeriodToLockIn + _period) <= MAX_PERIODS, "exceed max allowed periods"); @@ -187,6 +188,48 @@ contract ContinuousLocking4Reputation is Agreement { emit LockToken(msg.sender, lockingId, _amount, _period); } + /** + * @dev extendLocking function + * @param _extendPeriod the period to extend the locking. in periodsUnit. + * @param _lockingPeriodToLockIn the locking id to lock at . + * @param _lockingId the locking id to extend + */ + function extendLocking( + uint256 _extendPeriod, + uint256 _lockingPeriodToLockIn, + bytes32 _lockingId, + bytes32 _agreementHash) + public + onlyAgree(_agreementHash) + { + Locker storage locker = lockers[msg.sender][_lockingId]; + require(locker.lockingTime != 0, "wrong locking id"); + uint256 lockingPeriodRemain = + ((locker.lockingTime + (locker.period*periodsUnit) - startTime)/periodsUnit).sub(_lockingPeriodToLockIn); + uint256 extendPeriodsFromNow = lockingPeriodRemain + _extendPeriod; + require(extendPeriodsFromNow <= maxLockingPeriods, "locking period exceed the maximum allowed"); + require(_extendPeriod > 0, "extend locking period equal to zero"); + require((_lockingPeriodToLockIn + extendPeriodsFromNow) <= MAX_PERIODS, + "exceed max allowed periods"); + // solhint-disable-next-line not-rely-on-time + uint256 lockingPeriodToLockIn = (now - startTime) / periodsUnit; + require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); + uint256 j = extendPeriodsFromNow; + //fill in the next lockings scores. + //todo : check limitation of _period and require that on the init function. + for (int256 i = int256(lockingPeriodToLockIn + extendPeriodsFromNow - 1); + i >= int256(lockingPeriodToLockIn); + i--) { + Locking storage locking = lockings[uint256(i)]; + uint256 score = (extendPeriodsFromNow - j + 1) * locker.amount; + j--; + locking.totalScore = locking.totalScore.add(score).sub(locking.scores[msg.sender]); + locking.scores[msg.sender] = score; + } + locker.period = locker.period + _extendPeriod; + emit ExtendLocking(msg.sender, _lockingId, _extendPeriod); + } + /** * @dev release function * @param _beneficiary the beneficiary for the release diff --git a/test/continuouslockingtoken4reputation.js b/test/continuouslockingtoken4reputation.js index 62a678cc..b328486c 100644 --- a/test/continuouslockingtoken4reputation.js +++ b/test/continuouslockingtoken4reputation.js @@ -388,4 +388,101 @@ contract('ContinuousLocking4Reputation', accounts => { } }); + it("extend locking withouth lock should fail", async () => { + let testSetup = await setup(accounts); + try { + await testSetup.continuousLocking4Reputation.extendLocking(1,0,helpers.SOME_HASH,testSetup.agreementHash); + assert(false, "extend locking withouth lock should fail"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + 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,id); + 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.continuousLocking4Reputation.redeem(accounts[0],id); + var redeemAmount = 0; + for (var lockingPeriodToRedeemFrom = 0; lockingPeriodToRedeemFrom < period+1; lockingPeriodToRedeemFrom++) { + redeemAmount += testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + } + redeemAmount = Math.floor(redeemAmount); + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"Redeem"); + assert.equal(tx.logs[0].args._amount.toNumber(),redeemAmount); + assert.equal(tx.logs[0].args._beneficiary,accounts[0]); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+redeemAmount); + + }); + + it("extend locking should not exceed the max period allowed", 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); + try { + await testSetup.continuousLocking4Reputation.extendLocking(1,0,id,testSetup.agreementHash); + assert(false, "extend locking should not exceed the max period allowed"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + 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.continuousLocking4Reputation.redeem(accounts[0],id); + var redeemAmount = 0; + for (var lockingPeriodToRedeemFrom = 0; lockingPeriodToRedeemFrom < 100; lockingPeriodToRedeemFrom++) { + redeemAmount += testSetup.repRewardConstA * (Math.pow((testSetup.repRewardConstB/1000),lockingPeriodToRedeemFrom)); + } + redeemAmount = Math.floor(redeemAmount); + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"Redeem"); + assert.equal(tx.logs[0].args._amount.toNumber(),redeemAmount); + assert.equal(tx.logs[0].args._beneficiary,accounts[0]); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000+redeemAmount); + + }); + + + }); From 7d2e1818b387d001e085eb53ec3eafdececf8ddc Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Sun, 8 Sep 2019 14:08:17 +0300 Subject: [PATCH 09/13] comments --- contracts/schemes/ContinuousLockingToken4Reputation.sol | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index 67151c08..fb69abac 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -72,6 +72,10 @@ contract ContinuousLocking4Reputation is Agreement { * @param _redeemEnableTime redeem enable time . * redeem reputation can be done after this time. * @param _maxLockingPeriods - maximum number of locking periods (in _periodsUnit units) + * @param _repRewardConstA - reputation allocation per period is calculated by : + * _repRewardConstA * (_repRewardConstB ** periodNumber) + * @param _repRewardConstB - reputation allocation per period is calculated by : + * _repRewardConstA * (_repRewardConstB ** periodNumber) * @param _token the locking token * @param _agreementHash is a hash of agreement required to be added to the TX by participants */ @@ -167,7 +171,6 @@ contract ContinuousLocking4Reputation is Agreement { require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); uint256 j = _period; //fill in the next lockings scores. - //todo : check limitation of _period and require that on the init function. for (int256 i = int256(lockingPeriodToLockIn + _period - 1); i >= int256(lockingPeriodToLockIn); i--) { Locking storage locking = lockings[uint256(i)]; uint256 score = (_period - j + 1) * _amount; @@ -216,7 +219,6 @@ contract ContinuousLocking4Reputation is Agreement { require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); uint256 j = extendPeriodsFromNow; //fill in the next lockings scores. - //todo : check limitation of _period and require that on the init function. for (int256 i = int256(lockingPeriodToLockIn + extendPeriodsFromNow - 1); i >= int256(lockingPeriodToLockIn); i--) { From b22f5c36d7d0b666bcf11645ad1d6b7f733a3768 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Sun, 8 Sep 2019 17:16:58 +0300 Subject: [PATCH 10/13] naming --- .../ContinuousLockingToken4Reputation.sol | 51 ++++++++++--------- test/continuouslockingtoken4reputation.js | 29 +++++++++++ 2 files changed, 57 insertions(+), 23 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index fb69abac..6bc5d086 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -23,7 +23,7 @@ contract ContinuousLocking4Reputation is Agreement { event LockToken(address indexed _locker, bytes32 indexed _lockingId, uint256 _amount, uint256 _period); event ExtendLocking(address indexed _locker, bytes32 indexed _lockingId, uint256 _extendPeriod); - struct Locking { + struct Batch { uint256 totalScore; // A mapping from locker addresses to their locking score. mapping(address=>uint256) scores; @@ -38,29 +38,30 @@ contract ContinuousLocking4Reputation is Agreement { // A mapping from lockers addresses their lock balances. mapping(address => mapping(bytes32=>Locker)) public lockers; // A mapping from locking index to locking. - mapping(uint256=>Locking) public lockings; + mapping(uint256=>Batch) public batches; Avatar public avatar; uint256 public reputationRewardLeft; uint256 public startTime; uint256 public numberOfLockingPeriods; uint256 public redeemEnableTime; - uint256 public maxLockingPeriods; + uint256 public maxLockingBatches; uint256 public periodsUnit; IERC20 public token; - uint256 public lockingsCounter; // Total number of lockings + uint256 public batchesCounter; // Total number of batches uint256 public totalLockedLeft; uint256 public repRewardConstA; uint256 public repRewardConstB; + uint256 public periodsCap; uint256 constant private REAL_FBITS = 40; /** * What's the first non-fractional bit */ - uint256 constant private REAL_ONE = uint256(1) << REAL_FBITS; - uint256 constant public MAX_PERIODS = 100; - uint256 constant public MAX_LOCKING_PERIODS = 24; + uint256 constant private REAL_ONE = uint256(1) << REAL_FBITS; + uint256 constant private PERIODS_HARDCAP = 100; + uint256 constant public MAX_LOCKING_BATCHES_HARDCAP = 24; /** * @dev initialize @@ -71,7 +72,7 @@ contract ContinuousLocking4Reputation is Agreement { * @param _periodsUnit locking periods units (e.g 30 days). * @param _redeemEnableTime redeem enable time . * redeem reputation can be done after this time. - * @param _maxLockingPeriods - maximum number of locking periods (in _periodsUnit units) + * @param _maxLockingBatches - maximum number of locking periods (in _periodsUnit units) * @param _repRewardConstA - reputation allocation per period is calculated by : * _repRewardConstA * (_repRewardConstB ** periodNumber) * @param _repRewardConstB - reputation allocation per period is calculated by : @@ -85,9 +86,10 @@ contract ContinuousLocking4Reputation is Agreement { uint256 _startTime, uint256 _periodsUnit, uint256 _redeemEnableTime, - uint256 _maxLockingPeriods, + uint256 _maxLockingBatches, uint256 _repRewardConstA, uint256 _repRewardConstB, + uint256 _periodsCap, IERC20 _token, bytes32 _agreementHash ) external @@ -96,20 +98,23 @@ contract ContinuousLocking4Reputation is Agreement { require(_avatar != Avatar(0), "avatar cannot be zero"); //_periodsUnit should be greater than block interval require(_periodsUnit > 15, "lockingPeriod should be > 15"); - require(_maxLockingPeriods <= MAX_LOCKING_PERIODS, "maxLockingPeriods should be <= MAX_LOCKING_PERIODS"); + require(_maxLockingBatches <= MAX_LOCKING_BATCHES_HARDCAP, + "maxLockingBatches should be <= MAX_LOCKING_BATCHES_HARDCAP"); require(_redeemEnableTime >= _startTime+_periodsUnit, "_redeemEnableTime >= _startTime+_periodsUnit"); + require(_periodsCap <= PERIODS_HARDCAP, "_periodsCap > PERIODS_HARDCAP"); token = _token; avatar = _avatar; startTime = _startTime; reputationRewardLeft = _reputationReward; redeemEnableTime = _redeemEnableTime; - maxLockingPeriods = _maxLockingPeriods; + maxLockingBatches = _maxLockingBatches; periodsUnit = _periodsUnit; require(_repRewardConstB < 1000, "_repRewardConstB should be < 1000"); require(repRewardConstA < _reputationReward, "repRewardConstA should be < _reputationReward"); repRewardConstA = toReal(uint216(_repRewardConstA)); repRewardConstB = uint216(_repRewardConstB).fraction(uint216(1000)); + periodsCap = _periodsCap; super.setAgreementHash(_agreementHash); } @@ -128,7 +133,7 @@ contract ContinuousLocking4Reputation is Agreement { uint256 currentLockingPeriod = (now - startTime) / periodsUnit; uint256 lastLockingPeriodToRedeem = currentLockingPeriod.min(periodToRedeemFrom + locker.period); for (periodToRedeemFrom; periodToRedeemFrom < lastLockingPeriodToRedeem; periodToRedeemFrom++) { - Locking storage locking = lockings[periodToRedeemFrom]; + Batch storage locking = batches[periodToRedeemFrom]; uint256 score = locking.scores[_beneficiary]; if (score > 0) { locking.scores[_beneficiary] = 0; @@ -162,25 +167,25 @@ contract ContinuousLocking4Reputation is Agreement { require(_amount > 0, "locking amount should be > 0"); // solhint-disable-next-line not-rely-on-time require(now >= startTime, "locking is enable only after locking startTime"); - require(_period <= maxLockingPeriods, "locking period exceed the maximum allowed"); + require(_period <= maxLockingBatches, "locking period exceed the maximum allowed"); require(_period > 0, "locking period equal to zero"); - require((_lockingPeriodToLockIn + _period) <= MAX_PERIODS, "exceed max allowed periods"); + require((_lockingPeriodToLockIn + _period) <= periodsCap, "exceed max allowed periods"); address(token).safeTransferFrom(msg.sender, address(this), _amount); // solhint-disable-next-line not-rely-on-time uint256 lockingPeriodToLockIn = (now - startTime) / periodsUnit; require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); uint256 j = _period; - //fill in the next lockings scores. + //fill in the next batche scores. for (int256 i = int256(lockingPeriodToLockIn + _period - 1); i >= int256(lockingPeriodToLockIn); i--) { - Locking storage locking = lockings[uint256(i)]; + Batch storage locking = batches[uint256(i)]; uint256 score = (_period - j + 1) * _amount; j--; locking.totalScore = locking.totalScore.add(score); locking.scores[msg.sender] = score; } - lockingId = keccak256(abi.encodePacked(address(this), lockingsCounter)); - lockingsCounter = lockingsCounter.add(1); + lockingId = keccak256(abi.encodePacked(address(this), batchesCounter)); + batchesCounter = batchesCounter.add(1); Locker storage locker = lockers[msg.sender][lockingId]; locker.amount = _amount; @@ -210,19 +215,19 @@ contract ContinuousLocking4Reputation is Agreement { uint256 lockingPeriodRemain = ((locker.lockingTime + (locker.period*periodsUnit) - startTime)/periodsUnit).sub(_lockingPeriodToLockIn); uint256 extendPeriodsFromNow = lockingPeriodRemain + _extendPeriod; - require(extendPeriodsFromNow <= maxLockingPeriods, "locking period exceed the maximum allowed"); + require(extendPeriodsFromNow <= maxLockingBatches, "locking period exceed the maximum allowed"); require(_extendPeriod > 0, "extend locking period equal to zero"); - require((_lockingPeriodToLockIn + extendPeriodsFromNow) <= MAX_PERIODS, + require((_lockingPeriodToLockIn + extendPeriodsFromNow) <= periodsCap, "exceed max allowed periods"); // solhint-disable-next-line not-rely-on-time uint256 lockingPeriodToLockIn = (now - startTime) / periodsUnit; require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); uint256 j = extendPeriodsFromNow; - //fill in the next lockings scores. + //fill in the next batche scores. for (int256 i = int256(lockingPeriodToLockIn + extendPeriodsFromNow - 1); i >= int256(lockingPeriodToLockIn); i--) { - Locking storage locking = lockings[uint256(i)]; + Batch storage locking = batches[uint256(i)]; uint256 score = (extendPeriodsFromNow - j + 1) * locker.amount; j--; locking.totalScore = locking.totalScore.add(score).sub(locking.scores[msg.sender]); @@ -259,7 +264,7 @@ contract ContinuousLocking4Reputation is Agreement { * @return repReward */ function repRewardPerPeriod(uint256 _periodNumber) public view returns(uint256 repReward) { - if (_periodNumber <= MAX_PERIODS) { + if (_periodNumber <= periodsCap) { repReward = mul(repRewardConstA, repRewardConstB.pow(_periodNumber)); } } diff --git a/test/continuouslockingtoken4reputation.js b/test/continuouslockingtoken4reputation.js index b328486c..79add6ea 100644 --- a/test/continuouslockingtoken4reputation.js +++ b/test/continuouslockingtoken4reputation.js @@ -15,6 +15,7 @@ const setup = async function (accounts, _maxLockingPeriod = 12, _repRewardConstA = 85000, _repRewardConstB = 900, + _periodsCap = 100, _agreementHash = helpers.SOME_HASH ) { var testSetup = new helpers.TestSetup(); @@ -33,6 +34,7 @@ const setup = async function (accounts, testSetup.repRewardConstA = _repRewardConstA; testSetup.repRewardConstB = _repRewardConstB; testSetup.reputationReward = _reputationReward; + testSetup.periodsCap = _periodsCap; if (_initialize === true ) { await testSetup.continuousLocking4Reputation.initialize(testSetup.org.avatar.address, testSetup.reputationReward, @@ -42,6 +44,7 @@ const setup = async function (accounts, testSetup.maxLockingPeriod, testSetup.repRewardConstA, testSetup.repRewardConstB, + testSetup.periodsCap, testSetup.lockingToken.address, testSetup.agreementHash, {gas : constants.ARC_GAS_LIMIT}); @@ -62,6 +65,7 @@ contract('ContinuousLocking4Reputation', accounts => { assert.equal(await testSetup.continuousLocking4Reputation.token(),testSetup.lockingToken.address); assert.equal(await testSetup.continuousLocking4Reputation.periodsUnit(),testSetup.periodsUnit); assert.equal(await testSetup.continuousLocking4Reputation.getAgreementHash(),testSetup.agreementHash); + assert.equal(await testSetup.continuousLocking4Reputation.periodsCap(),testSetup.periodsCap); }); it("initialize periodsUnit <= 15 seconds is not allowed", async () => { @@ -75,6 +79,7 @@ contract('ContinuousLocking4Reputation', accounts => { testSetup.maxLockingPeriod, testSetup.repRewardConstA, testSetup.repRewardConstB, + testSetup.periodsCap, testSetup.lockingToken.address, testSetup.agreementHash, {gas : constants.ARC_GAS_LIMIT}); @@ -95,6 +100,7 @@ contract('ContinuousLocking4Reputation', accounts => { testSetup.maxLockingPeriod, testSetup.repRewardConstA, testSetup.repRewardConstB, + testSetup.periodsCap, testSetup.lockingToken.address, testSetup.agreementHash, {gas : constants.ARC_GAS_LIMIT}); @@ -104,6 +110,27 @@ contract('ContinuousLocking4Reputation', accounts => { } }); + it("period cap", async () => { + let testSetup = await setup(accounts,false); + try { + await testSetup.continuousLocking4Reputation.initialize(testSetup.org.avatar.address, + testSetup.reputationReward, + testSetup.startTime, + testSetup.periodsUnit, + testSetup.startTime, + testSetup.maxLockingPeriod, + testSetup.repRewardConstA, + testSetup.repRewardConstB, + testSetup.periodsCap +1, + testSetup.lockingToken.address, + testSetup.agreementHash, + {gas : constants.ARC_GAS_LIMIT}); + assert(false, "period cap cannot be greater than 100"); + } catch(error) { + helpers.assertVMException(error); + } + }); + it("lock", async () => { let testSetup = await setup(accounts); @@ -296,6 +323,7 @@ contract('ContinuousLocking4Reputation', accounts => { testSetup.maxLockingPeriod, testSetup.repRewardConstA, testSetup.repRewardConstB, + testSetup.periodsCap, testSetup.lockingToken.address, testSetup.agreementHash, {gas : constants.ARC_GAS_LIMIT}); @@ -376,6 +404,7 @@ contract('ContinuousLocking4Reputation', accounts => { period, testSetup.repRewardConstA, testSetup.repRewardConstB, + testSetup.periodsCap, testSetup.lockingToken.address, testSetup.agreementHash, {gas : constants.ARC_GAS_LIMIT}); From 00e2858b1f234af6a4933d654f4f6e5bc3cb4a93 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Sun, 8 Sep 2019 17:21:21 +0300 Subject: [PATCH 11/13] naming --- .../schemes/ContinuousLockingToken4Reputation.sol | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index 6bc5d086..ce24fa83 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -29,14 +29,14 @@ contract ContinuousLocking4Reputation is Agreement { mapping(address=>uint256) scores; } - struct Locker { + struct Lock { uint256 amount; uint256 lockingTime; uint256 period; } // A mapping from lockers addresses their lock balances. - mapping(address => mapping(bytes32=>Locker)) public lockers; + mapping(address => mapping(bytes32=>Lock)) public lockers; // A mapping from locking index to locking. mapping(uint256=>Batch) public batches; @@ -77,6 +77,7 @@ contract ContinuousLocking4Reputation is Agreement { * _repRewardConstA * (_repRewardConstB ** periodNumber) * @param _repRewardConstB - reputation allocation per period is calculated by : * _repRewardConstA * (_repRewardConstB ** periodNumber) + * @param _periodsCap the max periods number to allow to lock in . this value capped by PERIODS_HARDCAP * @param _token the locking token * @param _agreementHash is a hash of agreement required to be added to the TX by participants */ @@ -127,7 +128,7 @@ contract ContinuousLocking4Reputation is Agreement { function redeem(address _beneficiary, bytes32 _lockingId) public returns(uint256 reputation) { // solhint-disable-next-line not-rely-on-time require(now > redeemEnableTime, "now > redeemEnableTime"); - Locker storage locker = lockers[_beneficiary][_lockingId]; + Lock storage locker = lockers[_beneficiary][_lockingId]; uint256 periodToRedeemFrom = (locker.lockingTime - startTime) / periodsUnit; // solhint-disable-next-line not-rely-on-time uint256 currentLockingPeriod = (now - startTime) / periodsUnit; @@ -187,7 +188,7 @@ contract ContinuousLocking4Reputation is Agreement { lockingId = keccak256(abi.encodePacked(address(this), batchesCounter)); batchesCounter = batchesCounter.add(1); - Locker storage locker = lockers[msg.sender][lockingId]; + Lock storage locker = lockers[msg.sender][lockingId]; locker.amount = _amount; locker.period = _period; // solhint-disable-next-line not-rely-on-time @@ -210,7 +211,7 @@ contract ContinuousLocking4Reputation is Agreement { public onlyAgree(_agreementHash) { - Locker storage locker = lockers[msg.sender][_lockingId]; + Lock storage locker = lockers[msg.sender][_lockingId]; require(locker.lockingTime != 0, "wrong locking id"); uint256 lockingPeriodRemain = ((locker.lockingTime + (locker.period*periodsUnit) - startTime)/periodsUnit).sub(_lockingPeriodToLockIn); @@ -244,7 +245,7 @@ contract ContinuousLocking4Reputation is Agreement { * @return bool */ function release(address _beneficiary, bytes32 _lockingId) public returns(uint256 amount) { - Locker storage locker = lockers[_beneficiary][_lockingId]; + Lock storage locker = lockers[_beneficiary][_lockingId]; require(locker.amount > 0, "amount should be > 0"); amount = locker.amount; locker.amount = 0; From 123c5f279efd24252fcfa897c4f41757ea20918d Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Sun, 8 Sep 2019 20:58:56 +0300 Subject: [PATCH 12/13] nameing --- .../ContinuousLockingToken4Reputation.sol | 141 +++++++++--------- test/continuouslockingtoken4reputation.js | 8 +- 2 files changed, 74 insertions(+), 75 deletions(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index ce24fa83..fd12a79a 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -35,24 +35,23 @@ contract ContinuousLocking4Reputation is Agreement { uint256 period; } - // A mapping from lockers addresses their lock balances. + // A mapping from lockers addresses to their locks. mapping(address => mapping(bytes32=>Lock)) public lockers; - // A mapping from locking index to locking. - mapping(uint256=>Batch) public batches; + //A mapping from batch index to batch. + mapping(uint256 => Batch) public batches; Avatar public avatar; uint256 public reputationRewardLeft; uint256 public startTime; - uint256 public numberOfLockingPeriods; uint256 public redeemEnableTime; uint256 public maxLockingBatches; - uint256 public periodsUnit; + uint256 public batchTime; IERC20 public token; uint256 public batchesCounter; // Total number of batches uint256 public totalLockedLeft; uint256 public repRewardConstA; uint256 public repRewardConstB; - uint256 public periodsCap; + uint256 public batchesIndexCap; uint256 constant private REAL_FBITS = 40; /** @@ -60,7 +59,7 @@ contract ContinuousLocking4Reputation is Agreement { */ uint256 constant private REAL_ONE = uint256(1) << REAL_FBITS; - uint256 constant private PERIODS_HARDCAP = 100; + uint256 constant private BATCHES_INDEX_HARDCAP = 100; uint256 constant public MAX_LOCKING_BATCHES_HARDCAP = 24; /** @@ -68,16 +67,16 @@ contract ContinuousLocking4Reputation is Agreement { * @param _avatar the avatar to mint reputation from * @param _reputationReward the reputation reward per auction this contract will reward * for the token locking - * @param _startTime auctions period start time - * @param _periodsUnit locking periods units (e.g 30 days). + * @param _startTime locking period start time + * @param _batchTime batch time (e.g 30 days). * @param _redeemEnableTime redeem enable time . * redeem reputation can be done after this time. - * @param _maxLockingBatches - maximum number of locking periods (in _periodsUnit units) - * @param _repRewardConstA - reputation allocation per period is calculated by : - * _repRewardConstA * (_repRewardConstB ** periodNumber) - * @param _repRewardConstB - reputation allocation per period is calculated by : - * _repRewardConstA * (_repRewardConstB ** periodNumber) - * @param _periodsCap the max periods number to allow to lock in . this value capped by PERIODS_HARDCAP + * @param _maxLockingBatches - maximum number of locking batches (in _batchTime units) + * @param _repRewardConstA - reputation allocation per batch is calculated by : + * _repRewardConstA * (_repRewardConstB ** batchIndex) + * @param _repRewardConstB - reputation allocation per batch is calculated by : + * _repRewardConstA * (_repRewardConstB ** batchIndex) + * @param _batchesIndexCap the max batch index which allows to lock in . this value capped by BATCHES_HARDCAP * @param _token the locking token * @param _agreementHash is a hash of agreement required to be added to the TX by participants */ @@ -85,37 +84,37 @@ contract ContinuousLocking4Reputation is Agreement { Avatar _avatar, uint256 _reputationReward, uint256 _startTime, - uint256 _periodsUnit, + uint256 _batchTime, uint256 _redeemEnableTime, uint256 _maxLockingBatches, uint256 _repRewardConstA, uint256 _repRewardConstB, - uint256 _periodsCap, + uint256 _batchesIndexCap, IERC20 _token, bytes32 _agreementHash ) external { require(avatar == Avatar(0), "can be called only one time"); require(_avatar != Avatar(0), "avatar cannot be zero"); - //_periodsUnit should be greater than block interval - require(_periodsUnit > 15, "lockingPeriod should be > 15"); + //_batchTime should be greater than block interval + require(_batchTime > 15, "batchTime should be > 15"); require(_maxLockingBatches <= MAX_LOCKING_BATCHES_HARDCAP, "maxLockingBatches should be <= MAX_LOCKING_BATCHES_HARDCAP"); - require(_redeemEnableTime >= _startTime+_periodsUnit, - "_redeemEnableTime >= _startTime+_periodsUnit"); - require(_periodsCap <= PERIODS_HARDCAP, "_periodsCap > PERIODS_HARDCAP"); + require(_redeemEnableTime >= _startTime+_batchTime, + "_redeemEnableTime >= _startTime+_batchTime"); + require(_batchesIndexCap <= BATCHES_INDEX_HARDCAP, "_batchesIndexCap > BATCHES_INDEX_HARDCAP"); token = _token; avatar = _avatar; startTime = _startTime; reputationRewardLeft = _reputationReward; redeemEnableTime = _redeemEnableTime; maxLockingBatches = _maxLockingBatches; - periodsUnit = _periodsUnit; + batchTime = _batchTime; require(_repRewardConstB < 1000, "_repRewardConstB should be < 1000"); require(repRewardConstA < _reputationReward, "repRewardConstA should be < _reputationReward"); repRewardConstA = toReal(uint216(_repRewardConstA)); repRewardConstB = uint216(_repRewardConstB).fraction(uint216(1000)); - periodsCap = _periodsCap; + batchesIndexCap = _batchesIndexCap; super.setAgreementHash(_agreementHash); } @@ -129,17 +128,17 @@ contract ContinuousLocking4Reputation is Agreement { // solhint-disable-next-line not-rely-on-time require(now > redeemEnableTime, "now > redeemEnableTime"); Lock storage locker = lockers[_beneficiary][_lockingId]; - uint256 periodToRedeemFrom = (locker.lockingTime - startTime) / periodsUnit; + uint256 batchIndexToRedeemFrom = (locker.lockingTime - startTime) / batchTime; // solhint-disable-next-line not-rely-on-time - uint256 currentLockingPeriod = (now - startTime) / periodsUnit; - uint256 lastLockingPeriodToRedeem = currentLockingPeriod.min(periodToRedeemFrom + locker.period); - for (periodToRedeemFrom; periodToRedeemFrom < lastLockingPeriodToRedeem; periodToRedeemFrom++) { - Batch storage locking = batches[periodToRedeemFrom]; + uint256 currentBatch = (now - startTime) / batchTime; + uint256 lastBatchIndexToRedeem = currentBatch.min(batchIndexToRedeemFrom + locker.period); + for (batchIndexToRedeemFrom; batchIndexToRedeemFrom < lastBatchIndexToRedeem; batchIndexToRedeemFrom++) { + Batch storage locking = batches[batchIndexToRedeemFrom]; uint256 score = locking.scores[_beneficiary]; if (score > 0) { locking.scores[_beneficiary] = 0; - uint256 lockingPeriodReputationReward = repRewardPerPeriod(periodToRedeemFrom); - uint256 repRelation = mul(toReal(uint216(score)), lockingPeriodReputationReward); + uint256 batchReputationReward = repRewardPerBatch(batchIndexToRedeemFrom); + uint256 repRelation = mul(toReal(uint216(score)), batchReputationReward); reputation = reputation.add(div(repRelation, toReal(uint216(locking.totalScore)))); } } @@ -155,12 +154,12 @@ contract ContinuousLocking4Reputation is Agreement { /** * @dev lock function - * @param _amount the amount to bid with - * @param _period the period to lock. in periodsUnit. - * @param _lockingPeriodToLockIn the locking id to lock at . + * @param _amount the amount of token to lock + * @param _period the period to lock. in batchTime units + * @param _batchIndexToLockIn the locking id to lock in. * @return lockingId */ - function lock(uint256 _amount, uint256 _period, uint256 _lockingPeriodToLockIn, bytes32 _agreementHash) + function lock(uint256 _amount, uint256 _period, uint256 _batchIndexToLockIn, bytes32 _agreementHash) public onlyAgree(_agreementHash) returns(bytes32 lockingId) @@ -168,21 +167,21 @@ contract ContinuousLocking4Reputation is Agreement { require(_amount > 0, "locking amount should be > 0"); // solhint-disable-next-line not-rely-on-time require(now >= startTime, "locking is enable only after locking startTime"); - require(_period <= maxLockingBatches, "locking period exceed the maximum allowed"); - require(_period > 0, "locking period equal to zero"); - require((_lockingPeriodToLockIn + _period) <= periodsCap, "exceed max allowed periods"); + require(_period <= maxLockingBatches, "period exceed the maximum allowed"); + require(_period > 0, "period equal to zero"); + require((_batchIndexToLockIn + _period) <= batchesIndexCap, "exceed max allowed batches"); address(token).safeTransferFrom(msg.sender, address(this), _amount); // solhint-disable-next-line not-rely-on-time - uint256 lockingPeriodToLockIn = (now - startTime) / periodsUnit; - require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); + uint256 batchIndexToLockIn = (now - startTime) / batchTime; + require(batchIndexToLockIn == _batchIndexToLockIn, "locking is not active"); uint256 j = _period; - //fill in the next batche scores. - for (int256 i = int256(lockingPeriodToLockIn + _period - 1); i >= int256(lockingPeriodToLockIn); i--) { - Batch storage locking = batches[uint256(i)]; + //fill in the next batches scores. + for (int256 i = int256(batchIndexToLockIn + _period - 1); i >= int256(batchIndexToLockIn); i--) { + Batch storage batch = batches[uint256(i)]; uint256 score = (_period - j + 1) * _amount; j--; - locking.totalScore = locking.totalScore.add(score); - locking.scores[msg.sender] = score; + batch.totalScore = batch.totalScore.add(score); + batch.scores[msg.sender] = score; } lockingId = keccak256(abi.encodePacked(address(this), batchesCounter)); @@ -199,13 +198,13 @@ contract ContinuousLocking4Reputation is Agreement { /** * @dev extendLocking function - * @param _extendPeriod the period to extend the locking. in periodsUnit. - * @param _lockingPeriodToLockIn the locking id to lock at . + * @param _extendPeriod the period to extend the locking. in batchTime. + * @param _batchIndexToLockIn the locking id to lock at . * @param _lockingId the locking id to extend */ function extendLocking( uint256 _extendPeriod, - uint256 _lockingPeriodToLockIn, + uint256 _batchIndexToLockIn, bytes32 _lockingId, bytes32 _agreementHash) public @@ -213,26 +212,26 @@ contract ContinuousLocking4Reputation is Agreement { { Lock storage locker = lockers[msg.sender][_lockingId]; require(locker.lockingTime != 0, "wrong locking id"); - uint256 lockingPeriodRemain = - ((locker.lockingTime + (locker.period*periodsUnit) - startTime)/periodsUnit).sub(_lockingPeriodToLockIn); - uint256 extendPeriodsFromNow = lockingPeriodRemain + _extendPeriod; - require(extendPeriodsFromNow <= maxLockingBatches, "locking period exceed the maximum allowed"); + uint256 remainBatches = + ((locker.lockingTime + (locker.period*batchTime) - startTime)/batchTime).sub(_batchIndexToLockIn); + uint256 batchesCountFromCurrent = remainBatches + _extendPeriod; + require(batchesCountFromCurrent <= maxLockingBatches, "locking period exceed the maximum allowed"); require(_extendPeriod > 0, "extend locking period equal to zero"); - require((_lockingPeriodToLockIn + extendPeriodsFromNow) <= periodsCap, - "exceed max allowed periods"); + require((_batchIndexToLockIn + batchesCountFromCurrent) <= batchesIndexCap, + "exceed max allowed batches"); // solhint-disable-next-line not-rely-on-time - uint256 lockingPeriodToLockIn = (now - startTime) / periodsUnit; - require(lockingPeriodToLockIn == _lockingPeriodToLockIn, "locking is not active"); - uint256 j = extendPeriodsFromNow; + uint256 batchIndexToLockIn = (now - startTime) / batchTime; + require(batchIndexToLockIn == _batchIndexToLockIn, "locking is not active"); + uint256 j = batchesCountFromCurrent; //fill in the next batche scores. - for (int256 i = int256(lockingPeriodToLockIn + extendPeriodsFromNow - 1); - i >= int256(lockingPeriodToLockIn); + for (int256 i = int256(batchIndexToLockIn + batchesCountFromCurrent - 1); + i >= int256(batchIndexToLockIn); i--) { - Batch storage locking = batches[uint256(i)]; - uint256 score = (extendPeriodsFromNow - j + 1) * locker.amount; + Batch storage batch = batches[uint256(i)]; + uint256 score = (batchesCountFromCurrent - j + 1) * locker.amount; j--; - locking.totalScore = locking.totalScore.add(score).sub(locking.scores[msg.sender]); - locking.scores[msg.sender] = score; + batch.totalScore = batch.totalScore.add(score).sub(batch.scores[msg.sender]); + batch.scores[msg.sender] = score; } locker.period = locker.period + _extendPeriod; emit ExtendLocking(msg.sender, _lockingId, _extendPeriod); @@ -250,23 +249,23 @@ contract ContinuousLocking4Reputation is Agreement { amount = locker.amount; locker.amount = 0; // solhint-disable-next-line not-rely-on-time - require(block.timestamp > locker.lockingTime + (locker.period*periodsUnit), - "check the lock period pass"); + require(block.timestamp > locker.lockingTime + (locker.period*batchTime), + "locking period not passed"); totalLockedLeft = totalLockedLeft.sub(amount); address(token).safeTransfer(_beneficiary, amount); emit Release(_lockingId, _beneficiary, amount); } /** - * @dev repRewardPerPeriod function + * @dev repRewardPerBatch function * the calculation is done the following formula: - * RepReward = repRewardConstA * (repRewardConstB**_periodNumber) - * @param _periodNumber the period number to calc rep reward of + * RepReward = repRewardConstA * (repRewardConstB**_batchIndex) + * @param _batchIndex the batch number to calc rep reward of * @return repReward */ - function repRewardPerPeriod(uint256 _periodNumber) public view returns(uint256 repReward) { - if (_periodNumber <= periodsCap) { - repReward = mul(repRewardConstA, repRewardConstB.pow(_periodNumber)); + function repRewardPerBatch(uint256 _batchIndex) public view returns(uint256 repReward) { + if (_batchIndex <= batchesIndexCap) { + repReward = mul(repRewardConstA, repRewardConstB.pow(_batchIndex)); } } diff --git a/test/continuouslockingtoken4reputation.js b/test/continuouslockingtoken4reputation.js index 79add6ea..7391e3bf 100644 --- a/test/continuouslockingtoken4reputation.js +++ b/test/continuouslockingtoken4reputation.js @@ -63,9 +63,9 @@ contract('ContinuousLocking4Reputation', accounts => { assert.equal(await testSetup.continuousLocking4Reputation.startTime(),testSetup.startTime); assert.equal(await testSetup.continuousLocking4Reputation.redeemEnableTime(),testSetup.redeemEnableTime); assert.equal(await testSetup.continuousLocking4Reputation.token(),testSetup.lockingToken.address); - assert.equal(await testSetup.continuousLocking4Reputation.periodsUnit(),testSetup.periodsUnit); + assert.equal(await testSetup.continuousLocking4Reputation.batchTime(),testSetup.periodsUnit); assert.equal(await testSetup.continuousLocking4Reputation.getAgreementHash(),testSetup.agreementHash); - assert.equal(await testSetup.continuousLocking4Reputation.periodsCap(),testSetup.periodsCap); + assert.equal(await testSetup.continuousLocking4Reputation.batchesIndexCap(),testSetup.periodsCap); }); it("initialize periodsUnit <= 15 seconds is not allowed", async () => { @@ -385,11 +385,11 @@ contract('ContinuousLocking4Reputation', accounts => { it("redeem reward limits 100 periods", async () => { let testSetup = await setup(accounts); - var repForPeriod = await testSetup.continuousLocking4Reputation.repRewardPerPeriod(100); + var repForPeriod = await testSetup.continuousLocking4Reputation.repRewardPerBatch(100); var REAL_FBITS = 40; var res = (repForPeriod.shrn(REAL_FBITS).toNumber() + (repForPeriod.maskn(REAL_FBITS)/Math.pow(2,REAL_FBITS))).toFixed(2); assert.equal(Math.floor(res),Math.floor(testSetup.repRewardConstA* Math.pow(testSetup.repRewardConstB/1000,100))); - assert.equal(await testSetup.continuousLocking4Reputation.repRewardPerPeriod(101),0); + assert.equal(await testSetup.continuousLocking4Reputation.repRewardPerBatch(101),0); }); it("redeem limits 100 periods", async () => { From 3708ee4a52d78466c17781c90dffa7ccb0dd4bfb Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Sun, 8 Sep 2019 21:03:15 +0300 Subject: [PATCH 13/13] comment --- contracts/schemes/ContinuousLockingToken4Reputation.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLockingToken4Reputation.sol index fd12a79a..a6edc6c3 100644 --- a/contracts/schemes/ContinuousLockingToken4Reputation.sol +++ b/contracts/schemes/ContinuousLockingToken4Reputation.sol @@ -65,7 +65,7 @@ contract ContinuousLocking4Reputation is Agreement { /** * @dev initialize * @param _avatar the avatar to mint reputation from - * @param _reputationReward the reputation reward per auction this contract will reward + * @param _reputationReward the reputation reward per locking batch that this contract will reward * for the token locking * @param _startTime locking period start time * @param _batchTime batch time (e.g 30 days).