Skip to content

Commit

Permalink
Allowlist metatx methods (#461)
Browse files Browse the repository at this point in the history
  • Loading branch information
zajck authored Nov 14, 2022
1 parent be86eaf commit 88fcb46
Show file tree
Hide file tree
Showing 12 changed files with 539 additions and 71 deletions.
2 changes: 1 addition & 1 deletion contracts/domain/BosonConstants.sol
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,10 @@ string constant NATIVE_NOT_ALLOWED = "Transfer of native currency not allowed";
// Revert Reasons: Meta-Transactions related
string constant NONCE_USED_ALREADY = "Nonce used already";
string constant FUNCTION_CALL_NOT_SUCCESSFUL = "Function call not successful";
string constant INVALID_FUNCTION_SIGNATURE = "functionSignature can not be of executeMetaTransaction method";
string constant SIGNER_AND_SIGNATURE_DO_NOT_MATCH = "Signer and signature do not match";
string constant INVALID_FUNCTION_NAME = "Invalid function name";
string constant INVALID_SIGNATURE = "Invalid signature";
string constant FUNCTION_NOT_ALLOWLISTED = "Function can not be executed via meta transaction";

// Revert Reasons: Dispute related
string constant DISPUTE_PERIOD_HAS_ELAPSED = "Dispute period has already elapsed";
Expand Down
2 changes: 2 additions & 0 deletions contracts/interfaces/events/IBosonMetaTransactionsEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,6 @@ interface IBosonMetaTransactionsEvents {
string indexed functionName,
uint256 nonce
);

event FunctionsAllowlisted(bytes32[] functionNameHashes, bool isAllowlisted, address indexed executedBy);
}
33 changes: 31 additions & 2 deletions contracts/interfaces/handlers/IBosonMetaTransactionsHandler.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { IBosonMetaTransactionsEvents } from "../events/IBosonMetaTransactionsEv
*
* @notice Manages incoming meta-transactions in the protocol.
*
* The ERC-165 identifier for this interface is: 0xd25fcdc1
* The ERC-165 identifier for this interface is: 0xb3e4e803
*/
interface IBosonMetaTransactionsHandler is IBosonMetaTransactionsEvents {
/**
Expand All @@ -27,7 +27,7 @@ interface IBosonMetaTransactionsHandler is IBosonMetaTransactionsEvents {
* Reverts if:
* - The meta-transactions region of protocol is paused
* - Nonce is already used by the msg.sender for another transaction
* - Function signature matches executeMetaTransaction
* - Function is not allowlisted to be called using metatransactions
* - Function name does not match the bytes4 version of the function signature
* - sender does not match the recovered signer
* - Any code executed in the signed transaction reverts
Expand All @@ -50,4 +50,33 @@ interface IBosonMetaTransactionsHandler is IBosonMetaTransactionsEvents {
bytes32 _sigS,
uint8 _sigV
) external payable returns (bytes memory);

/**
* @notice Manages allow list of functions that can be executed using metatransactions.
*
* Emits a FunctionsAllowlisted event if successful.
*
* Reverts if:
* - Caller is not a protocol admin
*
* @param _functionNameHashes - a list of hashed function names (keccak256)
* @param _isAllowlisted - new allowlist status
*/
function setAllowlistedFunctions(bytes32[] calldata _functionNameHashes, bool _isAllowlisted) external;

/**
* @notice Tells if function can be executed as meta transaction or not.
*
* @param _functionNameHash - hashed function name (keccak256)
* @return isAllowlisted - allowlist status
*/
function isFunctionAllowlisted(bytes32 _functionNameHash) external view returns (bool isAllowlisted);

/**
* @notice Tells if function can be executed as meta transaction or not.
*
* @param _functionName - function name
* @return isAllowlisted - allowlist status
*/
function isFunctionAllowlisted(string calldata _functionName) external view returns (bool isAllowlisted);
}
72 changes: 65 additions & 7 deletions contracts/protocol/facets/MetaTransactionsHandlerFacet.sol
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,10 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol
* @notice Initializes Facet.
* This function is callable only once.
*/
function initialize() public onlyUnInitialized(type(IBosonMetaTransactionsHandler).interfaceId) {
function initialize(bytes32[] calldata _functionNameHashes)
public
onlyUnInitialized(type(IBosonMetaTransactionsHandler).interfaceId)
{
DiamondLib.addSupportedInterface(type(IBosonMetaTransactionsHandler).interfaceId);

// Set types for special metatxs
Expand All @@ -47,6 +50,8 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol
META_TX_DISPUTE_RESOLUTIONS_TYPEHASH,
hashDisputeResolutionDetails
);

setAllowlistedFunctions(_functionNameHashes, true);
}

