Skip to content

Commit

Permalink
feat: new set up interface implemented, tests and documentation updat…
Browse files Browse the repository at this point in the history
…ed accordingly
  • Loading branch information
cbrzn committed Aug 25, 2021
1 parent afc0edf commit 635e5dc
Show file tree
Hide file tree
Showing 6 changed files with 632 additions and 765 deletions.
39 changes: 25 additions & 14 deletions contracts/SafeExit.sol → contracts/ExitModule.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import "@gnosis/zodiac/contracts/core/Module.sol";

import "./CirculatingSupply.sol";

contract SafeExit is Module {
contract Exit is Module {
ERC20 public designatedToken;
CirculatingSupply public circulatingSupply;

Expand All @@ -16,33 +16,44 @@ contract SafeExit is Module {
/// @notice Mapping of denied tokens defined by the executor
mapping(address => bool) public deniedTokens;

constructor(
address _owner,
address _executor,
address _designatedToken,
address _circulatingSupply
) {
setUp(_owner, _executor, _designatedToken, _circulatingSupply);
}

/// @dev Initialize function, will be triggered when a new proxy is deployed
/// @param _owner Address of the owner
/// @param _executor Address of the executor (e.g. a Safe or Delay Module)
/// @param _designatedToken Address of the ERC20 token that will define the share of users
/// @param _circulatingSupply Circulating Supply of designated token
/// @notice Designated token address can not be zero
function setUp(
constructor(
address _owner,
address _executor,
address _designatedToken,
address _circulatingSupply
) public {
require(executor == address(0), "Module is already initialized");
) {
bytes memory initParams = abi.encode(
_owner,
_executor,
_designatedToken,
_circulatingSupply
);
setUp(initParams);
}

function setUp(bytes memory initParams) public override {
(
address _owner,
address _executor,
address _designatedToken,
address _circulatingSupply
) = abi.decode(initParams, (address, address, address, address));
require(!initialized, "Module is already initialized");
executor = _executor;
designatedToken = ERC20(_designatedToken);
circulatingSupply = CirculatingSupply(_circulatingSupply);

if (_executor != address(0)) transferOwnership(_owner);
if (_owner != address(0)) {
__Ownable_init();
transferOwnership(_owner);
initialized = true;
}

emit SafeExitModuleSetup(msg.sender, _executor);
}
Expand Down
10 changes: 5 additions & 5 deletions docs/setup_guide.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# SafeExit Setup Guide
# Exit Setup Guide

This guide shows how to setup the SafeExit module with a Gnosis Safe on the Rinkeby testnetwork.
This guide shows how to setup the Exit module with a Gnosis Safe on the Rinkeby testnetwork.

## Prerequisites

Expand All @@ -20,14 +20,14 @@ Note 2: If you want to test the exit function (the how-to is described below) -

## Setting up the module

The first step is to deploy the module. Every Safe will have their own module. The module is linked to a Safe (called executor in the contract). The Safe can only be changed by the owner of the module.
The first step is to deploy the module. Every Safe will have their own module. The module is linked to a Safe (called owner in the contract).

- The owner is the address that can call setter functions (would usually be the safe)
- The executor is the address that the module uses to execute transactions (it calls the `execTransactionFromModule` function)

## Deploying the module

Hardhat tasks can be used to deploy a Safe Exit instance. There are two different tasks to deploy the module, the first one is through a normal deployment and passing arguments to the constructor (with the task `setup`), or, deploy the Module through a [Minimal Proxy Factory](https://eips.ethereum.org/EIPS/eip-1167) and save on gas costs (with the task `factorySetup`) - In rinkeby the address of the Proxy Factory is: `0xd067410a85ffC8C55f7245DE4BfE16C95329D232` and the Master Copy of the Safe Exit: `0x3c1EDB810E3D1fF860F1894d40F946f2ca588AAD`.
Hardhat tasks can be used to deploy a Exit module instance. There are two different tasks to deploy the module, the first one is through a normal deployment and passing arguments to the constructor (with the task `setup`), or, deploy the Module through a [Minimal Proxy Factory](https://eips.ethereum.org/EIPS/eip-1167) and save on gas costs (with the task `factorySetup`) - In rinkeby the address of the Proxy Factory is: `0xd067410a85ffC8C55f7245DE4BfE16C95329D232` and the Master Copy of the Safe Exit: `0xe1a55322aDE704208129E74E963fa25C8C257eD6`.

These setup tasks requires the following parameters:

Expand Down Expand Up @@ -55,7 +55,7 @@ An example for this on Rinkeby would be:

## Enabling the module

To allow the SafeExit module to actually work it is required to enable it on the Safe that it is connected to. For this it is possible to use the Transaction Builder on https://rinkeby.gnosis-safe.io. For this you can follow our tutorial on [adding a module](https://help.gnosis-safe.io/en/articles/4934427-add-a-module).
To allow the Exit module to actually work it is required to enable it on the Safe that it is connected to. For this it is possible to use the Transaction Builder on https://rinkeby.gnosis-safe.io. For this you can follow our tutorial on [adding a module](https://help.gnosis-safe.io/en/articles/4934427-add-a-module).

## Executing the exit

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "@gnosis/safe-exit",
"name": "@zodiac/exit",
"version": "1.0.0",
"description": "A module that allows users to redeem their designated tokens for a proportional share assets held in an account.",
"scripts": {
Expand Down Expand Up @@ -49,6 +49,7 @@
"typescript": "4.3.5"
},
"dependencies": {
"@gnosis.pm/safe-contracts": "1.3.0",
"@openzeppelin/contracts": "^4.2.0",
"argv": "0.0.2",
"dotenv": "10.0.0",
Expand Down
20 changes: 12 additions & 8 deletions src/tasks/setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ import "hardhat-deploy";
import "@nomiclabs/hardhat-ethers";
import { task, types } from "hardhat/config";
import { BigNumber, Contract } from "ethers";
import { AbiCoder } from "ethers/lib/utils";

const AddressOne = "0x0000000000000000000000000000000000000001";

task("setup", "deploy a SafeExit Module")
task("setup", "deploy a Exit Module")
.addParam("owner", "Address of the owner", undefined, types.string)
.addParam(
"executor",
Expand All @@ -27,7 +28,7 @@ task("setup", "deploy a SafeExit Module")
const Supply = await hardhatRuntime.ethers.getContractFactory(
"CirculatingSupply"
);
const Module = await hardhatRuntime.ethers.getContractFactory("SafeExit");
const Module = await hardhatRuntime.ethers.getContractFactory("Exit");

const supply = await Supply.deploy(taskArgs.supply);
const module = await Module.deploy(
Expand Down Expand Up @@ -80,13 +81,16 @@ task("factorySetup", "Deploy and initialize Safe Exit through a Proxy Factory")

const supply = await Supply.deploy(taskArgs.supply);
const Factory = new Contract(taskArgs.factory, FactoryAbi, caller);
const Module = await hardhatRuntime.ethers.getContractFactory("SafeExit");

const encodedData = new AbiCoder().encode(
["address", "address", "address", "address"],
[taskArgs.owner, taskArgs.executor, taskArgs.token, supply.address]
);

const Module = await hardhatRuntime.ethers.getContractFactory("Exit");

const initParams = Module.interface.encodeFunctionData("setUp", [
taskArgs.owner,
taskArgs.executor,
taskArgs.token,
supply.address,
encodedData,
]);

const receipt = await Factory.deployModule(
Expand Down Expand Up @@ -129,7 +133,7 @@ task("deployMasterCopy", "deploy a master copy of Safe Exit").setAction(
async (_, hardhatRuntime) => {
const [caller] = await hardhatRuntime.ethers.getSigners();
console.log("Using the account:", caller.address);
const Module = await hardhatRuntime.ethers.getContractFactory("SafeExit");
const Module = await hardhatRuntime.ethers.getContractFactory("Exit");
const module = await Module.deploy(
AddressOne,
AddressOne,
Expand Down
46 changes: 21 additions & 25 deletions test/SafeExit.spec.ts → test/ExitModule.spec.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
import { expect } from "chai";
import { BigNumber } from "ethers";
import { AbiCoder } from "ethers/lib/utils";
import hre, { deployments, waffle } from "hardhat";

const AddressZero = "0x0000000000000000000000000000000000000000";
const DesignatedTokenBalance = BigNumber.from(10).pow(18).mul(5); // Equal to 5
const RandomTokenOneBalance = BigNumber.from(10).pow(6).mul(10); // Equal to 100
const RandomTokenTwoBalance = BigNumber.from(10).pow(12).mul(10); // Equal to 100

describe("SafeExit", async () => {
describe("Exit", async () => {
let initializeParams: string;
const [user, anotherUser] = waffle.provider.getWallets();

const setUpToken = deployments.createFixture(async () => {
Expand Down Expand Up @@ -40,6 +42,16 @@ describe("SafeExit", async () => {
DesignatedTokenBalance.mul(5)
);

initializeParams = new AbiCoder().encode(
["address", "address", "address", "address"],
[
executor.address,
executor.address,
token.designatedToken.address,
circulatingSupply.address,
]
);

return {
Executor,
executor,
Expand All @@ -52,64 +64,48 @@ describe("SafeExit", async () => {

const setupTestWithTestExecutor = deployments.createFixture(async () => {
const base = await baseSetup();
const Module = await hre.ethers.getContractFactory("SafeExit");
const Module = await hre.ethers.getContractFactory("Exit");
const module = await Module.deploy(
AddressZero,
AddressZero,
base.designatedToken.address,
base.circulatingSupply.address
);

await module.setUp(
base.executor.address,
base.executor.address,
base.designatedToken.address,
base.circulatingSupply.address
);
await module.setUp(initializeParams);

return { ...base, Module, module };
});

describe("setUp() ", () => {
it("throws if module has already been initialized", async () => {
const { designatedToken, circulatingSupply } = await baseSetup();
const Module = await hre.ethers.getContractFactory("SafeExit");
const Module = await hre.ethers.getContractFactory("Exit");
const module = await Module.deploy(
user.address,
user.address,
designatedToken.address,
circulatingSupply.address
);
await expect(
module.setUp(
user.address,
user.address,
designatedToken.address,
circulatingSupply.address
)
module.setUp(initializeParams)
).to.be.revertedWith("Module is already initialized");
});

it("should emit event because of successful set up", async () => {
const { designatedToken, executor, circulatingSupply } =
await baseSetup();
const Module = await hre.ethers.getContractFactory("SafeExit");
const { designatedToken, executor, circulatingSupply } = await baseSetup();
const Module = await hre.ethers.getContractFactory("Exit");
const module = await Module.deploy(
AddressZero,
AddressZero,
designatedToken.address,
circulatingSupply.address
);

const setupTx = await module.setUp(
executor.address,
executor.address,
designatedToken.address,
circulatingSupply.address
);
const setupTx = await module.setUp(initializeParams);
const transaction = await setupTx.wait();

const [initiator, safe] = transaction.events[0].args;
const [initiator, safe] = transaction.events[2].args;

expect(safe).to.be.equal(executor.address);
expect(initiator).to.be.equal(user.address);
Expand Down
Loading

0 comments on commit 635e5dc

Please sign in to comment.