Skip to content

Commit 5dca122

Browse files
author
valentinpollart
committed
feat: add deployment script, fix several bugs
1 parent e566ace commit 5dca122

File tree

13 files changed

+220
-33
lines changed

13 files changed

+220
-33
lines changed

contracts/Ballot.sol

+8-3
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,9 @@ import "@openzeppelin/contracts/proxy/utils/Initializable.sol";
88
import "./VotingDelegation.sol";
99

1010
contract Ballot is Ownable, Initializable {
11-
IERC20Metadata public immutable DPS;
11+
IERC20Metadata public DPS;
1212
VotingDelegation public proxy;
13+
BallotFactory public factory;
1314

1415
struct Vote {
1516
uint32 choiceIndex;
@@ -35,12 +36,15 @@ contract Ballot is Ownable, Initializable {
3536
proxy = _proxy;
3637
}
3738

38-
function init(string memory _subject, string memory _topic, string[] memory _choices) public initializer {
39+
function init(IERC20Metadata _DPS, VotingDelegation _proxy, BallotFactory _factory, string memory _subject, string memory _topic, string[] memory _choices) public initializer {
3940
subject = _subject;
4041
topic = _topic;
4142
closed = false;
4243
choices = _choices;
4344
resultStorage = new uint256[](choices.length);
45+
DPS = _DPS;
46+
proxy = _proxy;
47+
factory = _factory;
4448
}
4549

4650
function getChoices() external view returns(string[] memory) {
@@ -67,7 +71,8 @@ contract Ballot is Ownable, Initializable {
6771
votes[msg.sender].choiceIndex = choiceIndex;
6872
}
6973

70-
function close() external onlyOwner {
74+
function close() external {
75+
require(msg.sender == factory.owner(), "Voting: Restricted to factory owner.");
7176
require(!closed, "Voting: Ballot already closed.");
7277

7378
closed = true;

contracts/factories/BallotFactory.sol

+8-4
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,29 @@
33
pragma solidity ^0.8.0;
44

55
import "@openzeppelin/contracts/access/Ownable.sol";
6+
import "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
67
import "@openzeppelin/contracts/proxy/Clones.sol";
78
import "../Ballot.sol";
89
import "../VotingDelegation.sol";
910

1011
contract BallotFactory is Ownable {
12+
IERC20Metadata public DPS;
1113
address[] public ballotAddresses;
1214
address public implementationAddress;
1315

1416
event BallotCreated(address ballotAddress);
1517

16-
constructor(address _implementationAddress){
18+
constructor(IERC20Metadata _DPS, address _implementationAddress){
19+
require(address(_DPS) != address(0), "BallotFactory: Implementation address should not be zero address");
1720
require(_implementationAddress != address(0), "BallotFactory: Implementation address should not be zero address");
18-
implementationAddress = _implementationAddress;
21+
DPS = _DPS;
22+
implementationAddress = _implementationAddress;
1923
}
2024

2125
function createBallot(string memory subject, string memory topic, string[] memory _choices) external onlyOwner {
26+
Ballot implementation = Ballot(implementationAddress);
2227
address cloneAddress = Clones.clone(implementationAddress);
23-
Ballot(cloneAddress).init(subject, topic, _choices);
24-
28+
Ballot(cloneAddress).init(implementation.DPS(), implementation.proxy(), this, subject, topic, _choices);
2529
ballotAddresses.push(cloneAddress);
2630
emit BallotCreated(cloneAddress);
2731
}

scripts/deploy-vote.ts

+26-11
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { ethers, network } from 'hardhat';
2-
import { parseUnits } from '@ethersproject/units';
3-
import { DeepSquare__factory } from '../typings';
2+
import {formatUnits, parseUnits} from '@ethersproject/units';
43
import { Ballot__factory } from '../typings/factories/contracts/Ballot__factory';
4+
import { DeepSquare__factory } from '../typings/factories/contracts/DeepSquare__factory';
55
import { VotingDelegation__factory } from '../typings/factories/contracts/VotingDelegation__factory';
66
import { BallotFactory__factory } from '../typings/factories/contracts/factories/BallotFactory__factory';
77

@@ -18,7 +18,7 @@ const addresses: Record<ContractName, Record<NetworkName, string>> = {
1818

1919
async function main() {
2020
const networkName = network.name as NetworkName;
21-
const [deployer, ...accounts] = await ethers.getSigners();
21+
const [deployer, dpsHolder, ...accounts] = await ethers.getSigners();
2222

2323
const DeepSquareFactory = new DeepSquare__factory(deployer);
2424
const DeepSquare = DeepSquareFactory.attach(addresses.DeepSquare[networkName]);
@@ -28,9 +28,17 @@ async function main() {
2828
console.log('deployer:', deployer.address);
2929
console.log('gnosis:', gnosisAddress);
3030

31+
const avaxBalance = await deployer.getBalance();
32+
3133
const votingDelegation = await new VotingDelegation__factory(deployer).deploy(DeepSquare.address);
34+
console.log('votingDelegation:', votingDelegation.address);
3235
const ballotImplementation = await new Ballot__factory(deployer).deploy(DeepSquare.address, votingDelegation.address);
33-
const ballotFactory = await new BallotFactory__factory(deployer).deploy(ballotImplementation.address);
36+
console.log('ballotImplementation:', ballotImplementation.address);
37+
const ballotFactory = await new BallotFactory__factory(deployer).deploy(
38+
DeepSquare.address,
39+
ballotImplementation.address,
40+
);
41+
console.log('ballotFactory:', ballotFactory.address);
3442

3543
if (networkName === 'fuji') {
3644
const ballotCreationTransaction = await ballotFactory.createBallot(
@@ -39,14 +47,16 @@ async function main() {
3947
['Yes', 'No'],
4048
);
4149
const cloneAddress: string = (await ballotCreationTransaction.wait()).events?.pop()?.args?.[0];
42-
console.log('Voting clone deploy at: ', cloneAddress);
50+
console.log('Voting clone deployed at:', cloneAddress);
4351
const clone = new Ballot__factory(deployer).attach(cloneAddress);
44-
await DeepSquare.connect(gnosisAddress).transfer(deployer.address, parseUnits('50000', 18));
45-
await clone.vote(0);
46-
await DeepSquare.connect(gnosisAddress).transfer(accounts[0].address, parseUnits('25000', 18));
47-
await clone.connect(accounts[0].address).vote(0);
48-
await DeepSquare.connect(gnosisAddress).transfer(accounts[1].address, parseUnits('25000', 18));
49-
await clone.connect(accounts[1].address).vote(1);
52+
53+
await DeepSquare.connect(dpsHolder).transfer(accounts[0].address, parseUnits('50000', 18));
54+
await clone.connect(accounts[0]).vote(0);
55+
console.log('Voting with:' + accounts[0].address);
56+
57+
await DeepSquare.connect(dpsHolder).transfer(accounts[1].address, parseUnits('25000', 18));
58+
await clone.connect(accounts[1]).vote(1);
59+
console.log('Voting with:' + accounts[1].address);
5060

5161
await clone.close();
5262
console.log('Vote results: ');
@@ -57,8 +67,13 @@ async function main() {
5767
}
5868

5969
await votingDelegation.transferOwnership(gnosisAddress);
70+
console.log('Transferred voting delegation contract ownership to ' + gnosisAddress);
6071
await ballotImplementation.renounceOwnership();
72+
console.log('Renounced to ballot implementation ownership.');
6173
await ballotFactory.transferOwnership(gnosisAddress);
74+
console.log('Transferred ballot factory contract ownership to ' + gnosisAddress);
75+
76+
console.log('Deployment cost : ' + formatUnits(avaxBalance.sub(await deployer.getBalance()), 18));
6277
}
6378

6479
main().catch((e) => {

test/Ballot.spec.ts

+9-4
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { SignerWithAddress } from '@nomiclabs/hardhat-ethers/signers';
55
import { ZERO_ADDRESS } from '../lib/constants';
66
import { DeepSquare } from '../typings/contracts/DeepSquare';
77
import { VotingDelegation } from '../typings/contracts/VotingDelegation';
8+
import { BallotFactory } from '../typings/contracts/factories/BallotFactory';
89
import { ExposedBallot } from '../typings/contracts/testing/ExposedBallot';
910
import { ExposedBallot__factory } from '../typings/factories/contracts/testing/ExposedBallot__factory';
1011
import { ERC20Agent } from './testing/ERC20Agent';
@@ -18,10 +19,11 @@ describe('Ballot', () => {
1819
let ballot: ExposedBallot;
1920
let agentDPS: ERC20Agent;
2021
let votingDelegation: VotingDelegation;
22+
let ballotFactory: BallotFactory;
2123

2224
beforeEach(async () => {
2325
({ owner, accounts, DPS, agentDPS } = await setup());
24-
({ votingDelegation } = await setupVoting(owner, DPS));
26+
({ votingDelegation, ballotFactory } = await setupVoting(owner, DPS));
2527

2628
ballot = await new ExposedBallot__factory(owner).deploy(DPS.address, votingDelegation.address);
2729
});
@@ -36,7 +38,7 @@ describe('Ballot', () => {
3638

3739
describe('init', () => {
3840
it('should initialize ballot state variables', async () => {
39-
await ballot.init('foo', 'bar', ['baz', 'qux']);
41+
await ballot.init(DPS.address, votingDelegation.address, ballotFactory.address, 'foo', 'bar', ['baz', 'qux']);
4042
expect(await ballot.subject()).to.equals('foo');
4143
expect(await ballot.topic()).to.equals('bar');
4244
expect(await ballot.getChoices()).to.deep.equals(['baz', 'qux']);
@@ -46,7 +48,7 @@ describe('Ballot', () => {
4648

4749
describe('vote', () => {
4850
beforeEach(async () => {
49-
await ballot.init('foo', 'qux', ['bar', 'baz']);
51+
await ballot.init(DPS.address, votingDelegation.address, ballotFactory.address, 'foo', 'qux', ['bar', 'baz']);
5052
});
5153
it('should throw if ballot is closed', async () => {
5254
await ballot.close();
@@ -78,7 +80,10 @@ describe('Ballot', () => {
7880

7981
describe('closeBallot', async () => {
8082
beforeEach(async () => {
81-
await ballot.init('foo', 'qux', ['bar', 'baz']);
83+
await ballot.init(DPS.address, votingDelegation.address, ballotFactory.address, 'foo', 'qux', ['bar', 'baz']);
84+
});
85+
it('should throw if is not the factory owner', async () => {
86+
await expect(ballot.connect(accounts[0]).close()).to.revertedWith('Voting: Restricted to factory owner.');
8287
});
8388
it('should throw if ballot is not closed', async () => {
8489
await ballot.close();

test/BallotFactory.spec.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,12 @@ describe('Ballot Factory', async () => {
2121

2222
describe('constructor', () => {
2323
it('should revert if the DPS contract is the zero address', async () => {
24-
await expect(new BallotFactory__factory(owner).deploy(ZERO_ADDRESS)).to.be.revertedWith(
24+
await expect(
25+
new BallotFactory__factory(owner).deploy(ZERO_ADDRESS, ballotImplementation.address),
26+
).to.be.revertedWith('BallotFactory: Implementation address should not be zero address');
27+
});
28+
it('should revert if the DPS contract is the zero address', async () => {
29+
await expect(new BallotFactory__factory(owner).deploy(DPS.address, ZERO_ADDRESS)).to.be.revertedWith(
2530
'BallotFactory: Implementation address should not be zero address',
2631
);
2732
});

test/testing/setupVoting.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ interface SetupVotingOutput {
1616
export default async function setupVoting(owner: SignerWithAddress, DPS: DeepSquare): Promise<SetupVotingOutput> {
1717
const votingDelegation = await new VotingDelegation__factory(owner).deploy(DPS.address);
1818
const ballotImplementation = await new Ballot__factory(owner).deploy(DPS.address, votingDelegation.address);
19-
const ballotFactory = await new BallotFactory__factory(owner).deploy(ballotImplementation.address);
19+
const ballotFactory = await new BallotFactory__factory(owner).deploy(DPS.address, ballotImplementation.address);
2020

2121
return {
2222
votingDelegation,

typings/contracts/Ballot.ts

+31-2
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,10 @@ export interface BallotInterface extends utils.Interface {
3232
"choices(uint256)": FunctionFragment;
3333
"close()": FunctionFragment;
3434
"closed()": FunctionFragment;
35+
"factory()": FunctionFragment;
3536
"getChoices()": FunctionFragment;
3637
"getResults()": FunctionFragment;
37-
"init(string,string,string[])": FunctionFragment;
38+
"init(address,address,address,string,string,string[])": FunctionFragment;
3839
"owner()": FunctionFragment;
3940
"proxy()": FunctionFragment;
4041
"renounceOwnership()": FunctionFragment;
@@ -51,6 +52,7 @@ export interface BallotInterface extends utils.Interface {
5152
| "choices"
5253
| "close"
5354
| "closed"
55+
| "factory"
5456
| "getChoices"
5557
| "getResults"
5658
| "init"
@@ -71,6 +73,7 @@ export interface BallotInterface extends utils.Interface {
7173
): string;
7274
encodeFunctionData(functionFragment: "close", values?: undefined): string;
7375
encodeFunctionData(functionFragment: "closed", values?: undefined): string;
76+
encodeFunctionData(functionFragment: "factory", values?: undefined): string;
7477
encodeFunctionData(
7578
functionFragment: "getChoices",
7679
values?: undefined
@@ -81,7 +84,7 @@ export interface BallotInterface extends utils.Interface {
8184
): string;
8285
encodeFunctionData(
8386
functionFragment: "init",
84-
values: [string, string, string[]]
87+
values: [string, string, string, string, string, string[]]
8588
): string;
8689
encodeFunctionData(functionFragment: "owner", values?: undefined): string;
8790
encodeFunctionData(functionFragment: "proxy", values?: undefined): string;
@@ -105,6 +108,7 @@ export interface BallotInterface extends utils.Interface {
105108
decodeFunctionResult(functionFragment: "choices", data: BytesLike): Result;
106109
decodeFunctionResult(functionFragment: "close", data: BytesLike): Result;
107110
decodeFunctionResult(functionFragment: "closed", data: BytesLike): Result;
111+
decodeFunctionResult(functionFragment: "factory", data: BytesLike): Result;
108112
decodeFunctionResult(functionFragment: "getChoices", data: BytesLike): Result;
109113
decodeFunctionResult(functionFragment: "getResults", data: BytesLike): Result;
110114
decodeFunctionResult(functionFragment: "init", data: BytesLike): Result;
@@ -182,11 +186,16 @@ export interface Ballot extends BaseContract {
182186

183187
closed(overrides?: CallOverrides): Promise<[boolean]>;
184188

189+
factory(overrides?: CallOverrides): Promise<[string]>;
190+
185191
getChoices(overrides?: CallOverrides): Promise<[string[]]>;
186192

187193
getResults(overrides?: CallOverrides): Promise<[BigNumber[]]>;
188194

189195
init(
196+
_DPS: string,
197+
_proxy: string,
198+
_factory: string,
190199
_subject: string,
191200
_topic: string,
192201
_choices: string[],
@@ -231,11 +240,16 @@ export interface Ballot extends BaseContract {
231240

232241
closed(overrides?: CallOverrides): Promise<boolean>;
233242

243+
factory(overrides?: CallOverrides): Promise<string>;
244+
234245
getChoices(overrides?: CallOverrides): Promise<string[]>;
235246

236247
getResults(overrides?: CallOverrides): Promise<BigNumber[]>;
237248

238249
init(
250+
_DPS: string,
251+
_proxy: string,
252+
_factory: string,
239253
_subject: string,
240254
_topic: string,
241255
_choices: string[],
@@ -278,11 +292,16 @@ export interface Ballot extends BaseContract {
278292

279293
closed(overrides?: CallOverrides): Promise<boolean>;
280294

295+
factory(overrides?: CallOverrides): Promise<string>;
296+
281297
getChoices(overrides?: CallOverrides): Promise<string[]>;
282298

283299
getResults(overrides?: CallOverrides): Promise<BigNumber[]>;
284300

285301
init(
302+
_DPS: string,
303+
_proxy: string,
304+
_factory: string,
286305
_subject: string,
287306
_topic: string,
288307
_choices: string[],
@@ -334,11 +353,16 @@ export interface Ballot extends BaseContract {
334353

335354
closed(overrides?: CallOverrides): Promise<BigNumber>;
336355

356+
factory(overrides?: CallOverrides): Promise<BigNumber>;
357+
337358
getChoices(overrides?: CallOverrides): Promise<BigNumber>;
338359

339360
getResults(overrides?: CallOverrides): Promise<BigNumber>;
340361

341362
init(
363+
_DPS: string,
364+
_proxy: string,
365+
_factory: string,
342366
_subject: string,
343367
_topic: string,
344368
_choices: string[],
@@ -387,11 +411,16 @@ export interface Ballot extends BaseContract {
387411

388412
closed(overrides?: CallOverrides): Promise<PopulatedTransaction>;
389413

414+
factory(overrides?: CallOverrides): Promise<PopulatedTransaction>;
415+
390416
getChoices(overrides?: CallOverrides): Promise<PopulatedTransaction>;
391417

392418
getResults(overrides?: CallOverrides): Promise<PopulatedTransaction>;
393419

394420
init(
421+
_DPS: string,
422+
_proxy: string,
423+
_factory: string,
395424
_subject: string,
396425
_topic: string,
397426
_choices: string[],

0 commit comments

Comments
 (0)