-
Notifications
You must be signed in to change notification settings - Fork 2.4k
Labels
Description
Component
Forge
Have you ensured that all of these are up to date?
- Foundry
- Foundryup
What version of Foundry are you on?
forge --version forge Version: 1.5.1-stable Commit SHA: b0a9dd9 Build Timestamp: 2025-12-22T11:39:01.425730780Z (1766403541) Build Profile: maxperf
What version of Foundryup are you on?
foundryup: 1.5.0
What command(s) is the bug in?
forge test --match-path test/RuntimeLib.t.sol -vvvv
Operating System
Linux
Describe the bug
Reproduce:
- Generate deployed bytecode.
- Deploy the raw bytecode.
deployed.call{value: 0}("");. Andreceiveis invoked.
Expect: trace is receive
Actual: trace is fallback
msg.data is empty, so the correct trace is receive.
$ forge test --match-path test/RuntimeLib.t.sol -vvvv
[⠊] Compiling...
No files changed, compilation skipped
Ran 1 test for test/RuntimeLib.t.sol:RuntimeLibTest
[PASS] testDeployRawBytecode() (gas: 186959)
Traces:
[186959] RuntimeLibTest::testDeployRawBytecode()
├─ [147963] → new <unknown>@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
│ └─ ← [Return] 739 bytes of code
├─ [3296] 0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::fallback()
│ ├─ emit Log(: "receive", : RuntimeLibTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], : 0, : 0x)
│ └─ ← [Stop]
└─ ← [Stop]
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 243.92µs (77.35µs CPU time)
0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f::fallback() vs emit Log(: "receive"
// Runtime.t.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
import "forge-std/Test.sol";
import "./RuntimeLib.sol";
/*
contract Router {
event Log(string func, address sender, uint value, bytes data);
receive() external payable {
emit Log("receive", msg.sender, msg.value, "");
}
fallback() external payable {
emit Log("fallback", msg.sender, msg.value, msg.data);
}
}
*/
contract RuntimeLibTest is Test {
function testDeployRawBytecode() public {
// Raw runtime bytecode of the Router contract
bytes memory code = hex"608060405236610044577ff7f75251dee7d7fc22deac3247729ebe7c86541f35930bf10c2a4207479a3b6c333460405161003a929190610172565b60405180910390a1005b7ff7f75251dee7d7fc22deac3247729ebe7c86541f35930bf10c2a4207479a3b6c333460003660405161007a949392919061025a565b60405180910390a1005b600082825260208201905092915050565b7f7265636569766500000000000000000000000000000000000000000000000000600082015250565b60006100cb600783610084565b91506100d682610095565b602082019050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600061010c826100e1565b9050919050565b61011c81610101565b82525050565b6000819050919050565b61013581610122565b82525050565b600082825260208201905092915050565b50565b600061015c60008361013b565b91506101678261014c565b600082019050919050565b6000608082019050818103600083015261018b816100be565b905061019a6020830185610113565b6101a7604083018461012c565b81810360608301526101b88161014f565b90509392505050565b7f66616c6c6261636b000000000000000000000000000000000000000000000000600082015250565b60006101f7600883610084565b9150610202826101c1565b602082019050919050565b82818337600083830152505050565b6000601f19601f8301169050919050565b6000610239838561013b565b935061024683858461020d565b61024f8361021c565b840190509392505050565b60006080820190508181036000830152610273816101ea565b90506102826020830187610113565b61028f604083018661012c565b81810360608301526102a281848661022d565b90509594505050505056fea2646970667358221220749a95417d16869c0020868fb950c3602810c6148809abb4489b45f51181536964736f6c63430008130033";
// Deploy using RuntimeLib
address deployed = RuntimeLib.deploy(code);
// Assert deployment was successful
assertTrue(deployed != address(0), "Deployment failed, address is zero");
// Send 0 ETH and empty data
// This should trigger the receive() function (0x72656365697665... in hex)
(bool success, ) = deployed.call{value: 0}("");
assertTrue(success, "Call to deployed contract failed");
}
}Following is deploy helper.
// RuntimeLib.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.19;
import "./BytesLib.sol";
library RuntimeLib {
function deploy(bytes memory code) internal returns (address) {
// Prepends a minimal constructor to save some gas
bytes memory initcode = getInitCode(code);
address addr;
assembly {
addr := create(0, add(initcode, 0x20), mload(initcode))
}
require(addr != address(0), "failed to deploy contract");
return addr;
}
function getInitCode(
bytes memory code
) private pure returns (bytes memory) {
// PUSH size, offset, destoffset; CODECOPY; PUSH size, offset; RETURN;
bytes memory init = hex"610000600E6000396100006000F3";
// ---- ----
// Set the size of the runtime bytecode
uint256 len = code.length;
assembly {
let lowerByte := and(0xff, len)
let upperByte := shr(8, len)
mstore8(add(init, 0x21), upperByte)
mstore8(add(init, 0x22), lowerByte)
mstore8(add(init, 0x29), upperByte)
mstore8(add(init, 0x2A), lowerByte)
}
return BytesLib.concat(init, code);
}
}
// BytesLib.sol
// SPDX-License-Identifier: UNLICENSED
pragma solidity =0.8.19;
// Copied and unchanged from GNSPS/solidity-bytes-utils
// https://github.com/GNSPS/solidity-bytes-utils/blob/master/contracts/BytesLib.sol
library BytesLib {
function concat(
bytes memory _preBytes,
bytes memory _postBytes
) internal pure returns (bytes memory) {
bytes memory tempBytes;
assembly {
// Get a location of some free memory and store it in tempBytes as
// Solidity does for memory variables.
tempBytes := mload(0x40)
// Store the length of the first bytes array at the beginning of
// the memory for tempBytes.
let length := mload(_preBytes)
mstore(tempBytes, length)
// Maintain a memory counter for the current write location in the
// temp bytes array by adding the 32 bytes for the array length to
// the starting location.
let mc := add(tempBytes, 0x20)
// Stop copying when the memory counter reaches the length of the
// first bytes array.
let end := add(mc, length)
for {
// Initialize a copy counter to the start of the _preBytes data,
// 32 bytes into its memory.
let cc := add(_preBytes, 0x20)
} lt(mc, end) {
// Increase both counters by 32 bytes each iteration.
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
// Write the _preBytes data into the tempBytes memory 32 bytes
// at a time.
mstore(mc, mload(cc))
}
// Add the length of _postBytes to the current length of tempBytes
// and store it as the new length in the first 32 bytes of the
// tempBytes memory.
length := mload(_postBytes)
mstore(tempBytes, add(length, mload(tempBytes)))
// Move the memory counter back from a multiple of 0x20 to the
// actual end of the _preBytes data.
mc := end
// Stop copying when the memory counter reaches the new combined
// length of the arrays.
end := add(mc, length)
for {
let cc := add(_postBytes, 0x20)
} lt(mc, end) {
mc := add(mc, 0x20)
cc := add(cc, 0x20)
} {
mstore(mc, mload(cc))
}
// Update the free-memory pointer by padding our last write location
// to 32 bytes: add 31 bytes to the end of tempBytes to move to the
// next 32 byte block, then round down to the nearest multiple of
// 32. If the sum of the length of the two arrays is zero then add
// one before rounding down to leave a blank 32 bytes (the length block with 0).
mstore(
0x40,
and(
add(add(end, iszero(add(length, mload(_preBytes)))), 31),
not(31) // Round down to the nearest 32 bytes.
)
)
}
return tempBytes;
}
}Metadata
Metadata
Assignees
Labels
Type
Projects
Status
Done