From f143fae5ae47c0a7ca158ac5de17a22214e0cc40 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 1 May 2019 16:20:50 +0300 Subject: [PATCH 1/3] add ReputationFromToken contract --- contracts/schemes/ReputationFromToken.sol | 53 ++++++++++++++++ contracts/test/ExternalTokenLockerMock.sol | 6 +- test/reputationfromtoken.js | 70 ++++++++++++++++++++++ 3 files changed, 128 insertions(+), 1 deletion(-) create mode 100644 contracts/schemes/ReputationFromToken.sol create mode 100644 test/reputationfromtoken.js diff --git a/contracts/schemes/ReputationFromToken.sol b/contracts/schemes/ReputationFromToken.sol new file mode 100644 index 00000000..917f9d75 --- /dev/null +++ b/contracts/schemes/ReputationFromToken.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.5.4; + +import "../Controller/ControllerInterface.sol"; +/** + * @title A scheme for reputation allocation according to token balances + */ + +contract ReputationFromToken { + + address public tokenContract; + // beneficiary -> bool + mapping(address => bool) public redeems; + Avatar public avatar; + + event Redeem(address indexed _beneficiary, address indexed _sender, uint256 _amount); + + /** + * @dev initialize + * @param _avatar the avatar to mint reputation from + * @param _tokenContract the token contract + */ + function initialize(Avatar _avatar, address _tokenContract) external + { + require(avatar == Avatar(0), "can be called only one time"); + require(_avatar != Avatar(0), "avatar cannot be zero"); + tokenContract = _tokenContract; + avatar = _avatar; + } + + /** + * @dev redeem function + * @param _beneficiary the beneficiary address to redeem for + */ + function redeem(address _beneficiary) public { + require(avatar != Avatar(0), "should initialize first"); + require(redeems[msg.sender] == false, "redeeming twice from the same account is not allowed"); + redeems[msg.sender] = true; + (bool result, bytes memory returnValue) = + // solhint-disable-next-line avoid-call-value,avoid-low-level-calls + tokenContract.call(abi.encodeWithSignature("balanceOf(address)", msg.sender)); + require(result, "call to external contract should succeed"); + uint256 tokenAmount; + // solhint-disable-next-line no-inline-assembly + assembly { + tokenAmount := mload(add(returnValue, 0x20)) + } + require( + ControllerInterface( + avatar.owner()) + .mintReputation(tokenAmount, _beneficiary, address(avatar)), "mint reputation should succeed"); + emit Redeem(_beneficiary, msg.sender, tokenAmount); + } +} diff --git a/contracts/test/ExternalTokenLockerMock.sol b/contracts/test/ExternalTokenLockerMock.sol index 96e87e04..775341aa 100644 --- a/contracts/test/ExternalTokenLockerMock.sol +++ b/contracts/test/ExternalTokenLockerMock.sol @@ -5,9 +5,13 @@ import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; contract ExternalTokenLockerMock is Ownable { // user => amount - mapping (address => uint) public lockedTokenBalances; + mapping (address => uint256) public lockedTokenBalances; function lock(uint256 _amount, address _beneficiary) public onlyOwner { lockedTokenBalances[_beneficiary] = _amount; } + + function balanceOf(address _beneficiary) public view returns(uint256) { + return lockedTokenBalances[_beneficiary]; + } } diff --git a/test/reputationfromtoken.js b/test/reputationfromtoken.js new file mode 100644 index 00000000..07210e9e --- /dev/null +++ b/test/reputationfromtoken.js @@ -0,0 +1,70 @@ +const helpers = require('./helpers'); +const DaoCreator = artifacts.require("./DaoCreator.sol"); +const ControllerCreator = artifacts.require("./ControllerCreator.sol"); +const constants = require('./constants'); +var ReputationFromToken = artifacts.require("./ReputationFromToken.sol"); +var ExternalTokenLockerMock = artifacts.require("./ExternalTokenLockerMock.sol"); + +const setup = async function (accounts, _initialize = true) { + var testSetup = new helpers.TestSetup(); + 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.extetnalTokenLockerMock = await ExternalTokenLockerMock.new(); + await testSetup.extetnalTokenLockerMock.lock(100,accounts[0]); + await testSetup.extetnalTokenLockerMock.lock(200,accounts[1]); + await testSetup.extetnalTokenLockerMock.lock(300,accounts[2]); + + testSetup.reputationFromToken = await ReputationFromToken.new(); + if (_initialize === true) { + await testSetup.reputationFromToken.initialize(testSetup.org.avatar.address, + testSetup.extetnalTokenLockerMock.address); + } + + var permissions = "0x00000000"; + await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address,[testSetup.reputationFromToken.address],[helpers.NULL_HASH],[permissions],"metaData"); + return testSetup; +}; + +contract('ReputationFromToken', accounts => { + it("initialize", async () => { + let testSetup = await setup(accounts); + assert.equal(await testSetup.reputationFromToken.tokenContract(),testSetup.extetnalTokenLockerMock.address); + assert.equal(await testSetup.reputationFromToken.avatar(),testSetup.org.avatar.address); + }); + + it("externalLockingMock is onlyOwner", async () => { + let testSetup = await setup(accounts); + try { + await testSetup.extetnalTokenLockerMock.lock(1030,accounts[3],{from:accounts[1]}); + assert(false, "externalLockingMock is onlyOwner"); + } catch(error) { + helpers.assertVMException(error); + } + + }); + + it("redeem", async () => { + let testSetup = await setup(accounts); + var tx = await testSetup.reputationFromToken.redeem(accounts[1]); + assert.equal(tx.logs.length,1); + assert.equal(tx.logs[0].event,"Redeem"); + assert.equal(tx.logs[0].args._beneficiary,accounts[1]); + assert.equal(tx.logs[0].args._amount,100); + assert.equal(tx.logs[0].args._sender,accounts[0]); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[0]),1000); + assert.equal(await testSetup.org.reputation.balanceOf(accounts[1]),100); + }); + + it("cannot initialize twice", async () => { + let testSetup = await setup(accounts); + try { + await testSetup.reputationFromToken.initialize(testSetup.org.avatar.address, + testSetup.extetnalTokenLockerMock.address, + ); + assert(false, "cannot initialize twice"); + } catch(error) { + helpers.assertVMException(error); + } + }); +}); From 4d67486e0b42103de9b436985f9364eca6ef174e Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 1 May 2019 17:11:06 +0300 Subject: [PATCH 2/3] case sensativity fix --- contracts/schemes/ReputationFromToken.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/contracts/schemes/ReputationFromToken.sol b/contracts/schemes/ReputationFromToken.sol index 917f9d75..97a7c228 100644 --- a/contracts/schemes/ReputationFromToken.sol +++ b/contracts/schemes/ReputationFromToken.sol @@ -1,6 +1,6 @@ pragma solidity ^0.5.4; -import "../Controller/ControllerInterface.sol"; +import "../controller/ControllerInterface.sol"; /** * @title A scheme for reputation allocation according to token balances */ From f3cfdc4f5e91321ab96749e718b163da6c0024a5 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Wed, 1 May 2019 17:17:57 +0300 Subject: [PATCH 3/3] use IERC20 --- contracts/schemes/ReputationFromToken.sol | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/contracts/schemes/ReputationFromToken.sol b/contracts/schemes/ReputationFromToken.sol index 97a7c228..20e9ce23 100644 --- a/contracts/schemes/ReputationFromToken.sol +++ b/contracts/schemes/ReputationFromToken.sol @@ -1,13 +1,15 @@ pragma solidity ^0.5.4; import "../controller/ControllerInterface.sol"; +import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol"; + /** * @title A scheme for reputation allocation according to token balances */ contract ReputationFromToken { - address public tokenContract; + IERC20 public tokenContract; // beneficiary -> bool mapping(address => bool) public redeems; Avatar public avatar; @@ -19,7 +21,7 @@ contract ReputationFromToken { * @param _avatar the avatar to mint reputation from * @param _tokenContract the token contract */ - function initialize(Avatar _avatar, address _tokenContract) external + function initialize(Avatar _avatar, IERC20 _tokenContract) external { require(avatar == Avatar(0), "can be called only one time"); require(_avatar != Avatar(0), "avatar cannot be zero"); @@ -35,15 +37,7 @@ contract ReputationFromToken { require(avatar != Avatar(0), "should initialize first"); require(redeems[msg.sender] == false, "redeeming twice from the same account is not allowed"); redeems[msg.sender] = true; - (bool result, bytes memory returnValue) = - // solhint-disable-next-line avoid-call-value,avoid-low-level-calls - tokenContract.call(abi.encodeWithSignature("balanceOf(address)", msg.sender)); - require(result, "call to external contract should succeed"); - uint256 tokenAmount; - // solhint-disable-next-line no-inline-assembly - assembly { - tokenAmount := mload(add(returnValue, 0x20)) - } + uint256 tokenAmount = tokenContract.balanceOf(msg.sender); require( ControllerInterface( avatar.owner())