ERC2771Context.test.js 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117
  1. const { ethers } = require('hardhat');
  2. const { expect } = require('chai');
  3. const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers');
  4. const { impersonate } = require('../helpers/account');
  5. const { getDomain } = require('../helpers/eip712');
  6. const { MAX_UINT48 } = require('../helpers/constants');
  7. const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior');
  8. async function fixture() {
  9. const [sender] = await ethers.getSigners();
  10. const forwarder = await ethers.deployContract('ERC2771Forwarder', []);
  11. const forwarderAsSigner = await impersonate(forwarder.target);
  12. const context = await ethers.deployContract('ERC2771ContextMock', [forwarder]);
  13. const domain = await getDomain(forwarder);
  14. const types = {
  15. ForwardRequest: [
  16. { name: 'from', type: 'address' },
  17. { name: 'to', type: 'address' },
  18. { name: 'value', type: 'uint256' },
  19. { name: 'gas', type: 'uint256' },
  20. { name: 'nonce', type: 'uint256' },
  21. { name: 'deadline', type: 'uint48' },
  22. { name: 'data', type: 'bytes' },
  23. ],
  24. };
  25. return { sender, forwarder, forwarderAsSigner, context, domain, types };
  26. }
  27. describe('ERC2771Context', function () {
  28. beforeEach(async function () {
  29. Object.assign(this, await loadFixture(fixture));
  30. });
  31. it('recognize trusted forwarder', async function () {
  32. expect(await this.context.isTrustedForwarder(this.forwarder)).to.equal(true);
  33. });
  34. it('returns the trusted forwarder', async function () {
  35. expect(await this.context.trustedForwarder()).to.equal(this.forwarder.target);
  36. });
  37. describe('when called directly', function () {
  38. shouldBehaveLikeRegularContext();
  39. });
  40. describe('when receiving a relayed call', function () {
  41. describe('msgSender', function () {
  42. it('returns the relayed transaction original sender', async function () {
  43. const nonce = await this.forwarder.nonces(this.sender);
  44. const data = this.context.interface.encodeFunctionData('msgSender');
  45. const req = {
  46. from: await this.sender.getAddress(),
  47. to: await this.context.getAddress(),
  48. value: 0n,
  49. data,
  50. gas: 100000n,
  51. nonce,
  52. deadline: MAX_UINT48,
  53. };
  54. req.signature = await this.sender.signTypedData(this.domain, this.types, req);
  55. expect(await this.forwarder.verify(req)).to.equal(true);
  56. await expect(this.forwarder.execute(req)).to.emit(this.context, 'Sender').withArgs(this.sender.address);
  57. });
  58. it('returns the original sender when calldata length is less than 20 bytes (address length)', async function () {
  59. // The forwarder doesn't produce calls with calldata length less than 20 bytes so `this.forwarderAsSigner` is used instead.
  60. await expect(this.context.connect(this.forwarderAsSigner).msgSender())
  61. .to.emit(this.context, 'Sender')
  62. .withArgs(this.forwarder.target);
  63. });
  64. });
  65. describe('msgData', function () {
  66. it('returns the relayed transaction original data', async function () {
  67. const args = [42n, 'OpenZeppelin'];
  68. const nonce = await this.forwarder.nonces(this.sender);
  69. const data = this.context.interface.encodeFunctionData('msgData', args);
  70. const req = {
  71. from: await this.sender.getAddress(),
  72. to: await this.context.getAddress(),
  73. value: 0n,
  74. data,
  75. gas: 100000n,
  76. nonce,
  77. deadline: MAX_UINT48,
  78. };
  79. req.signature = this.sender.signTypedData(this.domain, this.types, req);
  80. expect(await this.forwarder.verify(req)).to.equal(true);
  81. await expect(this.forwarder.execute(req))
  82. .to.emit(this.context, 'Data')
  83. .withArgs(data, ...args);
  84. });
  85. });
  86. it('returns the full original data when calldata length is less than 20 bytes (address length)', async function () {
  87. const data = this.context.interface.encodeFunctionData('msgDataShort');
  88. // The forwarder doesn't produce calls with calldata length less than 20 bytes so `this.forwarderAsSigner` is used instead.
  89. await expect(await this.context.connect(this.forwarderAsSigner).msgDataShort())
  90. .to.emit(this.context, 'DataShort')
  91. .withArgs(data);
  92. });
  93. });
  94. });