Solidity Error: Version 0.8.0 : Struct containing a (nested) mapping cannot be constructed - struct

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;
}

Related

How to block bot from stealing deposited eth in smart contract

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/.

Solidity struct mapping not stored in contract

I read many articles on how to use mappings, mappings in struct and came out with something that should be correct to me, based on a few threads.
I know that since solidity 0.7.0 things have changed with nested mappings in struct and did the following :
contract Test {
constructor() {
}
struct Bid {
uint auction_id;
address addr;
uint amount;
}
struct Auction {
uint id;
string dtype;
uint start_date;
uint end_date;
string label;
uint price;
uint amount;
bool closed;
mapping(uint => Bid) bids;
uint bidCount;
}
uint public auctionCount = 0;
mapping(uint => Auction) public auctions;
function createAuction( string memory plabel, string memory ptype, uint nbhours, uint pprice) external {
Auction storage nd = auctions[auctionCount];
nd.id = auctionCount;
nd.dtype = ptype;
nd.start_date = block.timestamp;
nd.end_date = block.timestamp+nbhours*60*60;
nd.label = plabel;
nd.price = pprice;
nd.amount = 0;
nd.closed = false;
nd.bidCount = 0;
auctionCount++;
}
}
Everything compiles fine, the createAuction transaction is succesful.
When checking on the contract in Ganache, auctionCount is incremented but I have no items added in the drawsmapping.
I also debugged the transaction with truffle and it goes through the function, assigning values through the execution of createAuction, but the changes are not persistent.
I even tried removing one string attribute since I read that when there are 3 it could have been a problem (ok, I have only 2 max ;)).
I must have missed something, but I'm out of options right now.
Thanks in advance for your help !
If you are talking about auctions mapping, ensure you use the correct index when accessing mapping items. In your case, the first Auction item you add to the mapping will have a 0 index. I tried your contract in Remix, and everything worked well.

Simple way to concatenate tokenID to tokenURI

I am doing a project for my Honours year at the University of Cape Town using solidity and openzeppelin for my NFTs. I have uploaded a folder of json/png for the metadata. I need to now use the tokenID + .json to set the tokens correct uri when minting them. Below is the simple contract:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "#openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "#openzeppelin/contracts/utils/Strings.sol";
contract ImpactCollection is ERC721URIStorage {
uint256 public tokenCounter;
constructor () ERC721 ("Impact Tokens", "COLLECTION_TICKER"){
tokenCounter = 0;
}
function concatenate(string memory a,uint256 memory b,string memory c) public pure returns (string memory){
return string(abi.encodePacked(a,b,c));
}
function createCollectible() public returns (uint256) {
uint256 newItemId = tokenCounter;
string urinumber = string(abi.encodePacked(newItemId.toString()))
tokenURI = "https://ipfs.io/ipfs/QmQh54Rb8ZFY33P9bWUzgonRvA7XeChVWaAWG3nMqQ19xW/" + urinumber + ".json";
_safeMint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
tokenCounter = tokenCounter + 1;
return newItemId;
}
}
I have the folder url above and i just need to add the token id and then add a .json. My C# brain says: "ipfsurl" + newItemId.toString() + ".json";
What is the remix (solidity) equivalent?
From solidity version 0.8.12 you can use string.concat(s1,s2) for concatenate the strings.
I adjusted and put some notes in your smart contract code:
//SPDX-License-Identifier: MIT
pragma solidity ^0.8;
import "#openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";
import "#openzeppelin/contracts/utils/Strings.sol";
contract ImpactCollection is ERC721URIStorage {
uint256 public tokenCounter;
using Strings for *;
constructor () ERC721 ("Impact Tokens", "COLLECTION_TICKER"){
tokenCounter = 0;
}
function concatenate(string memory a,uint256 b,string memory c) public pure returns (string memory){
return string(abi.encodePacked(a,b,c));
}
function createCollectible() public returns (uint256) {
uint256 newItemId = tokenCounter;
// NOTE: Use Strings.toString for convert a uint to string datatype
string memory urinumber = Strings.toString(newItemId);
// NOTE: I declared a new variable for contain token URI
string memory tokenURI = "https://ipfs.io/ipfs/QmQh54Rb8ZFY33P9bWUzgonRvA7XeChVWaAWG3nMqQ19xW/";
// NOTE: I declare a new variable for contain tokenURI concatenated
string memory fullTokenURI = string.concat("https://ipfs.io/ipfs/QmQh54Rb8ZFY33P9bWUzgonRvA7XeChVWaAWG3nMqQ19xW/", urinumber, ".json");
_safeMint(msg.sender, newItemId);
_setTokenURI(newItemId, tokenURI);
tokenCounter = tokenCounter + 1;
return newItemId;
}
}
This will work!
_setTokenURI(newItemId, string(abi.encodePacked(_uri, '/', newItemId.toString(), '.json')));

