Add GSNContext and tests

pull/1844/head
Nicolás Venturo 6 years ago committed by Santiago Palladino
parent 2e28b70e68
commit f12245b3b5
  1. 71
      contracts/drafts/meta-tx/GSNContext.sol
  2. 10
      contracts/mocks/GSNContextMock.sol
  3. 42
      test/drafts/meta-tx/Context.behavior.js
  4. 37
      test/drafts/meta-tx/Context.test.js
  5. 17
      test/drafts/meta-tx/GSNContext.test.js

@ -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 ContextMock = artifacts.require('ContextMock');
const ContextMockCaller = artifacts.require('ContextMockCaller'); const ContextMockCaller = artifacts.require('ContextMockCaller');
const { shouldBehaveLikeRegularContext } = require('./Context.behavior');
contract('Context', function ([_, sender]) { contract('Context', function ([_, sender]) {
beforeEach(async function () { beforeEach(async function () {
this.context = await ContextMock.new(); this.context = await ContextMock.new();
this.caller = await ContextMockCaller.new(); this.caller = await ContextMockCaller.new();
}); });
describe('msgSender', function () { shouldBehaveLikeRegularContext(sender);
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 });
});
});
}); });

@ -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…
Cancel
Save