Skip to content

Commit

Permalink
Merge pull request #48 from gnosis/feat/target
Browse files Browse the repository at this point in the history
Feat: Target attribute supported
  • Loading branch information
auryn-macmillan authored Sep 8, 2021
2 parents 0d86dea + ca0e48c commit 94d8dcd
Show file tree
Hide file tree
Showing 11 changed files with 99 additions and 62 deletions.
28 changes: 20 additions & 8 deletions contracts/RealityModule.sol
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0;

import "@gnosis/zodiac/contracts/core/Module.sol";
import "@gnosis.pm/zodiac/contracts/core/Module.sol";
import "./interfaces/RealitioV3.sol";

abstract contract RealityModule is Module {
Expand All @@ -25,7 +25,12 @@ abstract contract RealityModule is Module {
string indexed proposalId
);

event RealityModuleSetup(address indexed initiator, address indexed avatar);
event RealityModuleSetup(
address indexed initiator,
address indexed owner,
address indexed avatar,
address target
);

RealitioV3 public oracle;
uint256 public template;
Expand All @@ -43,6 +48,7 @@ abstract contract RealityModule is Module {

/// @param _owner Address of the owner
/// @param _avatar Address of the avatar (e.g. a Safe)
/// @param _target Address of the contract that will call exec function
/// @param _oracle Address of the oracle (e.g. Realitio)
/// @param timeout Timeout in seconds that should be required for the oracle
/// @param cooldown Cooldown in seconds that should be required after a oracle provided answer
Expand All @@ -53,6 +59,7 @@ abstract contract RealityModule is Module {
constructor(
address _owner,
address _avatar,
address _target,
RealitioV3 _oracle,
uint32 timeout,
uint32 cooldown,
Expand All @@ -63,6 +70,7 @@ abstract contract RealityModule is Module {
bytes memory initParams = abi.encode(
_owner,
_avatar,
_target,
_oracle,
timeout,
cooldown,
Expand All @@ -77,6 +85,7 @@ abstract contract RealityModule is Module {
(
address _owner,
address _avatar,
address _target,
RealitioV3 _oracle,
uint32 timeout,
uint32 cooldown,
Expand All @@ -86,6 +95,7 @@ abstract contract RealityModule is Module {
) = abi.decode(
initParams,
(
address,
address,
address,
RealitioV3,
Expand All @@ -99,24 +109,26 @@ abstract contract RealityModule is Module {
require(!initialized, "Module is already initialized");
initialized = true;
require(_avatar != address(0), "Avatar can not be zero address");
require(_target != address(0), "Target can not be zero address");
require(timeout > 0, "Timeout has to be greater 0");
require(
expiration == 0 || expiration - cooldown >= 60,
"There need to be at least 60s between end of cooldown and expiration"
);
avatar = _avatar;
target = _target;
oracle = _oracle;
answerExpiration = expiration;
questionTimeout = timeout;
questionCooldown = cooldown;
questionArbitrator = avatar;
questionArbitrator = address(oracle);
minimumBond = bond;
template = templateId;

__Ownable_init();
transferOwnership(_owner);

emit RealityModuleSetup(msg.sender, avatar);
emit RealityModuleSetup(msg.sender, _owner, avatar, target);
}

/// @notice This can only be called by the owner
Expand Down Expand Up @@ -232,7 +244,7 @@ abstract contract RealityModule is Module {
/// @notice This can only be called by the owner
function markProposalAsInvalid(
string memory proposalId,
bytes32[] memory txHashes // avatar only is checked in markProposalAsInvalidByHash(bytes32)
bytes32[] memory txHashes // owner only is checked in markProposalAsInvalidByHash(bytes32)
) public {
string memory question = buildQuestion(proposalId, txHashes);
bytes32 questionHash = keccak256(bytes(question));
Expand Down Expand Up @@ -274,7 +286,7 @@ abstract contract RealityModule is Module {
questionIds[questionHash] = INVALIDATED;
}

/// @dev Executes the transactions of a proposal via the avatar if accepted
/// @dev Executes the transactions of a proposal via the target if accepted
/// @param proposalId Id that should identify the proposal uniquely
/// @param txHashes EIP-712 hashes of the transactions that should be executed
/// @param to Target of the transaction that should be executed
Expand All @@ -301,7 +313,7 @@ abstract contract RealityModule is Module {
);
}

/// @dev Executes the transactions of a proposal via the avatar if accepted
/// @dev Executes the transactions of a proposal via the target if accepted
/// @param proposalId Id that should identify the proposal uniquely
/// @param txHashes EIP-712 hashes of the transactions that should be executed
/// @param to Target of the transaction that should be executed
Expand Down Expand Up @@ -377,7 +389,7 @@ abstract contract RealityModule is Module {
);
// Mark transaction as executed
executedProposalTransactions[questionHash][txHash] = true;
// Execute the transaction via the avatar.
// Execute the transaction via the target.
require(exec(to, value, data, operation), "Module transaction failed");
}

Expand Down
3 changes: 3 additions & 0 deletions contracts/RealityModuleERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "./interfaces/RealitioV3.sol";
contract RealityModuleERC20 is RealityModule {
/// @param _owner Address of the owner
/// @param _avatar Address of the avatar (e.g. a Safe)
/// @param _target Address of the contract that will call exec function
/// @param _oracle Address of the oracle (e.g. Realitio)
/// @param timeout Timeout in seconds that should be required for the oracle
/// @param cooldown Cooldown in seconds that should be required after a oracle provided answer
Expand All @@ -17,6 +18,7 @@ contract RealityModuleERC20 is RealityModule {
constructor(
address _owner,
address _avatar,
address _target,
RealitioV3 _oracle,
uint32 timeout,
uint32 cooldown,
Expand All @@ -27,6 +29,7 @@ contract RealityModuleERC20 is RealityModule {
RealityModule(
_owner,
_avatar,
_target,
_oracle,
timeout,
cooldown,
Expand Down
3 changes: 3 additions & 0 deletions contracts/RealityModuleETH.sol
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "./interfaces/RealitioV3.sol";
contract RealityModuleETH is RealityModule {
/// @param _owner Address of the owner
/// @param _avatar Address of the avatar (e.g. a Safe)
/// @param _target Address of the contract that will call exec function
/// @param _oracle Address of the oracle (e.g. Realitio)
/// @param timeout Timeout in seconds that should be required for the oracle
/// @param cooldown Cooldown in seconds that should be required after a oracle provided answer
Expand All @@ -17,6 +18,7 @@ contract RealityModuleETH is RealityModule {
constructor(
address _owner,
address _avatar,
address _target,
RealitioV3 _oracle,
uint32 timeout,
uint32 cooldown,
Expand All @@ -27,6 +29,7 @@ contract RealityModuleETH is RealityModule {
RealityModule(
_owner,
_avatar,
_target,
_oracle,
timeout,
cooldown,
Expand Down
2 changes: 1 addition & 1 deletion contracts/test/TestFactory.sol
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.8.0;

import "@gnosis/zodiac/contracts/factory/ModuleProxyFactory.sol";
import "@gnosis.pm/zodiac/contracts/factory/ModuleProxyFactory.sol";
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@zodiac/zodiac-module-reality",
"version": "1.0.0",
"name": "zodiac-module-reality",
"version": "0.0.1-prealpha1",
"description": "A module that allows on-chain execution triggered by answers from the Reality.eth oracle.",
"directories": {
"test": "test"
Expand All @@ -23,7 +23,7 @@
"author": "richard@gnosis.io",
"license": "MIT",
"devDependencies": {
"@gnosis/zodiac": "github:gnosis/zodiac#master",
"@gnosis.pm/zodiac": "0.0.1-prealpha1",
"@nomiclabs/hardhat-ethers": "^2.0.0",
"@nomiclabs/hardhat-etherscan": "^2.1.0",
"@nomiclabs/hardhat-waffle": "^2.0.0",
Expand Down
2 changes: 1 addition & 1 deletion src/deploy/deploy_modules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const deploy: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { deployments, getNamedAccounts } = hre;
const { deployer } = await getNamedAccounts();
const { deploy } = deployments;
const args = [FIRST_ADDRESS, FIRST_ADDRESS, FIRST_ADDRESS, 1, 0, 60, 0, 0];
const args = [FIRST_ADDRESS, FIRST_ADDRESS, FIRST_ADDRESS, FIRST_ADDRESS, 1, 0, 60, 0, 0];

await deploy("RealityModuleERC20", {
from: deployer,
Expand Down
43 changes: 25 additions & 18 deletions test/DaoModuleERC20.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,14 +48,14 @@ describe("RealityModuleERC20", async () => {
const setupTestWithTestAvatar = deployments.createFixture(async () => {
const base = await baseSetup();
const Module = await hre.ethers.getContractFactory("RealityModuleERC20");
const module = await Module.deploy(base.avatar.address, base.avatar.address, base.mock.address, 42, 23, 0, 0, 1337);
const module = await Module.deploy(base.avatar.address, base.avatar.address, base.avatar.address, base.mock.address, 42, 23, 0, 0, 1337);
return { ...base, Module, module };
})

const setupTestWithMockAvatar = deployments.createFixture(async () => {
const base = await baseSetup();
const Module = await hre.ethers.getContractFactory("RealityModuleERC20");
const module = await Module.deploy(base.mock.address, base.mock.address, base.mock.address, 42, 23, 0, 0, 1337);
const module = await Module.deploy(base.mock.address, base.mock.address, base.mock.address, base.mock.address, 42, 23, 0, 0, 1337);
return { ...base, Module, module };
})
const [user1] = waffle.provider.getWallets();
Expand All @@ -64,7 +64,7 @@ describe("RealityModuleERC20", async () => {
it("throws if is already initialized", async () => {
const { mock } = await baseSetup()
const Module = await hre.ethers.getContractFactory("RealityModuleERC20")
const module = await Module.deploy(user1.address, user1.address, user1.address, 42, 23, 0, 0, 1337)
const module = await Module.deploy(user1.address, user1.address, user1.address, user1.address, 42, 23, 0, 0, 1337)
await expect(
module.setUp(buildMockInitializerParams(mock))
).to.be.revertedWith("Module is already initialized")
Expand All @@ -73,38 +73,45 @@ describe("RealityModuleERC20", async () => {
it("throws if avatar is zero address", async () => {
const Module = await hre.ethers.getContractFactory("RealityModuleETH")
await expect(
Module.deploy(user1.address, ZERO_ADDRESS, user1.address, 42, 23, 0, 0, 1337)
Module.deploy(user1.address, ZERO_ADDRESS, user1.address, user1.address, 42, 23, 0, 0, 1337)
).to.be.revertedWith("Avatar can not be zero address")
})

it("throws if avatar is zero address", async () => {
const Module = await hre.ethers.getContractFactory("RealityModuleETH")
await expect(
Module.deploy(user1.address, user1.address, ZERO_ADDRESS, user1.address, 42, 23, 0, 0, 1337)
).to.be.revertedWith("Target can not be zero address")
})

it("throws if timeout is 0", async () => {
const Module = await hre.ethers.getContractFactory("RealityModuleERC20")
await expect(
Module.deploy(user1.address, user1.address, user1.address, 0, 0, 0, 0, 0)
Module.deploy(user1.address, user1.address, user1.address, user1.address, 0, 0, 0, 0, 0)
).to.be.revertedWith("Timeout has to be greater 0")
})

it("throws if not enough time between cooldown and expiration", async () => {
const Module = await hre.ethers.getContractFactory("RealityModuleERC20")
await expect(
Module.deploy(user1.address, user1.address, user1.address, 1, 0, 59, 0, 0)
Module.deploy(user1.address, user1.address, user1.address, user1.address, 1, 0, 59, 0, 0)
).to.be.revertedWith("There need to be at least 60s between end of cooldown and expiration")
})

it("answer expiration can be 0", async () => {
const Module = await hre.ethers.getContractFactory("RealityModuleERC20")
await Module.deploy(user1.address, user1.address, user1.address, 1, 10, 0, 0, 0)
await Module.deploy(user1.address, user1.address, user1.address, user1.address, 1, 10, 0, 0, 0)
})

it("should emit event because of successful set up", async () => {
const Module = await hre.ethers.getContractFactory("RealityModuleERC20")
const module = await Module.deploy(
user1.address, user1.address, user1.address, 1, 10, 0, 0, 0
user1.address, user1.address, user1.address, user1.address, 1, 10, 0, 0, 0
)
await module.deployed()
await expect(module.deployTransaction)
.to.emit(module, "RealityModuleSetup").
withArgs(user1.address, user1.address)
withArgs(user1.address, user1.address, user1.address, user1.address)
})
})

Expand Down Expand Up @@ -256,11 +263,11 @@ describe("RealityModuleERC20", async () => {
})

it("updates arbitrator", async () => {
const { module, avatar } = await setupTestWithTestAvatar();
const { module, avatar, oracle } = await setupTestWithTestAvatar();

expect(
await module.questionArbitrator()
).to.be.equals(avatar.address);
).to.be.equals(oracle.address);

const calldata = module.interface.encodeFunctionData("setArbitrator", [ethers.constants.AddressZero])
await avatar.exec(module.address, 0, calldata)
Expand Down Expand Up @@ -678,7 +685,7 @@ describe("RealityModuleERC20", async () => {
await module.questionIds(questionHash)
).to.be.deep.equals(questionId)

const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, avatar.address, 42, 0, 0, 0, 0])
const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, mock.address, 42, 0, 0, 0, 0])
expect(
(await mock.callStatic.invocationCountForCalldata(askQuestionCalldata)).toNumber()
).to.be.equals(1);
Expand Down Expand Up @@ -712,7 +719,7 @@ describe("RealityModuleERC20", async () => {
await module.questionIds(questionHash)
).to.be.deep.equals(questionId)

const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, avatar.address, 42, 0, 0, 7331, 0])
const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, mock.address, 42, 0, 0, 7331, 0])
expect(
(await mock.callStatic.invocationCountForCalldata(askQuestionCalldata)).toNumber()
).to.be.equals(1);
Expand All @@ -723,7 +730,7 @@ describe("RealityModuleERC20", async () => {

describe("addProposalWithNonce", async () => {
it("throws if previous nonce was not invalid", async () => {
const { module, mock, oracle, avatar } = await setupTestWithTestAvatar();
const { module, mock, oracle, } = await setupTestWithTestAvatar();
await mock.givenMethodReturnUint(oracle.interface.getSighash("askQuestionWithMinBondERC20"), 42)
const id = "some_random_id";
const txHash = ethers.utils.solidityKeccak256(["string"], ["some_tx_data"]);
Expand Down Expand Up @@ -761,7 +768,7 @@ describe("RealityModuleERC20", async () => {
await module.questionIds(questionHash)
).to.be.deep.equals(questionId)

const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, avatar.address, 42, 0, 1, 0, 0])
const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, mock.address, 42, 0, 1, 0, 0])
expect(
(await mock.callStatic.invocationCountForCalldata(askQuestionCalldata)).toNumber()
).to.be.equals(1);
Expand Down Expand Up @@ -799,7 +806,7 @@ describe("RealityModuleERC20", async () => {
await module.questionIds(questionHash)
).to.be.deep.equals(questionId)

const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, avatar.address, 23, 0, 11, 0, 0])
const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, mock.address, 23, 0, 11, 0, 0])
expect(
(await mock.callStatic.invocationCountForCalldata(askQuestionCalldata)).toNumber()
).to.be.equals(1);
Expand All @@ -810,7 +817,7 @@ describe("RealityModuleERC20", async () => {
})

it("can invalidate multiple times", async () => {
const { module, mock, oracle, avatar } = await setupTestWithTestAvatar();
const { module, mock, oracle } = await setupTestWithTestAvatar();
const id = "some_random_id";
const txHash = ethers.utils.solidityKeccak256(["string"], ["some_tx_data"]);

Expand Down Expand Up @@ -843,7 +850,7 @@ describe("RealityModuleERC20", async () => {
await module.questionIds(questionHash)
).to.be.deep.equals(finalQuestionId)

const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, avatar.address, 42, 0, 1337, 0, 0])
const askQuestionCalldata = oracle.interface.encodeFunctionData("askQuestionWithMinBondERC20", [1337, question, mock.address, 42, 0, 1337, 0, 0])
expect(
(await mock.callStatic.invocationCountForCalldata(askQuestionCalldata)).toNumber()
).to.be.equals(1);
Expand Down
Loading

0 comments on commit 94d8dcd

Please sign in to comment.