github.com/ethereum-optimism/optimism@v1.7.2/packages/contracts-bedrock/test/periphery/drippie/Drippie.t.sol (about)

     1  // SPDX-License-Identifier: MIT
     2  pragma solidity 0.8.15;
     3  
     4  import { Test } from "forge-std/Test.sol";
     5  import { Drippie } from "src/periphery/drippie/Drippie.sol";
     6  import { IDripCheck } from "src/periphery/drippie/IDripCheck.sol";
     7  import { CheckTrue } from "src/periphery/drippie/dripchecks/CheckTrue.sol";
     8  import { SimpleStorage } from "test/mocks/SimpleStorage.sol";
     9  
    10  /// @title  TestDrippie
    11  /// @notice This is a wrapper contract around Drippie used for testing.
    12  ///         Returning an entire `Drippie.DripState` causes stack too
    13  ///         deep errors without `--vir-ir` which causes the compile time
    14  ///         to go up by ~4x. Each of the methods is a simple getter around
    15  ///         parts of the `DripState`.
    16  contract TestDrippie is Drippie {
    17      constructor(address owner) Drippie(owner) { }
    18  
    19      function dripStatus(string memory name) external view returns (Drippie.DripStatus) {
    20          return drips[name].status;
    21      }
    22  
    23      function dripStateLast(string memory name) external view returns (uint256) {
    24          return drips[name].last;
    25      }
    26  
    27      function dripConfig(string memory name) external view returns (Drippie.DripConfig memory) {
    28          return drips[name].config;
    29      }
    30  
    31      function dripConfigActions(string memory name) external view returns (Drippie.DripAction[] memory) {
    32          return drips[name].config.actions;
    33      }
    34  
    35      function dripConfigCheckParams(string memory name) external view returns (bytes memory) {
    36          return drips[name].config.checkparams;
    37      }
    38  
    39      function dripConfigCheckAddress(string memory name) external view returns (address) {
    40          return address(drips[name].config.dripcheck);
    41      }
    42  }
    43  
    44  /// @title  Drippie_Test
    45  /// @notice Test coverage of the Drippie contract.
    46  contract Drippie_Test is Test {
    47      /// @notice Emitted when a drip is executed.
    48      event DripExecuted(string indexed nameref, string name, address executor, uint256 timestamp);
    49  
    50      /// @notice Emitted when a drip's status is updated.
    51      event DripStatusUpdated(string indexed nameref, string name, Drippie.DripStatus status);
    52  
    53      /// @notice Emitted when a drip is created.
    54      event DripCreated(string indexed nameref, string name, Drippie.DripConfig config);
    55  
    56      /// @notice Address of the test DripCheck. CheckTrue is deployed
    57      ///         here so it always returns true.
    58      IDripCheck check;
    59  
    60      /// @notice Address of a SimpleStorage contract. Used to test that
    61      ///         calls are actually made by Drippie.
    62      SimpleStorage simpleStorage;
    63  
    64      /// @notice Address of the Drippie contract.
    65      TestDrippie drippie;
    66  
    67      /// @notice Address of the Drippie owner
    68      address constant alice = address(0x42);
    69  
    70      /// @notice The name of the default drip
    71      string constant dripName = "foo";
    72  
    73      /// @notice Set up the test suite by deploying a CheckTrue DripCheck.
    74      ///         This is a mock that always returns true. Alice is the owner
    75      ///         and also give drippie 1 ether so that it can balance transfer.
    76      function setUp() external {
    77          check = IDripCheck(new CheckTrue());
    78          simpleStorage = new SimpleStorage();
    79  
    80          drippie = new TestDrippie(alice);
    81          vm.deal(address(drippie), 1 ether);
    82      }
    83  
    84      /// @notice Builds a default Drippie.DripConfig. Uses CheckTrue as the
    85      ///         dripcheck so it will always be able to do its DripActions.
    86      ///         Gives a dummy DripAction that does a simple transfer to
    87      ///         a dummy address.
    88      function _defaultConfig() internal view returns (Drippie.DripConfig memory) {
    89          Drippie.DripAction[] memory actions = new Drippie.DripAction[](1);
    90          actions[0] = Drippie.DripAction({ target: payable(address(0x44)), data: hex"", value: 1 });
    91  
    92          return Drippie.DripConfig({
    93              interval: 100,
    94              dripcheck: check,
    95              reentrant: false,
    96              checkparams: hex"",
    97              actions: actions
    98          });
    99      }
   100  
   101      /// @notice Creates a default drip using the default drip config.
   102      function _createDefaultDrip(string memory name) internal {
   103          address owner = drippie.owner();
   104          Drippie.DripConfig memory cfg = _defaultConfig();
   105          vm.prank(owner);
   106          drippie.create(name, cfg);
   107      }
   108  
   109      /// @notice Moves the block's timestamp forward so that it is
   110      ///         possible to execute a drip.
   111      function _warpToExecutable(string memory name) internal {
   112          Drippie.DripConfig memory config = drippie.dripConfig(name);
   113          vm.warp(config.interval + drippie.dripStateLast(name));
   114      }
   115  
   116      /// @notice Creates a drip and asserts that it was configured as expected.
   117      function test_create_succeeds() external {
   118          Drippie.DripConfig memory cfg = _defaultConfig();
   119          vm.expectEmit(address(drippie));
   120          emit DripCreated(dripName, dripName, cfg);
   121  
   122          if (cfg.reentrant) {
   123              assertEq(cfg.interval, 0);
   124          } else {
   125              assertTrue(cfg.interval > 0);
   126          }
   127  
   128          vm.prank(drippie.owner());
   129          drippie.create(dripName, cfg);
   130  
   131          Drippie.DripStatus status = drippie.dripStatus(dripName);
   132          Drippie.DripConfig memory config = drippie.dripConfig(dripName);
   133  
   134          assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED));
   135  
   136          assertEq(config.interval, cfg.interval);
   137          assertEq(config.reentrant, cfg.reentrant);
   138          assertEq(address(config.dripcheck), address(cfg.dripcheck));
   139          assertEq(config.checkparams, cfg.checkparams);
   140  
   141          assertEq(config.actions.length, cfg.actions.length);
   142  
   143          for (uint256 i; i < config.actions.length; i++) {
   144              Drippie.DripAction memory a = config.actions[i];
   145              Drippie.DripAction memory b = cfg.actions[i];
   146  
   147              assertEq(a.target, b.target);
   148              assertEq(a.data, b.data);
   149              assertEq(a.value, b.value);
   150          }
   151      }
   152  
   153      /// @notice Ensures that the same drip cannot be created two times.
   154      function test_create_calledTwice_reverts() external {
   155          vm.startPrank(drippie.owner());
   156          Drippie.DripConfig memory cfg = _defaultConfig();
   157          drippie.create(dripName, cfg);
   158          vm.expectRevert("Drippie: drip with that name already exists");
   159          drippie.create(dripName, cfg);
   160          vm.stopPrank();
   161      }
   162  
   163      /// @notice Ensures that only the owner of Drippie can create a drip.
   164      function testFuzz_owner_unauthorized_reverts(address caller) external {
   165          vm.assume(caller != drippie.owner());
   166          vm.prank(caller);
   167          vm.expectRevert("UNAUTHORIZED");
   168          drippie.create(dripName, _defaultConfig());
   169      }
   170  
   171      /// @notice The owner should be able to set the status of the drip.
   172      function test_set_status_succeeds() external {
   173          vm.expectEmit(address(drippie));
   174          emit DripCreated(dripName, dripName, _defaultConfig());
   175          _createDefaultDrip(dripName);
   176  
   177          address owner = drippie.owner();
   178  
   179          {
   180              Drippie.DripStatus status = drippie.dripStatus(dripName);
   181              assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED));
   182          }
   183  
   184          vm.prank(owner);
   185  
   186          vm.expectEmit(address(drippie));
   187          emit DripStatusUpdated({ nameref: dripName, name: dripName, status: Drippie.DripStatus.ACTIVE });
   188  
   189          drippie.status(dripName, Drippie.DripStatus.ACTIVE);
   190  
   191          {
   192              Drippie.DripStatus status = drippie.dripStatus(dripName);
   193              assertEq(uint256(status), uint256(Drippie.DripStatus.ACTIVE));
   194          }
   195  
   196          vm.prank(owner);
   197  
   198          vm.expectEmit(address(drippie));
   199          emit DripStatusUpdated({ nameref: dripName, name: dripName, status: Drippie.DripStatus.PAUSED });
   200  
   201          drippie.status(dripName, Drippie.DripStatus.PAUSED);
   202  
   203          {
   204              Drippie.DripStatus status = drippie.dripStatus(dripName);
   205              assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED));
   206          }
   207      }
   208  
   209      /// @notice The drip status cannot be set back to NONE after it is created.
   210      function test_set_statusNone_reverts() external {
   211          _createDefaultDrip(dripName);
   212  
   213          vm.prank(drippie.owner());
   214  
   215          vm.expectRevert("Drippie: drip status can never be set back to NONE after creation");
   216  
   217          drippie.status(dripName, Drippie.DripStatus.NONE);
   218      }
   219  
   220      /// @notice The owner cannot set the status of the drip to the status that
   221      ///         it is already set as.
   222      function test_set_statusSame_reverts() external {
   223          _createDefaultDrip(dripName);
   224  
   225          vm.prank(drippie.owner());
   226  
   227          vm.expectRevert("Drippie: cannot set drip status to the same status as its current status");
   228  
   229          drippie.status(dripName, Drippie.DripStatus.PAUSED);
   230      }
   231  
   232      /// @notice The owner should be able to archive the drip if it is in the
   233      ///         paused state.
   234      function test_shouldArchive_ifPaused_succeeds() external {
   235          _createDefaultDrip(dripName);
   236  
   237          address owner = drippie.owner();
   238  
   239          vm.prank(owner);
   240  
   241          vm.expectEmit(address(drippie));
   242          emit DripStatusUpdated({ nameref: dripName, name: dripName, status: Drippie.DripStatus.ARCHIVED });
   243  
   244          drippie.status(dripName, Drippie.DripStatus.ARCHIVED);
   245  
   246          Drippie.DripStatus status = drippie.dripStatus(dripName);
   247          assertEq(uint256(status), uint256(Drippie.DripStatus.ARCHIVED));
   248      }
   249  
   250      /// @notice The owner should not be able to archive the drip if it is in the
   251      ///         active state.
   252      function test_shouldNotArchive_ifActive_reverts() external {
   253          _createDefaultDrip(dripName);
   254  
   255          vm.prank(drippie.owner());
   256          drippie.status(dripName, Drippie.DripStatus.ACTIVE);
   257  
   258          vm.prank(drippie.owner());
   259  
   260          vm.expectRevert("Drippie: drip must first be paused before being archived");
   261  
   262          drippie.status(dripName, Drippie.DripStatus.ARCHIVED);
   263      }
   264  
   265      /// @notice The owner should not be allowed to pause the drip if it
   266      ///         has already been archived.
   267      function test_shouldNotAllowPaused_ifArchived_reverts() external {
   268          _createDefaultDrip(dripName);
   269  
   270          _notAllowFromArchive(dripName, Drippie.DripStatus.PAUSED);
   271      }
   272  
   273      /// @notice The owner should not be allowed to make the drip active again if
   274      ///         it has already been archived.
   275      function test_shouldNotAllowActive_ifArchived_reverts() external {
   276          _createDefaultDrip(dripName);
   277  
   278          _notAllowFromArchive(dripName, Drippie.DripStatus.ACTIVE);
   279      }
   280  
   281      /// @notice Archive the drip and then attempt to set the status to the passed
   282      ///         in status.
   283      function _notAllowFromArchive(string memory name, Drippie.DripStatus status) internal {
   284          address owner = drippie.owner();
   285          vm.prank(owner);
   286          drippie.status(name, Drippie.DripStatus.ARCHIVED);
   287  
   288          vm.expectRevert("Drippie: drip with that name has been archived and cannot be updated");
   289  
   290          vm.prank(owner);
   291          drippie.status(name, status);
   292      }
   293  
   294      /// @notice Attempt to update a drip that does not exist.
   295      function test_name_notExist_reverts() external {
   296          string memory otherName = "bar";
   297  
   298          vm.prank(drippie.owner());
   299  
   300          vm.expectRevert("Drippie: drip with that name does not exist and cannot be updated");
   301  
   302          assertFalse(keccak256(abi.encode(dripName)) == keccak256(abi.encode(otherName)));
   303  
   304          drippie.status(otherName, Drippie.DripStatus.ARCHIVED);
   305      }
   306  
   307      /// @notice Expect a revert when attempting to set the status when not the owner.
   308      function test_status_unauthorized_reverts() external {
   309          _createDefaultDrip(dripName);
   310  
   311          vm.expectRevert("UNAUTHORIZED");
   312          drippie.status(dripName, Drippie.DripStatus.ACTIVE);
   313      }
   314  
   315      /// @notice The drip should execute and be able to transfer value.
   316      function test_drip_amount_succeeds() external {
   317          _createDefaultDrip(dripName);
   318  
   319          vm.prank(drippie.owner());
   320          drippie.status(dripName, Drippie.DripStatus.ACTIVE);
   321  
   322          _warpToExecutable(dripName);
   323  
   324          vm.expectEmit(address(drippie));
   325          emit DripExecuted({ nameref: dripName, name: dripName, executor: address(this), timestamp: block.timestamp });
   326  
   327          Drippie.DripAction[] memory actions = drippie.dripConfigActions(dripName);
   328          assertEq(actions.length, 1);
   329  
   330          vm.expectCall(drippie.dripConfigCheckAddress(dripName), drippie.dripConfigCheckParams(dripName));
   331  
   332          vm.expectCall(actions[0].target, actions[0].value, actions[0].data);
   333  
   334          drippie.drip(dripName);
   335      }
   336  
   337      /// @notice A single DripAction should be able to make a state modifying call.
   338      function test_trigger_oneFunction_succeeds() external {
   339          Drippie.DripConfig memory cfg = _defaultConfig();
   340  
   341          bytes32 key = bytes32(uint256(2));
   342          bytes32 value = bytes32(uint256(3));
   343  
   344          // Add in an action
   345          cfg.actions[0] = Drippie.DripAction({
   346              target: payable(address(simpleStorage)),
   347              data: abi.encodeWithSelector(SimpleStorage.set.selector, key, value),
   348              value: 0
   349          });
   350  
   351          vm.prank(drippie.owner());
   352          drippie.create(dripName, cfg);
   353  
   354          _warpToExecutable(dripName);
   355  
   356          vm.prank(drippie.owner());
   357          drippie.status(dripName, Drippie.DripStatus.ACTIVE);
   358  
   359          vm.expectCall(address(simpleStorage), 0, abi.encodeWithSelector(SimpleStorage.set.selector, key, value));
   360  
   361          vm.expectEmit(address(drippie));
   362          emit DripExecuted(dripName, dripName, address(this), block.timestamp);
   363          drippie.drip(dripName);
   364  
   365          assertEq(simpleStorage.get(key), value);
   366      }
   367  
   368      /// @notice Multiple drip actions should be able to be triggered with the same check.
   369      function test_trigger_twoFunctions_succeeds() external {
   370          Drippie.DripConfig memory cfg = _defaultConfig();
   371          Drippie.DripAction[] memory actions = new Drippie.DripAction[](2);
   372  
   373          bytes32 keyOne = bytes32(uint256(2));
   374          bytes32 valueOne = bytes32(uint256(3));
   375          actions[0] = Drippie.DripAction({
   376              target: payable(address(simpleStorage)),
   377              data: abi.encodeWithSelector(simpleStorage.set.selector, keyOne, valueOne),
   378              value: 0
   379          });
   380  
   381          bytes32 keyTwo = bytes32(uint256(4));
   382          bytes32 valueTwo = bytes32(uint256(5));
   383          actions[1] = Drippie.DripAction({
   384              target: payable(address(simpleStorage)),
   385              data: abi.encodeWithSelector(simpleStorage.set.selector, keyTwo, valueTwo),
   386              value: 0
   387          });
   388  
   389          cfg.actions = actions;
   390  
   391          vm.prank(drippie.owner());
   392          drippie.create(dripName, cfg);
   393  
   394          _warpToExecutable(dripName);
   395  
   396          vm.prank(drippie.owner());
   397          drippie.status(dripName, Drippie.DripStatus.ACTIVE);
   398  
   399          vm.expectCall(drippie.dripConfigCheckAddress(dripName), drippie.dripConfigCheckParams(dripName));
   400  
   401          vm.expectCall(address(simpleStorage), 0, abi.encodeWithSelector(SimpleStorage.set.selector, keyOne, valueOne));
   402  
   403          vm.expectCall(address(simpleStorage), 0, abi.encodeWithSelector(SimpleStorage.set.selector, keyTwo, valueTwo));
   404  
   405          vm.expectEmit(address(drippie));
   406          emit DripExecuted(dripName, dripName, address(this), block.timestamp);
   407          drippie.drip(dripName);
   408  
   409          assertEq(simpleStorage.get(keyOne), valueOne);
   410  
   411          assertEq(simpleStorage.get(keyTwo), valueTwo);
   412      }
   413  
   414      /// @notice The drips can only be triggered once per interval. Attempt to
   415      ///         trigger the same drip multiple times in the same interval. Then
   416      ///         move forward to the next interval and it should trigger.
   417      function test_twice_inOneInterval_reverts() external {
   418          _createDefaultDrip(dripName);
   419  
   420          vm.prank(drippie.owner());
   421          drippie.status(dripName, Drippie.DripStatus.ACTIVE);
   422  
   423          _warpToExecutable(dripName);
   424  
   425          vm.prank(drippie.owner());
   426  
   427          drippie.drip(dripName);
   428  
   429          vm.prank(drippie.owner());
   430  
   431          vm.expectRevert("Drippie: drip interval has not elapsed since last drip");
   432  
   433          drippie.drip(dripName);
   434  
   435          _warpToExecutable(dripName);
   436  
   437          vm.expectEmit(address(drippie));
   438          emit DripExecuted({ nameref: dripName, name: dripName, executor: address(this), timestamp: block.timestamp });
   439  
   440          drippie.drip(dripName);
   441      }
   442  
   443      /// @notice It should revert if attempting to trigger a drip that does not exist.
   444      ///         Note that the drip was never created at the beginning of the test.
   445      function test_drip_notExist_reverts() external {
   446          vm.prank(drippie.owner());
   447  
   448          vm.expectRevert("Drippie: selected drip does not exist or is not currently active");
   449  
   450          drippie.drip(dripName);
   451      }
   452  
   453      /// @notice The owner cannot trigger the drip when it is paused.
   454      function test_not_active_reverts() external {
   455          _createDefaultDrip(dripName);
   456  
   457          Drippie.DripStatus status = drippie.dripStatus(dripName);
   458          assertEq(uint256(status), uint256(Drippie.DripStatus.PAUSED));
   459  
   460          vm.prank(drippie.owner());
   461  
   462          vm.expectRevert("Drippie: selected drip does not exist or is not currently active");
   463  
   464          drippie.drip(dripName);
   465      }
   466  
   467      /// @notice The interval must be 0 if reentrant is set on the config.
   468      function test_reentrant_succeeds() external {
   469          address owner = drippie.owner();
   470          Drippie.DripConfig memory cfg = _defaultConfig();
   471          cfg.reentrant = true;
   472          cfg.interval = 0;
   473  
   474          vm.prank(owner);
   475          vm.expectEmit(address(drippie));
   476          emit DripCreated(dripName, dripName, cfg);
   477          drippie.create(dripName, cfg);
   478  
   479          Drippie.DripConfig memory _cfg = drippie.dripConfig(dripName);
   480          assertEq(_cfg.reentrant, true);
   481          assertEq(_cfg.interval, 0);
   482      }
   483  
   484      /// @notice A non zero interval when reentrant is true will cause a revert
   485      ///         when creating a drip.
   486      function test_drip_reentrant_reverts() external {
   487          address owner = drippie.owner();
   488          Drippie.DripConfig memory cfg = _defaultConfig();
   489          cfg.reentrant = true;
   490          cfg.interval = 1;
   491  
   492          vm.prank(owner);
   493  
   494          vm.expectRevert("Drippie: if allowing reentrant drip, must set interval to zero");
   495          drippie.create(dripName, cfg);
   496      }
   497  
   498      /// @notice If reentrant is false and the interval is 0 then it should
   499      ///         revert when the drip is created.
   500      function test_notReentrant_zeroInterval_reverts() external {
   501          address owner = drippie.owner();
   502          Drippie.DripConfig memory cfg = _defaultConfig();
   503          cfg.reentrant = false;
   504          cfg.interval = 0;
   505  
   506          vm.prank(owner);
   507  
   508          vm.expectRevert("Drippie: interval must be greater than zero if drip is not reentrant");
   509          drippie.create(dripName, cfg);
   510      }
   511  }