Skip to content

Commit cc98a7c

Browse files
authored
Add IndexPowah contract for counting Snapshot votes (#51)
* add indexpowah and tests * fix tests * add investor and dex LP votes * fix failed dependency tests * count votes in onsen * add owner that can add new farms and vesting contracts * add clarity in comments for permissioned functions * improve comments * add updateMasterChef function
1 parent 3b8e8af commit cc98a7c

File tree

12 files changed

+762
-6
lines changed

12 files changed

+762
-6
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
pragma solidity 0.6.10;
2+
3+
interface IMasterChef {
4+
function userInfo(uint256 nr, address who) external view returns (uint256, uint256);
5+
}

contracts/interfaces/IPair.sol

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
pragma solidity 0.6.10;
2+
3+
interface IPair {
4+
event Approval(address indexed owner, address indexed spender, uint value);
5+
event Transfer(address indexed from, address indexed to, uint value);
6+
7+
function name() external pure returns (string memory);
8+
function symbol() external pure returns (string memory);
9+
function decimals() external pure returns (uint8);
10+
function totalSupply() external view returns (uint);
11+
function balanceOf(address owner) external view returns (uint);
12+
function allowance(address owner, address spender) external view returns (uint);
13+
14+
function approve(address spender, uint value) external returns (bool);
15+
function transfer(address to, uint value) external returns (bool);
16+
function transferFrom(address from, address to, uint value) external returns (bool);
17+
18+
function DOMAIN_SEPARATOR() external view returns (bytes32);
19+
function PERMIT_TYPEHASH() external pure returns (bytes32);
20+
function nonces(address owner) external view returns (uint);
21+
22+
function permit(address owner, address spender, uint value, uint deadline, uint8 v, bytes32 r, bytes32 s) external;
23+
24+
event Mint(address indexed sender, uint amount0, uint amount1);
25+
event Burn(address indexed sender, uint amount0, uint amount1, address indexed to);
26+
event Swap(
27+
address indexed sender,
28+
uint amount0In,
29+
uint amount1In,
30+
uint amount0Out,
31+
uint amount1Out,
32+
address indexed to
33+
);
34+
event Sync(uint112 reserve0, uint112 reserve1);
35+
36+
function MINIMUM_LIQUIDITY() external pure returns (uint);
37+
function factory() external view returns (address);
38+
function token0() external view returns (address);
39+
function token1() external view returns (address);
40+
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 blockTimestampLast);
41+
function price0CumulativeLast() external view returns (uint);
42+
function price1CumulativeLast() external view returns (uint);
43+
function kLast() external view returns (uint);
44+
45+
function mint(address to) external returns (uint liquidity);
46+
function burn(address to) external returns (uint amount0, uint amount1);
47+
function swap(uint amount0Out, uint amount1Out, address to, bytes calldata data) external;
48+
function skim(address to) external;
49+
function sync() external;
50+
51+
function initialize(address, address) external;
52+
}

contracts/mocks/MasterChefMock.sol

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
pragma solidity 0.6.10;
2+
3+
contract MasterChefMock {
4+
5+
uint256 private amount;
6+
7+
constructor() public {
8+
amount = 0;
9+
}
10+
11+
function userInfo(uint256 _id, address _user) external view returns (uint256, uint256) {
12+
if (_id == 75) return (amount, 0);
13+
return (0, 0);
14+
}
15+
16+
function setAmount(uint256 _amount) external {
17+
amount = _amount;
18+
}
19+
}

