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