Solidity: problem creating a struct containing mappings inside a mapping

This is my code where i am trying to create a struct containing two mappings and insert the structs into a mapping:
pragma solidity ^0.7.2;
contract Campaign {
struct Usuario {
string id;
mapping(string => uint) debe;
mapping(string => uint) leDebe;
}
Usuario[] public usuarios;
uint numUsuarios;
mapping(string => Usuario) public circulo;
constructor () {
}
function usuarioPrueba(string memory id, string memory idDebe, uint valDebe, string memory idLeDebe, uint valLedebe) public {
usuarios.push();
Usuario storage newUsuario = usuarios[numUsuarios];
numUsuarios++;
newUsuario.id = id;
newUsuario.debe[idDebe] = valDebe;
newUsuario.leDebe[idLeDebe] = valLedebe;
circulo[id] = newUsuario;
}
}
but I am getting the following error at line 28 (circulo[id] = newUsuario;) on Remix:
TypeError: Types in storage containing (nested) mappings cannot be
assigned to. circulo[id] = newUsuario;
Thank you so much for the help beforehand and I am sorry for my english, I am from Spain and if the solution its just to obvious, I am kind of new to solidity and smart contracts.
Since v 0.7.0 you cannot assign structs containing nested mappings. What you can do instead is to create new instances like this one and then asign the values to the properties of the struct!
Usuario storage newUsuario = circulo[id];
numUsuarios++;
newUsuario.id = id;
newUsuario.debe[idDebe] = valDebe;
newUsuario.leDebe[idLeDebe] = valLedebe;

Is the mapping in solidity of one key on multiple structs of same type possible?

I am trying to map one address on multiple structs of same type, which belongs to the same address. How can I do this, if I want to choose any of the "stored" structs for one address on request afterwards?
I created a struct called Prescription, and a mapping with the patients address. So what I really want is to map the patients address to several Prescription-structs.
struct Prescription {
address patients_address;
string medicament;
string dosage_form;
uint amount;
uint date;
}
mapping (address => Prescription) ownerOfPrescription;
address [] public patients;
function createPrescription(address patients_address, string medicament, string dosage_form, uint amount, uint date) public restricted {
var newPrescription = ownerOfPrescription[patients_address];
newPrescription.medicament = medicament;
newPrescription.dosage_form = dosage_form;
newPrescription.amount = amount;
newPrescription.date = date;
patients.push(patients_address) -1;
}
function getPre(address _address)view public returns (string, string, uint, uint){
return(
ownerOfPrescription[_address].medicament,
ownerOfPrescription[_address].dosage_form,
ownerOfPrescription[_address].amount,
ownerOfPrescription[_address].date);
}
Now I would have a function, where I can call all written Prescriptions for one patient. Actually I am able to call only the last written prescription for one address.
Sure, the value type of a mapping can be an array:
// map to an array
mapping (address => Prescription[]) ownerOfPrescription;
function createPrescription(...) ... {
// add to the end of the array
ownerOfPrescription[patients_address].push(Prescription({
medicament: medicament,
...
});
patients.push(patients_address);
}

Resources