From 4c91fc567f8db7c3b868218ec278378a2e79f393 Mon Sep 17 00:00:00 2001 From: Oren Date: Mon, 15 Oct 2018 14:35:41 +0300 Subject: [PATCH 1/2] add forwarder contract --- contracts/schemes/Forwarder.sol | 56 +++++++++++++++ test/forwarder.js | 122 ++++++++++++++++++++++++++++++++ 2 files changed, 178 insertions(+) create mode 100644 contracts/schemes/Forwarder.sol create mode 100644 test/forwarder.js diff --git a/contracts/schemes/Forwarder.sol b/contracts/schemes/Forwarder.sol new file mode 100644 index 00000000..5b483c3c --- /dev/null +++ b/contracts/schemes/Forwarder.sol @@ -0,0 +1,56 @@ +pragma solidity ^0.4.25; + +import "../controller/ControllerInterface.sol"; +import "openzeppelin-solidity/contracts/ownership/Ownable.sol"; + +/** + * @title A scheme to forward a call to a dao. + * The scheme can unregister itself when its expirationTime reached. + */ + + +contract Forwarder is Ownable { + + Avatar public avatar; + uint public expirationTime; + + /** + * @dev forwardCall forward a call to the dao controller + */ + function () external onlyOwner { + // solium-disable-next-line security/no-block-members + require(expirationTime > now, "expirationTime > now"); + // solium-disable-next-line security/no-low-level-calls + bool result = avatar.owner().call(msg.data); + // solium-disable-next-line security/no-inline-assembly + assembly { + // Copy the returned data. + returndatacopy(0, 0, returndatasize) + + switch result + // call returns 0 on error. + case 0 { revert(0, returndatasize) } + default { return(0, returndatasize) } + } + } + + /** + * @dev initialize + * @param _avatar the avatar of the dao to forward the call to + * @param _expirationTime the expirationTime to forwardCall + */ + function initialize(Avatar _avatar, uint _expirationTime) external onlyOwner { + avatar = _avatar; + expirationTime = _expirationTime; + } + + /** + * @dev unregisterSelf function + * @return bool + */ + function unregisterSelf() public returns(bool) { + // solium-disable-next-line security/no-block-members + require(expirationTime <= now, "expirationTime <= now"); + return ControllerInterface(avatar.owner()).unregisterSelf(address(avatar)); + } +} diff --git a/test/forwarder.js b/test/forwarder.js new file mode 100644 index 00000000..60085275 --- /dev/null +++ b/test/forwarder.js @@ -0,0 +1,122 @@ +const helpers = require('./helpers'); +const DaoCreator = artifacts.require("./DaoCreator.sol"); +const ControllerCreator = artifacts.require("./ControllerCreator.sol"); +const constants = require('./constants'); +const StandardTokenMock = artifacts.require('./test/StandardTokenMock.sol'); +var Forwarder = artifacts.require("./Forwarder.sol"); +var ControllerInterface = artifacts.require("./ControllerInterface.sol"); + +const setup = async function (accounts, + _expirationTime = 300) + { + var testSetup = new helpers.TestSetup(); + testSetup.biddingToken = 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); + testSetup.forwarder = await Forwarder.new(); + + testSetup.expirationTime = (await web3.eth.getBlock("latest")).timestamp + _expirationTime; + + await testSetup.forwarder.initialize(testSetup.org.avatar.address, + testSetup.expirationTime); + + var permissions = "0x0000001f"; + await testSetup.daoCreator.setSchemes(testSetup.org.avatar.address, + [accounts[0],testSetup.forwarder.address], + [web3.utils.asciiToHex("0"),web3.utils.asciiToHex("0")], + [permissions,permissions]); + return testSetup; +}; + +contract('Forwarder', accounts => { + it("initialize", async () => { + let testSetup = await setup(accounts); + + assert.equal(await testSetup.forwarder.avatar(),testSetup.org.avatar.address); + assert.equal(await testSetup.forwarder.expirationTime(),testSetup.expirationTime); + }); + + it("forwardCall (fallback)", async () => { + let testSetupA = await setup(accounts); + let testSetupB = await setup(accounts); + // transferOwnership of testSetupA.forwarder to testSetupB avatar + await testSetupA.forwarder.transferOwnership(testSetupB.org.avatar.address); + //do generic call from testSetupB controller to testSetupA controller to "registerScheme" + let controllerB = await ControllerInterface.at(await testSetupB.org.avatar.owner()); + let controllerA = await ControllerInterface.at(await testSetupA.org.avatar.owner()); + const encodeABI = await new web3.eth.Contract(controllerA.abi). + methods. + registerScheme(accounts[1],helpers.NULL_HASH,"0x0000001f",testSetupA.org.avatar.address). + encodeABI(); + assert.equal(await controllerA.isSchemeRegistered(accounts[1],testSetupA.org.avatar.address),false); + await controllerB.genericCall(testSetupA.forwarder.address,encodeABI,testSetupB.org.avatar.address); + //check that accounts[1] register as scheme at controllerA. + assert.equal(await controllerA.isSchemeRegistered(accounts[1],testSetupA.org.avatar.address),true); + + }); + + it("forwardCall (fallback) -check expirationTime", async () => { + let testSetupA = await setup(accounts); + let testSetupB = await setup(accounts); + // transferOwnership of testSetupA.forwarder to testSetupB avatar + await testSetupA.forwarder.transferOwnership(testSetupB.org.avatar.address); + //do generic call from testSetupB controller to testSetupA controller to "registerScheme" + let controllerB = await ControllerInterface.at(await testSetupB.org.avatar.owner()); + let controllerA = await ControllerInterface.at(await testSetupA.org.avatar.owner()); + const encodeABI = await new web3.eth.Contract(controllerA.abi). + methods. + registerScheme(accounts[1],helpers.NULL_HASH,"0x0000001f",testSetupA.org.avatar.address). + encodeABI(); + await helpers.increaseTime(301); + + try { + await controllerB.genericCall(testSetupA.forwarder.address,encodeABI,testSetupB.org.avatar.address); + + assert(false, "expired"); + } catch(error) { + helpers.assertVMException(error); + } + + }); + + + it("forwardCall (fallback) is onlyOwner ", async () => { + let testSetupA = await setup(accounts); + let testSetupB = await setup(accounts); + //do generic call from testSetupB controller to testSetupA controller to "registerScheme" + let controllerB = await ControllerInterface.at(await testSetupB.org.avatar.owner()); + let controllerA = await ControllerInterface.at(await testSetupA.org.avatar.owner()); + const encodeABI = await new web3.eth.Contract(controllerA.abi). + methods. + registerScheme(accounts[1],helpers.NULL_HASH,"0x0000001f",testSetupA.org.avatar.address). + encodeABI(); + try { + await controllerB.genericCall(testSetupA.forwarder.address,encodeABI,testSetupB.org.avatar.address); + + assert(false, "forwardCall is onlyOwner"); + } catch(error) { + helpers.assertVMException(error); + } + }); + + it("unregisterSelf", async () => { + let testSetupA = await setup(accounts); + let testSetupB = await setup(accounts); + let controllerA = await ControllerInterface.at(await testSetupA.org.avatar.owner()); + // transferOwnership of testSetupA.forwarder to testSetupB avatar + await testSetupA.forwarder.transferOwnership(testSetupB.org.avatar.address); + assert.equal(await controllerA.isSchemeRegistered(testSetupA.forwarder.address,testSetupA.org.avatar.address),true); + try { + await testSetupA.forwarder.unregisterSelf(); + assert(false, "expirationTime did not passed"); + } catch(error) { + helpers.assertVMException(error); + } + assert.equal(await controllerA.isSchemeRegistered(testSetupA.forwarder.address,testSetupA.org.avatar.address),true); + await helpers.increaseTime(301); + await testSetupA.forwarder.unregisterSelf(); + assert.equal(await controllerA.isSchemeRegistered(testSetupA.forwarder.address,testSetupA.org.avatar.address),false); + + }); +}); From 51552e603313d11c189b6e6e14274925fbd22762 Mon Sep 17 00:00:00 2001 From: Oren Date: Tue, 16 Oct 2018 11:44:09 +0300 Subject: [PATCH 2/2] cannot initilze twice --- contracts/schemes/Forwarder.sol | 2 ++ test/forwarder.js | 11 +++++++++++ 2 files changed, 13 insertions(+) diff --git a/contracts/schemes/Forwarder.sol b/contracts/schemes/Forwarder.sol index 5b483c3c..8b555a83 100644 --- a/contracts/schemes/Forwarder.sol +++ b/contracts/schemes/Forwarder.sol @@ -40,6 +40,8 @@ contract Forwarder is Ownable { * @param _expirationTime the expirationTime to forwardCall */ function initialize(Avatar _avatar, uint _expirationTime) external onlyOwner { + require(avatar == Avatar(0), "can be called only one time"); + require(_avatar != Avatar(0), "avatar cannot be zero"); avatar = _avatar; expirationTime = _expirationTime; } diff --git a/test/forwarder.js b/test/forwarder.js index 60085275..080164af 100644 --- a/test/forwarder.js +++ b/test/forwarder.js @@ -37,6 +37,17 @@ contract('Forwarder', accounts => { assert.equal(await testSetup.forwarder.expirationTime(),testSetup.expirationTime); }); + it("cannot initialize twice", async () => { + let testSetup = await setup(accounts); + try { + await testSetup.forwarder.initialize(testSetup.org.avatar.address, + testSetup.expirationTime); + assert(false, "cannot initialize twice"); + } catch(error) { + helpers.assertVMException(error); + } + }); + it("forwardCall (fallback)", async () => { let testSetupA = await setup(accounts); let testSetupB = await setup(accounts);