ERC2771Context.test.js 4.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. const ethSigUtil = require('eth-sig-util');
  2. const Wallet = require('ethereumjs-wallet').default;
  3. const { getDomain, domainType } = require('../helpers/eip712');
  4. const { MAX_UINT48 } = require('../helpers/constants');
  5. const { expectEvent } = require('@openzeppelin/test-helpers');
  6. const { expect } = require('chai');
  7. const ERC2771ContextMock = artifacts.require('ERC2771ContextMock');
  8. const ERC2771Forwarder = artifacts.require('ERC2771Forwarder');
  9. const ContextMockCaller = artifacts.require('ContextMockCaller');
  10. const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior');
  11. contract('ERC2771Context', function (accounts) {
  12. const [, trustedForwarder] = accounts;
  13. beforeEach(async function () {
  14. this.forwarder = await ERC2771Forwarder.new('ERC2771Forwarder');
  15. this.recipient = await ERC2771ContextMock.new(this.forwarder.address);
  16. this.domain = await getDomain(this.forwarder);
  17. this.types = {
  18. EIP712Domain: domainType(this.domain),
  19. ForwardRequest: [
  20. { name: 'from', type: 'address' },
  21. { name: 'to', type: 'address' },
  22. { name: 'value', type: 'uint256' },
  23. { name: 'gas', type: 'uint256' },
  24. { name: 'nonce', type: 'uint256' },
  25. { name: 'deadline', type: 'uint48' },
  26. { name: 'data', type: 'bytes' },
  27. ],
  28. };
  29. });
  30. it('recognize trusted forwarder', async function () {
  31. expect(await this.recipient.isTrustedForwarder(this.forwarder.address)).to.equal(true);
  32. });
  33. it('returns the trusted forwarder', async function () {
  34. expect(await this.recipient.trustedForwarder()).to.equal(this.forwarder.address);
  35. });
  36. context('when called directly', function () {
  37. beforeEach(async function () {
  38. this.context = this.recipient; // The Context behavior expects the contract in this.context
  39. this.caller = await ContextMockCaller.new();
  40. });
  41. shouldBehaveLikeRegularContext(...accounts);
  42. });
  43. context('when receiving a relayed call', function () {
  44. beforeEach(async function () {
  45. this.wallet = Wallet.generate();
  46. this.sender = web3.utils.toChecksumAddress(this.wallet.getAddressString());
  47. this.data = {
  48. types: this.types,
  49. domain: this.domain,
  50. primaryType: 'ForwardRequest',
  51. };
  52. });
  53. describe('msgSender', function () {
  54. it('returns the relayed transaction original sender', async function () {
  55. const data = this.recipient.contract.methods.msgSender().encodeABI();
  56. const req = {
  57. from: this.sender,
  58. to: this.recipient.address,
  59. value: '0',
  60. gas: '100000',
  61. nonce: (await this.forwarder.nonces(this.sender)).toString(),
  62. deadline: MAX_UINT48,
  63. data,
  64. };
  65. req.signature = ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), {
  66. data: { ...this.data, message: req },
  67. });
  68. expect(await this.forwarder.verify(req)).to.equal(true);
  69. const { tx } = await this.forwarder.execute(req);
  70. await expectEvent.inTransaction(tx, ERC2771ContextMock, 'Sender', { sender: this.sender });
  71. });
  72. it('returns the original sender when calldata length is less than 20 bytes (address length)', async function () {
  73. // The forwarder doesn't produce calls with calldata length less than 20 bytes
  74. const recipient = await ERC2771ContextMock.new(trustedForwarder);
  75. const { receipt } = await recipient.msgSender({ from: trustedForwarder });
  76. await expectEvent(receipt, 'Sender', { sender: trustedForwarder });
  77. });
  78. });
  79. describe('msgData', function () {
  80. it('returns the relayed transaction original data', async function () {
  81. const integerValue = '42';
  82. const stringValue = 'OpenZeppelin';
  83. const data = this.recipient.contract.methods.msgData(integerValue, stringValue).encodeABI();
  84. const req = {
  85. from: this.sender,
  86. to: this.recipient.address,
  87. value: '0',
  88. gas: '100000',
  89. nonce: (await this.forwarder.nonces(this.sender)).toString(),
  90. deadline: MAX_UINT48,
  91. data,
  92. };
  93. req.signature = ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), {
  94. data: { ...this.data, message: req },
  95. });
  96. expect(await this.forwarder.verify(req)).to.equal(true);
  97. const { tx } = await this.forwarder.execute(req);
  98. await expectEvent.inTransaction(tx, ERC2771ContextMock, 'Data', { data, integerValue, stringValue });
  99. });
  100. });
  101. it('returns the full original data when calldata length is less than 20 bytes (address length)', async function () {
  102. // The forwarder doesn't produce calls with calldata length less than 20 bytes
  103. const recipient = await ERC2771ContextMock.new(trustedForwarder);
  104. const { receipt } = await recipient.msgDataShort({ from: trustedForwarder });
  105. const data = recipient.contract.methods.msgDataShort().encodeABI();
  106. await expectEvent(receipt, 'DataShort', { data });
  107. });
  108. });
  109. });