/**
Expand Down Expand Up @@ -170,7 +175,7 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol
*
* Reverts if:
* - Nonce is already used by the msg.sender for another transaction
* - Function signature matches executeMetaTransaction
* - Function is not allowlisted to be called using metatransactions
* - Function name does not match the bytes4 version of the function signature
*
* @param _functionName - the function name that we want to execute
Expand All @@ -183,12 +188,18 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol
uint256 _nonce,
address _userAddress
) internal view {
require(!protocolMetaTxInfo().usedNonce[_userAddress][_nonce], NONCE_USED_ALREADY);
ProtocolLib.ProtocolMetaTxInfo storage pmti = protocolMetaTxInfo();

bytes4 destinationFunctionSig = convertBytesToBytes4(_functionSignature);
require(destinationFunctionSig != msg.sig, INVALID_FUNCTION_SIGNATURE);
// Nonce should be unused
require(!pmti.usedNonce[_userAddress][_nonce], NONCE_USED_ALREADY);

// Function must be allowlisted
bytes32 functionNameHash = keccak256(abi.encodePacked(_functionName));
require(pmti.isAllowlisted[functionNameHash], FUNCTION_NOT_ALLOWLISTED);

bytes4 functionNameSig = bytes4(keccak256(abi.encodePacked(_functionName)));
// Function name must correspond to selector
bytes4 destinationFunctionSig = convertBytesToBytes4(_functionSignature);
bytes4 functionNameSig = bytes4(functionNameHash);
require(destinationFunctionSig == functionNameSig, INVALID_FUNCTION_NAME);
}

Expand Down Expand Up @@ -259,7 +270,7 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol
* Reverts if:
* - The meta-transactions region of protocol is paused
* - Nonce is already used by the msg.sender for another transaction
* - Function signature matches executeMetaTransaction
* - Function is not allowlisted to be called using metatransactions
* - Function name does not match the bytes4 version of the function signature
* - sender does not match the recovered signer
* - Any code executed in the signed transaction reverts
Expand Down Expand Up @@ -305,4 +316,51 @@ contract MetaTransactionsHandlerFacet is IBosonMetaTransactionsHandler, Protocol

return executeTx(_userAddress, _functionName, _functionSignature, _nonce);
}

/**
* @notice Manages allow list of functions that can be executed using metatransactions.
*
* Emits a FunctionsAllowlisted event if successful.
*
* Reverts if:
* - Caller is not a protocol admin
*
* @param _functionNameHashes - a list of hashed function names (keccak256)
* @param _isAllowlisted - new allowlist status
*/
function setAllowlistedFunctions(bytes32[] calldata _functionNameHashes, bool _isAllowlisted)
public
override
onlyRole(ADMIN)
{
ProtocolLib.ProtocolMetaTxInfo storage pmti = protocolMetaTxInfo();

// set new values
for (uint256 i = 0; i < _functionNameHashes.length; i++) {
pmti.isAllowlisted[_functionNameHashes[i]] = _isAllowlisted;
}

// Notify external observers
emit FunctionsAllowlisted(_functionNameHashes, _isAllowlisted, msgSender());
}

/**
* @notice Tells if function can be executed as meta transaction or not.
*
* @param _functionNameHash - hashed function name (keccak256)
* @return isAllowlisted - allowlist status
*/
function isFunctionAllowlisted(bytes32 _functionNameHash) external view override returns (bool isAllowlisted) {
return protocolMetaTxInfo().isAllowlisted[_functionNameHash];
}

/**
* @notice Tells if function can be executed as meta transaction or not.
*
* @param _functionName - function name
* @return isAllowlisted - allowlist status
*/
function isFunctionAllowlisted(string calldata _functionName) external view override returns (bool isAllowlisted) {
return protocolMetaTxInfo().isAllowlisted[keccak256(abi.encodePacked(_functionName))];
}
}
2 changes: 2 additions & 0 deletions contracts/protocol/libs/ProtocolLib.sol
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,8 @@ library ProtocolLib {
mapping(string => BosonTypes.MetaTxInputType) inputType;
// map input type => hash info
mapping(BosonTypes.MetaTxInputType => BosonTypes.HashInfo) hashInfo;
// Can function be executed using meta transactions
mapping(bytes32 => bool) isAllowlisted;
}

// Individual facet initialization states
Expand Down
56 changes: 36 additions & 20 deletions scripts/config/facet-deploy.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const { getStateModifyingFunctionsHashes } = require("../../scripts/util/diamond-utils.js");

/**
* Config file used to deploy the facets
*
* Function getFacets() returns the object that is used by the deploy script. To specify custom deployment parameters, modify return value.
* Returned value should have the following fields:
* - noArgFacets: list of facet names that don't expect any argument passed into initializer
* - argFacets: object that specify facet names and arguments that needs to be passed into initializer in format object {facetName: initializerArguments}
*
Expand All @@ -13,23 +17,35 @@
}
*
*/
module.exports = {
noArgFacets: [
"AccountHandlerFacet",
"SellerHandlerFacet",
"BuyerHandlerFacet",
"DisputeResolverHandlerFacet",
"AgentHandlerFacet",
"BundleHandlerFacet",
"DisputeHandlerFacet",
"ExchangeHandlerFacet",
"FundsHandlerFacet",
"GroupHandlerFacet",
"OfferHandlerFacet",
"OrchestrationHandlerFacet",
"TwinHandlerFacet",
"PauseHandlerFacet",
"MetaTransactionsHandlerFacet",
],
argFacets: {},
};

