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)