contracts/token/IndexPowah.sol

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
/*
2+
Copyright 2021 Set Labs Inc.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
pragma solidity 0.6.10;
18+
19+
20+
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
21+
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
22+
import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol";
23+
24+
import { StakingRewardsV2 } from "../staking/StakingRewardsV2.sol";
25+
import { IMasterChef } from "../interfaces/IMasterChef.sol";
26+
import { IPair } from "../interfaces/IPair.sol";
27+
import { Vesting } from "./Vesting.sol";
28+
29+
30+
/**
31+
* @title IndexPowah
32+
* @author Set Protocol
33+
*
34+
* An ERC20 token used for tracking the voting power for the Index Coop. The mutative functions of
35+
* the ERC20 interface have been disabled since the token is only designed to count votes for the
36+
* sake of utilizing Snapshot's erc20-balance-of strategy. This contract is inspired by Sushiswap's
37+
* SUSHIPOWAH contract which serves the same purpose.
38+
*/
39+
contract IndexPowah is IERC20, Ownable {
40+
41+
using SafeMath for uint256;
42+
43+
IERC20 public indexToken;
44+
45+
IMasterChef public masterChef;
46+
uint256 public masterChefId;
47+
IPair public uniPair;
48+
IPair public sushiPair;
49+
50+
StakingRewardsV2[] public farms;
51+
Vesting[] public vesting;
52+
53+
/**
54+
* Sets the appropriate state variables for the contract.
55+
*
56+
* @param _owner owner of this contract
57+
* @param _indexToken Index Coop's governance token contract
58+
* @param _uniPair INDEX-WETH Uniswap pair
59+
* @param _sushiPair INDEX-WETH Sushiswap pair
60+
* @param _masterChef Sushiswap MasterChef (Onsen) contract
61+
* @param _farms array of Index Coop staking farms
62+
* @param _vesting array of vesting contracts from the index sale and full time contributors
63+
*/
64+
constructor(
65+
address _owner,
66+
IERC20 _indexToken,
67+
IPair _uniPair,
68+
IPair _sushiPair,
69+
IMasterChef _masterChef,
70+
uint256 _masterChefId,
71+
StakingRewardsV2[] memory _farms,
72+
Vesting[] memory _vesting
73+
)
74+
public
75+
{
76+
indexToken = _indexToken;
77+
uniPair = _uniPair;
78+
sushiPair = _sushiPair;
79+
masterChef = _masterChef;
80+
masterChefId = _masterChefId;
81+
farms = _farms;
82+
vesting = _vesting;
83+
84+
transferOwnership(_owner);
85+
}
86+
87+
/**
88+
* Computes an address's balance of IndexPowah. Balances can not be transfered in the traditional way,
89+
* but are instead computed by the amount of index that an account directly hold, or indirectly holds
90+
* through the staking contracts, vesting contracts, uniswap, and sushiswap.
91+
*
92+
* @param _account the address of the voter
93+
*/
94+
function balanceOf(address _account) public view override returns (uint256) {
95+
uint256 indexAmount = indexToken.balanceOf(_account);
96+
uint256 unclaimedInFarms = _getFarmVotes(_account);
97+
uint256 vestingVotes = _getVestingVotes(_account);
98+
uint256 dexVotes = _getDexVotes(_account, uniPair) + _getDexVotes(_account, sushiPair) + _getMasterChefVotes(_account);
99+
100+
return indexAmount + unclaimedInFarms + vestingVotes + dexVotes;
101+
}
102+
103+
/**
104+
* ONLY OWNER: Adds new Index farms to be tracked
105+
*
106+
* @param _newFarms list of new farms to be tracked
107+
*/
108+
function addFarms(StakingRewardsV2[] calldata _newFarms) external onlyOwner {
109+
for (uint256 i = 0; i < _newFarms.length; i++) {
110+
farms.push(_newFarms[0]);
111+
}
112+
}
113+
114+
/**
115+
* ONLY OWNER: Adds new Index vesting contracts to be tracked
116+
*
117+
* @param _newVesting list of new vesting contracts to be tracked
118+
*/
119+
function addVesting(Vesting[] calldata _newVesting) external onlyOwner {
120+
for (uint256 i = 0; i < _newVesting.length; i++) {
121+
vesting.push(_newVesting[i]);
122+
}
123+
}
124+
125+
/**
126+
* ONLY OWNER: Updates the MasterChef contract and pool ID
127+
*
128+
* @param _newMasterChef address of the new MasterChef contract
129+
* @param _newMasterChefId new pool id for the index-eth MasterChef rewards
130+
*/
131+
function updateMasterChef(IMasterChef _newMasterChef, uint256 _newMasterChefId) external onlyOwner {
132+
masterChef = _newMasterChef;
133+
masterChefId = _newMasterChefId;
134+
}
135+
136+
function _getFarmVotes(address _account) internal view returns (uint256) {
137+
uint256 sum = 0;
138+
for (uint256 i = 0; i < farms.length; i++) {
139+
sum += farms[i].earned(_account);
140+
}
141+
return sum;
142+
}
143+
144+
function _getVestingVotes(address _account) internal view returns (uint256) {
145+
uint256 sum = 0;
146+
for (uint256 i = 0; i < vesting.length; i++) {
147+
if(vesting[i].recipient() == _account) {
148+
sum += indexToken.balanceOf(address(vesting[i]));
149+
}
150+
}
151+
return sum;
152+
}
153+
154+
function _getDexVotes(address _account, IPair pair) internal view returns (uint256) {
155+
uint256 lpBalance = pair.balanceOf(_account);
156+
return _getDexVotesFromBalance(lpBalance, pair);
157+
}
158+
159+
function _getMasterChefVotes(address _account) internal view returns (uint256) {
160+
(uint256 lpBalance,) = masterChef.userInfo(masterChefId, _account);
161+
return _getDexVotesFromBalance(lpBalance, sushiPair);
162+
}
163+
164+
function _getDexVotesFromBalance(uint256 lpBalance, IPair pair) internal view returns (uint256) {
165+
uint256 lpIndex = indexToken.balanceOf(address(pair));
166+
uint256 lpTotal = pair.totalSupply();
167+
if (lpTotal == 0) return 0;
168+
return lpIndex.mul(lpBalance).div(lpTotal);
169+
}
170+
171+
172+
/**
173+
* These functions are not used, but have been left in to keep the token ERC20 compliant
174+
*/
175+
function name() public pure returns (string memory) { return "INDEXPOWAH"; }
176+
function symbol() public pure returns (string memory) { return "INDEXPOWAH"; }
177+
function decimals() public pure returns(uint8) { return 18; }
178+
function totalSupply() public view override returns (uint256) { return indexToken.totalSupply(); }
179+
function allowance(address, address) public view override returns (uint256) { return 0; }
180+
function transfer(address, uint256) public override returns (bool) { return false; }
181+
function approve(address, uint256) public override returns (bool) { return false; }
182+
function transferFrom(address, address, uint256) public override returns (bool) { return false; }
183+
}

0 commit comments

Comments
 (0)