From 72cea2c1c3cc796edf996188e560fef8d3d70cfd Mon Sep 17 00:00:00 2001 From: John Feras Date: Tue, 28 Nov 2023 13:36:56 -0500 Subject: [PATCH 01/10] Pauser and renouncAdmin Drips tests --- foundry.toml | 2 +- src/interfaces/IDrips.sol | 16 ++++ test/Constants.sol | 1 + test/RadworksDripsGovernance.t.sol | 130 +++++++++++++++++++++++++++++ test/RadworksGovernor.t.sol | 9 -- test/helpers/ProposalTest.sol | 20 +++-- 6 files changed, 161 insertions(+), 17 deletions(-) create mode 100644 src/interfaces/IDrips.sol create mode 100644 test/RadworksDripsGovernance.t.sol diff --git a/foundry.toml b/foundry.toml index b85263e..bf945fe 100644 --- a/foundry.toml +++ b/foundry.toml @@ -1,5 +1,5 @@ [profile.default] - evm_version = "paris" + evm_version = "shanghai" fuzz = { seed = "1" } optimizer = true optimizer_runs = 10_000_000 diff --git a/src/interfaces/IDrips.sol b/src/interfaces/IDrips.sol new file mode 100644 index 0000000..e754c50 --- /dev/null +++ b/src/interfaces/IDrips.sol @@ -0,0 +1,16 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.8.0; + +interface IDrips { + function admin() external view returns (address); + function proposeNewAdmin(address newAdmin) external; + function acceptAdmin() external; + function renounceAdmin() external; + function grantPauser(address pauser) external; + function revokePauser(address pauser) external; + function isPauser(address pauser) external view returns (bool); + function allPausers() external view returns (address[] memory); + function isPaused() external view returns (bool); + function pause() external; + function unpause() external; +} diff --git a/test/Constants.sol b/test/Constants.sol index 52e3db4..961f4f0 100644 --- a/test/Constants.sol +++ b/test/Constants.sol @@ -5,6 +5,7 @@ contract Constants { address constant GOVERNOR_ALPHA = 0x690e775361AD66D1c4A25d89da9fCd639F5198eD; address payable constant RAD_TOKEN = payable(0x31c8EAcBFFdD875c74b94b077895Bd78CF1E64A3); address constant TIMELOCK = 0x8dA8f82d2BbDd896822de723F55D6EdF416130ba; + address constant DRIPS = 0xd0Dd053392db676D57317CD4fe96Fc2cCf42D0b4; // TODO: resolve the list of large delegates with tallyaddress address constant PROPOSER = 0x464D78a5C97A2E2E9839C353ee9B6d4204c90B0b; // cloudhead.eth diff --git a/test/RadworksDripsGovernance.t.sol b/test/RadworksDripsGovernance.t.sol new file mode 100644 index 0000000..0fd9768 --- /dev/null +++ b/test/RadworksDripsGovernance.t.sol @@ -0,0 +1,130 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import {ERC20VotesComp} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20VotesComp.sol"; +import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; +import {IGovernorAlpha} from "src/interfaces/IGovernorAlpha.sol"; +import {RadworksGovernorTest} from "test/helpers/RadworksGovernorTest.sol"; +import {ProposalTest} from "test/helpers/ProposalTest.sol"; + +abstract contract RadworksDripsGovernance is ProposalTest { + function setUp() public virtual override(ProposalTest) { + ProposalTest.setUp(); + _upgradeToBravoGovernor(); + } + + function _proposePassAndExecuteDripsProposal(string memory _description, bytes memory _callData) + internal + { + address[] memory _targets = new address[](1); + uint256[] memory _values = new uint256[](1); + bytes[] memory _calldatas = new bytes[](1); + + _targets[0] = DRIPS; + _calldatas[0] = _callData; + + // Submit the new proposal + vm.prank(PROPOSER); + uint256 _newProposalId = governorBravo.propose(_targets, _values, _calldatas, _description); + + // Ensure proposal is in the expected state + IGovernor.ProposalState _state = governorBravo.state(_newProposalId); + assertEq(_state, IGovernor.ProposalState.Pending); + + _jumpToActiveProposal(_newProposalId); + + _delegatesCastVoteOnBravoGovernor(_newProposalId, FOR); + _jumpToVotingComplete(_newProposalId); + + // Ensure the proposal has succeeded + _state = governorBravo.state(_newProposalId); + assertEq(_state, IGovernor.ProposalState.Succeeded); + + // Queue the proposal + governorBravo.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); + + // Ensure the proposal is queued + _state = governorBravo.state(_newProposalId); + assertEq(_state, IGovernor.ProposalState.Queued); + + _jumpPastProposalEta(_newProposalId); + + // Execute the proposal + governorBravo.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); + + // Ensure the proposal is executed + _state = governorBravo.state(_newProposalId); + assertEq(_state, IGovernor.ProposalState.Executed); + } + + function _grantNewPauserViaGovernance(address _newPauser) internal { + _proposePassAndExecuteDripsProposal( + "Grant Pauser role to an address", + _buildProposalData("grantPauser(address)", abi.encode(_newPauser)) + ); + } + + function _revokePauserViaGovernance(address _newPauser) internal { + _proposePassAndExecuteDripsProposal( + "Revoke Pauser role from an address", + _buildProposalData("revokePauser(address)", abi.encode(_newPauser)) + ); + } + + function testFuzz_grantPauserOnDrips(address _newPauser) public { + assummeNotTimelock(_newPauser); + address[] memory _originalPausers = drips.allPausers(); + + _grantNewPauserViaGovernance(_newPauser); + + // Ensure the new pauser has been granted pauser role + assertEq(drips.isPauser(_newPauser), true); + + // Ensure the the list of pausers got longer by 1 + assertEq(_originalPausers.length + 1, drips.allPausers().length); + + // Ensure the new pauser can pause the DRIPS protocol + vm.prank(_newPauser); + drips.pause(); + assertTrue(drips.isPaused()); + + // Ensure the new pauser can un-pause the DRIPS protocol + vm.prank(_newPauser); + drips.unpause(); + assertFalse(drips.isPaused()); + } + + function testFuzz_revokePauserOnDrips(address _newPauser) public { + assummeNotTimelock(_newPauser); + _grantNewPauserViaGovernance(_newPauser); + + // Ensure the new pauser has been granted pauser role + assertEq(drips.isPauser(_newPauser), true); + + _revokePauserViaGovernance(_newPauser); + + // Ensure the new pauser has subsequently had pauser role revoked + assertEq(drips.isPauser(_newPauser), false); + + // Ensure the newly-revoked pauser cannot pause the DRIPS protocol + vm.prank(_newPauser); + vm.expectRevert("Caller not the admin or a pauser"); + drips.pause(); + } + + function test_renounceAdminViaGovernance() public { + _proposePassAndExecuteDripsProposal( + "Renounce Admin role", _buildProposalData("renounceAdmin()", abi.encode()) + ); + + // Ensure the admin role has been renounced + assertEq(drips.admin(), address(0)); + } +} + +contract _ExecuteTestWithDeployScriptGovernor is RadworksDripsGovernance { + function _useDeployedGovernorBravo() internal pure override returns (bool) { + return false; + } +} diff --git a/test/RadworksGovernor.t.sol b/test/RadworksGovernor.t.sol index c3a07f2..f54790e 100644 --- a/test/RadworksGovernor.t.sol +++ b/test/RadworksGovernor.t.sol @@ -654,9 +654,6 @@ abstract contract Propose is ProposalTest { } } -// TODO: future PR -abstract contract Execute is ProposalTest {} - // Run the tests using the deployed Governor Bravo (future PR) // Run the tests using a version of the Governor deployed by the Deploy script @@ -680,9 +677,3 @@ contract ProposeTestWithDeployScriptGovernor is Propose { // return false; // } // } - -// contract _ExecuteTestWithDeployScriptGovernor is _Execute { -// function _useDeployedGovernorBravo() internal pure override returns (bool) { -// return false; -// } -// } diff --git a/test/helpers/ProposalTest.sol b/test/helpers/ProposalTest.sol index bc5da64..4e7bb05 100644 --- a/test/helpers/ProposalTest.sol +++ b/test/helpers/ProposalTest.sol @@ -8,11 +8,13 @@ import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; import {TestableProposeScript} from "test/helpers/TestableProposeScript.sol"; import {IGovernorAlpha} from "src/interfaces/IGovernorAlpha.sol"; +import {IDrips} from "src/interfaces/IDrips.sol"; import {RadworksGovernorTest} from "test/helpers/RadworksGovernorTest.sol"; abstract contract ProposalTest is RadworksGovernorTest { //----------------- State and Setup ----------- // + IDrips drips = IDrips(DRIPS); IGovernorAlpha governorAlpha = IGovernorAlpha(GOVERNOR_ALPHA); ICompoundTimelock timelock = ICompoundTimelock(payable(TIMELOCK)); uint256 initialProposalCount; @@ -57,25 +59,29 @@ abstract contract ProposalTest is RadworksGovernorTest { // a cheat for fuzzing addresses that are payable only // Why is this no longer in standard cheats? JJF // see https://github.com/foundry-rs/foundry/issues/3631 - function assumePayable(address addr) internal virtual { - (bool success,) = payable(addr).call{value: 0}(""); + function assumePayable(address _addr) internal virtual { + (bool success,) = payable(_addr).call{value: 0}(""); vm.assume(success); } - function _assumeReceiver(address _receiver) internal { - assumePayable(_receiver); + function assummeNotTimelock(address _addr) internal virtual { vm.assume( // We don't want the receiver to be the Timelock, as that would make our // assertions less meaningful -- most of our tests want to confirm that // proposals can cause tokens to be sent *from* the timelock to somewhere // else. - _receiver != TIMELOCK + _addr != TIMELOCK // We also can't have the receiver be the zero address because RAD // blocks transfers to the zero address -- see line 396: // https://etherscan.io/address/0x31c8EAcBFFdD875c74b94b077895Bd78CF1E64A3#code - && _receiver > address(0) + && _addr > address(0) ); - assumeNoPrecompiles(_receiver); + assumeNoPrecompiles(_addr); + } + + function _assumeReceiver(address _receiver) internal { + assumePayable(_receiver); + assummeNotTimelock(_receiver); } function _randomERC20Token(uint256 _seed) internal pure returns (IERC20 _token) { From 9355885233d4c6ec157b997d94f94543e488a103 Mon Sep 17 00:00:00 2001 From: John Feras Date: Tue, 28 Nov 2023 14:04:42 -0500 Subject: [PATCH 02/10] Removed unneeded imports from Drips tests --- test/RadworksDripsGovernance.t.sol | 4 ---- 1 file changed, 4 deletions(-) diff --git a/test/RadworksDripsGovernance.t.sol b/test/RadworksDripsGovernance.t.sol index 0fd9768..c59e052 100644 --- a/test/RadworksDripsGovernance.t.sol +++ b/test/RadworksDripsGovernance.t.sol @@ -1,11 +1,7 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.20; -import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import {ERC20VotesComp} from "@openzeppelin/contracts/token/ERC20/extensions/ERC20VotesComp.sol"; import {IGovernor} from "@openzeppelin/contracts/governance/IGovernor.sol"; -import {IGovernorAlpha} from "src/interfaces/IGovernorAlpha.sol"; -import {RadworksGovernorTest} from "test/helpers/RadworksGovernorTest.sol"; import {ProposalTest} from "test/helpers/ProposalTest.sol"; abstract contract RadworksDripsGovernance is ProposalTest { From 44ece7bbebce07790cf3dd557dd5c64be7eef550 Mon Sep 17 00:00:00 2001 From: John Feras Date: Mon, 4 Dec 2023 13:04:45 -0500 Subject: [PATCH 03/10] Removed mostly-redundant function and made use of proposal builder in Drips tests --- test/RadworksDripsGovernance.t.sol | 74 +++++++++++------------------- test/helpers/ProposalTest.sol | 20 ++++++++ 2 files changed, 47 insertions(+), 47 deletions(-) diff --git a/test/RadworksDripsGovernance.t.sol b/test/RadworksDripsGovernance.t.sol index c59e052..53c84b3 100644 --- a/test/RadworksDripsGovernance.t.sol +++ b/test/RadworksDripsGovernance.t.sol @@ -10,62 +10,34 @@ abstract contract RadworksDripsGovernance is ProposalTest { _upgradeToBravoGovernor(); } - function _proposePassAndExecuteDripsProposal(string memory _description, bytes memory _callData) - internal - { - address[] memory _targets = new address[](1); - uint256[] memory _values = new uint256[](1); - bytes[] memory _calldatas = new bytes[](1); - - _targets[0] = DRIPS; - _calldatas[0] = _callData; - - // Submit the new proposal - vm.prank(PROPOSER); - uint256 _newProposalId = governorBravo.propose(_targets, _values, _calldatas, _description); - - // Ensure proposal is in the expected state - IGovernor.ProposalState _state = governorBravo.state(_newProposalId); - assertEq(_state, IGovernor.ProposalState.Pending); - - _jumpToActiveProposal(_newProposalId); - - _delegatesCastVoteOnBravoGovernor(_newProposalId, FOR); - _jumpToVotingComplete(_newProposalId); - - // Ensure the proposal has succeeded - _state = governorBravo.state(_newProposalId); - assertEq(_state, IGovernor.ProposalState.Succeeded); - - // Queue the proposal - governorBravo.queue(_targets, _values, _calldatas, keccak256(bytes(_description))); - - // Ensure the proposal is queued - _state = governorBravo.state(_newProposalId); - assertEq(_state, IGovernor.ProposalState.Queued); - - _jumpPastProposalEta(_newProposalId); - - // Execute the proposal - governorBravo.execute(_targets, _values, _calldatas, keccak256(bytes(_description))); - - // Ensure the proposal is executed - _state = governorBravo.state(_newProposalId); - assertEq(_state, IGovernor.ProposalState.Executed); - } - function _grantNewPauserViaGovernance(address _newPauser) internal { - _proposePassAndExecuteDripsProposal( + ( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) = _buildDripsGovernanceProposal( "Grant Pauser role to an address", _buildProposalData("grantPauser(address)", abi.encode(_newPauser)) ); + _queueAndVoteAndExecuteProposalWithBravoGovernor( + _targets, _values, _calldatas, _description, FOR + ); } function _revokePauserViaGovernance(address _newPauser) internal { - _proposePassAndExecuteDripsProposal( + ( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) = _buildDripsGovernanceProposal( "Revoke Pauser role from an address", _buildProposalData("revokePauser(address)", abi.encode(_newPauser)) ); + _queueAndVoteAndExecuteProposalWithBravoGovernor( + _targets, _values, _calldatas, _description, FOR + ); } function testFuzz_grantPauserOnDrips(address _newPauser) public { @@ -110,9 +82,17 @@ abstract contract RadworksDripsGovernance is ProposalTest { } function test_renounceAdminViaGovernance() public { - _proposePassAndExecuteDripsProposal( + ( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldatas, + string memory _description + ) = _buildDripsGovernanceProposal( "Renounce Admin role", _buildProposalData("renounceAdmin()", abi.encode()) ); + _queueAndVoteAndExecuteProposalWithBravoGovernor( + _targets, _values, _calldatas, _description, FOR + ); // Ensure the admin role has been renounced assertEq(drips.admin(), address(0)); diff --git a/test/helpers/ProposalTest.sol b/test/helpers/ProposalTest.sol index 4e7bb05..46a6241 100644 --- a/test/helpers/ProposalTest.sol +++ b/test/helpers/ProposalTest.sol @@ -196,6 +196,26 @@ abstract contract ProposalTest is RadworksGovernorTest { return abi.encodePacked(bytes4(keccak256(bytes(_signature))), _calldata); } + function _buildDripsGovernanceProposal(string memory _description, bytes memory _callData) + internal + pure + returns ( + address[] memory _targets, + uint256[] memory _values, + bytes[] memory _calldata, + string memory _returnedDescription + ) + { + // Craft a new proposal to peform a Drips governance operation. + _targets = new address[](1); + _values = new uint256[](1); + _calldata = new bytes[](1); + + _targets[0] = DRIPS; + _calldata[0] = _callData; + _returnedDescription = _description; + } + function _jumpToActiveProposal(uint256 _proposalId) internal { uint256 _snapshot = governorBravo.proposalSnapshot(_proposalId); vm.roll(_snapshot + 1); From 2ced77b2bcb8e1d3c527ba3911672e7cf32f3395 Mon Sep 17 00:00:00 2001 From: John Feras Date: Mon, 4 Dec 2023 17:36:40 -0500 Subject: [PATCH 04/10] Now using IDrips interface create by 'cast interface' --- src/interfaces/IDrips.sol | 186 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 179 insertions(+), 7 deletions(-) diff --git a/src/interfaces/IDrips.sol b/src/interfaces/IDrips.sol index e754c50..1dc6eb2 100644 --- a/src/interfaces/IDrips.sol +++ b/src/interfaces/IDrips.sol @@ -1,16 +1,188 @@ // SPDX-License-Identifier: AGPL-3.0-only -pragma solidity >=0.8.0; +pragma solidity ^0.8.4; interface IDrips { - function admin() external view returns (address); - function proposeNewAdmin(address newAdmin) external; + type StreamConfig is uint256; + + struct AccountMetadata { + bytes32 key; + bytes value; + } + + struct SplitsReceiver { + uint256 accountId; + uint32 weight; + } + + struct StreamReceiver { + uint256 accountId; + StreamConfig config; + } + + struct StreamsHistory { + bytes32 streamsHash; + StreamReceiver[] receivers; + uint32 updateTime; + uint32 maxEnd; + } + + event AccountMetadataEmitted(uint256 indexed accountId, bytes32 indexed key, bytes value); + event AdminChanged(address previousAdmin, address newAdmin); + event BeaconUpgraded(address indexed beacon); + event Collectable(uint256 indexed accountId, address indexed erc20, uint128 amt); + event Collected(uint256 indexed accountId, address indexed erc20, uint128 collected); + event DriverAddressUpdated( + uint32 indexed driverId, address indexed oldDriverAddr, address indexed newDriverAddr + ); + event DriverRegistered(uint32 indexed driverId, address indexed driverAddr); + event Given( + uint256 indexed accountId, uint256 indexed receiver, address indexed erc20, uint128 amt + ); + event NewAdminProposed(address indexed currentAdmin, address indexed newAdmin); + event Paused(address indexed pauser); + event PauserGranted(address indexed pauser, address indexed admin); + event PauserRevoked(address indexed pauser, address indexed admin); + event ReceivedStreams( + uint256 indexed accountId, address indexed erc20, uint128 amt, uint32 receivableCycles + ); + event Split( + uint256 indexed accountId, uint256 indexed receiver, address indexed erc20, uint128 amt + ); + event SplitsReceiverSeen(bytes32 indexed receiversHash, uint256 indexed accountId, uint32 weight); + event SplitsSet(uint256 indexed accountId, bytes32 indexed receiversHash); + event SqueezedStreams( + uint256 indexed accountId, + address indexed erc20, + uint256 indexed senderId, + uint128 amt, + bytes32[] streamsHistoryHashes + ); + event StreamReceiverSeen( + bytes32 indexed receiversHash, uint256 indexed accountId, StreamConfig config + ); + event StreamsSet( + uint256 indexed accountId, + address indexed erc20, + bytes32 indexed receiversHash, + bytes32 streamsHistoryHash, + uint128 balance, + uint32 maxEnd + ); + event Unpaused(address indexed pauser); + event Upgraded(address indexed implementation); + event Withdrawn(address indexed erc20, address indexed receiver, uint256 amt); + + function AMT_PER_SEC_EXTRA_DECIMALS() external view returns (uint8); + function AMT_PER_SEC_MULTIPLIER() external view returns (uint160); + function DRIVER_ID_OFFSET() external view returns (uint8); + function MAX_SPLITS_RECEIVERS() external view returns (uint256); + function MAX_STREAMS_RECEIVERS() external view returns (uint256); + function MAX_TOTAL_BALANCE() external view returns (uint128); + function TOTAL_SPLITS_WEIGHT() external view returns (uint32); function acceptAdmin() external; - function renounceAdmin() external; + function admin() external view returns (address); + function allPausers() external view returns (address[] memory pausersList); + function balanceAt( + uint256 accountId, + address erc20, + StreamReceiver[] memory currReceivers, + uint32 timestamp + ) external view returns (uint128 balance); + function balances(address erc20) + external + view + returns (uint128 streamsBalance, uint128 splitsBalance); + function collect(uint256 accountId, address erc20) external returns (uint128 amt); + function collectable(uint256 accountId, address erc20) external view returns (uint128 amt); + function cycleSecs() external view returns (uint32); + function driverAddress(uint32 driverId) external view returns (address driverAddr); + function emitAccountMetadata(uint256 accountId, AccountMetadata[] memory accountMetadata) + external; + function give(uint256 accountId, uint256 receiver, address erc20, uint128 amt) external; function grantPauser(address pauser) external; - function revokePauser(address pauser) external; - function isPauser(address pauser) external view returns (bool); - function allPausers() external view returns (address[] memory); + function hashSplits(SplitsReceiver[] memory receivers) + external + pure + returns (bytes32 receiversHash); + function hashStreams(StreamReceiver[] memory receivers) + external + pure + returns (bytes32 streamsHash); + function hashStreamsHistory( + bytes32 oldStreamsHistoryHash, + bytes32 streamsHash, + uint32 updateTime, + uint32 maxEnd + ) external pure returns (bytes32 streamsHistoryHash); + function implementation() external view returns (address); function isPaused() external view returns (bool); + function isPauser(address pauser) external view returns (bool isAddrPauser); + function minAmtPerSec() external view returns (uint160); + function nextDriverId() external view returns (uint32 driverId); function pause() external; + function proposeNewAdmin(address newAdmin) external; + function proposedAdmin() external view returns (address); + function proxiableUUID() external view returns (bytes32); + function receivableStreamsCycles(uint256 accountId, address erc20) + external + view + returns (uint32 cycles); + function receiveStreams(uint256 accountId, address erc20, uint32 maxCycles) + external + returns (uint128 receivedAmt); + function receiveStreamsResult(uint256 accountId, address erc20, uint32 maxCycles) + external + view + returns (uint128 receivableAmt); + function registerDriver(address driverAddr) external returns (uint32 driverId); + function renounceAdmin() external; + function revokePauser(address pauser) external; + function setSplits(uint256 accountId, SplitsReceiver[] memory receivers) external; + function setStreams( + uint256 accountId, + address erc20, + StreamReceiver[] memory currReceivers, + int128 balanceDelta, + StreamReceiver[] memory newReceivers, + uint32 maxEndHint1, + uint32 maxEndHint2 + ) external returns (int128 realBalanceDelta); + function split(uint256 accountId, address erc20, SplitsReceiver[] memory currReceivers) + external + returns (uint128 collectableAmt, uint128 splitAmt); + function splitResult(uint256 accountId, SplitsReceiver[] memory currReceivers, uint128 amount) + external + view + returns (uint128 collectableAmt, uint128 splitAmt); + function splitsHash(uint256 accountId) external view returns (bytes32 currSplitsHash); + function splittable(uint256 accountId, address erc20) external view returns (uint128 amt); + function squeezeStreams( + uint256 accountId, + address erc20, + uint256 senderId, + bytes32 historyHash, + StreamsHistory[] memory streamsHistory + ) external returns (uint128 amt); + function squeezeStreamsResult( + uint256 accountId, + address erc20, + uint256 senderId, + bytes32 historyHash, + StreamsHistory[] memory streamsHistory + ) external view returns (uint128 amt); + function streamsState(uint256 accountId, address erc20) + external + view + returns ( + bytes32 streamsHash, + bytes32 streamsHistoryHash, + uint32 updateTime, + uint128 balance, + uint32 maxEnd + ); function unpause() external; + function updateDriverAddress(uint32 driverId, address newDriverAddr) external; + function upgradeTo(address newImplementation) external; + function upgradeToAndCall(address newImplementation, bytes memory data) external payable; + function withdraw(address erc20, address receiver, uint256 amt) external; } From d5e3291479633ba2639d549927a3b209defa6785 Mon Sep 17 00:00:00 2001 From: John Feras Date: Tue, 5 Dec 2023 14:08:06 -0500 Subject: [PATCH 05/10] Changed Drips setStreams function parameter names to avoice CI warning --- src/interfaces/IDrips.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/interfaces/IDrips.sol b/src/interfaces/IDrips.sol index 1dc6eb2..5c87308 100644 --- a/src/interfaces/IDrips.sol +++ b/src/interfaces/IDrips.sol @@ -144,8 +144,8 @@ interface IDrips { StreamReceiver[] memory currReceivers, int128 balanceDelta, StreamReceiver[] memory newReceivers, - uint32 maxEndHint1, - uint32 maxEndHint2 + uint32 FirstMaxEndHint, + uint32 SecondMaxEndHint ) external returns (int128 realBalanceDelta); function split(uint256 accountId, address erc20, SplitsReceiver[] memory currReceivers) external From 7a2ff849f4133592b88501e253b5672982733fc2 Mon Sep 17 00:00:00 2001 From: John Feras Date: Tue, 5 Dec 2023 15:39:51 -0500 Subject: [PATCH 06/10] Broke out pauser tests with more meaningful names --- test/RadworksDripsGovernance.t.sol | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test/RadworksDripsGovernance.t.sol b/test/RadworksDripsGovernance.t.sol index 53c84b3..4b3fd2d 100644 --- a/test/RadworksDripsGovernance.t.sol +++ b/test/RadworksDripsGovernance.t.sol @@ -51,6 +51,12 @@ abstract contract RadworksDripsGovernance is ProposalTest { // Ensure the the list of pausers got longer by 1 assertEq(_originalPausers.length + 1, drips.allPausers().length); + } + + function testFuzz_grantedPauserCanPauseAndUnPause(address _newPauser) public { + assummeNotTimelock(_newPauser); + + _grantNewPauserViaGovernance(_newPauser); // Ensure the new pauser can pause the DRIPS protocol vm.prank(_newPauser); @@ -74,11 +80,26 @@ abstract contract RadworksDripsGovernance is ProposalTest { // Ensure the new pauser has subsequently had pauser role revoked assertEq(drips.isPauser(_newPauser), false); + } + + function testFuzz_revertWhenRevokedPauserAttemptsPause(address _newPauser) public { + assummeNotTimelock(_newPauser); + _grantNewPauserViaGovernance(_newPauser); + + // Ensure the new pauser has been granted pauser role + assertEq(drips.isPauser(_newPauser), true); + + _revokePauserViaGovernance(_newPauser); // Ensure the newly-revoked pauser cannot pause the DRIPS protocol vm.prank(_newPauser); vm.expectRevert("Caller not the admin or a pauser"); drips.pause(); + + // Ensure that the Timelock contract is can still pause the DRIPS protocol + vm.prank(TIMELOCK); + drips.pause(); + assertEq(drips.isPauser(_newPauser), false); } function test_renounceAdminViaGovernance() public { From 2651f69dc494057e265d8ede1043fe374e6990f4 Mon Sep 17 00:00:00 2001 From: John Feras Date: Wed, 6 Dec 2023 12:56:14 -0500 Subject: [PATCH 07/10] Added comment about how IDrips.sol was created --- src/interfaces/IDrips.sol | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/interfaces/IDrips.sol b/src/interfaces/IDrips.sol index 5c87308..ee2baea 100644 --- a/src/interfaces/IDrips.sol +++ b/src/interfaces/IDrips.sol @@ -1,6 +1,8 @@ // SPDX-License-Identifier: AGPL-3.0-only pragma solidity ^0.8.4; +/// @dev This interface file for the DRIPS protocol was created via the command: +/// `cast interface 0xb0C9B6D67608bE300398d0e4FB0cCa3891E1B33F` interface IDrips { type StreamConfig is uint256; From bf585a14b4137012761daa82892af10a402534c4 Mon Sep 17 00:00:00 2001 From: John Feras Date: Wed, 20 Dec 2023 14:02:43 -0500 Subject: [PATCH 08/10] Added .DS_Store to .gitignore --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 2036134..8d4e238 100644 --- a/.gitignore +++ b/.gitignore @@ -10,6 +10,9 @@ out/ # Dotenv file .env +# MacOS file cruff +.DS_Store + # Coverage lcov.info From b70ca2bd4dd6339680378b2484403a58fa6fc4c4 Mon Sep 17 00:00:00 2001 From: John Feras Date: Wed, 20 Dec 2023 14:34:17 -0500 Subject: [PATCH 09/10] Update test function names --- test/RadworksDripsGovernance.t.sol | 8 ++++---- test/helpers/ProposalTest.sol | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/test/RadworksDripsGovernance.t.sol b/test/RadworksDripsGovernance.t.sol index 4b3fd2d..58a8e30 100644 --- a/test/RadworksDripsGovernance.t.sol +++ b/test/RadworksDripsGovernance.t.sol @@ -41,7 +41,7 @@ abstract contract RadworksDripsGovernance is ProposalTest { } function testFuzz_grantPauserOnDrips(address _newPauser) public { - assummeNotTimelock(_newPauser); + _assumeNotTimelock(_newPauser); address[] memory _originalPausers = drips.allPausers(); _grantNewPauserViaGovernance(_newPauser); @@ -54,7 +54,7 @@ abstract contract RadworksDripsGovernance is ProposalTest { } function testFuzz_grantedPauserCanPauseAndUnPause(address _newPauser) public { - assummeNotTimelock(_newPauser); + _assumeNotTimelock(_newPauser); _grantNewPauserViaGovernance(_newPauser); @@ -70,7 +70,7 @@ abstract contract RadworksDripsGovernance is ProposalTest { } function testFuzz_revokePauserOnDrips(address _newPauser) public { - assummeNotTimelock(_newPauser); + _assumeNotTimelock(_newPauser); _grantNewPauserViaGovernance(_newPauser); // Ensure the new pauser has been granted pauser role @@ -83,7 +83,7 @@ abstract contract RadworksDripsGovernance is ProposalTest { } function testFuzz_revertWhenRevokedPauserAttemptsPause(address _newPauser) public { - assummeNotTimelock(_newPauser); + _assumeNotTimelock(_newPauser); _grantNewPauserViaGovernance(_newPauser); // Ensure the new pauser has been granted pauser role diff --git a/test/helpers/ProposalTest.sol b/test/helpers/ProposalTest.sol index 46a6241..27a9139 100644 --- a/test/helpers/ProposalTest.sol +++ b/test/helpers/ProposalTest.sol @@ -59,12 +59,12 @@ abstract contract ProposalTest is RadworksGovernorTest { // a cheat for fuzzing addresses that are payable only // Why is this no longer in standard cheats? JJF // see https://github.com/foundry-rs/foundry/issues/3631 - function assumePayable(address _addr) internal virtual { + function _assumePayable(address _addr) internal virtual { (bool success,) = payable(_addr).call{value: 0}(""); vm.assume(success); } - function assummeNotTimelock(address _addr) internal virtual { + function _assumeNotTimelock(address _addr) internal virtual { vm.assume( // We don't want the receiver to be the Timelock, as that would make our // assertions less meaningful -- most of our tests want to confirm that @@ -80,8 +80,8 @@ abstract contract ProposalTest is RadworksGovernorTest { } function _assumeReceiver(address _receiver) internal { - assumePayable(_receiver); - assummeNotTimelock(_receiver); + _assumePayable(_receiver); + _assumeNotTimelock(_receiver); } function _randomERC20Token(uint256 _seed) internal pure returns (IERC20 _token) { From 8b89ae610e34dcb41ed910f8823fe60bd52ab907 Mon Sep 17 00:00:00 2001 From: John Feras Date: Wed, 20 Dec 2023 15:07:15 -0500 Subject: [PATCH 10/10] Added assume for pauser test that fuzzed pauser not already in pauser list --- test/RadworksDripsGovernance.t.sol | 1 + 1 file changed, 1 insertion(+) diff --git a/test/RadworksDripsGovernance.t.sol b/test/RadworksDripsGovernance.t.sol index 58a8e30..f558d38 100644 --- a/test/RadworksDripsGovernance.t.sol +++ b/test/RadworksDripsGovernance.t.sol @@ -42,6 +42,7 @@ abstract contract RadworksDripsGovernance is ProposalTest { function testFuzz_grantPauserOnDrips(address _newPauser) public { _assumeNotTimelock(_newPauser); + vm.assume(!drips.isPauser(_newPauser)); address[] memory _originalPausers = drips.allPausers(); _grantNewPauserViaGovernance(_newPauser);