mirror of
https://github.com/ethereum/solidity
synced 2023-10-03 13:03:40 +00:00
2989 lines
116 KiB
Solidity
2989 lines
116 KiB
Solidity
==== 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 - <stefan@gnosis.pm>
|
|
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 - <stefan@gnosis.pm>
|
|
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 - <stefan@gnosis.pm>
|
|
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 - <stefan@gnosis.pm>
|
|
/// @author Richard Meissner - <richard@gnosis.pm>
|
|
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 - <stefan@gnosis.pm>
|
|
/// @author Richard Meissner - <richard@gnosis.pm>
|
|
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 - <stefan@gnosis.pm>
|
|
/// @author Richard Meissner - <richard@gnosis.pm>
|
|
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 - <stefan@gnosis.pm>
|
|
/// @author Richard Meissner - <richard@gnosis.pm>
|
|
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 - <richard@gnosis.pm>
|
|
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 - <richard@gnosis.pm>
|
|
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 - <richard@gnosis.io>
|
|
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 - <stefan@gnosis.pm>
|
|
/// @author Richard Meissner - <richard@gnosis.pm>
|
|
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. <byte_array_len_1><byte_array_data_1><byte_array_len_2><byte_array_data_2>)
|
|
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 - <nick.dodson@consensys.net>
|
|
/// @author Gonçalo Sá - <goncalo.sa@consensys.net>
|
|
/// @author Stefan George - <stefan@gnosis.io>
|
|
/// @author Richard Meissner - <richard@gnosis.io>
|
|
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 - <richard@gnosis.pm>
|
|
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 - <richard@gnosis.pm>
|
|
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 - <richard@gnosis.io>
|
|
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 - <richard@gnosis.pm>
|
|
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 - <richard@gnosis.pm>
|
|
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 - <richard@gnosis.pm>
|
|
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 - <richard@gnosis.io>
|
|
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 - <stefan@gnosis.io>
|
|
/// @author Richard Meissner - <richard@gnosis.io>
|
|
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 - <stefan@gnosis.pm>
|
|
/// @author Richard Meissner - <richard@gnosis.pm>
|
|
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 - <stefan@gnosis.pm>
|
|
/// @author Richard Meissner - <richard@gnosis.pm>
|
|
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 - <stefan@gnosis.pm>
|
|
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 - <stefan@gnosis.io>
|
|
/// @author Richard Meissner - <richard@gnosis.io>
|
|
/// @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 - <richard@gnosis.pm>
|
|
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);
|
|
}
|
|
|
|
|