MinimalForwarder.test.js 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. const ethSigUtil = require('eth-sig-util');
  2. const Wallet = require('ethereumjs-wallet').default;
  3. const { getDomain, domainType } = require('../helpers/eip712');
  4. const { expectRevert, constants } = require('@openzeppelin/test-helpers');
  5. const { expect } = require('chai');
  6. const MinimalForwarder = artifacts.require('MinimalForwarder');
  7. const CallReceiverMock = artifacts.require('CallReceiverMock');
  8. contract('MinimalForwarder', function (accounts) {
  9. beforeEach(async function () {
  10. this.forwarder = await MinimalForwarder.new();
  11. this.domain = await getDomain(this.forwarder);
  12. this.types = {
  13. EIP712Domain: domainType(this.domain),
  14. ForwardRequest: [
  15. { name: 'from', type: 'address' },
  16. { name: 'to', type: 'address' },
  17. { name: 'value', type: 'uint256' },
  18. { name: 'gas', type: 'uint256' },
  19. { name: 'nonce', type: 'uint256' },
  20. { name: 'data', type: 'bytes' },
  21. ],
  22. };
  23. });
  24. context('with message', function () {
  25. beforeEach(async function () {
  26. this.wallet = Wallet.generate();
  27. this.sender = web3.utils.toChecksumAddress(this.wallet.getAddressString());
  28. this.req = {
  29. from: this.sender,
  30. to: constants.ZERO_ADDRESS,
  31. value: '0',
  32. gas: '100000',
  33. nonce: Number(await this.forwarder.getNonce(this.sender)),
  34. data: '0x',
  35. };
  36. this.sign = () =>
  37. ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), {
  38. data: {
  39. types: this.types,
  40. domain: this.domain,
  41. primaryType: 'ForwardRequest',
  42. message: this.req,
  43. },
  44. });
  45. });
  46. context('verify', function () {
  47. context('valid signature', function () {
  48. beforeEach(async function () {
  49. expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce));
  50. });
  51. it('success', async function () {
  52. expect(await this.forwarder.verify(this.req, this.sign())).to.be.equal(true);
  53. });
  54. afterEach(async function () {
  55. expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce));
  56. });
  57. });
  58. context('invalid signature', function () {
  59. it('tampered from', async function () {
  60. expect(await this.forwarder.verify({ ...this.req, from: accounts[0] }, this.sign())).to.be.equal(false);
  61. });
  62. it('tampered to', async function () {
  63. expect(await this.forwarder.verify({ ...this.req, to: accounts[0] }, this.sign())).to.be.equal(false);
  64. });
  65. it('tampered value', async function () {
  66. expect(await this.forwarder.verify({ ...this.req, value: web3.utils.toWei('1') }, this.sign())).to.be.equal(
  67. false,
  68. );
  69. });
  70. it('tampered nonce', async function () {
  71. expect(await this.forwarder.verify({ ...this.req, nonce: this.req.nonce + 1 }, this.sign())).to.be.equal(
  72. false,
  73. );
  74. });
  75. it('tampered data', async function () {
  76. expect(await this.forwarder.verify({ ...this.req, data: '0x1742' }, this.sign())).to.be.equal(false);
  77. });
  78. it('tampered signature', async function () {
  79. const tamperedsign = web3.utils.hexToBytes(this.sign());
  80. tamperedsign[42] ^= 0xff;
  81. expect(await this.forwarder.verify(this.req, web3.utils.bytesToHex(tamperedsign))).to.be.equal(false);
  82. });
  83. });
  84. });
  85. context('execute', function () {
  86. context('valid signature', function () {
  87. beforeEach(async function () {
  88. expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce));
  89. });
  90. it('success', async function () {
  91. await this.forwarder.execute(this.req, this.sign()); // expect to not revert
  92. });
  93. afterEach(async function () {
  94. expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(
  95. web3.utils.toBN(this.req.nonce + 1),
  96. );
  97. });
  98. });
  99. context('invalid signature', function () {
  100. it('tampered from', async function () {
  101. await expectRevert(
  102. this.forwarder.execute({ ...this.req, from: accounts[0] }, this.sign()),
  103. 'MinimalForwarder: signature does not match request',
  104. );
  105. });
  106. it('tampered to', async function () {
  107. await expectRevert(
  108. this.forwarder.execute({ ...this.req, to: accounts[0] }, this.sign()),
  109. 'MinimalForwarder: signature does not match request',
  110. );
  111. });
  112. it('tampered value', async function () {
  113. await expectRevert(
  114. this.forwarder.execute({ ...this.req, value: web3.utils.toWei('1') }, this.sign()),
  115. 'MinimalForwarder: signature does not match request',
  116. );
  117. });
  118. it('tampered nonce', async function () {
  119. await expectRevert(
  120. this.forwarder.execute({ ...this.req, nonce: this.req.nonce + 1 }, this.sign()),
  121. 'MinimalForwarder: signature does not match request',
  122. );
  123. });
  124. it('tampered data', async function () {
  125. await expectRevert(
  126. this.forwarder.execute({ ...this.req, data: '0x1742' }, this.sign()),
  127. 'MinimalForwarder: signature does not match request',
  128. );
  129. });
  130. it('tampered signature', async function () {
  131. const tamperedsign = web3.utils.hexToBytes(this.sign());
  132. tamperedsign[42] ^= 0xff;
  133. await expectRevert(
  134. this.forwarder.execute(this.req, web3.utils.bytesToHex(tamperedsign)),
  135. 'MinimalForwarder: signature does not match request',
  136. );
  137. });
  138. });
  139. it('bubble out of gas', async function () {
  140. const receiver = await CallReceiverMock.new();
  141. const gasAvailable = 100000;
  142. this.req.to = receiver.address;
  143. this.req.data = receiver.contract.methods.mockFunctionOutOfGas().encodeABI();
  144. this.req.gas = 1000000;
  145. await expectRevert.assertion(this.forwarder.execute(this.req, this.sign(), { gas: gasAvailable }));
  146. const { transactions } = await web3.eth.getBlock('latest');
  147. const { gasUsed } = await web3.eth.getTransactionReceipt(transactions[0]);
  148. expect(gasUsed).to.be.equal(gasAvailable);
  149. });
  150. });
  151. });
  152. });