mirror of
https://github.com/getwax/zk-account-abstraction.git
synced 2026-01-09 20:47:58 -05:00
AA-29: gnosis proxy (#96)
* inital code import Gnosis code as-is. probably can remove all non-essential contracts (e.g. test, samples) or better, import as external library. * removed unused contracts (not used,fail compilation) * initial Gnosis-Safe Proxy account * refactor: - use @gnosis.pm/safe-contracts package - separate contracts into separate files. * cleanup, single owner * cleanup contracts simpler fallback handler * added tests failure cases counterfactual creation * change to "Manager" - manager is not a module, only fallback, entrypoint - replaceManager now works * ignore from coverage (fails to compile for coverage) * fix dangling test * Fix lint * Set expected code lenght to be 324 Co-authored-by: Alex Forshtat <forshtat1@gmail.com>
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -15,3 +15,4 @@ artifacts
|
||||
/coverage
|
||||
/coverage.json
|
||||
/.DS_Store
|
||||
.DS_Store
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
module.exports = {
|
||||
skipFiles: [
|
||||
"test",
|
||||
//solc-coverage fails to compile our Manager module.
|
||||
"gnosis"
|
||||
],
|
||||
};
|
||||
|
||||
32
contracts/gnosis/EIP4337Fallback.sol
Normal file
32
contracts/gnosis/EIP4337Fallback.sol
Normal file
@@ -0,0 +1,32 @@
|
||||
//SPDX-License-Identifier: GPL
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
/* solhint-disable no-inline-assembly */
|
||||
|
||||
import "@gnosis.pm/safe-contracts/contracts/handler/DefaultCallbackHandler.sol";
|
||||
import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
|
||||
import "../IWallet.sol";
|
||||
import "./EIP4337Manager.sol";
|
||||
|
||||
contract EIP4337Fallback is DefaultCallbackHandler, IWallet {
|
||||
address immutable public eip4337manager;
|
||||
constructor(address _eip4337manager) {
|
||||
eip4337manager = _eip4337manager;
|
||||
}
|
||||
|
||||
/**
|
||||
* handler is called from the Safe. delegate actual work to EIP4337Manager
|
||||
*/
|
||||
function validateUserOp(UserOperation calldata, bytes32, uint256) external {
|
||||
//delegate entire msg.data (including the appended "msg.sender") to the EIP4337Manager
|
||||
// will work only for GnosisSafe contracts
|
||||
GnosisSafe safe = GnosisSafe(payable(msg.sender));
|
||||
(bool success, bytes memory ret) = safe.execTransactionFromModuleReturnData(eip4337manager, 0, msg.data, Enum.Operation.DelegateCall);
|
||||
if (!success) {
|
||||
assembly {
|
||||
revert(add(ret, 32), mload(ret))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
183
contracts/gnosis/EIP4337Manager.sol
Normal file
183
contracts/gnosis/EIP4337Manager.sol
Normal file
@@ -0,0 +1,183 @@
|
||||
//SPDX-License-Identifier: GPL
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
/* solhint-disable avoid-low-level-calls */
|
||||
/* solhint-disable no-inline-assembly */
|
||||
/* solhint-disable reason-string */
|
||||
|
||||
import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
|
||||
import "@gnosis.pm/safe-contracts/contracts/GnosisSafe.sol";
|
||||
import "./EIP4337Fallback.sol";
|
||||
import "../EntryPoint.sol";
|
||||
using ECDSA for bytes32;
|
||||
|
||||
/**
|
||||
* Main EIP4337 module.
|
||||
* Called (through the fallback module) using "delegate" from the GnosisSafe as an "IWallet",
|
||||
* so must implement validateUserOp
|
||||
* holds an immutable reference to the EntryPoint
|
||||
* Inherits GnosisSafeStorage so that it can reference the memory storage
|
||||
*/
|
||||
contract EIP4337Manager is GnosisSafe, IWallet {
|
||||
|
||||
address public immutable eip4337Fallback;
|
||||
address public immutable entryPoint;
|
||||
|
||||
constructor(address anEntryPoint) {
|
||||
entryPoint = anEntryPoint;
|
||||
eip4337Fallback = address(new EIP4337Fallback(address(this)));
|
||||
}
|
||||
|
||||
/**
|
||||
* delegate-called (using execFromModule) through the fallback, so "real" msg.sender is attached as last 20 bytes
|
||||
*/
|
||||
function validateUserOp(UserOperation calldata userOp, bytes32 requestId, uint256 missingWalletFunds) external override {
|
||||
address _msgSender = address(bytes20(msg.data[msg.data.length - 20 :]));
|
||||
require(_msgSender == entryPoint, "wallet: not from entrypoint");
|
||||
|
||||
GnosisSafe pThis = GnosisSafe(payable(address(this)));
|
||||
bytes32 hash = requestId.toEthSignedMessageHash();
|
||||
address recovered = hash.recover(userOp.signature);
|
||||
require(threshold == 1, "wallet: only threshold 1");
|
||||
require(pThis.isOwner(recovered), "wallet: wrong signature");
|
||||
|
||||
if (userOp.initCode.length == 0) {
|
||||
require(nonce++ == userOp.nonce, "wallet: invalid nonce");
|
||||
}
|
||||
|
||||
if (missingWalletFunds > 0) {
|
||||
//TODO: MAY pay more than the minimum, to deposit for future transactions
|
||||
(bool success,) = payable(_msgSender).call{value : missingWalletFunds}("");
|
||||
(success);
|
||||
//ignore failure (its EntryPoint's job to verify, not wallet.)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* set up a safe as EIP-4337 enabled.
|
||||
* called from the GnosisSafeProxy4337 during construction time
|
||||
* - enable 3 modules (this module, fallback and the entrypoint)
|
||||
* - this method is called with delegateCall, so the module (usually itself) is passed as parameter, and "this" is the safe itself
|
||||
*/
|
||||
function setupEIP4337(
|
||||
address singleton,
|
||||
EIP4337Manager manager,
|
||||
address owner
|
||||
) external {
|
||||
address eip4337fallback = manager.eip4337Fallback();
|
||||
|
||||
address[] memory owners = new address[](1);
|
||||
owners[0] = owner;
|
||||
uint threshold = 1;
|
||||
|
||||
execute(singleton, 0, abi.encodeCall(GnosisSafe.setup, (
|
||||
owners, threshold,
|
||||
address(0), "", //no delegate call
|
||||
eip4337fallback,
|
||||
address(0), 0, payable(0) //no payment receiver
|
||||
)),
|
||||
Enum.Operation.DelegateCall, gasleft()
|
||||
);
|
||||
|
||||
_enableModule(manager.entryPoint());
|
||||
_enableModule(eip4337fallback);
|
||||
}
|
||||
|
||||
/**
|
||||
* replace EIP4337 module, to support a new EntryPoint.
|
||||
* must be called using execTransaction and Enum.Operation.DelegateCall
|
||||
* @param prevModule returned by getCurrentEIP4337Manager
|
||||
* @param oldManager the old EIP4337 manager to remove, returned by getCurrentEIP4337Manager
|
||||
* @param newManager the new EIP4337Manager, usually with a new EntryPoint
|
||||
*/
|
||||
function replaceEIP4337Manager(address prevModule, EIP4337Manager oldManager, EIP4337Manager newManager) public {
|
||||
|
||||
GnosisSafe pThis = GnosisSafe(payable(address(this)));
|
||||
address oldFallback = oldManager.eip4337Fallback();
|
||||
require(pThis.isModuleEnabled(oldFallback), "replaceEIP4337Manager: oldManager is not active");
|
||||
pThis.disableModule(oldFallback, oldManager.entryPoint());
|
||||
pThis.disableModule(prevModule, oldFallback);
|
||||
|
||||
address eip4337fallback = newManager.eip4337Fallback();
|
||||
|
||||
pThis.enableModule(newManager.entryPoint());
|
||||
pThis.enableModule(eip4337fallback);
|
||||
|
||||
pThis.setFallbackHandler(eip4337fallback);
|
||||
|
||||
validateEip4337(pThis, newManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate this gnosisSafe is callable through the EntryPoint.
|
||||
* the test is might be incomplete: we check that we reach our validateUserOp and fail on signature.
|
||||
* we don't test full transaction
|
||||
*/
|
||||
function validateEip4337(GnosisSafe safe, EIP4337Manager manager) public {
|
||||
|
||||
// this prevent mistaken replaceEIP4337Manager to disable the module completely.
|
||||
// minimal signature that pass "recover"
|
||||
bytes memory sig = new bytes(65);
|
||||
sig[64] = bytes1(uint8(27));
|
||||
sig[2] = bytes1(uint8(1));
|
||||
sig[35] = bytes1(uint8(1));
|
||||
UserOperation memory userOp = UserOperation(address(safe), 0, "", "", 0, 1000000, 0, 0, 0, address(0), "", sig);
|
||||
UserOperation[] memory userOps = new UserOperation[](1);
|
||||
userOps[0] = userOp;
|
||||
EntryPoint _entryPoint = EntryPoint(payable(manager.entryPoint()));
|
||||
try _entryPoint.handleOps(userOps, payable(msg.sender)) {
|
||||
revert("validateEip4337: handleOps must fail");
|
||||
} catch (bytes memory error) {
|
||||
if (keccak256(error) != keccak256(abi.encodeWithSignature("FailedOp(uint256,address,string)", 0, address(0), "wallet: wrong signature"))) {
|
||||
revert(string(error));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function delegateCall(address to, bytes memory data) internal {
|
||||
bool success;
|
||||
assembly {
|
||||
success := delegatecall(sub(0, 1), to, add(data, 0x20), mload(data), 0, 0)
|
||||
}
|
||||
require(success, "delegate failed");
|
||||
}
|
||||
|
||||
/// copied from GnosisSafe ModuleManager, FallbackManager
|
||||
/// enableModule is "external authorizeOnly", can't be used during construction using a "delegatecall"
|
||||
|
||||
/// @dev Allows to add a module to the whitelist.
|
||||
/// this is a variant of enableModule that is used only during construction
|
||||
/// @notice Enables the module `module` for the Safe.
|
||||
/// @param module Module to be whitelisted.
|
||||
function _enableModule(address module) private {
|
||||
|
||||
// Module address cannot be null or sentinel.
|
||||
require(module != address(0) && module != SENTINEL_MODULES, "GS101");
|
||||
// Module cannot be added twice.
|
||||
require(modules[module] == address(0), "GS102");
|
||||
modules[module] = modules[SENTINEL_MODULES];
|
||||
modules[SENTINEL_MODULES] = module;
|
||||
emit EnabledModule(module);
|
||||
}
|
||||
|
||||
/**
|
||||
* enumerate modules, and find the currently active EIP4337 manager (and previous module)
|
||||
* @return prev prev module, needed by replaceEIP4337Manager
|
||||
* @return manager the current active EIP4337Manager
|
||||
*/
|
||||
function getCurrentEIP4337Manager(GnosisSafe safe) public view returns (address prev, address manager) {
|
||||
|
||||
prev = address(SENTINEL_MODULES);
|
||||
(address[] memory modules,) = safe.getModulesPaginated(SENTINEL_MODULES, 100);
|
||||
for (uint i = 0; i < modules.length; i++) {
|
||||
address module = modules[i];
|
||||
(bool success,bytes memory ret) = module.staticcall(abi.encodeWithSignature("eip4337manager()"));
|
||||
if (success) {
|
||||
manager = abi.decode(ret, (address));
|
||||
return (prev, manager);
|
||||
}
|
||||
prev = module;
|
||||
}
|
||||
return (address(0), address(0));
|
||||
}
|
||||
}
|
||||
24
contracts/gnosis/GnosisSafeProxy4337.sol
Normal file
24
contracts/gnosis/GnosisSafeProxy4337.sol
Normal file
@@ -0,0 +1,24 @@
|
||||
//SPDX-License-Identifier: GPL
|
||||
pragma solidity ^0.8.7;
|
||||
|
||||
/* solhint-disable avoid-low-level-calls */
|
||||
|
||||
import "./EIP4337Manager.sol";
|
||||
import "@gnosis.pm/safe-contracts/contracts/proxies/GnosisSafeProxy.sol";
|
||||
|
||||
/**
|
||||
* Create a proxy to a GnosisSafe, which accepts calls through Account-Abstraction.
|
||||
* The created GnosisSafe has a single owner.
|
||||
* It is possible to add more owners, but currently, it can only be accessed via Account-Abstraction
|
||||
* if the owners threshold is exactly 1.
|
||||
*/
|
||||
contract SafeProxy4337 is GnosisSafeProxy {
|
||||
constructor(
|
||||
address singleton, EIP4337Manager aaModule,
|
||||
address owner
|
||||
) GnosisSafeProxy(singleton) {
|
||||
(bool success,bytes memory ret) = address(aaModule).delegatecall(abi.encodeCall(
|
||||
EIP4337Manager.setupEIP4337, (singleton, aaModule, owner)));
|
||||
require(success, string(ret));
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ function getNetwork (name: string): { url: string, accounts: { mnemonic: string
|
||||
|
||||
const config: HardhatUserConfig = {
|
||||
solidity: {
|
||||
version: '0.8.12',
|
||||
version: '0.8.15',
|
||||
settings: {
|
||||
optimizer: { enabled: true, runs: 1000000 }
|
||||
}
|
||||
|
||||
@@ -49,6 +49,7 @@
|
||||
"typechain": "^8.1.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@gnosis.pm/safe-contracts": "^1.3.0",
|
||||
"@nomiclabs/hardhat-etherscan": "^2.1.6",
|
||||
"@openzeppelin/contracts": "^4.2.0",
|
||||
"@typechain/hardhat": "^2.3.0",
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import './aa.init'
|
||||
import { describe } from 'mocha'
|
||||
import { BigNumber, Wallet } from 'ethers'
|
||||
import { expect } from 'chai'
|
||||
import {
|
||||
|
||||
198
test/gnosis.test.ts
Normal file
198
test/gnosis.test.ts
Normal file
@@ -0,0 +1,198 @@
|
||||
import './aa.init'
|
||||
import { ethers } from 'hardhat'
|
||||
import { Signer } from 'ethers'
|
||||
import {
|
||||
EIP4337Manager,
|
||||
EIP4337Manager__factory,
|
||||
EntryPoint,
|
||||
GnosisSafe,
|
||||
GnosisSafe__factory,
|
||||
SafeProxy4337,
|
||||
SafeProxy4337__factory,
|
||||
TestCounter,
|
||||
TestCounter__factory
|
||||
} from '../typechain'
|
||||
import {
|
||||
AddressZero,
|
||||
createAddress,
|
||||
createWalletOwner,
|
||||
deployEntryPoint,
|
||||
getBalance,
|
||||
HashZero,
|
||||
isContractDeployed
|
||||
} from './testutils'
|
||||
import { fillAndSign } from './UserOp'
|
||||
import { defaultAbiCoder, hexConcat, hexZeroPad, parseEther } from 'ethers/lib/utils'
|
||||
import { expect } from 'chai'
|
||||
|
||||
describe('Gnosis Proxy', function () {
|
||||
this.timeout(30000)
|
||||
|
||||
let ethersSigner: Signer
|
||||
let safeSingleton: GnosisSafe
|
||||
let owner: Signer
|
||||
let ownerAddress: string
|
||||
let proxy: SafeProxy4337
|
||||
let manager: EIP4337Manager
|
||||
let entryPoint: EntryPoint
|
||||
let counter: TestCounter
|
||||
let proxySafe: GnosisSafe
|
||||
let safe_execTxCallData: string
|
||||
|
||||
before('before', async function () {
|
||||
// EIP4337Manager fails to compile with solc-coverage
|
||||
if (process.env.COVERAGE != null) {
|
||||
return this.skip()
|
||||
}
|
||||
|
||||
const provider = ethers.provider
|
||||
ethersSigner = provider.getSigner()
|
||||
safeSingleton = await new GnosisSafe__factory(ethersSigner).deploy()
|
||||
entryPoint = await deployEntryPoint(1, 1)
|
||||
manager = await new EIP4337Manager__factory(ethersSigner).deploy(entryPoint.address)
|
||||
owner = createWalletOwner()
|
||||
ownerAddress = await owner.getAddress()
|
||||
counter = await new TestCounter__factory(ethersSigner).deploy()
|
||||
|
||||
proxy = await new SafeProxy4337__factory(ethersSigner).deploy(safeSingleton.address, manager.address, ownerAddress)
|
||||
|
||||
proxySafe = GnosisSafe__factory.connect(proxy.address, owner)
|
||||
|
||||
await ethersSigner.sendTransaction({ to: proxy.address, value: parseEther('0.1') })
|
||||
|
||||
const counter_countCallData = counter.interface.encodeFunctionData('count')
|
||||
safe_execTxCallData = safeSingleton.interface.encodeFunctionData('execTransactionFromModule', [counter.address, 0, counter_countCallData, 0])
|
||||
})
|
||||
let beneficiary: string
|
||||
beforeEach(() => {
|
||||
beneficiary = createAddress()
|
||||
})
|
||||
|
||||
it('should validate', async function () {
|
||||
await manager.callStatic.validateEip4337(proxySafe.address, manager.address, { gasLimit: 10e6 })
|
||||
})
|
||||
|
||||
it('should fail from wrong entrypoint', async function () {
|
||||
const op = await fillAndSign({
|
||||
sender: proxy.address
|
||||
}, owner, entryPoint)
|
||||
|
||||
const anotherEntryPoint = await deployEntryPoint(2, 2)
|
||||
|
||||
await expect(anotherEntryPoint.handleOps([op], beneficiary)).to.revertedWith('wallet: not from entrypoint')
|
||||
})
|
||||
|
||||
it('should fail on invalid userop', async function () {
|
||||
const op = await fillAndSign({
|
||||
sender: proxy.address,
|
||||
nonce: 1234,
|
||||
callGas: 1e6,
|
||||
callData: safe_execTxCallData
|
||||
}, owner, entryPoint)
|
||||
await expect(entryPoint.handleOps([op], beneficiary)).to.revertedWith('wallet: invalid nonce')
|
||||
|
||||
op.callGas = 1
|
||||
await expect(entryPoint.handleOps([op], beneficiary)).to.revertedWith('wallet: wrong signature')
|
||||
})
|
||||
|
||||
it('should exec', async function () {
|
||||
const op = await fillAndSign({
|
||||
sender: proxy.address,
|
||||
callGas: 1e6,
|
||||
callData: safe_execTxCallData
|
||||
}, owner, entryPoint)
|
||||
const rcpt = await entryPoint.handleOps([op], beneficiary).then(async r => r.wait())
|
||||
console.log('gasUsed=', rcpt.gasUsed, rcpt.transactionHash)
|
||||
|
||||
const ev = rcpt.events!.find(ev => ev.event === 'UserOperationEvent')!
|
||||
expect(ev.args!.success).to.eq(true)
|
||||
expect(await getBalance(beneficiary)).to.eq(ev.args!.actualGasCost)
|
||||
})
|
||||
|
||||
let counterfactualAddress: string
|
||||
it('should create wallet', async function () {
|
||||
const initCode = await new SafeProxy4337__factory(ethersSigner).getDeployTransaction(safeSingleton.address, manager.address, ownerAddress).data!
|
||||
|
||||
const salt = Date.now()
|
||||
counterfactualAddress = await entryPoint.getSenderAddress(initCode, salt)
|
||||
expect(!await isContractDeployed(counterfactualAddress))
|
||||
|
||||
await ethersSigner.sendTransaction({ to: counterfactualAddress, value: parseEther('0.1') })
|
||||
const op = await fillAndSign({
|
||||
initCode,
|
||||
nonce: salt,
|
||||
verificationGas: 400000
|
||||
}, owner, entryPoint)
|
||||
|
||||
const rcpt = await entryPoint.handleOps([op], beneficiary).then(async r => r.wait())
|
||||
console.log('gasUsed=', rcpt.gasUsed, rcpt.transactionHash)
|
||||
expect(await isContractDeployed(counterfactualAddress))
|
||||
|
||||
const newCode = await ethers.provider.getCode(counterfactualAddress)
|
||||
expect(newCode.length).eq(324)
|
||||
})
|
||||
|
||||
it('another op after creation', async function () {
|
||||
if (counterfactualAddress == null) this.skip()
|
||||
expect(await isContractDeployed(counterfactualAddress))
|
||||
|
||||
const op = await fillAndSign({
|
||||
sender: counterfactualAddress,
|
||||
callData: safe_execTxCallData
|
||||
}, owner, entryPoint)
|
||||
|
||||
const rcpt = await entryPoint.handleOps([op], beneficiary).then(async r => r.wait())
|
||||
console.log('gasUsed=', rcpt.gasUsed, rcpt.transactionHash)
|
||||
})
|
||||
|
||||
context('#replaceEIP4337', () => {
|
||||
let signature: string
|
||||
let newEntryPoint: EntryPoint
|
||||
let newFallback: string
|
||||
let newManager: EIP4337Manager
|
||||
let oldManager: string
|
||||
let prev: string
|
||||
|
||||
before(async () => {
|
||||
// sig is r{32}s{32}v{1}. for trusting the caller, r=address, v=1
|
||||
signature = hexConcat([
|
||||
hexZeroPad(ownerAddress, 32),
|
||||
HashZero,
|
||||
'0x01'])
|
||||
newEntryPoint = await deployEntryPoint(2, 2)
|
||||
newManager = await new EIP4337Manager__factory(ethersSigner).deploy(newEntryPoint.address)
|
||||
newFallback = await newManager.eip4337Fallback();
|
||||
[prev, oldManager] = await manager.getCurrentEIP4337Manager(proxySafe.address)
|
||||
})
|
||||
|
||||
it('should reject to replace if wrong old manager', async () => {
|
||||
const replaceManagerCallData = manager.interface.encodeFunctionData('replaceEIP4337Manager',
|
||||
[prev, newManager.address, oldManager])
|
||||
// using call from module, so it return value..
|
||||
const proxyFromModule = proxySafe.connect(entryPoint.address)
|
||||
const ret = await proxyFromModule.callStatic.execTransactionFromModuleReturnData(manager.address, 0, replaceManagerCallData, 1)
|
||||
const [errorStr] = defaultAbiCoder.decode(['string'], ret.returnData.replace(/0x.{8}/, '0x'))
|
||||
expect(errorStr).to.equal('replaceEIP4337Manager: oldManager is not active')
|
||||
})
|
||||
|
||||
it('should replace manager', async function () {
|
||||
const oldFallback = await manager.eip4337Fallback()
|
||||
expect(await proxySafe.isModuleEnabled(entryPoint.address)).to.equal(true)
|
||||
expect(await proxySafe.isModuleEnabled(oldFallback)).to.equal(true)
|
||||
|
||||
expect(oldManager.toLowerCase()).to.eq(manager.address.toLowerCase())
|
||||
await ethersSigner.sendTransaction({ to: ownerAddress, value: parseEther('0.1') })
|
||||
|
||||
const replaceManagerCallData = manager.interface.encodeFunctionData('replaceEIP4337Manager',
|
||||
[prev, oldManager, newManager.address])
|
||||
await proxySafe.execTransaction(manager.address, 0, replaceManagerCallData, 1, 1e6, 0, 0, AddressZero, AddressZero, signature).then(async r => r.wait())
|
||||
|
||||
// console.log(rcpt.events?.slice(-1)[0].event)
|
||||
|
||||
expect(await proxySafe.isModuleEnabled(newEntryPoint.address)).to.equal(true)
|
||||
expect(await proxySafe.isModuleEnabled(newFallback)).to.equal(true)
|
||||
expect(await proxySafe.isModuleEnabled(entryPoint.address)).to.equal(false)
|
||||
expect(await proxySafe.isModuleEnabled(oldFallback)).to.equal(false)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,4 +1,3 @@
|
||||
import { describe } from 'mocha'
|
||||
import { Wallet } from 'ethers'
|
||||
import { ethers } from 'hardhat'
|
||||
import { expect } from 'chai'
|
||||
@@ -254,7 +253,7 @@ describe('EntryPoint with paymaster', function () {
|
||||
await paymaster.unlockStake()
|
||||
const amount = await entryPoint.getDepositInfo(paymaster.address).then(info => info.stake)
|
||||
expect(amount).to.be.gte(ONE_ETH.div(2))
|
||||
await ethers.provider.send('evm_mine', [Math.floor(Date.now() / 1000) + 100])
|
||||
await ethers.provider.send('evm_mine', [Math.floor(Date.now() / 1000) + 1000])
|
||||
await paymaster.withdrawStake(withdrawAddress)
|
||||
expect(await ethers.provider.getBalance(withdrawAddress)).to.eql(amount)
|
||||
expect(await entryPoint.getDepositInfo(paymaster.address).then(info => info.stake)).to.eq(0)
|
||||
|
||||
@@ -225,7 +225,7 @@ export async function deployEntryPoint (paymasterStake: BigNumberish, unstakeDel
|
||||
return EntryPoint__factory.connect(addr, provider.getSigner())
|
||||
}
|
||||
|
||||
export async function isDeployed (addr: string): Promise<boolean> {
|
||||
export async function isContractDeployed (addr: string): Promise<boolean> {
|
||||
const code = await ethers.provider.getCode(addr)
|
||||
return code.length > 2
|
||||
}
|
||||
|
||||
@@ -597,6 +597,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.6.0.tgz#602afbbfcfb7e169210469b697365ef740d7e930"
|
||||
integrity sha512-DWSsg8zMHOYMYBqIQi96BQuthZrp98LCeMNcUOaffCIVYQ5yxDbNikLF+H7jEnmNNmXbtVic46iCuVWzar+MgA==
|
||||
|
||||
"@gnosis.pm/safe-contracts@^1.3.0":
|
||||
version "1.3.0"
|
||||
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-contracts/-/safe-contracts-1.3.0.tgz#316741a7690d8751a1f701538cfc9ec80866eedc"
|
||||
integrity sha512-1p+1HwGvxGUVzVkFjNzglwHrLNA67U/axP0Ct85FzzH8yhGJb4t9jDjPYocVMzLorDoWAfKicGy1akPY9jXRVw==
|
||||
|
||||
"@nodelib/fs.scandir@2.1.5":
|
||||
version "2.1.5"
|
||||
resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
|
||||
|
||||
Reference in New Issue
Block a user