diff --git a/contracts/CommonErrors.sol b/contracts/CommonErrors.sol
index cedccd39..88419e0a 100644
--- a/contracts/CommonErrors.sol
+++ b/contracts/CommonErrors.sol
@@ -27,3 +27,4 @@ error GroupIndexIsInvalid(uint256 index);
error IsNotContract(address account);
error NotEnoughFunds();
error RoleRequired(bytes32 role);
+error TokensTransferFailure();
diff --git a/contracts/delegation/Distributor.sol b/contracts/delegation/Distributor.sol
index 8b3ab460..af21d9ae 100644
--- a/contracts/delegation/Distributor.sol
+++ b/contracts/delegation/Distributor.sol
@@ -19,24 +19,21 @@
along with SKALE Manager. If not, see .
*/
-pragma solidity 0.8.17;
-
-import {IERC1820Registry} from "@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol";
-import {IERC777Recipient} from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
-import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
-
-import {IDistributor} from "@skalenetwork/skale-manager-interfaces/delegation/IDistributor.sol";
-import {
- IValidatorService
-} from "@skalenetwork/skale-manager-interfaces/delegation/IValidatorService.sol";
-import {
- IDelegationController
-} from "@skalenetwork/skale-manager-interfaces/delegation/IDelegationController.sol";
-import {ITimeHelpers} from "@skalenetwork/skale-manager-interfaces/delegation/ITimeHelpers.sol";
-
-import {Permissions} from "../Permissions.sol";
-import {ConstantsHolder} from "../ConstantsHolder.sol";
-import {MathUtils} from "../utils/MathUtils.sol";
+pragma solidity 0.8.26;
+
+import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
+import { IERC777Recipient } from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
+import { IERC1820Registry } from "@openzeppelin/contracts/utils/introspection/IERC1820Registry.sol";
+import { IDelegationController }
+from "@skalenetwork/skale-manager-interfaces/delegation/IDelegationController.sol";
+import { IDistributor } from "@skalenetwork/skale-manager-interfaces/delegation/IDistributor.sol";
+import { ITimeHelpers } from "@skalenetwork/skale-manager-interfaces/delegation/ITimeHelpers.sol";
+import { IValidatorService }
+from "@skalenetwork/skale-manager-interfaces/delegation/IValidatorService.sol";
+import { IConstantsHolder } from "@skalenetwork/skale-manager-interfaces/IConstantsHolder.sol";
+import { TokensTransferFailure } from "./../CommonErrors.sol";
+import { Permissions } from "./../Permissions.sol";
+import { MathUtils } from "./../utils/MathUtils.sol";
/**
* @title Distributor
@@ -48,15 +45,17 @@ contract Distributor is Permissions, IERC777Recipient, IDistributor {
IERC1820Registry private _erc1820;
- // validatorId => month => token
- mapping(uint256 => mapping(uint256 => uint256)) private _bountyPaid;
- // validatorId => month => token
- mapping(uint256 => mapping(uint256 => uint256)) private _feePaid;
- // holder => validatorId => month
- mapping(address => mapping(uint256 => uint256))
- private _firstUnwithdrawnMonth;
- // validatorId => month
- mapping(uint256 => uint256) private _firstUnwithdrawnMonthForValidator;
+ mapping(uint256 validatorId => mapping(uint256 month => uint256 amount)) private _bountyPaid;
+ mapping(uint256 validatorId => mapping(uint256 month => uint256 amount)) private _feePaid;
+ mapping(
+ address holder => mapping(uint256 validatorId => uint256 month)
+ ) private _firstUnwithdrawnMonth;
+ mapping(uint256 validatorId => uint256 month) private _firstUnwithdrawnMonthForValidator;
+
+ error BountyIsLocked();
+ error DataLengthIsIncorrect();
+ error FeeIsLocked();
+ error ReceiverIsIncorrect();
function initialize(address contractsAddress) public override initializer {
Permissions.initialize(contractsAddress);
@@ -91,18 +90,16 @@ contract Distributor is Permissions, IERC777Recipient, IDistributor {
ITimeHelpers timeHelpers = ITimeHelpers(
contractManager.getContract("TimeHelpers")
);
- ConstantsHolder constantsHolder = ConstantsHolder(
+ IConstantsHolder constantsHolder = IConstantsHolder(
contractManager.getContract("ConstantsHolder")
);
- require(
- block.timestamp >=
- timeHelpers.addMonths(
+ if (block.timestamp < timeHelpers.addMonths(
constantsHolder.launchTimestamp(),
constantsHolder.BOUNTY_LOCKUP_MONTHS()
- ),
- "Bounty is locked"
- );
+ )) {
+ revert BountyIsLocked();
+ }
uint256 bounty;
uint256 endMonth;
@@ -114,7 +111,9 @@ contract Distributor is Permissions, IERC777Recipient, IDistributor {
_firstUnwithdrawnMonth[msg.sender][validatorId] = endMonth;
IERC20 skaleToken = IERC20(contractManager.getContract("SkaleToken"));
- require(skaleToken.transfer(to, bounty), "Failed to transfer tokens");
+ if(!skaleToken.transfer(to, bounty)) {
+ revert TokensTransferFailure();
+ }
emit WithdrawBounty(msg.sender, validatorId, to, bounty);
}
@@ -137,18 +136,17 @@ contract Distributor is Permissions, IERC777Recipient, IDistributor {
ITimeHelpers timeHelpers = ITimeHelpers(
contractManager.getContract("TimeHelpers")
);
- ConstantsHolder constantsHolder = ConstantsHolder(
+ IConstantsHolder constantsHolder = IConstantsHolder(
contractManager.getContract("ConstantsHolder")
);
- require(
- block.timestamp >=
- timeHelpers.addMonths(
- constantsHolder.launchTimestamp(),
- constantsHolder.BOUNTY_LOCKUP_MONTHS()
- ),
- "Fee is locked"
- );
+ if (block.timestamp < timeHelpers.addMonths(
+ constantsHolder.launchTimestamp(),
+ constantsHolder.BOUNTY_LOCKUP_MONTHS()
+ )) {
+ revert FeeIsLocked();
+ }
+
// check Validator Exist inside getValidatorId
uint256 validatorId = validatorService.getValidatorId(msg.sender);
@@ -158,7 +156,9 @@ contract Distributor is Permissions, IERC777Recipient, IDistributor {
_firstUnwithdrawnMonthForValidator[validatorId] = endMonth;
- require(skaleToken.transfer(to, fee), "Failed to transfer tokens");
+ if(!skaleToken.transfer(to, fee)) {
+ revert TokensTransferFailure();
+ }
emit WithdrawFee(validatorId, to, fee);
}
@@ -171,8 +171,12 @@ contract Distributor is Permissions, IERC777Recipient, IDistributor {
bytes calldata userData,
bytes calldata
) external override allow("SkaleToken") {
- require(to == address(this), "Receiver is incorrect");
- require(userData.length == 32, "Data length is incorrect");
+ if (to != address(this)) {
+ revert ReceiverIsIncorrect();
+ }
+ if (userData.length != 32) {
+ revert DataLengthIsIncorrect();
+ }
uint256 validatorId = abi.decode(userData, (uint256));
_distributeBounty(amount, validatorId);
}
diff --git a/contracts/test/SkaleTokenInternalTester.sol b/contracts/test/SkaleTokenInternalTester.sol
index 0cfa6c19..998edb47 100644
--- a/contracts/test/SkaleTokenInternalTester.sol
+++ b/contracts/test/SkaleTokenInternalTester.sol
@@ -21,6 +21,7 @@
pragma solidity 0.8.17;
+import { IERC777Recipient } from "@openzeppelin/contracts/token/ERC777/IERC777Recipient.sol";
import { SkaleToken } from "../SkaleToken.sol";
import { ISkaleTokenInterfaceTester } from "./interfaces/ISkaleTokenInterfaceTester.sol";
@@ -32,6 +33,30 @@ contract SkaleTokenInternalTester is SkaleToken, ISkaleTokenInterfaceTester {
// solhint-disable-next-line no-empty-blocks
{ }
+ function callTokensReceived(
+ address implementer,
+ address operator,
+ address from,
+ address to,
+ uint256 amount,
+ bytes memory userData,
+ bytes memory operatorData
+ )
+ external
+ override
+ {
+ if (implementer != address(0)) {
+ IERC777Recipient(implementer).tokensReceived({
+ operator: operator,
+ from: from,
+ to: to,
+ amount: amount,
+ userData: userData,
+ operatorData: operatorData
+ });
+ }
+ }
+
function getMsgData() external view override returns (bytes memory msgData) {
return _msgData();
}
diff --git a/contracts/test/interfaces/ISkaleTokenInterfaceTester.sol b/contracts/test/interfaces/ISkaleTokenInterfaceTester.sol
index 5bdc2150..df5104b6 100644
--- a/contracts/test/interfaces/ISkaleTokenInterfaceTester.sol
+++ b/contracts/test/interfaces/ISkaleTokenInterfaceTester.sol
@@ -23,5 +23,15 @@ pragma solidity 0.8.17;
interface ISkaleTokenInterfaceTester {
+ function callTokensReceived(
+ address implementer,
+ address operator,
+ address from,
+ address to,
+ uint256 amount,
+ bytes memory userData,
+ bytes memory operatorData
+ )
+ external;
function getMsgData() external view returns (bytes memory msgData);
}
diff --git a/contracts/utils/MathUtils.sol b/contracts/utils/MathUtils.sol
index 08225e8a..fe07bf61 100644
--- a/contracts/utils/MathUtils.sol
+++ b/contracts/utils/MathUtils.sol
@@ -19,7 +19,7 @@
along with SKALE Manager. If not, see .
*/
-pragma solidity 0.8.17;
+pragma solidity ^0.8.17;
library MathUtils {
diff --git a/package.json b/package.json
index 0d3fa039..6287ae9d 100644
--- a/package.json
+++ b/package.json
@@ -48,7 +48,7 @@
"@skalenetwork/ima-interfaces": "2.0.0-develop.67",
"@skalenetwork/marionette-interfaces": "^0.0.0-main.6",
"@skalenetwork/paymaster-interfaces": "^1.0.0-main.10",
- "@skalenetwork/skale-manager-interfaces": "3.2.0-develop.0",
+ "@skalenetwork/skale-manager-interfaces": "3.2.0-develop.1",
"@skalenetwork/upgrade-tools": "^3.0.0-linter.42",
"@typechain/hardhat": "^9.1.0",
"dotenv": "^16.3.1",
diff --git a/test/delegation/Delegation.ts b/test/delegation/Delegation.ts
index ea207a74..2018ab1c 100644
--- a/test/delegation/Delegation.ts
+++ b/test/delegation/Delegation.ts
@@ -234,6 +234,30 @@ describe("Delegation", () => {
.should.be.eventually.rejectedWith("Limit of validators is reached");
});
+ it("should revert on incorrect transfers", async () => {
+ const skaleTokenInternalTesterFactory = await ethers.getContractFactory("SkaleTokenInternalTester");
+ const skaleTokenInternalTester = await skaleTokenInternalTesterFactory.deploy(contractManager, []);
+ await contractManager.setContractsAddress("SkaleToken", skaleTokenInternalTester);
+ await skaleTokenInternalTester.callTokensReceived(
+ distributor,
+ ethers.ZeroAddress,
+ holder1,
+ holder2, // Receiver always needs to be the Distributor
+ 5,
+ "0x",
+ "0x"
+ ).should.be.revertedWithCustomError(distributor, "ReceiverIsIncorrect");
+ await skaleTokenInternalTester.callTokensReceived(
+ distributor,
+ ethers.ZeroAddress,
+ holder1,
+ distributor,
+ 5,
+ "0x", // Data length always must be 32 bytes
+ "0x"
+ ).should.be.revertedWithCustomError(distributor, "DataLengthIsIncorrect");
+ })
+
describe("when holders have tokens and validator is registered", () => {
let validatorId: bigint;
fastBeforeEach(async () => {
@@ -599,9 +623,9 @@ describe("Delegation", () => {
validatorId))[0].should.be.equal(31);
await distributor.connect(validator).withdrawFee(bountyAddress.address)
- .should.be.eventually.rejectedWith("Fee is locked");
+ .should.be.revertedWithCustomError(distributor, "FeeIsLocked");
await distributor.connect(holder1).withdrawBounty(validatorId, bountyAddress.address)
- .should.be.eventually.rejectedWith("Bounty is locked");
+ .should.be.revertedWithCustomError(distributor, "BountyIsLocked");
await nextMonth(contractManager, 3);
diff --git a/yarn.lock b/yarn.lock
index 8e1f9481..6db439d5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1741,10 +1741,10 @@
dependencies:
axios "^1.4.0"
-"@skalenetwork/skale-manager-interfaces@3.2.0-develop.0":
- version "3.2.0-develop.0"
- resolved "https://registry.yarnpkg.com/@skalenetwork/skale-manager-interfaces/-/skale-manager-interfaces-3.2.0-develop.0.tgz#01e64eeabf2b14b320074eb588b557bbd33bab39"
- integrity sha512-f++aLtSwjLJgAb3dHAm/SMHvx2a0L3uWwF7aam1wCtB6YZrFLR3flzlH3Ms3JlLZtfl5iE7VBmsuidhAqpbfpw==
+"@skalenetwork/skale-manager-interfaces@3.2.0-develop.1":
+ version "3.2.0-develop.1"
+ resolved "https://registry.yarnpkg.com/@skalenetwork/skale-manager-interfaces/-/skale-manager-interfaces-3.2.0-develop.1.tgz#ec4ad8f2b4d12a5ff8d03763674a214be61c773d"
+ integrity sha512-ACugcMzRFa7nbHcJ8mZE/J60M8GW9gJKsqmQYEYoElzZBMTCstEeWy1ff6Vj7kcv+xESHPPhTzJ34ieMCL3pFg==
"@skalenetwork/skale-manager-interfaces@^3.2.0-develop.0":
version "3.2.0-paymaster.2"