You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何实现管理员调用withdraw函数前需2/3多签批准交易?

How to Implement 2/3 Multi-Signature Approval for Your Withdraw Function

Absolutely, your requirement is fully achievable! Let’s break down how to add the multi-signature approval flow to your withdraw function, plus explain why your current Gnosis Safe interaction is failing.

Why Your Current Gnosis Safe Interaction Fails

Your existing withdraw function sends ETH directly to msg.sender (the account calling the function) as soon as a managerRole holder invokes it. When using Gnosis Safe here:

  • msg.sender becomes the Gnosis Safe contract address, not your intended owner wallet, which breaks your target recipient logic.
  • There’s no built-in approval check, so the transaction tries to execute immediately without meeting your 2/3 multi-sig requirement—leading to the "this transaction will fail" warning.

Step-by-Step Implementation

We’ll split the withdraw process into three secure phases: initiate a request, collect approvals, and execute the transfer once 2/3 of the owner multisig members have signed off.

1. Add Core State Variables & Roles

First, define structures to track requests and approvals, plus a dedicated role for your owner multisig members:

import "@openzeppelin/contracts/access/AccessControl.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";

contract YourContract is AccessControl, ReentrancyGuard {
    bytes32 public constant MANAGER_ROLE = keccak256("MANAGER_ROLE");
    bytes32 public constant OWNER_MULTISIG_ROLE = keccak256("OWNER_MULTISIG_ROLE");

    struct WithdrawRequest {
        address requester;
        uint256 amount;
        uint256 approvalCount;
        bool isExecuted;
        mapping(address => bool) hasApproved;
    }

    mapping(uint256 => WithdrawRequest) public withdrawRequests;
    uint256 public requestCounter;

    // Events for transparency on-chain
    event WithdrawRequestInitiated(uint256 requestId, address requester, uint256 amount);
    event WithdrawRequestApproved(uint256 requestId, address approver);
    event WithdrawExecuted(uint256 requestId, address recipient, uint256 amount);

    // Constructor - set initial admin/manager roles
    constructor() {
        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        // Add initial MANAGER_ROLE holders here if needed
    }
}

2. Replace Direct Withdraw with Request Initiation

Instead of transferring ETH immediately, let managerRole holders create a pending withdraw request:

function initiateWithdraw() external onlyRole(MANAGER_ROLE) returns(uint256 requestId) {
    uint256 withdrawAmount = getBalance();
    require(withdrawAmount > 0, "No balance available to withdraw");

    requestId = requestCounter++;
    WithdrawRequest storage newRequest = withdrawRequests[requestId];
    newRequest.requester = msg.sender;
    newRequest.amount = withdrawAmount;
    newRequest.approvalCount = 0;
    newRequest.isExecuted = false;

    emit WithdrawRequestInitiated(requestId, msg.sender, withdrawAmount);
}

3. Add Approval Function for Multisig Members

Only OWNER_MULTISIG_ROLE holders can approve pending requests:

function approveWithdraw(uint256 requestId) external onlyRole(OWNER_MULTISIG_ROLE) {
    WithdrawRequest storage request = withdrawRequests[requestId];
    
    require(!request.isExecuted, "Request has already been executed");
    require(!request.hasApproved[msg.sender], "You've already approved this request");

    request.hasApproved[msg.sender] = true;
    request.approvalCount++;

    emit WithdrawRequestApproved(requestId, msg.sender);
}

4. Add Executable Withdraw (After 2/3 Approvals)

Once enough approvals are collected, anyone (or restricted roles) can trigger the ETH transfer to your owner wallet:

function executeWithdraw(uint256 requestId) external nonReentrant {
    WithdrawRequest storage request = withdrawRequests[requestId];
    
    require(!request.isExecuted, "Request has already been executed");
    require(address(this).balance >= request.amount, "Insufficient contract balance");

    // Calculate required approvals (2/3 of multisig members, rounded up for odd counts)
    uint256 totalMultisigMembers = getRoleMemberCount(OWNER_MULTISIG_ROLE);
    uint256 requiredApprovals = (totalMultisigMembers * 2) / 3;
    if ((totalMultisigMembers * 2) % 3 != 0) {
        requiredApprovals++;
    }

    require(request.approvalCount >= requiredApprovals, "Not enough approvals to execute");

    // Mark request as executed first to prevent reentrancy risks
    request.isExecuted = true;

    // Transfer ETH to your intended owner wallet (replace with your target address if needed)
    (bool sent, ) = payable(owner()).call{value: request.amount}("");
    require(sent, "Ether transfer failed");

    emit WithdrawExecuted(requestId, owner(), request.amount);
}

Gnosis Safe Integration Tips

  • Grant the OWNER_MULTISIG_ROLE to your Gnosis Safe contract address (not individual owners). This lets multi-sig transactions from the Safe count as valid approvals.
  • Workflow for Gnosis Safe users:
    1. A managerRole holder initiates the withdraw request via a regular transaction.
    2. Gnosis Safe members create and approve a multi-sig transaction to call approveWithdraw for the request ID.
    3. Once enough approvals are collected, anyone can call executeWithdraw (this can also be done via a Gnosis Safe transaction for extra security).

Security Checks to Remember

  • Keep nonReentrant on executeWithdraw to block reentrancy attacks.
  • Verify getBalance() correctly returns the contract’s available ETH (exclude any locked funds if applicable).
  • Test with different multisig sizes (e.g., 3 members need 2 approvals, 5 need 4) to confirm the approval calculation works as intended.

内容的提问来源于stack exchange,提问作者Eniola Agboola

火山引擎 最新活动