Commit 55fe9711 authored by Maurelian's avatar Maurelian

safe-tools: vendor in safe-tools lib

parent d9163367
// SPDX-License-Identifier: LGPL-3.0-only
pragma solidity >=0.7.0 <0.9.0;
import "safe-contracts/interfaces/ERC1155TokenReceiver.sol";
import "safe-contracts/interfaces/ERC721TokenReceiver.sol";
import "safe-contracts/interfaces/ERC777TokensRecipient.sol";
import "safe-contracts/interfaces/IERC165.sol";
import "safe-contracts/interfaces/ISignatureValidator.sol";
import "safe-contracts/GnosisSafe.sol";
/// @dev NOTE: removed VERSION and NAME due to inheritance conflicts
contract DefaultCallbackHandler is ERC1155TokenReceiver, ERC777TokensRecipient, ERC721TokenReceiver, IERC165 {
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes calldata
)
external
pure
override
returns (bytes4)
{
return 0xf23a6e61;
}
function onERC1155BatchReceived(
address,
address,
uint256[] calldata,
uint256[] calldata,
bytes calldata
)
external
pure
override
returns (bytes4)
{
return 0xbc197c81;
}
function onERC721Received(address, address, uint256, bytes calldata) external pure override returns (bytes4) {
return 0x150b7a02;
}
function tokensReceived(
address,
address,
address,
uint256,
bytes calldata,
bytes calldata
)
external
pure
override
{
// We implement this for completeness, doesn't really have any value
}
function supportsInterface(bytes4 interfaceId) external view virtual override returns (bool) {
return interfaceId == type(ERC1155TokenReceiver).interfaceId
|| interfaceId == type(ERC721TokenReceiver).interfaceId || interfaceId == type(IERC165).interfaceId;
}
}
address constant SENTINEL_MODULES = address(0x1);
/// @title Compatibility Fallback Handler - fallback handler to provider compatibility between pre 1.3.0 and 1.3.0+ Safe
/// contracts
/// @author Richard Meissner - <richard@gnosis.pm>
contract CompatibilityFallbackHandler is DefaultCallbackHandler, ISignatureValidator {
//keccak256(
// "SafeMessage(bytes message)"
//);
bytes32 private constant SAFE_MSG_TYPEHASH = 0x60b3cbf8b4a223d68d641b3b6ddf9a298e7f33710cf3d3a9d1146b5a6150fbca;
bytes4 internal constant SIMULATE_SELECTOR = bytes4(keccak256("simulate(address,bytes)"));
bytes4 internal constant UPDATED_MAGIC_VALUE = 0x1626ba7e;
/**
* Implementation of ISignatureValidator (see `interfaces/ISignatureValidator.sol`)
* @dev Should return whether the signature provided is valid for the provided data.
* @param _data Arbitrary length data signed on the behalf of address(msg.sender)
* @param _signature Signature byte array associated with _data
* @return a bool upon valid or invalid signature with corresponding _data
*/
function isValidSignature(bytes memory _data, bytes memory _signature) public view override returns (bytes4) {
// Caller should be a Safe
GnosisSafe safe = GnosisSafe(payable(msg.sender));
bytes32 messageHash = getMessageHashForSafe(safe, _data);
if (_signature.length == 0) {
require(safe.signedMessages(messageHash) != 0, "Hash not approved");
} else {
safe.checkSignatures(messageHash, _data, _signature);
}
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) {
return getMessageHashForSafe(GnosisSafe(payable(msg.sender)), message);
}
/// @dev Returns hash of a message that can be signed by owners.
/// @param safe Safe to which the message is targeted
/// @param message Message that should be hashed
/// @return Message hash.
function getMessageHashForSafe(GnosisSafe safe, 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), safe.domainSeparator(), safeMessageHash));
}
/**
* Implementation of updated EIP-1271
* @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 _dataHash Hash of the data signed on the behalf of address(msg.sender)
* @param _signature Signature byte array associated with _dataHash
* @return a bool upon valid or invalid signature with corresponding _dataHash
* @notice See
* https://github.com/gnosis/util-contracts/blob/bb5fe5fb5df6d8400998094fb1b32a178a47c3a1/contracts/StorageAccessible.sol
*/
function isValidSignature(bytes32 _dataHash, bytes calldata _signature) external view returns (bytes4) {
ISignatureValidator validator = ISignatureValidator(msg.sender);
bytes4 value = validator.isValidSignature(abi.encode(_dataHash), _signature);
return (value == EIP1271_MAGIC_VALUE) ? UPDATED_MAGIC_VALUE : bytes4(0);
}
/// @dev Returns array of first 10 modules.
/// @return Array of modules.
function getModules() external view returns (address[] memory) {
// Caller should be a Safe
GnosisSafe safe = GnosisSafe(payable(msg.sender));
(address[] memory array,) = safe.getModulesPaginated(SENTINEL_MODULES, 10);
return array;
}
/**
* @dev Performs a delegetecall on a targetContract in the context of self.
* Internally reverts execution to avoid side effects (making it static). Catches revert and returns encoded result
* as bytes.
* @param targetContract Address of the contract containing the code to execute.
* @param calldataPayload Calldata that should be sent to the target contract (encoded method name and arguments).
*/
function simulate(
address targetContract,
bytes calldata calldataPayload
)
external
returns (bytes memory response)
{
// Suppress compiler warnings about not using parameters, while allowing
// parameters to keep names for documentation purposes. This does not
// generate code.
targetContract;
calldataPayload;
// solhint-disable-next-line no-inline-assembly
assembly {
let internalCalldata := mload(0x40)
// Store `simulateAndRevert.selector`.
// String representation is used to force right padding
mstore(internalCalldata, "\xb4\xfa\xba\x09")
// Abuse the fact that both this and the internal methods have the
// same signature, and differ only in symbol name (and therefore,
// selector) and copy calldata directly. This saves us approximately
// 250 bytes of code and 300 gas at runtime over the
// `abi.encodeWithSelector` builtin.
calldatacopy(add(internalCalldata, 0x04), 0x04, sub(calldatasize(), 0x04))
// `pop` is required here by the compiler, as top level expressions
// can't have return values in inline assembly. `call` typically
// returns a 0 or 1 value indicated whether or not it reverted, but
// since we know it will always revert, we can safely ignore it.
pop(
call(
gas(),
// address() has been changed to caller() to use the implementation of the Safe
caller(),
0,
internalCalldata,
calldatasize(),
// The `simulateAndRevert` call always reverts, and
// instead encodes whether or not it was successful in the return
// data. The first 32-byte word of the return data contains the
// `success` value, so write it to memory address 0x00 (which is
// reserved Solidity scratch space and OK to use).
0x00,
0x20
)
)
// Allocate and copy the response bytes, making sure to increment
// the free memory pointer accordingly (in case this method is
// called as an internal function). The remaining `returndata[0x20:]`
// contains the ABI encoded response bytes, so we can just write it
// as is to memory.
let responseSize := sub(returndatasize(), 0x20)
response := mload(0x40)
mstore(0x40, add(response, responseSize))
returndatacopy(response, 0x20, responseSize)
if iszero(mload(0x00)) { revert(add(response, 0x20), mload(response)) }
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity >=0.7.0 <0.9.0;
import "forge-std/Test.sol";
import "solady/utils/LibSort.sol";
import "safe-contracts/GnosisSafe.sol";
import "safe-contracts/proxies/GnosisSafeProxyFactory.sol";
import "safe-contracts/examples/libraries/SignMessage.sol";
import "./CompatibilityFallbackHandler_1_3_0.sol";
import "safe-contracts/examples/libraries/SignMessage.sol";
address constant VM_ADDR = 0x7109709ECfa91a80626fF3989D68f67F5b1DD12D;
bytes12 constant ADDR_MASK = 0xffffffffffffffffffffffff;
function getAddr(uint256 pk) pure returns (address) {
return Vm(VM_ADDR).addr(pk);
}
function encodeSmartContractWalletAsPK(address addr) pure returns (uint256 encodedPK) {
assembly {
let addr_b32 := addr
encodedPK := or(addr, ADDR_MASK)
}
}
function decodeSmartContractWalletAsAddress(uint256 pk) pure returns (address decodedAddr) {
assembly {
let addr := shl(96, pk)
decodedAddr := shr(96, addr)
}
}
function isSmartContractPK(uint256 pk) pure returns (bool isEncoded) {
assembly {
isEncoded := eq(shr(160, pk), shr(160, ADDR_MASK))
}
}
library Sort {
function sort(address[] memory arr) public pure returns (address[] memory) {
LibSort.sort(arr);
return arr;
}
}
function sortPKsByComputedAddress(uint256[] memory _pks) pure returns (uint256[] memory) {
uint256[] memory sortedPKs = new uint256[](_pks.length);
address[] memory addresses = new address[](_pks.length);
bytes32[2][] memory accounts = new bytes32[2][](_pks.length);
for (uint256 i; i < _pks.length; i++) {
address signer = getAddr(_pks[i]);
addresses[i] = signer;
accounts[i][0] = bytes32(abi.encode(signer));
accounts[i][1] = bytes32(_pks[i]);
}
addresses = Sort.sort(addresses);
uint256 found;
for (uint256 j; j < addresses.length; j++) {
address signer = addresses[j];
uint256 pk;
for (uint256 k; k < accounts.length; k++) {
if (address(uint160(uint256(accounts[k][0]))) == signer) {
pk = uint256(accounts[k][1]);
found++;
}
}
sortedPKs[j] = pk;
}
if (found < _pks.length) {
revert("SAFETESTTOOLS: issue with private key sorting, please open a ticket on github");
}
return sortedPKs;
}
// collapsed interface that includes comapatibilityfallback handler calls
abstract contract DeployedSafe is GnosisSafe, CompatibilityFallbackHandler { }
struct AdvancedSafeInitParams {
bool includeFallbackHandler;
uint256 saltNonce;
address setupModulesCall_to;
bytes setupModulesCall_data;
uint256 refundAmount;
address refundToken;
address payable refundReceiver;
bytes initData;
}
struct SafeInstance {
uint256 instanceId;
uint256[] ownerPKs;
address[] owners;
uint256 threshold;
DeployedSafe safe;
}
library SafeTestLib {
function execTransaction(
SafeInstance memory instance,
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
bytes memory signatures
)
public
returns (bool)
{
if (instance.owners.length == 0) {
revert("SAFETEST: Instance not initialized. Call _setupSafe() to initialize a test safe");
}
bytes32 safeTxHash;
{
uint256 _nonce = instance.safe.nonce();
safeTxHash = instance.safe.getTransactionHash({
to: to,
value: value,
data: data,
operation: operation,
safeTxGas: safeTxGas,
baseGas: baseGas,
gasPrice: gasPrice,
gasToken: gasToken,
refundReceiver: refundReceiver,
_nonce: _nonce
});
}
if (signatures.length == 0) {
for (uint256 i; i < instance.ownerPKs.length; ++i) {
uint256 pk = instance.ownerPKs[i];
(uint8 v, bytes32 r, bytes32 s) = Vm(VM_ADDR).sign(pk, safeTxHash);
if (isSmartContractPK(pk)) {
v = 0;
address addr = decodeSmartContractWalletAsAddress(pk);
assembly {
r := addr
}
console.logBytes32(r);
}
signatures = bytes.concat(signatures, abi.encodePacked(r, s, v));
}
}
return instance.safe.execTransaction({
to: to,
value: value,
data: data,
operation: operation,
safeTxGas: safeTxGas,
baseGas: baseGas,
gasPrice: gasPrice,
gasToken: gasToken,
refundReceiver: payable(refundReceiver),
signatures: signatures
});
}
function execTransaction(
SafeInstance memory instance,
address to,
uint256 value,
bytes memory data,
Enum.Operation operation
)
public
returns (bool)
{
return execTransaction(instance, to, value, data, operation, 0, 0, 0, address(0), address(0), "");
}
/// @dev performs a noraml "call"
function execTransaction(
SafeInstance memory instance,
address to,
uint256 value,
bytes memory data
)
public
returns (bool)
{
return execTransaction(instance, to, value, data, Enum.Operation.Call, 0, 0, 0, address(0), address(0), "");
}
function enableModule(SafeInstance memory instance, address module) public {
execTransaction(
instance,
address(instance.safe),
0,
abi.encodeWithSelector(ModuleManager.enableModule.selector, module),
Enum.Operation.Call,
0,
0,
0,
address(0),
address(0),
""
);
}
function disableModule(SafeInstance memory instance, address module) public {
(address[] memory modules,) = instance.safe.getModulesPaginated(SENTINEL_MODULES, 1000);
address prevModule = SENTINEL_MODULES;
bool moduleFound;
for (uint256 i; i < modules.length; i++) {
if (modules[i] == module) {
moduleFound = true;
break;
}
prevModule = modules[i];
}
if (!moduleFound) revert("SAFETESTTOOLS: cannot disable module that is not enabled");
execTransaction(
instance,
address(instance.safe),
0,
abi.encodeWithSelector(ModuleManager.disableModule.selector, prevModule, module),
Enum.Operation.Call,
0,
0,
0,
address(0),
address(0),
""
);
}
function EIP1271Sign(SafeInstance memory instance, bytes memory data) public {
address signMessageLib = address(new SignMessageLib());
execTransaction({
instance: instance,
to: signMessageLib,
value: 0,
data: abi.encodeWithSelector(SignMessageLib.signMessage.selector, data),
operation: Enum.Operation.DelegateCall,
safeTxGas: 0,
baseGas: 0,
gasPrice: 0,
gasToken: address(0),
refundReceiver: payable(address(0)),
signatures: ""
});
}
function EIP1271Sign(SafeInstance memory instance, bytes32 digest) public {
EIP1271Sign(instance, abi.encodePacked(digest));
}
function signTransaction(
SafeInstance memory instance,
uint256 pk,
address to,
uint256 value,
bytes memory data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver
)
public
view
returns (uint8 v, bytes32 r, bytes32 s)
{
bytes32 txDataHash;
{
uint256 _nonce = instance.safe.nonce();
txDataHash = instance.safe.getTransactionHash({
to: to,
value: value,
data: data,
operation: operation,
safeTxGas: safeTxGas,
baseGas: baseGas,
gasPrice: gasPrice,
gasToken: gasToken,
refundReceiver: refundReceiver,
_nonce: _nonce
});
}
(v, r, s) = Vm(VM_ADDR).sign(pk, txDataHash);
}
function incrementNonce(SafeInstance memory instance) public returns (uint256 newNonce) {
execTransaction(instance, address(0), 0, "", Enum.Operation.Call, 0, 0, 0, address(0), address(0), "");
return instance.safe.nonce();
}
}
contract SafeTestTools {
using SafeTestLib for SafeInstance;
GnosisSafe internal singleton = new GnosisSafe();
GnosisSafeProxyFactory internal proxyFactory = new GnosisSafeProxyFactory();
CompatibilityFallbackHandler internal handler = new CompatibilityFallbackHandler();
SafeInstance[] internal instances;
/// @dev can be called to reinitialize the singleton, proxyFactory and handler. Useful for forking.
function _initializeSafeTools() internal {
singleton = new GnosisSafe();
proxyFactory = new GnosisSafeProxyFactory();
handler = new CompatibilityFallbackHandler();
}
function _setupSafe(
uint256[] memory ownerPKs,
uint256 threshold,
uint256 initialBalance,
AdvancedSafeInitParams memory advancedParams
)
public
returns (SafeInstance memory)
{
uint256[] memory sortedPKs = sortPKsByComputedAddress(ownerPKs);
address[] memory owners = new address[](sortedPKs.length);
for (uint256 i; i < sortedPKs.length; i++) {
if (isSmartContractPK(sortedPKs[i])) {
owners[i] = decodeSmartContractWalletAsAddress(sortedPKs[i]);
} else {
owners[i] = getAddr(sortedPKs[i]);
}
}
// store the initialization parameters
bytes memory initData = advancedParams.initData.length > 0
? advancedParams.initData
: abi.encodeWithSelector(
GnosisSafe.setup.selector,
owners,
threshold,
advancedParams.setupModulesCall_to,
advancedParams.setupModulesCall_data,
advancedParams.includeFallbackHandler ? address(handler) : address(0),
advancedParams.refundToken,
advancedParams.refundAmount,
advancedParams.refundReceiver
);
DeployedSafe safe0 = DeployedSafe(
payable(
advancedParams.saltNonce != 0
? proxyFactory.createProxyWithNonce(address(singleton), initData, advancedParams.saltNonce)
: proxyFactory.createProxy(address(singleton), initData)
)
);
SafeInstance memory instance0 = SafeInstance({
instanceId: instances.length,
ownerPKs: sortedPKs,
owners: owners,
threshold: threshold,
// setup safe ecosystem, singleton, proxy factory, fallback handler, and create a new safe
safe: safe0
});
instances.push(instance0);
Vm(VM_ADDR).deal(address(safe0), initialBalance);
return instance0;
}
function _setupSafe(
uint256[] memory ownerPKs,
uint256 threshold,
uint256 initialBalance
)
public
returns (SafeInstance memory)
{
return _setupSafe(
ownerPKs,
threshold,
initialBalance,
AdvancedSafeInitParams({
includeFallbackHandler: true,
initData: "",
saltNonce: 0,
setupModulesCall_to: address(0),
setupModulesCall_data: "",
refundAmount: 0,
refundToken: address(0),
refundReceiver: payable(address(0))
})
);
}
function _setupSafe(uint256[] memory ownerPKs, uint256 threshold) public returns (SafeInstance memory) {
return _setupSafe(
ownerPKs,
threshold,
10000 ether,
AdvancedSafeInitParams({
includeFallbackHandler: true,
initData: "",
saltNonce: 0,
setupModulesCall_to: address(0),
setupModulesCall_data: "",
refundAmount: 0,
refundToken: address(0),
refundReceiver: payable(address(0))
})
);
}
function _setupSafe() public returns (SafeInstance memory) {
string[3] memory users;
users[0] = "SAFETEST: Signer 0";
users[1] = "SAFETEST: Signer 1";
users[2] = "SAFETEST: Signer 2";
uint256[] memory defaultPKs = new uint256[](3);
defaultPKs[0] = 0xac0974bec39a17e36ba4a6b4d238ff944bacb478cbed5efcae784d7bf4f2ff80;
defaultPKs[1] = 0x59c6995e998f97a5a0044966f0945389dc9e86dae88c7a8412f4603b6b78690d;
defaultPKs[2] = 0x5de4111afa1a4b94908f83103eb1f1706367c2e68ca870fc3fb9a804cdab365a;
for (uint256 i; i < 3; i++) {
Vm(VM_ADDR).label(getAddr(defaultPKs[i]), users[i]);
}
return _setupSafe(
defaultPKs,
2,
10000 ether,
AdvancedSafeInitParams({
includeFallbackHandler: true,
initData: "",
saltNonce: uint256(keccak256(bytes("SAFE TEST"))),
setupModulesCall_to: address(0),
setupModulesCall_data: "",
refundAmount: 0,
refundToken: address(0),
refundReceiver: payable(address(0))
})
);
}
function getSafe() public view returns (SafeInstance memory) {
if (instances.length == 0) {
revert("SAFETESTTOOLS: Test Safe has not been deployed, use _setupSafe() calling safe()");
}
return instances[0];
}
function getSafe(address _safe) public view returns (SafeInstance memory) {
for (uint256 i; i < instances.length; ++i) {
if (address(instances[i].safe) == _safe) return instances[i];
}
revert("SAFETESTTOOLS: Safe instance not found");
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment