Skip to content

Contracts: use SafeSend for ETH transfers #2041

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 1, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion contracts/deploy/00-home-chain-arbitration-neo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
const disputeKit = await deployUpgradable(deployments, "DisputeKitClassicNeo", {
from: deployer,
contract: "DisputeKitClassic",
args: [deployer, ZeroAddress],
args: [deployer, ZeroAddress, weth.target],
log: true,
});

Expand Down Expand Up @@ -81,6 +81,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
ethers.toBeHex(5), // Extra data for sortition module will return the default value of K
sortitionModule.address,
nft.target,
weth.target,
],
log: true,
}); // nonce+2 (implementation), nonce+3 (proxy)
Expand Down
2 changes: 1 addition & 1 deletion contracts/deploy/00-home-chain-arbitration-university.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
const disputeKit = await deployUpgradable(deployments, "DisputeKitClassicUniversity", {
from: deployer,
contract: "DisputeKitClassic",
args: [deployer, ZeroAddress],
args: [deployer, ZeroAddress, weth.target],
log: true,
});

Expand Down
9 changes: 5 additions & 4 deletions contracts/deploy/00-home-chain-arbitration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)

const disputeKit = await deployUpgradable(deployments, "DisputeKitClassic", {
from: deployer,
args: [deployer, ZeroAddress],
args: [deployer, ZeroAddress, weth.target],
log: true,
});

Expand Down Expand Up @@ -74,6 +74,7 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
[0, 0, 0, 10], // evidencePeriod, commitPeriod, votePeriod, appealPeriod
ethers.toBeHex(5), // Extra data for sortition module will return the default value of K
sortitionModule.address,
weth.target,
],
log: true,
}); // nonce+2 (implementation), nonce+3 (proxy)
Expand Down Expand Up @@ -105,23 +106,23 @@ const deployArbitration: DeployFunction = async (hre: HardhatRuntimeEnvironment)
// Extra dispute kits
const disputeKitShutter = await deployUpgradable(deployments, "DisputeKitShutter", {
from: deployer,
args: [deployer, core.target],
args: [deployer, core.target, weth.target],
log: true,
});
await core.addNewDisputeKit(disputeKitShutter.address);
await core.enableDisputeKits(Courts.GENERAL, [2], true); // enable disputeKitShutter on the General Court

const disputeKitGated = await deployUpgradable(deployments, "DisputeKitGated", {
from: deployer,
args: [deployer, core.target],
args: [deployer, core.target, weth.target],
log: true,
});
await core.addNewDisputeKit(disputeKitGated.address);
await core.enableDisputeKits(Courts.GENERAL, [3], true); // enable disputeKitGated on the General Court

