This repository has been archived by the owner on Sep 30, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* Working recovery test, a lot of clean up needed * Fix some warnings * Clean up the updateOwner function in the ecdsaPlugin * Clean up the recovery plugin * Initial recovery plugin test clean up * Minor clean up * More clean up of the recovery tests * Linting fixes * Remove unnecessary code in plugin * Update naming * Eslint error in test * Fixing more eslint issues * Remove disable eslint issue * Add note in read me about pre alpha state
- Loading branch information
1 parent
7529998
commit d3eb9c9
Showing
8 changed files
with
604 additions
and
10 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
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
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,56 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity ^0.8.0; | ||
|
||
contract Enum { | ||
enum Operation { | ||
Call, | ||
DelegateCall | ||
} | ||
} | ||
|
||
interface ISafe { | ||
/// @dev Allows a Module to execute a Safe transaction without any further confirmations. | ||
/// @param to Destination address of module transaction. | ||
/// @param value Ether value of module transaction. | ||
/// @param data Data payload of module transaction. | ||
/// @param operation Operation type of module transaction. | ||
function execTransactionFromModule( | ||
address to, | ||
uint256 value, | ||
bytes calldata data, | ||
Enum.Operation operation | ||
) external returns (bool success); | ||
} | ||
|
||
contract SafeECDSARecoveryPlugin { | ||
address private immutable storedEOA; | ||
address public storedSafe; | ||
|
||
error SENDER_NOT_STORED_EOA(address sender); | ||
error ATTEMPTING_RESET_ON_WRONG_SAFE(address attemptedSafe); | ||
|
||
constructor(address _safe, address _eoa) { | ||
storedSafe = _safe; | ||
storedEOA = _eoa; | ||
} | ||
|
||
modifier onlyStoredEOA { | ||
if (msg.sender != storedEOA) { | ||
revert SENDER_NOT_STORED_EOA(msg.sender); | ||
} | ||
_; | ||
} | ||
|
||
function resetEcdsaAddress( | ||
address safe, | ||
address ecdsaPluginAddress, | ||
address newValidatingEcdsaAddress | ||
) external onlyStoredEOA { | ||
if (safe != storedSafe) { | ||
revert ATTEMPTING_RESET_ON_WRONG_SAFE(safe); | ||
} | ||
|
||
bytes memory data = abi.encodeWithSignature("updateOwner(address)", newValidatingEcdsaAddress); | ||
ISafe(safe).execTransactionFromModule(ecdsaPluginAddress, 0, data, Enum.Operation.Call); | ||
} | ||
} |
136 changes: 136 additions & 0 deletions
136
account-integrations/safe/test/hardhat/SafeECDSARecoveryPlugin.test.ts
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,136 @@ | ||
import { ethers } from "hardhat"; | ||
import { expect } from "chai"; | ||
import { HDNodeWallet, Provider } from "ethers"; | ||
import { setupTests, sendTx } from "./setupTests"; | ||
|
||
import { executeContractCallWithSigners } from "./utils/execution"; | ||
|
||
import { EntryPoint } from "../../typechain-types/lib/account-abstraction/contracts/core/EntryPoint"; | ||
import { SafeECDSAPlugin } from "../../typechain-types/src/SafeECDSAPlugin.sol/SafeECDSAPlugin"; | ||
|
||
const MNEMONIC = "test test test test test test test test test test test junk"; | ||
|
||
describe("SafeECDSARecoveryPlugin", () => { | ||
let provider: Provider; | ||
let safeSigner: HDNodeWallet; | ||
let entryPoint: EntryPoint; | ||
let safeECDSAPlugin: SafeECDSAPlugin; | ||
let safeCounterfactualAddress: string; | ||
|
||
before(async () => { | ||
const { | ||
safeOwner, | ||
entryPointContract, | ||
safeEcdsaPluginContract, | ||
counterfactualAddress, | ||
} = await setupTests(); | ||
|
||
provider = ethers.provider; | ||
safeSigner = safeOwner; | ||
entryPoint = entryPointContract; | ||
safeECDSAPlugin = safeEcdsaPluginContract; | ||
safeCounterfactualAddress = counterfactualAddress; | ||
}); | ||
|
||
it("Should enable a recovery plugin on a safe.", async () => { | ||
const [, , recoverySigner] = await ethers.getSigners(); | ||
|
||
const recoveryPlugin = await ( | ||
await ethers.getContractFactory("SafeECDSARecoveryPlugin") | ||
).deploy(safeCounterfactualAddress, recoverySigner.address); | ||
const recoveryPluginAddress = await recoveryPlugin.getAddress(); | ||
|
||
// Enable recovery plugin on safe | ||
|
||
const deployedSafe = await ethers.getContractAt( | ||
"Safe", | ||
safeCounterfactualAddress, | ||
); | ||
const isModuleEnabledBefore = await deployedSafe.isModuleEnabled( | ||
recoveryPluginAddress, | ||
); | ||
|
||
await executeContractCallWithSigners( | ||
deployedSafe, | ||
deployedSafe, | ||
"enableModule", | ||
[recoveryPluginAddress], | ||
// @ts-expect-error safeSigner doesn't have all properties for some reason | ||
[safeSigner], | ||
); | ||
|
||
const isModuleEnabledAfter = await deployedSafe.isModuleEnabled( | ||
recoveryPluginAddress, | ||
); | ||
|
||
expect(isModuleEnabledBefore).to.equal(false); | ||
expect(isModuleEnabledAfter).to.equal(true); | ||
}); | ||
|
||
it("Should use recovery plugin to reset signing key and then send tx with new key.", async () => { | ||
// Setup recovery plugin | ||
|
||
const [, , , recoverySigner] = await ethers.getSigners(); | ||
|
||
const recoveryPlugin = await ( | ||
await ethers.getContractFactory("SafeECDSARecoveryPlugin") | ||
).deploy(safeCounterfactualAddress, recoverySigner.address); | ||
const recoveryPluginAddress = await recoveryPlugin.getAddress(); | ||
|
||
const deployedSafe = await ethers.getContractAt( | ||
"Safe", | ||
safeCounterfactualAddress, | ||
); | ||
|
||
// Enable recovery plugin | ||
|
||
await executeContractCallWithSigners( | ||
deployedSafe, | ||
deployedSafe, | ||
"enableModule", | ||
[recoveryPluginAddress], | ||
// @ts-expect-error safeSigner doesn't have all properties for some reason | ||
[safeSigner], | ||
); | ||
|
||
const isModuleEnabled = await deployedSafe.isModuleEnabled( | ||
recoveryPluginAddress, | ||
); | ||
expect(isModuleEnabled).to.equal(true); | ||
|
||
// Reset ecdsa address | ||
|
||
const ecdsaPluginAddress = await safeECDSAPlugin.getAddress(); | ||
const newEcdsaPluginSigner = ethers.Wallet.createRandom().connect(provider); | ||
|
||
const recoveryPluginSinger = recoveryPlugin.connect(recoverySigner); | ||
|
||
await recoveryPluginSinger.resetEcdsaAddress( | ||
await deployedSafe.getAddress(), | ||
ecdsaPluginAddress, | ||
newEcdsaPluginSigner.address, | ||
); | ||
|
||
// Send tx with new key | ||
|
||
const recipientAddress = ethers.Wallet.createRandom().address; | ||
const transferAmount = ethers.parseEther("1"); | ||
const userOpCallData = safeECDSAPlugin.interface.encodeFunctionData( | ||
"execTransaction", | ||
[recipientAddress, transferAmount, "0x00"], | ||
); | ||
const recipientBalanceBefore = await provider.getBalance(recipientAddress); | ||
await sendTx( | ||
newEcdsaPluginSigner, | ||
entryPoint, | ||
safeCounterfactualAddress, | ||
"0x1", | ||
"0x", | ||
userOpCallData, | ||
); | ||
|
||
const recipientBalanceAfter = await provider.getBalance(recipientAddress); | ||
const expectedRecipientBalance = recipientBalanceBefore + transferAmount; | ||
expect(recipientBalanceAfter).to.equal(expectedRecipientBalance); | ||
}); | ||
}); |
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
Oops, something went wrong.