Skip to content

Commit c4e79ec

Browse files
committed
Update in response to review comments
1 parent 082e3f0 commit c4e79ec

File tree

2 files changed

+113
-67
lines changed

2 files changed

+113
-67
lines changed

contracts/extensions/Korporatio.sol

Lines changed: 17 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -29,16 +29,14 @@ contract Korporatio is ColonyExtensionMeta {
2929

3030
// Constants
3131

32-
uint256 constant APPLICATION_FEE = 6500 * WAD;
33-
3432
// Events
3533

3634
event ApplicationCreated(uint256 indexed stakeId, address indexed applicant);
3735
event ApplicationCancelled(uint256 indexed stakeId);
3836
event StakeReclaimed(uint256 indexed stakeId);
3937
event StakeSlashed(uint256 indexed stakeId);
4038
event ApplicationUpdated(uint256 indexed stakeId, bytes32 ipfsHash);
41-
event ApplicationSubmitted(uint256 indexed stakeId, bytes32 ipfsHash);
39+
event ApplicationSubmitted(uint256 indexed stakeId);
4240

4341
// Data structures
4442

@@ -52,8 +50,6 @@ contract Korporatio is ColonyExtensionMeta {
5250

5351
address colonyNetworkAddress;
5452

55-
address paymentToken;
56-
uint256 applicationFee;
5753
uint256 stakeFraction;
5854
uint256 claimDelay;
5955

@@ -62,6 +58,11 @@ contract Korporatio is ColonyExtensionMeta {
6258

6359
// Modifiers
6460

61+
modifier onlyApplicant(uint256 _applicationId) {
62+
require(msgSender() == applications[_applicationId].applicant, "korporatio-not-applicant");
63+
_;
64+
}
65+
6566
// Overrides
6667

6768
/// @notice Returns the identifier of the extension
@@ -98,21 +99,13 @@ contract Korporatio is ColonyExtensionMeta {
9899

99100
// Public
100101

101-
function initialise(
102-
address _paymentToken,
103-
uint256 _applicationFee,
104-
uint256 _stakeFraction,
105-
uint256 _claimDelay
106-
)
107-
public
108-
{
102+
function initialise(uint256 _stakeFraction, uint256 _claimDelay) public {
103+
require(numApplications <= 0, "korporatio-cannot-initialise");
109104
require(
110105
colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Architecture),
111106
"korporatio-not-root-architect"
112107
);
113108

114-
paymentToken = _paymentToken;
115-
applicationFee = _applicationFee;
116109
stakeFraction = _stakeFraction;
117110
claimDelay = _claimDelay;
118111
}
@@ -166,21 +159,18 @@ contract Korporatio is ColonyExtensionMeta {
166159
emit ApplicationCreated(numApplications, msgSender());
167160
}
168161

169-
function cancelApplication(uint256 _applicationId) public {
170-
require(applications[_applicationId].applicant == msgSender(), "korporatio-cannot-cancel");
171-
162+
function cancelApplication(uint256 _applicationId) public onlyApplicant(_applicationId) {
172163
applications[_applicationId].cancelledAt = block.timestamp;
173164

174165
emit ApplicationCancelled(_applicationId);
175166
}
176167

177-
function reclaimStake(uint256 _applicationId) public {
178-
require(
179-
applications[_applicationId].cancelledAt + claimDelay <= block.timestamp,
180-
"korporatio-cannot-reclaim"
181-
);
168+
function reclaimStake(uint256 _applicationId) public onlyApplicant(_applicationId) {
169+
Application storage application = applications[_applicationId];
170+
require(application.applicant == msgSender(), "korporatio-not-applicant");
171+
require(application.cancelledAt + claimDelay <= block.timestamp, "korporatio-cannot-reclaim");
182172

183-
uint256 stakeAmount = applications[_applicationId].stakeAmount;
173+
uint256 stakeAmount = application.stakeAmount;
184174
delete applications[_applicationId];
185175

186176
colony.deobligateStake(msgSender(), 1, stakeAmount);
@@ -206,35 +196,23 @@ contract Korporatio is ColonyExtensionMeta {
206196
emit StakeSlashed(_applicationId);
207197
}
208198

209-
function updateApplication(uint256 _applicationId, bytes32 _ipfsHash) public {
210-
require(applications[_applicationId].applicant == msgSender(), "korporatio-not-applicant");
199+
function updateApplication(uint256 _applicationId, bytes32 _ipfsHash) public onlyApplicant(_applicationId) {
211200
require(applications[_applicationId].cancelledAt == UINT256_MAX, "korporatio-stake-cancelled");
212201

213202
emit ApplicationUpdated(_applicationId, _ipfsHash);
214203
}
215204

216-
function submitApplication(uint256 _applicationId, bytes32 _ipfsHash) public {
205+
function submitApplication(uint256 _applicationId) public {
217206
require(colony.hasUserRole(msgSender(), 1, ColonyDataTypes.ColonyRole.Root), "korporatio-caller-not-root");
218207
require(applications[_applicationId].cancelledAt == UINT256_MAX, "korporatio-stake-cancelled");
219208

220209
applications[_applicationId].cancelledAt = block.timestamp;
221210

222-
address metaColony = IColonyNetwork(colonyNetworkAddress).getMetaColony();
223-
require(ERC20(paymentToken).transferFrom(msgSender(), metaColony, applicationFee), "korporatio-transfer-failed");
224-
225-
emit ApplicationSubmitted(_applicationId, _ipfsHash);
211+
emit ApplicationSubmitted(_applicationId);
226212
}
227213

228214
// View
229215

230-
function getPaymentToken() external view returns (address) {
231-
return paymentToken;
232-
}
233-
234-
function getApplicationFee() external view returns (uint256) {
235-
return applicationFee;
236-
}
237-
238216
function getStakeFraction() external view returns (uint256) {
239217
return stakeFraction;
240218
}

test/extensions/korporatio.js

Lines changed: 96 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
/* globals artifacts */
22

3+
const { BN } = require("bn.js");
34
const chai = require("chai");
45
const bnChai = require("bn-chai");
56
const { ethers } = require("ethers");
@@ -16,6 +17,7 @@ const {
1617
forwardTime,
1718
getBlockTime,
1819
expectEvent,
20+
encodeTxData,
1921
} = require("../../helpers/test-helper");
2022

2123
const { setupRandomColony, getMetaTransactionParameters } = require("../../helpers/test-data-generator");
@@ -28,10 +30,12 @@ chai.use(bnChai(web3.utils.BN));
2830
const EtherRouter = artifacts.require("EtherRouter");
2931
const IColonyNetwork = artifacts.require("IColonyNetwork");
3032
const IReputationMiningCycle = artifacts.require("IReputationMiningCycle");
33+
const IVotingReputation = artifacts.require("IVotingReputation");
3134
const Korporatio = artifacts.require("Korporatio");
3235
const TokenLocking = artifacts.require("TokenLocking");
3336

3437
const KORPORATIO = soliditySha3("Korporatio");
38+
const VOTING_REPUTATION = soliditySha3("VotingReputation");
3539

3640
contract("Korporatio", (accounts) => {
3741
let colony;
@@ -60,8 +64,6 @@ contract("Korporatio", (accounts) => {
6064
const USER2 = accounts[2];
6165
const MINER = accounts[5];
6266

63-
const APPLICATION_FEE = WAD.muln(6500);
64-
6567
before(async () => {
6668
const etherRouter = await EtherRouter.deployed();
6769
colonyNetwork = await IColonyNetwork.at(etherRouter.address);
@@ -168,32 +170,47 @@ contract("Korporatio", (accounts) => {
168170
await checkErrorRevert(korporatio.deprecate(true, { from: USER1 }), "ds-auth-unauthorized");
169171
await checkErrorRevert(korporatio.uninstall({ from: USER1 }), "ds-auth-unauthorized");
170172
});
173+
174+
it("cannot create applications unless initialised", async () => {
175+
await checkErrorRevert(
176+
korporatio.createApplication(domain1Key, domain1Value, domain1Mask, domain1Siblings, user0Key, user0Value, user0Mask, user0Siblings, {
177+
from: USER0,
178+
}),
179+
"korporatio-not-initialised"
180+
);
181+
});
171182
});
172183

173184
describe("creating applications", async () => {
174185
beforeEach(async () => {
175-
await korporatio.initialise(token.address, APPLICATION_FEE, WAD.divn(100), SECONDS_PER_DAY, { from: USER0 });
176-
177186
await colony.approveStake(korporatio.address, 1, WAD, { from: USER0 });
187+
188+
await korporatio.initialise(WAD.divn(100), SECONDS_PER_DAY, { from: USER0 });
189+
});
190+
191+
it("can re-initialise until first application is created", async () => {
192+
await korporatio.initialise(WAD.divn(10), SECONDS_PER_DAY, { from: USER0 });
193+
194+
const stakeFraction = await korporatio.getStakeFraction();
195+
expect(stakeFraction).to.eq.BN(WAD.divn(10));
196+
197+
await korporatio.createApplication(domain1Key, domain1Value, domain1Mask, domain1Siblings, user0Key, user0Value, user0Mask, user0Siblings, {
198+
from: USER0,
199+
});
200+
201+
await checkErrorRevert(korporatio.initialise(WAD.divn(100), SECONDS_PER_DAY, { from: USER1 }), "korporatio-cannot-initialise");
178202
});
179203

180204
it("can query for configuration params", async () => {
181-
const paymentToken = await korporatio.getPaymentToken();
182-
const applicationFee = await korporatio.getApplicationFee();
183205
const stakeFraction = await korporatio.getStakeFraction();
184206
const claimDelay = await korporatio.getClaimDelay();
185207

186-
expect(paymentToken).to.equal(token.address);
187-
expect(applicationFee).to.eq.BN(APPLICATION_FEE);
188208
expect(stakeFraction).to.eq.BN(WAD.divn(100));
189209
expect(claimDelay).to.eq.BN(SECONDS_PER_DAY);
190210
});
191211

192212
it("cannot set configuration params if not root architect", async () => {
193-
await checkErrorRevert(
194-
korporatio.initialise(token.address, APPLICATION_FEE, WAD.divn(100), SECONDS_PER_DAY, { from: USER1 }),
195-
"korporatio-not-root-architect"
196-
);
213+
await checkErrorRevert(korporatio.initialise(WAD.divn(100), SECONDS_PER_DAY, { from: USER1 }), "korporatio-not-root-architect");
197214
});
198215

199216
it("can create an application", async () => {
@@ -225,7 +242,7 @@ contract("Korporatio", (accounts) => {
225242
});
226243

227244
it("cannot create an application with insufficient rep", async () => {
228-
await korporatio.initialise(token.address, APPLICATION_FEE, WAD, SECONDS_PER_DAY, { from: USER0 });
245+
await korporatio.initialise(WAD, SECONDS_PER_DAY, { from: USER0 });
229246

230247
await checkErrorRevert(
231248
korporatio.createApplication(domain1Key, domain1Value, domain1Mask, domain1Siblings, user0Key, user0Value, user0Mask, user0Siblings, {
@@ -256,7 +273,7 @@ contract("Korporatio", (accounts) => {
256273
const applicationId = await korporatio.getNumApplications();
257274

258275
// Only applicant can cancel
259-
await checkErrorRevert(korporatio.cancelApplication(applicationId, { from: USER1 }), "korporatio-cannot-cancel");
276+
await checkErrorRevert(korporatio.cancelApplication(applicationId, { from: USER1 }), "korporatio-not-applicant");
260277

261278
const tx = await korporatio.cancelApplication(applicationId, { from: USER0 });
262279
const blockTime = await getBlockTime(tx.receipt.blockNumber);
@@ -278,7 +295,7 @@ contract("Korporatio", (accounts) => {
278295

279296
await forwardTime(SECONDS_PER_DAY, this);
280297

281-
await korporatio.reclaimStake(applicationId);
298+
await korporatio.reclaimStake(applicationId, { from: USER0 });
282299

283300
const obligation = await colony.getObligation(USER0, korporatio.address, 1);
284301
expect(obligation).to.be.zero;
@@ -331,6 +348,28 @@ contract("Korporatio", (accounts) => {
331348
await checkErrorRevert(korporatio.slashStake(applicationId, false, { from: USER2 }), "korporatio-caller-not-arbitration");
332349
});
333350

351+
it("can reclaim a stake via arbitration if the extension is deleted", async () => {
352+
const korporatioAddress = korporatio.address;
353+
await korporatio.createApplication(domain1Key, domain1Value, domain1Mask, domain1Siblings, user0Key, user0Value, user0Mask, user0Siblings, {
354+
from: USER0,
355+
});
356+
357+
const lockPre = await tokenLocking.getUserLock(token.address, USER0);
358+
const obligationPre = await colony.getObligation(USER0, korporatioAddress, 1);
359+
expect(obligationPre).to.eq.BN(WAD.divn(100).muln(3));
360+
361+
await colony.uninstallExtension(KORPORATIO, { from: USER0 });
362+
363+
await colony.transferStake(1, UINT256_MAX, korporatioAddress, USER0, 1, obligationPre, USER0, { from: USER1 });
364+
365+
const lockPost = await tokenLocking.getUserLock(token.address, USER0);
366+
const obligationPost = await colony.getObligation(USER0, korporatioAddress, 1);
367+
368+
// Obligation is zeroed out, but token balance is unchanged
369+
expect(obligationPost).to.be.zero;
370+
expect(new BN(lockPre.balance)).to.eq.BN(lockPost.balance);
371+
});
372+
334373
it("can update an application", async () => {
335374
await korporatio.createFreeApplication({ from: USER0 });
336375

@@ -348,27 +387,56 @@ contract("Korporatio", (accounts) => {
348387
await checkErrorRevert(korporatio.updateApplication(applicationId, ipfsHash, { from: USER0 }), "korporatio-stake-cancelled");
349388
});
350389

351-
it("can submit an application and pay the fee", async () => {
352-
await token.mint(USER0, APPLICATION_FEE);
353-
await token.approve(korporatio.address, APPLICATION_FEE);
354-
390+
it("can submit an application", async () => {
355391
await korporatio.createFreeApplication({ from: USER0 });
356392

357393
const applicationId = await korporatio.getNumApplications();
358-
const ipfsHash = soliditySha3("IPFS Hash");
359394

360395
// Cannot submit if not root
361-
await checkErrorRevert(korporatio.submitApplication(applicationId, ipfsHash, { from: USER1 }), "korporatio-caller-not-root");
396+
await checkErrorRevert(korporatio.submitApplication(applicationId, { from: USER1 }), "korporatio-caller-not-root");
397+
398+
const tx = await korporatio.submitApplication(applicationId, { from: USER0 });
399+
await expectEvent(tx, "ApplicationSubmitted", [applicationId]);
362400

363-
const tx = await korporatio.submitApplication(applicationId, ipfsHash, { from: USER0 });
364-
await expectEvent(tx, "ApplicationSubmitted", [applicationId, ipfsHash]);
401+
// Cannot submit twice
402+
await checkErrorRevert(korporatio.submitApplication(applicationId, { from: USER0 }), "korporatio-stake-cancelled");
403+
});
404+
405+
it("can submit an application via a motion", async () => {
406+
await colony.installExtension(VOTING_REPUTATION, 9);
407+
const votingAddress = await colonyNetwork.getExtensionInstallation(VOTING_REPUTATION, colony.address);
408+
await colony.setArbitrationRole(1, UINT256_MAX, votingAddress, 1, true);
409+
await colony.setRootRole(votingAddress, true);
410+
const voting = await IVotingReputation.at(votingAddress);
365411

366-
const metaColonyAddress = await colonyNetwork.getMetaColony();
367-
const metaColonyBalance = await token.balanceOf(metaColonyAddress);
368-
expect(metaColonyBalance).to.eq.BN(APPLICATION_FEE);
412+
await voting.initialise(WAD.divn(1000), 0, 0, WAD, SECONDS_PER_DAY, SECONDS_PER_DAY, SECONDS_PER_DAY, SECONDS_PER_DAY);
369413

370-
// Cannot submit once cancelled
371-
await checkErrorRevert(korporatio.submitApplication(applicationId, ipfsHash, { from: USER0 }), "korporatio-stake-cancelled");
414+
await korporatio.createFreeApplication({ from: USER0 });
415+
const applicationId = await korporatio.getNumApplications();
416+
417+
const action = await encodeTxData(korporatio, "submitApplication", [applicationId]);
418+
419+
// Can't create a motion in a subdomain
420+
await colony.addDomain(1, UINT256_MAX, 1);
421+
await checkErrorRevert(
422+
voting.createMotion(2, UINT256_MAX, korporatio.address, action, domain1Key, domain1Value, domain1Mask, domain1Siblings),
423+
"voting-rep-invalid-domain-id"
424+
);
425+
426+
// Only in the root domain
427+
await voting.createMotion(1, UINT256_MAX, korporatio.address, action, domain1Key, domain1Value, domain1Mask, domain1Siblings);
428+
const motionId = await voting.getMotionCount();
429+
430+
await colony.approveStake(voting.address, 1, WAD, { from: USER0 });
431+
await voting.stakeMotion(motionId, 1, UINT256_MAX, 1, WAD.muln(3).divn(1000), user0Key, user0Value, user0Mask, user0Siblings, { from: USER0 });
432+
433+
await forwardTime(SECONDS_PER_DAY, this);
434+
435+
const tx = await voting.finalizeMotion(motionId);
436+
const finalizedAt = await getBlockTime(tx.blockNumber);
437+
438+
const application = await korporatio.getApplication(applicationId);
439+
expect(application.cancelledAt).to.eq.BN(finalizedAt);
372440
});
373441

374442
it("can submit a stake via metatransactions", async () => {

0 commit comments

Comments
 (0)