const disputeKitGatedShutter = await deployUpgradable(deployments, "DisputeKitGatedShutter", {
from: deployer,
args: [deployer, core.target],
args: [deployer, core.target, weth.target],
log: true,
});
await core.addNewDisputeKit(disputeKitGatedShutter.address);
Expand Down
7 changes: 5 additions & 2 deletions contracts/src/arbitration/KlerosCore.sol
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ contract KlerosCore is KlerosCoreBase {
/// @param _timesPerPeriod The `timesPerPeriod` property value of the general court.
/// @param _sortitionExtraData The extra data for sortition module.
/// @param _sortitionModuleAddress The sortition module responsible for sortition of the jurors.
/// @param _wNative The wrapped native token address, typically wETH.
function initialize(
address _governor,
address _guardian,
Expand All @@ -40,7 +41,8 @@ contract KlerosCore is KlerosCoreBase {
uint256[4] memory _courtParameters,
uint256[4] memory _timesPerPeriod,
bytes memory _sortitionExtraData,
ISortitionModule _sortitionModuleAddress
ISortitionModule _sortitionModuleAddress,
address _wNative
) external reinitializer(1) {
__KlerosCoreBase_initialize(
_governor,
Expand All @@ -52,7 +54,8 @@ contract KlerosCore is KlerosCoreBase {
_courtParameters,
_timesPerPeriod,
_sortitionExtraData,
_sortitionModuleAddress
_sortitionModuleAddress,
_wNative
);
}

Expand Down
13 changes: 9 additions & 4 deletions contracts/src/arbitration/KlerosCoreBase.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@ import {ISortitionModule} from "./interfaces/ISortitionModule.sol";
import {Initializable} from "../proxy/Initializable.sol";
import {UUPSProxiable} from "../proxy/UUPSProxiable.sol";
import {SafeERC20, IERC20} from "../libraries/SafeERC20.sol";
import {SafeSend} from "../libraries/SafeSend.sol";
import "../libraries/Constants.sol";

/// @title KlerosCoreBase
/// Core arbitrator contract for Kleros v2.
/// Note that this contract trusts the PNK token, the dispute kit and the sortition module contracts.
abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable {
using SafeERC20 for IERC20;
using SafeSend for address payable;

// ************************************* //
// * Enums / Structs * //
Expand Down Expand Up @@ -99,6 +101,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
Dispute[] public disputes; // The disputes.
mapping(IERC20 => CurrencyRate) public currencyRates; // The price of each token in ETH.
bool public paused; // Whether asset withdrawals are paused.
address public wNative; // The wrapped native token for safeSend().

// ************************************* //
// * Events * //
Expand Down Expand Up @@ -199,13 +202,15 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
uint256[4] memory _courtParameters,
uint256[4] memory _timesPerPeriod,
bytes memory _sortitionExtraData,
ISortitionModule _sortitionModuleAddress
ISortitionModule _sortitionModuleAddress,
address _wNative
) internal onlyInitializing {
governor = _governor;
guardian = _guardian;
pinakion = _pinakion;
jurorProsecutionModule = _jurorProsecutionModule;
sortitionModule = _sortitionModuleAddress;
wNative = _wNative;

// NULL_DISPUTE_KIT: an empty element at index 0 to indicate when a dispute kit is not supported.
disputeKits.push();
Expand Down Expand Up @@ -802,7 +807,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
// No one was coherent, send the rewards to the governor.
if (round.feeToken == NATIVE_CURRENCY) {
// The dispute fees were paid in ETH
payable(governor).send(round.totalFeesForJurors);
payable(governor).safeSend(round.totalFeesForJurors, wNative);
} else {
// The dispute fees were paid in ERC20
round.feeToken.safeTransfer(governor, round.totalFeesForJurors);
Expand Down Expand Up @@ -854,7 +859,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
pinakion.safeTransfer(account, pnkReward);
if (round.feeToken == NATIVE_CURRENCY) {
// The dispute fees were paid in ETH
payable(account).send(feeReward);
payable(account).safeSend(feeReward, wNative);
} else {
// The dispute fees were paid in ERC20
round.feeToken.safeTransfer(account, feeReward);
Expand All @@ -880,7 +885,7 @@ abstract contract KlerosCoreBase is IArbitratorV2, Initializable, UUPSProxiable
if (leftoverFeeReward != 0) {
if (round.feeToken == NATIVE_CURRENCY) {
// The dispute fees were paid in ETH
payable(governor).send(leftoverFeeReward);
payable(governor).safeSend(leftoverFeeReward, wNative);
} else {
// The dispute fees were paid in ERC20
round.feeToken.safeTransfer(governor, leftoverFeeReward);
Expand Down
7 changes: 5 additions & 2 deletions contracts/src/arbitration/KlerosCoreNeo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ contract KlerosCoreNeo is KlerosCoreBase {
/// @param _sortitionExtraData The extra data for sortition module.
/// @param _sortitionModuleAddress The sortition module responsible for sortition of the jurors.
/// @param _jurorNft NFT contract to vet the jurors.
/// @param _wNative The wrapped native token address, typically wETH.
function initialize(
address _governor,
address _guardian,
Expand All @@ -50,7 +51,8 @@ contract KlerosCoreNeo is KlerosCoreBase {
uint256[4] memory _timesPerPeriod,
bytes memory _sortitionExtraData,
ISortitionModule _sortitionModuleAddress,
IERC721 _jurorNft
IERC721 _jurorNft,
address _wNative
) external reinitializer(2) {
__KlerosCoreBase_initialize(
_governor,
Expand All @@ -62,7 +64,8 @@ contract KlerosCoreNeo is KlerosCoreBase {
_courtParameters,
_timesPerPeriod,
_sortitionExtraData,
_sortitionModuleAddress
_sortitionModuleAddress,
_wNative
);
jurorNft = _jurorNft;
}
Expand Down
15 changes: 11 additions & 4 deletions contracts/src/arbitration/KlerosGovernor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
pragma solidity ^0.8.24;

import {IArbitrableV2, IArbitratorV2} from "./interfaces/IArbitrableV2.sol";
import {SafeSend} from "../libraries/SafeSend.sol";
import "./interfaces/IDisputeTemplateRegistry.sol";

/// @title KlerosGovernor for V2. Note that appeal functionality and evidence submission will be handled by the court.
contract KlerosGovernor is IArbitrableV2 {
using SafeSend for address payable;

// ************************************* //
// * Enums / Structs * //
// ************************************* //
Expand Down Expand Up @@ -46,6 +49,7 @@ contract KlerosGovernor is IArbitrableV2 {

IArbitratorV2 public arbitrator; // Arbitrator contract.
bytes public arbitratorExtraData; // Extra data for arbitrator.
address public wNative; // The wrapped native token for safeSend().
IDisputeTemplateRegistry public templateRegistry; // The dispute template registry.
uint256 public templateId; // The current dispute template identifier.

Expand Down Expand Up @@ -111,6 +115,7 @@ contract KlerosGovernor is IArbitrableV2 {
/// @param _submissionTimeout Time in seconds allocated for submitting transaction list.
/// @param _executionTimeout Time in seconds after approval that allows to execute transactions of the approved list.
/// @param _withdrawTimeout Time in seconds after submission that allows to withdraw submitted list.
/// @param _wNative The wrapped native token address, typically wETH.
constructor(
IArbitratorV2 _arbitrator,
bytes memory _arbitratorExtraData,
Expand All @@ -119,10 +124,12 @@ contract KlerosGovernor is IArbitrableV2 {
uint256 _submissionBaseDeposit,
uint256 _submissionTimeout,
uint256 _executionTimeout,
uint256 _withdrawTimeout
uint256 _withdrawTimeout,
address _wNative
) {
arbitrator = _arbitrator;
arbitratorExtraData = _arbitratorExtraData;
wNative = _wNative;

lastApprovalTime = block.timestamp;
submissionBaseDeposit = _submissionBaseDeposit;
Expand Down Expand Up @@ -237,7 +244,7 @@ contract KlerosGovernor is IArbitrableV2 {
emit ListSubmitted(submissions.length - 1, msg.sender, sessions.length - 1, _description);

uint256 remainder = msg.value - submission.deposit;
if (remainder > 0) payable(msg.sender).send(remainder);
if (remainder > 0) payable(msg.sender).safeSend(remainder, wNative);

reservedETH += submission.deposit;
}
Expand Down Expand Up @@ -277,7 +284,7 @@ contract KlerosGovernor is IArbitrableV2 {
submission.approvalTime = block.timestamp;
uint256 sumDeposit = session.sumDeposit;
session.sumDeposit = 0;
submission.submitter.send(sumDeposit);
submission.submitter.safeSend(sumDeposit, wNative);
lastApprovalTime = block.timestamp;
session.status = Status.Resolved;
sessions.push();
Expand Down Expand Up @@ -311,7 +318,7 @@ contract KlerosGovernor is IArbitrableV2 {
Submission storage submission = submissions[session.submittedLists[_ruling - 1]];
submission.approved = true;
submission.approvalTime = block.timestamp;
submission.submitter.send(session.sumDeposit);
submission.submitter.safeSend(session.sumDeposit, wNative);
}
// If the ruling is "0" the reserved funds of this session become expendable.
reservedETH -= session.sumDeposit;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,9 @@ contract DisputeKitClassic is DisputeKitClassicBase {
/// @dev Initializer.
/// @param _governor The governor's address.
/// @param _core The KlerosCore arbitrator.
function initialize(address _governor, KlerosCore _core) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core);
/// @param _wNative The wrapped native token address, typically wETH.
function initialize(address _governor, KlerosCore _core, address _wNative) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core, _wNative);
}

function initialize7() external reinitializer(7) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ pragma solidity ^0.8.24;
import {KlerosCore, KlerosCoreBase, IDisputeKit, ISortitionModule} from "../KlerosCore.sol";
import {Initializable} from "../../proxy/Initializable.sol";
import {UUPSProxiable} from "../../proxy/UUPSProxiable.sol";
import {SafeSend} from "../../libraries/SafeSend.sol";

/// @title DisputeKitClassicBase
/// Abstract Dispute kit classic implementation of the Kleros v1 features including:
Expand All @@ -13,6 +14,8 @@ import {UUPSProxiable} from "../../proxy/UUPSProxiable.sol";
/// - an incentive system: equal split between coherent votes,
/// - an appeal system: fund 2 choices only, vote on any choice.
abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxiable {
using SafeSend for address payable;

// ************************************* //
// * Structs * //
// ************************************* //
Expand Down Expand Up @@ -64,6 +67,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
mapping(uint256 localDisputeID => mapping(uint256 localRoundID => mapping(address drawnAddress => bool)))
public alreadyDrawn; // True if the address has already been drawn, false by default. To be added to the Round struct when fully redeploying rather than upgrading.
mapping(uint256 coreDisputeID => bool) public coreDisputeIDToActive; // True if this dispute kit is active for this core dispute ID.
address public wNative; // The wrapped native token for safeSend().

// ************************************* //
// * Events * //
Expand Down Expand Up @@ -142,9 +146,15 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
/// @dev Initializer.
/// @param _governor The governor's address.
/// @param _core The KlerosCore arbitrator.
function __DisputeKitClassicBase_initialize(address _governor, KlerosCore _core) internal onlyInitializing {
/// @param _wNative The wrapped native token address, typically wETH.
function __DisputeKitClassicBase_initialize(
address _governor,
KlerosCore _core,
address _wNative
) internal onlyInitializing {
governor = _governor;
core = _core;
wNative = _wNative;
}

// ************************ //
Expand Down Expand Up @@ -409,7 +419,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
core.appeal{value: appealCost}(_coreDisputeID, dispute.numberOfChoices, dispute.extraData);
}

if (msg.value > contribution) payable(msg.sender).send(msg.value - contribution);
if (msg.value > contribution) payable(msg.sender).safeSend(msg.value - contribution, wNative);
}

/// @dev Allows those contributors who attempted to fund an appeal round to withdraw any reimbursable fees or rewards after the dispute gets resolved.
Expand Down Expand Up @@ -454,7 +464,7 @@ abstract contract DisputeKitClassicBase is IDisputeKit, Initializable, UUPSProxi
round.contributions[_beneficiary][_choice] = 0;

if (amount != 0) {
_beneficiary.send(amount); // Deliberate use of send to prevent reverting fallback. It's the user's responsibility to accept ETH.
_beneficiary.safeSend(amount, wNative);
emit Withdrawal(_coreDisputeID, _coreRoundID, _choice, _beneficiary, amount);
}
}
Expand Down
5 changes: 3 additions & 2 deletions contracts/src/arbitration/dispute-kits/DisputeKitGated.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,9 @@ contract DisputeKitGated is DisputeKitClassicBase {
/// @dev Initializer.
/// @param _governor The governor's address.
/// @param _core The KlerosCore arbitrator.
function initialize(address _governor, KlerosCore _core) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core);
/// @param _wNative The wrapped native token address, typically wETH.
function initialize(address _governor, KlerosCore _core, address _wNative) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core, _wNative);
}

function initialize7() external reinitializer(7) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,9 @@ contract DisputeKitGatedShutter is DisputeKitClassicBase {
/// @dev Initializer.
/// @param _governor The governor's address.
/// @param _core The KlerosCore arbitrator.
function initialize(address _governor, KlerosCore _core) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core);
/// @param _wNative The wrapped native token address, typically wETH.
function initialize(address _governor, KlerosCore _core, address _wNative) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core, _wNative);
}

function initialize7() external reinitializer(7) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,9 @@ contract DisputeKitShutter is DisputeKitClassicBase {
/// @dev Initializer.
/// @param _governor The governor's address.
/// @param _core The KlerosCore arbitrator.
function initialize(address _governor, KlerosCore _core) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core);
/// @param _wNative The wrapped native token address, typically wETH.
function initialize(address _governor, KlerosCore _core, address _wNative) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core, _wNative);
}

function initialize8() external reinitializer(8) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,14 @@ contract DisputeKitSybilResistant is DisputeKitClassicBase {
/// @param _governor The governor's address.
/// @param _core The KlerosCore arbitrator.
/// @param _poh The Proof of Humanity registry.
function initialize(address _governor, KlerosCore _core, IProofOfHumanity _poh) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core);
/// @param _wNative The wrapped native token address, typically wETH.
function initialize(
address _governor,
KlerosCore _core,
IProofOfHumanity _poh,
address _wNative
) external reinitializer(1) {
__DisputeKitClassicBase_initialize(_governor, _core, _wNative);
poh = _poh;
singleDrawPerJuror = true;
}
Expand Down
Loading
Loading