Как использовать функцию BytesToUint в Solidity (ту, что со сборкой)?

Я использовал следующую функцию для преобразования байтов в uint:

function bytesToUint(bytes b) public pure returns (uint){
    uint number;

    for(uint i=0;i<b.length;i++){
        number = number + uint(b[b.length-1-i])*(10**i);
    }

    return number;
}

Поскольку явное преобразование byte1 в uint больше не поддерживается, я нашел следующую альтернативу:

function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) {
    require(_bytes.length >= (_start + 32), "Read out of bounds");
    uint256 tempUint;

    assembly {
        tempUint := mload(add(add(_bytes, 0x20), _start))
    }

    return tempUint;
}

Байты вводятся в функцию ApproveAndCall токена ERC20.

function approveAndCall(address spender, uint tokens, bytes data) public returns (bool success) {
    allowed[msg.sender][spender] = tokens;
    emit Approval(msg.sender, spender, tokens);
    ApproveAndCallFallBack(spender).receiveApproval(msg.sender, tokens, this, data);
    return true;
}

который отправляется на получение одобрения моего смарт-контракта.

function receiveApproval(address _from, uint _token, address _tokenContract, bytes memory _data) public {
    
    if(!ERC20Interface(_tokenContract).transferFrom(_from, address(this), _token)) {
        revert();
    }
    
    _0xChangeLib.place_sell_order(exchange, _from, _tokenContract, _token, _0xChangeLib.toUint256(_data, 0));

}

Может кто-нибудь объяснить, как работает этот новый BytesToUint256? Я не могу разобраться в коде сборки и том, как использовать эту функцию. Я не понимаю аргумент uint256 _start. Я также не уверен, смогу ли я использовать в качестве ввода тот же формат, что и использовал. В качестве аргумента я преобразовывал количество wei в байты, например 100 wei = 0x100, с простой функцией в javascript и отправленной на адрес токена с помощью Web3.js.

Я хотел бы в функции ReceiveApproval смарт-контракта вызвать функцию BytesToUint для дальнейшей обработки данных.

Заранее большое спасибо за вашу помощь!


person Gilles Walther    schedule 04.08.2020    source источник
comment
Я не знаю этого ассемблера, но похоже, что _start - это просто начальное значение, которое нужно добавить, а не uint number = 0;. Но я не вижу, чтобы он умножался на 10, поэтому похоже, что он просто складывает все цифры, игнорируя их разрядные значения. Или, может быть, он превращает массив целых цифр в строку ASCII как uint256? Но ASCII '0' - это 0x30, а не 0x20, так что это не так.   -  person Peter Cordes    schedule 04.08.2020
comment
Я заблудился в том, что если я введу 0x0 в качестве входных данных для байтовых данных в ApproveAndCall и поставлю 0 как _start, он вернет ошибку за пределами границ, обнаруженную в функции ToUint256.   -  person Gilles Walther    schedule 04.08.2020


Ответы (1)


_start в основном указывает на байтовый индекс в массиве bytes, где начинается целочисленное значение. Первые 32 байта (или 0x20 в шестнадцатеричном формате) содержат длину массива bytes, а затем начинают целочисленное значение, которое сохраняется в следующих 32 байтах. Значение _start равно нулю, потому что второй набор из 32 байтов содержит нужное вам целочисленное значение. По сути, вы можете преобразовать эту функцию в this.

function toUint256(bytes memory _bytes)   
  internal
  pure
  returns (uint256 value) {

    assembly {
      value := mload(add(_bytes, 0x20))
    }
}

Что касается вашего комментария 0x0 будет означать, что массив байтов имеет длину 1, то есть второй ноль, а оператор require ожидает длину не менее 32.

person Sheraz Arshad    schedule 05.08.2020
comment
Спасибо, это начинает обретать смысл, я могу запустить функцию с предложенным вами кодом. Он все еще нуждается в дополнительном тестировании, но я добираюсь до цели! - person Gilles Walther; 05.08.2020
comment
Привет, я дополнительно проверил функцию, и результаты странные. Используя 0x1C6BF52634000 в качестве входных данных для байтов (500000000000000 в шестнадцатеричной системе), результатом функции toUint256 будет это огромное число: 803469022129495137770981046170581301261101496891396417650688000000000000000. что на самом деле является умножителем исходного числа на 16 ^ 50. Я пробовал с другим числом, и результат немного отличается от исходного умножителя числа на 16 ^ 48. Есть какие-нибудь подсказки, что происходит? - person Gilles Walther; 18.08.2020
comment
байты равны Big Endian, что означает, что их данные начинаются с крайних левых битов, а целые числа равны Little Endian, что означает, что их данные начинаются с крайних правых битов. Поскольку байты считываются из памяти по 32 за раз, а передаваемое вами шестнадцатеричное значение меньше этого, а автоматически добавляемые нули в правых битах делают его огромным значением. Вместо этого укажите байтовое значение длиной 32 байта, которое имеет целочисленное значение в крайних правых битах. Вот так, 0000000000000000000000000000000000000000000000000001c6bf52634000. Используйте этот код для его создания. web3.padLeft(web3.toHex("500000000000000").substr(2), 64) - person Sheraz Arshad; 19.08.2020