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; } }
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; } }
Top comments (0)