Skip to content

Instantly share code, notes, and snippets.

@darkerego
Created December 16, 2024 11:47
Show Gist options
  • Save darkerego/091894709357390135bae71e676a10d7 to your computer and use it in GitHub Desktop.
Save darkerego/091894709357390135bae71e676a10d7 to your computer and use it in GitHub Desktop.
Obfuscated Forwarder
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Forwarder {
// admin of the contract
address private immutable owner;
// Some randomness to ensure that each deployment has unique bytecode
bytes32 public immutable entropy = bytes32(keccak256(abi.encode(uint256(uint160(block.timestamp ^ (block.number / block.timestamp))))));
// this key is set at deploy time and never changes
bytes32 private immutable staticKey;
// this key can be updated
bytes32 private dynamicKey;
// encrpypted destination address for forwarding
bytes32 private encryptedDest;
error Unauthorized();
error SetFirst();
error AlreadySet();
error ForwardFailed(bytes returnData);
constructor(bytes32 initialDynamicKey){
(staticKey, dynamicKey, owner) = (keccak256(abi.encode(getBytecode(address(this)))), initialDynamicKey, msg.sender);
}
/*
@dev return true if caller is admin
*/
function authorizedCaller() internal view returns (bool) {
return msg.sender == owner;
}
/*
@dev Revert if caller is not admin
*/
modifier auth {
if (! authorizedCaller()) {
revert Unauthorized();
}
_;
}
/*
@dev Set encrypted destination address for forwarding
*/
function setDest(bytes32 next) external returns(bool) {
encryptedDest = next;
return true;
}
/*
@dev Update the dynamic key to a new value
*/
function updateDynamicKey(bytes32 newKey) public auth {
dynamicKey = newKey;
}
function getDest() private view returns (bytes32 _key) {
assembly {
let __key := sload(encryptedDest.slot)
if eq(__key, 0) {
_key := 0
}
_key := __key
}
}
function getBytecode(address target) internal view returns (bytes memory _code) {
assembly {
let size := extcodesize(target)
_code := mload(0x40) // Load the free memory pointer
mstore(_code, size) // Store the size of the bytecode at the beginning
let dataPointer := add(_code, 0x20) // Pointer to the actual bytecode data
extcodecopy(target, dataPointer, 0, size)
mstore(0x40, add(dataPointer, size))
}
}
/*
@dev Warning: this is for convience and debugging. you should really generate the next key offline.
@return bytes32: Some random salt.
*/
function lazyNewSalt() public view returns (bytes32){
bytes memory _salt = abi.encodePacked(block.prevrandao);
return keccak256(_salt);
}
/*
@notice: Warning: This is for convience. Unless you have your own node, it is highly recommended to calculate offchain
@dev Encode an address input with the xorValue.
@return bytes32: The xor'd salt+nonce value.
*/
function encodeTarget(address target) external view returns(bytes32 encoded) {
if(target == address(0)) revert Unauthorized();
//if (encryptedDest == 0x0) revert SetFirst();
encoded = xorEncode(target);
}
/*
@dev Encode an address input with the xorValue
@return bytes32: The xor'd salt+nonce value
*/
function xorEncode(address input) internal view returns (bytes32) {
return bytes32(uint256(uint160(input))) ^ (staticKey ^ dynamicKey);
}
// Decode the XOR result back into an address
function xorDecode(bytes32 encoded) internal view returns (address) {
// Reverse the XOR operation and truncate to address size (20 bytes)
//bytes32 decoded = (encoded ^ xorValue);
return address(uint160(uint256(encoded ^ (staticKey ^ dynamicKey))));
}
/*
@dev: Function to executeCall a transaction with arbitrary parameters.
@dev: Warning: uses assembly for gas efficiency
@param recipient: Recipients address
@param _value: Value in wei to send.
@param data: Calldata to call the contract with.
@return success, retData: Boolean for if the transaction was successful, the resulting bytecode data from the msg call
*/
function executeCall(
address recipient,
uint256 _value,
bytes memory data
) internal returns(bool success, bytes memory retData) {
assembly {
let ptr := mload(0x40)
success := call(gas(), recipient, _value, add(data, 0x20), mload(data), 0x00, 0x00)
success := eq(success, 0x1)
let retSz := returndatasize()
retData := mload(0x40)
returndatacopy(mload(0x40), 0 , returndatasize())
if iszero(success) {
revert(retData, retSz)
}
}
}
/*
@dev: expose the `forward()` function for direct calls
*/
function forward() external payable returns(bool success, bytes memory ret) {
return _forward();
}
/*
@notice: decrypt the forwarder address and execute the call
*/
function _forward() internal returns(bool success, bytes memory ret) {
bytes32 __key = getDest();
if (__key == 0x0) {
revert("0x0");
} else {
(success, ret) = executeCall(xorDecode(__key), msg.value, msg.data);
}
}
/*
@notice: Any call to this contract will forward
*/
fallback() external payable { _forward(); }
receive() external payable { _forward(); }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment