1

I have a smart contract that calls a custom precompile smart contract in geth. My problem is that parameters after the 160th(0xA0) byte from the smart contract are not sent to the precompiled one. I am allocating 32 bytes for each parameter and passing it to the allocated space. This works fine for up to 5 params and afterwards they are are not sent. Sample Smart contract code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract SampleContract {
    function execute(
        bytes32 chainId,
        bytes32 tokenName,
        bytes32 tokenSymbol,
        bytes32 amount,
        bytes32 action
    ) public
        address prebuilt_addr = address(0x64);
        uint256[1] memory output;

        assembly {
            // define pointer
            let p := mload(0x40)

            mstore(add(p, 0x20), chainId) // 32
            mstore(add(p, 0x40), tokenName) // 64
            mstore(add(p, 0x60), tokenSymbol) // 96
            mstore(add(p, 0x80), amount) // 128
            mstore(add(p, 0xA0), action) // 160
            mstore(add(p, 0xC0), action) // 192 -->>> this is not sent to the precompile
            mstore(add(p, 0xE0), action) // 224 -->>> this is not sent to the precompile
            if iszero(call(not(0), prebuilt_addr, 0, p, 0xc0, output, 0x20)) {
                revert(0, 0)
            }
        }
    }
}

Sample precompile source code:

func (c *precompileTest) Run(in []byte) ([]byte, error) {
    in = removeNullBytes(in)
    fmt.Println(in)
    bytesString := string(in)
    fmt.Println(bytesString)
}

func removeNullBytes(input []byte) []byte {
    result := make([]byte, 0)

    for _, b := range input {
        if b != 0x00 {
            result = append(result, b)
        }
    }

    return result
}

This code results in:

"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00123457;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00SAMPLE_TOKEN;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00SAMPLE_SYMBOL;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x001000;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00SAMPLE_ACTION;\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"

and after removing the null bytes

"123457;SAMPLE_TOKEN;SAMPLE_SYMBOL;1000;SAMPLE_ACTION;"

but the two parameters from 0xC0 and 0xE0 are not passed. Expected was:

"123457;SAMPLE_TOKEN;SAMPLE_SYMBOL;1000;SAMPLE_ACTION;SAMPLE_ACTION;SAMPLE_ACTION;"

This is how I am calling the smart contract from truffle. Please note that I am duplicating the action param in the constructor just for testing purposes.

const ethers = require('ethers')
let s0 = ethers.encodeBytes32String("123457;");
let s1 = ethers.encodeBytes32String("SAMPLE_TOKEN;");
let s2 = ethers.encodeBytes32String("SAMPLE_SYMBOL;");
let s3 = ethers.encodeBytes32String("1000;");
let s4 = ethers.encodeBytes32String("SAMPLE_ACTION;");
let i = await SampleContract.deployed()
i.SampleContract(s0,s1,s2,s3,s4)

Thank you in advance !

1
  • A couple of issues with your code: 1/ the first 32 bytes from p are not initialized, 2/ the calls sends 0xc0 bytes, so the last two entries aren't sent at all. The fourth parameter is the address offset, and the fifth parameter is the size.
    – Ismael
    Sep 2, 2023 at 3:46

1 Answer 1

0

Ok, I found the problem or at least I think that is the problem. It is related to how Solidity handles dynamic data in storage. Solidity's bytes32 type is a fixed-size type, meaning it will pad the data with zeros to occupy the entire 32 bytes of storage space.

In my contract, I am using bytes32 to store each of the parameters, and am manually placing them in memory at specific offsets. In this case Solidity will try to pad my data to the full 32 bytes even if it is shorter and thus lead to problems or no place for the other data ? So I tried to use dynamic arrays instead of fixed bytes32 for the params and it is working.

Sample code:

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;

contract SampleContract {
    function execute(
        bytes[] memory params
    ) public {
        address prebuilt_addr = address(0x64);
        uint256[1] memory output;

        bytes memory input = abi.encodePacked(params[0], params[1], params[2], params[3], params[4], params[5], params[6);

        assembly {
            let p := add(input, 0x20)

            if iszero(call(not(0), prebuilt_addr, 0, p, mload(input), output, 0x20)) {
                revert(0, 0)
            }
        }
    }
}

and call them with truffle like this:

const ethers = require('ethers');

let params = [
    ethers.utils.formatBytes32String("123457;"),
    ethers.utils.formatBytes32String("SAMPLE_TOKEN;"),
    ethers.utils.formatBytes32String("SAMPLE_SYMBOL;"),
    ethers.utils.formatBytes32String("1000;"),
    ethers.utils.formatBytes32String("SAMPLE_ACTION;")
    ethers.utils.formatBytes32String("SAMPLE_ACTION1;")
    ethers.utils.formatBytes32String("SAMPLE_ACTION2;")
];

let i = await SampleContract.deployed();
await i.execute(params);

This time all the parameters are passed to the geth node and no cut off is occuring.

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.