Files

203 lines
8.0 KiB
Solidity

// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.29;
import {Test} from "forge-std/Test.sol";
import {Magnetar} from "contracts/Magnetar.sol";
import {SafeMagnetarSigner} from "contracts/SafeMagnetarSigner.sol";
import {SafeMagnetarCoSigner} from "contracts/SafeMagnetarCoSigner.sol";
import {MagnetarAccount} from "contracts/MagnetarAccount.sol";
import {IERC1271} from "contracts/interfaces/IERC1271.sol";
import {PackedUserOperation} from "contracts/interfaces/IERC4337.sol";
import {MockMagnetarPrecompile} from "./Magnetar.t.sol";
/// @notice A minimal Safe stand-in exposing exactly the surface the
/// {SafeMagnetarCoSigner} guard reads (`nonce`, `getTransactionHash`).
contract MockSafe {
uint256 public nonce = 1;
bytes32 public immutable txHash;
constructor(bytes32 txHash_) {
txHash = txHash_;
}
function getTransactionHash(
address,
uint256,
bytes calldata,
uint8,
uint256,
uint256,
uint256,
address,
address,
uint256
) external view returns (bytes32) {
return txHash;
}
}
contract E2ETest is Test {
uint8 internal mode;
bytes internal pubkey;
bytes32 internal msgHash;
bytes internal sig;
bytes internal expectedInput;
address internal constant ENTRY_POINT = address(0xEE);
function setUp() public {
string memory json = vm.readFile(string.concat(vm.projectRoot(), "/tests/kat.json"));
mode = uint8(vm.parseJsonUint(json, ".mode"));
pubkey = vm.parseJsonBytes(json, ".pubkey");
msgHash = bytes32(vm.parseJsonBytes(json, ".msgHash"));
sig = vm.parseJsonBytes(json, ".sig");
expectedInput = vm.parseJsonBytes(json, ".input");
vm.etch(Magnetar.PRECOMPILE, type(MockMagnetarPrecompile).runtimeCode);
vm.store(Magnetar.PRECOMPILE, bytes32(uint256(0)), keccak256(expectedInput));
}
function _payload() internal view returns (bytes memory) {
return abi.encode(pubkey, sig);
}
// --- SafeMagnetarSigner: EIP-1271 owner ----------------------------------
function test_Signer_ReturnsMagicValueForValidSignature() external {
SafeMagnetarSigner signer = new SafeMagnetarSigner(mode, pubkey);
bytes4 magic = signer.isValidSignature(msgHash, _payload());
assertEq(magic, IERC1271.isValidSignature.selector, "valid Magnetar sig must yield ERC-1271 magic value");
}
function test_Signer_LegacyERC1271FailsClosedForWrongHash() external {
SafeMagnetarSigner signer = new SafeMagnetarSigner(mode, pubkey);
bytes memory message = "legacy-erc1271-message";
bytes4 magic = signer.isValidSignature(message, _payload());
assertEq(magic, bytes4(0), "legacy path must fail-closed for a non-matching message hash");
}
function test_Signer_RejectsWrongKeyBinding() external {
SafeMagnetarSigner signer = new SafeMagnetarSigner(mode, pubkey);
bytes memory otherPk = bytes.concat(pubkey);
otherPk[0] ^= 0xFF;
bytes4 magic = signer.isValidSignature(msgHash, abi.encode(otherPk, sig));
assertEq(magic, bytes4(0), "wrong key must not yield magic value");
}
function test_Signer_RejectsTamperedSignature() external {
SafeMagnetarSigner signer = new SafeMagnetarSigner(mode, pubkey);
bytes memory bad = bytes.concat(sig);
bad[200] ^= 0xFF;
bytes4 magic = signer.isValidSignature(msgHash, abi.encode(pubkey, bad));
assertEq(magic, bytes4(0), "tampered sig must not yield magic value");
}
function test_Signer_ConstructorRejectsBadMode() external {
vm.expectRevert(SafeMagnetarSigner.InvalidMode.selector);
new SafeMagnetarSigner(0x99, pubkey);
}
function test_Signer_ConstructorRejectsWrongPubkeyLength() external {
vm.expectRevert(SafeMagnetarSigner.InvalidPublicKey.selector);
new SafeMagnetarSigner(mode, new bytes(pubkey.length - 1));
}
// --- SafeMagnetarCoSigner: transaction guard -----------------------------
function test_CoSigner_AcceptsCoSignedTransaction() external {
SafeMagnetarCoSigner coSigner = new SafeMagnetarCoSigner(mode, pubkey);
MockSafe safe = new MockSafe(msgHash);
bytes memory leading = abi.encodePacked(uint256(uint160(address(this))), uint256(0), uint8(1));
bytes memory signatures = bytes.concat(leading, _payload());
vm.prank(address(safe));
coSigner.checkTransaction(
address(safe), 0, "", 0, 0, 0, 0, address(0), payable(address(0)), signatures, address(this)
);
assertEq(coSigner.coSignatureLength(), _payload().length, "coSignatureLength must equal tuple length");
}
function test_CoSigner_RevertsWithoutCoSignature() external {
SafeMagnetarCoSigner coSigner = new SafeMagnetarCoSigner(mode, pubkey);
MockSafe safe = new MockSafe(msgHash);
bytes memory bad = bytes.concat(sig);
bad[300] ^= 0xFF;
bytes memory signatures = abi.encode(pubkey, bad);
vm.prank(address(safe));
vm.expectRevert(SafeMagnetarCoSigner.Unauthorized.selector);
coSigner.checkTransaction(
address(safe), 0, "", 0, 0, 0, 0, address(0), payable(address(0)), signatures, address(this)
);
}
function test_CoSigner_RevertsOnShortSignatures() external {
SafeMagnetarCoSigner coSigner = new SafeMagnetarCoSigner(mode, pubkey);
MockSafe safe = new MockSafe(msgHash);
vm.prank(address(safe));
vm.expectRevert(SafeMagnetarCoSigner.Unauthorized.selector);
coSigner.checkTransaction(
address(safe), 0, "", 0, 0, 0, 0, address(0), payable(address(0)), hex"deadbeef", address(this)
);
}
// --- MagnetarAccount: ERC-4337 -------------------------------------------
function test_Account_ValidatesUserOp() external {
MagnetarAccount account = new MagnetarAccount(ENTRY_POINT, mode, pubkey);
PackedUserOperation memory userOp = _emptyUserOp();
userOp.signature = _payload();
vm.prank(ENTRY_POINT);
uint256 validationData = account.validateUserOp(userOp, msgHash, 0);
assertEq(validationData, 0, "valid Magnetar user op must validate (0)");
}
function test_Account_RejectsTamperedUserOp() external {
MagnetarAccount account = new MagnetarAccount(ENTRY_POINT, mode, pubkey);
PackedUserOperation memory userOp = _emptyUserOp();
bytes memory bad = bytes.concat(sig);
bad[400] ^= 0xFF;
userOp.signature = abi.encode(pubkey, bad);
vm.prank(ENTRY_POINT);
uint256 validationData = account.validateUserOp(userOp, msgHash, 0);
assertEq(validationData, 1, "tampered Magnetar user op must fail validation (1)");
}
function test_Account_RejectsWrongKeyBinding() external {
MagnetarAccount account = new MagnetarAccount(ENTRY_POINT, mode, pubkey);
PackedUserOperation memory userOp = _emptyUserOp();
bytes memory otherPk = bytes.concat(pubkey);
otherPk[1] ^= 0xFF;
userOp.signature = abi.encode(otherPk, sig);
vm.prank(ENTRY_POINT);
uint256 validationData = account.validateUserOp(userOp, msgHash, 0);
assertEq(validationData, 1, "wrong key user op must fail validation (1)");
}
function test_Account_OnlyEntryPoint() external {
MagnetarAccount account = new MagnetarAccount(ENTRY_POINT, mode, pubkey);
PackedUserOperation memory userOp = _emptyUserOp();
userOp.signature = _payload();
vm.expectRevert(MagnetarAccount.UnsupportedEntryPoint.selector);
account.validateUserOp(userOp, msgHash, 0);
}
function _emptyUserOp() internal pure returns (PackedUserOperation memory userOp) {
userOp = PackedUserOperation({
sender: address(0),
nonce: 0,
initCode: "",
callData: "",
accountGasLimits: bytes32(0),
preVerificationGas: 0,
gasFees: bytes32(0),
paymasterAndData: "",
signature: ""
});
}
}