parent
2e28b70e68
commit
f12245b3b5
@ -0,0 +1,71 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
import "./Context.sol"; |
||||
|
||||
/* |
||||
* @dev Enables GSN support by recognizing calls from RelayHub and extracting |
||||
* the actual sender and call data from the received values. |
||||
*/ |
||||
contract GSNContext is Context { |
||||
address private _relayHub; |
||||
|
||||
constructor(address relayHub) public { |
||||
_setRelayHub(relayHub); |
||||
} |
||||
|
||||
function _setRelayHub(address relayHub) internal { |
||||
_relayHub = relayHub; |
||||
} |
||||
|
||||
// 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 |
||||
|
||||
// These fields are not accessible from assembly |
||||
bytes memory array = msg.data; |
||||
uint256 index = msg.data.length; |
||||
|
||||
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) { |
||||
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; |
||||
} |
||||
} |
@ -0,0 +1,10 @@ |
||||
pragma solidity ^0.5.0; |
||||
|
||||
import "./ContextMock.sol"; |
||||
import "../drafts/meta-tx/GSNContext.sol"; |
||||
|
||||
// By inheriting from GSNContext, the internal functions are overridden automatically |
||||
contract GSNContextMock is ContextMock, GSNContext { |
||||
constructor(address relayHub) public GSNContext(relayHub) { |
||||
} |
||||
} |
@ -0,0 +1,42 @@ |
||||
const { BN, expectEvent } = require('openzeppelin-test-helpers'); |
||||
|
||||
const ContextMock = artifacts.require('ContextMock'); |
||||
|
||||
function shouldBehaveLikeRegularContext(sender) { |
||||
describe('msgSender', function () { |
||||
it('returns the transaction sender when called from an EOA', async function () { |
||||
const { logs } = await this.context.msgSender({ from: sender }); |
||||
expectEvent.inLogs(logs, 'Sender', { sender }); |
||||
}); |
||||
|
||||
it('returns the transaction sender when from another contract', async function () { |
||||
const { tx } = await this.caller.callSender(this.context.address, { from: sender }); |
||||
await expectEvent.inTransaction(tx, ContextMock, 'Sender', { sender: this.caller.address }); |
||||
}); |
||||
}); |
||||
|
||||
describe('msgData', function () { |
||||
const integerValue = new BN('42'); |
||||
const stringValue = 'OpenZeppelin'; |
||||
|
||||
let callData; |
||||
|
||||
beforeEach(async function () { |
||||
callData = this.context.contract.methods.msgData(integerValue.toString(), stringValue).encodeABI(); |
||||
}); |
||||
|
||||
it('returns the transaction data when called from an EOA', async function () { |
||||
const { logs } = await this.context.msgData(integerValue, stringValue); |
||||
expectEvent.inLogs(logs, 'Data', { data: callData, integerValue, stringValue }); |
||||
}); |
||||
|
||||
it('returns the transaction sender when from another contract', async function () { |
||||
const { tx } = await this.caller.callData(this.context.address, integerValue, stringValue); |
||||
await expectEvent.inTransaction(tx, ContextMock, 'Data', { data: callData, integerValue, stringValue }); |
||||
}); |
||||
}); |
||||
} |
||||
|
||||
module.exports = { |
||||
shouldBehaveLikeRegularContext |
||||
}; |
@ -1,44 +1,15 @@ |
||||
const { BN, expectEvent } = require('openzeppelin-test-helpers'); |
||||
require('openzeppelin-test-helpers'); |
||||
|
||||
const ContextMock = artifacts.require('ContextMock'); |
||||
const ContextMockCaller = artifacts.require('ContextMockCaller'); |
||||
|
||||
const { shouldBehaveLikeRegularContext } = require('./Context.behavior'); |
||||
|
||||
contract('Context', function ([_, sender]) { |
||||
beforeEach(async function () { |
||||
this.context = await ContextMock.new(); |
||||
this.caller = await ContextMockCaller.new(); |
||||
}); |
||||
|
||||
describe('msgSender', function () { |
||||
it('returns the transaction sender when called directly', async function () { |
||||
const { logs } = await this.context.msgSender({ from: sender }); |
||||
expectEvent.inLogs(logs, 'Sender', { sender }); |
||||
}); |
||||
|
||||
it('returns the transaction sender when from another contract', async function () { |
||||
const { tx } = await this.caller.callSender(this.context.address, { from: sender }); |
||||
await expectEvent.inTransaction(tx, ContextMock, 'Sender', { sender: this.caller.address }); |
||||
}); |
||||
}); |
||||
|
||||
describe('msgData', function () { |
||||
const integerValue = new BN('42'); |
||||
const stringValue = 'OpenZeppelin'; |
||||
|
||||
let callData; |
||||
|
||||
beforeEach(async function () { |
||||
callData = this.context.contract.methods.msgData(integerValue.toString(), stringValue).encodeABI(); |
||||
}); |
||||
|
||||
it('returns the transaction data when called directly', async function () { |
||||
const { logs } = await this.context.msgData(integerValue, stringValue); |
||||
expectEvent.inLogs(logs, 'Data', { data: callData, integerValue, stringValue }); |
||||
}); |
||||
|
||||
it('returns the transaction sender when from another contract', async function () { |
||||
const { tx } = await this.caller.callData(this.context.address, integerValue, stringValue); |
||||
await expectEvent.inTransaction(tx, ContextMock, 'Data', { data: callData, integerValue, stringValue }); |
||||
}); |
||||
}); |
||||
shouldBehaveLikeRegularContext(sender); |
||||
}); |
||||
|
@ -0,0 +1,17 @@ |
||||
require('openzeppelin-test-helpers'); |
||||
|
||||
const GSNContextMock = artifacts.require('GSNContextMock'); |
||||
const ContextMockCaller = artifacts.require('ContextMockCaller'); |
||||
|
||||
const { shouldBehaveLikeRegularContext } = require('./Context.behavior'); |
||||
|
||||
contract('GSNContext', function ([_, sender, rhub]) { |
||||
beforeEach(async function () { |
||||
this.context = await GSNContextMock.new(rhub); |
||||
this.caller = await ContextMockCaller.new(); |
||||
}); |
||||
|
||||
context('when called directly', function () { |
||||
shouldBehaveLikeRegularContext(sender); |
||||
}); |
||||
}); |
Loading…
Reference in new issue