1

Using assembly only, how do I switch, for A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48, the last 20 bytes of the second 32-bytes from the bytes variable below, and keep the rest as it is?

  • Original bytes:

0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000037cb1a23e763d2f975bff3b2b86cfa901f7b517e0000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d2bd5d716ee38d00000000000000000000000000000000000000000000000000c0e05cf2a0fe6f

  • Second 32-bytes:

0x00000000000000000000000037cb1a23e763d2f975bff3b2b86cfa901f7b517e

  • Last 20 bytes to switch for A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48:

37cb1a23e763d2f975bff3b2b86cfa901f7b517e

  • Final bytes:

0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB480000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d2bd5d716ee38d00000000000000000000000000000000000000000000000000c0e05cf2a0fe6f

Thanks!

1 Answer 1

2

This should work for you:

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

import "hardhat/console.sol";

contract Manipulation {
    // 0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000037cb1a23e763d2f975bff3b2b86cfa901f7b517e0000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d2bd5d716ee38d00000000000000000000000000000000000000000000000000c0e05cf2a0fe6f, 0x000000000000000000000000A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    function test_manipulateBytes(bytes memory originalBytes, bytes32 substringBytes) public view returns (bytes20, bytes memory) {
        bytes20 chunk;
        assembly {
            chunk := shl(mul(12, 8), substringBytes) // get the last 20 bytes of substringBytes (shift by (32 - 20 = 12) * 8 bits)
            mstore(add(originalBytes, 76), chunk) // store the corresponding slice of substringBytes at the position of originalBytes
        }
        
        return (chunk, originalBytes);
    }
}

In my Remix IDE it returns 0x0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d2bd5d716ee38d00000000000000000000000000000000000000000000000000c0e05cf2a0fe6f as originalBytes. Which is equal to the output you specified as desired in your question.

The simplified steps are the following:

  • Get the last 20 bytes of the bytes32 substring input (transform 0x00000000000000000000000037cb1a23e763d2f975bff3b2b86cfa901f7b517e ---> 0x000000000000000000000000A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48)
  • Store the 20-bytes chunk as chunk
  • Load the chunk at the correct bytes position at originalBytes

P.S. It's pure assembly, except for the bytes20 chunk declaration.


Update:

I noticed that there's a 22 bytes shift after the index 96 in the outputBytes array, though the preceding characters and the ones starting at 96+22 remain original.

I identified the problem only when I decided to test a more populated bytes value with some characters set at index 97.

So here's a fixed version of this Yul code that doesn't mask the original bytes in the 96-22 range (version №2):

// SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.8.13;

contract Manipulation {
    // To test whether other characters are overwriten due to padding: 0x111111111111111111111111111111111111111111111111111111111111114111111111111111111111111137cb1a23e763d2f975bff3b2b86cfa911f7b517e1111111111111111111111111111111111111111111111156bc75e2d631111111111111111111111111111111111111111111111111111111111111111111141111111111111111111111111111111111111111111111111111111111111111211111111111111111111111111111111111111111111111111d2bd5d716ee38d11111111111111111111111111111111111111111111111111c1e15cf2a1fe6f, 0x000000000000000000000000A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    // Normal plain test: 0x000000000000000000000000000000000000000000000000000000000000004000000000000000000000000037cb1a23e763d2f975bff3b2b86cfa901f7b517e0000000000000000000000000000000000000000000000056bc75e2d631000000000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000d2bd5d716ee38d00000000000000000000000000000000000000000000000000c0e05cf2a0fe6f, 0x000000000000000000000000A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48
    function test_manipulateBytes(bytes memory originalBytes, bytes32 substringBytes) public pure returns (/*bytes20, */bytes memory) {
        bytes20 chunk;
        // bytes memory outputBytes = new bytes(originalBytes.length);


        assembly {
            chunk := shl(mul(12, 8), substringBytes)
            mstore(add(originalBytes, 76), or(and(mload(add(originalBytes, 76)), not(shl(96, 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF))), chunk))    
        }

        return (/*chunk, */originalBytes);
    }
}

With the version №1, if you call the test_manipulateBytes(bytes memory originalBytes, bytes32 substringBytes) function with this bytes value: 0x111111111111111111111111111111111111111111111111111111111111114111111111111111111111111137cb1a23e763d2f975bff3b2b86cfa911f7b517e1111111111111111111111111111111111111111111111156bc75e2d631111111111111111111111111111111111111111111111111111111111111111111141111111111111111111111111111111111111111111111111111111111111111211111111111111111111111111111111111111111111111111d2bd5d716ee38d11111111111111111111111111111111111111111111111111c1e15cf2a1fe6f, you'll see that the output is not 0x1111111111111111111111111111111111111111111111111111111111111141111111111111111111111111a0b86991c6218b36c1d19d4a2e9eb0ce3606eb481111111111111111111111111111111111111111111111156bc75e2d631111111111111111111111111111111111111111111111111111111111111111111141111111111111111111111111111111111111111111111111111111111111111211111111111111111111111111111111111111111111111111d2bd5d716ee38d11111111111111111111111111111111111111111111111111c1e15cf2a1fe6f, but 0x1111111111111111111111111111111111111111111111111111111111111141111111111111111111111111a0b86991c6218b36c1d19d4a2e9eb0ce3606eb480000000000000000000000001111111111111111111111156bc75e2d631111111111111111111111111111111111111111111111111111111111111111111141111111111111111111111111111111111111111111111111111111111111111211111111111111111111111111111111111111111111111111d2bd5d716ee38d11111111111111111111111111111111111111111111111111c1e15cf2a1fe6f.

So there will be unexpected 000s (zeros).

To fix that, please use the version №2 code that I shared above, it addresses the problem by clearing the bytes range that we want to further replace, and thereby doesn't cause the zeroed padding issue.

Then the result will always be correct.


The function is pretty much generic and can be utilized universally if you allow the replaceOffset and replaceChunkSize arguments and do some slight updates 🙂.

3
  • 1
    Nice! Thanks a lot.
    – dNyrM
    Feb 15 at 15:17
  • 1
    I updated my answer with some fixes of the issues that I noticed recently. Please check it out :)
    – Mila A
    Feb 16 at 16:28
  • 1
    Awesome! Pretty educative as well. We need more Yul brainers here. Keep it coming.
    – dNyrM
    Feb 18 at 16:28

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.