0

I'm trying to do the following operation in Solidity:

BetCampaign memory newCampaign = BetCampaign(id, new Bet[](0), betEndTime, false);
betCampaigns.push(newCampaign);

With the following member declaration:

BetCampaign[] public betCampaigns;

But I keep getting the following error:

UnimplementedFeatureError: Copying of type struct Betting.Bet memory[] memory to storage not yet supported.

I've tried changing the newCampaign declaration to a "storage" instead of "memory" but that causes errors as well. How do I resolve this?

Here is the whole file I'm using:

pragma solidity ^0.5.0;

contract Betting {

    struct Bet {
        uint betId;
        uint campaignId;
        address sender;
        uint stake;
        bool betValue;
    }

    struct BetCampaign {
        uint campaignId;
        Bet[] bets;
        mapping(address => bool) userReserved; // mapping ignored in constructor (?)
        uint betEndTime;
        bool isEnded;
    }

    event BetCampaignCreated(uint campaignId, uint betEndTime);
    event BetAdded(uint betId, uint campaignId, address indexed user, uint256 betAmount, bool betsTrue);

    BetCampaign[] public betCampaigns;
    mapping(uint => bool)  betCampaignReserved;
    mapping(uint => bool) betReserved;
    uint nonce;

    constructor() public {
        nonce = 0;
    }

    modifier isLater(uint time) {
        require(time > now, "The end time must be in the future");
        _;
    }

    modifier campaignExistsM(uint campaignId) {
        require(campaignExists(campaignId), "The campaign does not exist");
        _;
    }

    modifier userNotInCampaign(uint campaignId, address user) {
        require(!betPlaced(campaignId, user));
        _;
    }

    modifier userInCampaign(uint campaignId, address user) {
        require(betPlaced(campaignId, user));
        _;
    }

    modifier isSelf(address target) {
        require(msg.sender == target);
        _;
    }

    modifier enoughMoney(uint amount) {
        require(msg.value >= amount);
        _;
    }

    function generateId() internal returns (uint) {
        nonce += 1;
        return uint(keccak256(abi.encodePacked(nonce)));
    }

    function campaignExists(uint campaignId) internal view returns (bool) {
        return betCampaignReserved[campaignId];
    }

    function createCampaign(uint betEndTime) public isLater(betEndTime) {
        while (true) {
            uint id = generateId();
            if (!campaignExists(id)) {
                BetCampaign memory newCampaign = BetCampaign(id, new Bet[](0), betEndTime, false);
                betCampaigns.push(newCampaign);
                betCampaignReserved[id] = true;
                emit BetCampaignCreated(id, betEndTime);
                break;
            }
        }
    }

    function getCampaignInformation(uint campaignId) public view campaignExistsM(campaignId) returns (uint, uint, bool) {
        BetCampaign memory campaign;
        for (uint i = 0; i < betCampaigns.length; i++) {
            if (betCampaigns[i].campaignId == campaignId) {
                campaign = betCampaigns[i];
                break;
            }
        }
        return (campaign.campaignId, campaign.betEndTime, campaign.isEnded);
    }

    function getCampaign(uint campaignId) internal view campaignExistsM(campaignId) returns (BetCampaign storage) {
        uint index = 0;
        for (uint i = 0; i < betCampaigns.length; i++) {
            if (betCampaigns[i].campaignId == campaignId) {
                index = i;
                break;
            }
        }
        BetCampaign storage campaign = betCampaigns[index];
        return campaign;
    }

    function betExists(uint betId) internal view returns (bool) {
        return betReserved[betId];
    }

    function addBet(uint campaignId, address sender, uint stake, bool betValue)
    payable public isSelf(sender) enoughMoney(stake) campaignExistsM(campaignId) userNotInCampaign(campaignId, sender) {
        while (true) {
            uint id = generateId();
            if (!betExists(id)) {
                BetCampaign storage campaign = getCampaign(campaignId);
                campaign.bets.push(Bet(id, campaignId, sender, stake, betValue));
                campaign.userReserved[sender] = true;
                emit BetAdded(id, campaignId, sender, stake, betValue);
                break;
            }
        }
    }

    function getBet(uint campaignId, address sender)
    public view campaignExistsM(campaignId) userInCampaign(campaignId, sender) returns (uint, uint, address, uint, bool) {
        Bet memory bet;
        BetCampaign memory campaign = getCampaign(campaignId);
        for (uint i = 0; i < campaign.bets.length; i++) {
            if (campaign.bets[i].sender == sender) {
                bet = campaign.bets[i];
            }
        }
        return (bet.betId, bet.campaignId, bet.sender, bet.stake, bet.betValue);
    }

    function betPlaced(uint campaignId, address sender) public view campaignExistsM(campaignId) returns (bool) {
        BetCampaign storage campaign = getCampaign(campaignId);
        return campaign.userReserved[sender];
    }

}
2
  • Please provide you contract file. It is impossible to help you without seeing the BetCampaign and Bet structs. Commented Feb 18, 2019 at 3:02
  • I've updated the question with the whole file. Commented Feb 19, 2019 at 23:53

1 Answer 1

1

The array of BetCampaign isn't ideal and this will be awkward.

struct BetCampaign {
        uint campaignId;
        Bet[] bets;
        mapping(address => bool) userReserved; // mapping ignored in constructor (?)
        uint betEndTime;
        bool isEnded;
}

BetCampaign[] public betCampaigns;

You're running into trouble because you can't copy an instance that was constructed in memory to storage, and you don't want to construct a storage singleton just to copy (push) it into the array. That would be using expensive storage twice for a single move.

This is suboptimal in another way. Including the id in the struct like that implies fishing through the list to find one that matches.

Refactor like this to solve both problems.

struct BetCampaign {
        // uint campaignId;
        Bet[] bets;
        mapping(address => bool) userReserved; // mapping ignored in constructor (?)
        uint betEndTime;
        bool isEnded;
}

// BetCampaign[] public betCampaigns;
mapping(uint => BetCampaign) public betCampaigns; // id => struct

Now, you can find a matching campaign by ID in a single operation. You also solve the nasty memory/storage problem because you will always write to an indexed betCampaigns[] in the mapping.

Check this for an important warning: https://blog.b9lab.com/storage-pointers-in-solidity-7dcfaa536089

Check this for a useful pattern and elaboration on the solution described: https://medium.com/@robhitchens/solidity-crud-part-1-824ffa69509a

Hope it helps.

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.