generated from gnosisguild/zodiac-mod-starter-kit
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
add: first pass at implementing OSXAdapter
* Almost certainly broken. * no tests implemented. * Pulled code from Roles mod to unwrap multisend transactions, need to revisit and write tests to ensure payloads are converted correctly.
- Loading branch information
1 parent
9c5f6d3
commit a04bcc6
Showing
9 changed files
with
344 additions
and
49 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
pragma solidity >=0.8.26 <0.9.0; | ||
|
||
import "./Types.sol"; | ||
|
||
interface IOSx { | ||
/// @notice Executes a list of actions. If a zero allow-failure map is provided, a failing action reverts the entire execution. If a non-zero allow-failure map is provided, allowed actions can fail without the entire call being reverted. | ||
/// @param _callId The ID of the call. The definition of the value of `callId` is up to the calling contract and can be used, e.g., as a nonce. | ||
/// @param _actions The array of actions. | ||
/// @param _allowFailureMap A bitmap allowing execution to succeed, even if individual actions might revert. If the bit at index `i` is 1, the execution succeeds even if the `i`th action reverts. A failure map value of 0 requires every action to not revert. | ||
/// @return The array of results obtained from the executed actions in `bytes`. | ||
/// @return The resulting failure map containing the actions have actually failed. | ||
function execute( | ||
bytes32 _callId, | ||
Action[] memory _actions, | ||
uint256 _allowFailureMap | ||
) external returns (bytes[] memory, uint256); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,117 @@ | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
pragma solidity >=0.8.26 <0.9.0; | ||
|
||
import "./Types.sol"; | ||
|
||
contract MultiSendUnwrapper is ITransactionUnwrapper { | ||
uint256 private constant OFFSET_START = 68; | ||
|
||
error UnsupportedMode(); | ||
error MalformedHeader(); | ||
error MalformedBody(); | ||
|
||
function unwrap( | ||
address, | ||
uint256 value, | ||
bytes calldata data, | ||
Enum.Operation operation | ||
) external pure returns (UnwrappedTransaction[] memory) { | ||
if (value != 0) { | ||
revert UnsupportedMode(); | ||
} | ||
if (operation != Enum.Operation.DelegateCall) { | ||
revert UnsupportedMode(); | ||
} | ||
_validateHeader(data); | ||
uint256 count = _validateEntries(data); | ||
return _unwrapEntries(data, count); | ||
} | ||
|
||
function _validateHeader(bytes calldata data) private pure { | ||
// first 4 bytes are the selector for multiSend(bytes) | ||
if (bytes4(data) != IMultiSend.multiSend.selector) { | ||
revert MalformedHeader(); | ||
} | ||
|
||
// the following 32 bytes are the offset to the bytes param | ||
// (always 0x20) | ||
if (bytes32(data[4:]) != bytes32(uint256(0x20))) { | ||
revert MalformedHeader(); | ||
} | ||
|
||
// the following 32 bytes are the length of the bytes param | ||
uint256 length = uint256(bytes32(data[36:])); | ||
|
||
// validate that the total calldata length matches | ||
// it's the 4 + 32 + 32 bytes checked above + the <length> bytes | ||
// padded to a multiple of 32 | ||
if (4 + _ceil32(32 + 32 + length) != data.length) { | ||
revert MalformedHeader(); | ||
} | ||
} | ||
|
||
function _validateEntries(bytes calldata data) private pure returns (uint256 count) { | ||
uint256 offset = OFFSET_START; | ||
|
||
// data is padded to 32 bytes we can't simply do offset < data.length | ||
for (; offset + 32 < data.length; ) { | ||
// Per transaction: | ||
// Operation 1 bytes | ||
// To 20 bytes | ||
// Value 32 bytes | ||
// Length 32 bytes | ||
// Data Length bytes | ||
uint8 operation = uint8(bytes1(data[offset:])); | ||
if (operation > 1) { | ||
revert MalformedBody(); | ||
} | ||
|
||
uint256 length = uint256(bytes32(data[offset + 53:])); | ||
if (offset + 85 + length > data.length) { | ||
revert MalformedBody(); | ||
} | ||
|
||
offset += 85 + length; | ||
count++; | ||
} | ||
|
||
if (count == 0) { | ||
revert MalformedBody(); | ||
} | ||
} | ||
|
||
function _unwrapEntries( | ||
bytes calldata data, | ||
uint256 count | ||
) private pure returns (UnwrappedTransaction[] memory result) { | ||
result = new UnwrappedTransaction[](count); | ||
|
||
uint256 offset = OFFSET_START; | ||
for (uint256 i; i < count; ) { | ||
result[i].operation = Enum.Operation(uint8(bytes1(data[offset:]))); | ||
offset += 1; | ||
|
||
result[i].to = address(bytes20(data[offset:])); | ||
offset += 20; | ||
|
||
result[i].value = uint256(bytes32(data[offset:])); | ||
offset += 32; | ||
|
||
uint256 size = uint256(bytes32(data[offset:])); | ||
offset += 32; | ||
|
||
result[i].data = bytes(data[offset:size]); | ||
|
||
offset += size; | ||
|
||
unchecked { | ||
++i; | ||
} | ||
} | ||
} | ||
|
||
function _ceil32(uint256 length) private pure returns (uint256) { | ||
// pad size. Source: http://www.cs.nott.ac.uk/~psarb2/G51MPC/slides/NumberLogic.pdf | ||
return ((length + 32 - 1) / 32) * 32; | ||
} | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,141 @@ | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
pragma solidity >=0.8.26 <0.9.0; | ||
|
||
import {Modifier} from "@gnosis.pm/zodiac/contracts/core/Modifier.sol"; | ||
import {Enum} from "@gnosis.pm/zodiac/contracts/core/Module.sol"; | ||
import {IOSx} from "./IOSx.sol"; | ||
import "./Types.sol"; | ||
|
||
contract OSXAdapter is Modifier { | ||
/// @notice The ID of the permission required to call the OSx `execute` function. | ||
bytes32 public constant EXECUTE_PERMISSION_ID = keccak256("EXECUTE_PERMISSION"); | ||
|
||
/// @notice Maps allowed multisend addresses to their corresponding transaction unwrappers. | ||
/// @dev Delegate calls to mapped addresses will be unwrapped into an array of calls. | ||
mapping(address multisend => ITransactionUnwrapper transactionUnwrapper) public transactionUnwrappers; | ||
|
||
event TransactionUnwrapperSet(address multisendAddress, ITransactionUnwrapper transactionUnwrapper); | ||
|
||
error DelegateCallNotAllowed(); | ||
error MultisendAddressNotAllowed(); | ||
error TransactionUnwrapperAlreadySet(); | ||
|
||
constructor(address _owner, address _avatar, address _target) { | ||
bytes memory initializeParams = abi.encode(_owner, _avatar, _target); | ||
setUp(initializeParams); | ||
} | ||
|
||
/// @dev Initialize function, will be triggered when a new proxy is deployed | ||
/// @param initializeParams Parameters of initialization encoded | ||
function setUp(bytes memory initializeParams) public override initializer { | ||
__Ownable_init(msg.sender); | ||
(address _owner, address _avatar, address _target) = abi.decode(initializeParams, (address, address, address)); | ||
|
||
setAvatar(_avatar); | ||
setTarget(_target); | ||
transferOwnership(_owner); | ||
} | ||
|
||
function execTransactionFromModule( | ||
address to, | ||
uint256 value, | ||
bytes calldata data, | ||
Enum.Operation operation | ||
) public override moduleOnly returns (bool success) { | ||
success = exec(to, value, data, operation); | ||
} | ||
|
||
function execTransactionFromModuleReturnData( | ||
address to, | ||
uint256 value, | ||
bytes calldata data, | ||
Enum.Operation operation | ||
) public override moduleOnly returns (bool success, bytes memory returnData) { | ||
(success, returnData) = execAndReturnData(to, value, data, operation); | ||
} | ||
|
||
function setTransactionUnwrapper( | ||
address multisendAddress, | ||
ITransactionUnwrapper transactionUnwrapper | ||
) public onlyOwner { | ||
require(transactionUnwrappers[multisendAddress] != transactionUnwrapper, TransactionUnwrapperAlreadySet()); | ||
transactionUnwrappers[multisendAddress] = transactionUnwrapper; | ||
emit TransactionUnwrapperSet(multisendAddress, transactionUnwrapper); | ||
} | ||
|
||
/// @dev Passes a transaction to be executed by the avatar. | ||
/// @notice Can only be called by this contract. | ||
/// @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: 0 == call, 1 == delegate call. | ||
function exec( | ||
address to, | ||
uint256 value, | ||
bytes memory data, | ||
Enum.Operation operation | ||
) internal override returns (bool success) { | ||
Action[] memory actions = convertTransaction(to, value, data, operation); | ||
IOSx(target).execute(bytes32(0), actions, 0); | ||
success = true; | ||
} | ||
|
||
/// @dev Passes a transaction to be executed by the target and returns data. | ||
/// @notice Can only be called by this contract. | ||
/// @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: 0 == call, 1 == delegate call. | ||
function execAndReturnData( | ||
address to, | ||
uint256 value, | ||
bytes memory data, | ||
Enum.Operation operation | ||
) internal override returns (bool, bytes memory) { | ||
Action[] memory actions = convertTransaction(to, value, data, operation); | ||
(bytes[] memory returnData, ) = IOSx(target).execute(bytes32(0), actions, 0); | ||
return (true, abi.encode(returnData)); | ||
} | ||
|
||
function convertTransaction( | ||
address to, | ||
uint256 value, | ||
bytes memory data, | ||
Enum.Operation operation | ||
) private view returns (Action[] memory actions) { | ||
if (operation == Enum.Operation.DelegateCall) { | ||
ITransactionUnwrapper transactionUnwrapper = transactionUnwrappers[to]; | ||
require(transactionUnwrapper != ITransactionUnwrapper(address(0)), MultisendAddressNotAllowed()); | ||
|
||
UnwrappedTransaction[] memory unwrappedTransactions = transactionUnwrapper.unwrap( | ||
to, | ||
value, | ||
data, | ||
operation | ||
); | ||
|
||
for (uint i = 0; i < unwrappedTransactions.length; i++) { | ||
actions[i] = convert( | ||
unwrappedTransactions[i].to, | ||
unwrappedTransactions[i].value, | ||
unwrappedTransactions[i].data, | ||
unwrappedTransactions[i].operation | ||
); | ||
} | ||
} else { | ||
actions[0] = convert(to, value, data, operation); | ||
} | ||
} | ||
|
||
function convert( | ||
address to, | ||
uint256 value, | ||
bytes memory data, | ||
Enum.Operation operation | ||
) private pure returns (Action memory action) { | ||
require(operation == Enum.Operation.Call, DelegateCallNotAllowed()); | ||
action.to = to; | ||
action.value = value; | ||
action.data = data; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
// SPDX-License-Identifier: LGPL-3.0-only | ||
pragma solidity >=0.8.26 <0.9.0; | ||
|
||
import "@gnosis.pm/safe-contracts/contracts/common/Enum.sol"; | ||
|
||
interface IMultiSend { | ||
function multiSend(bytes memory transactions) external payable; | ||
} | ||
|
||
struct UnwrappedTransaction { | ||
Enum.Operation operation; | ||
address to; | ||
uint256 value; | ||
bytes data; | ||
// We wanna deal in calldata slices. We return location, let invoker slice | ||
// uint256 dataLocation; | ||
// uint256 dataSize; | ||
} | ||
|
||
/// @notice The action struct to be consumed by the DAO's `execute` function resulting in an external call. | ||
/// @param to The address to call. | ||
/// @param value The native token value to be sent with the call. | ||
/// @param data The bytes-encoded function selector and calldata for the call. | ||
struct Action { | ||
address to; | ||
uint256 value; | ||
bytes data; | ||
} | ||
|
||
interface ITransactionUnwrapper { | ||
function unwrap( | ||
address to, | ||
uint256 value, | ||
bytes calldata data, | ||
Enum.Operation operation | ||
) external view returns (UnwrappedTransaction[] memory result); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.