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