From a71fd990eb85abf7210302b993eb3494af6da463 Mon Sep 17 00:00:00 2001 From: Oren Sokolowsky Date: Thu, 23 Apr 2020 01:38:07 +0300 Subject: [PATCH] add RageQuitWithToken --- ...n.sol => ContinuousLocking4Reputation.sol} | 0 contracts/schemes/RageQuitWithToken.sol | 60 ++++++++++++ test/helpers.js | 18 ++-- test/ragequitwithtoken.js | 91 +++++++++++++++++++ 4 files changed, 163 insertions(+), 6 deletions(-) rename contracts/schemes/{ContinuousLockingToken4Reputation.sol => ContinuousLocking4Reputation.sol} (100%) create mode 100644 contracts/schemes/RageQuitWithToken.sol create mode 100644 test/ragequitwithtoken.js diff --git a/contracts/schemes/ContinuousLockingToken4Reputation.sol b/contracts/schemes/ContinuousLocking4Reputation.sol similarity index 100% rename from contracts/schemes/ContinuousLockingToken4Reputation.sol rename to contracts/schemes/ContinuousLocking4Reputation.sol diff --git a/contracts/schemes/RageQuitWithToken.sol b/contracts/schemes/RageQuitWithToken.sol new file mode 100644 index 00000000..0cc76f82 --- /dev/null +++ b/contracts/schemes/RageQuitWithToken.sol @@ -0,0 +1,60 @@ +pragma solidity ^0.5.17; + +import "@openzeppelin/upgrades/contracts/Initializable.sol"; +import "@openzeppelin/contracts-ethereum-package/contracts/math/SafeMath.sol"; +import "../controller/Controller.sol"; + + +/** + * @title A scheme to rage quit from a dao with token. + * by sending the dao native token to the RageQuit function the sender will get is proportional share of the dao + * rageQuitToken (DAI in most case) + */ +contract RageQuitWithToken is Initializable { + using SafeMath for uint; + + event RageQuit( + address indexed _avatar, + address indexed _rageQuitter, + uint256 indexed _refund + ); + + Avatar public avatar; + IERC20 public rageQuitToken; //the token which is given back for rageQuit - DAI in most cases + + /** + * @dev initialize + * @param _avatar the avatar this scheme referring to. + * @param _rageQuitToken the token which is given back for rageQuit - DAI in most + */ + function initialize( + Avatar _avatar, + IERC20 _rageQuitToken + ) + external + initializer + { + require(_avatar != Avatar(0), "avatar cannot be zero"); + avatar = _avatar; + rageQuitToken = _rageQuitToken; + } + + /** + * @dev rageQuit quit from the dao. + * @param _amountToRageQuitWith amount of native token to rageQuit with. + * @return refund the refund amount + */ + function rageQuit(uint256 _amountToRageQuitWith) public returns(uint256 refund) { + + uint256 totalTokenSupply = avatar.nativeToken().totalSupply(); + uint256 rageQuitTokenTotalSupply = rageQuitToken.balanceOf(address(avatar)); + refund = _amountToRageQuitWith.mul(rageQuitTokenTotalSupply).div(totalTokenSupply); + //this will revert if the msg.sender token balance is less than _amountToRageQuitWith. + avatar.nativeToken().burnFrom(msg.sender, _amountToRageQuitWith); + require( + Controller( + avatar.owner()).externalTokenTransfer(rageQuitToken, msg.sender, refund), "send token failed"); + emit RageQuit(address(avatar), msg.sender, refund); + } + +} diff --git a/test/helpers.js b/test/helpers.js index 88764faa..1ee62cce 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -35,6 +35,8 @@ const VoteInOrganization = artifacts.require("./VoteInOrganizationScheme.sol"); const ARCVotingMachineCallbacksMock = artifacts.require("./ARCVotingMachineCallbacksMock.sol"); const JoinAndQuit = artifacts.require("./JoinAndQuit.sol"); const FundingRequest = artifacts.require("./FundingRequest.sol"); +const RageQuitWithToken = artifacts.require("./RageQuitWithToken.sol"); + const MAX_UINT_256 = '0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'; @@ -153,6 +155,8 @@ const SOME_ADDRESS = '0x1000000000000000000000000000000000000000'; registration.arcVotingMachineCallbacksMock = await ARCVotingMachineCallbacksMock.new(); registration.joinAndQuit = await JoinAndQuit.new(); registration.fundingRequest = await FundingRequest.new(); + registration.rageQuitWithToken = await RageQuitWithToken.new(); + @@ -183,6 +187,8 @@ const SOME_ADDRESS = '0x1000000000000000000000000000000000000000'; await implementationDirectory.setImplementation("ARCVotingMachineCallbacksMock",registration.arcVotingMachineCallbacksMock.address); await implementationDirectory.setImplementation("JoinAndQuit",registration.joinAndQuit.address); await implementationDirectory.setImplementation("FundingRequest",registration.fundingRequest.address); + await implementationDirectory.setImplementation("RageQuitWithToken",registration.rageQuitWithToken.address); + registration.implementationDirectory = implementationDirectory; @@ -261,9 +267,9 @@ const SOME_ADDRESS = '0x1000000000000000000000000000000000000000'; const setupOrganizationWithArraysDAOFactory = async function (proxyAdmin, accounts, registration, - daoFactoryOwner, - founderToken, - founderReputation, + founders, + foundersToken, + foundersReputation, cap=0) { var org = new Organization(); var nativeTokenData = await new web3.eth.Contract(registration.daoToken.abi) @@ -272,9 +278,9 @@ const SOME_ADDRESS = '0x1000000000000000000000000000000000000000'; .encodeABI(); var tx = await registration.daoFactory.forgeOrg("testOrg", nativeTokenData, - daoFactoryOwner, - founderToken, - founderReputation, + founders, + foundersToken, + foundersReputation, [0,0,0], {from:proxyAdmin,gas:constants.ARC_GAS_LIMIT}); assert.equal(tx.logs.length, 5); diff --git a/test/ragequitwithtoken.js b/test/ragequitwithtoken.js new file mode 100644 index 00000000..39574ecf --- /dev/null +++ b/test/ragequitwithtoken.js @@ -0,0 +1,91 @@ +const helpers = require("./helpers"); +const RageQuitWithToken = artifacts.require("./RageQuitWithToken.sol"); +const ERC20Mock = artifacts.require('./test/ERC20Mock.sol'); + +class RageQuitWithTokenParams { + constructor() { + } +} + +const setupRageQuitWithToken = async function( + accounts, + tokenAddress, + avatarAddress, + ) { + var rageQuitWithTokenParams = new RageQuitWithTokenParams(); + rageQuitWithTokenParams.initdata = await new web3.eth.Contract(registration.rageQuitWithToken.abi) + .methods + .initialize(avatarAddress,tokenAddress) + .encodeABI(); + return rageQuitWithTokenParams; +}; +var registration; +const setup = async function (accounts) { + var testSetup = new helpers.TestSetup(); + testSetup.rageQuitToken = await ERC20Mock.new(accounts[0],100000); + registration = await helpers.registerImplementation(); + + testSetup.proxyAdmin = accounts[0]; + testSetup.reputationArray = [0,0,0]; + testSetup.tokenArray = [100,200,300]; + testSetup.founderAccounts = [accounts[1],accounts[2],accounts[3]]; + + testSetup.org = await helpers.setupOrganizationWithArraysDAOFactory(testSetup.proxyAdmin, + accounts, + registration, + testSetup.founderAccounts, + testSetup.tokenArray, + testSetup.reputationArray); + + + testSetup.rageQuitWithTokenParams= await setupRageQuitWithToken( + accounts, + testSetup.rageQuitToken.address, + testSetup.org.avatar.address); + + var permissions = "0x00000000"; + var tx = await registration.daoFactory.setSchemes( + testSetup.org.avatar.address, + [web3.utils.fromAscii("RageQuitWithToken")], + testSetup.rageQuitWithTokenParams.initdata, + [helpers.getBytesLength(testSetup.rageQuitWithTokenParams.initdata)], + [permissions], + "metaData",{from:testSetup.proxyAdmin}); + testSetup.rageQuitWithToken = await RageQuitWithToken.at(tx.logs[1].args._scheme); + + return testSetup; +}; +contract('RageQuitWithToken', accounts => { + + it("initialize", async function() { + var testSetup = await setup(accounts); + assert.equal(await testSetup.rageQuitWithToken.rageQuitToken(),testSetup.rageQuitToken.address); + assert.equal(await testSetup.rageQuitWithToken.avatar(),testSetup.org.avatar.address); + }); + + it("rageQuit", async function() { + var testSetup = await setup(accounts); + //send the dao some rageQuitTokens + await testSetup.rageQuitToken.transfer(testSetup.org.avatar.address,1000); + assert.equal(await testSetup.rageQuitToken.balanceOf(testSetup.org.avatar.address),1000); + //accounts 1 ragequit with all is tokens (100) + assert.equal((await testSetup.org.token.balanceOf(accounts[1])).toNumber(),100); + //give approval for burn + await testSetup.org.token.approve(testSetup.rageQuitWithToken.address,100,{from:accounts[1]}); + var tx = await testSetup.rageQuitWithToken.rageQuit(100,{from:accounts[1]}); + assert.equal(tx.logs.length, 1); + var expectedRefund = Math.floor((100/(100+200+300)) * 1000); + assert.equal(tx.logs[0].event, "RageQuit"); + assert.equal(tx.logs[0].args._rageQuitter, accounts[1]); + assert.equal(tx.logs[0].args._refund.toNumber(),expectedRefund); + assert.equal(await testSetup.rageQuitToken.balanceOf(testSetup.org.avatar.address),1000-expectedRefund); + assert.equal(await testSetup.rageQuitToken.balanceOf(accounts[1]),expectedRefund); + try { + await testSetup.org.token.approve(testSetup.rageQuitWithToken.address,100,{from:accounts[1]}); + await testSetup.rageQuitWithToken.rageQuit(100,{from:accounts[1]}); + assert(false, "cannot rageQuit twice"); + } catch(error) { + helpers.assertVMException(error); + } + }); +});