Как управлять большими циклами в Solidity?

#ethereum #solidity #smartcontracts

#ethereum #solidity #смарт-контракты

Вопрос:

Итак, у меня есть этот контракт, и финансирующий может получить общую сумму конкретной кампании с помощью функции getFundsByAddress. Проблема в том, что если у кампании более 30 тысяч учредителей, контракт не сможет выполнить код, потому что ему нужно будет пройти через 30 тысяч раз, чтобы найти все правильные адреса

В Rinkeby newwork максимальный цикл, которого он может достичь, равен 30 кб, после чего возвращается 0

Как я могу разрешить такие случаи?

 contract CrowdFunding {
    struct Funder {
        address addr;
        uint amount;
    }

    struct Campaign {
        address beneficiary;
        uint numFunders;
        uint amount;
        mapping (uint => Funder) funders;
    }

    uint numCampaigns;
    Campaign[] public campaigns;

    function newCampaign() public returns (uint campaignID) {
        campaignID = campaigns.length  ;
        Campaign storage c = campaigns[campaignID];
        c.beneficiary = msg.sender;
    }

    function contribute(uint _campaignID, uint _amount) public {
        Campaign storage c = campaigns[_campaignID];
        c.funders[c.numFunders  ] = Funder({addr: msg.sender, amount: _amount});
        c.amount  = 100;
    }

    // not tested
    function getFundsByAddress() public view returns (uint[] memory) {
        Campaign storage c = campaigns[0];
        uint cont = c.numFunders;

        uint[] memory allAmount = new uint[](TotalAmountOfUser);

        uint counter = 0;

        for (uint i=0; i < cont; i  ) {
           if (c.funders[counter].addr == msg.sender) {
               allAmount[amountCont] = c.funders[counter].amount;
           }
           counter  ;
        }

        return allAmount;
    }   
}
  

Ответ №1:

Я не вижу ничего особенного в числе 30K, которое могло бы это объяснить.

Ваша проблема может заключаться в том, что у транзакции либо заканчивается газ, либо она достигает предела объема газа в блоке. Если вам приходится перебирать массив и вы не можете сделать это каким-либо другим способом, вам следует рассмотреть возможность перебора массива в нескольких транзакциях (т. е. 0-9999, 10.000-19.999, …).

Однако перебор такого количества записей будет довольно дорогим с точки зрения газа, который в реальной сети стоит денег. Но если это невозможно сделать другим способом, то вышесказанное должно вам помочь.

Комментарии:

1. Есть ли какие-либо проблемы с циклированием по огромному массиву внутри функции просмотра?

2. Потенциально да. Хотя это ничего не стоит с точки зрения оплаты, функции просмотра по-прежнему используют gas внутри, чтобы избежать бесконечных циклов, поэтому наличие огромного цикла может привести к тому, что у вашей функции закончится газ и произойдет сбой. Это ничего вам не будет стоить, но сделает вашу функцию непригодной для использования. Однако, пока у функции не заканчивается топливо, все должно быть в порядке, поскольку вы не платите за функции просмотра.

Ответ №2:

Трудно угадать, что getFundsByAddress предполагается делать, потому что код не компилируется, и цикл, похоже, ничего не делает. (Переменная цикла i никогда не используется.)

Но если мне нужно было угадать, он должен возвращать сумму взносов, внесенных вызывающей стороной. Если это так, просто отслеживайте это общее количество по мере внесения взносов и вообще избегайте цикла:

 mapping(address => uint256) public totalContributions;

function contribute(uint _campaignID, uint _amount) public {
    ...

    // Add this line to keep track of the total.
    totalContributions[msg.sender]  = _amount;
}

// No need for getFundsByAddress at all because a call to `totalContributions(address)`
// (the auto-generated getter) does the trick.

// But if you want a function that returns specifically `msg.sender`'s total:
function getMyContributions() external view returns (uint256) {
    return totalContributions[msg.sender];
}