diff --git a/solidity/src/FeesManager.sol b/solidity/src/FeesManager.sol index dca790f8..60a2cd12 100644 --- a/solidity/src/FeesManager.sol +++ b/solidity/src/FeesManager.sol @@ -13,10 +13,6 @@ contract FeesManager is IFeesManager, Ownable { using SafeERC20 for IERC20; uint16 public currentEpoch; - bytes32 public WITHDRAW_ROLE; - bytes32 public UPGRADE_ROLE; - bytes32 public SET_FEE_ROLE; - mapping(uint16 => mapping(address => uint256)) public depositedAmountByEpoch; mapping(uint16 => mapping(address => mapping(address => uint256))) @@ -26,6 +22,7 @@ contract FeesManager is IFeesManager, Ownable { mapping(address => Fee) public feeInfoByAsset; event FeeUpdated(address token, uint256 minFee, uint16 basisPoints); + event NewEpochStarted(uint16 epoch); error InvalidToken(); error InvalidFromAddress(); @@ -82,11 +79,16 @@ contract FeesManager is IFeesManager, Ownable { address[] calldata nodes, uint256[] calldata amounts ) external onlyOwner { + if (nodes.length != amounts.length) + revert DifferentLength(nodes.length, amounts.length); + currentEpoch += 1; for (uint i = 0; i < nodes.length; i++) { stakedAmountByEpoch[currentEpoch][nodes[i]] = amounts[i]; totalStakedAmountByEpoch[currentEpoch] += amounts[i]; } + + emit NewEpochStarted(currentEpoch); } /// @inheritdoc IFeesManager diff --git a/solidity/src/xerc20/XERC20.sol b/solidity/src/xerc20/XERC20.sol index a276996a..79fee332 100644 --- a/solidity/src/xerc20/XERC20.sol +++ b/solidity/src/xerc20/XERC20.sol @@ -58,7 +58,7 @@ contract XERC20 is ERC20, Ownable, IXERC20, ERC20Permit { feesManager = newAddress; } else if (msg.sender != feesManager) revert OnlyFeesManager(); - if (newAddress.code.length == 0) revert NotAContract(feesManager); + if (newAddress.code.length == 0) revert NotAContract(newAddress); feesManager = newAddress; diff --git a/solidity/test/forge/FeesManager.t.sol b/solidity/test/forge/FeesManager.t.sol index 13d53375..92922a13 100644 --- a/solidity/test/forge/FeesManager.t.sol +++ b/solidity/test/forge/FeesManager.t.sol @@ -77,6 +77,37 @@ contract FeesManagerTest is Test, Helper { vm.stopPrank(); } + function test_constructor_nodesAndStakedAmountsSetCorrectly() public view { + uint16 epoch = 0; + for (uint i = 0; i < nodes.length; i++) { + assertEq( + feesManager.stakedAmountByEpoch(epoch, nodes[i]), + stakedAmounts[i] + ); + } + + assertEq(feesManager.totalStakedAmountByEpoch(epoch), totalStaked); + } + + function test_constructor_RevertWhen_nodesAndAmountsHaveDifferentLength() + public + { + uint16 epoch = 0; + uint256 lenX = 2; + uint256 lenY = 1; + address[] memory x = new address[](lenX); + uint256[] memory y = new uint256[](lenY); + vm.prank(owner); + vm.expectRevert( + abi.encodeWithSelector( + FeesManager.DifferentLength.selector, + lenX, + lenY + ) + ); + new FeesManager(epoch, x, y); + } + function test_setFees_EmitFeesUpdatedEvent() public { uint256 minAmount = 0; uint16 basisPoints = 2000; @@ -181,6 +212,19 @@ contract FeesManagerTest is Test, Helper { assertEq(userBalancePre - userBalancePost, fees); assertEq(xerc20.balanceOf(address(feesManager)), fees); assertEq(xerc20.balanceOf(address(feesManager)), fees); + assertEq( + feesManager.depositedAmountByEpoch(epoch, address(xerc20)), + fees + ); + + vm.startPrank(user); + xerc20.approve(address(feesManager), fees); + feesManager.depositFee(address(xerc20), fees); + + assertEq( + feesManager.depositedAmountByEpoch(epoch, address(xerc20)), + fees * 2 + ); } function testFuzz_calculateFee_returnTheCorrectFees( @@ -279,4 +323,128 @@ contract FeesManagerTest is Test, Helper { feesManager.claimFeeByEpoch(address(xerc20), epoch); vm.stopPrank(); } + + function test_registerAndAdvanceEpoch_RevertWhen_callerIsNotTheOwner() + public + { + vm.prank(evil); + _expectOwnableUnauthorizedAccountRevert(evil); + feesManager.registerAndAdvanceEpoch(nodes, stakedAmounts); + } + + function test_registerAndAdvanceEpoch_RevertWhen_nodesAndAmountsHaveDifferentLength() + public + { + uint256 lenX = 2; + uint256 lenY = 1; + address[] memory x = new address[](lenX); + uint256[] memory y = new uint256[](lenY); + vm.prank(owner); + vm.expectRevert( + abi.encodeWithSelector( + FeesManager.DifferentLength.selector, + lenX, + lenY + ) + ); + + feesManager.registerAndAdvanceEpoch(x, y); + } + + function test_registerAndAdvanceEpoch_EmitNewEpochStartedEvent() public { + address[] memory newNodes = new address[](3); + uint256[] memory newStakedAmounts = new uint256[](3); + + newNodes[0] = node2; + newNodes[1] = node4; + newNodes[2] = node5; + newStakedAmounts[0] = stakedAmount2; + newStakedAmounts[1] = stakedAmount4; + newStakedAmounts[2] = stakedAmount5; + + address[] memory nodesWithoutStaking = new address[](3); + nodesWithoutStaking[0] = node0; + nodesWithoutStaking[1] = node1; + nodesWithoutStaking[2] = node3; + + uint256 newTotalStakedAmount = stakedAmount2 + + stakedAmount4 + + stakedAmount5; + uint16 expectedEpoch = feesManager.currentEpoch() + 1; + + vm.prank(owner); + vm.expectEmit(address(feesManager)); + emit FeesManager.NewEpochStarted(expectedEpoch); + feesManager.registerAndAdvanceEpoch(newNodes, newStakedAmounts); + + assertEq(feesManager.currentEpoch(), expectedEpoch); + + for (uint i = 0; i < newNodes.length; i++) { + assertEq( + feesManager.stakedAmountByEpoch(expectedEpoch, newNodes[i]), + newStakedAmounts[i] + ); + } + + for (uint i = 0; i < nodesWithoutStaking.length; i++) { + assertEq( + feesManager.stakedAmountByEpoch( + expectedEpoch, + nodesWithoutStaking[i] + ), + 0 + ); + } + + assertEq( + feesManager.totalStakedAmountByEpoch(expectedEpoch), + newTotalStakedAmount + ); + } + + function test_setFeesManagerForXERC20_onlyFeesManagerCanChangeTheAddress() + public + { + vm.expectEmit(address(xerc20)); + emit XERC20.FeesManagerChanged(address(feesManager)); + xerc20.setFeesManager(address(feesManager)); + + vm.startPrank(owner); + vm.expectRevert(XERC20.OnlyFeesManager.selector); + xerc20.setFeesManager(address(0)); + + address notAContract = vm.addr(2222); + vm.expectRevert( + abi.encodeWithSelector(XERC20.NotAContract.selector, notAContract) + ); + feesManager.setFeesManagerForXERC20(address(xerc20), notAContract); + + uint16 epoch = 0; + FeesManager newFeesManager = new FeesManager( + epoch, + nodes, + stakedAmounts + ); + vm.expectEmit(address(xerc20)); + emit XERC20.FeesManagerChanged(address(newFeesManager)); + feesManager.setFeesManagerForXERC20( + address(xerc20), + address(newFeesManager) + ); + + // Try to reset the original fees manager + vm.expectRevert(XERC20.OnlyFeesManager.selector); + feesManager.setFeesManagerForXERC20( + address(xerc20), + address(feesManager) + ); + } + + function test_setFeesManagerForXERC20_RevertWhen_callerIsNotOwner() public { + xerc20.setFeesManager(address(feesManager)); + + vm.prank(evil); + _expectOwnableUnauthorizedAccountRevert(evil); + feesManager.setFeesManagerForXERC20(address(xerc20), address(0)); + } }