Solidity Log Events – How to Split Calldata Bytes to Multiple Log Events?

assemblyevmserpentsolidity

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);
  }
}

Best Answer

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.

Related Topic