146 lines
5.4 KiB
JavaScript
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,
|
|
};
|