-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
✨ develop the one-step create account flow
This commit implements the function to create and initializing an account. This function can be used in a one-step flow where the account is created and directly initialized with a signer. In addition to the unit tests, a new integration test has been added to garantee the deterministic deployment of the account matches the formulas defined in the getAddress function and both flow works the same. It's important to note this function also deploy a ERC1967Proxy for the user using the provided login hash. The additionnal arguments are used to initialize the proxy with the provided signer. A script has been written to call the function from the CLI. Last but not least, it is important to mention this function can revert either by the recovery library with use or by the factory itself when the signature provided is valid but the signer is not the one expected. Some notes have been added at the end of the file to give some details about the factory contract and the natspec documentation of the contract has been written.
- Loading branch information
Showing
6 changed files
with
282 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
// SPDX-License-Identifier: APACHE-2.0 | ||
pragma solidity >=0.8.19 <0.9.0; | ||
|
||
import { AccountFactory } from "src/AccountFactory.sol"; | ||
import { BaseScript } from "../Base.s.sol"; | ||
|
||
/// @title Create an Account using an already deployed AccountFactory and init it | ||
/** | ||
* @notice | ||
* forge script CreateAndInitAccount --sig run(address,uint256,uint256,bytes32,bytes,bytes)" | ||
* " <...args> <...flags> | ||
*/ | ||
/// @dev If you need to deploy an AccountFactory, use the Deploy script in this directory | ||
contract CreateAndInitAccount is BaseScript { | ||
function run( | ||
address factoryAddress, | ||
uint256 pubKeyX, | ||
uint256 pubKeyY, | ||
bytes32 loginHash, | ||
bytes calldata credId, | ||
bytes calldata nameServiceSignature // ℹ️ must be made by the nameServiceOwner of the AccountFactory | ||
) | ||
public | ||
broadcaster | ||
returns (address) | ||
{ | ||
AccountFactory factory = AccountFactory(factoryAddress); | ||
return factory.createAndInitAccount(pubKeyX, pubKeyY, loginHash, credId, nameServiceSignature); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
4 changes: 4 additions & 0 deletions
4
test/integration/AccountFactory/deterministicDeployments.tree
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,3 +1,7 @@ | ||
AccountFactoryDeterministicDeployment | ||
├── when using the createAccount flow | ||
│ └── it should deploy the account to the same address calculated by getAddress | ||
├── when using the createAccountAndInt flow | ||
│ └── it should deploy the account to the same address calculated by getAddress | ||
└── when using both flows with the same parameters | ||
└── it should deploy the account to the same address |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,126 @@ | ||
// SPDX-License-Identifier: UNLICENSED | ||
pragma solidity >=0.8.20 <0.9.0; | ||
|
||
import { AccountFactory } from "src/AccountFactory.sol"; | ||
import { BaseTest } from "test/BaseTest.sol"; | ||
|
||
contract AccountFactory__CreateAndInitAccount is BaseTest { | ||
bytes32 private constant LOGIN_HASH = keccak256("qdqd"); | ||
address private constant SIGNER = 0x7a8c35e1CcE64FD85baeD9a3e4f399cAADb52f20; | ||
bytes private constant SIGNATURE = hex"247bbb60d4e8fd56e177234fb566331249f367465120c95ce65f" | ||
hex"a784b0b917cd6e19a4b6ebfb5d93a217ea76c37ff6d98d5f3aa18015e7220543a95d215a50381c"; | ||
AccountFactory private factory; | ||
|
||
// copy here the event definition from the contract | ||
// @dev: once we bump to 0.8.21, import the event from the contract | ||
event AccountCreatedAndInit(bytes32 loginHash, address account, bytes credId, uint256 pubKeyX, uint256 pubKeyY); | ||
|
||
function setUp() external { | ||
factory = new AccountFactory(address(0), address(0), SIGNER); | ||
} | ||
|
||
function test_ShouldUseADeterministicDeploymentProcess() external { | ||
// predict where the account linked to a specific hash will be deployed | ||
address predictedAddress = factory.getAddress(LOGIN_HASH); | ||
|
||
// check the address of the account doesn't have any code before the deployment | ||
assertEq(keccak256(predictedAddress.code), keccak256("")); | ||
|
||
// deploy the account contract using the same hash | ||
factory.createAndInitAccount(___, ___, LOGIN_HASH, ____, SIGNATURE); | ||
|
||
// make sure the account contract has been deployed | ||
assertNotEq(keccak256(predictedAddress.code), keccak256("")); | ||
} | ||
|
||
function test_GivenAHashAlreadyUsed() external { | ||
// it should return the existing account address | ||
|
||
// make sure the second attempt of creation return the already deployed address | ||
// without reverting or something else | ||
assertEq( | ||
factory.createAndInitAccount(___, ___, LOGIN_HASH, ____, SIGNATURE), | ||
factory.createAndInitAccount(___, ___, LOGIN_HASH, ____, SIGNATURE) | ||
); | ||
} | ||
|
||
function test_GivenANewHash() external { | ||
// it should deploy a new account if none exists | ||
|
||
// deploy a valid proxy account using the constants predefined | ||
address proxy1 = factory.createAndInitAccount(___, ___, LOGIN_HASH, ____, SIGNATURE); | ||
|
||
// generated using the same private key as the one used to generate the SIGNATURE constant | ||
bytes memory newValidCorrectSignature = | ||
hex"4b4b6f4ecc5fb0427bbbe61b539a6c45062b45d794641a5dc86e12bd8c6f68a747" | ||
hex"df2abe57a1df59f2d2d5ba5f9c89d723ea8a7c1ca79e95fc1321f3eeb775f51c"; | ||
bytes32 newLoginHash = keccak256("xoxo"); | ||
|
||
// deploy a valid proxy account using a different loginHash and a correct valid signature | ||
address proxy2 = factory.createAndInitAccount(___, ___, newLoginHash, ____, newValidCorrectSignature); | ||
|
||
assertNotEq(proxy1, proxy2); | ||
assertNotEq(keccak256(proxy1.code), keccak256("")); | ||
assertNotEq(keccak256(proxy2.code), keccak256("")); | ||
} | ||
|
||
function test_RevertGiven_AnIncorrectValidSignature() external { | ||
// it should revert | ||
|
||
// this signature is a valid ECDSA signature but it as been createrd using a non authorized private key | ||
bytes memory invalidSignature = hex"1020211079cccfe88a67ed9d00d719c922b4d79e11ddb5f1f59c2e41" | ||
hex"fb27d5fa3f7825d448a05d75273f75f42def0010fdfb4f6ac1e0abe65dc426f7536d325c1b"; | ||
|
||
// we tell the VM to expect a revert with a precise error | ||
vm.expectRevert( | ||
abi.encodeWithSelector(AccountFactory.InvalidNameServiceSignature.selector, LOGIN_HASH, invalidSignature) | ||
); | ||
|
||
// we call the function with the invalid signature to trigger the error | ||
factory.createAndInitAccount(___, ___, LOGIN_HASH, ____, invalidSignature); | ||
} | ||
|
||
function test_ShouldCallInitialize() external { | ||
// we tell the VM to expect *one* call to the initialize function with the loginHash as parameter | ||
vm.expectCall(factory.accountImplementation(), abi.encodeCall(this.initialize, (LOGIN_HASH)), 1); | ||
|
||
// we call the function that is supposed to trigger the call | ||
factory.createAndInitAccount(___, ___, LOGIN_HASH, ____, SIGNATURE); | ||
} | ||
|
||
function test_ShouldCallTheProxyAddFirstSignerFunction() external { | ||
uint256 pubKeyX = uint256(43); | ||
uint256 pubKeyY = uint256(22); | ||
bytes memory credId = abi.encodePacked(keccak256("a"), keccak256("b")); | ||
|
||
// we tell the VM to expect *one* call to the addFirstSigner function with the loginHash as parameter | ||
vm.expectCall( | ||
factory.getAddress(LOGIN_HASH), abi.encodeCall(this.addFirstSigner, (pubKeyX, pubKeyY, credId)), 1 | ||
); | ||
|
||
// we call the function that is supposed to trigger the call | ||
factory.createAndInitAccount(pubKeyX, pubKeyY, LOGIN_HASH, credId, SIGNATURE); | ||
} | ||
|
||
function test_ShouldTriggerAnEventOnDeployment() external { | ||
uint256 pubKeyX = uint256(43); | ||
uint256 pubKeyY = uint256(22); | ||
bytes memory credId = abi.encodePacked(keccak256("a"), keccak256("b")); | ||
|
||
// we tell the VM to expect an event | ||
vm.expectEmit(true, true, true, true, address(factory)); | ||
// we trigger the exact event we expect to be emitted in the next call | ||
emit AccountCreatedAndInit(LOGIN_HASH, factory.getAddress(LOGIN_HASH), credId, pubKeyX, pubKeyY); | ||
|
||
// we call the function that is supposed to trigger the event | ||
// if the exact event is not triggered, the test will fail | ||
factory.createAndInitAccount(pubKeyX, pubKeyY, LOGIN_HASH, credId, SIGNATURE); | ||
} | ||
|
||
// @dev: I don't know why but encodeCall crashes when using Account.XXX | ||
// when using the utils Test contract from Forge, so I had to copy the function here | ||
// it works as expected if I switch to the utils Test contract from PRB 🤷♂️ | ||
// Anyway, remove this useless function once the bug is fixed | ||
function initialize(bytes32) public { } | ||
function addFirstSigner(uint256, uint256, bytes calldata) public { } | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
AccountFactory__CreateAndInitAccount | ||
├── it should use a deterministic deployment process | ||
├── given a hash already used | ||
│ └── it should return existing account address | ||
├── given a new hash | ||
│ └── it should deploy a new account if none exists | ||
├── given an incorrect valid signature | ||
│ └── it should revert | ||
├── it should call initialize | ||
├── it should call the proxy addFirstSigner function | ||
└── it should trigger an event on deployment |