Skip to content

Added hardfork support + deterministic state to reference implementation #31

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

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions src/examples/simple/ERC6551Account.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ interface IERC6551Account {
view
returns (uint256 chainId, address tokenContract, uint256 tokenId);

function state() external view returns (uint256);
function state() external view returns (bytes32);

function isValidSigner(address signer, bytes calldata context)
external
Expand All @@ -30,7 +30,9 @@ interface IERC6551Executable {
}

contract ERC6551Account is IERC165, IERC1271, IERC6551Account, IERC6551Executable {
uint256 public state;
uint256 immutable deploymentChainId = block.chainid;

bytes32 public state;

receive() external payable {}

Expand All @@ -43,7 +45,7 @@ contract ERC6551Account is IERC165, IERC1271, IERC6551Account, IERC6551Executabl
require(_isValidSigner(msg.sender), "Invalid signer");
require(operation == 0, "Only call operations are supported");

++state;
state = keccak256(abi.encode(state, msg.data));

bool success;
(success, result) = to.call{value: value}(data);
Expand Down Expand Up @@ -96,7 +98,8 @@ contract ERC6551Account is IERC165, IERC1271, IERC6551Account, IERC6551Executabl

function owner() public view virtual returns (address) {
(uint256 chainId, address tokenContract, uint256 tokenId) = token();
if (chainId != block.chainid) return address(0);

if (chainId != deploymentChainId) return address(0);

return IERC721(tokenContract).ownerOf(tokenId);
}
Expand Down
10 changes: 6 additions & 4 deletions src/examples/upgradeable/ERC6551AccountUpgradeable.sol
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ contract ERC6551AccountUpgradeable is
bytes32 internal constant _IMPLEMENTATION_SLOT =
0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc;

uint256 public state;
uint256 immutable deploymentChainId = block.chainid;

bytes32 public state;

receive() external payable {}

Expand All @@ -51,7 +53,7 @@ contract ERC6551AccountUpgradeable is
{
require(_isValidSigner(msg.sender), "Caller is not owner");
require(_operation == 0, "Only call operations are supported");
++state;
state = keccak256(abi.encode(state, msg.data));
bool success;
// solhint-disable-next-line avoid-low-level-calls
(success, _result) = _target.call{value: _value}(_data);
Expand All @@ -65,7 +67,7 @@ contract ERC6551AccountUpgradeable is
function upgrade(address implementation_) external virtual {
require(_isValidSigner(msg.sender), "Caller is not owner");
require(implementation_ != address(0), "Invalid implementation address");
++state;
state = keccak256(abi.encode(state, msg.data));
StorageSlot.getAddressSlot(_IMPLEMENTATION_SLOT).value = implementation_;
}

Expand Down Expand Up @@ -139,7 +141,7 @@ contract ERC6551AccountUpgradeable is

function owner() public view virtual returns (address) {
(uint256 chainId, address contractAddress, uint256 tokenId) = token();
if (chainId != block.chainid) return address(0);
if (chainId != deploymentChainId) return address(0);
return IERC721(contractAddress).ownerOf(tokenId);
}

Expand Down
2 changes: 1 addition & 1 deletion src/interfaces/IERC6551Account.sol
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ interface IERC6551Account {
*
* @return The current account state
*/
function state() external view returns (uint256);
function state() external view returns (bytes32);

/**
* @dev Returns a magic value indicating whether a given signer is authorized to act on behalf
Expand Down
35 changes: 34 additions & 1 deletion test/examples/simple/ERC6551Account.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -49,11 +49,44 @@ contract AccountTest is Test {

vm.deal(account, 1 ether);

bytes32 expectedState = keccak256(
abi.encode(
bytes32(0),
abi.encodeWithSignature(
"execute(address,uint256,bytes,uint8)", vm.addr(2), 0.5 ether, "", 0
)
)
);

vm.prank(vm.addr(1));
executableAccountInstance.execute(payable(vm.addr(2)), 0.5 ether, "", 0);

assertEq(account.balance, 0.5 ether);
assertEq(vm.addr(2).balance, 0.5 ether);
assertEq(accountInstance.state(), 1);
assertEq(accountInstance.state(), expectedState);
}

function testChainIdPostHardfork() public {
nft.mint(vm.addr(1), 1);

uint256 anvilChainId = block.chainid;
uint256 anvilHardforkChainId = anvilChainId + 1;
uint256 otherChainId = anvilChainId + 2;

address anvilAccount =
registry.createAccount(address(implementation), 0, anvilChainId, address(nft), 1);
address otherChainOnAnvilAccount =
registry.createAccount(address(implementation), 0, otherChainId, address(nft), 1);

assertTrue(anvilAccount != address(0));
assertTrue(otherChainOnAnvilAccount != address(0));

assertEq(ERC6551Account(payable(anvilAccount)).owner(), vm.addr(1));
assertEq(ERC6551Account(payable(otherChainOnAnvilAccount)).owner(), address(0));

vm.chainId(anvilHardforkChainId);

assertEq(ERC6551Account(payable(anvilAccount)).owner(), vm.addr(1));
assertEq(ERC6551Account(payable(otherChainOnAnvilAccount)).owner(), address(0));
}
}
38 changes: 35 additions & 3 deletions test/examples/upgradeable/ERC6551AccountUpgradeable.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,15 @@ contract AccountProxyTest is Test {
IERC6551Account accountInstance = IERC6551Account(payable(account));
IERC6551Executable executableAccountInstance = IERC6551Executable(account);

bytes32 expectedState = keccak256(
abi.encode(
bytes32(0),
abi.encodeWithSignature(
"execute(address,uint256,bytes,uint8)", vm.addr(2), 0.5 ether, "", 0
)
)
);

vm.prank(vm.addr(3));
vm.expectRevert("Caller is not owner");
executableAccountInstance.execute(payable(vm.addr(2)), 0.5 ether, "", 0);
Expand All @@ -103,7 +112,7 @@ contract AccountProxyTest is Test {

assertEq(account.balance, 0.5 ether);
assertEq(vm.addr(2).balance, 0.5 ether);
assertEq(accountInstance.state(), 1);
assertEq(accountInstance.state(), expectedState);
}

function testCannotOwnSelf() public {
Expand Down Expand Up @@ -287,14 +296,13 @@ contract AccountProxyTest is Test {
assertEq(address(uint160(uint256(rawImplementation))), address(0));

// Send ETH to initialize.
(bool success, ) = payable(account).call{value: 0}("");
(bool success,) = payable(account).call{value: 0}("");
assertTrue(success);

rawImplementation =
vm.load(account, 0x360894a13ba1a3210667c828492db98dca3e2076cc3735a920a3ca505d382bbc);

assertEq(address(uint160(uint256(rawImplementation))), address(implementation));

}

function testERC721Receive() public {
Expand Down Expand Up @@ -396,4 +404,28 @@ contract AccountProxyTest is Test {
vm.expectRevert(InvalidImplementation.selector);
new ERC6551AccountProxy(address(0));
}

function testChainIdPostHardfork() public {
nft.mint(vm.addr(1), 1);

uint256 anvilChainId = block.chainid;
uint256 anvilHardforkChainId = anvilChainId + 1;
uint256 otherChainId = anvilChainId + 2;

address anvilAccount =
registry.createAccount(address(implementation), 0, anvilChainId, address(nft), 1);
address otherChainOnAnvilAccount =
registry.createAccount(address(implementation), 0, otherChainId, address(nft), 1);

assertTrue(anvilAccount != address(0));
assertTrue(otherChainOnAnvilAccount != address(0));

assertEq(ERC6551AccountUpgradeable(payable(anvilAccount)).owner(), vm.addr(1));
assertEq(ERC6551AccountUpgradeable(payable(otherChainOnAnvilAccount)).owner(), address(0));

vm.chainId(anvilHardforkChainId);

assertEq(ERC6551AccountUpgradeable(payable(anvilAccount)).owner(), vm.addr(1));
assertEq(ERC6551AccountUpgradeable(payable(otherChainOnAnvilAccount)).owner(), address(0));
}
}
2 changes: 1 addition & 1 deletion test/mocks/MockERC6551Account.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import "../../src/interfaces/IERC6551Executable.sol";
import "../../src/lib/ERC6551AccountLib.sol";

contract MockERC6551Account is IERC165, IERC6551Account, IERC6551Executable {
uint256 public state;
bytes32 public state;
bool private _initialized;

receive() external payable {}
Expand Down