Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
afcc51a4
Unverified
Commit
afcc51a4
authored
Sep 06, 2024
by
Matt Solomon
Committed by
GitHub
Sep 06, 2024
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
test: more blueprint tests (#11782)
* test: more blueprint tests * address PR feedback
parent
ccf9d3e2
Changes
2
Hide whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
228 additions
and
26 deletions
+228
-26
Blueprint.sol
packages/contracts-bedrock/src/libraries/Blueprint.sol
+11
-7
Blueprint.t.sol
packages/contracts-bedrock/test/libraries/Blueprint.t.sol
+217
-19
No files found.
packages/contracts-bedrock/src/libraries/Blueprint.sol
View file @
afcc51a4
...
...
@@ -28,7 +28,7 @@ library Blueprint {
/// @notice Thrown when parsing a blueprint preamble and the preamble data is not empty.
/// We do not use the preamble data, so it's expected to be empty.
error UnexpectedPreambleData();
error UnexpectedPreambleData(
bytes data
);
/// @notice Thrown during deployment if the ERC version is not supported.
error UnsupportedERCVersion(uint8 version);
...
...
@@ -37,6 +37,9 @@ library Blueprint {
/// which will deploy a corresponding blueprint contract (with no data section). Based on the
/// reference implementation in https://eips.ethereum.org/EIPS/eip-5202.
function blueprintDeployerBytecode(bytes memory _initcode) internal pure returns (bytes memory) {
// Check that the initcode is not empty.
if (_initcode.length == 0) revert EmptyInitcode();
bytes memory blueprintPreamble = hex"FE7100"; // ERC-5202 preamble.
bytes memory blueprintBytecode = bytes.concat(blueprintPreamble, _initcode);
...
...
@@ -89,12 +92,18 @@ library Blueprint {
return Preamble(ercVersion, preambleData, initcode);
}
/// @notice Parses the code at the given `_target` as a blueprint and deploys the resulting initcode.
/// This version of `deployFrom` is used when the initcode requires no constructor arguments.
function deployFrom(address _target, bytes32 _salt) internal returns (address) {
return deployFrom(_target, _salt, new bytes(0));
}
/// @notice Parses the code at the given `_target` as a blueprint and deploys the resulting initcode
/// with the given `_data` appended, i.e. `_data` is the ABI-encoded constructor arguments.
function deployFrom(address _target, bytes32 _salt, bytes memory _data) internal returns (address newContract_) {
Preamble memory preamble = parseBlueprintPreamble(address(_target).code);
if (preamble.ercVersion != 0) revert UnsupportedERCVersion(preamble.ercVersion);
if (preamble.preambleData.length != 0) revert UnexpectedPreambleData();
if (preamble.preambleData.length != 0) revert UnexpectedPreambleData(
preamble.preambleData
);
bytes memory initcode = bytes.concat(preamble.initcode, _data);
assembly ("memory-safe") {
...
...
@@ -103,11 +112,6 @@ library Blueprint {
if (newContract_ == address(0)) revert DeploymentFailed();
}
/// @notice Parses the code at the given `_target` as a blueprint and deploys the resulting initcode.
function deployFrom(address _target, bytes32 _salt) internal returns (address) {
return deployFrom(_target, _salt, new bytes(0));
}
/// @notice Convert a bytes array to a uint256.
function bytesToUint(bytes memory _b) internal pure returns (uint256) {
if (_b.length > 32) revert BytesArrayTooLong();
...
...
packages/contracts-bedrock/test/libraries/Blueprint.t.sol
View file @
afcc51a4
...
...
@@ -4,43 +4,241 @@ pragma solidity 0.8.15;
import { Test } from "forge-std/Test.sol";
import { Blueprint } from "src/libraries/Blueprint.sol";
// Used to test that constructor args are appended properly when deploying from a blueprint.
contract ConstructorArgMock {
uint256 public x;
bytes public y;
constructor(uint256 _x, bytes memory _y) {
x = _x;
y = _y;
}
}
// Foundry cheatcodes operate on the next call, and since all library methods are internal we would
// just JUMP to them if called directly in the test. Therefore we wrap the library in a contract.
contract BlueprintHarness {
function blueprintDeployerBytecode(bytes memory _initcode) public pure returns (bytes memory) {
return Blueprint.blueprintDeployerBytecode(_initcode);
}
function parseBlueprintPreamble(bytes memory _bytecode) public pure returns (Blueprint.Preamble memory) {
return Blueprint.parseBlueprintPreamble(_bytecode);
}
function deployFrom(address _blueprint, bytes32 _salt) public returns (address) {
return Blueprint.deployFrom(_blueprint, _salt);
}
function deployFrom(address _blueprint, bytes32 _salt, bytes memory _args) public returns (address) {
return Blueprint.deployFrom(_blueprint, _salt, _args);
}
function bytesToUint(bytes memory _bytes) public pure returns (uint256) {
return Blueprint.bytesToUint(_bytes);
}
}
contract Blueprint_Test is Test {
// TODO add tests that things revert if an address has no code.
BlueprintHarness blueprint;
function setUp() public {
blueprint = new BlueprintHarness();
}
function deployWithCreate2(bytes memory _initcode, bytes32 _salt) public returns (address addr_) {
assembly ("memory-safe") {
addr_ := create2(0, add(_initcode, 0x20), mload(_initcode), _salt)
}
require(addr_ != address(0), "deployWithCreate2: deployment failed");
}
// --- We start with the test cases from ERC-5202 ---
// An example (and trivial!) blueprint contract with no data section, whose initcode is just the STOP instruction.
function test_ERC5202_trivialBlueprint_succeeds() public view {
bytes memory bytecode = hex"FE710000";
Blueprint.Preamble memory preamble = blueprint.parseBlueprintPreamble(bytecode);
assertEq(preamble.ercVersion, 0, "100");
assertEq(preamble.preambleData, hex"", "200");
assertEq(preamble.initcode, hex"00", "300");
}
// An example blueprint contract whose initcode is the trivial STOP instruction and whose data
// section contains the byte 0xFF repeated seven times.
function test_ERC5202_blueprintWithDataSection_succeeds() public view {
// Here, 0xFE71 is the magic header, 0x01 means version 0 + 1 length bit, 0x07 encodes the
// length in bytes of the data section. These are followed by the data section, and then the
// initcode. For illustration, this code with delimiters would be:
// 0xFE71|01|07|FFFFFFFFFFFFFF|00
bytes memory bytecode = hex"FE710107FFFFFFFFFFFFFF00";
Blueprint.Preamble memory preamble = blueprint.parseBlueprintPreamble(bytecode);
assertEq(preamble.ercVersion, 0, "100");
assertEq(preamble.preambleData, hex"FFFFFFFFFFFFFF", "200");
assertEq(preamble.initcode, hex"00", "300");
}
// An example blueprint whose initcode is the trivial STOP instruction and whose data section
// contains the byte 0xFF repeated 256 times.
function test_ERC5202_blueprintWithLargeDataSection_succeeds() public view {
// Delimited, this would be 0xFE71|02|0100|FF...FF|00
bytes memory bytecode =
hex"FE71020100FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF00";
Blueprint.Preamble memory preamble = blueprint.parseBlueprintPreamble(bytecode);
assertEq(preamble.ercVersion, 0, "100");
assertEq(preamble.preambleData.length, 256, "200");
for (uint256 i = 0; i < 256; i++) {
assertEq(preamble.preambleData[i], bytes1(0xFF), string.concat("300-", vm.toString(i)));
}
assertEq(preamble.initcode, hex"00", "400");
}
function test_roundtrip_succeeds(bytes memory _initcode) public {
// --- Now we add a generic roundtrip test ---
// Test that a roundtrip from initcode to blueprint to initcode succeeds, i.e. the invariant
// here is that `parseBlueprintPreamble(blueprintDeployerBytecode(x)) = x`.
function testFuzz_roundtrip_succeeds(bytes memory _initcode) public {
vm.assume(_initcode.length > 0);
// Convert the initcode to match the ERC-5202 blueprint format.
bytes memory blueprintInitcode =
B
lueprint.blueprintDeployerBytecode(_initcode);
bytes memory blueprintInitcode =
b
lueprint.blueprintDeployerBytecode(_initcode);
// Deploy the blueprint.
address blueprintAddress;
assembly ("memory-safe") {
blueprintAddress := create2(0, add(blueprintInitcode, 0x20), mload(blueprintInitcode), 0)
}
require(blueprintAddress != address(0), "DeployImplementations: create2 failed");
address blueprintAddress = deployWithCreate2(blueprintInitcode, bytes32(0));
// Read the blueprint code from the deployed code.
bytes memory blueprintCode = address(blueprintAddress).code;
// Parse the blueprint preamble.
Blueprint.Preamble memory preamble =
B
lueprint.parseBlueprintPreamble(blueprintCode);
// Parse the blueprint preamble
and ensure it matches the expected values
.
Blueprint.Preamble memory preamble =
b
lueprint.parseBlueprintPreamble(blueprintCode);
assertEq(preamble.ercVersion, 0, "100");
assertEq(preamble.preambleData, hex"", "200");
assertEq(preamble.initcode, _initcode, "300");
}
function test_bytesToUint_succeeds() public pure {
// --- Lastly, function-specific unit tests ---
function test_blueprintDeployerBytecode_emptyInitcode_reverts() public {
bytes memory initcode = "";
vm.expectRevert(Blueprint.EmptyInitcode.selector);
blueprint.blueprintDeployerBytecode(initcode);
}
function test_parseBlueprintPreamble_notABlueprint_reverts() public {
// Length too short.
bytes memory invalidBytecode = hex"01";
vm.expectRevert(Blueprint.NotABlueprint.selector);
blueprint.parseBlueprintPreamble(invalidBytecode);
// First byte is not 0xFE.
invalidBytecode = hex"0071";
vm.expectRevert(Blueprint.NotABlueprint.selector);
blueprint.parseBlueprintPreamble(invalidBytecode);
// Second byte is not 0x71.
invalidBytecode = hex"FE00";
vm.expectRevert(Blueprint.NotABlueprint.selector);
blueprint.parseBlueprintPreamble(invalidBytecode);
}
function test_parseBlueprintPreamble_reservedBitsSet_reverts() public {
bytes memory invalidBytecode = hex"FE7103";
vm.expectRevert(Blueprint.ReservedBitsSet.selector);
blueprint.parseBlueprintPreamble(invalidBytecode);
}
function test_parseBlueprintPreamble_emptyInitcode_reverts() public {
bytes memory invalidBytecode = hex"FE7100";
vm.expectRevert(Blueprint.EmptyInitcode.selector);
blueprint.parseBlueprintPreamble(invalidBytecode);
}
function testFuzz_deployFrom_succeeds(bytes memory _initcode, bytes32 _salt) public {
vm.assume(_initcode.length > 0);
vm.assume(_initcode[0] != 0xef); // https://eips.ethereum.org/EIPS/eip-3541
// This deployBytecode prefix is the same bytecode used in `blueprintDeployerBytecode`, and
// it ensures that whatever initcode the fuzzer generates is actually deployable.
bytes memory deployBytecode = bytes.concat(hex"61", bytes2(uint16(_initcode.length)), hex"3d81600a3d39f3");
bytes memory initcode = bytes.concat(deployBytecode, _initcode);
bytes memory blueprintInitcode = blueprint.blueprintDeployerBytecode(initcode);
// Deploy the blueprint.
address blueprintAddress = deployWithCreate2(blueprintInitcode, _salt);
// Deploy from the blueprint.
address deployedContract = Blueprint.deployFrom(blueprintAddress, _salt);
// Verify the deployment worked.
assertTrue(deployedContract != address(0), "100");
assertTrue(deployedContract.code.length > 0, "200");
assertEq(keccak256(deployedContract.code), keccak256(_initcode), "300");
}
// Here we deploy a simple mock contract to test that constructor args are appended properly.
function testFuzz_deployFrom_withConstructorArgs_succeeds(uint256 _x, bytes memory _y, bytes32 _salt) public {
bytes memory blueprintInitcode = blueprint.blueprintDeployerBytecode(type(ConstructorArgMock).creationCode);
// Deploy the blueprint.
address blueprintAddress = deployWithCreate2(blueprintInitcode, _salt);
// Deploy from the blueprint.
bytes memory args = abi.encode(_x, _y);
address deployedContract = blueprint.deployFrom(blueprintAddress, _salt, args);
// Verify the deployment worked.
assertTrue(deployedContract != address(0), "100");
assertTrue(deployedContract.code.length > 0, "200");
assertEq(keccak256(deployedContract.code), keccak256(type(ConstructorArgMock).runtimeCode), "300");
assertEq(ConstructorArgMock(deployedContract).x(), _x, "400");
assertEq(ConstructorArgMock(deployedContract).y(), _y, "500");
}
function test_deployFrom_unsupportedERCVersion_reverts() public {
bytes32 salt = bytes32(0);
address blueprintAddress = makeAddr("blueprint");
bytes memory invalidBlueprintCode = hex"FE710400"; // ercVersion = uint8(0x04 & 0xfc) >> 2 = 1
vm.etch(blueprintAddress, invalidBlueprintCode);
vm.expectRevert(abi.encodeWithSelector(Blueprint.UnsupportedERCVersion.selector, 1));
blueprint.deployFrom(blueprintAddress, salt);
invalidBlueprintCode = hex"FE71B000"; // ercVersion = uint8(0xB0 & 0xfc) >> 2 = 44
vm.etch(blueprintAddress, invalidBlueprintCode);
vm.expectRevert(abi.encodeWithSelector(Blueprint.UnsupportedERCVersion.selector, 44));
blueprint.deployFrom(blueprintAddress, salt);
}
function test_deployFrom_unexpectedPreambleData_reverts() public {
bytes32 salt = bytes32(0);
address blueprintAddress = makeAddr("blueprint");
// Create invalid blueprint code with non-empty preamble data
bytes memory invalidBlueprintCode = hex"FE7101030102030001020304";
vm.etch(blueprintAddress, invalidBlueprintCode);
// Expect revert with UnexpectedPreambleData error
vm.expectRevert(abi.encodeWithSelector(Blueprint.UnexpectedPreambleData.selector, hex"010203"));
blueprint.deployFrom(blueprintAddress, salt);
}
function test_bytesToUint_succeeds() public view {
// These test cases (and the logic for bytesToUint) are taken from forge-std.
assertEq(3,
B
lueprint.bytesToUint(hex"03"));
assertEq(2,
B
lueprint.bytesToUint(hex"02"));
assertEq(255,
B
lueprint.bytesToUint(hex"ff"));
assertEq(29625,
B
lueprint.bytesToUint(hex"73b9"));
assertEq(3,
b
lueprint.bytesToUint(hex"03"));
assertEq(2,
b
lueprint.bytesToUint(hex"02"));
assertEq(255,
b
lueprint.bytesToUint(hex"ff"));
assertEq(29625,
b
lueprint.bytesToUint(hex"73b9"));
// Additional test cases.
assertEq(0, Blueprint.bytesToUint(hex""));
assertEq(0, Blueprint.bytesToUint(hex"00"));
assertEq(14545064521499334880, Blueprint.bytesToUint(hex"c9da731e871ad8e0"));
assertEq(type(uint256).max, Blueprint.bytesToUint(bytes.concat(bytes32(type(uint256).max))));
assertEq(0, blueprint.bytesToUint(hex""));
assertEq(0, blueprint.bytesToUint(hex"00"));
assertEq(3, blueprint.bytesToUint(hex"0003"));
assertEq(3145731, blueprint.bytesToUint(hex"300003"));
assertEq(14545064521499334880, blueprint.bytesToUint(hex"c9da731e871ad8e0"));
assertEq(14545064521499334880, blueprint.bytesToUint(hex"00c9da731e871ad8e0"));
assertEq(type(uint256).max, blueprint.bytesToUint(bytes.concat(bytes32(type(uint256).max))));
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment