Skip to content

Commit

Permalink
Merge pull request #83 from morpho-org/fix/more-explicit-function-nam…
Browse files Browse the repository at this point in the history
…e-80

Rename `RootRevoked` to `PendingRootRevoked`
  • Loading branch information
MerlinEgalite authored Nov 7, 2023
2 parents 28cf27a + c6b6ce2 commit 378e73c
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 41 deletions.
27 changes: 13 additions & 14 deletions src/UniversalRewardsDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,9 @@ contract UniversalRewardsDistributor is IUniversalRewardsDistributorStaticTyping
if (timelock == 0) {
_setRoot(newRoot, newIpfsHash);
} else {
pendingRoot = PendingRoot(block.timestamp + timelock, newRoot, newIpfsHash);
emit EventsLib.RootProposed(newRoot, newIpfsHash);
pendingRoot = PendingRoot({root: newRoot, ipfsHash: newIpfsHash, validAt: block.timestamp + timelock});

emit EventsLib.PendingRootSet(msg.sender, newRoot, newIpfsHash);
}
}

Expand All @@ -96,6 +97,16 @@ contract UniversalRewardsDistributor is IUniversalRewardsDistributorStaticTyping
_setRoot(pendingRoot.root, pendingRoot.ipfsHash);
}

/// @notice Revokes the pending root.
/// @dev Can be frontrunned with `acceptRoot` in case the timelock has passed.
function revokePendingRoot() external onlyUpdaterRole {
require(pendingRoot.validAt != 0, ErrorsLib.NO_PENDING_ROOT);

delete pendingRoot;

emit EventsLib.PendingRootRevoked(msg.sender);
}

/// @notice Claims rewards.
/// @param account The address to claim rewards for.
/// @param reward The address of the reward token.
Expand Down Expand Up @@ -152,18 +163,6 @@ contract UniversalRewardsDistributor is IUniversalRewardsDistributorStaticTyping
emit EventsLib.RootUpdaterSet(updater, active);
}

/// @notice Revokes the pending root of a given distribution.
/// @dev This function can only be called by the owner of the distribution at any time.
/// @dev Can be frontrunned by triggering the `acceptRoot` function in case the timelock has passed. This if the
/// `owner` responsibility to trigger this function before the end of the timelock.
function revokeRoot() external onlyOwner {
require(pendingRoot.validAt != 0, ErrorsLib.NO_PENDING_ROOT);

delete pendingRoot;

emit EventsLib.RootRevoked();
}

