Merge GSNContext into GSNRecipient (#1906)
* Merge GSNContext into GSNRecipient
* Update GSNRecipient.test.js
* Update GSNRecipient.sol
* Make GSNRecipient inherit from Context
(cherry picked from commit 1efa9f6281
)
release-v2.4.0
parent
5f1d8e19da
commit
dba4d5023d
@ -1,88 +0,0 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
import "./Context.sol"; |
||||
|
||||
/** |
||||
* @dev Enables GSN support on `Context` contracts by recognizing calls from |
||||
* RelayHub and extracting the actual sender and call data from the received |
||||
* calldata. |
||||
* |
||||
* > This contract does not perform all required tasks to implement a GSN |
||||
* recipient contract: end users should use `GSNRecipient` instead. |
||||
*/ |
||||
contract GSNContext is Context { |
||||
address internal _relayHub = 0xD216153c06E857cD7f72665E0aF1d7D82172F494; |
||||
|
||||
event RelayHubChanged(address indexed oldRelayHub, address indexed newRelayHub); |
||||
|
||||
constructor() internal { |
||||
// solhint-disable-previous-line no-empty-blocks |
||||
} |
||||
|
||||
function _upgradeRelayHub(address newRelayHub) internal { |
||||
address currentRelayHub = _relayHub; |
||||
require(newRelayHub != address(0), "GSNContext: new RelayHub is the zero address"); |
||||
require(newRelayHub != currentRelayHub, "GSNContext: new RelayHub is the current one"); |
||||
|
||||
emit RelayHubChanged(currentRelayHub, newRelayHub); |
||||
|
||||
_relayHub = newRelayHub; |
||||
} |
||||
|
||||
// Overrides for Context's functions: when called from RelayHub, sender and |
||||
// data require some pre-processing: the actual sender is stored at the end |
||||
// of the call data, which in turns means it needs to be removed from it |
||||
// when handling said data. |
||||
|
||||
function _msgSender() internal view returns (address) { |
||||
if (msg.sender != _relayHub) { |
||||
return msg.sender; |
||||
} else { |
||||
return _getRelayedCallSender(); |
||||
} |
||||
} |
||||
|
||||
function _msgData() internal view returns (bytes memory) { |
||||
if (msg.sender != _relayHub) { |
||||
return msg.data; |
||||
} else { |
||||
return _getRelayedCallData(); |
||||
} |
||||
} |
||||
|
||||
function _getRelayedCallSender() private pure returns (address result) { |
||||
// We need to read 20 bytes (an address) located at array index msg.data.length - 20. In memory, the array |
||||
// is prefixed with a 32-byte length value, so we first add 32 to get the memory read index. However, doing |
||||
// so would leave the address in the upper 20 bytes of the 32-byte word, which is inconvenient and would |
||||
// require bit shifting. We therefore subtract 12 from the read index so the address lands on the lower 20 |
||||
// bytes. This can always be done due to the 32-byte prefix. |
||||
|
||||
// The final memory read index is msg.data.length - 20 + 32 - 12 = msg.data.length. Using inline assembly is the |
||||
// easiest/most-efficient way to perform this operation. |
||||
|
||||
// These fields are not accessible from assembly |
||||
bytes memory array = msg.data; |
||||
uint256 index = msg.data.length; |
||||
|
||||
// solhint-disable-next-line no-inline-assembly |
||||
assembly { |
||||
// Load the 32 bytes word from memory with the address on the lower 20 bytes, and mask those. |
||||
result := and(mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff) |
||||
} |
||||
return result; |
||||
} |
||||
|
||||
function _getRelayedCallData() private pure returns (bytes memory) { |
||||
// RelayHub appends the sender address at the end of the calldata, so in order to retrieve the actual msg.data, |
||||
// we must strip the last 20 bytes (length of an address type) from it. |
||||
|
||||
uint256 actualDataLength = msg.data.length - 20; |
||||
bytes memory actualData = new bytes(actualDataLength); |
||||
|
||||
for (uint256 i = 0; i < actualDataLength; ++i) { |
||||
actualData[i] = msg.data[i]; |
||||
} |
||||
|
||||
return actualData; |
||||
} |
||||
} |
@ -1,46 +0,0 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
import "./ContextMock.sol"; |
||||
import "../GSN/GSNContext.sol"; |
||||
import "../GSN/IRelayRecipient.sol"; |
||||
|
||||
// By inheriting from GSNContext, Context's internal functions are overridden automatically |
||||
contract GSNContextMock is ContextMock, GSNContext, IRelayRecipient { |
||||
function getHubAddr() public view returns (address) { |
||||
return _relayHub; |
||||
} |
||||
|
||||
function acceptRelayedCall( |
||||
address, |
||||
address, |
||||
bytes calldata, |
||||
uint256, |
||||
uint256, |
||||
uint256, |
||||
uint256, |
||||
bytes calldata, |
||||
uint256 |
||||
) |
||||
external |
||||
view |
||||
returns (uint256, bytes memory) |
||||
{ |
||||
return (0, ""); |
||||
} |
||||
|
||||
function preRelayedCall(bytes calldata) external returns (bytes32) { |
||||
// solhint-disable-previous-line no-empty-blocks |
||||
} |
||||
|
||||
function postRelayedCall(bytes calldata, bool, uint256, bytes32) external { |
||||
// solhint-disable-previous-line no-empty-blocks |
||||
} |
||||
|
||||
function getRelayHub() public view returns (address) { |
||||
return _relayHub; |
||||
} |
||||
|
||||
function upgradeRelayHub(address newRelayHub) public { |
||||
return _upgradeRelayHub(newRelayHub); |
||||
} |
||||
} |
@ -1,78 +0,0 @@ |
||||
const { BN, constants, expectEvent, expectRevert } = require('openzeppelin-test-helpers'); |
||||
const { ZERO_ADDRESS } = constants; |
||||
const gsn = require('@openzeppelin/gsn-helpers'); |
||||
|
||||
const GSNContextMock = artifacts.require('GSNContextMock'); |
||||
const ContextMockCaller = artifacts.require('ContextMockCaller'); |
||||
|
||||
const { shouldBehaveLikeRegularContext } = require('./Context.behavior'); |
||||
|
||||
contract('GSNContext', function ([_, deployer, sender, newRelayHub]) { |
||||
beforeEach(async function () { |
||||
this.context = await GSNContextMock.new(); |
||||
this.caller = await ContextMockCaller.new(); |
||||
}); |
||||
|
||||
describe('get/set RelayHub', function () { |
||||
const singletonRelayHub = '0xD216153c06E857cD7f72665E0aF1d7D82172F494'; |
||||
|
||||
it('initially returns the singleton instance address', async function () { |
||||
expect(await this.context.getRelayHub()).to.equal(singletonRelayHub); |
||||
}); |
||||
|
||||
it('can be upgraded to a new RelayHub', async function () { |
||||
const { logs } = await this.context.upgradeRelayHub(newRelayHub); |
||||
expectEvent.inLogs(logs, 'RelayHubChanged', { oldRelayHub: singletonRelayHub, newRelayHub }); |
||||
}); |
||||
|
||||
it('cannot upgrade to the same RelayHub', async function () { |
||||
await expectRevert( |
||||
this.context.upgradeRelayHub(singletonRelayHub), |
||||
'GSNContext: new RelayHub is the current one' |
||||
); |
||||
}); |
||||
|
||||
it('cannot upgrade to the zero address', async function () { |
||||
await expectRevert(this.context.upgradeRelayHub(ZERO_ADDRESS), 'GSNContext: new RelayHub is the zero address'); |
||||
}); |
||||
|
||||
context('with new RelayHub', function () { |
||||
beforeEach(async function () { |
||||
await this.context.upgradeRelayHub(newRelayHub); |
||||
}); |
||||
|
||||
it('returns the new instance address', async function () { |
||||
expect(await this.context.getRelayHub()).to.equal(newRelayHub); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
context('when called directly', function () { |
||||
shouldBehaveLikeRegularContext(sender); |
||||
}); |
||||
|
||||
context('when receiving a relayed call', function () { |
||||
beforeEach(async function () { |
||||
await gsn.fundRecipient(web3, { recipient: this.context.address }); |
||||
}); |
||||
|
||||
describe('msgSender', function () { |
||||
it('returns the relayed transaction original sender', async function () { |
||||
const { tx } = await this.context.msgSender({ from: sender, useGSN: true }); |
||||
await expectEvent.inTransaction(tx, GSNContextMock, 'Sender', { sender }); |
||||
}); |
||||
}); |
||||
|
||||
describe('msgData', function () { |
||||
it('returns the relayed transaction original data', async function () { |
||||
const integerValue = new BN('42'); |
||||
const stringValue = 'OpenZeppelin'; |
||||
const callData = this.context.contract.methods.msgData(integerValue.toString(), stringValue).encodeABI(); |
||||
|
||||
// The provider doesn't properly estimate gas for a relayed call, so we need to manually set a higher value
|
||||
const { tx } = await this.context.msgData(integerValue, stringValue, { gas: 1000000, useGSN: true }); |
||||
await expectEvent.inTransaction(tx, GSNContextMock, 'Data', { data: callData, integerValue, stringValue }); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
Loading…
Reference in new issue