Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add meta transactions support for bridged ERC721 and ERC1155 tokens #42

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .solcover.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
module.exports = {
providerOptions: {
_chainId: 1337,
port: 8545,
seed: 'TestRPC is awesome!'
},
mocha: {
timeout: 30000
},
Expand Down
79 changes: 79 additions & 0 deletions contracts/gsn/BaseRelayRecipient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
pragma solidity 0.7.5;

import "@openzeppelin/contracts/utils/Context.sol";
import "./IRelayRecipient.sol";

/**
* @title BaseRelayRecipient
* @dev A base contract to be inherited by any contract that want to receive relayed transactions
* A subclass must use "_msgSender()" instead of "msg.sender"
*/
abstract contract BaseRelayRecipient is IRelayRecipient, Context {
function trustedForwarder() public view returns (address forwarder) {
assembly {
// bytes32(uint256(keccak256('gsn.trustedForwarder')))
forwarder := sload(0x7f6d43cd94d5a48725c9a80b72b83bbdb80ef2feb98fcf5461b698486de60deb)
}
}

function isTrustedForwarder(address forwarder) public view override returns (bool) {
return forwarder == trustedForwarder();
}

function versionRecipient() external view override returns (string memory) {
return "1.0.0";
}

function _setTrustedForwarder(address _forwarder) internal {
assembly {
// bytes32(uint256(keccak256('gsn.trustedForwarder')))
sstore(0x7f6d43cd94d5a48725c9a80b72b83bbdb80ef2feb98fcf5461b698486de60deb, _forwarder)
}
}

/**
* return the sender of this call.
* if the call came through our trusted forwarder, return the original sender.
* otherwise, return `msg.sender`.
* should be used in the contract anywhere instead of msg.sender
*/
function _msgSender() internal view virtual override(Context, IRelayRecipient) returns (address payable ret) {
if (msg.data.length >= 20 && isTrustedForwarder(msg.sender)) {
// At this point we know that the sender is a trusted forwarder,
// so we trust that the last bytes of msg.data are the verified sender address.
// extract sender address from the end of msg.data
assembly {
ret := shr(96, calldataload(sub(calldatasize(), 20)))
}
} else {
return msg.sender;
}
}

/**
* return the msg.data of this call.
* if the call came through our trusted forwarder, then the real sender was appended as the last 20 bytes
* of the msg.data - so this method will strip those 20 bytes off.
* otherwise, return `msg.data`
* should be used in the contract instead of msg.data, where the difference matters (e.g. when explicitly
* signing or hashing the
*/
function _msgData() internal view virtual override(Context, IRelayRecipient) returns (bytes memory ret) {
if (msg.data.length >= 24 && isTrustedForwarder(msg.sender)) {
// At this point we know that the sender is a trusted forwarder,
// we copy the msg.data , except the last 20 bytes (and update the total length)
assembly {
let ptr := mload(0x40)
// copy only size-20 bytes
let size := sub(calldatasize(), 20)
// structure RLP data as <offset> <length> <bytes>
mstore(ptr, 0x20)
mstore(add(ptr, 32), size)
calldatacopy(add(ptr, 64), 0, size)
return(ptr, add(size, 64))
}
} else {
return msg.data;
}
}
}
35 changes: 35 additions & 0 deletions contracts/gsn/IRelayRecipient.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
pragma solidity 0.7.5;

/**
* @title IRelayRecipient
* @dev a contract must implement this interface in order to support relayed transaction.
* It is better to inherit the BaseRelayRecipient as its implementation.
*/
abstract contract IRelayRecipient {
/**
* return if the forwarder is trusted to forward relayed transactions to us.
* the forwarder is required to verify the sender's signature, and verify
* the call is not a replay.
*/
function isTrustedForwarder(address forwarder) public view virtual returns (bool);

/**
* return the sender of this call.
* if the call came through our trusted forwarder, then the real sender is appended as the last 20 bytes
* of the msg.data.
* otherwise, return `msg.sender`
* should be used in the contract anywhere instead of msg.sender
*/
function _msgSender() internal view virtual returns (address payable);

/**
* return the msg.data of this call.
* if the call came through our trusted forwarder, then the real sender was appended as the last 20 bytes
* of the msg.data - so this method will strip those 20 bytes off.
* otherwise (if the call was made directly and not through the forwarder), return `msg.data`
* should be used in the contract instead of msg.data, where this difference matters.
*/
function _msgData() internal view virtual returns (bytes memory);

function versionRecipient() external view virtual returns (string memory);
}
113 changes: 113 additions & 0 deletions contracts/tokens/BaseMetaTransactions.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
pragma solidity 0.7.5;

import "@openzeppelin/contracts/cryptography/ECDSA.sol";

/**
* @title BaseMetaTransactions
* @dev Common functionality for supporting native meta-transactions in the token contracts
*/
contract BaseMetaTransactions {
// it is important to keep this storage slot after all slots in ERC721BridgeToken/ERC1155BridgeToken
mapping(address => uint256) public nonces;

event NonceChange(address indexed signer, uint256 newNonce);

// Allowed signature types (only 0x01, 0x02 are valid)
enum SignatureType { Illegal, EIP712, EthSign, NSignatureTypes }

// keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")
bytes32 private constant DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
bytes32 private immutable HASHED_NAME;
bytes32 private immutable HASHED_VERSION;

constructor(string memory _name, string memory _version) {
HASHED_NAME = keccak256(bytes(_name));
HASHED_VERSION = keccak256(bytes(_version));
}

/**
* @dev Verifies signatures for this contract.
* @param _signer Address of signer.
* @param _encMembers Encoded EIP-712 type members.
* @param _signature Encoded signature parameters (vrs) and its type (66 bytes).
*/
function _signatureValidation(
address _signer,
bytes memory _encMembers,
bytes memory _signature
) internal {
bytes32 hash = _hashEIP712Message(keccak256(_encMembers));

require(_isValidSignature(_signer, hash, _signature), "INVALID_SIGNATURE");
}

/**
* @dev Verifies that a hash has been signed by the given signer.
* @param _signerAddress Address that should have signed the given hash.
* @param _hash Hash of the EIP-712 encoded data
* @param _sig Proof that the hash has been signed by signer.
* Encoded in the form of (bytes32 r, bytes32 s, uint8 v, SignatureType sigType).
* @return True if the address recovered from the provided signature matches the input signer address.
*/
function _isValidSignature(
address _signerAddress,
bytes32 _hash,
bytes memory _sig
) public view returns (bool) {
require(_sig.length == 66, "LENGTH_66_REQUIRED");
require(_signerAddress != address(0x0), "INVALID_SIGNER");

// Pop last byte off of signature byte array.
uint8 signatureTypeRaw = uint8(_sig[65]);
assembly {
mstore(_sig, 65)
}

// Ensure signature is supported
require(signatureTypeRaw < uint8(SignatureType.NSignatureTypes), "UNSUPPORTED_SIGNATURE");

// Extract signature type
SignatureType signatureType = SignatureType(signatureTypeRaw);

require(signatureType != SignatureType.Illegal, "ILLEGAL_SIGNATURE");

if (signatureType == SignatureType.EIP712) {
return _signerAddress == ECDSA.recover(_hash, _sig);
} else if (signatureType == SignatureType.EthSign) {
return _signerAddress == ECDSA.recover(ECDSA.toEthSignedMessageHash(_hash), _sig);
} else {
revert("UNSUPPORTED_SIGNATURE");
}
}

/**
* @dev Calculates EIP712 encoding for a hash struct in this EIP712 Domain.
* @param _hashStruct The EIP712 hash struct.
* @return EIP712 hash applied to this EIP712 Domain.
*/
function _hashEIP712Message(bytes32 _hashStruct) internal returns (bytes32) {
uint256 chainId;
assembly {
chainId := chainid()
}
bytes32 domainSeparator =
keccak256(abi.encode(DOMAIN_TYPEHASH, HASHED_NAME, HASHED_VERSION, chainId, address(this)));

return keccak256(abi.encodePacked("\x19\x01", domainSeparator, _hashStruct));
}

/**
* @dev Internal function for checking if the given nonce is valid. Update nonce after successful check.
* A valid nonce is a nonce that is within 100 value from the current nonce.
* @param _signer address of the meta-transaction signer.
* @param _nonce nonce argument given in the signed meta-transaction message.
*/
function _verifyNonce(address _signer, uint256 _nonce) internal {
uint256 currentNonce = nonces[_signer];

require(_nonce >= currentNonce && _nonce < currentNonce + 100, "INVALID_NONCE");

nonces[_signer] = _nonce + 1;
emit NonceChange(_signer, _nonce + 1);
}
}
Loading