ERC7821.behavior.js 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145
  1. const { ethers, predeploy } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { CALL_TYPE_BATCH, encodeMode, encodeBatch } = require('../../helpers/erc7579');
  4. function shouldBehaveLikeERC7821({ deployable = true } = {}) {
  5. describe('supports ERC-7821', function () {
  6. beforeEach(async function () {
  7. // give eth to the account (before deployment)
  8. await this.other.sendTransaction({ to: this.mock.target, value: ethers.parseEther('1') });
  9. // account is not initially deployed
  10. await expect(ethers.provider.getCode(this.mock)).to.eventually.equal('0x');
  11. this.encodeUserOpCalldata = (...calls) =>
  12. this.mock.interface.encodeFunctionData('execute', [
  13. encodeMode({ callType: CALL_TYPE_BATCH }),
  14. encodeBatch(...calls),
  15. ]);
  16. });
  17. it('should revert if the caller is not the canonical entrypoint or the account itself', async function () {
  18. await this.mock.deploy();
  19. await expect(
  20. this.mock.connect(this.other).execute(
  21. encodeMode({ callType: CALL_TYPE_BATCH }),
  22. encodeBatch({
  23. target: this.target,
  24. data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
  25. }),
  26. ),
  27. )
  28. .to.be.revertedWithCustomError(this.mock, 'AccountUnauthorized')
  29. .withArgs(this.other);
  30. });
  31. if (deployable) {
  32. describe('when not deployed', function () {
  33. it('should be created with handleOps and increase nonce', async function () {
  34. const operation = await this.mock
  35. .createUserOp({
  36. callData: this.encodeUserOpCalldata({
  37. target: this.target,
  38. value: 17,
  39. data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
  40. }),
  41. })
  42. .then(op => op.addInitCode())
  43. .then(op => this.signUserOp(op));
  44. // Can't call the account to get its nonce before it's deployed
  45. await expect(predeploy.entrypoint.v08.getNonce(this.mock.target, 0)).to.eventually.equal(0);
  46. await expect(predeploy.entrypoint.v08.handleOps([operation.packed], this.beneficiary))
  47. .to.emit(predeploy.entrypoint.v08, 'AccountDeployed')
  48. .withArgs(operation.hash(), this.mock, this.helper.factory, ethers.ZeroAddress)
  49. .to.emit(this.target, 'MockFunctionCalledExtra')
  50. .withArgs(this.mock, 17);
  51. await expect(this.mock.getNonce()).to.eventually.equal(1);
  52. });
  53. it('should revert if the signature is invalid', async function () {
  54. const operation = await this.mock
  55. .createUserOp({
  56. callData: this.encodeUserOpCalldata({
  57. target: this.target,
  58. value: 17,
  59. data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
  60. }),
  61. })
  62. .then(op => op.addInitCode());
  63. operation.signature = '0x00';
  64. await expect(predeploy.entrypoint.v08.handleOps([operation.packed], this.beneficiary)).to.be.reverted;
  65. });
  66. });
  67. }
  68. describe('when deployed', function () {
  69. beforeEach(async function () {
  70. await this.mock.deploy();
  71. });
  72. it('should increase nonce and call target', async function () {
  73. const operation = await this.mock
  74. .createUserOp({
  75. callData: this.encodeUserOpCalldata({
  76. target: this.target,
  77. value: 42,
  78. data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
  79. }),
  80. })
  81. .then(op => this.signUserOp(op));
  82. await expect(this.mock.getNonce()).to.eventually.equal(0);
  83. await expect(predeploy.entrypoint.v08.handleOps([operation.packed], this.beneficiary))
  84. .to.emit(this.target, 'MockFunctionCalledExtra')
  85. .withArgs(this.mock, 42);
  86. await expect(this.mock.getNonce()).to.eventually.equal(1);
  87. });
  88. it('should support sending eth to an EOA', async function () {
  89. const operation = await this.mock
  90. .createUserOp({ callData: this.encodeUserOpCalldata({ target: this.other, value: 42 }) })
  91. .then(op => this.signUserOp(op));
  92. await expect(this.mock.getNonce()).to.eventually.equal(0);
  93. await expect(predeploy.entrypoint.v08.handleOps([operation.packed], this.beneficiary)).to.changeEtherBalance(
  94. this.other,
  95. 42,
  96. );
  97. await expect(this.mock.getNonce()).to.eventually.equal(1);
  98. });
  99. it('should support batch execution', async function () {
  100. const value1 = 43374337n;
  101. const value2 = 69420n;
  102. const operation = await this.mock
  103. .createUserOp({
  104. callData: this.encodeUserOpCalldata(
  105. { target: this.other, value: value1 },
  106. {
  107. target: this.target,
  108. value: value2,
  109. data: this.target.interface.encodeFunctionData('mockFunctionExtra'),
  110. },
  111. ),
  112. })
  113. .then(op => this.signUserOp(op));
  114. await expect(this.mock.getNonce()).to.eventually.equal(0);
  115. const tx = predeploy.entrypoint.v08.handleOps([operation.packed], this.beneficiary);
  116. await expect(tx).to.changeEtherBalances([this.other, this.target], [value1, value2]);
  117. await expect(tx).to.emit(this.target, 'MockFunctionCalledExtra').withArgs(this.mock, value2);
  118. await expect(this.mock.getNonce()).to.eventually.equal(1);
  119. });
  120. });
  121. });
  122. }
  123. module.exports = {
  124. shouldBehaveLikeERC7821,
  125. };