I am making a decentralized crowd funding smart contract in solidity to learn more about the language.
The contract is able to store many crowdfunding projects in a mapping that looks like this
struct CrowdFundingProject{
address author,
string description,
string title,
uint goal,
bool exists
}
mapping(uint, CrowdFundingProject) public projects;
uint lastProject = 0;
the key of the mapping is an autoincremented number. When you create a project it looks like this:
function createProject(string title, string desc, uint goal) public{
CrowdFundingProject newProject;
newProject.author = msg.sender;
newProject.title = title;
newProject.description = desc;
newProject.goal = goal;
newProject.exists = true;
lastProject += 1;
projects[lastProject] = newProject;
}
But i belive that this could cause problems, for example, if user A ran the function createProject and just when the EVM is executing lastProject += 1;
User B runs the function too, making that if lastProject
was equal to 100 it is now 101 at the same time that projects[lastProject] = newProject;
wasn't done executing for user A, both would have a project with the ID 101. which would overwrite the project for user A and just leave the one for user B.
Is this correct? If so, my other approach is instead of directly adding the project as project[lastProject]
it would be better to do something like
lastProject += 1;
uint myProjectsId = lastProject - 1;
projects[myProjectsId] = newProject;
I don't know if this approach would cause any sort of error, or if the overwriting error is even possible, so i want to make sure:
Can an error like the overlapping IDs happen? If so: Would the second approach that I proposed come with any weird behavior?
if user A ran the function createProject and just when the EVM is executing lastProject += 1; User B runs the function too
The EVM executes transactions in series, not in parallel.
Transactions in each block are ordered in a way that the block miner chooses. Most miners simply order from largest gasPrice
to smallest.
So based on the provided code, there's no way to overwrite the projects
key, even if two separate transactions execute the createProject()
function in the same block: The transaction with larger gasPrice
will get project ID 100, and the other will execute milliseconds later and will get project ID 101.
There's an attack vector called frontrunning, related to ordering transactions in the block based on their gasPrice
. It might be useful to read up about it, for example there's a great article describing the basics.
Your code is vulnerable to this vector, but it practically doesn't matter because the attacker could only frontrun someone and steal their desired project ID (leaving the original project creator with ID higher by 1). Usual incentives for frontrunners are stealing ETH and tokens (and your code doesn't show working with ETH nor tokens).