From b6e63414baa1419c4edac9869a09798cb315c5d2 Mon Sep 17 00:00:00 2001 From: orenyodfat Date: Tue, 27 Nov 2018 15:17:05 +0200 Subject: [PATCH] lockingtoken4reputation.sol : multi token support (#563) * lockingtoken4reputation.sol : multi token support * use truffle "5.0.0-beta.1" --- .../schemes/ExternalLocking4Reputation.sol | 4 +- contracts/schemes/Locking4Reputation.sol | 7 +- contracts/schemes/LockingEth4Reputation.sol | 2 +- contracts/schemes/LockingToken4Reputation.sol | 39 +++++++--- contracts/schemes/PriceOracleInterface.sol | 7 ++ contracts/test/PriceOracleMock.sol | 24 ++++++ package.json | 2 +- test/lockingtoken4reputation.js | 78 +++++++++++++------ 8 files changed, 126 insertions(+), 37 deletions(-) create mode 100644 contracts/schemes/PriceOracleInterface.sol create mode 100644 contracts/test/PriceOracleMock.sol diff --git a/contracts/schemes/ExternalLocking4Reputation.sol b/contracts/schemes/ExternalLocking4Reputation.sol index dd004bbb..b33c18ea 100644 --- a/contracts/schemes/ExternalLocking4Reputation.sol +++ b/contracts/schemes/ExternalLocking4Reputation.sol @@ -72,7 +72,7 @@ contract ExternalLocking4Reputation is Locking4Reputation, Ownable { require(registrar[_beneficiary],"beneficiary should be register"); beneficiary = _beneficiary; } - require(externalLockers[beneficiary] == false, "claiming twice is not allowed"); + require(externalLockers[beneficiary] == false, "claiming twice for the same beneficiary is not allowed"); externalLockers[beneficiary] = true; // solium-disable-next-line security/no-low-level-calls bool result = externalLockingContract.call(abi.encodeWithSignature(getBalanceFuncSignature, beneficiary)); @@ -86,7 +86,7 @@ contract ExternalLocking4Reputation is Locking4Reputation, Ownable { default { lockedAmount := mload(0) } } - return super._lock(lockedAmount, 1, beneficiary); + return super._lock(lockedAmount, 1, beneficiary,1,1); } /** diff --git a/contracts/schemes/Locking4Reputation.sol b/contracts/schemes/Locking4Reputation.sol index 5c4f8111..f534d82d 100644 --- a/contracts/schemes/Locking4Reputation.sol +++ b/contracts/schemes/Locking4Reputation.sol @@ -86,9 +86,11 @@ contract Locking4Reputation { * @param _amount the amount to lock * @param _period the locking period * @param _locker the locker + * @param _numerator price numerator + * @param _denominator price denominator * @return lockingId */ - function _lock(uint _amount, uint _period, address _locker) internal returns(bytes32 lockingId) { + function _lock(uint _amount, uint _period, address _locker,uint _numerator,uint _denominator) internal returns(bytes32 lockingId) { require(_amount > 0, "locking amount should be > 0"); require(_period <= maxLockingPeriod, "locking period should be <= maxLockingPeriod"); require(_period > 0, "locking period should be > 0"); @@ -106,7 +108,8 @@ contract Locking4Reputation { locker.releaseTime = now + _period; totalLocked += _amount; totalLockedLeft = totalLocked; - uint score = _period.mul(_amount); + uint score = _period.mul(_amount).mul(_numerator).div(_denominator); + require(score>0,"score must me > 0"); scores[_locker] = scores[_locker].add(score); totalScore = totalScore.add(score); diff --git a/contracts/schemes/LockingEth4Reputation.sol b/contracts/schemes/LockingEth4Reputation.sol index 29f92367..4fb4eb2f 100644 --- a/contracts/schemes/LockingEth4Reputation.sol +++ b/contracts/schemes/LockingEth4Reputation.sol @@ -60,7 +60,7 @@ contract LockingEth4Reputation is Locking4Reputation, Ownable { * @return lockingId the unique Id */ function lock(uint _period) public payable returns(bytes32 lockingId) { - return super._lock(msg.value, _period, msg.sender); + return super._lock(msg.value, _period, msg.sender,1,1); } } diff --git a/contracts/schemes/LockingToken4Reputation.sol b/contracts/schemes/LockingToken4Reputation.sol index a63011ff..b3c669b5 100644 --- a/contracts/schemes/LockingToken4Reputation.sol +++ b/contracts/schemes/LockingToken4Reputation.sol @@ -1,7 +1,9 @@ pragma solidity ^0.4.25; import "./Locking4Reputation.sol"; +import "./PriceOracleInterface.sol"; import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/StandardToken.sol"; /** @@ -9,7 +11,12 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; */ contract LockingToken4Reputation is Locking4Reputation, Ownable { - StandardToken public token; + + PriceOracleInterface public priceOracleContract; + // lockingId => token + mapping(bytes32 => StandardToken) public lockedTokens; + + event LockToken(bytes32 indexed _lockingId, address indexed _token, uint _numerator, uint _denominator); /** * @dev initialize @@ -22,7 +29,8 @@ contract LockingToken4Reputation is Locking4Reputation, Ownable { * @param _redeemEnableTime redeem enable time . * redeem reputation can be done after this time. * @param _maxLockingPeriod maximum locking period allowed. - * @param _token the locking token + * @param _priceOracleContract the price oracle contract which the locked token will be + * validated against */ function initialize( Avatar _avatar, @@ -31,11 +39,11 @@ contract LockingToken4Reputation is Locking4Reputation, Ownable { uint _lockingEndTime, uint _redeemEnableTime, uint _maxLockingPeriod, - StandardToken _token) + PriceOracleInterface _priceOracleContract) external onlyOwner { - token = _token; + priceOracleContract = _priceOracleContract; super._initialize( _avatar, _reputationReward, @@ -53,7 +61,7 @@ contract LockingToken4Reputation is Locking4Reputation, Ownable { */ function release(address _beneficiary,bytes32 _lockingId) public returns(bool) { uint amount = super._release(_beneficiary, _lockingId); - require(token.transfer(_beneficiary, amount), "transfer should success"); + require(lockedTokens[_lockingId].transfer(_beneficiary, amount), "transfer should success"); return true; } @@ -62,12 +70,25 @@ contract LockingToken4Reputation is Locking4Reputation, Ownable { * @dev lock function * @param _amount the amount to lock * @param _period the locking period + * @param _token the token to lock - this should be whitelisted at the priceOracleContract * @return lockingId */ - function lock(uint _amount, uint _period) public returns(bytes32) { - require(token.transferFrom(msg.sender, address(this), _amount), "transferFrom should success"); + function lock(uint _amount, uint _period,StandardToken _token) public returns(bytes32 lockingId) { - return super._lock(_amount, _period, msg.sender); - } + uint numerator; + uint denominator; + + (numerator,denominator) = priceOracleContract.getPrice(address(_token)); + + require(numerator > 0,"numerator should be > 0"); + require(denominator > 0,"denominator should be > 0"); + require(_token.transferFrom(msg.sender, address(this), _amount), "transferFrom should success"); + + lockingId = super._lock(_amount, _period, msg.sender,numerator,denominator); + + lockedTokens[lockingId] = _token; + + emit LockToken(lockingId,address(_token),numerator,denominator); + } } diff --git a/contracts/schemes/PriceOracleInterface.sol b/contracts/schemes/PriceOracleInterface.sol new file mode 100644 index 00000000..ad59145d --- /dev/null +++ b/contracts/schemes/PriceOracleInterface.sol @@ -0,0 +1,7 @@ +pragma solidity ^0.4.25; + +interface PriceOracleInterface { + + function getPrice(address token) external view returns (uint, uint); + +} diff --git a/contracts/test/PriceOracleMock.sol b/contracts/test/PriceOracleMock.sol new file mode 100644 index 00000000..d770d3f9 --- /dev/null +++ b/contracts/test/PriceOracleMock.sol @@ -0,0 +1,24 @@ +pragma solidity ^0.4.25; + +import "../schemes/PriceOracleInterface.sol"; + + +contract PriceOracleMock is PriceOracleInterface { + + struct Price { + uint numerator; + uint denominator; + } + // user => amount + mapping (address => Price) public tokenPrices; + + + function getPrice(address token) public view returns (uint, uint) { + Price memory price = tokenPrices[token]; + return (price.numerator, price.denominator); + } + + function setTokenPrice(address token,uint numerator,uint denominator) public { + tokenPrices[token] = Price(numerator,denominator); + } +} diff --git a/package.json b/package.json index 49e149b3..8c802c1c 100644 --- a/package.json +++ b/package.json @@ -55,7 +55,7 @@ "solc": "^0.4.24", "solc-cli": "^0.3.0", "solium": "^1.1.8", - "truffle": "beta", + "truffle": "5.0.0-beta.1", "uint32": "^0.2.1" }, "repository": { diff --git a/test/lockingtoken4reputation.js b/test/lockingtoken4reputation.js index 477c1b5d..4f065ebf 100644 --- a/test/lockingtoken4reputation.js +++ b/test/lockingtoken4reputation.js @@ -4,6 +4,7 @@ const ControllerCreator = artifacts.require("./ControllerCreator.sol"); const constants = require('./constants'); const StandardTokenMock = artifacts.require('./test/StandardTokenMock.sol'); var LockingToken4Reputation = artifacts.require("./LockingToken4Reputation.sol"); +const PriceOracleMock = artifacts.require('./test/PriceOracleMock.sol'); const setup = async function (accounts, _repAllocation = 100, @@ -14,6 +15,7 @@ const setup = async function (accounts, _initialize = true) { var testSetup = new helpers.TestSetup(); testSetup.lockingToken = await StandardTokenMock.new(accounts[0], web3.utils.toWei('100', "ether")); + testSetup.lockingToken2 = await StandardTokenMock.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); @@ -22,6 +24,10 @@ const setup = async function (accounts, testSetup.redeemEnableTime = (await web3.eth.getBlock("latest")).timestamp + _redeemEnableTime; testSetup.lockingToken4Reputation = await LockingToken4Reputation.new(); + testSetup.priceOracleMock = await PriceOracleMock.new(); + await testSetup.priceOracleMock.setTokenPrice(testSetup.lockingToken.address,100,4); + await testSetup.priceOracleMock.setTokenPrice(testSetup.lockingToken2.address,200,4); + if (_initialize === true) { await testSetup.lockingToken4Reputation.initialize(testSetup.org.avatar.address, _repAllocation, @@ -29,7 +35,7 @@ const setup = async function (accounts, testSetup.lockingEndTime, testSetup.redeemEnableTime, _maxLockingPeriod, - testSetup.lockingToken.address); + testSetup.priceOracleMock.address); } var permissions = "0x00000000"; @@ -45,29 +51,35 @@ contract('LockingToken4Reputation', accounts => { assert.equal(await testSetup.lockingToken4Reputation.maxLockingPeriod(),6000); assert.equal(await testSetup.lockingToken4Reputation.lockingEndTime(),testSetup.lockingEndTime); assert.equal(await testSetup.lockingToken4Reputation.redeemEnableTime(),testSetup.redeemEnableTime); - assert.equal(await testSetup.lockingToken4Reputation.token(),testSetup.lockingToken.address); + assert.equal(await testSetup.lockingToken4Reputation.priceOracleContract(),testSetup.priceOracleMock.address); }); it("lock", async () => { let testSetup = await setup(accounts); - var tx = await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + var tx = await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); var lockingId = await helpers.getValueFromLogs(tx, '_lockingId',1); - assert.equal(tx.logs.length,1); + assert.equal(tx.logs.length,2); assert.equal(tx.logs[0].event,"Lock"); 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._period,100); assert.equal(tx.logs[0].args._locker,accounts[0]); - assert.equal(await testSetup.lockingToken4Reputation.totalScore(),100*web3.utils.toWei('1', "ether")); - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); - assert.equal(await testSetup.lockingToken4Reputation.totalScore(),2*100*web3.utils.toWei('1', "ether")); + assert.equal(tx.logs[1].event,"LockToken"); + assert.equal(tx.logs[1].args._lockingId,lockingId); + assert.equal(tx.logs[1].args._token,testSetup.lockingToken.address); + assert.equal(tx.logs[1].args._numerator,100); + assert.equal(tx.logs[1].args._denominator,4); + + assert.equal(await testSetup.lockingToken4Reputation.totalScore(),100*web3.utils.toWei('1', "ether")*100/4); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); + assert.equal(await testSetup.lockingToken4Reputation.totalScore(),2*100*web3.utils.toWei('1', "ether")*100/4); }); it("cannot lock without initialize", async () => { let testSetup = await setup(accounts,100,0,3000,3000,6000,false); try { - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); assert(false, "cannot lock without initialize"); } catch(error) { helpers.assertVMException(error); @@ -77,18 +89,40 @@ contract('LockingToken4Reputation', accounts => { it("lock with value == 0 should revert", async () => { let testSetup = await setup(accounts); try { - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('0', "ether"),100); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('0', "ether"),100,testSetup.lockingToken.address); assert(false, "lock with value == 0 should revert"); } catch(error) { helpers.assertVMException(error); } }); + it("numerator == 0 should revert", async () => { + let testSetup = await setup(accounts); + await testSetup.priceOracleMock.setTokenPrice(testSetup.lockingToken.address,0,4); + try { + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); + assert(false, "numerator == 0 should revert"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("denominator == 0 should revert", async () => { + let testSetup = await setup(accounts); + await testSetup.priceOracleMock.setTokenPrice(testSetup.lockingToken.address,100,0); + try { + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); + assert(false, "denominator == 0 should revert"); + } catch(error) { + helpers.assertVMException(error); + } + }); + it("lock after _lockingEndTime should revert", async () => { let testSetup = await setup(accounts); await helpers.increaseTime(3001); try { - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); assert(false, "lock after _lockingEndTime should revert"); } catch(error) { helpers.assertVMException(error); @@ -98,7 +132,7 @@ contract('LockingToken4Reputation', accounts => { it("lock before start should revert", async () => { let testSetup = await setup(accounts,100,100); try { - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),0); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),0,testSetup.lockingToken.address); assert(false, "lock before start should revert"); } catch(error) { helpers.assertVMException(error); @@ -108,7 +142,7 @@ contract('LockingToken4Reputation', accounts => { it("lock with period == 0 should revert", async () => { let testSetup = await setup(accounts); try { - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),0); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),0,testSetup.lockingToken.address); assert(false, "lock with period == 0 should revert"); } catch(error) { helpers.assertVMException(error); @@ -118,7 +152,7 @@ contract('LockingToken4Reputation', accounts => { it("lock over _maxLockingPeriod should revert", async () => { let testSetup = await setup(accounts); try { - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),6001); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),6001,testSetup.lockingToken.address); assert(false, "lock over _maxLockingPeriod should revert"); } catch(error) { helpers.assertVMException(error); @@ -127,7 +161,7 @@ contract('LockingToken4Reputation', accounts => { it("release", async () => { let testSetup = await setup(accounts); - var tx = await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + var tx = await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); var lockingId = await helpers.getValueFromLogs(tx, '_lockingId',1); await helpers.increaseTime(101); tx = await testSetup.lockingToken4Reputation.release(accounts[0],lockingId); @@ -140,7 +174,7 @@ contract('LockingToken4Reputation', accounts => { it("release before locking period should revert", async () => { let testSetup = await setup(accounts); - var tx = await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + var tx = await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); var lockingId = await helpers.getValueFromLogs(tx, '_lockingId',1); try { await testSetup.lockingToken4Reputation.release(accounts[0],lockingId); @@ -152,7 +186,7 @@ contract('LockingToken4Reputation', accounts => { it("release cannot release twice", async () => { let testSetup = await setup(accounts); - var tx = await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + var tx = await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); var lockingId = await helpers.getValueFromLogs(tx, '_lockingId',1); await helpers.increaseTime(101); await testSetup.lockingToken4Reputation.release(accounts[0],lockingId); @@ -166,7 +200,7 @@ contract('LockingToken4Reputation', accounts => { it("redeem", async () => { let testSetup = await setup(accounts); - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); await helpers.increaseTime(3001); var tx = await testSetup.lockingToken4Reputation.redeem(accounts[0]); assert.equal(tx.logs.length,1); @@ -178,10 +212,10 @@ contract('LockingToken4Reputation', accounts => { it("redeem score ", async () => { let testSetup = await setup(accounts); - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100 ,{from:accounts[0]}); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100 ,testSetup.lockingToken.address,{from:accounts[0]}); await testSetup.lockingToken.transfer(accounts[1],web3.utils.toWei('1', "ether")); await testSetup.lockingToken.approve(testSetup.lockingToken4Reputation.address,web3.utils.toWei('100', "ether"),{from:accounts[1]}); - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),300 ,{from:accounts[1]}); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),300,testSetup.lockingToken.address,{from:accounts[1]}); await helpers.increaseTime(3001); await testSetup.lockingToken4Reputation.redeem(accounts[0]); await testSetup.lockingToken4Reputation.redeem(accounts[1]); @@ -191,7 +225,7 @@ contract('LockingToken4Reputation', accounts => { it("redeem cannot redeem twice", async () => { let testSetup = await setup(accounts); - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); await helpers.increaseTime(3001); await testSetup.lockingToken4Reputation.redeem(accounts[0]); try { @@ -204,7 +238,7 @@ contract('LockingToken4Reputation', accounts => { it("redeem before lockingEndTime should revert", async () => { let testSetup = await setup(accounts); - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); await helpers.increaseTime(50); try { await testSetup.lockingToken4Reputation.redeem(accounts[0]); @@ -216,7 +250,7 @@ contract('LockingToken4Reputation', accounts => { it("redeem before redeemEnableTime should revert", async () => { let testSetup = await setup(accounts,100,0,3000,4000,6000,true); - await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100); + await testSetup.lockingToken4Reputation.lock(web3.utils.toWei('1', "ether"),100,testSetup.lockingToken.address); await helpers.increaseTime(3500); try { await testSetup.lockingToken4Reputation.redeem(accounts[0]);