Skip to content

Commit 4162a4b

Browse files
author
valentinpollart
committed
fix: meet pr changes requirements
1 parent 3b086b9 commit 4162a4b

26 files changed

+679
-1874
lines changed

contracts/Ballot.sol

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@ pragma solidity ^0.8.0;
55
import "@openzeppelin/contracts/access/Ownable.sol";
66
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
77
import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
8-
import "./VotingProxy.sol";
8+
import "./VotingDelegation.sol";
99

1010
contract Ballot is Ownable, Initializable {
1111
IERC20Metadata public immutable DPS;
12-
VotingProxy public proxy;
12+
VotingDelegation public proxy;
1313

1414
struct Vote {
1515
uint32 choiceIndex;
@@ -18,25 +18,26 @@ contract Ballot is Ownable, Initializable {
1818

1919
string public subject;
2020
bool public closed;
21-
uint32 public tagIndex;
21+
string public topic;
2222
string[] public choices;
2323
uint256[] public resultStorage;
24+
uint256 immutable votingLimit = 25e3 * 1e18;
2425

2526
address[] internal voters;
2627
mapping(address => Vote) internal votes;
2728

2829
constructor(
2930
IERC20Metadata _DPS,
30-
VotingProxy _proxy
31+
VotingDelegation _proxy
3132
) {
3233
require(address(_DPS) != address(0), "Vote: DPS address is zero.");
3334
DPS = _DPS;
3435
proxy = _proxy;
3536
}
3637

37-
function init(string memory _subject, uint32 _tagIndex, string[] memory _choices) public initializer {
38+
function init(string memory _subject, string memory _topic, string[] memory _choices) public initializer {
3839
subject = _subject;
39-
tagIndex = _tagIndex;
40+
topic = _topic;
4041
closed = false;
4142
choices = _choices;
4243
resultStorage = new uint256[](choices.length);
@@ -54,9 +55,9 @@ contract Ballot is Ownable, Initializable {
5455
require(!closed, "Voting: Ballot is closed.");
5556
require(choices.length > choiceIndex, "Voting: Choice index is too high.");
5657

57-
require(!proxy.hasDelegated(msg.sender,tagIndex), "Voting: Vote is delegated."); // Verify that voter has not granted proxy to somebody.
58+
require(!proxy.hasDelegated(msg.sender,topic), "Voting: Vote is delegated."); // Verify that voter has not granted proxy to somebody.
5859

59-
require(DPS.balanceOf(msg.sender) >= 25e3 * 1e18, "Voting: Not enough DPS to vote."); // 25k DPS limit
60+
require(DPS.balanceOf(msg.sender) >= votingLimit, "Voting: Not enough DPS to vote."); // 25k DPS limit
6061

6162
if(!votes[msg.sender].hasVoted) {
6263
votes[msg.sender].hasVoted = true;
@@ -66,14 +67,14 @@ contract Ballot is Ownable, Initializable {
6667
votes[msg.sender].choiceIndex = choiceIndex;
6768
}
6869

69-
function closeBallot() external onlyOwner {
70+
function close() external onlyOwner {
7071
require(!closed, "Voting: Ballot already closed.");
7172

7273
closed = true;
7374

7475
for(uint i = 0; i < voters.length; i++) { // if A has granted proxy to B
7576
address voter = voters[i];
76-
resultStorage[votes[voter].choiceIndex] += DPS.balanceOf(voter) + proxy.proxyAmount(voter, tagIndex);
77+
resultStorage[votes[voter].choiceIndex] += DPS.balanceOf(voter) + proxy.delegationAmount(voter, topic);
7778
}
7879
}
7980

contracts/BallotTagManager.sol

Lines changed: 0 additions & 18 deletions
This file was deleted.

contracts/VotingDelegation.sol

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
// SPDX-License-Identifier: MIT
2+
3+
pragma solidity ^0.8.0;
4+
5+
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
6+
import "./factories/BallotFactory.sol";
7+
8+
contract VotingDelegation is Ownable {
9+
IERC20Metadata public immutable DPS;
10+
BallotFactory public ballotFactory;
11+
12+
struct Grants {
13+
mapping(address => uint256) indexes;
14+
address[] delegators;
15+
}
16+
17+
uint256 immutable delegatingLimit = 25e3 * 1e18;
18+
19+
mapping(address => mapping(bytes32 => Grants)) internal delegates; // proxy => tag => voters
20+
21+
mapping(address => mapping(bytes32 => address)) internal proxyVoters; // voter => tag => proxy
22+
23+
constructor(IERC20Metadata _DPS) {
24+
require(address(_DPS) != address(0), "VotingDelegation: DPS address is zero.");
25+
26+
DPS = _DPS;
27+
}
28+
29+
function delegate(address to, string memory topic) external {
30+
require(DPS.balanceOf(to) >= delegatingLimit || to == address(0), "VotingDelegation: Proxy has not enough DPS.");
31+
bytes32 topicHash = keccak256(bytes(topic));
32+
33+
if(proxyVoters[msg.sender][topicHash] != address(0)) {
34+
Grants storage formerDelegateGrants = delegates[proxyVoters[msg.sender][topicHash]][topicHash];
35+
uint256 senderIndex = formerDelegateGrants.indexes[msg.sender];
36+
formerDelegateGrants.delegators[senderIndex] = formerDelegateGrants.delegators[formerDelegateGrants.delegators.length - 1];
37+
formerDelegateGrants.delegators.pop();
38+
formerDelegateGrants.indexes[msg.sender] = 0;
39+
}
40+
41+
proxyVoters[msg.sender][topicHash] = to;
42+
43+
if(to != address(0)) {
44+
Grants storage newDelegateGrants = delegates[to][topicHash];
45+
newDelegateGrants.indexes[msg.sender] = newDelegateGrants.delegators.length;
46+
newDelegateGrants.delegators.push(msg.sender);
47+
}
48+
}
49+
50+
function delegationAmount(address voter, string memory topic) public view returns (uint256) {
51+
uint256 total;
52+
bytes32 topicHash = keccak256(bytes(topic));
53+
for(uint32 i = 0; i < delegates[voter][topicHash].delegators.length; i++) {
54+
total += DPS.balanceOf(delegates[voter][topicHash].delegators[i]);
55+
}
56+
return total;
57+
}
58+
59+
function hasDelegated(address voter, string memory topic) external view returns (bool) {
60+
return proxyVoters[voter][keccak256(bytes(topic))] != address(0);
61+
}
62+
63+
function delegators(address to, string memory topic) external view returns(address[] memory) {
64+
bytes32 topicHash = keccak256(bytes(topic));
65+
address[] memory proxies = new address[](delegates[to][topicHash].delegators.length);
66+
for(uint32 i = 0; i < delegates[to][topicHash].delegators.length; i++) {
67+
proxies[i] = delegates[to][topicHash].delegators[i];
68+
}
69+
return proxies;
70+
}
71+
72+
function representative(address from, string memory topic) external view returns(address) {
73+
return proxyVoters[from][keccak256(bytes(topic))];
74+
}
75+
}

contracts/VotingProxy.sol

Lines changed: 0 additions & 66 deletions
This file was deleted.

contracts/factories/BallotFactory.sol

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,22 @@ pragma solidity ^0.8.0;
55
import "@openzeppelin/contracts/access/Ownable.sol";
66
import "@openzeppelin/contracts/proxy/Clones.sol";
77
import "../Ballot.sol";
8-
import "../VotingProxy.sol";
9-
import "../BallotTagManager.sol";
8+
import "../VotingDelegation.sol";
109

1110
contract BallotFactory is Ownable {
1211
address[] public ballotAddresses;
1312
address public implementationAddress;
14-
BallotTagManager public ballotTagManager;
1513

1614
event BallotCreated(address ballotAddress);
1715

18-
constructor(address _implementationAddress, BallotTagManager _ballotTagManager){
16+
constructor(address _implementationAddress){
1917
require(_implementationAddress != address(0), "BallotFactory: Implementation address should not be zero address");
20-
require(address(_ballotTagManager) != address(0), "BallotFactory: Ballot tag manager address should not be zero address");
2118
implementationAddress = _implementationAddress;
22-
ballotTagManager = _ballotTagManager;
2319
}
2420

25-
function createBallot(string memory subject, uint32 tagIndex, string[] memory _choices) external onlyOwner {
26-
require(ballotTagManager.getTags().length > tagIndex, "BallotFactory: Tag index is too high.");
27-
21+
function createBallot(string memory subject, string memory topic, string[] memory _choices) external onlyOwner {
2822
address cloneAddress = Clones.clone(implementationAddress);
29-
Ballot(cloneAddress).init(subject, tagIndex, _choices);
23+
Ballot(cloneAddress).init(subject, topic, _choices);
3024

3125
ballotAddresses.push(cloneAddress);
3226
emit BallotCreated(cloneAddress);

contracts/testing/ExposedBallot.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ import "../Ballot.sol";
66

77
contract ExposedBallot is Ballot {
88

9-
constructor(IERC20Metadata _DPS, VotingProxy _proxy) Ballot(_DPS, _proxy) {}
9+
constructor(IERC20Metadata _DPS, VotingDelegation _proxy) Ballot(_DPS, _proxy) {}
1010

1111
struct ResultSample {
1212
address voter;

contracts/testing/ExposedVotingProxy.sol

Lines changed: 0 additions & 21 deletions
This file was deleted.

test/Ballot.spec.ts

Lines changed: 17 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { BigNumber } from '@ethersproject/bignumber';
33
import { parseUnits } from '@ethersproject/units';
44
import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
55
import { ZERO_ADDRESS } from '../lib/constants';
6-
import { BallotTagManager, DeepSquare, ExposedBallot, ExposedBallot__factory, ExposedVotingProxy } from '../typings';
6+
import { DeepSquare, ExposedBallot, ExposedBallot__factory, VotingDelegation } from '../typings';
77
import { ERC20Agent } from './testing/ERC20Agent';
88
import setup from './testing/setup';
99
import setupVoting from './testing/setupVoting';
@@ -14,51 +14,49 @@ describe('Ballot', () => {
1414
let DPS: DeepSquare;
1515
let ballot: ExposedBallot;
1616
let agentDPS: ERC20Agent;
17-
let ballotTagManager: BallotTagManager;
18-
let votingProxy: ExposedVotingProxy;
17+
let votingDelegation: VotingDelegation;
1918

2019
beforeEach(async () => {
2120
({ owner, accounts, DPS, agentDPS } = await setup());
22-
({ ballotTagManager, votingProxy } = await setupVoting(owner, DPS));
21+
({ votingDelegation } = await setupVoting(owner, DPS));
2322

24-
ballot = await new ExposedBallot__factory(owner).deploy(DPS.address, votingProxy.address);
23+
ballot = await new ExposedBallot__factory(owner).deploy(DPS.address, votingDelegation.address);
2524
});
2625

2726
describe('constructor', () => {
2827
it('should revert if the DPS contract is the zero address', async () => {
29-
await expect(new ExposedBallot__factory(owner).deploy(ZERO_ADDRESS, votingProxy.address)).to.be.revertedWith(
28+
await expect(new ExposedBallot__factory(owner).deploy(ZERO_ADDRESS, votingDelegation.address)).to.be.revertedWith(
3029
'Vote: DPS address is zero.',
3130
);
3231
});
3332
});
3433

3534
describe('init', () => {
3635
it('should initialize ballot state variables', async () => {
37-
await ballot.init('foo', BigNumber.from(0), ['bar', 'baz']);
36+
await ballot.init('foo', 'bar', ['baz', 'qux']);
3837
expect(await ballot.subject()).to.equals('foo');
39-
expect(await ballot.tagIndex()).to.equals(BigNumber.from(0));
40-
expect(await ballot.getChoices()).to.deep.equals(['bar', 'baz']);
38+
expect(await ballot.topic()).to.equals('bar');
39+
expect(await ballot.getChoices()).to.deep.equals(['baz', 'qux']);
4140
expect(await ballot.getResults()).to.deep.equals([BigNumber.from(0), BigNumber.from(0)]);
4241
});
4342
});
4443

4544
describe('vote', () => {
4645
beforeEach(async () => {
47-
await ballotTagManager.addTag('foo');
48-
await ballot.init('foo', BigNumber.from(0), ['bar', 'baz']);
46+
await ballot.init('foo', 'qux', ['bar', 'baz']);
4947
});
5048
it('should throw if ballot is closed', async () => {
51-
await ballot.closeBallot();
49+
await ballot.close();
5250
await expect(ballot.connect(accounts[0]).vote(BigNumber.from(0))).to.revertedWith('Voting: Ballot is closed.');
5351
});
5452
it('should throw if proposal does not exist', async () => {
5553
await expect(ballot.connect(accounts[0]).vote(BigNumber.from(2))).to.revertedWith(
5654
'Voting: Choice index is too high.',
5755
);
5856
});
59-
it('should throw if voter has granted proxy on the tag', async () => {
57+
it('should throw if voter has granted proxy on the topic', async () => {
6058
await agentDPS.transfer(accounts[1], 25000, 18);
61-
await votingProxy.connect(accounts[0]).grantProxy(accounts[1].address, BigNumber.from(0));
59+
await votingDelegation.connect(accounts[0]).delegate(accounts[1].address, 'qux');
6260
await expect(ballot.connect(accounts[0]).vote(BigNumber.from(0))).to.revertedWith('Voting: Vote is delegated.');
6361
});
6462
it('should throw if voter has less than 25k DPS', async () => {
@@ -77,21 +75,20 @@ describe('Ballot', () => {
7775

7876
describe('closeBallot', async () => {
7977
beforeEach(async () => {
80-
await ballotTagManager.addTag('foo');
81-
await ballot.init('foo', BigNumber.from(0), ['bar', 'baz']);
78+
await ballot.init('foo', 'qux', ['bar', 'baz']);
8279
});
8380
it('should throw if ballot is not closed', async () => {
84-
await ballot.closeBallot();
85-
await expect(ballot.closeBallot()).to.revertedWith('Voting: Ballot already closed.');
81+
await ballot.close();
82+
await expect(ballot.close()).to.revertedWith('Voting: Ballot already closed.');
8683
});
8784
it('should show results', async () => {
8885
await agentDPS.transfer(accounts[0], 25000, 18);
8986
await agentDPS.transfer(accounts[1], 25000, 18);
9087
await agentDPS.transfer(accounts[2], 25000, 18);
91-
await votingProxy.connect(accounts[2]).grantProxy(accounts[1].address, BigNumber.from(0));
88+
await votingDelegation.connect(accounts[2]).delegate(accounts[1].address, 'qux');
9289
await ballot.connect(accounts[0]).vote(BigNumber.from(0));
9390
await ballot.connect(accounts[1]).vote(BigNumber.from(1));
94-
await ballot.closeBallot();
91+
await ballot.close();
9592
expect(await ballot.getResults()).to.deep.equals([parseUnits('25000', 18), parseUnits('50000', 18)]);
9693
});
9794
});

0 commit comments

Comments
 (0)