github.com/codingfuture/orig-energi3@v0.8.4/energi/contracts/test/TreasuryV1.spec.js (about) 1 // Copyright 2019 The Energi Core Authors 2 // This file is part of the Energi Core library. 3 // 4 // The Energi Core library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The Energi Core library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the Energi Core library. If not, see <http://www.gnu.org/licenses/>. 16 17 // Energi Governance system is the fundamental part of Energi Core. 18 19 'use strict'; 20 21 const MockProxy = artifacts.require('MockProxy'); 22 const MockContract = artifacts.require('MockContract'); 23 const TreasuryV1 = artifacts.require('TreasuryV1'); 24 const ITreasury = artifacts.require('ITreasury'); 25 const IBlockReward = artifacts.require('IBlockReward'); 26 const IProposal = artifacts.require('IProposal'); 27 const MasternodeRegistryV1 = artifacts.require('MasternodeRegistryV1'); 28 const MasternodeTokenV1 = artifacts.require('MasternodeTokenV1'); 29 const StorageTreasuryV1 = artifacts.require('StorageTreasuryV1'); 30 31 const common = require('./common'); 32 33 contract("TreasuryV1", async accounts => { 34 const s = { 35 artifacts, 36 accounts, 37 assert, 38 it, 39 web3, 40 storage: null, 41 }; 42 43 before(async () => { 44 s.registry_orig = await MasternodeRegistryV1.deployed(); 45 s.registry = await MasternodeRegistryV1.at(await s.registry_orig.proxy()); 46 47 s.mntoken_orig = await MasternodeTokenV1.deployed(); 48 s.mntoken = await MasternodeTokenV1.at(await s.mntoken_orig.proxy()); 49 50 s.orig = await TreasuryV1.deployed(); 51 s.proxy = await MockProxy.at(await s.orig.proxy()); 52 s.fake = await MockContract.new(s.proxy.address); 53 s.proxy_abi = await TreasuryV1.at(s.proxy.address); 54 s.token_abi = await ITreasury.at(s.proxy.address); 55 s.treasury_abi = s.token_abi; 56 s.reward_abi = await IBlockReward.at(s.proxy.address); 57 await s.proxy.setImpl(s.orig.address); 58 s.storage = await StorageTreasuryV1.at(await s.proxy_abi.v1storage()); 59 Object.freeze(s); 60 }); 61 62 after(async () => { 63 const impl = await TreasuryV1.new(s.proxy.address, s.registry.address, common.superblock_cycles); 64 await s.proxy.setImpl(impl.address); 65 }); 66 67 describe('common pre', () => common.govPreTests(s) ); 68 69 //--- 70 describe('ITreasury', () => { 71 const { fromAscii, toBN, toWei } = web3.utils; 72 const superblock_reward = toBN(toWei('184000', 'ether')); 73 const payer1 = accounts[0]; 74 const def_period = 14*24*60*60; // 2 weeks 75 const def_amount = toBN(toWei('1000', 'ether')); 76 const fee_amount = toBN(toWei('100', 'ether')); 77 78 const collateral1 = toBN(toWei('30000', 'ether')); 79 const collateral2 = toBN(toWei('20000', 'ether')); 80 81 const owner1 = accounts[0]; 82 const owner2 = accounts[1]; 83 const owner3 = accounts[3]; 84 const owner4 = accounts[4]; 85 86 const masternode1 = accounts[9]; 87 const masternode2 = accounts[8]; 88 89 const ip1 = toBN(0x12345678); 90 const ip2 = toBN(0x87654321); 91 92 const enode_common = '123456789012345678901234567890' 93 const enode1 = [fromAscii(enode_common + '11'), fromAscii(enode_common + '11')]; 94 const enode2 = [fromAscii(enode_common + '11'), fromAscii(enode_common + '22')]; 95 96 before(async () => { 97 await s.mntoken.depositCollateral({ 98 from: owner1, 99 value: collateral1, 100 }); 101 await s.mntoken.depositCollateral({ 102 from: owner2, 103 value: collateral2, 104 }); 105 await s.registry.announce(masternode1, ip1, enode1, {from: owner1}); 106 await s.registry.announce(masternode2, ip2, enode2, {from: owner2}); 107 }); 108 109 after(async () => { 110 await s.mntoken.withdrawCollateral(collateral1, { 111 from: owner1, 112 }); 113 await s.mntoken.withdrawCollateral(collateral2, { 114 from: owner2, 115 }); 116 }); 117 118 it ('should correctly identify superblocks', async () => { 119 const period = 8; 120 expect(await s.treasury_abi.isSuperblock(0)).false; 121 122 for (let i = 1; i < period; ++i) { 123 expect(await s.treasury_abi.isSuperblock(i)).false; 124 } 125 126 expect(await s.treasury_abi.isSuperblock(period)).true; 127 128 for (let i = period + 1; i < 2*period; ++i) { 129 expect(await s.treasury_abi.isSuperblock(i)).false; 130 } 131 132 expect(await s.treasury_abi.isSuperblock(2*period)).true; 133 }); 134 135 it ('should correctly identify reward', async () => { 136 const period = 8; 137 expect(await s.treasury_abi.isSuperblock(0)).false; 138 139 for (let i = 1; i < period; ++i) { 140 expect(await s.treasury_abi.isSuperblock(i)).false; 141 } 142 143 expect(await s.treasury_abi.isSuperblock(period)).true; 144 145 for (let i = period + 1; i < 2*period; ++i) { 146 expect(await s.treasury_abi.isSuperblock(i)).false; 147 } 148 149 expect(await s.treasury_abi.isSuperblock(2*period)).true; 150 }); 151 152 it ('should correctly reflect balance()', async () => { 153 await s.treasury_abi.contribute({value: '234'}); 154 155 const orig_bal = toBN(await web3.eth.getBalance(s.orig.address)); 156 expect(toBN(await s.treasury_abi.balance()).toString()).equal(orig_bal.toString()); 157 }); 158 159 it ('should handle contribute()', async () => { 160 const orig_bal = toBN(await web3.eth.getBalance(s.orig.address)); 161 162 await s.treasury_abi.contribute({value: '123'}); 163 164 const bal_after = toBN(await s.treasury_abi.balance()); 165 expect(bal_after.sub(orig_bal).toString()).equal('123'); 166 167 const evt = await s.orig.getPastEvents('Contribution', common.evt_last_block); 168 expect(evt).lengthOf(1); 169 common.stringifyBN(web3, evt[0].args); 170 expect(evt[0].args).deep.include({ 171 '0': payer1, 172 '1': '123', 173 '__length__': 2, 174 'from': payer1, 175 'amount': '123', 176 }); 177 }); 178 179 it ('should refuse propose() without proper fee', async () => { 180 try { 181 await s.treasury_abi.propose(def_amount, '1', def_period); 182 assert.fail('It must fail'); 183 } catch (e) { 184 assert.match(e.message, /Invalid fee/); 185 } 186 187 try { 188 await s.treasury_abi.propose( 189 def_amount, '1', def_period, 190 { value: toWei('99.99', 'ether') } 191 ); 192 assert.fail('It must fail'); 193 } catch (e) { 194 assert.match(e.message, /Invalid fee/); 195 } 196 197 try { 198 await s.treasury_abi.propose( 199 def_amount, '1', def_period, 200 { value: toWei('100.1', 'ether') } 201 ); 202 assert.fail('It must fail'); 203 } catch (e) { 204 assert.match(e.message, /Invalid fee/); 205 } 206 }); 207 208 it ('should refuse propose() without proper amount', async () => { 209 try { 210 await s.treasury_abi.propose( 211 toWei('99', 'ether'), '1', def_period, 212 { value: fee_amount } 213 ); 214 assert.fail('It must fail'); 215 } catch (e) { 216 assert.match(e.message, /Too small amount/); 217 } 218 219 try { 220 await s.treasury_abi.propose( 221 toWei('184000.1', 'ether'), '1', def_period, 222 { value: fee_amount } 223 ); 224 assert.fail('It must fail'); 225 } catch (e) { 226 assert.match(e.message, /Too large amount/); 227 } 228 }); 229 230 it ('should refuse propose() without proper period', async () => { 231 try { 232 await s.treasury_abi.propose( 233 def_amount, '1', 7*24*60*60-1, 234 { value: fee_amount } 235 ); 236 assert.fail('It must fail'); 237 } catch (e) { 238 assert.match(e.message, /Too small period/); 239 } 240 241 try { 242 await s.treasury_abi.propose( 243 def_amount, '1', 30*24*60*60+1, 244 { value: fee_amount } 245 ); 246 assert.fail('It must fail'); 247 } catch (e) { 248 assert.match(e.message, /Too large period/); 249 } 250 }); 251 252 it ('should propose()', async () => { 253 const min_amount = toWei('100', 'ether'); 254 await s.treasury_abi.propose( 255 min_amount, '11111111', def_period, 256 { value: fee_amount } 257 ); 258 259 const bn = await web3.eth.getBlockNumber(); 260 const b = await web3.eth.getBlock(bn); 261 262 const evt = await s.orig.getPastEvents('BudgetProposal', common.evt_last_block); 263 expect(evt).lengthOf(1); 264 common.stringifyBN(web3, evt[0].args); 265 expect(evt[0].args).deep.include({ 266 '0': '11111111', 267 '2': payer1, 268 '3': min_amount.toString(), 269 '4': toBN(b.timestamp + def_period).toString(), 270 '__length__': 5, 271 'ref_uuid': '11111111', 272 'payout_address': payer1, 273 'amount': min_amount.toString(), 274 'deadline': toBN(b.timestamp + def_period).toString(), 275 }); 276 expect(evt[0].args).include.keys('proposal'); 277 }); 278 279 it ('should refuse propose() on UUID duplicate', async () => { 280 try { 281 await s.treasury_abi.propose( 282 def_amount, '11111111', def_period, 283 { value: fee_amount } 284 ); 285 assert.fail('It must fail'); 286 } catch (e) { 287 assert.match(e.message, /UUID in use/); 288 } 289 }); 290 291 it ('should record & map proposals()', async () => { 292 const max_amount = superblock_reward; 293 await s.treasury_abi.propose( 294 max_amount, '22222222', def_period, 295 { value: fee_amount } 296 ); 297 const proposal2 = await s.treasury_abi.uuid_proposal('22222222'); 298 expect(proposal2.toString()).not.equal(toBN('0').toString()); 299 300 const proposal1 = await s.treasury_abi.uuid_proposal('11111111'); 301 expect(proposal1.toString()).not.equal(toBN('0').toString()); 302 303 expect((await s.treasury_abi.proposal_uuid(proposal1)).toString()).equal('11111111'); 304 expect((await s.treasury_abi.proposal_uuid(proposal2)).toString()).equal('22222222'); 305 }); 306 307 it ('should refuse propose() over total limit', async () => { 308 for (let i = 3; i <= 100; ++i) { 309 await s.treasury_abi.propose( 310 def_amount, String(i).repeat(8), def_period + (i * def_period / 100), 311 { value: fee_amount } 312 ); 313 } 314 315 try { 316 await s.treasury_abi.propose( 317 def_amount, '101'.repeat(8), def_period, 318 { value: fee_amount } 319 ); 320 assert.fail('It must fail'); 321 } catch (e) { 322 assert.match(e.message, /Too many active proposals/); 323 } 324 }); 325 326 it ('should collect rejected proposals', async () => { 327 const orig_bal = toBN(await s.treasury_abi.balance()); 328 329 // No deadline 330 await s.reward_abi.reward(); 331 expect(toBN(await s.treasury_abi.balance()).sub(orig_bal).toString()) 332 .equal(toBN(toWei('0', 'ether')).toString()); 333 334 // 3 deadlined 335 await common.moveTime(web3, def_period+1); 336 await s.reward_abi.reward(); 337 expect(toBN(await s.treasury_abi.balance()).sub(orig_bal).toString()) 338 .equal(toBN(toWei('200', 'ether')).toString()); 339 340 // another 2 341 await common.moveTime(web3, (4*def_period/100)+1); 342 await s.reward_abi.reward(); 343 expect(toBN(await s.treasury_abi.balance()).sub(orig_bal).toString()) 344 .equal(toBN(toWei('400', 'ether')).toString()); 345 346 // remaining 95 347 await common.moveTime(web3, (96*def_period/100)+1); 348 await s.reward_abi.reward(); 349 expect(toBN(await s.treasury_abi.balance()).sub(orig_bal).toString()) 350 .equal(toBN(toWei('10000', 'ether')).toString()); 351 }); 352 353 it ('should correctly distribute full payouts', async() => { 354 const orig_bal = toBN(await s.treasury_abi.balance()); 355 356 const get_proposal = async () => { 357 const evt = await s.orig.getPastEvents('BudgetProposal', common.evt_last_block); 358 expect(evt).lengthOf(1); 359 return await IProposal.at(evt[0].args.proposal); 360 }; 361 362 await s.treasury_abi.propose( 363 toWei('300', 'ether'), '201', def_period, 364 { value: fee_amount } 365 ); 366 const proposal1 = await get_proposal(); 367 await s.treasury_abi.propose( 368 toWei('300', 'ether'), '202', def_period, 369 { value: fee_amount } 370 ); 371 const proposal2 = await get_proposal(); 372 await s.treasury_abi.propose( 373 toWei('300', 'ether'), '203', def_period, 374 { value: fee_amount } 375 ); 376 377 await proposal1.voteAccept({from: owner1}); 378 await proposal1.voteAccept({from: owner2}); 379 await proposal2.voteReject({from: owner1}); 380 await proposal2.voteReject({from: owner2}); 381 382 await s.reward_abi.reward(); 383 expect(toBN(await s.treasury_abi.balance()).sub(orig_bal).toString()) 384 .equal(toBN(toWei('-200', 'ether')).toString()); 385 386 await common.moveTime(web3, def_period+1); 387 await s.reward_abi.reward(); 388 expect(toBN(await s.treasury_abi.balance()).sub(orig_bal).toString()) 389 .equal(toBN(toWei('-100', 'ether')).toString()); 390 }); 391 392 it ('should correctly distribute partial payouts', async() => { 393 const orig_bal = toBN(await s.treasury_abi.balance()); 394 const start_bal3 = toBN(await web3.eth.getBalance(owner3)); 395 const start_bal4 = toBN(await web3.eth.getBalance(owner4)); 396 397 const get_proposal = async () => { 398 const evt = await s.orig.getPastEvents('BudgetProposal', common.evt_last_block); 399 expect(evt).lengthOf(1); 400 return await IProposal.at(evt[0].args.proposal); 401 }; 402 const mul1 = toBN(2); 403 const mul2 = toBN(3); 404 const tot = mul1.add(mul2); 405 const addbal = orig_bal.mul(tot.sub(toBN(1))); 406 const trunc = toBN(10); 407 const extra = toBN(12345678); 408 409 await s.treasury_abi.propose( 410 orig_bal.mul(mul1), '301', def_period, 411 { value: fee_amount, from: owner3 } 412 ); 413 const proposal1 = await get_proposal(); 414 await s.treasury_abi.propose( 415 orig_bal.mul(mul2), '302', def_period, 416 { value: fee_amount, from: owner4 } 417 ); 418 const proposal2 = await get_proposal(); 419 420 await proposal1.voteAccept({from: owner1}); 421 await proposal1.voteAccept({from: owner2}); 422 await proposal2.voteAccept({from: owner1}); 423 await proposal2.voteAccept({from: owner2}); 424 425 await s.reward_abi.reward(); 426 expect(toBN(await s.treasury_abi.balance()).div(trunc).toString()) 427 .equal(toBN(0).toString()); 428 429 const evt1 = await s.orig.getPastEvents('Payout', common.evt_last_block); 430 expect(evt1.length).equal(2); 431 expect(evt1[0].args.amount.div(trunc).toString()) 432 .equal(orig_bal.mul(mul1).div(tot).div(trunc).toString()); 433 expect(evt1[1].args.amount.div(trunc).toString()) 434 .equal(orig_bal.mul(mul2).div(tot).div(trunc).toString()); 435 await proposal1.withdraw({from: owner3}); 436 await proposal2.withdraw({from: owner4}); 437 438 await s.reward_abi.reward({from: owner1, value: addbal.add(extra)}); 439 expect(toBN(await s.treasury_abi.balance()).div(trunc).toString()) 440 .equal(extra.div(trunc).toString()); 441 const evt2 = await s.orig.getPastEvents('Payout', common.evt_last_block); 442 expect(evt2.length).equal(2); 443 expect(evt2[0].args.amount.div(trunc).toString()) 444 .equal(addbal.mul(mul1).div(tot).div(trunc).toString()); 445 expect(evt2[1].args.amount.div(trunc).toString()) 446 .equal(addbal.mul(mul2).div(tot).div(trunc).toString()); 447 448 await s.reward_abi.reward(); 449 const evt3 = await s.orig.getPastEvents('Payout', common.evt_last_block); 450 expect(evt3.length).equal(0); 451 452 const diff_bal1 = toBN(await web3.eth.getBalance(owner3)) 453 .sub(start_bal3); 454 const diff_bal2 = toBN(await web3.eth.getBalance(owner4)) 455 .sub(start_bal4); 456 expect(diff_bal1.add(orig_bal.sub(toBN(1))).div(orig_bal).toString()).equal(toBN(mul1).toString()); 457 expect(diff_bal2.add(orig_bal.sub(toBN(1))).div(orig_bal).toString()).equal(toBN(mul2).toString()); 458 expect(diff_bal1.lt(orig_bal.mul(mul1))).true; 459 expect(diff_bal2.lt(orig_bal.mul(mul2))).true; 460 expect(diff_bal1.gt(orig_bal)).true; 461 expect(diff_bal2.gt(orig_bal)).true; 462 }); 463 464 it ('should refuse propose() on UUID duplicate of past proposal', async () => { 465 try { 466 await s.treasury_abi.propose( 467 def_amount, '201', def_period, 468 { value: fee_amount } 469 ); 470 assert.fail('It must fail'); 471 } catch (e) { 472 assert.match(e.message, /UUID in use/); 473 } 474 try { 475 await s.treasury_abi.propose( 476 def_amount, '202', def_period, 477 { value: fee_amount } 478 ); 479 assert.fail('It must fail'); 480 } catch (e) { 481 assert.match(e.message, /UUID in use/); 482 } 483 484 }); 485 486 }); 487 488 //--- 489 describe('StorageTreasuryV1', async () => { 490 it ('should refuse setProposal() from outside', async () => { 491 try { 492 await s.storage.setProposal(0, s.fake.address); 493 assert.fail('It must fail'); 494 } catch (e) { 495 assert.match(e.message, /Not owner/); 496 } 497 }); 498 499 it ('should refuse deleteProposal() from outside', async () => { 500 try { 501 await s.storage.deleteProposal(s.fake.address); 502 assert.fail('It must fail'); 503 } catch (e) { 504 assert.match(e.message, /Not owner/); 505 } 506 }); 507 }); 508 509 //--- 510 describe('common post', () => common.govPostTests(s) ); 511 }); 512