diff --git a/contracts/schemes/ReputationFromToken.sol b/contracts/schemes/ReputationFromToken.sol new file mode 100644 index 00000000..20e9ce23 --- /dev/null +++ b/contracts/schemes/ReputationFromToken.sol @@ -0,0 +1,47 @@ +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 { + + IERC20 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, IERC20 _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; + uint256 tokenAmount = tokenContract.balanceOf(msg.sender); + 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); + } + }); +});