Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feat: Target attribute supported #48

Merged
merged 1 commit into from
Sep 8, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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