const noArgFacetNames = [
"AccountHandlerFacet",
"SellerHandlerFacet",
"BuyerHandlerFacet",
"DisputeResolverHandlerFacet",
"AgentHandlerFacet",
"BundleHandlerFacet",
"DisputeHandlerFacet",
"ExchangeHandlerFacet",
"FundsHandlerFacet",
"GroupHandlerFacet",
"OfferHandlerFacet",
"OrchestrationHandlerFacet",
"TwinHandlerFacet",
"PauseHandlerFacet",
];

async function getFacets() {
// metaTransactionsHandlerFacet initializer arguments.
const MetaTransactionsHandlerFacetInitArgs = await getStateModifyingFunctionsHashes(
[...noArgFacetNames, "MetaTransactionsHandlerFacet"],
["executeMetaTransaction(address,string,bytes,uint256,bytes32,bytes32,uint8)"]
);

return {
noArgFacets: noArgFacetNames,
argFacets: { MetaTransactionsHandlerFacet: [MetaTransactionsHandlerFacetInitArgs] },
};
}

exports.getFacets = getFacets;
20 changes: 12 additions & 8 deletions scripts/config/facet-upgrade.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
* Skip does not apply to facets that are completely removed.
* - initArgs: if facet initializer expects arguments, provide them here. For no-arg initializers you don't have to specify anything.
* - skipInit": list of facets for which you want to skip initialization call.
*
*
* Example:
{
addOrUpgrade: ["Facet1", "Facet2"],
Expand All @@ -21,10 +21,14 @@
skipInit: ["Facet2"],
}
*/
exports.Facets = {
addOrUpgrade: [],
remove: [],
skipSelectors: {},
initArgs: {},
skipInit: [],
};
async function getFacets() {
return {
addOrUpgrade: [],
remove: [],
skipSelectors: {},
initArgs: {},
skipInit: [],
};
}

exports.getFacets = getFacets;
2 changes: 1 addition & 1 deletion scripts/config/revert-reasons.js
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,10 @@ exports.RevertReasons = {
// Meta-Transactions related
NONCE_USED_ALREADY: "Nonce used already",
FUNCTION_CALL_NOT_SUCCESSFUL: "Function call not successful",
INVALID_FUNCTION_SIGNATURE: "functionSignature can not be of executeMetaTransaction method",
INVALID_SIGNATURE: "Invalid signature",
SIGNER_AND_SIGNATURE_DO_NOT_MATCH: "Signer and signature do not match",
INVALID_FUNCTION_NAME: "Invalid function name",
FUNCTION_NOT_ALLOWLISTED: "Function can not be executed via meta transaction",

// Dispute related
DISPUTE_PERIOD_HAS_ELAPSED: "Dispute period has already elapsed",
Expand Down
18 changes: 11 additions & 7 deletions scripts/deploy-suite.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const maxPriorityFeePerGas = ethers.BigNumber.from(tipSuggestion).mul(tipMultipl

const protocolConfig = require("./config/protocol-parameters");
const authTokenAddresses = require("./config/auth-token-addresses");
const facets = require("./config/facet-deploy");
const { getFacets } = require("./config/facet-deploy");

const Role = require("./domain/Role");
const { deployProtocolDiamond } = require("./util/deploy-protocol-diamond.js");
Expand Down Expand Up @@ -62,15 +62,15 @@ function getAuthTokenContracts() {
/**
* Get a list of no-arg initializer facet names to be cut into the Diamond
*/
function getNoArgFacetNames() {
return facets.noArgFacets;
async function getNoArgFacetNames() {
return (await getFacets()).noArgFacets;
}

/**
* Get a list of facet names to be cut into the Diamond
*/
function getArgFacetNames() {
return facets.argFacets;
async function getArgFacetNames() {
return (await getFacets()).argFacets;
}

async function main(env) {
Expand Down Expand Up @@ -148,7 +148,11 @@ async function main(env) {
console.log(`\n💎 Deploying and initializing protocol handler facets...`);

// Deploy and cut facets
const deployedFacets = await deployProtocolHandlerFacets(protocolDiamond, getNoArgFacetNames(), maxPriorityFeePerGas);
const deployedFacets = await deployProtocolHandlerFacets(
protocolDiamond,
await getNoArgFacetNames(),
maxPriorityFeePerGas
);
for (const deployedFacet of deployedFacets) {
deploymentComplete(
deployedFacet.name,
Expand All @@ -161,7 +165,7 @@ async function main(env) {

const deployedFacetsWithArgs = await deployProtocolHandlerFacetsWithArgs(
protocolDiamond,
getArgFacetNames(),
await getArgFacetNames(),
maxPriorityFeePerGas
);
for (const deployedFacet of deployedFacetsWithArgs) {
Expand Down
Loading

0 comments on commit 88fcb46

Please sign in to comment.