Forráskód Böngészése

Add TimedCrowdsale::_extendTime (#1636)

* Add TimedCrowdsale::_extendTime

* Add tests for TimedCrowdsale extending method

* Reverse event arguments order

* Rename method argument

* Refactor TimedCrowdsale test

* Simplify TimedCrowdsaleImpl

* Fix extendTime method behaviour to deny TimedCrowdsale re-opening after it was ended

* Append chengelog

* Update CHANGELOG.md

Co-Authored-By: k06a <k06aaa@gmail.com>

* Update contracts/crowdsale/validation/TimedCrowdsale.sol

Co-Authored-By: k06a <k06aaa@gmail.com>

* Improve tests
Anton Bukov 6 éve
szülő
commit
352ec94579

+ 1 - 0
CHANGELOG.md

@@ -7,6 +7,7 @@
  * `ERC20Metadata`: added internal `_setTokenURI(string memory tokenURI)`. ([#1618](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1618))
  * `ERC20Snapshot`: create snapshots on demand of the token balances and total supply, to later retrieve and e.g. calculate dividends at a past time. ([#1617](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1617))
  * `SafeERC20`: `ERC20` contracts with no return value (i.e. that revert on failure) are now supported. ([#1655](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/))
+ * `TimedCrowdsale`: added internal `_extendTime(uint256 newClosingTime)` as well as `TimedCrowdsaleExtended(uint256 prevClosingTime, uint256 newClosingTime)` event allowing to extend the crowdsale, as long as it hasn't already closed.
 
 ### Improvements:
  * Upgraded the minimum compiler version to v0.5.2: this removes many Solidity warnings that were false positives. ([#1606](https://github.com/OpenZeppelin/openzeppelin-solidity/pull/1606))

+ 19 - 0
contracts/crowdsale/validation/TimedCrowdsale.sol

@@ -13,6 +13,13 @@ contract TimedCrowdsale is Crowdsale {
     uint256 private _openingTime;
     uint256 private _closingTime;
 
+    /**
+     * Event for crowdsale extending
+     * @param newClosingTime new closing time
+     * @param prevClosingTime old closing time
+     */
+    event TimedCrowdsaleExtended(uint256 prevClosingTime, uint256 newClosingTime);
+
     /**
      * @dev Reverts if not in crowdsale time range.
      */
@@ -74,4 +81,16 @@ contract TimedCrowdsale is Crowdsale {
     function _preValidatePurchase(address beneficiary, uint256 weiAmount) internal onlyWhileOpen view {
         super._preValidatePurchase(beneficiary, weiAmount);
     }
+
+    /**
+     * @dev Extend crowdsale
+     * @param newClosingTime Crowdsale closing time
+     */
+    function _extendTime(uint256 newClosingTime) internal {
+        require(!hasClosed());
+        require(newClosingTime > _closingTime);
+
+        emit TimedCrowdsaleExtended(_closingTime, newClosingTime);
+        _closingTime = newClosingTime;
+    }
 }

+ 4 - 0
contracts/mocks/TimedCrowdsaleImpl.sol

@@ -11,4 +11,8 @@ contract TimedCrowdsaleImpl is TimedCrowdsale {
     {
         // solhint-disable-previous-line no-empty-blocks
     }
+
+    function extendTime(uint256 closingTime) public {
+        _extendTime(closingTime);
+    }
 }

+ 58 - 1
test/crowdsale/TimedCrowdsale.test.js

@@ -1,4 +1,4 @@
-const { BN, ether, shouldFail, time } = require('openzeppelin-test-helpers');
+const { BN, ether, expectEvent, shouldFail, time } = require('openzeppelin-test-helpers');
 
 const TimedCrowdsaleImpl = artifacts.require('TimedCrowdsaleImpl');
 const SimpleToken = artifacts.require('SimpleToken');
@@ -73,5 +73,62 @@ contract('TimedCrowdsale', function ([_, investor, wallet, purchaser]) {
         await shouldFail.reverting(this.crowdsale.buyTokens(investor, { value: value, from: purchaser }));
       });
     });
+
+    describe('extending closing time', function () {
+      it('should not reduce duration', async function () {
+        // Same date
+        await shouldFail.reverting(this.crowdsale.extendTime(this.closingTime));
+
+        // Prescending date
+        const newClosingTime = this.closingTime.sub(time.duration.seconds(1));
+        await shouldFail.reverting(this.crowdsale.extendTime(newClosingTime));
+      });
+
+      context('before crowdsale start', function () {
+        beforeEach(async function () {
+          (await this.crowdsale.isOpen()).should.equal(false);
+          await shouldFail.reverting(this.crowdsale.send(value));
+        });
+
+        it('it extends end time', async function () {
+          const newClosingTime = this.closingTime.add(time.duration.days(1));
+          const { logs } = await this.crowdsale.extendTime(newClosingTime);
+          expectEvent.inLogs(logs, 'TimedCrowdsaleExtended', {
+            prevClosingTime: this.closingTime,
+            newClosingTime: newClosingTime,
+          });
+          (await this.crowdsale.closingTime()).should.be.bignumber.equal(newClosingTime);
+        });
+      });
+
+      context('after crowdsale start', function () {
+        beforeEach(async function () {
+          await time.increaseTo(this.openingTime);
+          (await this.crowdsale.isOpen()).should.equal(true);
+          await this.crowdsale.send(value);
+        });
+
+        it('it extends end time', async function () {
+          const newClosingTime = this.closingTime.add(time.duration.days(1));
+          const { logs } = await this.crowdsale.extendTime(newClosingTime);
+          expectEvent.inLogs(logs, 'TimedCrowdsaleExtended', {
+            prevClosingTime: this.closingTime,
+            newClosingTime: newClosingTime,
+          });
+          (await this.crowdsale.closingTime()).should.be.bignumber.equal(newClosingTime);
+        });
+      });
+
+      context('after crowdsale end', function () {
+        beforeEach(async function () {
+          await time.increaseTo(this.afterClosingTime);
+        });
+
+        it('it reverts', async function () {
+          const newClosingTime = await time.latest();
+          await shouldFail.reverting(this.crowdsale.extendTime(newClosingTime));
+        });
+      });
+    });
   });
 });