Skip to content
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

chore: [v0.8-develop] alpha.0 deploy prep #111

Merged
merged 4 commits into from
Jul 26, 2024
Merged
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
20 changes: 20 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@

# Factory owner capable only of managing stake
OWNER=
# EP 0.7 address
ENTRYPOINT=

# Create2 expected addresses of the contracts.
# When running for the first time, the error message will contain the expected addresses.
ACCOUNT_IMPL=
FACTORY=
SINGLE_SIGNER_VALIDATION=

# Optional, defaults to bytes32(0)
ACCOUNT_IMPL_SALT=
FACTORY_SALT=
SINGLE_SIGNER_VALIDATION_SALT=

# Optional, defaults to 0.1 ether and 1 day, respectively
STAKE_AMOUNT=
UNSTAKE_DELAY=
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,6 @@ node_modules/
# Coverage
report/
lcov.info

# env vars
.env
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,14 @@ Since IR compilation generates different bytecode, it's useful to test against t
FOUNDRY_PROFILE=optimized-build forge build
FOUNDRY_PROFILE=optimized-test forge test -vvv
```

## Integration testing

The reference implementation provides a sample factory and deploy script for the factory, account implementation, and the demo validation module `SingleSignerValidation`. This is not auditted, nor intended for production use. Limitations set by the GPL-V3 license apply.

To run this script, provide appropriate values in a `.env` file based on the `.env.example` template, then run:
```bash
forge script script/Deploy.s.sol <wallet options> -r <rpc_url> --broadcast
```

Where `<wallet_options>` specifies a way to sign the deployment transaction (see [here](https://book.getfoundry.sh/reference/forge/forge-script#wallet-options---raw)) and `<rpc_url>` specifies an RPC for the network you are deploying on.
4 changes: 4 additions & 0 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ libs = ['lib']
out = 'out'
optimizer = true
optimizer_runs = 200
auto_detect_solc = false
bytecode_hash = "none"
auto_detect_remappings = false
fs_permissions = [
{ access = "read", path = "./out-optimized" }
]
Expand All @@ -22,6 +25,7 @@ depth = 10
[profile.optimized-build]
via_ir = true
test = 'src'
optimizer_runs = 20000
out = 'out-optimized'

[profile.optimized-test]
Expand Down
156 changes: 156 additions & 0 deletions script/Deploy.s.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import {IEntryPoint} from "@eth-infinitism/account-abstraction/interfaces/IEntryPoint.sol";
import {Script} from "forge-std/Script.sol";
import {console2} from "forge-std/Test.sol";

import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";

import {AccountFactory} from "../src/account/AccountFactory.sol";
import {UpgradeableModularAccount} from "../src/account/UpgradeableModularAccount.sol";
import {SingleSignerValidation} from "../src/modules/validation/SingleSignerValidation.sol";

contract DeployScript is Script {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we update readme for this?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add .env.example too.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added both a .env.example with comments, and a section in the README for running the script.

IEntryPoint public entryPoint = IEntryPoint(payable(vm.envAddress("ENTRYPOINT")));

address public owner = vm.envAddress("OWNER");

address public accountImpl = vm.envOr("ACCOUNT_IMPL", address(0));
address public factory = vm.envOr("FACTORY", address(0));
address public singleSignerValidation = vm.envOr("SINGLE_SIGNER_VALIDATION", address(0));

bytes32 public accountImplSalt = bytes32(vm.envOr("ACCOUNT_IMPL_SALT", uint256(0)));
bytes32 public factorySalt = bytes32(vm.envOr("FACTORY_SALT", uint256(0)));
bytes32 public singleSignerValidationSalt = bytes32(vm.envOr("SINGLE_SIGNER_VALIDATION_SALT", uint256(0)));

uint256 public requiredStakeAmount = vm.envOr("STAKE_AMOUNT", uint256(0.1 ether));
uint256 public requiredUnstakeDelay = vm.envOr("UNSTAKE_DELAY", uint256(1 days));

function run() public {
console2.log("******** Deploying ERC-6900 Reference Implementation ********");
console2.log("Chain: ", block.chainid);
console2.log("EP: ", address(entryPoint));
console2.log("Factory owner: ", owner);

_deployAccountImpl(accountImplSalt, accountImpl);
_deploySingleSignerValidation(singleSignerValidationSalt, singleSignerValidation);
_deployAccountFactory(factorySalt, factory);
_addStakeForFactory(uint32(requiredUnstakeDelay), requiredStakeAmount);
}

function _deployAccountImpl(bytes32 salt, address expected) internal {
console2.log(string.concat("Deploying AccountImpl with salt: ", vm.toString(salt)));

address addr = Create2.computeAddress(
salt, keccak256(abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(entryPoint)))
);
if (addr != expected) {
Copy link
Collaborator

@fangting-alchemy fangting-alchemy Jul 25, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if this is necessary in RI. It is gonna make test deployment annoying as you will need to compute all the addresses first and put them in env (account impl, validation, etc.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's just to prevent accidental drift between when it's calculated vs deployed, which can happen if using the wrong build profile, for example. For the first time pass, we can just run the script, and the error in console.log will tell us what address to put in.

console2.log("Expected address mismatch");
console2.log("Expected: ", expected);
console2.log("Actual: ", addr);
revert();
}

if (addr.code.length == 0) {
console2.log("No code found at expected address, deploying...");
UpgradeableModularAccount deployed = new UpgradeableModularAccount{salt: salt}(entryPoint);

if (address(deployed) != expected) {
console2.log("Deployed address mismatch");
console2.log("Expected: ", expected);
console2.log("Deployed: ", address(deployed));
revert();
}

console2.log("Deployed AccountImpl at: ", address(deployed));
} else {
console2.log("Code found at expected address, skipping deployment");
}
}

function _deploySingleSignerValidation(bytes32 salt, address expected) internal {
console2.log(string.concat("Deploying SingleSignerValidation with salt: ", vm.toString(salt)));

address addr =
Create2.computeAddress(salt, keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)));
if (addr != expected) {
console2.log("Expected address mismatch");
console2.log("Expected: ", expected);
console2.log("Actual: ", addr);
revert();
}

if (addr.code.length == 0) {
console2.log("No code found at expected address, deploying...");
SingleSignerValidation deployed = new SingleSignerValidation{salt: salt}();

if (address(deployed) != expected) {
console2.log("Deployed address mismatch");
console2.log("Expected: ", expected);
console2.log("Deployed: ", address(deployed));
revert();
}

console2.log("Deployed SingleSignerValidation at: ", address(deployed));
} else {
console2.log("Code found at expected address, skipping deployment");
}
}

function _deployAccountFactory(bytes32 salt, address expected) internal {
console2.log(string.concat("Deploying AccountFactory with salt: ", vm.toString(salt)));

address addr = Create2.computeAddress(
salt,
keccak256(
abi.encodePacked(
type(AccountFactory).creationCode,
abi.encode(entryPoint, accountImpl, singleSignerValidation, owner)
)
)
);
if (addr != expected) {
console2.log("Expected address mismatch");
console2.log("Expected: ", expected);
console2.log("Actual: ", addr);
revert();
}

if (addr.code.length == 0) {
console2.log("No code found at expected address, deploying...");
AccountFactory deployed = new AccountFactory{salt: salt}(
entryPoint, UpgradeableModularAccount(payable(accountImpl)), singleSignerValidation, owner
);

if (address(deployed) != expected) {
console2.log("Deployed address mismatch");
console2.log("Expected: ", expected);
console2.log("Deployed: ", address(deployed));
revert();
}

console2.log("Deployed AccountFactory at: ", address(deployed));
} else {
console2.log("Code found at expected address, skipping deployment");
}
}

function _addStakeForFactory(uint32 unstakeDelay, uint256 stakeAmount) internal {
console2.log("Adding stake to factory");

uint256 currentStake = entryPoint.getDepositInfo(address(factory)).stake;
console2.log("Current stake: ", currentStake);
uint256 stakeToAdd = stakeAmount - currentStake;

if (stakeToAdd > 0) {
console2.log("Adding stake: ", stakeToAdd);
entryPoint.addStake{value: stakeToAdd}(unstakeDelay);
console2.log("Staked factory: ", address(factory));
console2.log("Total stake amount: ", entryPoint.getDepositInfo(address(factory)).stake);
console2.log("Unstake delay: ", entryPoint.getDepositInfo(address(factory)).unstakeDelaySec);
} else {
console2.log("No stake to add");
}
}
}
9 changes: 6 additions & 3 deletions src/account/AccountFactory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,12 @@ contract AccountFactory is Ownable {
IEntryPoint public immutable ENTRY_POINT;
address public immutable SINGLE_SIGNER_VALIDATION;

constructor(IEntryPoint _entryPoint, UpgradeableModularAccount _accountImpl, address _singleSignerValidation)
Ownable(msg.sender)
{
constructor(
IEntryPoint _entryPoint,
UpgradeableModularAccount _accountImpl,
address _singleSignerValidation,
address owner
) Ownable(owner) {
ENTRY_POINT = _entryPoint;
_PROXY_BYTECODE_HASH =
keccak256(abi.encodePacked(type(ERC1967Proxy).creationCode, abi.encode(address(_accountImpl), "")));
Expand Down
2 changes: 1 addition & 1 deletion test/account/AccountFactory.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ contract AccountFactoryTest is AccountTestBase {

function setUp() public {
_account = new UpgradeableModularAccount(entryPoint);
_factory = new AccountFactory(entryPoint, _account, address(singleSignerValidation));
_factory = new AccountFactory(entryPoint, _account, address(singleSignerValidation), address(this));
}

function test_createAccount() public {
Expand Down
79 changes: 79 additions & 0 deletions test/script/Deploy.s.t.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.8.25;

import {Test} from "forge-std/Test.sol";

import {EntryPoint} from "@eth-infinitism/account-abstraction/core/EntryPoint.sol";
import {Create2} from "@openzeppelin/contracts/utils/Create2.sol";

import {DeployScript} from "../../script/Deploy.s.sol";

import {AccountFactory} from "../../src/account/AccountFactory.sol";
import {UpgradeableModularAccount} from "../../src/account/UpgradeableModularAccount.sol";
import {SingleSignerValidation} from "../../src/modules/validation/SingleSignerValidation.sol";

contract DeployTest is Test {
DeployScript internal _deployScript;

EntryPoint internal _entryPoint;

address internal _owner;

address internal _accountImpl;
address internal _singleSignerValidation;
address internal _factory;

function setUp() public {
_entryPoint = new EntryPoint();
_owner = makeAddr("OWNER");

vm.setEnv("ENTRYPOINT", vm.toString(address(_entryPoint)));
vm.setEnv("OWNER", vm.toString(_owner));

// Create1 derivation of the 2nd address deployed
address deployScriptAddr = address(0x2e234DAe75C793f67A35089C9d99245E1C58470b);

_accountImpl = Create2.computeAddress(
bytes32(0),
keccak256(
abi.encodePacked(type(UpgradeableModularAccount).creationCode, abi.encode(address(_entryPoint)))
),
deployScriptAddr
);

_singleSignerValidation = Create2.computeAddress(
bytes32(0), keccak256(abi.encodePacked(type(SingleSignerValidation).creationCode)), deployScriptAddr
);

_factory = Create2.computeAddress(
bytes32(0),
keccak256(
abi.encodePacked(
type(AccountFactory).creationCode,
abi.encode(address(_entryPoint), _accountImpl, _singleSignerValidation, _owner)
)
),
deployScriptAddr
);

vm.setEnv("ACCOUNT_IMPL", vm.toString(address(_accountImpl)));
vm.setEnv("FACTORY", vm.toString(address(_factory)));
vm.setEnv("SINGLE_SIGNER_VALIDATION", vm.toString(address(_singleSignerValidation)));

vm.setEnv("ACCOUNT_IMPL_SALT", vm.toString(uint256(0)));
vm.setEnv("FACTORY_SALT", vm.toString(uint256(0)));
vm.setEnv("SINGLE_SIGNER_VALIDATION_SALT", vm.toString(uint256(0)));

_deployScript = new DeployScript();

vm.deal(address(_deployScript), 0.1 ether);
}

function test_deployScript_run() public {
_deployScript.run();

assertTrue(_accountImpl.code.length > 0);
assertTrue(_factory.code.length > 0);
assertTrue(_singleSignerValidation.code.length > 0);
}
}
Loading