1

EIP-1014 describes salt as bytes32. However, the function create2 written in assembly allows passing byte string of arbitrary size as the salt parameter.

Example

function deploy(bytes memory bytecode, bytes memory _salt) public payable {
    address addr;

    /*
    NOTE: How to call create2

    create2(v, p, n, s)
    create new contract with code at memory p to p + n
    and send v wei
    and return the new address
    where new address = first 20 bytes of keccak256(0xff + address(this) + s + keccak256(mem[p…(p+n)))
          s = big-endian 256-bit value
    */
    assembly {
        addr := create2(
            callvalue(), // wei sent with current call
            // Actual code starts after skipping the first 32 bytes
            add(bytecode, 0x20),
            mload(bytecode), // Load the size of code contained in the first 32 bytes
            _salt // Salt from function arguments
        )

        if iszero(extcodesize(addr)) {
            revert(0, 0)
        }
    }

    emit Deployed(addr, _salt);
}

How to precompute deployed contract address in that case?

This function works for bytes32 salt, but gives incorrect result here.

function getAddress(
    bytes memory bytecode,
    bytes memory _salt
) public view returns (address) {
    bytes32 hash = keccak256(
        abi.encodePacked(bytes1(0xff), address(this), _salt, keccak256(bytecode))
    );

    // NOTE: cast last 20 bytes of hash to address
    return address(uint160(uint(hash)));
}

What should I change?

1 Answer 1

0

In reality, the salt that create2 OPCODE accepts is supposed to be a uint256 value:

create new contract with code mem[p…(p+n)) at address keccak256(0xff . this . s . keccak256(mem[p…(p+n))) and send v wei and return the new address, where 0xff is a 1 byte value, this is the current contract’s address as a 20 byte value and s is a big-endian 256-bit value; returns 0 on error (https://docs.soliditylang.org/en/latest/yul.html)


However, if for some particular reason you want to accept this salt value in a dynamically-sized bytes format, you can use the following code snippet that I prepared:

pragma solidity ^0.8.13;

contract Account {
    string public description;

    constructor() {
        description = "";
    }
}

contract Test {
    event Deployed(address /*indexed*/ outcomeAddress, uint256 /*indexed*/ salt);

    function deploy(bytes memory bytecode, bytes memory saltBytes) internal returns (address) {
        address addr;

        uint256 salt;

        assembly {
            salt := mload(add(saltBytes, add(0x20, 0))) // load saltInt from the saltBytes, it's not just explicitly referencing saltBytes
            // in the create2 call, but really loading the first 32 bytes
            // Loading the first 32 bytes because a uint value can never be more than 256 bits
            // ==> meaning 32 bytes IS PLENTY
        }

        assembly {
            addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt)

            if iszero(extcodesize(addr)) {
                revert(0, 0)
            }
        }

        emit Deployed(addr, salt);

        return addr;
    }

    function getAddress(
        bytes memory bytecode,
        bytes memory salt
    ) internal view returns (address) {
        bytes32 hash = keccak256(
            abi.encodePacked(bytes1(0xff), address(this), salt, keccak256(bytecode))
        );

        // NOTE: cast last 20 bytes of hash to address
        return address(uint160(uint(hash)));
    }

    function performTestDeploy(uint256 saltInt) external returns (address deploymentAddress) {
        bytes memory saltBytes = toBytes(saltInt);

        bytes memory byteCode = abi.encodePacked(type(Account).creationCode);

        return deploy(byteCode, saltBytes);

    }

    function performTestGetAndCompare(uint256 saltInt) external view returns (address create2GeneratedAddress) {
        bytes memory bytesSalt = toBytes(saltInt);

        return getAddress(abi.encodePacked(type(Account).creationCode), bytesSalt);
    }

    function toBytes(uint256 x) internal pure returns (bytes memory) {
        return abi.encodePacked(x); // convert (uint --> bytes) for testing
    }
}

... Which gives the following result:

result-create2-compute-precompute-address-bytes-dynamic-1


Some comments:

  • Loading the first 32 bytes is plenty, because a UINT value can never be > 256 bits (€ 32 bytes)

Hopefully, that answers your question!!

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.