-
Notifications
You must be signed in to change notification settings - Fork 1.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
bug: selfdestruct
has no effect in test whereas a user expects it would
#1543
Comments
|
Good to know! Is there any way to get around this limitation? Can you make a test depend on the state of a previous test? Also, is there a list of things that behave this way, so I know to look out for them in the future? (for anyone else who stumbles on this, I managed to learn this myself before @onbjerg's response, by putting the self destruct in the |
Not currently... Is there any specific reason you need to test that the contract was self-destructed by checking the code size? I realize this may be an odd question. I think there was another issue on here where OP asked for a cheatcode to perform the state transitions mid-test, but I think there would be too many side effects, and the technical difficulty of implementing it is pretty high for a small use case.
No, you can only use
There isn't, I'm not sure yet whether or not there should be - some of these oddities are mostly "odd" because most people really haven't had the need to know. This What do you think, does your use case warrant a cheatcode, or is there some other feature/adjustment you feel would accomodate your use case? |
It's not as odd a question as my use case 😅. I'm an auditor, and I like to test my exploits out with unit tests when possible. So in this case, I'm exploiting a contract and need to be able to selfdestruct it and assert things about the contracts that depend on the selfdestructed contract.
I was looking for this cheatcode in the docs and didn't find one, so that's why I created the issue in case I missed it. I understand this is a niche use case though. This is why I was asking about the list of things that behave this way, so I could gauge whether I should be asking for a cheatcode or if this is unique to selfdestruct (also so I know what to look out for in the future, as you could imagine, this was a head scratcher).
I don't know enough about the roadmap or how others are using foundry to say. I can give you my use case and I have faith that the foundry team will make the right decision for the long term success of the project 🙂. |
@onbjerg what if we add an inspector (or use existing inspector) and cheatcode and forge-std that:
|
Unsure, I still think it is a bit niche, especially considering that selfdestruct is a candidate for removal. But if we do add it, having a forge-std helper makes sense. |
Instead of committing state mid-execution, how about just clearing the code( |
We could, however that also breaks 1:1 EVM behavior which might not be desired. |
Maybe a cheatcode that proxies |
#2654 contradicts this issue - did this behavior change recently? |
No, it didn't. |
I am using This might be a bit overkill, since I'm technically testing compiler features, but delegate calling is a sensitive enough operation that warrants this kind of additional check. |
I'm running into something similar. I want to create a test that:
Im not sure if it's a foundry issue or general limitation of creating contracts that way pragma solidity ^0.8.13;
import "forge-std/Test.sol";
import {Clones} from "openzeppelin/proxy/Clones.sol";
contract ContractToClone {
address public owner;
function initialize(address _owner) external {
if (owner != address(0)) revert("Already initialied");
owner = _owner;
}
function goodbye() external {
selfdestruct(payable(owner));
}
}
/// @notice Meant to test out determistic cloning techniques
contract DetermistingCloningTest is Test {
function test_cloneCanBeCreatedTwiceUsingSameAddressIfDeleted() external {
ContractToClone cloneableContract = new ContractToClone();
address owner = vm.addr(1);
bytes32 salt = bytes32(uint256(5));
address clonedAddress = Clones.cloneDeterministic(address(cloneableContract), salt);
ContractToClone clonedContract = ContractToClone(clonedAddress);
clonedContract.initialize(owner);
assertEq(clonedContract.owner(), owner);
// now delete it, then destroy it
delete clonedAddress;
clonedContract.goodbye();
Clones.cloneDeterministic(address(cloneableContract), salt);
}
} The last line of the test results in an error: "[FAIL. Reason: ERC1167: create2 failed]" For reference, here are the contents of the function /**
* @dev Deploys and returns the address of a clone that mimics the behaviour of `implementation`.
*
* This function uses the create2 opcode and a `salt` to deterministically deploy
* the clone. Using the same `implementation` and `salt` multiple time will revert, since
* the clones cannot be deployed twice at the same address.
*/
library Clones {
function cloneDeterministic(address implementation, bytes32 salt) internal returns (address instance) {
/// @solidity memory-safe-assembly
assembly {
// Cleans the upper 96 bits of the `implementation` word, then packs the first 3 bytes
// of the `implementation` address with the bytecode before the address.
mstore(0x00, or(shr(0xe8, shl(0x60, implementation)), 0x3d602d80600a3d3981f3363d3d373d3d3d363d73000000))
// Packs the remaining 17 bytes of `implementation` with the bytecode after the address.
mstore(0x20, or(shl(0x78, implementation), 0x5af43d82803e903d91602b57fd5bf3))
instance := create2(0, 0x09, 0x37, salt)
}
require(instance != address(0), "ERC1167: create2 failed");
}
} |
thanks to @onbjerg suggestion the behavior works as expectedly if /// @notice Meant to test out determistic cloning techniques
contract DetermistingCloningTest is Test {
ContractToClone cloneableContract;
address owner;
bytes32 salt;
function setUp() external {
cloneableContract = new ContractToClone();
owner = vm.addr(1);
salt = bytes32(uint256(5));
address clonedAddress = Clones.cloneDeterministic(address(cloneableContract), salt);
ContractToClone clonedContract = ContractToClone(clonedAddress);
// now destroy it
clonedContract.goodbye();
}
function test_succeedsWhen_cloneCreatedInSetup() external {
// we already created and self-destructed the cloned contract in setup.
// we should be able to succeed now.
address clonedAddress = Clones.cloneDeterministic(address(cloneableContract), salt);
ContractToClone clonedContract = ContractToClone(clonedAddress);
clonedContract.initialize(owner);
assertEq(clonedContract.owner(), owner);
}
function test_revertsWhen_cloneCreatedInSameTest() external {
// this test creates 2 cloned contracts at the same address, but destroys the first one
// before creating the second one. it fails because foundry hasn't cleared the transaction in the same
// test.
bytes32 saltForTest = bytes32(uint256(6));
address clonedAddress = Clones.cloneDeterministic(address(cloneableContract), saltForTest);
ContractToClone clonedContract = ContractToClone(clonedAddress);
clonedContract.initialize(owner);
assertEq(clonedContract.owner(), owner);
// now delete it, then destroy it
clonedContract.goodbye();
// clone the contract again, using the same cloneable contract and salt - it should revert
vm.expectRevert();
Clones.cloneDeterministic(address(cloneableContract), saltForTest);
}
} |
selfdestruct
has no effect in test whereas a user expects it would
Component
Forge
Have you ensured that all of these are up to date?
What version of Foundry are you on?
forge 0.2.0 (bab38d6 2022-05-06T00:04:42.708656+00:00)
What command(s) is the bug in?
forge test
Operating System
macOS (M1)
Describe the bug
I'm not sure if this is actually a bug, or I just can't figure out how to achieve what I want.
I'm trying to test a very basic selfdestruct example, and it doesn't seem to be behaving as I expect:
SelfDestructor.sol:
SelfDestructor.t.sol:
Two things are confusing. Firstly, the size isn't changing between
kill()
invocations, and second, I can call the contract's kill() twice in the same test, both indicating that the selfdestruct isn't doing anything when it's called.I've tried both with and without the
vm.roll
calls. What am I missing?The text was updated successfully, but these errors were encountered: