0

There is currently a published contract (contract A), I must use another(contract B) to call withdraw() from contract A.

But the function withdraw() already uses msg.sender as a security check. In contract B the "EOA" address can't call contract A anymore to be recognized as the msg.sender.

where the "EOA" address is having sufficient balance to withdraw from contract A. When I try to call the withdraw from contract B, contract A keeps recognizing contract B address as the msg.sender, and using "delegatecall()" also doesn't work.

Contrat A code =>

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract TokenWithdrawal {
    mapping(address => uint256) private balances;

    // Event to log deposits
    event Deposit(address indexed account, uint256 amount);
    
    // Event to log withdrawals
    event Withdrawal(address indexed account, uint256 amount);

    // Function to deposit tokens into the contract
    function deposit() external payable {
        require(msg.value > 0, "Deposit amount must be greater than zero");
        balances[msg.sender] += msg.value;
        emit Deposit(msg.sender, msg.value);
    }

    function getBalance(address account) private view returns (uint256) {
        return balances[account];
    }

    function _checkCaller(address account) private view {
        require(balances[account] > 0, "No balance available for withdrawal");
    }

    function _transferToken(address to, uint256 amount) private {
        require(balances[to] >= amount, "Insufficient balance");
        balances[to] -= amount;
        payable(to).transfer(amount);
        emit Withdrawal(to, amount);
    }

    function withdraw() external {
        _withdraw(msg.sender);
    }

    function _withdraw(address account) private {
        _checkCaller(account);
        uint256 amount = getBalance(account);
        if (amount > 0) {
            _transferToken(account, amount);
        }
    }
}

contract B =>

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ITokenWithdrawal {
    function deposit() external payable;
    function withdraw() external;
}

contract AttackContract {
    address public myWallet;

    ITokenWithdrawal private tokenWithdrawalContract;

    constructor(address _tokenWithdrawalContractAddress) {
        myWallet = msg.sender;
        tokenWithdrawalContract = ITokenWithdrawal(_tokenWithdrawalContractAddress);
    }

    function depositToTokenWithdrawal() external payable {
        require(msg.sender == myWallet, "Caller is not the owner");
        tokenWithdrawalContract.deposit{value: msg.value}();
    }

    function executeWithdraw() external {
        require(msg.sender == myWallet, "Caller is not the owner");
        tokenWithdrawalContract.withdraw();
    }

    // Function to receive Ether. msg.data must be empty
    receive() external payable {}
}
delegatecall contract that keep reverting =>
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface ITokenWithdrawal {
    function withdraw() external;
}

contract AttackContract {
    address public myWallet;
    address private tokenWithdrawalAddress;

    constructor(address _tokenWithdrawalAddress) {
        myWallet = msg.sender;
        tokenWithdrawalAddress = _tokenWithdrawalAddress;
    }

    function executeWithdraw() external {
        require(msg.sender == myWallet, "Caller is not the owner");
        (bool success, ) = tokenWithdrawalAddress.delegatecall(
            abi.encodeWithSignature("withdraw()")
        );
        require(success, "Withdraw failed");
    }

    // Function to receive Ether. msg.data must be empty
    receive() external payable {}
}

Even with the use of low-level call by removing "delegatecall" replaced with "call" yet no way arround this. is this not ever possible?? thank you

1 Answer 1

0

It is as you said.

The msg.sender is the caller of a transaction (/function call). If a contract B is the caller of a function in an other contract A then this contract B will of course be the msg.sender.

There is NO work around. And delegatecall() doesn't have an effect on msg.sender. Delegatecall merely interacts with the function of an other contract while the storage being modified is the one of the contract that issued the delegatecall().

I may refer you to a previous answer i have given:

Why is my _transfer function transfering all my tokens to the deadWallet?

An other address will never be able to act in the stead of an other address if this address is the msg.sender registered. That would be a pretty big security concern. However, the contractB could send and withdraw the funds on behalf of a 3rd party and then wihtdraw them. You could look into allowances (as in ERC20, or ERC721) or simply have a user call first your contract B to send to contract A and then withdraw form contract A

2
  • sorry for late response, just getting back home from work and thank you for the insight, as you`ve suggested in the referred post, could the use of tx.origin be able to work for this?? since the "EOA" address would be the "tx.origin. since there is no function in contract A for allowance to other address to spend the token. Commented May 20 at 19:16
  • If contract A is already published, there is nothing you can do. The caller should be the one that owns the balance. Tx.origin is the EOA address that initiated the transaction but maybe not the address that called the function. In your case that wouldn't change anything. Msg.sender would still be contractB
    – Torof
    Commented May 20 at 22:12

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.