I am using a CampaignFactory contract to create multiple instances of the Campaign contract and keep track of them. Every campaign is initialized with an array of Struct called rewards. When I try to create a new Campaign with createCampaign in remix, I have the following error:
[vm]from: 0x5B3...eddC4to: CampaignFactory.createCampaign((uint256,uint256,string)[]) 0xd91...39138value: 0 weidata: 0x6d9...00000logs: 0hash: 0xa4d...ad5fd
transact to CampaignFactory.createCampaign errored: VM error: revert.
Here is my code:
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.15;
struct Reward {
uint256 contribution;
uint256 maxNumber;
string ImageLink;
}
contract CampaignFactory {
Campaign[] public deployedCampaigns;
function createCampaign(Reward[] memory _rewards)
public
{
Campaign newCampaign = new Campaign(msg.sender);
for (uint256 i = 0; i < _rewards.length; i++) {
newCampaign.createReward(
_rewards[i].contribution,
_rewards[i].maxNumber,
_rewards[i].ImageLink
);
}
deployedCampaigns.push(newCampaign);
}
function getDeployedCampaigns()
public
view
returns (Campaign[] memory)
{
return deployedCampaigns;
}
}
contract Campaign {
Reward[] public rewards;
address public manager;
modifier restricted() {
require(msg.sender == manager);
_;
}
constructor(address creator) {
manager = creator;
}
function createReward(
uint256 _contribution,
uint256 _maxNumber,
string memory _imageLink
) public restricted {
Reward memory newReward = Reward({
contribution: _contribution,
maxNumber: _maxNumber,
ImageLink: _imageLink
});
rewards.push(newReward);
}
}
When you deploy the Campaign
contract, the user's address is assigned to manager
.
Then you're invoking the createReward()
function. Its restricted
modifier allows the function to be invoked only by the manager
. But the function is invoked by your contract - not by the user directly. Which fails the validation and reverts the transaction.
You could get the address of the original transaction sender in tx.origin
, but using it for authorization is a security risk - https://swcregistry.io/docs/SWC-115
So instead, you can pass the user address in the function argument, and verify it there.
function createCampaign(Reward[] memory _rewards)
public
{
// pass the factory address to later validate `msg.sender`
Campaign newCampaign = new Campaign(address(this), msg.sender);
for (uint256 i = 0; i < _rewards.length; i++) {
// pass the user address to validate if they're the `manager`
newCampaign.createReward(
_rewards[i].contribution,
_rewards[i].maxNumber,
_rewards[i].ImageLink,
msg.sender
);
}
deployedCampaigns.push(newCampaign);
}
address factory;
address public manager;
modifier restrictedThroughFactory(address user) {
require(msg.sender == factory && user == manager);
_;
}
modifier restricted() {
require(msg.sender == manager);
_;
}
constructor(address _factory, address creator) {
factory = _factory;
manager = creator;
}
// original function that can be still executed directly by the user
function createReward(
uint256 _contribution,
uint256 _maxNumber,
string memory _imageLink
) public restricted {
_createReward(_contribution, _maxNumber, _imageLink);
}
// overloaded function that can be only executed by the Factory contract
// reverts if the `user` (can be passed only by the Factory contract) is not the `manager`
function createReward(
uint256 _contribution,
uint256 _maxNumber,
string memory _imageLink,
address user
) public restrictedThroughFactory(user) {
_createReward(_contribution, _maxNumber, _imageLink);
}
function _createReward(
uint256 _contribution,
uint256 _maxNumber,
string memory _imageLink
) internal {
Reward memory newReward = Reward({
contribution: _contribution,
maxNumber: _maxNumber,
ImageLink: _imageLink
});
rewards.push(newReward);
}