MinimalForwarder.test.js 6.6 KB

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