Files
openzeppelin-contracts/test/account/extensions/ERC7821.behavior.js
2025-06-02 08:22:57 -06:00

146 lines
5.4 KiB
JavaScript

const { ethers, entrypoint } = require('hardhat');
const { expect } = require('chai');
const { CALL_TYPE_BATCH, encodeMode, encodeBatch } = require('../../helpers/erc7579');
function shouldBehaveLikeERC7821({ deployable = true } = {}) {
describe('supports ERC-7821', function () {
beforeEach(async function () {
// give eth to the account (before deployment)
await this.other.sendTransaction({ to: this.mock.target, value: ethers.parseEther('1') });
// account is not initially deployed
await expect(ethers.provider.getCode(this.mock)).to.eventually.equal('0x');
this.encodeUserOpCalldata = (...calls) =>
this.mock.interface.encodeFunctionData('execute', [
encodeMode({ callType: CALL_TYPE_BATCH }),
encodeBatch(...calls),
]);
});
it('should revert if the caller is not the canonical entrypoint or the account itself', async function () {
await this.mock.deploy();
await expect(
this.mock.connect(this.other).execute(
encodeMode({ callType: CALL_TYPE_BATCH }),
encodeBatch({
target: this.target,
data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
}),
),
)
.to.be.revertedWithCustomError(this.mock, 'AccountUnauthorized')
.withArgs(this.other);
});
if (deployable) {
describe('when not deployed', function () {
it('should be created with handleOps and increase nonce', async function () {
const operation = await this.mock
.createUserOp({
callData: this.encodeUserOpCalldata({
target: this.target,
value: 17,
data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
}),
})
.then(op => op.addInitCode())
.then(op => this.signUserOp(op));
// Can't call the account to get its nonce before it's deployed
await expect(entrypoint.v08.getNonce(this.mock.target, 0)).to.eventually.equal(0);
await expect(entrypoint.v08.handleOps([operation.packed], this.beneficiary))
.to.emit(entrypoint.v08, 'AccountDeployed')
.withArgs(operation.hash(), this.mock, this.helper.factory, ethers.ZeroAddress)
.to.emit(this.target, 'MockFunctionCalledExtra')
.withArgs(this.mock, 17);
await expect(this.mock.getNonce()).to.eventually.equal(1);
});
it('should revert if the signature is invalid', async function () {
const operation = await this.mock
.createUserOp({
callData: this.encodeUserOpCalldata({
target: this.target,
value: 17,
data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
}),
})
.then(op => op.addInitCode());
operation.signature = '0x00';
await expect(entrypoint.v08.handleOps([operation.packed], this.beneficiary)).to.be.reverted;
});
});
}
describe('when deployed', function () {
beforeEach(async function () {
await this.mock.deploy();
});
it('should increase nonce and call target', async function () {
const operation = await this.mock
.createUserOp({
callData: this.encodeUserOpCalldata({
target: this.target,
value: 42,
data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
}),
})
.then(op => this.signUserOp(op));
await expect(this.mock.getNonce()).to.eventually.equal(0);
await expect(entrypoint.v08.handleOps([operation.packed], this.beneficiary))
.to.emit(this.target, 'MockFunctionCalledExtra')
.withArgs(this.mock, 42);
await expect(this.mock.getNonce()).to.eventually.equal(1);
});
it('should support sending eth to an EOA', async function () {
const operation = await this.mock
.createUserOp({ callData: this.encodeUserOpCalldata({ target: this.other, value: 42 }) })
.then(op => this.signUserOp(op));
await expect(this.mock.getNonce()).to.eventually.equal(0);
await expect(entrypoint.v08.handleOps([operation.packed], this.beneficiary)).to.changeEtherBalance(
this.other,
42,
);
await expect(this.mock.getNonce()).to.eventually.equal(1);
});
it('should support batch execution', async function () {
const value1 = 43374337n;
const value2 = 69420n;
const operation = await this.mock
.createUserOp({
callData: this.encodeUserOpCalldata(
{ target: this.other, value: value1 },
{
target: this.target,
value: value2,
data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
},
),
})
.then(op => this.signUserOp(op));
await expect(this.mock.getNonce()).to.eventually.equal(0);
const tx = entrypoint.v08.handleOps([operation.packed], this.beneficiary);
await expect(tx).to.changeEtherBalances([this.other, this.target], [value1, value2]);
await expect(tx).to.emit(this.target, 'MockFunctionCalledExtra').withArgs(this.mock, value2);
await expect(this.mock.getNonce()).to.eventually.equal(1);
});
});
});
}
module.exports = {
shouldBehaveLikeERC7821,
};