MinimalForwarder.test.js 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  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 = () =>
  45. ethSigUtil.signTypedMessage(this.wallet.getPrivateKey(), {
  46. data: {
  47. types: this.types,
  48. domain: this.domain,
  49. primaryType: 'ForwardRequest',
  50. message: this.req,
  51. },
  52. });
  53. });
  54. context('verify', function () {
  55. context('valid signature', function () {
  56. beforeEach(async function () {
  57. expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce));
  58. });
  59. it('success', async function () {
  60. expect(await this.forwarder.verify(this.req, this.sign())).to.be.equal(true);
  61. });
  62. afterEach(async function () {
  63. expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce));
  64. });
  65. });
  66. context('invalid signature', function () {
  67. it('tampered from', async function () {
  68. expect(await this.forwarder.verify({ ...this.req, from: accounts[0] }, this.sign())).to.be.equal(false);
  69. });
  70. it('tampered to', async function () {
  71. expect(await this.forwarder.verify({ ...this.req, to: accounts[0] }, this.sign())).to.be.equal(false);
  72. });
  73. it('tampered value', async function () {
  74. expect(await this.forwarder.verify({ ...this.req, value: web3.utils.toWei('1') }, this.sign())).to.be.equal(
  75. false,
  76. );
  77. });
  78. it('tampered nonce', async function () {
  79. expect(await this.forwarder.verify({ ...this.req, nonce: this.req.nonce + 1 }, this.sign())).to.be.equal(
  80. false,
  81. );
  82. });
  83. it('tampered data', async function () {
  84. expect(await this.forwarder.verify({ ...this.req, data: '0x1742' }, this.sign())).to.be.equal(false);
  85. });
  86. it('tampered signature', async function () {
  87. const tamperedsign = web3.utils.hexToBytes(this.sign());
  88. tamperedsign[42] ^= 0xff;
  89. expect(await this.forwarder.verify(this.req, web3.utils.bytesToHex(tamperedsign))).to.be.equal(false);
  90. });
  91. });
  92. });
  93. context('execute', function () {
  94. context('valid signature', function () {
  95. beforeEach(async function () {
  96. expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(web3.utils.toBN(this.req.nonce));
  97. });
  98. it('success', async function () {
  99. await this.forwarder.execute(this.req, this.sign()); // expect to not revert
  100. });
  101. afterEach(async function () {
  102. expect(await this.forwarder.getNonce(this.req.from)).to.be.bignumber.equal(
  103. web3.utils.toBN(this.req.nonce + 1),
  104. );
  105. });
  106. });
  107. context('invalid signature', function () {
  108. it('tampered from', async function () {
  109. await expectRevert(
  110. this.forwarder.execute({ ...this.req, from: accounts[0] }, this.sign()),
  111. 'MinimalForwarder: signature does not match request',
  112. );
  113. });
  114. it('tampered to', async function () {
  115. await expectRevert(
  116. this.forwarder.execute({ ...this.req, to: accounts[0] }, this.sign()),
  117. 'MinimalForwarder: signature does not match request',
  118. );
  119. });
  120. it('tampered value', async function () {
  121. await expectRevert(
  122. this.forwarder.execute({ ...this.req, value: web3.utils.toWei('1') }, this.sign()),
  123. 'MinimalForwarder: signature does not match request',
  124. );
  125. });
  126. it('tampered nonce', async function () {
  127. await expectRevert(
  128. this.forwarder.execute({ ...this.req, nonce: this.req.nonce + 1 }, this.sign()),
  129. 'MinimalForwarder: signature does not match request',
  130. );
  131. });
  132. it('tampered data', async function () {
  133. await expectRevert(
  134. this.forwarder.execute({ ...this.req, data: '0x1742' }, this.sign()),
  135. 'MinimalForwarder: signature does not match request',
  136. );
  137. });
  138. it('tampered signature', async function () {
  139. const tamperedsign = web3.utils.hexToBytes(this.sign());
  140. tamperedsign[42] ^= 0xff;
  141. await expectRevert(
  142. this.forwarder.execute(this.req, web3.utils.bytesToHex(tamperedsign)),
  143. 'MinimalForwarder: signature does not match request',
  144. );
  145. });
  146. });
  147. it('bubble out of gas', async function () {
  148. const receiver = await CallReceiverMock.new();
  149. const gasAvailable = 100000;
  150. this.req.to = receiver.address;
  151. this.req.data = receiver.contract.methods.mockFunctionOutOfGas().encodeABI();
  152. this.req.gas = 1000000;
  153. await expectRevert.assertion(this.forwarder.execute(this.req, this.sign(), { gas: gasAvailable }));
  154. const { transactions } = await web3.eth.getBlock('latest');
  155. const { gasUsed } = await web3.eth.getTransactionReceipt(transactions[0]);
  156. expect(gasUsed).to.be.equal(gasAvailable);
  157. });
  158. });
  159. });
  160. });