/// @notice Sets the `owner` of the distribution to `newOwner`.
function setOwner(address newOwner) external onlyOwner {
_setOwner(newOwner);
Expand Down
8 changes: 4 additions & 4 deletions src/interfaces/IUniversalRewardsDistributor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,12 @@ pragma solidity >=0.5.0;

/// @notice The pending root struct for a merkle tree distribution during the timelock.
struct PendingRoot {
/// @dev The timestamp at which the pending root can be accepted.
uint256 validAt;
/// @dev The submitted pending root.
bytes32 root;
/// @dev The optional ipfs hash containing metadata about the root (e.g. the merkle tree itself).
bytes32 ipfsHash;
/// @dev The timestamp at which the pending root can be accepted.
uint256 validAt;
}

/// @dev This interface is used for factorizing IUniversalRewardsDistributorStaticTyping and
Expand All @@ -26,7 +26,7 @@ interface IUniversalRewardsDistributorBase {
function setRoot(bytes32 newRoot, bytes32 newIpfsHash) external;
function setTimelock(uint256 newTimelock) external;
function setRootUpdater(address updater, bool active) external;
function revokeRoot() external;
function revokePendingRoot() external;
function setOwner(address newOwner) external;

function submitRoot(bytes32 newRoot, bytes32 ipfsHash) external;
Expand All @@ -40,7 +40,7 @@ interface IUniversalRewardsDistributorBase {
/// compiler.
/// @dev Consider using the IUniversalRewardsDistributor interface instead of this one.
interface IUniversalRewardsDistributorStaticTyping is IUniversalRewardsDistributorBase {
function pendingRoot() external view returns (uint256 submittedAt, bytes32 root, bytes32 ipfsHash);
function pendingRoot() external view returns (bytes32 root, bytes32 ipfsHash, uint256 validAt);
}

/// @title IUniversalRewardsDistributor
Expand Down
9 changes: 5 additions & 4 deletions src/libraries/EventsLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,13 @@ library EventsLib {
event RootSet(bytes32 indexed newRoot, bytes32 indexed newIpfsHash);

/// @notice Emitted when a new merkle root is proposed.
/// @param caller The address of the caller.
/// @param newRoot The new merkle root.
/// @param newIpfsHash The optional ipfs hash containing metadata about the root (e.g. the merkle tree itself).
event RootProposed(bytes32 indexed newRoot, bytes32 indexed newIpfsHash);
event PendingRootSet(address indexed caller, bytes32 indexed newRoot, bytes32 indexed newIpfsHash);

/// @notice Emitted when the pending root is revoked by the owner or an updater.
event PendingRootRevoked(address indexed caller);

/// @notice Emitted when a merkle tree distribution timelock is set.
/// @param newTimelock The new merkle timelock.
Expand All @@ -25,9 +29,6 @@ library EventsLib {
/// @param active The merkle tree updater's active state.
event RootUpdaterSet(address indexed rootUpdater, bool active);

/// @notice Emitted when a merkle pending root is revoked.
event RootRevoked();

/// @notice Emitted when rewards are claimed.
/// @param account The address for which rewards are claimd rewards for.
/// @param reward The address of the reward token.
Expand Down
52 changes: 33 additions & 19 deletions test/UniversalRewardsDistributorTest.sol
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ contract UniversalRewardsDistributorTest is Test {
function testSubmitRootWithTimelockAsOwner() public {
vm.prank(owner);
vm.expectEmit(address(distributionWithTimeLock));
emit EventsLib.RootProposed(DEFAULT_ROOT, DEFAULT_IPFS_HASH);
emit EventsLib.PendingRootSet(owner, DEFAULT_ROOT, DEFAULT_IPFS_HASH);
distributionWithTimeLock.submitRoot(DEFAULT_ROOT, DEFAULT_IPFS_HASH);

assert(distributionWithTimeLock.root() != DEFAULT_ROOT);
Expand All @@ -179,7 +179,7 @@ contract UniversalRewardsDistributorTest is Test {
function testSubmitRootWithTimelockAsUpdater() public {
vm.prank(updater);
vm.expectEmit(address(distributionWithTimeLock));
emit EventsLib.RootProposed(DEFAULT_ROOT, DEFAULT_IPFS_HASH);
emit EventsLib.PendingRootSet(updater, DEFAULT_ROOT, DEFAULT_IPFS_HASH);
distributionWithTimeLock.submitRoot(DEFAULT_ROOT, DEFAULT_IPFS_HASH);

assert(distributionWithTimeLock.root() != DEFAULT_ROOT);
Expand Down Expand Up @@ -263,18 +263,18 @@ contract UniversalRewardsDistributorTest is Test {
assertEq(pendingRoot.validAt, 0);
}

function testSetRootShouldRemovePendingRoot(bytes32 newRoot, address randomCaller) public {
vm.assume(newRoot != DEFAULT_ROOT && randomCaller != owner);

vm.startPrank(owner);
function testSetRootShouldUpdateTheCurrentPendingRoot(bytes32 newRoot, bytes32 newIpfsHash) public {
vm.prank(updater);
distributionWithTimeLock.submitRoot(DEFAULT_ROOT, DEFAULT_IPFS_HASH);

assertEq(distributionWithTimeLock.pendingRoot().root, DEFAULT_ROOT);
vm.prank(owner);
distributionWithTimeLock.setRoot(newRoot, newIpfsHash);

distributionWithTimeLock.setRoot(newRoot, DEFAULT_IPFS_HASH);
vm.stopPrank();
PendingRoot memory pendingRoot = distributionWithTimeLock.pendingRoot();

assertEq(distributionWithTimeLock.pendingRoot().root, bytes32(0));
assertEq(pendingRoot.validAt, 0);
assertEq(pendingRoot.root, 0);
assertEq(pendingRoot.ipfsHash, 0);
}

function testSetTimelockShouldChangeTheDistributionTimelock(uint256 newTimelock) public {
Expand Down Expand Up @@ -374,35 +374,49 @@ contract UniversalRewardsDistributorTest is Test {
distributionWithoutTimeLock.setRootUpdater(_addrFromHashedString("RANDOM_UPDATER"), active);
}

function testRevokeRootShouldRevokeWhenCalledWithOwner() public {
function testRevokePendingRootShouldRevokeWhenCalledWithOwner() public {
vm.prank(owner);
distributionWithTimeLock.submitRoot(DEFAULT_ROOT, DEFAULT_IPFS_HASH);

vm.prank(owner);
vm.expectEmit(address(distributionWithTimeLock));
emit EventsLib.RootRevoked();
distributionWithTimeLock.revokeRoot();
emit EventsLib.PendingRootRevoked(owner);
distributionWithTimeLock.revokePendingRoot();

PendingRoot memory pendingRoot = distributionWithTimeLock.pendingRoot();
assertEq(pendingRoot.root, bytes32(0));
assertEq(pendingRoot.validAt, 0);
}

function testRevokeRootShouldRevertIfNotOwner(bytes32 proposedRoot, address caller) public {
vm.assume(proposedRoot != bytes32(0) && caller != owner);
function testRevokePendingRootShouldRevokeWhenCalledWithUpdater() public {
vm.prank(owner);
distributionWithTimeLock.submitRoot(DEFAULT_ROOT, DEFAULT_IPFS_HASH);

vm.prank(updater);
vm.expectEmit(address(distributionWithTimeLock));
emit EventsLib.PendingRootRevoked(updater);
distributionWithTimeLock.revokePendingRoot();

PendingRoot memory pendingRoot = distributionWithTimeLock.pendingRoot();
assertEq(pendingRoot.root, bytes32(0));
assertEq(pendingRoot.validAt, 0);
}

function testRevokePendingRootShouldRevertIfNotUpdater(bytes32 proposedRoot, address caller) public {
vm.assume(!distributionWithTimeLock.isUpdater(caller) && caller != owner);

vm.prank(owner);
distributionWithTimeLock.submitRoot(proposedRoot, DEFAULT_IPFS_HASH);

vm.prank(caller);
vm.expectRevert(bytes(ErrorsLib.NOT_OWNER));
distributionWithTimeLock.revokeRoot();
vm.expectRevert(bytes(ErrorsLib.NOT_UPDATER_ROLE));
distributionWithTimeLock.revokePendingRoot();
}

function testRevokeRootShouldRevertWhenNoPendingRoot() public {
function testRevokePendingRootShouldRevertWhenNoPendingRoot() public {
vm.prank(owner);
vm.expectRevert(bytes(ErrorsLib.NO_PENDING_ROOT));
distributionWithTimeLock.revokeRoot();
distributionWithTimeLock.revokePendingRoot();
}

function testSetOwner(address newOwner) public {
Expand Down

0 comments on commit 378e73c

Please sign in to comment.