Files
staking-reward-streamer/test/Karma.t.sol
r4bbit bf9299ab5c fix(Karma): prevent overflow errors when issuing rewards
As described in #206 it's theoretically possible to cause overflows when
either Karma is issued via `mint()` or `setReward()` because both
functions don't take the supply of the other "domain" into account.

This commit fixes this by performing an arithmetic operation on
`super.totalSupply()`, `totalDistributorAllocation` and the newly issued
`amount`.

If there's an overflow, the code will cause an overflow and reverts.
This ensure overflows can't happen *after* rewards have been issued.

Closes #206
2025-05-01 17:10:23 +02:00

195 lines
6.1 KiB
Solidity

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import { Test } from "forge-std/Test.sol";
import { DeployKarmaScript } from "../script/DeployKarma.s.sol";
import { DeploymentConfig } from "../script/DeploymentConfig.s.sol";
import { Karma } from "../src/Karma.sol";
import { KarmaDistributorMock } from "./mocks/KarmaDistributorMock.sol";
contract KarmaTest is Test {
Karma public karma;
address public owner;
address public alice = makeAddr("alice");
address public bob = makeAddr("bob");
KarmaDistributorMock public distributor1;
KarmaDistributorMock public distributor2;
function setUp() public virtual {
DeployKarmaScript karmaDeployment = new DeployKarmaScript();
(Karma _karma, DeploymentConfig deploymentConfig) = karmaDeployment.run();
karma = _karma;
(address deployer,) = deploymentConfig.activeNetworkConfig();
owner = deployer;
distributor1 = new KarmaDistributorMock();
distributor2 = new KarmaDistributorMock();
vm.startBroadcast(owner);
karma.addRewardDistributor(address(distributor1));
karma.addRewardDistributor(address(distributor2));
vm.stopBroadcast();
}
function testAddKarmaDistributorOnlyOwner() public {
KarmaDistributorMock distributor3 = new KarmaDistributorMock();
vm.prank(alice);
vm.expectRevert("Ownable: caller is not the owner");
karma.addRewardDistributor(address(distributor3));
vm.prank(owner);
karma.addRewardDistributor(address(distributor3));
address[] memory distributors = karma.getRewardDistributors();
assertEq(distributors.length, 3);
assertEq(distributors[0], address(distributor1));
assertEq(distributors[1], address(distributor2));
assertEq(distributors[2], address(distributor3));
}
function testRemoveKarmaDistributorOnlyOwner() public {
vm.prank(alice);
vm.expectRevert("Ownable: caller is not the owner");
karma.removeRewardDistributor(address(distributor1));
vm.prank(owner);
karma.removeRewardDistributor(address(distributor1));
address[] memory distributors = karma.getRewardDistributors();
assertEq(distributors.length, 1);
assertEq(distributors[0], address(distributor2));
}
function testRemoveUnknownKarmaDistributor() public {
vm.prank(owner);
vm.expectRevert(Karma.Karma__UnknownDistributor.selector);
karma.removeRewardDistributor(address(1));
}
function testTotalSupply() public {
vm.startBroadcast(owner);
karma.setReward(address(distributor1), 1000 ether, 1000);
karma.setReward(address(distributor2), 2000 ether, 2000);
vm.stopBroadcast();
distributor1.setTotalKarmaShares(1000 ether);
distributor2.setTotalKarmaShares(2000 ether);
vm.prank(owner);
karma.mint(owner, 500 ether);
uint256 totalSupply = karma.totalSupply();
assertEq(totalSupply, 3500 ether);
}
function testBalanceOfWithNoSystemTotalKarma() public view {
uint256 aliceBalance = karma.balanceOf(alice);
assertEq(aliceBalance, 0);
uint256 bobBalance = karma.balanceOf(bob);
assertEq(bobBalance, 0);
}
function testBalanceOf() public {
vm.startBroadcast(owner);
karma.setReward(address(distributor1), 1000 ether, 1000);
karma.setReward(address(distributor2), 2000 ether, 2000);
vm.stopBroadcast();
distributor1.setTotalKarmaShares(1000 ether);
distributor2.setTotalKarmaShares(2000 ether);
distributor1.setUserKarmaShare(alice, 1000e18);
distributor2.setUserKarmaShare(alice, 2000e18);
vm.prank(owner);
karma.mint(alice, 500e18);
uint256 expectedBalance = 3500e18;
uint256 balance = karma.balanceOf(alice);
assertEq(balance, expectedBalance);
}
function testMintOnlyOwner() public {
vm.startBroadcast(owner);
karma.setReward(address(distributor1), 1000 ether, 1000);
karma.setReward(address(distributor2), 2000 ether, 2000);
vm.stopBroadcast();
distributor1.setTotalKarmaShares(1000 ether);
distributor2.setTotalKarmaShares(2000 ether);
assertEq(karma.totalSupply(), 3000 ether);
vm.prank(alice);
vm.expectRevert("Ownable: caller is not the owner");
karma.mint(alice, 1000e18);
vm.prank(owner);
karma.mint(alice, 1000e18);
assertEq(karma.totalSupply(), 4000e18);
}
function testTransfersNotAllowed() public {
vm.expectRevert(Karma.Karma__TransfersNotAllowed.selector);
karma.transfer(alice, 100e18);
vm.expectRevert(Karma.Karma__TransfersNotAllowed.selector);
karma.approve(alice, 100e18);
vm.expectRevert(Karma.Karma__TransfersNotAllowed.selector);
karma.transferFrom(alice, bob, 100e18);
uint256 allowance = karma.allowance(alice, bob);
assertEq(allowance, 0);
}
}
contract KarmaOwnershipTest is KarmaTest {
function setUp() public override {
super.setUp();
}
function testInitialOwner() public view {
assertEq(karma.owner(), owner);
}
function testOwnershipTransfer() public {
vm.prank(owner);
karma.transferOwnership(alice);
assertEq(karma.owner(), owner);
vm.prank(alice);
karma.acceptOwnership();
assertEq(karma.owner(), alice);
}
}
contract OverflowTest is KarmaTest {
function setUp() public override {
super.setUp();
}
function test_RevertWhen_MintingCausesOverflow() public {
vm.startBroadcast(owner);
karma.setReward(address(distributor1), type(uint256).max, 1000);
vm.stopBroadcast();
vm.prank(owner);
vm.expectRevert();
karma.mint(owner, 1e18);
}
function test_RevertWhen_SettingRewardCausesOverflow() public {
vm.prank(owner);
karma.mint(owner, type(uint256).max);
vm.prank(owner);
vm.expectRevert();
karma.setReward(address(distributor1), 1e18, 1000);
}
}