Skip to content

Commit

Permalink
Merge pull request #394 from SocketDotTech/feat/multisig-wrapper
Browse files Browse the repository at this point in the history
safe wrapper
  • Loading branch information
arthcp authored Feb 10, 2025
2 parents b11272a + 2c17187 commit 79456a6
Show file tree
Hide file tree
Showing 8 changed files with 622 additions and 6 deletions.
6 changes: 6 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,9 @@
[submodule "lib/solmate"]
path = lib/solmate
url = https://github.com/transmissions11/solmate
[submodule "lib/safe-smart-account"]
path = lib/safe-smart-account
url = https://github.com/safe-global/safe-smart-account
[submodule "lib/solady"]
path = lib/solady
url = https://github.com/Vectorized/solady
61 changes: 61 additions & 0 deletions contracts/mocks/MockSafe.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;

import "safe-smart-account/common/Enum.sol";

contract MockSafe {
uint256 public threshold = 1;
uint256 public nonce = 0;
address public lastTo;
uint256 public lastValue;
bytes public lastData;

function setThreshold(uint256 _threshold) external {
threshold = _threshold;
}

function execTransaction(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
) external payable returns (bool success) {
lastTo = to;
lastValue = value;
lastData = data;
return true;
}

function getThreshold() external view returns (uint256) {
return threshold;
}

function getTransactionHash(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes32) {
return keccak256(abi.encode(to, value, data, operation, _nonce));
}

function getLastTransaction()
external
view
returns (address, uint256, bytes memory)
{
return (lastTo, lastValue, lastData);
}
}
10 changes: 5 additions & 5 deletions contracts/socket/SocketBatcher.sol
Original file line number Diff line number Diff line change
Expand Up @@ -270,11 +270,11 @@ contract SocketBatcher is AccessControl {
IExecutionManager.setRelativeNativeTokenPrice.selector
) {
IExecutionManager(contractAddress_).setRelativeNativeTokenPrice(
setFeesRequests_[index].nonce,
setFeesRequests_[index].dstChainSlug,
setFeesRequests_[index].fees,
setFeesRequests_[index].signature
);
setFeesRequests_[index].nonce,
setFeesRequests_[index].dstChainSlug,
setFeesRequests_[index].fees,
setFeesRequests_[index].signature
);
} else if (
setFeesRequests_[index].functionSelector ==
IExecutionManager.setMsgValueMaxThreshold.selector
Expand Down
250 changes: 250 additions & 0 deletions contracts/utils/MultiSigWrapper.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,250 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity 0.8.19;

import "../libraries/RescueFundsLib.sol";

import "../utils/AccessControl.sol";
import {RESCUE_ROLE} from "../utils/AccessRoles.sol";
import "safe-smart-account/common/Enum.sol";
import "solady/utils/LibSort.sol";

interface ISafe {
function execTransaction(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address payable refundReceiver,
bytes memory signatures
) external payable returns (bool success);

function getThreshold() external view returns (uint256);

function nonce() external view returns (uint256);

function getTransactionHash(
address to,
uint256 value,
bytes calldata data,
Enum.Operation operation,
uint256 safeTxGas,
uint256 baseGas,
uint256 gasPrice,
address gasToken,
address refundReceiver,
uint256 _nonce
) external view returns (bytes32);
}

/**
* @title MultiSigWrapper
*/
contract MultiSigWrapper is AccessControl {
using LibSort for address;

ISafe public safe;

mapping(bytes32 => address[]) public owners;
mapping(address => uint256) public lastNonce;

mapping(bytes32 => mapping(address => bytes)) public signatures;
struct GasParams {
uint256 safeTxGas;
uint256 baseGas;
uint256 gasPrice;
address gasToken;
address refundReceiver;
}

event AddedTxHash(
bytes32 txHash,
address to,
uint256 value,
bytes data,
uint256 nonce
);

event ConstantsUpdated(
uint256 safeTxGas_,
uint256 baseGas_,
uint256 gasPrice_,
address gasToken_,
address refundReceiver_
);

event SafeUpdated(address safe_);

/**
* @notice initializes and grants RESCUE_ROLE to owner.
* @param owner_ The address of the owner of the contract.
* @param safe_ The address of the safe contract.
*/
constructor(address owner_, address safe_) AccessControl(owner_) {
safe = ISafe(safe_);
_grantRole(RESCUE_ROLE, owner_);
}

function storeOrRelaySignatures(
address from_,
address to_,
uint256 nonce_,
uint256 value_,
bytes calldata data_,
bytes memory signature_
) external {
uint256 threshold = safe.getThreshold();
lastNonce[from_] = nonce_;

GasParams memory gasParams;
if (threshold == 1)
return
_relay(
to_,
value_,
Enum.Operation.Call,
gasParams,
data_,
signature_
);

bytes32 txHash = keccak256(
abi.encode(to_, value_, data_, Enum.Operation.Call, nonce_)
);

uint256 totalSignatures = _storeSignatures(txHash, from_, signature_);

if (totalSignatures >= threshold) {
_relay(
to_,
value_,
Enum.Operation.Call,
gasParams,
data_,
_getSignatures(txHash, threshold)
);
}

emit AddedTxHash(txHash, to_, value_, data_, nonce_);
}

function storeOrRelaySignaturesWithOverrides(
address from_,
address to_,
uint256 nonce_,
uint256 value_,
Enum.Operation operation_,
GasParams calldata gasParams_,
bytes calldata data_,
bytes memory signature_
) external {
uint256 threshold = safe.getThreshold();
lastNonce[from_] = nonce_;

if (threshold == 1)
return
_relay(to_, value_, operation_, gasParams_, data_, signature_);

bytes32 txHash = keccak256(
abi.encode(
to_,
value_,
data_,
operation_,
nonce_,
gasParams_.gasPrice
)
);
uint256 totalSignatures = _storeSignatures(txHash, from_, signature_);

if (totalSignatures >= threshold) {
_relay(
to_,
value_,
operation_,
gasParams_,
data_,
_getSignatures(txHash, threshold)
);
}

emit AddedTxHash(txHash, to_, value_, data_, nonce_);
}

function _storeSignatures(
bytes32 txHash_,
address from_,
bytes memory signature_
) internal returns (uint256 totalSignatures) {
owners[txHash_].push(from_);
signatures[txHash_][from_] = signature_;
totalSignatures = owners[txHash_].length;
}

function _relay(
address to_,
uint256 value_,
Enum.Operation operation_,
GasParams memory gasParams_,
bytes calldata data_,
bytes memory signatures_
) internal {
safe.execTransaction(
to_,
value_,
data_,
operation_,
gasParams_.safeTxGas,
gasParams_.baseGas,
gasParams_.gasPrice,
gasParams_.gasToken,
payable(gasParams_.refundReceiver),
signatures_
);
}

function _getSignatures(
bytes32 txHash_,
uint256 threshold_
) internal returns (bytes memory signature) {
address[] memory txOwners = owners[txHash_];
LibSort.insertionSort(txOwners);
uint256 len = txOwners.length;

for (uint256 index = 0; index < len; index++) {
signature = abi.encodePacked(
signature,
signatures[txHash_][txOwners[index]]
);
}
}

/**
* @notice Update safe address
*/
function updateSafe(address safe_) external onlyOwner {
safe = ISafe(safe_);
emit SafeUpdated(safe_);
}

function getNonce() external view returns (uint256) {
return safe.nonce();
}

/**
* @notice Rescues funds from the contract if they are locked by mistake.
* @param token_ The address of the token contract.
* @param rescueTo_ The address where rescued tokens need to be sent.
* @param amount_ The amount of tokens to be rescued.
*/
function rescueFunds(
address token_,
address rescueTo_,
uint256 amount_
) external onlyRole(RESCUE_ROLE) {
RescueFundsLib.rescueFunds(token_, rescueTo_, amount_);
}
}
1 change: 1 addition & 0 deletions lib/safe-smart-account
Submodule safe-smart-account added at bf943f
1 change: 1 addition & 0 deletions lib/solady
Submodule solady added at a1f9be
4 changes: 3 additions & 1 deletion remappings.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,6 @@ ds-test/=lib/forge-std/lib/ds-test/src/
forge-std/=lib/forge-std/src/
openzeppelin-contracts/=lib/openzeppelin-contracts/
fx-portal/=lib/contracts/contracts/
solmate/=lib/solmate/src/
solmate/=lib/solmate/src/
safe-smart-account/=lib/safe-smart-account/contracts/
solady/=lib/solady/src/
Loading

0 comments on commit 79456a6

Please sign in to comment.