MinimalForwarder.test.js 6.5 KB

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