Skip to content

Commit

Permalink
feat: add veto power [CHAIN-264] (#15)
Browse files Browse the repository at this point in the history
* add deploy * grant role flow diagram

* add multisig into config

* add multisig into config

* * add proposal diagram with TODO
* add veto power
* upd example config with fluence multisig entity
* add role grant on deploy script
* add test on feature

* typing

* enable tests

* add check that veto does not block governor

* add check that veto does not block
bump scheme

* bump version of mythx

* fix mythc

* add emergency withdraw for multisig (#16)

* add role test to Deploy.spec.ts
  • Loading branch information
AlcibiadesCleinias authored Jan 19, 2024
1 parent 085df6d commit f1a0879
Show file tree
Hide file tree
Showing 15 changed files with 375 additions and 49 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/audit.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -35,5 +35,5 @@ jobs:
- run: npm i
working-directory: contracts

- run: mythx --config .mythx.yml --api-key ${{ secrets.MYTHX_API_KEY }} analyze --remap-import "@openzeppelin=$(pwd)/node_modules/@openzeppelin" --remap-import "@uniswap=$(pwd)/node_modules/@uniswap"
- run: mythx --config .mythx.yml --api-key ${{ secrets.MYTHX_API_KEY }} analyze --solc-version 0.8.20 --remap-import "@openzeppelin=$(pwd)/node_modules/@openzeppelin" --remap-import "@uniswap=$(pwd)/node_modules/@uniswap"
working-directory: contracts
144 changes: 143 additions & 1 deletion contracts/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,148 @@
# Fluence DAO Contracts

This is solidity contracts for Fluence DAO.
This is solidity contracts for Fluence DAO. For DAO it uses OpenZeppelin contracts with modifications.

## Feature
- timelock DAO governor based on **TimelockControllerUpgradeable** and **GovernorUpgradeable** (OpenZeppelin contracts).
- everyone could **execute a proposal**
- **veto power**: Finally CANCELLER_ROLE is granted to `Governor` contract & `Fluence Multisig`. Veto could be applied after proposal is queued to execute (before it is executed).
- LBP Vesting with moving funds after specified time to Uniswap (TODO: write more precisely)
- 3 Vesting Contract (TODO: write more precisely)
- [0003_DevRewardDistributor.ts](deploy%2F0003_DevRewardDistributor.ts) (TODO: write more precisely)
- **FluenceToken** based on **ERC20VotesUpgradeable** (OpenZeppelin) for the DAO purposes.

## DAO Proposal Flow

TODO: add other options like: threshold not reached, etc.
```mermaid
sequenceDiagram
actor user
box Fluence Contracts
participant Governor
participant Executor as Executor (TimelockController)
end
participant FluenceMultisig
user -->> Governor: propose (..., data, description)
loop wait voting deplay
Governor ->> Governor: wait for votingDelay()
end
user -->> Governor: castVote (proposalId, {Against, For, Abstain})
Note over user,Governor: ...other votes...
loop wait voting period
Governor ->> Governor: wait for votingPeriod()
end
user -->>+ Governor: queue (..., data, description)
Governor ->>- Executor: scheduleBatch(..., data, salt [based on description])
opt veto
FluenceMultisig -->> Executor: cancel (..., data, description)
end
loop wait minDelay
Executor ->> Executor: wait for minDelay()
end
alt no veto
user -->>+ Governor: execute(..., data, description)
Governor ->>- Executor: check status
end
alt veto
user -->>+ Governor: execute(..., data, description)
Governor -x- Executor: check status: cancelled
end
```

## Deploy & Role Delegation Flow
Deploy Flow according to [deploy scripts](deploy).

```mermaid
sequenceDiagram
actor deployer
box Fluence Contracts
participant FluenceToken
participant LPController
participant Uniswap
participant DevRewardDistributor
participant FluenceVesting
participant InvestorsVesting
participant TeamVesting as Vesting with Voting
participant Executor as Executor (TimelockController)
participant Governor
end
participant FluenceMultisig
alt 0000_FluenceToken.ts
deployer ->> FluenceToken: deploy (totalSupply)
Note over FluenceToken: Owner: deployer
FluenceToken -->> deployer: mint (totalSupply)
end
alt 0001_Executor.ts
deployer ->> Executor: deploy (minDelay)
Note over Executor: Admin Role: deployer, Executor Role: [0x0]
end
alt 0002_LPController.ts
deployer ->>+ LPController: deploy (...Uniswap, balancer, Executor, FluenceToken, token, weights...)
Note over LPController: Owner: deployer, Withdraw address: Executor
LPController ->>- Uniswap: uniswapPositionManager.mint()
Note over Uniswap: Recipient: Executor
end
alt 0003_DevRewardDistributor.ts
deployer ->> DevRewardDistributor: deploy (FluenceToken, Executor, merkle root, halvePeriod, initialReward, claimingPeriod)
Note over DevRewardDistributor: unclaimed reward receiver: Executor
deployer ->>+ FluenceToken: transfer (totalRewards) to DevRewardDistributor
FluenceToken ->>- DevRewardDistributor: transfer (totalRewards)
end
alt Vesting
alt 0004_FluenceVesting.ts
deployer ->> FluenceVesting: deploy (FluenceToken, cliffDuration , vestingDuration , accounts , amounts)
deployer ->>+ FluenceToken: transfer (totalAmounts) to FluenceVesting
FluenceToken ->>- FluenceVesting: transfer (totalAmounts)
Note over FluenceVesting: could transfer(0x00, amount) to release FluenceToken for accounts accordingly: accounts
end
alt 0005_InvestorsVesting.ts
deployer ->> InvestorsVesting: deploy (FluenceToken, cliffDuration , vestingDuration , accounts , amounts)
deployer ->>+ FluenceToken: transfer (totalAmounts) to FluenceVesting
FluenceToken ->>- InvestorsVesting: transfer (totalAmounts)
Note over InvestorsVesting: could transfer(0x00, amount) to release FluenceToken for accounts accordingly: accounts
end
alt 0006_TeamVesting.ts
deployer ->> TeamVesting: deploy (FluenceToken, cliffDuration , vestingDuration , accounts , amounts)
deployer ->>+ FluenceToken: transfer (totalAmounts) to FluenceVesting
FluenceToken ->>- TeamVesting: transfer (totalAmounts)
Note over TeamVesting: could transfer(0x00, amount) to release FluenceToken for accounts accordingly: accounts
end
end
alt 0007_Governor.ts
deployer ->> Governor: deploy (FluenceToken, TeamVesting, Executor, quorum, initialVotingDelay, initialVotingPeriod, initialProposalThreshold)
deployer ->> Executor: grantRole(RPROPOSER_ROLE, Governor)
deployer ->> Executor: grantRole(CANCELLER_ROLE, Governor)
deployer ->> Executor: grantRole(CANCELLER_ROLE, FluenceMultisig)
deployer ->> Executor: revokeRole(ADMIN_ROLE, deployer)
deployer ->>+ FluenceToken: transfer (balance) to Governor
FluenceToken ->>- Governor: transfer (balance)
Note over Governor: Owner: Executor
Note over Executor: Admin Role: [0x0]
Note over Executor: Proposer Role: [Governor]
Note over Executor: Canceller Role: [Governor, FluenceMultisig]
end
```

## Develop

Expand Down
2 changes: 2 additions & 0 deletions contracts/config.example.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,5 @@ deployment:
votingDelayDays: 1
votingPeriodDays: 1
proposalThreshold: 100

fluenceMultisig: "0x0000000000000000000000000000000000000000"
34 changes: 27 additions & 7 deletions contracts/contracts/DevRewardDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,11 @@ contract DevRewardDistributor {
**/
Executor public immutable executor;

/**
* @notice Canceler address (e.g. FluenceMultisig)
**/
address public immutable canceler;

/**
* @notice Claiming end time
**/
Expand Down Expand Up @@ -82,17 +87,20 @@ contract DevRewardDistributor {
* @param _halvePeriod - period for dividing the reward
* @param _initialReward - initial user reward
* @param _claimingPeriod - claiming period
* @param _canceler - can cancel distribution, and withdraw to _executor.
**/
constructor(
FluenceToken _token,
Executor _executor,
bytes32 _merkleRoot,
uint256 _halvePeriod,
uint256 _initialReward,
uint256 _claimingPeriod
uint256 _claimingPeriod,
address _canceler
) {
token = _token;
executor = _executor;
canceler = _canceler;

merkleRoot = _merkleRoot;
halvePeriod = _halvePeriod;
Expand Down Expand Up @@ -149,15 +157,18 @@ contract DevRewardDistributor {
}

/**
* @notice used to move any remaining tokens out of the contract after expiration
* @notice used to move any remaining tokens out of the contract to Executor after expiration
**/
function transferUnclaimed() external whenClaimingIs(false) {
IERC20 rewardToken = IERC20(token); //gas saving

uint256 remainingBalance = rewardToken.balanceOf(address(this));
rewardToken.safeTransfer(address(executor), remainingBalance);
_withdraw();
}

emit TransferUnclaimed(remainingBalance);
/**
* @notice used to move any remaining tokens out of the contract to Executor (DAO) in emergency situation.
**/
function withdraw() external {
require(msg.sender == canceler, "Only canceler can withdraw");
_withdraw();
}

/**
Expand Down Expand Up @@ -207,4 +218,13 @@ contract DevRewardDistributor {
claimedBitMap[claimedWordIndex] |
(1 << claimedBitIndex);
}

function _withdraw() private {
IERC20 rewardToken = IERC20(token); //gas saving

uint256 remainingBalance = rewardToken.balanceOf(address(this));
rewardToken.safeTransfer(address(executor), remainingBalance);

emit TransferUnclaimed(remainingBalance);
}
}
2 changes: 1 addition & 1 deletion contracts/contracts/Governor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ contract Governor is
* @param teamVesting_ - team
* @param executor_ - DAO timelock contract
* @param quorum_ - minimum percentage of quorum to accept a proposal
* @param initialVotingDelay - delay beetween the creation of a proposal and the start of voting
* @param initialVotingDelay - delay between the creation of a proposal and the start of voting
* @param initialVotingPeriod - voting duration
* @param initialProposalThreshold - tokens threshold for creating a proposal
**/
Expand Down
1 change: 1 addition & 0 deletions contracts/deploy/0003_DevRewardDistributor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
Math.floor(
config.deployment!.devRewardDistributor!.claimingPeriodMonths * MONTH
),
config.fluenceMultisig!,
],
log: true,
autoMine: true,
Expand Down
3 changes: 2 additions & 1 deletion contracts/deploy/0007_Governor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
governorDeployment.address
);

// Grant role of proposal canceller to the Fluence multisig.
await hre.deployments.execute(
"Executor",
{
Expand All @@ -86,7 +87,7 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
},
"grantRole",
CANCELLER_ROLE,
governorDeployment.address
config.fluenceMultisig!
);

await hre.deployments.execute(
Expand Down
2 changes: 1 addition & 1 deletion contracts/hardhat.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ let config: Config | null = null;
try {
const file = fs.readFileSync("./config.yaml", "utf8");
const c = YAML.parse(file);
config = Config.get(c.networks, c.deployment);
config = Config.get(c.networks, c.deployment, c.fluenceMultisig);
} catch (e) {
console.log("No config file found");
}
Expand Down
15 changes: 10 additions & 5 deletions contracts/test/Deploy.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,17 +41,19 @@ describe("Deploy script", () => {
let fluenceVesting: Vesting;
let teamVesting: VestingWithVoting;
let governor: Governor;
let fluenceMultisig: ethers.Signer;

let config: Config;

const setupTest = deployments.createFixture(
async (hre: HardhatRuntimeEnvironment) => {
const hardhatSigners = await hre.ethers.getSigners();
usdToken = await new DevERC20__factory(
(
await ethers.getSigners()
)[0]
hardhatSigners[0]
).deploy("USD", "USD", ethers.utils.parseEther(String(lbpUSDAmount)));

fluenceMultisig = hardhatSigners[hardhatSigners.length - 1];

Config.reset(
{
etherscanApiKey: "",
Expand Down Expand Up @@ -121,7 +123,8 @@ describe("Deploy script", () => {
votingPeriodDays: 7,
proposalThreshold: 12,
},
}
},
fluenceMultisig.address
);

config = Config.get();
Expand Down Expand Up @@ -264,7 +267,7 @@ describe("Deploy script", () => {
//TODO: veify balancer
});

it("executor is correct", async () => {
it("executor roles are correct", async () => {
const { deployer } = await getNamedAccounts();

expect(await executor.getMinDelay()).to.eq(
Expand All @@ -290,6 +293,8 @@ describe("Deploy script", () => {
"0x0000000000000000000000000000000000000000"
)
).to.eq(true);

expect(await executor.hasRole(CANCELLER_ROLE, fluenceMultisig.address)).to.eq(true);
});

it("DevRewardDistributor is correct", async () => {
Expand Down
Loading

0 comments on commit f1a0879

Please sign in to comment.