From 2bf883acea296dc4835c9188e1894933c86f5dcd Mon Sep 17 00:00:00 2001 From: Bhargava Shastry Date: Sat, 13 Feb 2021 11:27:39 +0100 Subject: [PATCH] Gnosis safe contracts flattened test that triggers a stack-overflow inside SMTChecker. --- .../multiSource/safe_contracts.sol | 2988 +++++++++++++++++ 1 file changed, 2988 insertions(+) create mode 100644 test/libsolidity/syntaxTests/multiSource/safe_contracts.sol diff --git a/test/libsolidity/syntaxTests/multiSource/safe_contracts.sol b/test/libsolidity/syntaxTests/multiSource/safe_contracts.sol new file mode 100644 index 000000000..9ac5cf6bb --- /dev/null +++ b/test/libsolidity/syntaxTests/multiSource/safe_contracts.sol @@ -0,0 +1,2988 @@ +==== Source: ./contracts/modules/WhitelistModule.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../base/Module.sol"; +import "../base/ModuleManager.sol"; +import "../base/OwnerManager.sol"; +import "../common/Enum.sol"; + + +/// @title Whitelist Module - Allows to execute transactions to whitelisted addresses without confirmations. +/// @author Stefan George - +contract WhitelistModule is Module { + + string public constant NAME = "Whitelist Module"; + string public constant VERSION = "0.1.0"; + + // isWhitelisted mapping maps destination address to boolean. + mapping (address => bool) public isWhitelisted; + + /// @dev Setup function sets initial storage of contract. + /// @param accounts List of whitelisted accounts. + function setup(address[] memory accounts) + public + { + setManager(); + for (uint256 i = 0; i < accounts.length; i++) { + address account = accounts[i]; + require(account != address(0), "Invalid account provided"); + isWhitelisted[account] = true; + } + } + + /// @dev Allows to add destination to whitelist. This can only be done via a Safe transaction. + /// @param account Destination address. + function addToWhitelist(address account) + public + authorized + { + require(account != address(0), "Invalid account provided"); + require(!isWhitelisted[account], "Account is already whitelisted"); + isWhitelisted[account] = true; + } + + /// @dev Allows to remove destination from whitelist. This can only be done via a Safe transaction. + /// @param account Destination address. + function removeFromWhitelist(address account) + public + authorized + { + require(isWhitelisted[account], "Account is not whitelisted"); + isWhitelisted[account] = false; + } + + /// @dev Returns if Safe transaction is to a whitelisted destination. + /// @param to Whitelisted destination address. + /// @param value Not checked. + /// @param data Not checked. + /// @return Returns if transaction can be executed. + function executeWhitelisted(address to, uint256 value, bytes memory data) + public + returns (bool) + { + // Only Safe owners are allowed to execute transactions to whitelisted accounts. + require(OwnerManager(address(manager)).isOwner(msg.sender), "Method can only be called by an owner"); + require(isWhitelisted[to], "Target account is not whitelisted"); + require(manager.execTransactionFromModule(to, value, data, Enum.Operation.Call), "Could not execute transaction"); + } +} +==== Source: ./contracts/modules/DailyLimitModule.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../base/Module.sol"; +import "../base/ModuleManager.sol"; +import "../base/OwnerManager.sol"; +import "../common/Enum.sol"; + + +/// @title Daily Limit Module - Allows to transfer limited amounts of ERC20 tokens and Ether without confirmations. +/// @author Stefan George - +contract DailyLimitModule is Module { + + string public constant NAME = "Daily Limit Module"; + string public constant VERSION = "0.1.0"; + + // dailyLimits mapping maps token address to daily limit settings. + mapping (address => DailyLimit) public dailyLimits; + + struct DailyLimit { + uint256 dailyLimit; + uint256 spentToday; + uint256 lastDay; + } + + /// @dev Setup function sets initial storage of contract. + /// @param tokens List of token addresses. Ether is represented with address 0x0. + /// @param _dailyLimits List of daily limits in smalles units (e.g. Wei for Ether). + function setup(address[] memory tokens, uint256[] memory _dailyLimits) + public + { + setManager(); + for (uint256 i = 0; i < tokens.length; i++) + dailyLimits[tokens[i]].dailyLimit = _dailyLimits[i]; + } + + /// @dev Allows to update the daily limit for a specified token. This can only be done via a Safe transaction. + /// @param token Token contract address. + /// @param dailyLimit Daily limit in smallest token unit. + function changeDailyLimit(address token, uint256 dailyLimit) + public + authorized + { + dailyLimits[token].dailyLimit = dailyLimit; + } + + /// @dev Returns if Safe transaction is a valid daily limit transaction. + /// @param token Address of the token that should be transfered (0 for Ether) + /// @param to Address to which the tokens should be transfered + /// @param amount Amount of tokens (or Ether) that should be transfered + function executeDailyLimit(address token, address to, uint256 amount) + public + { + // Only Safe owners are allowed to execute daily limit transactions. + require(OwnerManager(address(manager)).isOwner(msg.sender), "Method can only be called by an owner"); + require(to != address(0), "Invalid to address provided"); + require(amount > 0, "Invalid amount provided"); + // Validate that transfer is not exceeding daily limit. + require(isUnderLimit(token, amount), "Daily limit has been reached"); + dailyLimits[token].spentToday += amount; + if (token == address(0)) { + require(manager.execTransactionFromModule(to, amount, "", Enum.Operation.Call), "Could not execute ether transfer"); + } else { + bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", to, amount); + require(manager.execTransactionFromModule(token, 0, data, Enum.Operation.Call), "Could not execute token transfer"); + } + } + + function isUnderLimit(address token, uint256 amount) + internal + returns (bool) + { + DailyLimit storage dailyLimit = dailyLimits[token]; + if (today() > dailyLimit.lastDay) { + dailyLimit.lastDay = today(); + dailyLimit.spentToday = 0; + } + if (dailyLimit.spentToday + amount <= dailyLimit.dailyLimit && + dailyLimit.spentToday + amount > dailyLimit.spentToday) + return true; + return false; + } + + /// @dev Returns last midnight as Unix timestamp. + /// @return Unix timestamp. + function today() + public + view + returns (uint) + { + return block.timestamp - (block.timestamp % 1 days); + } +} +==== Source: ./contracts/modules/SocialRecoveryModule.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../base/Module.sol"; +import "../base/ModuleManager.sol"; +import "../base/OwnerManager.sol"; +import "../common/Enum.sol"; + + +/// @title Social Recovery Module - Allows to replace an owner without Safe confirmations if friends approve the replacement. +/// @author Stefan George - +contract SocialRecoveryModule is Module { + + string public constant NAME = "Social Recovery Module"; + string public constant VERSION = "0.1.0"; + + uint256 public threshold; + address[] public friends; + + // isFriend mapping maps friend's address to friend status. + mapping (address => bool) public isFriend; + // isExecuted mapping maps data hash to execution status. + mapping (bytes32 => bool) public isExecuted; + // isConfirmed mapping maps data hash to friend's address to confirmation status. + mapping (bytes32 => mapping (address => bool)) public isConfirmed; + + modifier onlyFriend() { + require(isFriend[msg.sender], "Method can only be called by a friend"); + _; + } + + /// @dev Setup function sets initial storage of contract. + /// @param _friends List of friends' addresses. + /// @param _threshold Required number of friends to confirm replacement. + function setup(address[] memory _friends, uint256 _threshold) + public + { + require(_threshold <= _friends.length, "Threshold cannot exceed friends count"); + require(_threshold >= 2, "At least 2 friends required"); + setManager(); + // Set allowed friends. + for (uint256 i = 0; i < _friends.length; i++) { + address friend = _friends[i]; + require(friend != address(0), "Invalid friend address provided"); + require(!isFriend[friend], "Duplicate friend address provided"); + isFriend[friend] = true; + } + friends = _friends; + threshold = _threshold; + } + + /// @dev Allows a friend to confirm a Safe transaction. + /// @param dataHash Safe transaction hash. + function confirmTransaction(bytes32 dataHash) + public + onlyFriend + { + require(!isExecuted[dataHash], "Recovery already executed"); + isConfirmed[dataHash][msg.sender] = true; + } + + /// @dev Returns if Safe transaction is a valid owner replacement transaction. + /// @param prevOwner Owner that pointed to the owner to be replaced in the linked list + /// @param oldOwner Owner address to be replaced. + /// @param newOwner New owner address. + function recoverAccess(address prevOwner, address oldOwner, address newOwner) + public + onlyFriend + { + bytes memory data = abi.encodeWithSignature("swapOwner(address,address,address)", prevOwner, oldOwner, newOwner); + bytes32 dataHash = getDataHash(data); + require(!isExecuted[dataHash], "Recovery already executed"); + require(isConfirmedByRequiredFriends(dataHash), "Recovery has not enough confirmations"); + isExecuted[dataHash] = true; + require(manager.execTransactionFromModule(address(manager), 0, data, Enum.Operation.Call), "Could not execute recovery"); + } + + /// @dev Returns if Safe transaction is a valid owner replacement transaction. + /// @param dataHash Data hash. + /// @return Confirmation status. + function isConfirmedByRequiredFriends(bytes32 dataHash) + public + view + returns (bool) + { + uint256 confirmationCount; + for (uint256 i = 0; i < friends.length; i++) { + if (isConfirmed[dataHash][friends[i]]) + confirmationCount++; + if (confirmationCount == threshold) + return true; + } + return false; + } + + /// @dev Returns hash of data encoding owner replacement. + /// @param data Data payload. + /// @return Data hash. + function getDataHash(bytes memory data) + public + pure + returns (bytes32) + { + return keccak256(data); + } +} +==== Source: ./contracts/modules/StateChannelModule.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../base/Module.sol"; +import "../base/OwnerManager.sol"; +import "../common/Enum.sol"; +import "../common/SignatureDecoder.sol"; + + +/// @title Gnosis Safe State Module - A module that allows interaction with statechannels. +/// @author Stefan George - +/// @author Richard Meissner - +contract StateChannelModule is Module, SignatureDecoder { + + string public constant NAME = "State Channel Module"; + string public constant VERSION = "0.1.0"; + + // isExecuted mapping allows to check if a transaction (by hash) was already executed. + mapping (bytes32 => uint256) public isExecuted; + + /// @dev Setup function sets manager + function setup() + public + { + setManager(); + } + + /// @dev Allows to execute a Safe transaction confirmed by required number of owners. + /// @param to Destination address of Safe transaction. + /// @param value Ether value of Safe transaction. + /// @param data Data payload of Safe transaction. + /// @param operation Operation type of Safe transaction. + /// @param nonce Nonce used for this Safe transaction. + /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v}) + function execTransaction( + address to, + uint256 value, + bytes memory data, + Enum.Operation operation, + uint256 nonce, + bytes memory signatures + ) + public + { + bytes32 transactionHash = getTransactionHash(to, value, data, operation, nonce); + require(isExecuted[transactionHash] == 0, "Transaction already executed"); + checkHash(transactionHash, signatures); + // Mark as executed and execute transaction. + isExecuted[transactionHash] = 1; + require(manager.execTransactionFromModule(to, value, data, operation), "Could not execute transaction"); + } + + function checkHash(bytes32 transactionHash, bytes memory signatures) + internal + view + { + // There cannot be an owner with address 0. + address lastOwner = address(0); + address currentOwner; + uint256 i; + uint256 threshold = OwnerManager(address(manager)).getThreshold(); + // Validate threshold is reached. + for (i = 0; i < threshold; i++) { + currentOwner = recoverKey(transactionHash, signatures, i); + require(OwnerManager(address(manager)).isOwner(currentOwner), "Signature not provided by owner"); + require(currentOwner > lastOwner, "Signatures are not ordered by owner address"); + lastOwner = currentOwner; + } + } + + /// @dev Returns hash to be signed by owners. + /// @param to Destination address. + /// @param value Ether value. + /// @param data Data payload. + /// @param operation Operation type. + /// @param nonce Transaction nonce. + /// @return Transaction hash. + function getTransactionHash( + address to, + uint256 value, + bytes memory data, + Enum.Operation operation, + uint256 nonce + ) + public + view + returns (bytes32) + { + return keccak256(abi.encodePacked(bytes1(0x19), bytes1(0), this, to, value, data, operation, nonce)); + } +} +==== Source: ./contracts/base/Module.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../common/MasterCopy.sol"; +import "./ModuleManager.sol"; + + +/// @title Module - Base class for modules. +/// @author Stefan George - +/// @author Richard Meissner - +contract Module is MasterCopy { + + ModuleManager public manager; + + modifier authorized() override { + require(msg.sender == address(manager), "Method can only be called from manager"); + _; + } + + function setManager() + internal + { + // manager can only be 0 at initalization of contract. + // Check ensures that setup function can only be called once. + require(address(manager) == address(0), "Manager has already been set"); + manager = ModuleManager(msg.sender); + } +} +==== Source: ./contracts/base/ModuleManager.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../common/Enum.sol"; +import "../common/SelfAuthorized.sol"; +import "./Executor.sol"; +import "./Module.sol"; + + +/// @title Module Manager - A contract that manages modules that can execute transactions via this contract +/// @author Stefan George - +/// @author Richard Meissner - +contract ModuleManager is SelfAuthorized, Executor { + + event EnabledModule(Module module); + event DisabledModule(Module module); + event ExecutionFromModuleSuccess(address indexed module); + event ExecutionFromModuleFailure(address indexed module); + + address internal constant SENTINEL_MODULES = address(0x1); + + mapping (address => address) internal modules; + + function setupModules(address to, bytes memory data) + internal + { + require(modules[SENTINEL_MODULES] == address(0), "Modules have already been initialized"); + modules[SENTINEL_MODULES] = SENTINEL_MODULES; + if (to != address(0)) + // Setup has to complete successfully or transaction fails. + require(executeDelegateCall(to, data, gasleft()), "Could not finish initialization"); + } + + /// @dev Allows to add a module to the whitelist. + /// This can only be done via a Safe transaction. + /// @notice Enables the module `module` for the Safe. + /// @param module Module to be whitelisted. + function enableModule(Module module) + public + authorized + { + // Module address cannot be null or sentinel. + require(address(module) != address(0) && address(module) != SENTINEL_MODULES, "Invalid module address provided"); + // Module cannot be added twice. + require(modules[address(module)] == address(0), "Module has already been added"); + modules[address(module)] = modules[SENTINEL_MODULES]; + modules[SENTINEL_MODULES] = address(module); + emit EnabledModule(module); + } + + /// @dev Allows to remove a module from the whitelist. + /// This can only be done via a Safe transaction. + /// @notice Disables the module `module` for the Safe. + /// @param prevModule Module that pointed to the module to be removed in the linked list + /// @param module Module to be removed. + function disableModule(Module prevModule, Module module) + public + authorized + { + // Validate module address and check that it corresponds to module index. + require(address(module) != address(0) && address(module) != SENTINEL_MODULES, "Invalid module address provided"); + require(modules[address(prevModule)] == address(module), "Invalid prevModule, module pair provided"); + modules[address(prevModule)] = modules[address(module)]; + modules[address(module)] = address(0); + emit DisabledModule(module); + } + + /// @dev Allows a Module to execute a Safe transaction without any further confirmations. + /// @param to Destination address of module transaction. + /// @param value Ether value of module transaction. + /// @param data Data payload of module transaction. + /// @param operation Operation type of module transaction. + function execTransactionFromModule(address to, uint256 value, bytes memory data, Enum.Operation operation) + public + returns (bool success) + { + // Only whitelisted modules are allowed. + require(msg.sender != SENTINEL_MODULES && modules[msg.sender] != address(0), "Method can only be called from an enabled module"); + // Execute transaction without further confirmations. + success = execute(to, value, data, operation, gasleft()); + if (success) emit ExecutionFromModuleSuccess(msg.sender); + else emit ExecutionFromModuleFailure(msg.sender); + } + + /// @dev Allows a Module to execute a Safe transaction without any further confirmations and return data + /// @param to Destination address of module transaction. + /// @param value Ether value of module transaction. + /// @param data Data payload of module transaction. + /// @param operation Operation type of module transaction. + function execTransactionFromModuleReturnData(address to, uint256 value, bytes memory data, Enum.Operation operation) + public + returns (bool success, bytes memory returnData) + { + success = execTransactionFromModule(to, value, data, operation); + // solium-disable-next-line security/no-inline-assembly + assembly { + // Load free memory location + let ptr := mload(0x40) + // We allocate memory for the return data by setting the free memory location to + // current free memory location + data size + 32 bytes for data size value + mstore(0x40, add(ptr, add(returndatasize(), 0x20))) + // Store the size + mstore(ptr, returndatasize()) + // Store the data + returndatacopy(add(ptr, 0x20), 0, returndatasize()) + // Point the return data to the correct memory location + returnData := ptr + } + } + + /// @dev Returns if an module is enabled + /// @return True if the module is enabled + function isModuleEnabled(Module module) + public + view + returns (bool) + { + return SENTINEL_MODULES != address(module) && modules[address(module)] != address(0); + } + + /// @dev Returns array of first 10 modules. + /// @return Array of modules. + function getModules() + public + view + returns (address[] memory) + { + (address[] memory array,) = getModulesPaginated(SENTINEL_MODULES, 10); + return array; + } + + /// @dev Returns array of modules. + /// @param start Start of the page. + /// @param pageSize Maximum number of modules that should be returned. + /// @return array Array of modules. + function getModulesPaginated(address start, uint256 pageSize) + public + view + returns (address[] memory array, address next) + { + // Init array with max page size + array = new address[](pageSize); + + // Populate return array + uint256 moduleCount = 0; + address currentModule = modules[start]; + while(currentModule != address(0x0) && currentModule != SENTINEL_MODULES && moduleCount < pageSize) { + array[moduleCount] = currentModule; + currentModule = modules[currentModule]; + moduleCount++; + } + next = currentModule; + // Set correct size of returned array + // solium-disable-next-line security/no-inline-assembly + assembly { + mstore(array, moduleCount) + } + } +} +==== Source: ./contracts/base/OwnerManager.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../common/SelfAuthorized.sol"; + +/// @title OwnerManager - Manages a set of owners and a threshold to perform actions. +/// @author Stefan George - +/// @author Richard Meissner - +contract OwnerManager is SelfAuthorized { + + event AddedOwner(address owner); + event RemovedOwner(address owner); + event ChangedThreshold(uint256 threshold); + + address internal constant SENTINEL_OWNERS = address(0x1); + + mapping(address => address) internal owners; + uint256 ownerCount; + uint256 internal threshold; + + /// @dev Setup function sets initial storage of contract. + /// @param _owners List of Safe owners. + /// @param _threshold Number of required confirmations for a Safe transaction. + function setupOwners(address[] memory _owners, uint256 _threshold) + internal + { + // Threshold can only be 0 at initialization. + // Check ensures that setup function can only be called once. + require(threshold == 0, "Owners have already been setup"); + // Validate that threshold is smaller than number of added owners. + require(_threshold <= _owners.length, "Threshold cannot exceed owner count"); + // There has to be at least one Safe owner. + require(_threshold >= 1, "Threshold needs to be greater than 0"); + // Initializing Safe owners. + address currentOwner = SENTINEL_OWNERS; + for (uint256 i = 0; i < _owners.length; i++) { + // Owner address cannot be null. + address owner = _owners[i]; + require(owner != address(0) && owner != SENTINEL_OWNERS, "Invalid owner address provided"); + // No duplicate owners allowed. + require(owners[owner] == address(0), "Duplicate owner address provided"); + owners[currentOwner] = owner; + currentOwner = owner; + } + owners[currentOwner] = SENTINEL_OWNERS; + ownerCount = _owners.length; + threshold = _threshold; + } + + /// @dev Allows to add a new owner to the Safe and update the threshold at the same time. + /// This can only be done via a Safe transaction. + /// @notice Adds the owner `owner` to the Safe and updates the threshold to `_threshold`. + /// @param owner New owner address. + /// @param _threshold New threshold. + function addOwnerWithThreshold(address owner, uint256 _threshold) + public + authorized + { + // Owner address cannot be null. + require(owner != address(0) && owner != SENTINEL_OWNERS, "Invalid owner address provided"); + // No duplicate owners allowed. + require(owners[owner] == address(0), "Address is already an owner"); + owners[owner] = owners[SENTINEL_OWNERS]; + owners[SENTINEL_OWNERS] = owner; + ownerCount++; + emit AddedOwner(owner); + // Change threshold if threshold was changed. + if (threshold != _threshold) + changeThreshold(_threshold); + } + + /// @dev Allows to remove an owner from the Safe and update the threshold at the same time. + /// This can only be done via a Safe transaction. + /// @notice Removes the owner `owner` from the Safe and updates the threshold to `_threshold`. + /// @param prevOwner Owner that pointed to the owner to be removed in the linked list + /// @param owner Owner address to be removed. + /// @param _threshold New threshold. + function removeOwner(address prevOwner, address owner, uint256 _threshold) + public + authorized + { + // Only allow to remove an owner, if threshold can still be reached. + require(ownerCount - 1 >= _threshold, "New owner count needs to be larger than new threshold"); + // Validate owner address and check that it corresponds to owner index. + require(owner != address(0) && owner != SENTINEL_OWNERS, "Invalid owner address provided"); + require(owners[prevOwner] == owner, "Invalid prevOwner, owner pair provided"); + owners[prevOwner] = owners[owner]; + owners[owner] = address(0); + ownerCount--; + emit RemovedOwner(owner); + // Change threshold if threshold was changed. + if (threshold != _threshold) + changeThreshold(_threshold); + } + + /// @dev Allows to swap/replace an owner from the Safe with another address. + /// This can only be done via a Safe transaction. + /// @notice Replaces the owner `oldOwner` in the Safe with `newOwner`. + /// @param prevOwner Owner that pointed to the owner to be replaced in the linked list + /// @param oldOwner Owner address to be replaced. + /// @param newOwner New owner address. + function swapOwner(address prevOwner, address oldOwner, address newOwner) + public + authorized + { + // Owner address cannot be null. + require(newOwner != address(0) && newOwner != SENTINEL_OWNERS, "Invalid owner address provided"); + // No duplicate owners allowed. + require(owners[newOwner] == address(0), "Address is already an owner"); + // Validate oldOwner address and check that it corresponds to owner index. + require(oldOwner != address(0) && oldOwner != SENTINEL_OWNERS, "Invalid owner address provided"); + require(owners[prevOwner] == oldOwner, "Invalid prevOwner, owner pair provided"); + owners[newOwner] = owners[oldOwner]; + owners[prevOwner] = newOwner; + owners[oldOwner] = address(0); + emit RemovedOwner(oldOwner); + emit AddedOwner(newOwner); + } + + /// @dev Allows to update the number of required confirmations by Safe owners. + /// This can only be done via a Safe transaction. + /// @notice Changes the threshold of the Safe to `_threshold`. + /// @param _threshold New threshold. + function changeThreshold(uint256 _threshold) + public + authorized + { + // Validate that threshold is smaller than number of owners. + require(_threshold <= ownerCount, "Threshold cannot exceed owner count"); + // There has to be at least one Safe owner. + require(_threshold >= 1, "Threshold needs to be greater than 0"); + threshold = _threshold; + emit ChangedThreshold(threshold); + } + + function getThreshold() + public + view + returns (uint256) + { + return threshold; + } + + function isOwner(address owner) + public + view + returns (bool) + { + return owner != SENTINEL_OWNERS && owners[owner] != address(0); + } + + /// @dev Returns array of owners. + /// @return Array of Safe owners. + function getOwners() + public + view + returns (address[] memory) + { + address[] memory array = new address[](ownerCount); + + // populate return array + uint256 index = 0; + address currentOwner = owners[SENTINEL_OWNERS]; + while(currentOwner != SENTINEL_OWNERS) { + array[index] = currentOwner; + currentOwner = owners[currentOwner]; + index ++; + } + return array; + } +} +==== Source: ./contracts/base/Executor.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../common/Enum.sol"; + + +/// @title Executor - A contract that can execute transactions +/// @author Richard Meissner - +contract Executor { + + function execute(address to, uint256 value, bytes memory data, Enum.Operation operation, uint256 txGas) + internal + returns (bool success) + { + if (operation == Enum.Operation.Call) + success = executeCall(to, value, data, txGas); + else if (operation == Enum.Operation.DelegateCall) + success = executeDelegateCall(to, data, txGas); + else + success = false; + } + + function executeCall(address to, uint256 value, bytes memory data, uint256 txGas) + internal + returns (bool success) + { + // solium-disable-next-line security/no-inline-assembly + assembly { + success := call(txGas, to, value, add(data, 0x20), mload(data), 0, 0) + } + } + + function executeDelegateCall(address to, bytes memory data, uint256 txGas) + internal + returns (bool success) + { + // solium-disable-next-line security/no-inline-assembly + assembly { + success := delegatecall(txGas, to, add(data, 0x20), mload(data), 0, 0) + } + } +} +==== Source: ./contracts/base/FallbackManager.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +import "../common/SelfAuthorized.sol"; + +/// @title Fallback Manager - A contract that manages fallback calls made to this contract +/// @author Richard Meissner - +contract FallbackManager is SelfAuthorized { + + // keccak256("fallback_manager.handler.address") + bytes32 internal constant FALLBACK_HANDLER_STORAGE_SLOT = 0x6c9a6c4a39284e37ed1cf53d337577d14212a4870fb976a4366c693b939918d5; + + function internalSetFallbackHandler(address handler) internal { + bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; + // solium-disable-next-line security/no-inline-assembly + assembly { + sstore(slot, handler) + } + } + + /// @dev Allows to add a contract to handle fallback calls. + /// Only fallback calls without value and with data will be forwarded. + /// This can only be done via a Safe transaction. + /// @param handler contract to handle fallbacks calls. + function setFallbackHandler(address handler) + public + authorized + { + internalSetFallbackHandler(handler); + } + + fallback() + external + payable + { + // Only calls without value and with data will be forwarded + if (msg.value > 0 || msg.data.length == 0) { + return; + } + bytes32 slot = FALLBACK_HANDLER_STORAGE_SLOT; + address handler; + // solium-disable-next-line security/no-inline-assembly + assembly { + handler := sload(slot) + } + + if (handler != address(0)) { + // solium-disable-next-line security/no-inline-assembly + assembly { + calldatacopy(0, 0, calldatasize()) + let success := call(gas(), handler, 0, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + if eq(success, 0) { revert(0, returndatasize()) } + return(0, returndatasize()) + } + } + } +} +==== Source: ./contracts/Migrations.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +contract Migrations { + address public owner; + uint public last_completed_migration; + + modifier restricted() { + if (msg.sender == owner) _; + } + + constructor() + { + owner = msg.sender; + } + + function setCompleted(uint completed) + public + restricted + { + last_completed_migration = completed; + } + + function upgrade(address new_address) + public + restricted + { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } +} +==== Source: ./contracts/interfaces/ISignatureValidator.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +abstract contract ISignatureValidatorConstants { + // bytes4(keccak256("isValidSignature(bytes,bytes)") + bytes4 constant internal EIP1271_MAGIC_VALUE = 0x20c13b0b; +} + +abstract contract ISignatureValidator is ISignatureValidatorConstants { + + /** + * @dev Should return whether the signature provided is valid for the provided data + * @param _data Arbitrary length data signed on the behalf of address(this) + * @param _signature Signature byte array associated with _data + * + * MUST return the bytes4 magic value 0x20c13b0b when function passes. + * MUST NOT modify state (using STATICCALL for solc < 0.5, view modifier for solc > 0.5) + * MUST allow external calls + */ + function isValidSignature( + bytes memory _data, + bytes memory _signature) + public + view + virtual + returns (bytes4); +} +==== Source: ./contracts/interfaces/ERC1155TokenReceiver.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +/** + Note: The ERC-165 identifier for this interface is 0x4e2312e0. +*/ +interface ERC1155TokenReceiver { + /** + @notice Handle the receipt of a single ERC1155 token type. + @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeTransferFrom` after the balance has been updated. + This function MUST return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` (i.e. 0xf23a6e61) if it accepts the transfer. + This function MUST revert if it rejects the transfer. + Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. + @param _operator The address which initiated the transfer (i.e. msg.sender) + @param _from The address which previously owned the token + @param _id The ID of the token being transferred + @param _value The amount of tokens being transferred + @param _data Additional data with no specified format + @return `bytes4(keccak256("onERC1155Received(address,address,uint256,uint256,bytes)"))` + */ + function onERC1155Received(address _operator, address _from, uint256 _id, uint256 _value, bytes calldata _data) external returns(bytes4); + + /** + @notice Handle the receipt of multiple ERC1155 token types. + @dev An ERC1155-compliant smart contract MUST call this function on the token recipient contract, at the end of a `safeBatchTransferFrom` after the balances have been updated. + This function MUST return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` (i.e. 0xbc197c81) if it accepts the transfer(s). + This function MUST revert if it rejects the transfer(s). + Return of any other value than the prescribed keccak256 generated value MUST result in the transaction being reverted by the caller. + @param _operator The address which initiated the batch transfer (i.e. msg.sender) + @param _from The address which previously owned the token + @param _ids An array containing ids of each token being transferred (order and length must match _values array) + @param _values An array containing amounts of each token being transferred (order and length must match _ids array) + @param _data Additional data with no specified format + @return `bytes4(keccak256("onERC1155BatchReceived(address,address,uint256[],uint256[],bytes)"))` + */ + function onERC1155BatchReceived(address _operator, address _from, uint256[] calldata _ids, uint256[] calldata _values, bytes calldata _data) external returns(bytes4); +} +==== Source: ./contracts/interfaces/ERC721TokenReceiver.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +/// @dev Note: the ERC-165 identifier for this interface is 0x150b7a02. +interface ERC721TokenReceiver { + /// @notice Handle the receipt of an NFT + /// @dev The ERC721 smart contract calls this function on the recipient + /// after a `transfer`. This function MAY throw to revert and reject the + /// transfer. Return of other than the magic value MUST result in the + /// transaction being reverted. + /// Note: the contract address is always the message sender. + /// @param _operator The address which called `safeTransferFrom` function + /// @param _from The address which previously owned the token + /// @param _tokenId The NFT identifier which is being transferred + /// @param _data Additional data with no specified format + /// @return `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))` + /// unless throwing + function onERC721Received(address _operator, address _from, uint256 _tokenId, bytes calldata _data) external returns(bytes4); +} +==== Source: ./contracts/interfaces/ERC777TokensRecipient.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +interface ERC777TokensRecipient { + function tokensReceived( + address operator, + address from, + address to, + uint256 amount, + bytes calldata data, + bytes calldata operatorData + ) external; +} +==== Source: ./contracts/libraries/CreateCall.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + + +/// @title Create Call - Allows to use the different create opcodes to deploy a contract +/// @author Richard Meissner - +contract CreateCall { + event ContractCreation(address newContract); + + function performCreate2(uint256 value, bytes memory deploymentData, bytes32 salt) public returns(address newContract) { + // solium-disable-next-line security/no-inline-assembly + assembly { + newContract := create2(value, add(0x20, deploymentData), mload(deploymentData), salt) + } + require(newContract != address(0), "Could not deploy contract"); + emit ContractCreation(newContract); + } + + function performCreate(uint256 value, bytes memory deploymentData) public returns(address newContract) { + // solium-disable-next-line security/no-inline-assembly + assembly { + newContract := create(value, add(deploymentData, 0x20), mload(deploymentData)) + } + require(newContract != address(0), "Could not deploy contract"); + emit ContractCreation(newContract); + } +} +==== Source: ./contracts/libraries/CreateAndAddModules.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../base/Module.sol"; + + +/// @title Create and Add Modules - Allows to create and add multiple module in one transaction. +/// @author Stefan George - +/// @author Richard Meissner - +contract CreateAndAddModules { + + /// @dev Function required to compile contract. Gnosis Safe function is called instead. + /// @param module Not used. + function enableModule(Module module) + public + { + revert(); + } + + /// @dev Allows to create and add multiple module in one transaction. + /// @param proxyFactory Module proxy factory contract. + /// @param data Modules constructor payload. This is the data for each proxy factory call concatinated. (e.g. ) + function createAndAddModules(address proxyFactory, bytes memory data) + public + { + uint256 length = data.length; + Module module; + uint256 i = 0; + while (i < length) { + // solium-disable-next-line security/no-inline-assembly + assembly { + let createBytesLength := mload(add(0x20, add(data, i))) + let createBytes := add(0x40, add(data, i)) + + let output := mload(0x40) + if eq(delegatecall(gas(), proxyFactory, createBytes, createBytesLength, output, 0x20), 0) { revert(0, 0) } + module := and(mload(output), 0xffffffffffffffffffffffffffffffffffffffff) + + // Data is always padded to 32 bytes + i := add(i, add(0x20, mul(div(add(createBytesLength, 0x1f), 0x20), 0x20))) + } + this.enableModule(module); + } + } +} +==== Source: ./contracts/libraries/MultiSend.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + + +/// @title Multi Send - Allows to batch multiple transactions into one. +/// @author Nick Dodson - +/// @author Gonçalo Sá - +/// @author Stefan George - +/// @author Richard Meissner - +contract MultiSend { + + bytes32 constant private GUARD_VALUE = keccak256("multisend.guard.bytes32"); + + bytes32 guard; + + constructor() { + guard = GUARD_VALUE; + } + + /// @dev Sends multiple transactions and reverts all if one fails. + /// @param transactions Encoded transactions. Each transaction is encoded as a packed bytes of + /// operation as a uint8 with 0 for a call or 1 for a delegatecall (=> 1 byte), + /// to as a address (=> 20 bytes), + /// value as a uint256 (=> 32 bytes), + /// data length as a uint256 (=> 32 bytes), + /// data as bytes. + /// see abi.encodePacked for more information on packed encoding + function multiSend(bytes memory transactions) + public + { + require(guard != GUARD_VALUE, "MultiSend should only be called via delegatecall"); + // solium-disable-next-line security/no-inline-assembly + assembly { + let length := mload(transactions) + let i := 0x20 + for { } lt(i, length) { } { + // First byte of the data is the operation. + // We shift by 248 bits (256 - 8 [operation byte]) it right since mload will always load 32 bytes (a word). + // This will also zero out unused data. + let operation := shr(0xf8, mload(add(transactions, i))) + // We offset the load address by 1 byte (operation byte) + // We shift it right by 96 bits (256 - 160 [20 address bytes]) to right-align the data and zero out unused data. + let to := shr(0x60, mload(add(transactions, add(i, 0x01)))) + // We offset the load address by 21 byte (operation byte + 20 address bytes) + let value := mload(add(transactions, add(i, 0x15))) + // We offset the load address by 53 byte (operation byte + 20 address bytes + 32 value bytes) + let dataLength := mload(add(transactions, add(i, 0x35))) + // We offset the load address by 85 byte (operation byte + 20 address bytes + 32 value bytes + 32 data length bytes) + let data := add(transactions, add(i, 0x55)) + let success := 0 + switch operation + case 0 { success := call(gas(), to, value, data, dataLength, 0, 0) } + case 1 { success := delegatecall(gas(), to, data, dataLength, 0, 0) } + if eq(success, 0) { revert(0, 0) } + // Next entry starts at 85 byte + data length + i := add(i, add(0x55, dataLength)) + } + } + } +} +==== Source: ./contracts/common/EtherPaymentFallback.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + + +/// @title EtherPaymentFallback - A contract that has a fallback to accept ether payments +/// @author Richard Meissner - +contract EtherPaymentFallback { + + /// @dev Receive function accepts Ether transactions. + receive() + external + payable + { + + } +} +==== Source: ./contracts/common/SecuredTokenTransfer.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + + +/// @title SecuredTokenTransfer - Secure token transfer +/// @author Richard Meissner - +contract SecuredTokenTransfer { + + /// @dev Transfers a token and returns if it was a success + /// @param token Token that should be transferred + /// @param receiver Receiver to whom the token should be transferred + /// @param amount The amount of tokens that should be transferred + function transferToken ( + address token, + address receiver, + uint256 amount + ) + internal + returns (bool transferred) + { + bytes memory data = abi.encodeWithSignature("transfer(address,uint256)", receiver, amount); + // solium-disable-next-line security/no-inline-assembly + assembly { + let success := call(sub(gas(), 10000), token, 0, add(data, 0x20), mload(data), 0, 0) + let ptr := mload(0x40) + mstore(0x40, add(ptr, returndatasize())) + returndatacopy(ptr, 0, returndatasize()) + switch returndatasize() + case 0 { transferred := success } + case 0x20 { transferred := iszero(or(iszero(success), iszero(mload(ptr)))) } + default { transferred := 0 } + } + } +} +==== Source: ./contracts/common/MasterCopy.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "./SelfAuthorized.sol"; + + +/// @title MasterCopy - Base for master copy contracts (should always be first super contract) +/// This contract is tightly coupled to our proxy contract (see `proxies/GnosisSafeProxy.sol`) +/// @author Richard Meissner - +contract MasterCopy is SelfAuthorized { + + event ChangedMasterCopy(address masterCopy); + + // masterCopy always needs to be first declared variable, to ensure that it is at the same location as in the Proxy contract. + // It should also always be ensured that the address is stored alone (uses a full word) + address private masterCopy; + + /// @dev Allows to upgrade the contract. This can only be done via a Safe transaction. + /// @param _masterCopy New contract address. + function changeMasterCopy(address _masterCopy) + public + authorized + { + // Master copy address cannot be null. + require(_masterCopy != address(0), "Invalid master copy address provided"); + masterCopy = _masterCopy; + emit ChangedMasterCopy(_masterCopy); + } +} +==== Source: ./contracts/common/SelfAuthorized.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + + +/// @title SelfAuthorized - authorizes current contract to perform actions +/// @author Richard Meissner - +contract SelfAuthorized { + modifier authorized() virtual { + require(msg.sender == address(this), "Method can only be called from this contract"); + _; + } +} +==== Source: ./contracts/common/SignatureDecoder.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + + +/// @title SignatureDecoder - Decodes signatures that a encoded as bytes +/// @author Ricardo Guilherme Schmidt (Status Research & Development GmbH) +/// @author Richard Meissner - +contract SignatureDecoder { + + /// @dev Recovers address who signed the message + /// @param messageHash operation ethereum signed message hash + /// @param messageSignature message `txHash` signature + /// @param pos which signature to read + function recoverKey ( + bytes32 messageHash, + bytes memory messageSignature, + uint256 pos + ) + internal + pure + returns (address) + { + uint8 v; + bytes32 r; + bytes32 s; + (v, r, s) = signatureSplit(messageSignature, pos); + return ecrecover(messageHash, v, r, s); + } + + /// @dev divides bytes signature into `uint8 v, bytes32 r, bytes32 s`. + /// @notice Make sure to peform a bounds check for @param pos, to avoid out of bounds access on @param signatures + /// @param pos which signature to read. A prior bounds check of this parameter should be performed, to avoid out of bounds access + /// @param signatures concatenated rsv signatures + function signatureSplit(bytes memory signatures, uint256 pos) + internal + pure + returns (uint8 v, bytes32 r, bytes32 s) + { + // The signature format is a compact form of: + // {bytes32 r}{bytes32 s}{uint8 v} + // Compact means, uint8 is not padded to 32 bytes. + // solium-disable-next-line security/no-inline-assembly + assembly { + let signaturePos := mul(0x41, pos) + r := mload(add(signatures, add(signaturePos, 0x20))) + s := mload(add(signatures, add(signaturePos, 0x40))) + // Here we are loading the last 32 bytes, including 31 bytes + // of 's'. There is no 'mload8' to do this. + // + // 'byte' is not working due to the Solidity parser, so lets + // use the second best option, 'and' + v := and(mload(add(signatures, add(signaturePos, 0x41))), 0xff) + } + } +} +==== Source: ./contracts/common/Enum.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + + +/// @title Enum - Collection of enums +/// @author Richard Meissner - +contract Enum { + enum Operation { + Call, + DelegateCall + } +} +==== Source: ./contracts/mocks/Token.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "mock-contract/contracts/MockContract.sol"; +abstract contract Token { + function transfer(address _to, uint value) public virtual returns (bool); +} +==== Source: ./contracts/mocks/ERC1155Token.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +import "../interfaces/ERC1155TokenReceiver.sol"; +import "../external/GnosisSafeMath.sol"; + +contract ERC1155Token { + + using GnosisSafeMath for uint256; + + // Mapping from token ID to owner balances + mapping (uint256 => mapping(address => uint256)) private _balances; + + // Mapping from owner to operator approvals + mapping (address => mapping(address => bool)) private _operatorApprovals; + + /** + @dev Get the specified address' balance for token with specified ID. + @param owner The address of the token holder + @param id ID of the token + @return The owner's balance of the token type requested + */ + function balanceOf(address owner, uint256 id) public view returns (uint256) { + require(owner != address(0), "ERC1155: balance query for the zero address"); + return _balances[id][owner]; + } + + /** + @dev Transfers `value` amount of an `id` from the `from` address to the `to` address specified. + Caller must be approved to manage the tokens being transferred out of the `from` account. + If `to` is a smart contract, will call `onERC1155Received` on `to` and act appropriately. + @param from Source address + @param to Target address + @param id ID of the token type + @param value Transfer amount + @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver + */ + function safeTransferFrom( + address from, + address to, + uint256 id, + uint256 value, + bytes calldata data + ) + external + { + require(to != address(0), "ERC1155: target address must be non-zero"); + require( + from == msg.sender || _operatorApprovals[from][msg.sender] == true, + "ERC1155: need operator approval for 3rd party transfers." + ); + + _balances[id][from] = _balances[id][from].sub(value); + _balances[id][to] = value.add(_balances[id][to]); + + _doSafeTransferAcceptanceCheck(msg.sender, from, to, id, value, data); + } + + /** + * @dev Test function to mint an amount of a token with the given ID + * @param to The address that will own the minted token + * @param id ID of the token to be minted + * @param value Amount of the token to be minted + * @param data Data forwarded to `onERC1155Received` if `to` is a contract receiver + */ + function mint(address to, uint256 id, uint256 value, bytes calldata data) external { + require(to != address(0), "ERC1155: mint to the zero address"); + + _balances[id][to] = value.add(_balances[id][to]); + + _doSafeTransferAcceptanceCheck(msg.sender, address(0), to, id, value, data); + } + + function isContract(address account) internal view returns (bool) { + // This method relies in extcodesize, which returns 0 for contracts in + // construction, since the code is only stored at the end of the + // constructor execution. + + uint256 size; + // solium-disable-next-line security/no-inline-assembly + assembly { size := extcodesize(account) } + return size > 0; + } + + function _doSafeTransferAcceptanceCheck( + address operator, + address from, + address to, + uint256 id, + uint256 value, + bytes memory data + ) + internal + { + if(isContract(to)) { + require( + ERC1155TokenReceiver(to).onERC1155Received(operator, from, id, value, data) == + ERC1155TokenReceiver(to).onERC1155Received.selector, + "ERC1155: got unknown value from onERC1155Received" + ); + } + } +} +==== Source: ./contracts/external/GnosisSafeMath.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +/** + * @title GnosisSafeMath + * @dev Math operations with safety checks that revert on error + * Renamed from SafeMath to GnosisSafeMath to avoid conflicts + * TODO: remove once open zeppelin update to solc 0.5.0 + */ +library GnosisSafeMath { + + /** + * @dev Multiplies two numbers, reverts on overflow. + */ + function mul(uint256 a, uint256 b) internal pure returns (uint256) { + // Gas optimization: this is cheaper than requiring 'a' not being zero, but the + // benefit is lost if 'b' is also tested. + // See: https://github.com/OpenZeppelin/openzeppelin-solidity/pull/522 + if (a == 0) { + return 0; + } + + uint256 c = a * b; + require(c / a == b); + + return c; + } + + /** + * @dev Integer division of two numbers truncating the quotient, reverts on division by zero. + */ + function div(uint256 a, uint256 b) internal pure returns (uint256) { + require(b > 0); // Solidity only automatically asserts when dividing by 0 + uint256 c = a / b; + // assert(a == b * c + a % b); // There is no case in which this doesn't hold + + return c; + } + + /** + * @dev Subtracts two numbers, reverts on overflow (i.e. if subtrahend is greater than minuend). + */ + function sub(uint256 a, uint256 b) internal pure returns (uint256) { + require(b <= a); + uint256 c = a - b; + + return c; + } + + /** + * @dev Adds two numbers, reverts on overflow. + */ + function add(uint256 a, uint256 b) internal pure returns (uint256) { + uint256 c = a + b; + require(c >= a); + + return c; + } + + /** + * @dev Divides two numbers and returns the remainder (unsigned integer modulo), + * reverts when dividing by zero. + */ + function mod(uint256 a, uint256 b) internal pure returns (uint256) { + require(b != 0); + return a % b; + } + + + /** + * @dev Returns the largest of two numbers. + */ + function max(uint256 a, uint256 b) internal pure returns (uint256) { + return a >= b ? a : b; + } +} +==== Source: ./contracts/proxies/GnosisSafeProxy.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +/// @title IProxy - Helper interface to access masterCopy of the Proxy on-chain +/// @author Richard Meissner - +interface IProxy { + function masterCopy() external view returns (address); +} + +/// @title GnosisSafeProxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. +/// @author Stefan George - +/// @author Richard Meissner - +contract GnosisSafeProxy { + + // masterCopy always needs to be first declared variable, to ensure that it is at the same location in the contracts to which calls are delegated. + // To reduce deployment costs this variable is internal and needs to be retrieved via `getStorageAt` + address internal masterCopy; + + /// @dev Constructor function sets address of master copy contract. + /// @param _masterCopy Master copy address. + constructor(address _masterCopy) + { + require(_masterCopy != address(0), "Invalid master copy address provided"); + masterCopy = _masterCopy; + } + + /// @dev Fallback function forwards all transactions and returns all received return data. + fallback() + external + payable + { + // solium-disable-next-line security/no-inline-assembly + assembly { + let masterCopy_ := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) + // 0xa619486e == keccak("masterCopy()"). The value is right padded to 32-bytes with 0s + if eq(calldataload(0), 0xa619486e00000000000000000000000000000000000000000000000000000000) { + mstore(0, masterCopy_) + return(0, 0x20) + } + calldatacopy(0, 0, calldatasize()) + let success := delegatecall(gas(), masterCopy_, 0, calldatasize(), 0, 0) + returndatacopy(0, 0, returndatasize()) + if eq(success, 0) { revert(0, returndatasize()) } + return(0, returndatasize()) + } + } +} +==== Source: ./contracts/proxies/IProxyCreationCallback.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.3; +import "./GnosisSafeProxy.sol"; + +interface IProxyCreationCallback { + function proxyCreated(GnosisSafeProxy proxy, address _mastercopy, bytes calldata initializer, uint256 saltNonce) external; +} +==== Source: ./contracts/proxies/PayingProxy.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "../common/SecuredTokenTransfer.sol"; +import "./DelegateConstructorProxy.sol"; + +/// @title Paying Proxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. It is possible to send along initialization data with the constructor. And sends funds after creation to a specified account. +/// @author Stefan George - +/// @author Richard Meissner - +contract PayingProxy is DelegateConstructorProxy, SecuredTokenTransfer { + + /// @dev Constructor function sets address of master copy contract. + /// @param _masterCopy Master copy address. + /// @param initializer Data used for a delegate call to initialize the contract. + /// @param funder Address that should be paid for the execution of this call + /// @param paymentToken Token that should be used for the payment (0 is ETH) + /// @param payment Value that should be paid + constructor(address _masterCopy, bytes memory initializer, address payable funder, address paymentToken, uint256 payment) + DelegateConstructorProxy(_masterCopy, initializer) + { + if (payment > 0) { + if (paymentToken == address(0)) { + // solium-disable-next-line security/no-send + require(funder.send(payment), "Could not pay safe creation with ether"); + } else { + require(transferToken(paymentToken, funder, payment), "Could not pay safe creation with token"); + } + } + } +} +==== Source: ./contracts/proxies/DelegateConstructorProxy.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "./GnosisSafeProxy.sol"; + + +/// @title Delegate Constructor Proxy - Generic proxy contract allows to execute all transactions applying the code of a master contract. It is possible to send along initialization data with the constructor. +/// @author Stefan George - +/// @author Richard Meissner - +contract DelegateConstructorProxy is GnosisSafeProxy { + + /// @dev Constructor function sets address of master copy contract. + /// @param _masterCopy Master copy address. + /// @param initializer Data used for a delegate call to initialize the contract. + constructor(address _masterCopy, bytes memory initializer) GnosisSafeProxy(_masterCopy) + { + if (initializer.length > 0) { + // solium-disable-next-line security/no-inline-assembly + assembly { + let masterCopy_ := and(sload(0), 0xffffffffffffffffffffffffffffffffffffffff) + let success := delegatecall(sub(gas(), 10000), masterCopy_, add(initializer, 0x20), mload(initializer), 0, 0) + let ptr := mload(0x40) + returndatacopy(ptr, 0, returndatasize()) + if eq(success, 0) { revert(ptr, returndatasize()) } + } + } + } +} +==== Source: ./contracts/proxies/GnosisSafeProxyFactory.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.3; +import "./GnosisSafeProxy.sol"; +import "./IProxyCreationCallback.sol"; + +/// @title Proxy Factory - Allows to create new proxy contact and execute a message call to the new proxy within one transaction. +/// @author Stefan George - +contract GnosisSafeProxyFactory { + + event ProxyCreation(GnosisSafeProxy proxy); + + /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. + /// @param masterCopy Address of master copy. + /// @param data Payload for message call sent to new proxy contract. + function createProxy(address masterCopy, bytes memory data) + public + returns (GnosisSafeProxy proxy) + { + proxy = new GnosisSafeProxy(masterCopy); + if (data.length > 0) + // solium-disable-next-line security/no-inline-assembly + assembly { + if eq(call(gas(), proxy, 0, add(data, 0x20), mload(data), 0, 0), 0) { revert(0, 0) } + } + emit ProxyCreation(proxy); + } + + /// @dev Allows to retrieve the runtime code of a deployed Proxy. This can be used to check that the expected Proxy was deployed. + function proxyRuntimeCode() public pure returns (bytes memory) { + return type(GnosisSafeProxy).runtimeCode; + } + + /// @dev Allows to retrieve the creation code used for the Proxy deployment. With this it is easily possible to calculate predicted address. + function proxyCreationCode() public pure returns (bytes memory) { + return type(GnosisSafeProxy).creationCode; + } + + /// @dev Allows to create new proxy contact using CREATE2 but it doesn't run the initializer. + /// This method is only meant as an utility to be called from other methods + /// @param _mastercopy Address of master copy. + /// @param initializer Payload for message call sent to new proxy contract. + /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. + function deployProxyWithNonce(address _mastercopy, bytes memory initializer, uint256 saltNonce) + internal + returns (GnosisSafeProxy proxy) + { + // If the initializer changes the proxy address should change too. Hashing the initializer data is cheaper than just concatinating it + bytes32 salt = keccak256(abi.encodePacked(keccak256(initializer), saltNonce)); + bytes memory deploymentData = abi.encodePacked(type(GnosisSafeProxy).creationCode, uint256(uint160(_mastercopy))); + // solium-disable-next-line security/no-inline-assembly + assembly { + proxy := create2(0x0, add(0x20, deploymentData), mload(deploymentData), salt) + } + require(address(proxy) != address(0), "Create2 call failed"); + } + + /// @dev Allows to create new proxy contact and execute a message call to the new proxy within one transaction. + /// @param _mastercopy Address of master copy. + /// @param initializer Payload for message call sent to new proxy contract. + /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. + function createProxyWithNonce(address _mastercopy, bytes memory initializer, uint256 saltNonce) + public + returns (GnosisSafeProxy proxy) + { + proxy = deployProxyWithNonce(_mastercopy, initializer, saltNonce); + if (initializer.length > 0) + // solium-disable-next-line security/no-inline-assembly + assembly { + if eq(call(gas(), proxy, 0, add(initializer, 0x20), mload(initializer), 0, 0), 0) { revert(0,0) } + } + emit ProxyCreation(proxy); + } + + /// @dev Allows to create new proxy contact, execute a message call to the new proxy and call a specified callback within one transaction + /// @param _mastercopy Address of master copy. + /// @param initializer Payload for message call sent to new proxy contract. + /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. + /// @param callback Callback that will be invoced after the new proxy contract has been successfully deployed and initialized. + function createProxyWithCallback(address _mastercopy, bytes memory initializer, uint256 saltNonce, IProxyCreationCallback callback) + public + returns (GnosisSafeProxy proxy) + { + uint256 saltNonceWithCallback = uint256(keccak256(abi.encodePacked(saltNonce, callback))); + proxy = createProxyWithNonce(_mastercopy, initializer, saltNonceWithCallback); + if (address(callback) != address(0)) + callback.proxyCreated(proxy, _mastercopy, initializer, saltNonce); + } + + /// @dev Allows to get the address for a new proxy contact created via `createProxyWithNonce` + /// This method is only meant for address calculation purpose when you use an initializer that would revert, + /// therefore the response is returned with a revert. When calling this method set `from` to the address of the proxy factory. + /// @param _mastercopy Address of master copy. + /// @param initializer Payload for message call sent to new proxy contract. + /// @param saltNonce Nonce that will be used to generate the salt to calculate the address of the new proxy contract. + function calculateCreateProxyWithNonceAddress(address _mastercopy, bytes calldata initializer, uint256 saltNonce) + external + returns (GnosisSafeProxy proxy) + { + proxy = deployProxyWithNonce(_mastercopy, initializer, saltNonce); + revert(string(abi.encodePacked(proxy))); + } + +} +==== Source: ./contracts/GnosisSafe.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; +import "./base/ModuleManager.sol"; +import "./base/OwnerManager.sol"; +import "./base/FallbackManager.sol"; +import "./common/MasterCopy.sol"; +import "./common/SignatureDecoder.sol"; +import "./common/SecuredTokenTransfer.sol"; +import "./interfaces/ISignatureValidator.sol"; +import "./external/GnosisSafeMath.sol"; + +/// @title Gnosis Safe - A multisignature wallet with support for confirmations using signed messages based on ERC191. +/// @author Stefan George - +/// @author Richard Meissner - +/// @author Ricardo Guilherme Schmidt - (Status Research & Development GmbH) - Gas Token Payment +contract GnosisSafe + is MasterCopy, ModuleManager, OwnerManager, SignatureDecoder, SecuredTokenTransfer, ISignatureValidatorConstants, FallbackManager { + + using GnosisSafeMath for uint256; + + string public constant NAME = "Gnosis Safe"; + string public constant VERSION = "1.2.0"; + + //keccak256( + // "EIP712Domain(address verifyingContract)" + //); + bytes32 private constant DOMAIN_SEPARATOR_TYPEHASH = 0x035aff83d86937d35b32e04f0ddc6ff469290eef2f1b692d8a815c89404d4749; + + //keccak256( + // "SafeTx(address to,uint256 value,bytes data,uint8 operation,uint256 safeTxGas,uint256 baseGas,uint256 gasPrice,address gasToken,address refundReceiver,uint256 nonce)" + //); + bytes32 private constant SAFE_TX_TYPEHASH = 0xbb8310d486368db6bd6f849402fdd73ad53d316b5a4b2644ad6efe0f941286d8; + + //keccak256( + // "SafeMessage(bytes message)" + //); + bytes32 private constant SAFE_MSG_TYPEHASH = 0x60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca; + + event ApproveHash( + bytes32 indexed approvedHash, + address indexed owner + ); + event SignMsg( + bytes32 indexed msgHash + ); + event ExecutionFailure( + bytes32 txHash, uint256 payment + ); + event ExecutionSuccess( + bytes32 txHash, uint256 payment + ); + + uint256 public nonce; + bytes32 public domainSeparator; + // Mapping to keep track of all message hashes that have been approve by ALL REQUIRED owners + mapping(bytes32 => uint256) public signedMessages; + // Mapping to keep track of all hashes (message or transaction) that have been approve by ANY owners + mapping(address => mapping(bytes32 => uint256)) public approvedHashes; + + // This constructor ensures that this contract can only be used as a master copy for Proxy contracts + constructor() { + // By setting the threshold it is not possible to call setup anymore, + // so we create a Safe with 0 owners and threshold 1. + // This is an unusable Safe, perfect for the mastercopy + threshold = 1; + } + + /// @dev Setup function sets initial storage of contract. + /// @param _owners List of Safe owners. + /// @param _threshold Number of required confirmations for a Safe transaction. + /// @param to Contract address for optional delegate call. + /// @param data Data payload for optional delegate call. + /// @param fallbackHandler Handler for fallback calls to this contract + /// @param paymentToken Token that should be used for the payment (0 is ETH) + /// @param payment Value that should be paid + /// @param paymentReceiver Adddress that should receive the payment (or 0 if tx.origin) + function setup( + address[] calldata _owners, + uint256 _threshold, + address to, + bytes calldata data, + address fallbackHandler, + address paymentToken, + uint256 payment, + address payable paymentReceiver + ) + external + { + require(domainSeparator == 0, "Domain Separator already set!"); + domainSeparator = keccak256(abi.encode(DOMAIN_SEPARATOR_TYPEHASH, this)); + setupOwners(_owners, _threshold); + if (fallbackHandler != address(0)) internalSetFallbackHandler(fallbackHandler); + // As setupOwners can only be called if the contract has not been initialized we don't need a check for setupModules + setupModules(to, data); + + if (payment > 0) { + // To avoid running into issues with EIP-170 we reuse the handlePayment function (to avoid adjusting code of that has been verified we do not adjust the method itself) + // baseGas = 0, gasPrice = 1 and gas = payment => amount = (payment + 0) * 1 = payment + handlePayment(payment, 0, 1, paymentToken, paymentReceiver); + } + } + + /// @dev Allows to execute a Safe transaction confirmed by required number of owners and then pays the account that submitted the transaction. + /// Note: The fees are always transfered, even if the user transaction fails. + /// @param to Destination address of Safe transaction. + /// @param value Ether value of Safe transaction. + /// @param data Data payload of Safe transaction. + /// @param operation Operation type of Safe transaction. + /// @param safeTxGas Gas that should be used for the Safe transaction. + /// @param baseGas Gas costs for that are indipendent of the transaction execution(e.g. base transaction fee, signature check, payment of the refund) + /// @param gasPrice Gas price that should be used for the payment calculation. + /// @param gasToken Token address (or 0 if ETH) that is used for the payment. + /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). + /// @param signatures Packed signature data ({bytes32 r}{bytes32 s}{uint8 v}) + function execTransaction( + address to, + uint256 value, + bytes calldata data, + Enum.Operation operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address payable refundReceiver, + bytes calldata signatures + ) + external + payable + returns (bool success) + { + bytes32 txHash; + // Use scope here to limit variable lifetime and prevent `stack too deep` errors + { + bytes memory txHashData = encodeTransactionData( + to, value, data, operation, // Transaction info + safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, // Payment info + nonce + ); + // Increase nonce and execute transaction. + nonce++; + txHash = keccak256(txHashData); + checkSignatures(txHash, txHashData, signatures, true); + } + // We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500) + // We also include the 1/64 in the check that is not send along with a call to counteract potential shortings because of EIP-150 + require(gasleft() >= (safeTxGas * 64 / 63).max(safeTxGas + 2500) + 500, "Not enough gas to execute safe transaction"); + // Use scope here to limit variable lifetime and prevent `stack too deep` errors + { + uint256 gasUsed = gasleft(); + // If the gasPrice is 0 we assume that nearly all available gas can be used (it is always more than safeTxGas) + // We only substract 2500 (compared to the 3000 before) to ensure that the amount passed is still higher than safeTxGas + success = execute(to, value, data, operation, gasPrice == 0 ? (gasleft() - 2500) : safeTxGas); + gasUsed = gasUsed.sub(gasleft()); + // We transfer the calculated tx costs to the tx.origin to avoid sending it to intermediate contracts that have made calls + uint256 payment = 0; + if (gasPrice > 0) { + payment = handlePayment(gasUsed, baseGas, gasPrice, gasToken, refundReceiver); + } + if (success) emit ExecutionSuccess(txHash, payment); + else emit ExecutionFailure(txHash, payment); + } + } + + function handlePayment( + uint256 gasUsed, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address payable refundReceiver + ) + private + returns (uint256 payment) + { + // solium-disable-next-line security/no-tx-origin + address payable receiver = refundReceiver == address(0) ? payable(tx.origin) : refundReceiver; + if (gasToken == address(0)) { + // For ETH we will only adjust the gas price to not be higher than the actual used gas price + payment = gasUsed.add(baseGas).mul(gasPrice < tx.gasprice ? gasPrice : tx.gasprice); + // solium-disable-next-line security/no-send + require(receiver.send(payment), "Could not pay gas costs with ether"); + } else { + payment = gasUsed.add(baseGas).mul(gasPrice); + require(transferToken(gasToken, receiver, payment), "Could not pay gas costs with token"); + } + } + + /** + * @dev Checks whether the signature provided is valid for the provided data, hash. Will revert otherwise. + * @param dataHash Hash of the data (could be either a message hash or transaction hash) + * @param data That should be signed (this is passed to an external validator contract) + * @param signatures Signature data that should be verified. Can be ECDSA signature, contract signature (EIP-1271) or approved hash. + * @param consumeHash Indicates that in case of an approved hash the storage can be freed to save gas + */ + function checkSignatures(bytes32 dataHash, bytes memory data, bytes memory signatures, bool consumeHash) + internal + { + // Load threshold to avoid multiple storage loads + uint256 _threshold = threshold; + // Check that a threshold is set + require(_threshold > 0, "Threshold needs to be defined!"); + // Check that the provided signature data is not too short + require(signatures.length >= _threshold.mul(65), "Signatures data too short"); + // There cannot be an owner with address 0. + address lastOwner = address(0); + address currentOwner; + uint8 v; + bytes32 r; + bytes32 s; + uint256 i; + for (i = 0; i < _threshold; i++) { + (v, r, s) = signatureSplit(signatures, i); + // If v is 0 then it is a contract signature + if (v == 0) { + // When handling contract signatures the address of the contract is encoded into r + currentOwner = address(uint160(uint256(r))); + + // Check that signature data pointer (s) is not pointing inside the static part of the signatures bytes + // This check is not completely accurate, since it is possible that more signatures than the threshold are send. + // Here we only check that the pointer is not pointing inside the part that is being processed + require(uint256(s) >= _threshold.mul(65), "Invalid contract signature location: inside static part"); + + // Check that signature data pointer (s) is in bounds (points to the length of data -> 32 bytes) + require(uint256(s).add(32) <= signatures.length, "Invalid contract signature location: length not present"); + + // Check if the contract signature is in bounds: start of data is s + 32 and end is start + signature length + uint256 contractSignatureLen; + // solium-disable-next-line security/no-inline-assembly + assembly { + contractSignatureLen := mload(add(add(signatures, s), 0x20)) + } + require(uint256(s).add(32).add(contractSignatureLen) <= signatures.length, "Invalid contract signature location: data not complete"); + + // Check signature + bytes memory contractSignature; + // solium-disable-next-line security/no-inline-assembly + assembly { + // The signature data for contract signatures is appended to the concatenated signatures and the offset is stored in s + contractSignature := add(add(signatures, s), 0x20) + } + require(ISignatureValidator(currentOwner).isValidSignature(data, contractSignature) == EIP1271_MAGIC_VALUE, "Invalid contract signature provided"); + // If v is 1 then it is an approved hash + } else if (v == 1) { + // When handling approved hashes the address of the approver is encoded into r + currentOwner = address(uint160(uint256(r))); + // Hashes are automatically approved by the sender of the message or when they have been pre-approved via a separate transaction + require(msg.sender == currentOwner || approvedHashes[currentOwner][dataHash] != 0, "Hash has not been approved"); + // Hash has been marked for consumption. If this hash was pre-approved free storage + if (consumeHash && msg.sender != currentOwner) { + approvedHashes[currentOwner][dataHash] = 0; + } + } else if (v > 30) { + // To support eth_sign and similar we adjust v and hash the messageHash with the Ethereum message prefix before applying ecrecover + currentOwner = ecrecover(keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", dataHash)), v - 4, r, s); + } else { + // Use ecrecover with the messageHash for EOA signatures + currentOwner = ecrecover(dataHash, v, r, s); + } + require ( + currentOwner > lastOwner && owners[currentOwner] != address(0) && currentOwner != SENTINEL_OWNERS, + "Invalid owner provided" + ); + lastOwner = currentOwner; + } + } + + /// @dev Allows to estimate a Safe transaction. + /// This method is only meant for estimation purpose, therefore two different protection mechanism against execution in a transaction have been made: + /// 1.) The method can only be called from the safe itself + /// 2.) The response is returned with a revert + /// When estimating set `from` to the address of the safe. + /// Since the `estimateGas` function includes refunds, call this method to get an estimated of the costs that are deducted from the safe with `execTransaction` + /// @param to Destination address of Safe transaction. + /// @param value Ether value of Safe transaction. + /// @param data Data payload of Safe transaction. + /// @param operation Operation type of Safe transaction. + /// @return Estimate without refunds and overhead fees (base transaction and payload data gas costs). + function requiredTxGas(address to, uint256 value, bytes calldata data, Enum.Operation operation) + external + authorized + returns (uint256) + { + uint256 startGas = gasleft(); + // We don't provide an error message here, as we use it to return the estimate + // solium-disable-next-line error-reason + require(execute(to, value, data, operation, gasleft())); + uint256 requiredGas = startGas - gasleft(); + // Convert response to string and return via error message + revert(string(abi.encodePacked(requiredGas))); + } + + /** + * @dev Marks a hash as approved. This can be used to validate a hash that is used by a signature. + * @param hashToApprove The hash that should be marked as approved for signatures that are verified by this contract. + */ + function approveHash(bytes32 hashToApprove) + external + { + require(owners[msg.sender] != address(0), "Only owners can approve a hash"); + approvedHashes[msg.sender][hashToApprove] = 1; + emit ApproveHash(hashToApprove, msg.sender); + } + + /** + * @dev Marks a message as signed, so that it can be used with EIP-1271 + * @notice Marks a message (`_data`) as signed. + * @param _data Arbitrary length data that should be marked as signed on the behalf of address(this) + */ + function signMessage(bytes calldata _data) + external + authorized + { + bytes32 msgHash = getMessageHash(_data); + signedMessages[msgHash] = 1; + emit SignMsg(msgHash); + } + + /** + * Implementation of ISignatureValidator (see `interfaces/ISignatureValidator.sol`) + * @dev Should return whether the signature provided is valid for the provided data. + * The save does not implement the interface since `checkSignatures` is not a view method. + * The method will not perform any state changes (see parameters of `checkSignatures`) + * @param _data Arbitrary length data signed on the behalf of address(this) + * @param _signature Signature byte array associated with _data + * @return a bool upon valid or invalid signature with corresponding _data + */ + function isValidSignature(bytes calldata _data, bytes calldata _signature) + external + returns (bytes4) + { + bytes32 messageHash = getMessageHash(_data); + if (_signature.length == 0) { + require(signedMessages[messageHash] != 0, "Hash not approved"); + } else { + // consumeHash needs to be false, as the state should not be changed + checkSignatures(messageHash, _data, _signature, false); + } + return EIP1271_MAGIC_VALUE; + } + + /// @dev Returns hash of a message that can be signed by owners. + /// @param message Message that should be hashed + /// @return Message hash. + function getMessageHash( + bytes memory message + ) + public + view + returns (bytes32) + { + bytes32 safeMessageHash = keccak256( + abi.encode(SAFE_MSG_TYPEHASH, keccak256(message)) + ); + return keccak256( + abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, safeMessageHash) + ); + } + + /// @dev Returns the bytes that are hashed to be signed by owners. + /// @param to Destination address. + /// @param value Ether value. + /// @param data Data payload. + /// @param operation Operation type. + /// @param safeTxGas Fas that should be used for the safe transaction. + /// @param baseGas Gas costs for data used to trigger the safe transaction. + /// @param gasPrice Maximum gas price that should be used for this transaction. + /// @param gasToken Token address (or 0 if ETH) that is used for the payment. + /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). + /// @param _nonce Transaction nonce. + /// @return Transaction hash bytes. + function encodeTransactionData( + address to, + uint256 value, + bytes memory data, + Enum.Operation operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + uint256 _nonce + ) + public + view + returns (bytes memory) + { + bytes32 safeTxHash = keccak256( + abi.encode(SAFE_TX_TYPEHASH, to, value, keccak256(data), operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce) + ); + return abi.encodePacked(bytes1(0x19), bytes1(0x01), domainSeparator, safeTxHash); + } + + /// @dev Returns hash to be signed by owners. + /// @param to Destination address. + /// @param value Ether value. + /// @param data Data payload. + /// @param operation Operation type. + /// @param safeTxGas Fas that should be used for the safe transaction. + /// @param baseGas Gas costs for data used to trigger the safe transaction. + /// @param gasPrice Maximum gas price that should be used for this transaction. + /// @param gasToken Token address (or 0 if ETH) that is used for the payment. + /// @param refundReceiver Address of receiver of gas payment (or 0 if tx.origin). + /// @param _nonce Transaction nonce. + /// @return Transaction hash. + function getTransactionHash( + address to, + uint256 value, + bytes memory data, + Enum.Operation operation, + uint256 safeTxGas, + uint256 baseGas, + uint256 gasPrice, + address gasToken, + address refundReceiver, + uint256 _nonce + ) + public + view + returns (bytes32) + { + return keccak256(encodeTransactionData(to, value, data, operation, safeTxGas, baseGas, gasPrice, gasToken, refundReceiver, _nonce)); + } +} +==== Source: ./contracts/handler/DefaultCallbackHandler.sol ==== +pragma experimental SMTChecker; +pragma solidity >=0.5.0; + +import "../interfaces/ERC1155TokenReceiver.sol"; +import "../interfaces/ERC721TokenReceiver.sol"; +import "../interfaces/ERC777TokensRecipient.sol"; + +/// @title Default Callback Handler - returns true for known token callbacks +/// @author Richard Meissner - +contract DefaultCallbackHandler is ERC1155TokenReceiver, ERC777TokensRecipient, ERC721TokenReceiver { + + string public constant NAME = "Default Callback Handler"; + string public constant VERSION = "1.0.0"; + + function onERC1155Received(address, address, uint256, uint256, bytes calldata) override + external + returns(bytes4) + { + return 0xf23a6e61; + } + + function onERC1155BatchReceived(address, address, uint256[] calldata, uint256[] calldata, bytes calldata) override + external + returns(bytes4) + { + return 0xbc197c81; + } + + function onERC721Received(address, address, uint256, bytes calldata) override + external + returns(bytes4) + { + return 0x150b7a02; + } + + // solium-disable-next-line no-empty-blocks + function tokensReceived(address, address, address, uint256, bytes calldata, bytes calldata) external override { + // We implement this for completeness, doesn't really have any value + } + +} +==== Source: mock-contract/contracts/MockContract.sol ==== +pragma experimental SMTChecker; +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.7.0 <0.9.0; + +interface MockInterface { + /** + * @dev After calling this method, the mock will return `response` when it is called + * with any calldata that is not mocked more specifically below + * (e.g. using givenMethodReturn). + * @param response ABI encoded response that will be returned if method is invoked + */ + function givenAnyReturn(bytes calldata response) external; + function givenAnyReturnBool(bool response) external; + function givenAnyReturnUint(uint response) external; + function givenAnyReturnAddress(address response) external; + + function givenAnyRevert() external; + function givenAnyRevertWithMessage(string calldata message) external; + function givenAnyRunOutOfGas() external; + + /** + * @dev After calling this method, the mock will return `response` when the given + * methodId is called regardless of arguments. If the methodId and arguments + * are mocked more specifically (using `givenMethodAndArguments`) the latter + * will take precedence. + * @param method ABI encoded methodId. It is valid to pass full calldata (including arguments). The mock will extract the methodId from it + * @param response ABI encoded response that will be returned if method is invoked + */ + function givenMethodReturn(bytes calldata method, bytes calldata response) external; + function givenMethodReturnBool(bytes calldata method, bool response) external; + function givenMethodReturnUint(bytes calldata method, uint response) external; + function givenMethodReturnAddress(bytes calldata method, address response) external; + + function givenMethodRevert(bytes calldata method) external; + function givenMethodRevertWithMessage(bytes calldata method, string calldata message) external; + function givenMethodRunOutOfGas(bytes calldata method) external; + + /** + * @dev After calling this method, the mock will return `response` when the given + * methodId is called with matching arguments. These exact calldataMocks will take + * precedence over all other calldataMocks. + * @param call ABI encoded calldata (methodId and arguments) + * @param response ABI encoded response that will be returned if contract is invoked with calldata + */ + function givenCalldataReturn(bytes calldata call, bytes calldata response) external; + function givenCalldataReturnBool(bytes calldata call, bool response) external; + function givenCalldataReturnUint(bytes calldata call, uint response) external; + function givenCalldataReturnAddress(bytes calldata call, address response) external; + + function givenCalldataRevert(bytes calldata call) external; + function givenCalldataRevertWithMessage(bytes calldata call, string calldata message) external; + function givenCalldataRunOutOfGas(bytes calldata call) external; + + /** + * @dev Returns the number of times anything has been called on this mock since last reset + */ + function invocationCount() external returns (uint); + + /** + * @dev Returns the number of times the given method has been called on this mock since last reset + * @param method ABI encoded methodId. It is valid to pass full calldata (including arguments). The mock will extract the methodId from it + */ + function invocationCountForMethod(bytes calldata method) external returns (uint); + + /** + * @dev Returns the number of times this mock has been called with the exact calldata since last reset. + * @param call ABI encoded calldata (methodId and arguments) + */ + function invocationCountForCalldata(bytes calldata call) external returns (uint); + + /** + * @dev Resets all mocked methods and invocation counts. + */ + function reset() external; +} + +/** + * Implementation of the MockInterface. + */ +contract MockContract is MockInterface { + enum MockType { Return, Revert, OutOfGas } + + bytes32 public constant MOCKS_LIST_START = hex"01"; + bytes public constant MOCKS_LIST_END = "0xff"; + bytes32 public constant MOCKS_LIST_END_HASH = keccak256(MOCKS_LIST_END); + bytes4 public constant SENTINEL_ANY_MOCKS = hex"01"; + bytes public constant DEFAULT_FALLBACK_VALUE = abi.encode(false); + + // A linked list allows easy iteration and inclusion checks + mapping(bytes32 => bytes) calldataMocks; + mapping(bytes => MockType) calldataMockTypes; + mapping(bytes => bytes) calldataExpectations; + mapping(bytes => string) calldataRevertMessage; + mapping(bytes32 => uint) calldataInvocations; + + mapping(bytes4 => bytes4) methodIdMocks; + mapping(bytes4 => MockType) methodIdMockTypes; + mapping(bytes4 => bytes) methodIdExpectations; + mapping(bytes4 => string) methodIdRevertMessages; + mapping(bytes32 => uint) methodIdInvocations; + + MockType fallbackMockType; + bytes fallbackExpectation = DEFAULT_FALLBACK_VALUE; + string fallbackRevertMessage; + uint invocations; + uint resetCount; + + constructor() { + calldataMocks[MOCKS_LIST_START] = MOCKS_LIST_END; + methodIdMocks[SENTINEL_ANY_MOCKS] = SENTINEL_ANY_MOCKS; + } + + function trackCalldataMock(bytes memory call) private { + bytes32 callHash = keccak256(call); + if (calldataMocks[callHash].length == 0) { + calldataMocks[callHash] = calldataMocks[MOCKS_LIST_START]; + calldataMocks[MOCKS_LIST_START] = call; + } + } + + function trackMethodIdMock(bytes4 methodId) private { + if (methodIdMocks[methodId] == 0x0) { + methodIdMocks[methodId] = methodIdMocks[SENTINEL_ANY_MOCKS]; + methodIdMocks[SENTINEL_ANY_MOCKS] = methodId; + } + } + + function _givenAnyReturn(bytes memory response) internal { + fallbackMockType = MockType.Return; + fallbackExpectation = response; + } + + function givenAnyReturn(bytes calldata response) override external { + _givenAnyReturn(response); + } + + function givenAnyReturnBool(bool response) override external { + uint flag = response ? 1 : 0; + _givenAnyReturn(uintToBytes(flag)); + } + + function givenAnyReturnUint(uint response) override external { + _givenAnyReturn(uintToBytes(response)); + } + + function givenAnyReturnAddress(address response) override external { + _givenAnyReturn(uintToBytes(uint(uint160(response)))); + } + + function givenAnyRevert() override external { + fallbackMockType = MockType.Revert; + fallbackRevertMessage = ""; + } + + function givenAnyRevertWithMessage(string calldata message) override external { + fallbackMockType = MockType.Revert; + fallbackRevertMessage = message; + } + + function givenAnyRunOutOfGas() override external { + fallbackMockType = MockType.OutOfGas; + } + + function _givenCalldataReturn(bytes memory call, bytes memory response) private { + calldataMockTypes[call] = MockType.Return; + calldataExpectations[call] = response; + trackCalldataMock(call); + } + + function givenCalldataReturn(bytes calldata call, bytes calldata response) override external { + _givenCalldataReturn(call, response); + } + + function givenCalldataReturnBool(bytes calldata call, bool response) override external { + uint flag = response ? 1 : 0; + _givenCalldataReturn(call, uintToBytes(flag)); + } + + function givenCalldataReturnUint(bytes calldata call, uint response) override external { + _givenCalldataReturn(call, uintToBytes(response)); + } + + function givenCalldataReturnAddress(bytes calldata call, address response) override external { + _givenCalldataReturn(call, uintToBytes(uint(uint160(response)))); + } + + function _givenMethodReturn(bytes memory call, bytes memory response) private { + bytes4 method = bytesToBytes4(call); + methodIdMockTypes[method] = MockType.Return; + methodIdExpectations[method] = response; + trackMethodIdMock(method); + } + + function givenMethodReturn(bytes calldata call, bytes calldata response) override external { + _givenMethodReturn(call, response); + } + + function givenMethodReturnBool(bytes calldata call, bool response) override external { + uint flag = response ? 1 : 0; + _givenMethodReturn(call, uintToBytes(flag)); + } + + function givenMethodReturnUint(bytes calldata call, uint response) override external { + _givenMethodReturn(call, uintToBytes(response)); + } + + function givenMethodReturnAddress(bytes calldata call, address response) override external { + _givenMethodReturn(call, uintToBytes(uint(uint160(response)))); + } + + function givenCalldataRevert(bytes calldata call) override external { + calldataMockTypes[call] = MockType.Revert; + calldataRevertMessage[call] = ""; + trackCalldataMock(call); + } + + function givenMethodRevert(bytes calldata call) override external { + bytes4 method = bytesToBytes4(call); + methodIdMockTypes[method] = MockType.Revert; + trackMethodIdMock(method); + } + + function givenCalldataRevertWithMessage(bytes calldata call, string calldata message) override external { + calldataMockTypes[call] = MockType.Revert; + calldataRevertMessage[call] = message; + trackCalldataMock(call); + } + + function givenMethodRevertWithMessage(bytes calldata call, string calldata message) override external { + bytes4 method = bytesToBytes4(call); + methodIdMockTypes[method] = MockType.Revert; + methodIdRevertMessages[method] = message; + trackMethodIdMock(method); + } + + function givenCalldataRunOutOfGas(bytes calldata call) override external { + calldataMockTypes[call] = MockType.OutOfGas; + trackCalldataMock(call); + } + + function givenMethodRunOutOfGas(bytes calldata call) override external { + bytes4 method = bytesToBytes4(call); + methodIdMockTypes[method] = MockType.OutOfGas; + trackMethodIdMock(method); + } + + function invocationCount() override external view returns (uint) { + return invocations; + } + + function invocationCountForMethod(bytes calldata call) override external view returns (uint) { + bytes4 method = bytesToBytes4(call); + return methodIdInvocations[keccak256(abi.encodePacked(resetCount, method))]; + } + + function invocationCountForCalldata(bytes calldata call) override external view returns (uint) { + return calldataInvocations[keccak256(abi.encodePacked(resetCount, call))]; + } + + function reset() override external { + // Reset all exact calldataMocks + bytes memory nextMock = calldataMocks[MOCKS_LIST_START]; + bytes32 mockHash = keccak256(nextMock); + // We cannot compary bytes + while(mockHash != MOCKS_LIST_END_HASH) { + // Reset all mock maps + calldataMockTypes[nextMock] = MockType.Return; + calldataExpectations[nextMock] = hex""; + calldataRevertMessage[nextMock] = ""; + // Set next mock to remove + nextMock = calldataMocks[mockHash]; + // Remove from linked list + calldataMocks[mockHash] = ""; + // Update mock hash + mockHash = keccak256(nextMock); + } + // Clear list + calldataMocks[MOCKS_LIST_START] = MOCKS_LIST_END; + + // Reset all any calldataMocks + bytes4 nextAnyMock = methodIdMocks[SENTINEL_ANY_MOCKS]; + while(nextAnyMock != SENTINEL_ANY_MOCKS) { + bytes4 currentAnyMock = nextAnyMock; + methodIdMockTypes[currentAnyMock] = MockType.Return; + methodIdExpectations[currentAnyMock] = hex""; + methodIdRevertMessages[currentAnyMock] = ""; + nextAnyMock = methodIdMocks[currentAnyMock]; + // Remove from linked list + methodIdMocks[currentAnyMock] = 0x0; + } + // Clear list + methodIdMocks[SENTINEL_ANY_MOCKS] = SENTINEL_ANY_MOCKS; + + fallbackExpectation = DEFAULT_FALLBACK_VALUE; + fallbackMockType = MockType.Return; + invocations = 0; + resetCount += 1; + } + + function useAllGas() private { + while(true) { + bool s; + assembly { + //expensive call to EC multiply contract + s := call(sub(gas(), 2000), 6, 0, 0x0, 0xc0, 0x0, 0x60) + } + } + } + + function bytesToBytes4(bytes memory b) private pure returns (bytes4) { + bytes4 out; + for (uint i = 0; i < 4; i++) { + out |= bytes4(b[i] & 0xFF) >> (i * 8); + } + return out; + } + + function uintToBytes(uint256 x) private pure returns (bytes memory b) { + b = new bytes(32); + assembly { mstore(add(b, 32), x) } + } + + function updateInvocationCount(bytes4 methodId, bytes memory originalMsgData) public { + require(msg.sender == address(this), "Can only be called from the contract itself"); + invocations += 1; + methodIdInvocations[keccak256(abi.encodePacked(resetCount, methodId))] += 1; + calldataInvocations[keccak256(abi.encodePacked(resetCount, originalMsgData))] += 1; + } + + receive() payable external { + fallbackImpl(); + } + fallback() payable external { + fallbackImpl(); + } + + function fallbackImpl() internal { + bytes4 methodId = msg.sig; + + // First, check exact matching overrides + if (calldataMockTypes[msg.data] == MockType.Revert) { + revert(calldataRevertMessage[msg.data]); + } + if (calldataMockTypes[msg.data] == MockType.OutOfGas) { + useAllGas(); + } + bytes memory result = calldataExpectations[msg.data]; + + // Then check method Id overrides + if (result.length == 0) { + if (methodIdMockTypes[methodId] == MockType.Revert) { + revert(methodIdRevertMessages[methodId]); + } + if (methodIdMockTypes[methodId] == MockType.OutOfGas) { + useAllGas(); + } + result = methodIdExpectations[methodId]; + } + + // Last, use the fallback override + if (result.length == 0) { + if (fallbackMockType == MockType.Revert) { + revert(fallbackRevertMessage); + } + if (fallbackMockType == MockType.OutOfGas) { + useAllGas(); + } + result = fallbackExpectation; + } + + // Record invocation as separate call so we don't rollback in case we are called with STATICCALL + (, bytes memory r) = address(this).call{gas: 100000}(abi.encodeWithSignature("updateInvocationCount(bytes4,bytes)", methodId, msg.data)); + assert(r.length == 0); + + assembly { + return(add(0x20, result), mload(result)) + } + } +} + +==== Source: mock-contract/contracts/ExampleContractUnderTest.sol ==== +pragma experimental SMTChecker; +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >0.7.0 <0.9.0; + +import './ComplexInterface.sol'; + +contract ExampleContractUnderTest { + ComplexInterface complexInterface; + + constructor(address _complexInterface) { + complexInterface = ComplexInterface(_complexInterface); + } + + function callMockedFunction3Times() public view returns (bool) { + complexInterface.acceptUintReturnUintView(1); + complexInterface.acceptUintReturnUintView(1); + complexInterface.acceptUintReturnUintView(1); + return true; + } + + function callMethodThatReturnsAddress() public returns (address) { + address foo = complexInterface.acceptUintReturnAddress(1); + return foo; + } + + function callMethodThatReturnsBool() public returns (bool) { + return complexInterface.acceptUintReturnBool(1); + } +} + +==== Source: mock-contract/contracts/ComplexInterface.sol ==== +pragma experimental SMTChecker; +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @dev Used for unit testing MockContract functionality. + */ +interface ComplexInterface { + function methodA() external; + function methodB() external; + function acceptAdressUintReturnBool(address recipient, uint amount) external returns (bool); + function acceptUintReturnString(uint) external returns (string memory); + function acceptUintReturnBool(uint) external returns (bool); + function acceptUintReturnUint(uint) external returns (uint); + function acceptUintReturnAddress(uint) external returns (address); + function acceptUintReturnUintView(uint) external view returns (uint); +} + +==== Source: mock-contract/flattened.sol ==== +pragma experimental SMTChecker; +==== Source: ./contracts/MockContract.sol ==== +pragma experimental SMTChecker; +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.7.0 <0.9.0; + +interface MockInterface { + /** + * @dev After calling this method, the mock will return `response` when it is called + * with any calldata that is not mocked more specifically below + * (e.g. using givenMethodReturn). + * @param response ABI encoded response that will be returned if method is invoked + */ + function givenAnyReturn(bytes calldata response) external; + function givenAnyReturnBool(bool response) external; + function givenAnyReturnUint(uint response) external; + function givenAnyReturnAddress(address response) external; + + function givenAnyRevert() external; + function givenAnyRevertWithMessage(string calldata message) external; + function givenAnyRunOutOfGas() external; + + /** + * @dev After calling this method, the mock will return `response` when the given + * methodId is called regardless of arguments. If the methodId and arguments + * are mocked more specifically (using `givenMethodAndArguments`) the latter + * will take precedence. + * @param method ABI encoded methodId. It is valid to pass full calldata (including arguments). The mock will extract the methodId from it + * @param response ABI encoded response that will be returned if method is invoked + */ + function givenMethodReturn(bytes calldata method, bytes calldata response) external; + function givenMethodReturnBool(bytes calldata method, bool response) external; + function givenMethodReturnUint(bytes calldata method, uint response) external; + function givenMethodReturnAddress(bytes calldata method, address response) external; + + function givenMethodRevert(bytes calldata method) external; + function givenMethodRevertWithMessage(bytes calldata method, string calldata message) external; + function givenMethodRunOutOfGas(bytes calldata method) external; + + /** + * @dev After calling this method, the mock will return `response` when the given + * methodId is called with matching arguments. These exact calldataMocks will take + * precedence over all other calldataMocks. + * @param call ABI encoded calldata (methodId and arguments) + * @param response ABI encoded response that will be returned if contract is invoked with calldata + */ + function givenCalldataReturn(bytes calldata call, bytes calldata response) external; + function givenCalldataReturnBool(bytes calldata call, bool response) external; + function givenCalldataReturnUint(bytes calldata call, uint response) external; + function givenCalldataReturnAddress(bytes calldata call, address response) external; + + function givenCalldataRevert(bytes calldata call) external; + function givenCalldataRevertWithMessage(bytes calldata call, string calldata message) external; + function givenCalldataRunOutOfGas(bytes calldata call) external; + + /** + * @dev Returns the number of times anything has been called on this mock since last reset + */ + function invocationCount() external returns (uint); + + /** + * @dev Returns the number of times the given method has been called on this mock since last reset + * @param method ABI encoded methodId. It is valid to pass full calldata (including arguments). The mock will extract the methodId from it + */ + function invocationCountForMethod(bytes calldata method) external returns (uint); + + /** + * @dev Returns the number of times this mock has been called with the exact calldata since last reset. + * @param call ABI encoded calldata (methodId and arguments) + */ + function invocationCountForCalldata(bytes calldata call) external returns (uint); + + /** + * @dev Resets all mocked methods and invocation counts. + */ + function reset() external; +} + +/** + * Implementation of the MockInterface. + */ +contract MockContract is MockInterface { + enum MockType { Return, Revert, OutOfGas } + + bytes32 public constant MOCKS_LIST_START = hex"01"; + bytes public constant MOCKS_LIST_END = "0xff"; + bytes32 public constant MOCKS_LIST_END_HASH = keccak256(MOCKS_LIST_END); + bytes4 public constant SENTINEL_ANY_MOCKS = hex"01"; + bytes public constant DEFAULT_FALLBACK_VALUE = abi.encode(false); + + // A linked list allows easy iteration and inclusion checks + mapping(bytes32 => bytes) calldataMocks; + mapping(bytes => MockType) calldataMockTypes; + mapping(bytes => bytes) calldataExpectations; + mapping(bytes => string) calldataRevertMessage; + mapping(bytes32 => uint) calldataInvocations; + + mapping(bytes4 => bytes4) methodIdMocks; + mapping(bytes4 => MockType) methodIdMockTypes; + mapping(bytes4 => bytes) methodIdExpectations; + mapping(bytes4 => string) methodIdRevertMessages; + mapping(bytes32 => uint) methodIdInvocations; + + MockType fallbackMockType; + bytes fallbackExpectation = DEFAULT_FALLBACK_VALUE; + string fallbackRevertMessage; + uint invocations; + uint resetCount; + + constructor() { + calldataMocks[MOCKS_LIST_START] = MOCKS_LIST_END; + methodIdMocks[SENTINEL_ANY_MOCKS] = SENTINEL_ANY_MOCKS; + } + + function trackCalldataMock(bytes memory call) private { + bytes32 callHash = keccak256(call); + if (calldataMocks[callHash].length == 0) { + calldataMocks[callHash] = calldataMocks[MOCKS_LIST_START]; + calldataMocks[MOCKS_LIST_START] = call; + } + } + + function trackMethodIdMock(bytes4 methodId) private { + if (methodIdMocks[methodId] == 0x0) { + methodIdMocks[methodId] = methodIdMocks[SENTINEL_ANY_MOCKS]; + methodIdMocks[SENTINEL_ANY_MOCKS] = methodId; + } + } + + function _givenAnyReturn(bytes memory response) internal { + fallbackMockType = MockType.Return; + fallbackExpectation = response; + } + + function givenAnyReturn(bytes calldata response) override external { + _givenAnyReturn(response); + } + + function givenAnyReturnBool(bool response) override external { + uint flag = response ? 1 : 0; + _givenAnyReturn(uintToBytes(flag)); + } + + function givenAnyReturnUint(uint response) override external { + _givenAnyReturn(uintToBytes(response)); + } + + function givenAnyReturnAddress(address response) override external { + _givenAnyReturn(uintToBytes(uint(uint160(response)))); + } + + function givenAnyRevert() override external { + fallbackMockType = MockType.Revert; + fallbackRevertMessage = ""; + } + + function givenAnyRevertWithMessage(string calldata message) override external { + fallbackMockType = MockType.Revert; + fallbackRevertMessage = message; + } + + function givenAnyRunOutOfGas() override external { + fallbackMockType = MockType.OutOfGas; + } + + function _givenCalldataReturn(bytes memory call, bytes memory response) private { + calldataMockTypes[call] = MockType.Return; + calldataExpectations[call] = response; + trackCalldataMock(call); + } + + function givenCalldataReturn(bytes calldata call, bytes calldata response) override external { + _givenCalldataReturn(call, response); + } + + function givenCalldataReturnBool(bytes calldata call, bool response) override external { + uint flag = response ? 1 : 0; + _givenCalldataReturn(call, uintToBytes(flag)); + } + + function givenCalldataReturnUint(bytes calldata call, uint response) override external { + _givenCalldataReturn(call, uintToBytes(response)); + } + + function givenCalldataReturnAddress(bytes calldata call, address response) override external { + _givenCalldataReturn(call, uintToBytes(uint(uint160(response)))); + } + + function _givenMethodReturn(bytes memory call, bytes memory response) private { + bytes4 method = bytesToBytes4(call); + methodIdMockTypes[method] = MockType.Return; + methodIdExpectations[method] = response; + trackMethodIdMock(method); + } + + function givenMethodReturn(bytes calldata call, bytes calldata response) override external { + _givenMethodReturn(call, response); + } + + function givenMethodReturnBool(bytes calldata call, bool response) override external { + uint flag = response ? 1 : 0; + _givenMethodReturn(call, uintToBytes(flag)); + } + + function givenMethodReturnUint(bytes calldata call, uint response) override external { + _givenMethodReturn(call, uintToBytes(response)); + } + + function givenMethodReturnAddress(bytes calldata call, address response) override external { + _givenMethodReturn(call, uintToBytes(uint(uint160(response)))); + } + + function givenCalldataRevert(bytes calldata call) override external { + calldataMockTypes[call] = MockType.Revert; + calldataRevertMessage[call] = ""; + trackCalldataMock(call); + } + + function givenMethodRevert(bytes calldata call) override external { + bytes4 method = bytesToBytes4(call); + methodIdMockTypes[method] = MockType.Revert; + trackMethodIdMock(method); + } + + function givenCalldataRevertWithMessage(bytes calldata call, string calldata message) override external { + calldataMockTypes[call] = MockType.Revert; + calldataRevertMessage[call] = message; + trackCalldataMock(call); + } + + function givenMethodRevertWithMessage(bytes calldata call, string calldata message) override external { + bytes4 method = bytesToBytes4(call); + methodIdMockTypes[method] = MockType.Revert; + methodIdRevertMessages[method] = message; + trackMethodIdMock(method); + } + + function givenCalldataRunOutOfGas(bytes calldata call) override external { + calldataMockTypes[call] = MockType.OutOfGas; + trackCalldataMock(call); + } + + function givenMethodRunOutOfGas(bytes calldata call) override external { + bytes4 method = bytesToBytes4(call); + methodIdMockTypes[method] = MockType.OutOfGas; + trackMethodIdMock(method); + } + + function invocationCount() override external view returns (uint) { + return invocations; + } + + function invocationCountForMethod(bytes calldata call) override external view returns (uint) { + bytes4 method = bytesToBytes4(call); + return methodIdInvocations[keccak256(abi.encodePacked(resetCount, method))]; + } + + function invocationCountForCalldata(bytes calldata call) override external view returns (uint) { + return calldataInvocations[keccak256(abi.encodePacked(resetCount, call))]; + } + + function reset() override external { + // Reset all exact calldataMocks + bytes memory nextMock = calldataMocks[MOCKS_LIST_START]; + bytes32 mockHash = keccak256(nextMock); + // We cannot compary bytes + while(mockHash != MOCKS_LIST_END_HASH) { + // Reset all mock maps + calldataMockTypes[nextMock] = MockType.Return; + calldataExpectations[nextMock] = hex""; + calldataRevertMessage[nextMock] = ""; + // Set next mock to remove + nextMock = calldataMocks[mockHash]; + // Remove from linked list + calldataMocks[mockHash] = ""; + // Update mock hash + mockHash = keccak256(nextMock); + } + // Clear list + calldataMocks[MOCKS_LIST_START] = MOCKS_LIST_END; + + // Reset all any calldataMocks + bytes4 nextAnyMock = methodIdMocks[SENTINEL_ANY_MOCKS]; + while(nextAnyMock != SENTINEL_ANY_MOCKS) { + bytes4 currentAnyMock = nextAnyMock; + methodIdMockTypes[currentAnyMock] = MockType.Return; + methodIdExpectations[currentAnyMock] = hex""; + methodIdRevertMessages[currentAnyMock] = ""; + nextAnyMock = methodIdMocks[currentAnyMock]; + // Remove from linked list + methodIdMocks[currentAnyMock] = 0x0; + } + // Clear list + methodIdMocks[SENTINEL_ANY_MOCKS] = SENTINEL_ANY_MOCKS; + + fallbackExpectation = DEFAULT_FALLBACK_VALUE; + fallbackMockType = MockType.Return; + invocations = 0; + resetCount += 1; + } + + function useAllGas() private { + while(true) { + bool s; + assembly { + //expensive call to EC multiply contract + s := call(sub(gas(), 2000), 6, 0, 0x0, 0xc0, 0x0, 0x60) + } + } + } + + function bytesToBytes4(bytes memory b) private pure returns (bytes4) { + bytes4 out; + for (uint i = 0; i < 4; i++) { + out |= bytes4(b[i] & 0xFF) >> (i * 8); + } + return out; + } + + function uintToBytes(uint256 x) private pure returns (bytes memory b) { + b = new bytes(32); + assembly { mstore(add(b, 32), x) } + } + + function updateInvocationCount(bytes4 methodId, bytes memory originalMsgData) public { + require(msg.sender == address(this), "Can only be called from the contract itself"); + invocations += 1; + methodIdInvocations[keccak256(abi.encodePacked(resetCount, methodId))] += 1; + calldataInvocations[keccak256(abi.encodePacked(resetCount, originalMsgData))] += 1; + } + + receive() payable external { + fallbackImpl(); + } + fallback() payable external { + fallbackImpl(); + } + + function fallbackImpl() internal { + bytes4 methodId = msg.sig; + + // First, check exact matching overrides + if (calldataMockTypes[msg.data] == MockType.Revert) { + revert(calldataRevertMessage[msg.data]); + } + if (calldataMockTypes[msg.data] == MockType.OutOfGas) { + useAllGas(); + } + bytes memory result = calldataExpectations[msg.data]; + + // Then check method Id overrides + if (result.length == 0) { + if (methodIdMockTypes[methodId] == MockType.Revert) { + revert(methodIdRevertMessages[methodId]); + } + if (methodIdMockTypes[methodId] == MockType.OutOfGas) { + useAllGas(); + } + result = methodIdExpectations[methodId]; + } + + // Last, use the fallback override + if (result.length == 0) { + if (fallbackMockType == MockType.Revert) { + revert(fallbackRevertMessage); + } + if (fallbackMockType == MockType.OutOfGas) { + useAllGas(); + } + result = fallbackExpectation; + } + + // Record invocation as separate call so we don't rollback in case we are called with STATICCALL + (, bytes memory r) = address(this).call{gas: 100000}(abi.encodeWithSignature("updateInvocationCount(bytes4,bytes)", methodId, msg.data)); + assert(r.length == 0); + + assembly { + return(add(0x20, result), mload(result)) + } + } +} + +==== Source: ./contracts/ExampleContractUnderTest.sol ==== +pragma experimental SMTChecker; +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >0.7.0 <0.9.0; + +import './ComplexInterface.sol'; + +contract ExampleContractUnderTest { + ComplexInterface complexInterface; + + constructor(address _complexInterface) { + complexInterface = ComplexInterface(_complexInterface); + } + + function callMockedFunction3Times() public view returns (bool) { + complexInterface.acceptUintReturnUintView(1); + complexInterface.acceptUintReturnUintView(1); + complexInterface.acceptUintReturnUintView(1); + return true; + } + + function callMethodThatReturnsAddress() public returns (address) { + address foo = complexInterface.acceptUintReturnAddress(1); + return foo; + } + + function callMethodThatReturnsBool() public returns (bool) { + return complexInterface.acceptUintReturnBool(1); + } +} + +==== Source: ./contracts/ComplexInterface.sol ==== +pragma experimental SMTChecker; +// SPDX-License-Identifier: UNLICENSED + +pragma solidity >=0.7.0 <0.9.0; + +/** + * @dev Used for unit testing MockContract functionality. + */ +interface ComplexInterface { + function methodA() external; + function methodB() external; + function acceptAdressUintReturnBool(address recipient, uint amount) external returns (bool); + function acceptUintReturnString(uint) external returns (string memory); + function acceptUintReturnBool(uint) external returns (bool); + function acceptUintReturnUint(uint) external returns (uint); + function acceptUintReturnAddress(uint) external returns (address); + function acceptUintReturnUintView(uint) external view returns (uint); +} + +