* GSN support Add base Context contract Add GSNContext and tests Add RelayHub deployment to tests Add RelayProvider integration, complete GSNContext tests Switch dependency to openzeppelin-gsn-provider Add default txfee to provider Add basic signing recipient Sign more values Add comment clarifying RelayHub's msg.data Make context constructors internal Rename SigningRecipient to GSNRecipientSignedData Add ERC20Charge recipients Harcode RelayHub address into GSNContext Fix Solidity linter errors Run server from binary, use gsn-helpers to fund it Migrate to published @openzeppelin/gsn-helpers Silence false-positive compiler warning Use GSN helper assertions Rename meta-tx to gsn, take out of drafts Merge ERC20 charge recipients into a single one Rename GSNRecipients to Bouncers Add GSNBouncerUtils to decouple the bouncers from GSNRecipient Add _upgradeRelayHub Store RelayHub address using unstructored storage Add IRelayHub Add _withdrawDeposits to GSNRecipient Add relayHub version to recipient Make _acceptRelayedCall and _declineRelayedCall easier to use Rename GSNBouncerUtils to GSNBouncerBase, make it IRelayRecipient Improve GSNBouncerBase, make pre and post sender-protected and optional Fix GSNBouncerERC20Fee, add tests Add missing GSNBouncerSignature test Override transferFrom in __unstable__ERC20PrimaryAdmin Rhub address slot reduced by 1 Rename relay hub changed event Use released gsn-provider * move gsn to all caps * update to gsn contracts in solidity/master * Adapt for ethereum-package * update gsn related packages * update dependencies to match contracts repo * remove mocha bail option * add changelog entry * add constructors to mocks * use unstructured storage for bouncer implementations
104 lines
4.2 KiB
Solidity
104 lines
4.2 KiB
Solidity
pragma solidity ^0.5.0;
|
|
|
|
import "@openzeppelin/upgrades/contracts/Initializable.sol";
|
|
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 Initializable, Context {
|
|
// We use a random storage slot to allow proxy contracts to enable GSN support in an upgrade without changing their
|
|
// storage layout. This value is calculated as: keccak256('gsn.relayhub.address'), minus 1.
|
|
bytes32 private constant RELAY_HUB_ADDRESS_STORAGE_SLOT = 0x06b7792c761dcc05af1761f0315ce8b01ac39c16cc934eb0b2f7a8e71414f262;
|
|
|
|
event RelayHubChanged(address indexed oldRelayHub, address indexed newRelayHub);
|
|
|
|
function initialize() public initializer {
|
|
_upgradeRelayHub(0xD216153c06E857cD7f72665E0aF1d7D82172F494);
|
|
}
|
|
|
|
function _getRelayHub() internal view returns (address relayHub) {
|
|
bytes32 slot = RELAY_HUB_ADDRESS_STORAGE_SLOT;
|
|
// solhint-disable-next-line no-inline-assembly
|
|
assembly {
|
|
relayHub := sload(slot)
|
|
}
|
|
}
|
|
|
|
function _upgradeRelayHub(address newRelayHub) internal {
|
|
address currentRelayHub = _getRelayHub();
|
|
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);
|
|
|
|
bytes32 slot = RELAY_HUB_ADDRESS_STORAGE_SLOT;
|
|
// solhint-disable-next-line no-inline-assembly
|
|
assembly {
|
|
sstore(slot, 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 != _getRelayHub()) {
|
|
return msg.sender;
|
|
} else {
|
|
return _getRelayedCallSender();
|
|
}
|
|
}
|
|
|
|
function _msgData() internal view returns (bytes memory) {
|
|
if (msg.sender != _getRelayHub()) {
|
|
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;
|
|
}
|
|
}
|