2

The following code is incomplete. What I want to do is split the bytes data (calldata) into two or more separate log functions for indexing reasons.

The problem is I can't figure out how to do it in Solidity. bytes is of arbitrary length, so I can't copy the calldata into memory as memory cannot be allocated that is of arbitrary length (AFAICT from the Solidity documentation). I also note the code below is simplified - it won't be just half, it will be some fraction set as another parameter to logit.

I'm not completely sure but when I look at the compiled output of Solidity it seems to me I could implement this directly in EVM assembly. the EVM instruction log1 takes a pointer and length of data as its input arguments, so it should be possible to do. The code below works, so I know that somehow calldata of arbitrary length can be sent to log1. Solidity just won't let me do the pointer arithmetic to split the data up (or I can't figure out how). I'd rather not have to code assembly... (I can switch to Serpent if necessary).

Yes, I could make separate functions and split the data outside of Ethereum. but then I incur the 21,000 gas penalty for each call. I'm trying to do this for low gas cost.

[EDIT] - yes I could use up to 7 function parameters (at most), but that only gives me 7 splits. I need on the order of 64. this is what I get for trying to simplify the question too much...

// 
// attempt to figure out how to split incoming byte data into separate logs
//

contract HelpLogs {

  event LogFirstHalf(bytes _data);
  event LogSecondHalf(bytes _data);

  function logit(bytes data) external {
    // can't do the pointer arithmetic to LogFirstHalf and
    // then LogSecondHalf of data.  But I can log all of it...
    LogFirstHalf(data);
  }
}
2
  • 1
    Tangentially: do not use Serpent, it is deprecated -- ethereum.stackexchange.com/questions/21375/why-did-serpent-die
    – carver
    Aug 16, 2017 at 17:35
  • BTW looked at generically, this turns into a more general question of how does one deal with arbitrary length incoming data without having to use storage to split up the data in arbitrary ways.
    – Paul S
    Aug 16, 2017 at 18:35

1 Answer 1

2

Arbitrary Split approach

Edit: Added this section to address arbitrary split destinations

By the time you are splitting, you should know the length of the target data. Below is an example implementation of copying the bytes to their destination inside solidity, which should be trivially extendable to N buckets.

pragma solidity ^0.4.15;

contract HelpLogs {

  event LogFirstHalf(bytes _data);
  event LogSecondHalf(bytes _data);

  function logit(bytes data) external {
      uint midpoint = data.length / 2;
      bytes memory data1 = new bytes(midpoint);
      for (uint i = 0; i < midpoint; i++) {
          data1[i] = data[i];
      }
      bytes memory data2 = new bytes(data.length - midpoint);
      for (i = 0; i < data.length - midpoint; i++) {
          data2[i] = data[i + midpoint];
      }
      LogFirstHalf(data1);
      LogSecondHalf(data2);
  }
}

Note that the gas usage is higher than it needs to be, because it works byte-by-byte. It would be faster to use 32-byte words, with bitmasking. A good reference is memcpy from Arachnid's solidity-string utils library.


[Edit: old] Fixed Bucket Approach

You can split the data externally without the 21 kgas overhead. Send the pre-split data as two parameters to a single function call:

pragma solidity ^0.4.15;

contract HelpLogs {

  event LogFirstHalf(bytes _data);
  event LogSecondHalf(bytes _data);

  function logit(bytes dataPart1, bytes dataPart2) external {
    LogFirstHalf(dataPart1);
    LogSecondHalf(dataPart2);
  }
}

This will cost less gas than splitting inside the EVM.

9
  • sorry, I should have anticipated this answer. I'll rephrase the question as 'arbitrary number of splits'. The method you propose only works out to (I believe) 8 splits. Thanks though!
    – Paul S
    Aug 16, 2017 at 18:32
  • 1
    I tested it. Solidity complains about 'stack too deep' at 8 arguments, so max is 7. I also need to have some sort of info block sent along so it'll more likely be 6.
    – Paul S
    Aug 16, 2017 at 19:05
  • thanks! That might just do the trick. My data is definitely on 32-byte boundaries, so thanks to the reference to memcpy. I'll check this when I have working code.
    – Paul S
    Aug 16, 2017 at 20:06
  • 1
    I changed the types to bytes32[] instead of bytes since my data is really 32 bytes anyways (and that's what log1 takes as well) and the cost of logging each additional 32 byte entry is 2620 gas. The original cost for logging it all to one index was 2450 gas. So net cost of the copy is 170 gas. I'm calling this good. Thanks!
    – Paul S
    Aug 16, 2017 at 20:37
  • 1
    Yeah, unfortunately the nodes have to keep all the transactions around to bootstrap other nodes, so it comes with a pretty high public cost.
    – carver
    Aug 16, 2017 at 21:08

Your Answer

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

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