ソースを参照

Adjust ERC2771Context._msgData for msg.data.length < 20 (#4484)

Francisco 2 年 前
コミット
9445f96223

+ 5 - 0
.changeset/warm-guests-rule.md

@@ -0,0 +1,5 @@
+---
+'openzeppelin-solidity': patch
+---
+
+`ERC2771Context`: Prevent revert in `_msgData()` when a call originating from a trusted forwarder is not long enough to contain the request signer address (i.e. `msg.data.length` is less than 20 bytes). Return the full calldata in that case.

+ 1 - 1
contracts/metatx/ERC2771Context.sol

@@ -34,7 +34,7 @@ abstract contract ERC2771Context is Context {
     }
 
     function _msgData() internal view virtual override returns (bytes calldata) {
-        if (isTrustedForwarder(msg.sender)) {
+        if (isTrustedForwarder(msg.sender) && msg.data.length >= 20) {
             return msg.data[:msg.data.length - 20];
         } else {
             return super._msgData();

+ 6 - 0
contracts/mocks/ContextMock.sol

@@ -16,6 +16,12 @@ contract ContextMock is Context {
     function msgData(uint256 integerValue, string memory stringValue) public {
         emit Data(_msgData(), integerValue, stringValue);
     }
+
+    event DataShort(bytes data);
+
+    function msgDataShort() public {
+        emit DataShort(_msgData());
+    }
 }
 
 contract ContextMockCaller {

+ 14 - 4
test/metatx/ERC2771Context.test.js

@@ -12,7 +12,7 @@ const ContextMockCaller = artifacts.require('ContextMockCaller');
 const { shouldBehaveLikeRegularContext } = require('../utils/Context.behavior');
 
 contract('ERC2771Context', function (accounts) {
-  const [, anotherAccount] = accounts;
+  const [, trustedForwarder] = accounts;
 
   const MAX_UINT48 = web3.utils.toBN(1).shln(48).subn(1).toString();
 
@@ -84,11 +84,11 @@ contract('ERC2771Context', function (accounts) {
 
       it('returns the original sender when calldata length is less than 20 bytes (address length)', async function () {
         // The forwarder doesn't produce calls with calldata length less than 20 bytes
-        const recipient = await ERC2771ContextMock.new(anotherAccount);
+        const recipient = await ERC2771ContextMock.new(trustedForwarder);
 
-        const { receipt } = await recipient.msgSender({ from: anotherAccount });
+        const { receipt } = await recipient.msgSender({ from: trustedForwarder });
 
-        await expectEvent(receipt, 'Sender', { sender: anotherAccount });
+        await expectEvent(receipt, 'Sender', { sender: trustedForwarder });
       });
     });
 
@@ -117,5 +117,15 @@ contract('ERC2771Context', function (accounts) {
         await expectEvent.inTransaction(tx, ERC2771ContextMock, 'Data', { data, integerValue, stringValue });
       });
     });
+
+    it('returns the full original data when calldata length is less than 20 bytes (address length)', async function () {
+      // The forwarder doesn't produce calls with calldata length less than 20 bytes
+      const recipient = await ERC2771ContextMock.new(trustedForwarder);
+
+      const { receipt } = await recipient.msgDataShort({ from: trustedForwarder });
+
+      const data = recipient.contract.methods.msgDataShort().encodeABI();
+      await expectEvent(receipt, 'DataShort', { data });
+    });
   });
 });