Skip to content
Closed
Show file tree
Hide file tree
Changes from 1 commit
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
22 changes: 10 additions & 12 deletions contracts/account/modules/ERC7579DelayedExecutor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -112,18 +112,6 @@ abstract contract ERC7579DelayedExecutor is ERC7579Executor {
return OperationState.Ready;
}

/// @dev See {ERC7579Executor-canExecute}. Allows anyone to execute an operation if it's {OperationState-Ready}.
function canExecute(
address account,
Mode mode,
bytes calldata executionCalldata,
bytes32 salt
) public view virtual override returns (bool) {
return
state(account, mode, executionCalldata, salt) == OperationState.Ready ||
super.canExecute(account, mode, executionCalldata, salt);
}

/**
* @dev Whether the caller is authorized to cancel operations.
* By default, this checks if the caller is the account itself. Derived contracts can
Expand Down Expand Up @@ -370,6 +358,16 @@ abstract contract ERC7579DelayedExecutor is ERC7579Executor {
return (id, schedule_);
}

/// @dev See {ERC7579Executor-canExecute}. Allows anyone to execute an operation if it's {OperationState-Ready}.
function canExecute(
address /* account */,
Mode /* mode */,
bytes calldata /* executionCalldata */,
bytes32 /* salt */
) public view virtual override returns (bool) {
return true;
}

/**
* @dev See {ERC7579Executor-_execute}.
*
Expand Down
46 changes: 27 additions & 19 deletions test/account/modules/ERC7579DelayedExecutor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -168,50 +168,58 @@ describe('ERC7579DelayedExecutor', function () {
await this.mock.$_schedule(this.mockAccount.address, this.mode, this.calldata, salt);
});

it('reverts with ERC7579UnauthorizedExecution before delay passes with any caller', async function () {
it('reverts with ERC7579ExecutorUnexpectedOperationState before delay passes with any caller', async function () {
// call from the account
await expect(
this.mockFromAccount.execute(this.mockAccount.address, this.mode, this.calldata, salt),
).to.be.revertedWithCustomError(this.mock, 'ERC7579ExecutorUnexpectedOperationState');

// call from anyone
await expect(
this.mock.execute(this.mockAccount.address, this.mode, this.calldata, salt),
).to.be.revertedWithCustomError(this.mock, 'ERC7579UnauthorizedExecution');
).to.be.revertedWithCustomError(this.mock, 'ERC7579ExecutorUnexpectedOperationState');
});

it('reverts with ERC7579UnauthorizedExecution before delay passes with the account as caller', async function () {
await expect(
this.mockFromAccount.execute(this.mockAccount.address, this.mode, this.calldata, salt),
).to.be.revertedWithCustomError(this.mock, 'ERC7579ExecutorUnexpectedOperationState'); // Allowed, not ready
it('executes if called by the account when delay passes but has not expired', async function () {
await time.increase(this.delay);
await expect(this.mock.execute(this.mockAccount.address, this.mode, this.calldata, salt))
.to.emit(this.target, 'MockFunctionCalledWithArgs')
.withArgs(...this.args);
});

it('executes if called by the account when delay passes but has not expired with any caller', async function () {
it('executes if called by any caller when delay passes but has not expired', async function () {
await time.increase(this.delay);
await expect(this.mock.execute(this.mockAccount.address, this.mode, this.calldata, salt))
.to.emit(this.target, 'MockFunctionCalledWithArgs')
.withArgs(...this.args);
await expect(
this.mock.execute(this.mockAccount.address, this.mode, this.calldata, salt),
).to.be.revertedWithCustomError(this.mock, 'ERC7579UnauthorizedExecution'); // Can't execute twice
});

it('executes if called by the account when delay passes but has not expired with the account as caller', async function () {
it('reverts with ERC7579ExecutorUnexpectedOperationState if the operation is already executed and called by any caller', async function () {
await time.increase(this.delay);
await expect(this.mockFromAccount.execute(this.mockAccount.address, this.mode, this.calldata, salt))
// call from anyone
await expect(this.mock.execute(this.mockAccount.address, this.mode, this.calldata, salt))
.to.emit(this.target, 'MockFunctionCalledWithArgs')
.withArgs(...this.args);
// call from anyone
await expect(
this.mock.execute(this.mockAccount.address, this.mode, this.calldata, salt),
).to.be.revertedWithCustomError(this.mock, 'ERC7579ExecutorUnexpectedOperationState'); // Can't execute twice
// call from the account
await expect(
this.mockFromAccount.execute(this.mockAccount.address, this.mode, this.calldata, salt),
).to.be.revertedWithCustomError(this.mock, 'ERC7579ExecutorUnexpectedOperationState'); // Can't execute twice
});

it('reverts if the operation was expired with any caller', async function () {
it('reverts with ERC7579ExecutorUnexpectedOperationState if the operation was expired with any caller', async function () {
await time.increase(this.delay + this.expiration);
// call from anyone
await expect(
this.mock.execute(this.mockAccount.address, this.mode, this.calldata, salt),
).to.be.revertedWithCustomError(this.mock, 'ERC7579UnauthorizedExecution');
});

it('reverts if the operation was expired with the account as caller', async function () {
await time.increase(this.delay + this.expiration);
).to.be.revertedWithCustomError(this.mock, 'ERC7579ExecutorUnexpectedOperationState');
// call from the account
await expect(
this.mockFromAccount.execute(this.mockAccount.address, this.mode, this.calldata, salt),
).to.be.revertedWithCustomError(this.mock, 'ERC7579ExecutorUnexpectedOperationState'); // Allowed, expired
).to.be.revertedWithCustomError(this.mock, 'ERC7579ExecutorUnexpectedOperationState');
});
});

Expand Down
Loading