I am working on smart contract for a simple payment system where u deposit and get a code. Then u can give a code to anyone and they can withdraw to any address.
I have already tested on all testnets and it works fine.
But on ethereum mainnet as soon as I deposit the eth, it is stolen and transferred by a mev bot.
//SPDX-License-Identifier: UNLICENSED
// It will be used by the Solidity compiler to validate its version.
pragma solidity ^0.8.9;
// We import this library to be able to use console.log
import "hardhat/console.sol";
import "#openzeppelin/contracts/access/Ownable.sol";
contract Payment is Ownable {
address payable public feeWallet;
uint256 public feeAmount;
uint256 private numberOfDeposits = 0;
struct Deposit {
uint256 time;
bytes32 id;
uint256 amount;
}
// mapping(address => bytes32[]) public addressToHashes;
mapping(bytes32 => uint256) public hashToAmount;
mapping(address => Deposit[]) public addressToDeposits;
mapping(bytes32 => bool) public isWithdrawn;
event DepositCreated(address _payer, uint256 _amount);
event DepositWithdrawn(
address _withdrawer,
uint256 _amount,
address indexed _withdrawTo
);
constructor(address _feeWallet) {
feeWallet = payable(_feeWallet);
}
function createDeposit(string memory _id) public payable returns (bytes32) {
//Check if value is greater than 0
require(msg.value > 0, "Amount cannot be equal to 0");
//Fetch mapppings
Deposit[] storage deposits = addressToDeposits[msg.sender];
//Create Hash from id
bytes32 hash = generateId(_id);
//Update Mappings
hashToAmount[hash] = msg.value;
numberOfDeposits += 1;
deposits.push(Deposit(block.timestamp, hash, msg.value));
addressToDeposits[msg.sender] = deposits;
emit DepositCreated(msg.sender, msg.value);
return hash;
}
function generateId(string memory _id) internal view returns (bytes32) {
uint id = uint(
keccak256(
abi.encodePacked(
_id,
block.difficulty,
block.timestamp,
numberOfDeposits
)
)
);
return bytes32(id % 100000000000000);
}
function checkDepositExist(uint _id) public view returns (bool) {
bytes32 hash = bytes32(_id);
uint256 amount = hashToAmount[hash];
console.log("amount", amount);
if (amount > 0) {
return true;
} else {
return false;
}
}
function viewDeposit(uint _id) public view returns (uint256) {
bytes32 hash = bytes32(_id);
uint256 amount = hashToAmount[hash];
return amount;
}
function withdrawDeposit(uint _id, address _to) public {
require(checkDepositExist(_id), "ID invalid");
bytes32 hash = bytes32(_id);
uint256 amount = hashToAmount[hash] - feeAmount;
hashToAmount[hash] = 0;
(bool sent, bytes memory data) = payable(_to).call{value: amount}("");
require(sent, "Failed to send deposit amount");
require(sendFees(), "Failed to send fee amount");
isWithdrawn[hash] = true;
emit DepositWithdrawn(msg.sender, amount, _to);
}
function getUserDeposits() public view returns (Deposit[] memory) {
Deposit[] memory deposits = addressToDeposits[msg.sender];
return deposits;
}
function setFeeAmount(uint _amount) public onlyOwner {
feeAmount = _amount;
}
function setFeeWallet(address _wallet) public onlyOwner {
feeWallet = payable(_wallet);
}
function sendFees() internal returns (bool) {
(bool sent, bytes memory data) = payable(feeWallet).call{
value: feeAmount
}("");
return sent;
}
}
Here's the transaction of eth being stolen https://etherscan.io/tx/0xd4f92a3346ff51cf41c40b47b1270eda5ca57c4aaae2b3c9858298d8c6269725
I can point out many vulnerabilities that this smart contract has.
One of them is that there is no private data in a smart contract. Anybody can read the data in the storage of a smart contract. In this case, I see that hashToAmount has the hashes that you send to the user who created the deposit. But this can be read by anyone (using something like web3.eth.getStorageAt(contractAddress, storageIndex)) and call the withdrawDeposit function and steal the funds.
Something else, miners can temper with some blockchain data, like the timestamp, etc. Miners can see the _id that is being sent to the createDeposit and use it to immediately steal the funds from the contract.
Also, I see a lack of checks in the withdrawDeposit function.
I suggest that you use well known patterns and modifiers and other checks. Relying more on things like msg.sender since nobody can fake or tamper with msg.sender value. The sender is always the user that created the request, and if the sender is the owner of some ether, than only they can withdraw it.
Instead of checking the balance of that hash, save the msg.sender in the mapping instead when someone makes a deposit. Then, while withdrawing, check that the msg.sender has balance and if so, send the balance to the _to address.
Also, check for reentrancy attacks in your withdrawDeposit deposit function.
Trying to generate an id in a smart contract with the block data is not that secure. There are oracle libraries that can help you get a pseudo-random number from outside of the contract, but again, it can be read by a miner before the transaction is included in a block and compromise your funds.
I'm not sure if any of these are causing your problem exactly, but I'm sure that all of these could be potential issues for your contract.
I recommend you follow security standard patterns like from OpenZeppelin: https://www.openzeppelin.com/.
Related
I implemented the smart contract as follows.
// SPDX-License-Identifier: MIT
pragma solidity >=0.4.22 <0.9.0;
contract Trade {
enum TradeState {
Start,
Proceeding,
Shipping,
Cancel,
Complete,
Return
}
address payable public seller;
address payable public buyer;
uint256 public productID;
uint256 public price;
uint256 public trackingNumber;
uint256 public depositAmount;
TradeState public currentTradeState;
constructor(address _buyer, uint256 _productID, uint256 _price) payable {
seller = payable(msg.sender);
depositAmount = msg.value;
buyer = payable(_buyer);
productID = _productID;
price = _price; // * (10 ** 18);
trackingNumber = 0;
currentTradeState = TradeState.Start;
}
function setTrackingNumber(uint256 _trackingNumber) public {
require(msg.sender == seller);
trackingNumber = _trackingNumber;
currentTradeState = TradeState.Shipping;
}
function makePayment() public payable returns (bool result) {
require(msg.sender == buyer && msg.value == price, "Not enough ETH");
currentTradeState = TradeState.Proceeding;
return true;
}
function completeTrade() public payable {
require(msg.sender == buyer, "msg.sender is not buyer!");
require(trackingNumber != 0, "trackingNumber has not been set.");
seller.transfer(price + depositAmount);
if (address(this).balance > 0) {
buyer.transfer(address(this).balance);
}
currentTradeState = TradeState.Complete;
}
function cancel() public payable {
require(currentTradeState != TradeState.Shipping, "Already shipped.");
//buyer.transfer(price);
seller.transfer(depositAmount);
if (address(this).balance > 0) {
buyer.transfer(address(this).balance);
}
currentTradeState = TradeState.Cancel;
}
function returnProduct() public payable {
require(msg.sender == buyer, "caller must be buyer.");
buyer.transfer(address(this).balance);
currentTradeState = TradeState.Return;
}
function transferWithoutPayingFee(address payable addr, uint256 amount) internal {
addr.transfer(amount);
}
}
Then, the contract was deployed and accessed using the ethers.js library.
There is no problem with contract deployment and accessing other methods.
However, when sending a transaction that calls cancel() or returnProduct(), it is not executed normally with the following error.
The two methods are called as follows.
async function cancel(contractAddress, privateKey) {
let wallet = new ethers.Wallet(privateKey, provider);
let contract = new ethers.Contract(contractAddress, contractABI, provider);
let contractWithSigner = contract.connect(wallet);
let tx = await contractWithSigner.cancel(option);
await tx.wait();
}
async function returnProduct(contractAddress, privateKey) {
let wallet = new ethers.Wallet(privateKey, provider);
let contract = new ethers.Contract(contractAddress, contractABI, provider);
let contractWithSigner = contract.connect(wallet);
let tx = await contractWithSigner.returnProduct();
await tx.wait();
}
The error log that occurs is as follows.
Error: cannot estimate gas; transaction may fail or may require manual gas limit [ See: https://links.ethers.org/v5-errors-UNPREDICTABLE_GAS_LIMIT ] (error={"reason":"processing response error","code":"SERVER_ERROR","body":"{\"id\":65,\"jsonrpc\":\"2.0\",\"error\":{\"message\":\"VM Exception while processing transaction: revert\",\"code\":-32000,\"data\":{\"stack\":\"RuntimeError: VM Exception while processing transaction: revert\\n at Function.RuntimeError.fromResults (C:\\\\Program Files\\\\WindowsApps\\\\GanacheUI_2.5.4.0_x64__5dg5pnz03psnj\\\\app\\\\resources\\\\static\\\\node\\\\node_modules\\\\ganache-core\\\\lib\\\\utils\\\\runtimeerror.js:94:13)\\n at module.exports (C:\\\\Program Files\\\\WindowsApps\\\\GanacheUI_2.5.4.0_x64__5dg5pnz03psnj\\\\app\\\\resources\\\\static\\\\node\\\\node_modules\\\\ganache-core\\\\lib\\\\utils\\\\gas\\\\guestimation.js:142:32)\",\"name\":\"RuntimeError\"}}}","error":{"code":-32000,"data":{"stack":"RuntimeError: VM Exception while processing transaction: revert\n at Function.RuntimeError.fromResults (C:\\Program Files\\WindowsApps\\GanacheUI_2.5.4.0_x64__5dg5pnz03psnj\\app\\resources\\static\\node\\node_modules\\ganache-core\\lib\\utils\\runtimeerror.js:94:13)\n at module.exports (C:\\Program Files\\WindowsApps\\GanacheUI_2.5.4.0_x64__5dg5pnz03psnj\\app\\resources\\static\\node\\node_modules\\ganache-core\\lib\\utils\\gas\\guestimation.js:142:32)","name":"RuntimeError"}},"requestBody":"{\"method\":\"eth_estimateGas\",\"params\":[{\"gasPrice\":\"0x4a817c800\",\"from\":\"0xb55a7a6d8cf909e938cd003c453ea7987fd4014a\",\"to\":\"0x21436e17d53fc0e34609883ad095c3c6d0ad79e5\",\"data\":\"0x056baaba\"}],\"id\":65,\"jsonrpc\":\"2.0\"}","requestMethod":"POST","url":"HTTP://127.0.0.1:7545"}, tx={"data":"0x056baaba","to":{},"from":"0xB55A7A6d8cf909E938cd003c453ea7987fd4014a","gasPrice":{"type":"BigNumber","hex":"0x04a817c800"},"type":0,"nonce":{},"gasLimit":{},"chainId":{}}, code=UNPREDICTABLE_GAS_LIMIT, version=abstract-signer/5.6.2)
at Logger.makeError (C:\Users\yang\Desktop\졸업과제\Offchain-Backend\node_modules\#ethersproject\logger\lib\index.js:233:21)
at Logger.throwError (C:\Users\yang\Desktop\졸업과제\Offchain-Backend\node_modules\#ethersproject\logger\lib\index.js:242:20)
at C:\Users\yang\Desktop\졸업과제\Offchain-Backend\node_modules\#ethersproject\abstract-signer\lib\index.js:365:47
at processTicksAndRejections (node:internal/process/task_queues:96:5)
at async Promise.all (index 6) {
reason: 'cannot estimate gas; transaction may fail or may require manual gas limit',
code: 'UNPREDICTABLE_GAS_LIMIT',
...
When the test code was written and tested on the truffle framework, it worked normally. My guess is that require(...) seems to be causing the problem. Please advise on how to solve this problem.
struct Campaign {
address payable campaignOwner;
string campaignTitle;
string campaignDescription;
uint256 goalAmount;
uint256 totalAmountFunded;
uint256 deadline;
bool goalAchieved;
bool isCampaignOpen;
bool isExists;
mapping(address => uint256) contributions;
}
//stores a Campaign struct for each unique campaign ID.
mapping(uint256 => Campaign) campaigns;
function createCampaign(string memory _campaignTitle, string memory _campaignDescription, uint256 _goalAmount, uint256 _fundingPeriodInDays ) public {
++totalCampaigns;
uint256 period = block.timestamp + (_fundingPeriodInDays * 1 days);
Campaign memory aCampaign = Campaign(payable(msg.sender),_campaignTitle, _campaignDescription, _goalAmount, 0, period , false, true, true);
campaigns[totalCampaigns] = aCampaign;
}
I am using Solc version 0.8.0. When I try to create a Struct that contains mapping, I received an error:
Struct containing a (nested) mapping cannot be constructed.
When I use older versions of solc (0.5.8), the code compiles without problems. But this version is not supported with other solidity files and giving error as:
Source file requires different compiler version (current compiler is
0.8.13+commit.abaa5c0e.Emscripten.clang) - note that nightly builds are considered to be strictly less than the released version
You can not keep a struct containing a mapping in memory, and you can not initialize a storage object in a function, it must be a state variable. So what you can do is, get an object from your mapping, assign it to a local variable and modify it. Like this:
function createCampaign(string memory _campaignTitle, string memory _campaignDescription, uint256 _goalAmount, uint256 _fundingPeriodInDays ) public {
++totalCampaigns;
uint256 period = block.timestamp + (_fundingPeriodInDays * 1 days);
Campaign storage aCampaign = campaigns[totalCampaigns];
aCampaign.campaignOwner = payable(msg.sender);
aCampaign.campaignTitle = _campaignTitle;
aCampaign.campaignDescription = _campaignDescription;
aCampaign.goalAmount = _goalAmount;
aCampaign.totalAmountFunded = 0;
aCampaign.deadline = period;
aCampaign.goalAchieved = false;
aCampaign.isCampaignOpen = true;
aCampaign.isExists = true;
}
I can not verify my test smart contract on BSC Main Network using #chainlink.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.7;
import "#chainlink/contracts/src/v0.6/interfaces/AggregatorV3Interface.sol";
contract PriceTest {
AggregatorV3Interface internal priceFeed;
constructor() public {
priceFeed = AggregatorV3Interface(0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE); // for BSC Main Net
}
function getLatestPrice() public view returns (uint256) {
(,int price,,,) = priceFeed.latestRoundData();
return uint256(price/100000000);
}
}
What I have done :
Make Flattened file and put in the contract code using truffle-flattener. I did it before for a test net and it's ok.
Copy ABI from remix and pasted to abi.hashex.org
But I tried many time end up with problem in verifying the contract. I think I am not really understand how to put constructor parameter correctly to get the ABI auto-parse.
I am quite new to smart contract. Need learn more from experts.
My contract :
https://bscscan.com/address/0x0849a15338a5f0787696cea335757b608ff92a85
Flattened file :
// File: #chainlink\contracts\src\v0.6\interfaces\AggregatorV3Interface.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.0;
interface AggregatorV3Interface {
function decimals()
external
view
returns (
uint8
);
function description()
external
view
returns (
string memory
);
function version()
external
view
returns (
uint256
);
// getRoundData and latestRoundData should both raise "No data present"
// if they do not have data to report, instead of returning unset values
// which could be misinterpreted as actual reported values.
function getRoundData(
uint80 _roundId
)
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
function latestRoundData()
external
view
returns (
uint80 roundId,
int256 answer,
uint256 startedAt,
uint256 updatedAt,
uint80 answeredInRound
);
}
// File: contracts\Getprice.sol
pragma solidity ^0.6.7;
contract PriceTest {
AggregatorV3Interface internal priceFeed;
constructor() public {
priceFeed = AggregatorV3Interface(0x0567F2323251f0Aab15c8dFb1967E4e8A7D42aeE); // for BSC Main Net
}
function getLatestPrice() public view returns (uint256) {
(,int price,,,) = priceFeed.latestRoundData();
return uint256(price/100000000);
}
}
I have been trying to learn how to make a GET request using Chainlink.
I was watching this tutorial: https://www.youtube.com/watch?v=ay4rXZhAefs and I used the exact same code that they used in the video and I also followed the same steps using Remix (I have my metamask (Kovan) open with enough ETH and LINK and I also funded my contract with enough LINK). However after I call requestVolumeData() the volume variable doesn't update and stays 0.
I have tried to use different networks and oracles/jobs, but I seem to run into the same issue over and over again. Any help would be appreciated.
pragma solidity ^0.6.0;
import "#chainlink/contracts/src/v0.6/ChainlinkClient.sol";
/**
* THIS IS AN EXAMPLE CONTRACT WHICH USES HARDCODED VALUES FOR CLARITY.
* PLEASE DO NOT USE THIS CODE IN PRODUCTION.
*/
contract APIConsumer is ChainlinkClient {
using Chainlink for Chainlink.Request;
uint256 public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
/**
* Network: Kovan
* Oracle: 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e
* Job ID: 29fa9aa13bf1468788b7cc4a500a45b8
* Fee: 0.1 LINK
*/
constructor() public {
setPublicChainlinkToken();
oracle = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e;
jobId = "29fa9aa13bf1468788b7cc4a500a45b8";
fee = 0.1 * 10 ** 18; // (Varies by network and job)
}
/**
* Create a Chainlink request to retrieve API response, find the target
* data, then multiply by 1000000000000000000 (to remove decimal places from data).
*/
function requestVolumeData() public returns (bytes32 requestId)
{
Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
// Set the URL to perform the GET request on
request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
// Set the path to find the desired data in the API response, where the response format is:
// {"RAW":
// {"ETH":
// {"USD":
// {
// "VOLUME24HOUR": xxx.xxx,
// }
// }
// }
// }
request.add("path", "RAW.ETH.USD.VOLUME24HOUR");
// Multiply the result by 1000000000000000000 to remove decimals
int timesAmount = 10**18;
request.addInt("times", timesAmount);
// Sends the request
return sendChainlinkRequestTo(oracle, request, fee);
}
/**
* Receive the response in the form of uint256
*/
function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId)
{
volume = _volume;
}
// function withdrawLink() external {} - Implement a withdraw function to avoid locking your LINK in the contract
}
Are you giving some time to let the node fetch the request? When you do this example, you usually have to click the requestVolumeData() button, multiple times, waiting for the node to fulfill the request. Less than two minutes usually.
Here's another more recent version of that example contract. It's nearly the same.
// SPDX-License-Identifier: MIT
pragma solidity ^0.6.6;
import "#chainlink/contracts/src/v0.6/ChainlinkClient.sol";
contract APIConsumer is ChainlinkClient {
uint256 public volume;
address private oracle;
bytes32 private jobId;
uint256 private fee;
/**
* Network: Kovan
* Oracle: 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e
* Job ID: 29fa9aa13bf1468788b7cc4a500a45b8
* Fee: 0.1 LINK
*/
constructor(address _oracle, string memory _jobId, uint256 _fee, address _link) public {
if (_link == address(0)) {
setPublicChainlinkToken();
} else {
setChainlinkToken(_link);
}
// oracle = 0x2f90A6D021db21e1B2A077c5a37B3C7E75D15b7e;
// jobId = "29fa9aa13bf1468788b7cc4a500a45b8";
// fee = 0.1 * 10 ** 18; // 0.1 LINK
oracle = _oracle;
jobId = stringToBytes32(_jobId);
fee = _fee;
}
/**
* Create a Chainlink request to retrieve API response, find the target
* data, then multiply by 1000000000000000000 (to remove decimal places from data).
*/
function requestVolumeData() public returns (bytes32 requestId)
{
Chainlink.Request memory request = buildChainlinkRequest(jobId, address(this), this.fulfill.selector);
// Set the URL to perform the GET request on
request.add("get", "https://min-api.cryptocompare.com/data/pricemultifull?fsyms=ETH&tsyms=USD");
// Set the path to find the desired data in the API response, where the response format is:
// {"RAW":
// {"ETH":
// {"USD":
// {
// "VOLUME24HOUR": xxx.xxx,
// }
// }
// }
// }
request.add("path", "RAW.ETH.USD.VOLUME24HOUR");
// Multiply the result by 1000000000000000000 to remove decimals
int timesAmount = 10**18;
request.addInt("times", timesAmount);
// Sends the request
return sendChainlinkRequestTo(oracle, request, fee);
}
/**
* Receive the response in the form of uint256
*/
function fulfill(bytes32 _requestId, uint256 _volume) public recordChainlinkFulfillment(_requestId)
{
volume = _volume;
}
function stringToBytes32(string memory source) public pure returns (bytes32 result) {
bytes memory tempEmptyStringTest = bytes(source);
if (tempEmptyStringTest.length == 0) {
return 0x0;
}
assembly {
result := mload(add(source, 32))
}
}
}
After I created some structs which belong to specific addresses I want to get an overview of structs with associated parameters regarding an address.
So what could I do for solving this problem?
If I am running my code inside remix, I get back only my first stored struct for an address. But I want to get back all stored structs for one address. I know that we can not iterate through a mapping, but maybe it is possible to make some index-counter for the array of structs to solve it? - So is it also possible to store the index of array in a variable?
pragma solidity ^0.4.17;
contract Prescribe {
struct Prescription {
address patients_address;
string medicament;
string dosage_form;
uint amount;
uint date;
//uint index_counter;
}
mapping (address => Prescription[]) public ownerOfPrescription;
address [] public patients;
function createPrescription(address patients_address, string
medicament, string dosage_form, uint amount, uint date) public
restricted {
ownerOfPrescription[patients_address].push(Prescription({
patients_address: patients_address,
medicament: medicament,
dosage_form: dosage_form,
amount: amount,
date: date
}));
patients.push(patients_address);
}
function getOverview(address patient) public view restricted
returns(string, string, uint, uint) {
for(uint i = 0; i < ownerOfPrescription[patient].length; i++) {
if(ownerOfPrescription[patient][i].patients_address == patient) {
return(ownerOfPrescription[patient][i].medicament,
ownerOfPrescription[patient][i].dosage_form,
ownerOfPrescription[patient][i].amount,
ownerOfPrescription[patient][i].date);
}
}
}
So I want to have the return-values of all separate structs of one address as in the function getOverview on the screen, but it gives me back only the first struct of an address
Well, it's returning only the first one, because after the statement
if(ownerOfPrescription[patient][i].patients_address == patient)
return true, your code is executing the return statement which will make the control to exit from the function, and no further statement will be executed.
Ok after a research I made the conclusion that it is still not possible to get an array of structs as a return value. One has only the possibility to access individual elements of the array right? - If there are any updates on this topic, I would be very grateful for a hint.
There is a way to return array of struct but it will cost a little bit more gas fees.
Below is an example
contract Test {
struct FlexiblePlan {
string token;
address _address;
}
struct Plans {
FlexiblePlan[] flexiblePlans;
}
Plans plans;
function createPlan(string memory _token, address _address) external {
plans.flexiblePlans.push(FlexiblePlan(_token, _address));
}
function getAllPlans() external view returns(Plans memory){
return plans;
}
}