diff --git a/contracts/schemes/ReputationFromToken.sol b/contracts/schemes/ReputationFromToken.sol index dd4b40f9..4c0d43a4 100644 --- a/contracts/schemes/ReputationFromToken.sol +++ b/contracts/schemes/ReputationFromToken.sol @@ -36,8 +36,9 @@ contract ReputationFromToken { /** * @dev redeem function * @param _beneficiary the beneficiary address to redeem for + * @return uint256 minted reputation */ - function redeem(address _beneficiary) public { + function redeem(address _beneficiary) public returns(uint256) { 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; @@ -53,5 +54,6 @@ contract ReputationFromToken { avatar.owner()) .mintReputation(tokenAmount, _beneficiary, address(avatar)), "mint reputation should succeed"); emit Redeem(_beneficiary, msg.sender, tokenAmount); + return tokenAmount; } } diff --git a/contracts/utils/RepAllocation.sol b/contracts/utils/RepAllocation.sol new file mode 100644 index 00000000..1f0982c0 --- /dev/null +++ b/contracts/utils/RepAllocation.sol @@ -0,0 +1,61 @@ +pragma solidity ^0.5.4; + +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; + + +/** + * @title reputation allocation contract + * This scheme can be used to allocate a pre define amount of reputation to whitelisted + * beneficiaries. + * this contract can be used as the rep mapping contract for RepitationFromToken contract. + */ +contract RepAllocation is Ownable { + + + // beneficiary -> amount + mapping(address => uint256) public reputationAllocations; + bool public isFreeze; + + event BeneficiaryAddressAdded(address indexed _beneficiary, uint256 indexed _amount); + + /** + * @dev addBeneficiary function + * @param _beneficiary to be whitelisted + */ + function addBeneficiary(address _beneficiary, uint256 _amount) public onlyOwner { + require(!isFreeze, "can add beneficiary only if not disable"); + + if (reputationAllocations[_beneficiary] == 0) { + reputationAllocations[_beneficiary] = _amount; + emit BeneficiaryAddressAdded(_beneficiary, _amount); + } + } + + /** + * @dev add addBeneficiaries function + * @param _beneficiaries addresses + */ + function addBeneficiaries(address[] memory _beneficiaries, uint256[] memory _amounts) public onlyOwner { + require(_beneficiaries.length == _amounts.length); + for (uint256 i = 0; i < _beneficiaries.length; i++) { + addBeneficiary(_beneficiaries[i], _amounts[i]); + } + } + + /** + * @dev freeze function + * cannot defreeze + */ + function freeze() public onlyOwner { + isFreeze = true; + } + + /** + * @dev get balanceOf _beneficiary function + * @param _beneficiary addresses + */ + function balanceOf(address _beneficiary) public view returns(uint256) { + return reputationAllocations[_beneficiary]; + } + +} diff --git a/package-lock.json b/package-lock.json index 4b19d667..26478fcb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@daostack/arc", - "version": "0.0.1-rc.22", + "version": "0.0.1-rc.23", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -2541,9 +2541,9 @@ "dev": true }, "eslint-plugin-react": { - "version": "7.14.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.2.tgz", - "integrity": "sha512-jZdnKe3ip7FQOdjxks9XPN0pjUKZYq48OggNMd16Sk+8VXx6JOvXmlElxROCgp7tiUsTsze3jd78s/9AFJP2mA==", + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.14.3.tgz", + "integrity": "sha512-EzdyyBWC4Uz2hPYBiEJrKCUi2Fn+BJ9B/pJQcjw5X+x/H2Nm59S4MJIvL4O5NEE0+WbnQwEBxWY03oUk+Bc3FA==", "dev": true, "requires": { "array-includes": "^3.0.3", @@ -5726,9 +5726,9 @@ "optional": true }, "semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, "optional": true } @@ -6068,22 +6068,22 @@ "dev": true }, "package-json": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.4.0.tgz", - "integrity": "sha512-bd1T8OBG7hcvMd9c/udgv6u5v9wISP3Oyl9Cm7Weop8EFwrtcQDnS2sb6zhwqus2WslSr5wSTIPiTTpxxmPm7Q==", + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-6.5.0.tgz", + "integrity": "sha512-k3bdm2n25tkyxcjSKzB5x8kfVxlMdgsbPr0GkZcwHsLpba6cBjqCt1KlcChKEvxHIcTB1FVMuwoijZ26xex5MQ==", "dev": true, "optional": true, "requires": { "got": "^9.6.0", - "registry-auth-token": "^3.4.0", + "registry-auth-token": "^4.0.0", "registry-url": "^5.0.0", - "semver": "^6.1.1" + "semver": "^6.2.0" }, "dependencies": { "semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, "optional": true } @@ -6589,9 +6589,9 @@ "optional": true }, "semver": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.2.0.tgz", - "integrity": "sha512-jdFC1VdUGT/2Scgbimf7FSx9iJLXoqfglSF+gJeuNWVpiE37OIbc1jywR/GJyFdz3mnkz2/id0L0J/cr0izR5A==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", "dev": true, "optional": true }, @@ -7108,13 +7108,13 @@ } }, "registry-auth-token": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-3.4.0.tgz", - "integrity": "sha512-4LM6Fw8eBQdwMYcES4yTnn2TqIasbXuwDx3um+QRs7S55aMKCBKBxvPXl2RiUjHwuJLTyYfxSpmfSAjQpcuP+A==", + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/registry-auth-token/-/registry-auth-token-4.0.0.tgz", + "integrity": "sha512-lpQkHxd9UL6tb3k/aHAVfnVtn+Bcs9ob5InuFLLEDqSqeq+AljB8GZW9xY0x7F+xYwEcjKe07nyoxzEYz6yvkw==", "dev": true, "optional": true, "requires": { - "rc": "^1.1.6", + "rc": "^1.2.8", "safe-buffer": "^5.0.1" } }, diff --git a/package.json b/package.json index 925f20c4..c6a31d9f 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@daostack/arc", - "version": "0.0.1-rc.22", + "version": "0.0.1-rc.23", "description": "A platform for building DAOs", "files": [ "contracts/", diff --git a/test/reputationfromtoken.js b/test/reputationfromtoken.js index 4932dc33..1f99547c 100644 --- a/test/reputationfromtoken.js +++ b/test/reputationfromtoken.js @@ -3,7 +3,8 @@ 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"); +var RepAllocation = artifacts.require("./RepAllocation.sol"); + var PolkaCurve = artifacts.require("./PolkaCurve.sol"); const setup = async function (accounts, _initialize = true) { @@ -11,16 +12,16 @@ const setup = async function (accounts, _initialize = true) { 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.repAllocation = await RepAllocation.new(); + await testSetup.repAllocation.addBeneficiary(accounts[0],100); + await testSetup.repAllocation.addBeneficiary(accounts[1],200); + await testSetup.repAllocation.addBeneficiary(accounts[2],300); testSetup.reputationFromToken = await ReputationFromToken.new(); testSetup.curve = await PolkaCurve.new(); if (_initialize === true) { await testSetup.reputationFromToken.initialize(testSetup.org.avatar.address, - testSetup.extetnalTokenLockerMock.address, + testSetup.repAllocation.address, testSetup.curve.address); } @@ -29,25 +30,55 @@ const setup = async function (accounts, _initialize = true) { return testSetup; }; -contract('ReputationFromToken', accounts => { +contract('ReputationFromToken and RepAllocation', accounts => { it("initialize", async () => { let testSetup = await setup(accounts); - assert.equal(await testSetup.reputationFromToken.tokenContract(),testSetup.extetnalTokenLockerMock.address); + assert.equal(await testSetup.reputationFromToken.tokenContract(),testSetup.repAllocation.address); assert.equal(await testSetup.reputationFromToken.avatar(),testSetup.org.avatar.address); assert.equal(await testSetup.reputationFromToken.curve(),testSetup.curve.address); }); - it("externalLockingMock is onlyOwner", async () => { + it("repAllocation is onlyOwner", async () => { let testSetup = await setup(accounts); try { - await testSetup.extetnalTokenLockerMock.lock(1030,accounts[3],{from:accounts[1]}); - assert(false, "externalLockingMock is onlyOwner"); + await testSetup.repAllocation.addBeneficiary(accounts[3],1030,{from:accounts[1]}); + assert(false, "repAllocation is onlyOwner"); } catch(error) { helpers.assertVMException(error); } }); + it("repAllocation cannot allocate after freeze", async () => { + let testSetup = await setup(accounts); + await testSetup.repAllocation.addBeneficiary(accounts[3],1030); + await testSetup.repAllocation.freeze(); + + + try { + await testSetup.repAllocation.addBeneficiary(accounts[4],1030); + assert(false, "cannot allocate after freeze"); + } catch(error) { + helpers.assertVMException(error); + } + + }); + + it("repAllocation cannot allocate twice", async () => { + let testSetup = await setup(accounts); + assert(await testSetup.repAllocation.balanceOf(accounts[1]),200); + await testSetup.repAllocation.addBeneficiary(accounts[1],1030); + assert(await testSetup.repAllocation.balanceOf(accounts[1]),200); + }); + + it("repAllocation addBeneficiaries", async () => { + let testSetup = await setup(accounts); + let tx = await testSetup.repAllocation.addBeneficiaries([accounts[3],accounts[4]],[300,400]); + assert.equal(tx.logs.length,2); + }); + + + it("redeem", async () => { let testSetup = await setup(accounts); var tx = await testSetup.reputationFromToken.redeem(accounts[1]); @@ -84,7 +115,7 @@ contract('ReputationFromToken', accounts => { let testSetup = await setup(accounts); try { await testSetup.reputationFromToken.initialize(testSetup.org.avatar.address, - testSetup.extetnalTokenLockerMock.address, + testSetup.repAllocation.address, testSetup.curve.address ); assert(false, "cannot initialize twice");