Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
N
nebula
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
exchain
nebula
Commits
55fe9711
Unverified
Commit
55fe9711
authored
Sep 30, 2023
by
Maurelian
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
safe-tools: vendor in safe-tools lib
parent
d9163367
Changes
2
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
679 additions
and
0 deletions
+679
-0
CompatibilityFallbackHandler_1_3_0.sol
...ck/test/safe-tools/CompatibilityFallbackHandler_1_3_0.sol
+212
-0
SafeTestTools.sol
packages/contracts-bedrock/test/safe-tools/SafeTestTools.sol
+467
-0
No files found.
packages/contracts-bedrock/test/safe-tools/CompatibilityFallbackHandler_1_3_0.sol
0 → 100644
View file @
55fe9711
// 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)) }
}
}
}
packages/contracts-bedrock/test/safe-tools/SafeTestTools.sol
0 → 100644
View file @
55fe9711
// 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");
}
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment