More Info
Private Name Tags
ContractCreator
Latest 25 from a total of 904 transactions
Transaction Hash |
Method
|
Block
|
From
|
To
|
|||||
---|---|---|---|---|---|---|---|---|---|
Perform Settleme... | 4674493 | 12 mins ago | IN | 0 S | 0.01219641 | ||||
Perform Settleme... | 4674478 | 12 mins ago | IN | 0 S | 0.01219575 | ||||
Perform Extracti... | 4670810 | 52 mins ago | IN | 0 S | 0.02406943 | ||||
Perform Fulfilme... | 4670753 | 53 mins ago | IN | 0 S | 0.01027246 | ||||
Perform Fulfilme... | 4670040 | 1 hr ago | IN | 0 S | 0.0121083 | ||||
Perform Actions | 4668994 | 1 hr ago | IN | 0 S | 0.00723173 | ||||
Perform Settleme... | 4667337 | 1 hr ago | IN | 0 S | 0.01325423 | ||||
Perform Settleme... | 4667310 | 1 hr ago | IN | 0 S | 0.01219575 | ||||
Perform Fulfilme... | 4667012 | 1 hr ago | IN | 0 S | 0.0121083 | ||||
Perform Fulfilme... | 4664537 | 2 hrs ago | IN | 0 S | 0.01111627 | ||||
Perform Fulfilme... | 4664495 | 2 hrs ago | IN | 0 S | 0.01111627 | ||||
Perform Fulfilme... | 4664395 | 2 hrs ago | IN | 0 S | 0.01111698 | ||||
Perform Settleme... | 4662694 | 2 hrs ago | IN | 0 S | 0.01272397 | ||||
Perform Fulfilme... | 4661445 | 2 hrs ago | IN | 0 S | 0.01028698 | ||||
Perform Fulfilme... | 4658331 | 3 hrs ago | IN | 0 S | 0.01029116 | ||||
Perform Settleme... | 4657325 | 3 hrs ago | IN | 0 S | 0.01219641 | ||||
Perform Extracti... | 4657054 | 3 hrs ago | IN | 0 S | 0.02483387 | ||||
Perform Extracti... | 4656272 | 3 hrs ago | IN | 0 S | 0.02963444 | ||||
Perform Extracti... | 4651327 | 4 hrs ago | IN | 0 S | 0.0240708 | ||||
Perform Fulfilme... | 4651282 | 4 hrs ago | IN | 0 S | 0.01027174 | ||||
Perform Settleme... | 4651234 | 4 hrs ago | IN | 0 S | 0.01219641 | ||||
Perform Settleme... | 4651230 | 4 hrs ago | IN | 0 S | 0.01483652 | ||||
Perform Fulfilme... | 4648125 | 5 hrs ago | IN | 0 S | 0.01173876 | ||||
Perform Extracti... | 4647118 | 5 hrs ago | IN | 0 S | 0.02609062 | ||||
Perform Fulfilme... | 4646681 | 5 hrs ago | IN | 0 S | 0.02265774 |
Latest 25 internal transactions (View All)
Parent Transaction Hash | Block | From | To | |||
---|---|---|---|---|---|---|
4674493 | 12 mins ago | 0.00740069 S | ||||
4674478 | 12 mins ago | 0.01005926 S | ||||
4670753 | 53 mins ago | 3.32979162 S | ||||
4667337 | 1 hr ago | 0.11321249 S | ||||
4667310 | 1 hr ago | 0.01005926 S | ||||
4662694 | 2 hrs ago | 0.0923937 S | ||||
4661445 | 2 hrs ago | 0.39343368 S | ||||
4658331 | 3 hrs ago | 45.08615876 S | ||||
4657325 | 3 hrs ago | 0.0080688 S | ||||
4651282 | 4 hrs ago | 8.5005011 S | ||||
4651234 | 4 hrs ago | 0.11230278 S | ||||
4651230 | 4 hrs ago | 0.04676944 S | ||||
4648125 | 5 hrs ago | 10.39800849 S | ||||
4646681 | 5 hrs ago | 1,837.42855931 S | ||||
4646681 | 5 hrs ago | 1,837.42855931 S | ||||
4646267 | 5 hrs ago | 1,829.51436644 S | ||||
4646267 | 5 hrs ago | 1,829.51436644 S | ||||
4645977 | 5 hrs ago | 1,832.04674303 S | ||||
4645977 | 5 hrs ago | 1,832.04674303 S | ||||
4645945 | 5 hrs ago | 1,832.22722726 S | ||||
4645945 | 5 hrs ago | 1,832.22722726 S | ||||
4645854 | 5 hrs ago | 1,833.49474631 S | ||||
4645854 | 5 hrs ago | 1,833.49474631 S | ||||
4645799 | 5 hrs ago | 1,834.36453252 S | ||||
4645799 | 5 hrs ago | 1,834.36453252 S |
Loading...
Loading
Contract Name:
Solver
Compiler Version
v0.8.19+commit.7dd6d404
Optimization Enabled:
Yes with 200 runs
Other Settings:
default evmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.17; import {Ownable} from "../utils/Ownable.sol"; import {RescueFundsLib} from "../lib/RescueFundsLib.sol"; import {ERC20} from "solady/src/tokens/ERC20.sol"; import {SafeTransferLib} from "solady/src/utils/SafeTransferLib.sol"; import {AuthenticationLib} from "./../lib/AuthenticationLib.sol"; import {BungeeGateway} from "../core/BungeeGateway.sol"; import {FulfilExec as SingleOutputFulfilExec, SingleOutputRequestImpl} from "../core/SingleOutputRequestImpl.sol"; contract Solver is Ownable { struct Action { address target; uint256 value; bytes data; } struct SwapAction { uint256 fulfilExecIndex; Action swapActionData; } error ActionFailed(); error ActionsFailed(uint256 index); error InvalidSigner(); error InvalidNonce(); error InvalidSwapActions(); error SwapActionFailed(uint256 index); error SwapOutputInsufficient(uint256 index); address public constant NATIVE_TOKEN_ADDRESS = 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE; /// @notice address of the signer address internal SOLVER_SIGNER; /// @notice mapping to track used nonces of SOLVER_SIGNER mapping(uint256 nonce => bool isNonceUsed) public nonceUsed; /** * @notice Constructor. * @param _owner address of the contract owner * @param _solverSigner address of the signer */ constructor(address _owner, address _solverSigner) Ownable(_owner) { SOLVER_SIGNER = _solverSigner; } function setSolverSigner(address _solverSigner) external onlyOwner { SOLVER_SIGNER = _solverSigner; } /// @dev separate specific functions for extract, fulfil, settle /// so that easier to index and track costs function performExtraction(uint256 nonce, Action calldata action, bytes calldata signature) external { // @todo assembly encode and hash - can look at solady verifySignature(keccak256(abi.encode(block.chainid, address(this), nonce, action)), signature); // verify nonce assembly { // load data slot from mapping mstore(0, nonce) mstore(0x20, nonceUsed.slot) let dataSlot := keccak256(0, 0x40) // check if nonce is used if and(sload(dataSlot), 0xff) { mstore(0x00, 0x756688fe) // revert InvalidNonce(); revert(0x1c, 0x04) } // if not used mark as used /// @dev not cleaning all the bits, just setting the first bit to 1 sstore(dataSlot, 0x01) } /// @dev no need for approvals in extraction bool success = _performAction(action); assembly { /// @dev not cleaning all the bits, just using success as is if iszero(success) { mstore(0x00, 0x080a1c27) // revert ActionFailed(); revert(0x1c, 0x04) } } } function performSettlement(uint256 nonce, Action calldata action, bytes calldata signature) external { verifySignature(keccak256(abi.encode(block.chainid, address(this), nonce, action)), signature); // verify nonce assembly { // load data slot from mapping mstore(0, nonce) mstore(0x20, nonceUsed.slot) let dataSlot := keccak256(0, 0x40) // check if nonce is used if and(sload(dataSlot), 0xff) { mstore(0x00, 0x756688fe) // revert InvalidNonce(); revert(0x1c, 0x04) } // if not used mark as used /// @dev not cleaning all the bits, just setting the first bit to 1 sstore(dataSlot, 0x01) } /// @dev no need for approvals in settlement bool success = _performAction(action); assembly { /// @dev not cleaning all the bits, just using success as is if iszero(success) { mstore(0x00, 0x080a1c27) // revert ActionFailed(); revert(0x1c, 0x04) } } } /** * @notice Convenience function that helps perform a destination swap and fulfil the request. * @dev Can be used to perform a single swap and single fulfilment * @dev Modifies the fulfilAmount of the fulfilExec to the received amount from the swap */ function performFulfilment( uint256 nonce, bytes[] calldata approvals, address bungeeGateway, uint256 value, Action calldata swapActionData, SingleOutputFulfilExec memory fulfilExec, bytes calldata signature ) external { verifySignature( keccak256( abi.encode( block.chainid, address(this), nonce, approvals, bungeeGateway, value, swapActionData, fulfilExec ) ), signature ); // verify nonce assembly { // load data slot from mapping mstore(0, nonce) mstore(0x20, nonceUsed.slot) let dataSlot := keccak256(0, 0x40) // check if nonce is used if and(sload(dataSlot), 0xff) { mstore(0x00, 0x756688fe) // revert InvalidNonce(); revert(0x1c, 0x04) } // if not used mark as used /// @dev not cleaning all the bits, just setting the first bit to 1 sstore(dataSlot, 0x01) } // _setApprovals(approvals); if (approvals.length > 0) { for (uint256 i = 0; i < approvals.length; i++) { // @todo assembly (address token, address spender, uint256 amount) = abi.decode( approvals[i], (address, address, uint256) ); SafeTransferLib.safeApprove(token, spender, amount); } } if (swapActionData.target != address(0)) { // check pre balance // @todo assembly bool isNativeToken = fulfilExec.request.basicReq.outputToken == NATIVE_TOKEN_ADDRESS; uint256 beforeBalance = isNativeToken ? address(this).balance : ERC20(fulfilExec.request.basicReq.outputToken).balanceOf(address(this)); // Perform swap action bool success = _performAction(swapActionData); if (!success) { // @todo replace with assembly revert SwapActionFailed(0); } // check post balance uint256 swapActionOutput = isNativeToken ? address(this).balance - beforeBalance : ERC20(fulfilExec.request.basicReq.outputToken).balanceOf(address(this)) - beforeBalance; if (fulfilExec.fulfilAmount > swapActionOutput) { revert SwapOutputInsufficient(0); } // Overwrite the fulfilAmount with the received amount fulfilExec.fulfilAmount = swapActionOutput; // also update fulfilExec.msgValue & value sent to gateway if native token if (isNativeToken) { fulfilExec.msgValue = swapActionOutput; value = swapActionOutput; } } // perform fulfilment SingleOutputFulfilExec[] memory fulfilExecs = new SingleOutputFulfilExec[](1); fulfilExecs[0] = fulfilExec; // @todo assembly BungeeGateway(payable(bungeeGateway)).executeSOR{value: value}( abi.encodeCall(SingleOutputRequestImpl.fulfilRequests, (fulfilExecs)) ); } /** * @notice Convenience function that helps perform a destination swap and fulfil the request. * @dev Can be used to perform a batch of (swap & fulfilment) * @dev Modifies the fulfilAmounts of each fulfilExecs to the received amount from the swap */ function performBatchFulfilment( uint256 nonce, bytes[] calldata approvals, address bungeeGateway, uint256 value, SwapAction[] calldata swapActions, SingleOutputFulfilExec[] memory fulfilExecs, bytes calldata signature ) external { verifySignature( keccak256( abi.encode( block.chainid, address(this), nonce, approvals, bungeeGateway, value, swapActions, fulfilExecs ) ), signature ); // verify nonce assembly { // load data slot from mapping mstore(0, nonce) mstore(0x20, nonceUsed.slot) let dataSlot := keccak256(0, 0x40) // check if nonce is used if and(sload(dataSlot), 0xff) { mstore(0x00, 0x756688fe) // revert InvalidNonce(); revert(0x1c, 0x04) } // if not used mark as used /// @dev not cleaning all the bits, just setting the first bit to 1 sstore(dataSlot, 0x01) } // _setApprovals(approvals); if (approvals.length > 0) { for (uint256 i = 0; i < approvals.length; i++) { (address token, address spender, uint256 amount) = abi.decode( approvals[i], (address, address, uint256) ); SafeTransferLib.safeApprove(token, spender, amount); } } // if there are swap actions, they should be equal to the number of fulfilExecs assembly { // if (swapActions.length > 0 && swapActions.length != fulfilExecs.length) if and( gt(swapActions.length, 0), iszero( eq( swapActions.length, // swapActions is calldata mload(fulfilExecs) // fulfilExecs is memory ) ) ) { mstore(0x00, 0x91433bb2) // revert InvalidSwapActions() revert(0x1c, 0x04) } } for (uint256 i = 0; i < swapActions.length; i++) { SwapAction calldata swapAction = swapActions[i]; // check pre balance bool isNativeToken = fulfilExecs[swapAction.fulfilExecIndex].request.basicReq.outputToken == NATIVE_TOKEN_ADDRESS; uint256 beforeBalance = isNativeToken ? address(this).balance : ERC20(fulfilExecs[swapAction.fulfilExecIndex].request.basicReq.outputToken).balanceOf(address(this)); // Perform swap action bool success = _performAction(swapAction.swapActionData); if (!success) { revert SwapActionFailed(i); } // check post balance uint256 swapActionOutput = isNativeToken ? address(this).balance - beforeBalance : ERC20(fulfilExecs[swapAction.fulfilExecIndex].request.basicReq.outputToken).balanceOf(address(this)) - beforeBalance; if (fulfilExecs[swapAction.fulfilExecIndex].fulfilAmount > swapActionOutput) { revert SwapOutputInsufficient(i); } // Overwrite the fulfilAmount with the received amount fulfilExecs[swapAction.fulfilExecIndex].fulfilAmount = swapActionOutput; // also update fulfilExec.msgValue & value sent to gateway if native token if (isNativeToken) { // update total value based on the difference bw old and new balance value = value + (swapActionOutput - fulfilExecs[swapAction.fulfilExecIndex].msgValue); // new value - old value fulfilExecs[swapAction.fulfilExecIndex].msgValue = fulfilExecs[swapAction.fulfilExecIndex].fulfilAmount; } } // perform fulfilment BungeeGateway(payable(bungeeGateway)).executeSOR{value: value}( // @todo can replace encodeCall maybe for savings abi.encodeCall(SingleOutputRequestImpl.fulfilRequests, (fulfilExecs)) ); } function performActions( uint256 nonce, bytes[] calldata approvals, Action[] calldata actions, bytes calldata signature ) external { verifySignature(keccak256(abi.encode(block.chainid, address(this), nonce, approvals, actions)), signature); // verify nonce assembly { // load data slot from mapping mstore(0, nonce) mstore(0x20, nonceUsed.slot) let dataSlot := keccak256(0, 0x40) // check if nonce is used if and(sload(dataSlot), 0xff) { mstore(0x00, 0x756688fe) // revert InvalidNonce(); revert(0x1c, 0x04) } // if not used mark as used /// @dev not cleaning all the bits, just setting the first bit to 1 sstore(dataSlot, 0x01) } // _setApprovals(approvals); if (approvals.length > 0) { for (uint256 i = 0; i < approvals.length; i++) { (address token, address spender, uint256 amount) = abi.decode( approvals[i], (address, address, uint256) ); SafeTransferLib.safeApprove(token, spender, amount); } } for (uint256 i = 0; i < actions.length; i++) { bool success = _performAction(actions[i]); if (!success) { // TODO: should we bubble up the revert reasons? slightly hard to debug. need to run the txn with traces revert ActionsFailed(i); } } } /// @dev Does not revert on failure. Caller should check the return value. function _performAction(Action calldata action) internal returns (bool success) { assembly { // Load the data offset and length from the calldata let action_dataLength := calldataload(add(action, 96)) // load calldata to memory to use for call() let freeMemPtr := mload(64) calldatacopy( freeMemPtr, add( add(action, 32), calldataload(add(action, 64)) // action_dataOffset - offset of action.data data part ), // action_dataStart - start of action.data data part action_dataLength ) // Perform the call success := call( gas(), // Forward all available gas calldataload(action), // Target address - first 32 bytes starting from action offset calldataload(add(action, 32)), // call value to send - second 32 bytes starting from action offset freeMemPtr, // Input data start action_dataLength, // Input data length 0, // Output data start (not needed) 0 // Output data length (not needed) ) } } function verifySignature(bytes32 messageHash, bytes calldata signature) public view { if (!(SOLVER_SIGNER == AuthenticationLib.authenticate(messageHash, signature))) { assembly { mstore(0x00, 0x815e1d64) // revert InvalidSigner(); revert(0x1c, 0x04) } } } /*////////////////////////////////////////////////////////////// ADMIN FUNCTIONS //////////////////////////////////////////////////////////////*/ /** * @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 onlyOwner { RescueFundsLib.rescueFunds(token_, rescueTo_, amount_); } /*////////////////////////////////////////////////////////////// RECEIVE ETHER //////////////////////////////////////////////////////////////*/ receive() external payable {} fallback() external payable {} }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; interface IEIP712 { function DOMAIN_SEPARATOR() external view returns (bytes32); }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; import {IEIP712} from "./IEIP712.sol"; /// @title SignatureTransfer /// @notice Handles ERC20 token transfers through signature based actions /// @dev Requires user's token approval on the Permit2 contract interface ISignatureTransfer is IEIP712 { /// @notice Thrown when the requested amount for a transfer is larger than the permissioned amount /// @param maxAmount The maximum amount a spender can request to transfer error InvalidAmount(uint256 maxAmount); /// @notice Thrown when the number of tokens permissioned to a spender does not match the number of tokens being transferred /// @dev If the spender does not need to transfer the number of tokens permitted, the spender can request amount 0 to be transferred error LengthMismatch(); /// @notice Emits an event when the owner successfully invalidates an unordered nonce. event UnorderedNonceInvalidation( address indexed owner, uint256 word, uint256 mask ); /// @notice The token and amount details for a transfer signed in the permit transfer signature struct TokenPermissions { // ERC20 token address address token; // the maximum amount that can be spent uint256 amount; } /// @notice The signed permit message for a single token transfer struct PermitTransferFrom { TokenPermissions permitted; // a unique value for every token owner's signature to prevent signature replays uint256 nonce; // deadline on the permit signature uint256 deadline; } /// @notice Specifies the recipient address and amount for batched transfers. /// @dev Recipients and amounts correspond to the index of the signed token permissions array. /// @dev Reverts if the requested amount is greater than the permitted signed amount. struct SignatureTransferDetails { // recipient address address to; // spender requested amount uint256 requestedAmount; } /// @notice Used to reconstruct the signed permit message for multiple token transfers /// @dev Do not need to pass in spender address as it is required that it is msg.sender /// @dev Note that a user still signs over a spender address struct PermitBatchTransferFrom { // the tokens and corresponding amounts permitted for a transfer TokenPermissions[] permitted; // a unique value for every token owner's signature to prevent signature replays uint256 nonce; // deadline on the permit signature uint256 deadline; } /// @notice A map from token owner address and a caller specified word index to a bitmap. Used to set bits in the bitmap to prevent against signature replay protection /// @dev Uses unordered nonces so that permit messages do not need to be spent in a certain order /// @dev The mapping is indexed first by the token owner, then by an index specified in the nonce /// @dev It returns a uint256 bitmap /// @dev The index, or wordPosition is capped at type(uint248).max function nonceBitmap(address, uint256) external view returns (uint256); /// @notice Transfers a token using a signed permit message /// @dev Reverts if the requested amount is greater than the permitted signed amount /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails The spender's requested transfer details for the permitted token /// @param signature The signature to verify function permitTransferFrom( PermitTransferFrom memory permit, SignatureTransferDetails calldata transferDetails, address owner, bytes calldata signature ) external; /// @notice Transfers a token using a signed permit message /// @notice Includes extra data provided by the caller to verify signature over /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition /// @dev Reverts if the requested amount is greater than the permitted signed amount /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails The spender's requested transfer details for the permitted token /// @param witness Extra data to include when checking the user signature /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash /// @param signature The signature to verify function permitWitnessTransferFrom( PermitTransferFrom memory permit, SignatureTransferDetails calldata transferDetails, address owner, bytes32 witness, string calldata witnessTypeString, bytes calldata signature ) external; /// @notice Transfers multiple tokens using a signed permit message /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails Specifies the recipient and requested amount for the token transfer /// @param signature The signature to verify function permitTransferFrom( PermitBatchTransferFrom memory permit, SignatureTransferDetails[] calldata transferDetails, address owner, bytes calldata signature ) external; /// @notice Transfers multiple tokens using a signed permit message /// @dev The witness type string must follow EIP712 ordering of nested structs and must include the TokenPermissions type definition /// @notice Includes extra data provided by the caller to verify signature over /// @param permit The permit data signed over by the owner /// @param owner The owner of the tokens to transfer /// @param transferDetails Specifies the recipient and requested amount for the token transfer /// @param witness Extra data to include when checking the user signature /// @param witnessTypeString The EIP-712 type definition for remaining string stub of the typehash /// @param signature The signature to verify function permitWitnessTransferFrom( PermitBatchTransferFrom memory permit, SignatureTransferDetails[] calldata transferDetails, address owner, bytes32 witness, string calldata witnessTypeString, bytes calldata signature ) external; /// @notice Invalidates the bits specified in mask for the bitmap at the word position /// @dev The wordPos is maxed at type(uint248).max /// @param wordPos A number to index the nonceBitmap at /// @param mask A bitmap masked against msg.sender's current bitmap at the word position function invalidateUnorderedNonces(uint256 wordPos, uint256 mask) external; }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Simple ERC20 + EIP-2612 implementation. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol) /// /// @dev Note: /// - The ERC20 standard allows minting and transferring to and from the zero address, /// minting and transferring zero tokens, as well as self-approvals. /// For performance, this implementation WILL NOT revert for such actions. /// Please add any checks with overrides if desired. /// - The `permit` function uses the ecrecover precompile (0x1). /// /// If you are overriding: /// - NEVER violate the ERC20 invariant: /// the total sum of all balances must be equal to `totalSupply()`. /// - Check that the overridden function is actually used in the function you want to /// change the behavior of. Much of the code has been manually inlined for performance. abstract contract ERC20 { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The total supply has overflowed. error TotalSupplyOverflow(); /// @dev The allowance has overflowed. error AllowanceOverflow(); /// @dev The allowance has underflowed. error AllowanceUnderflow(); /// @dev Insufficient balance. error InsufficientBalance(); /// @dev Insufficient allowance. error InsufficientAllowance(); /// @dev The permit is invalid. error InvalidPermit(); /// @dev The permit has expired. error PermitExpired(); /// @dev The allowance of Permit2 is fixed at infinity. error Permit2AllowanceIsFixedAtInfinity(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EVENTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Emitted when `amount` tokens is transferred from `from` to `to`. event Transfer(address indexed from, address indexed to, uint256 amount); /// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`. event Approval(address indexed owner, address indexed spender, uint256 amount); /// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`. uint256 private constant _TRANSFER_EVENT_SIGNATURE = 0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef; /// @dev `keccak256(bytes("Approval(address,address,uint256)"))`. uint256 private constant _APPROVAL_EVENT_SIGNATURE = 0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* STORAGE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The storage slot for the total supply. uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c; /// @dev The balance slot of `owner` is given by: /// ``` /// mstore(0x0c, _BALANCE_SLOT_SEED) /// mstore(0x00, owner) /// let balanceSlot := keccak256(0x0c, 0x20) /// ``` uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2; /// @dev The allowance slot of (`owner`, `spender`) is given by: /// ``` /// mstore(0x20, spender) /// mstore(0x0c, _ALLOWANCE_SLOT_SEED) /// mstore(0x00, owner) /// let allowanceSlot := keccak256(0x0c, 0x34) /// ``` uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20; /// @dev The nonce slot of `owner` is given by: /// ``` /// mstore(0x0c, _NONCES_SLOT_SEED) /// mstore(0x00, owner) /// let nonceSlot := keccak256(0x0c, 0x20) /// ``` uint256 private constant _NONCES_SLOT_SEED = 0x38377508; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`. uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901; /// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`. bytes32 private constant _DOMAIN_TYPEHASH = 0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f; /// @dev `keccak256("1")`. /// If you need to use a different version, override `_versionHash`. bytes32 private constant _DEFAULT_VERSION_HASH = 0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6; /// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`. bytes32 private constant _PERMIT_TYPEHASH = 0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9; /// @dev The canonical Permit2 address. /// For signature-based allowance granting for single transaction ERC20 `transferFrom`. /// To enable, override `_givePermit2InfiniteAllowance()`. /// [Github](https://github.com/Uniswap/permit2) /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC20 METADATA */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the name of the token. function name() public view virtual returns (string memory); /// @dev Returns the symbol of the token. function symbol() public view virtual returns (string memory); /// @dev Returns the decimals places of the token. function decimals() public view virtual returns (uint8) { return 18; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC20 */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns the amount of tokens in existence. function totalSupply() public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { result := sload(_TOTAL_SUPPLY_SLOT) } } /// @dev Returns the amount of tokens owned by `owner`. function balanceOf(address owner) public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { mstore(0x0c, _BALANCE_SLOT_SEED) mstore(0x00, owner) result := sload(keccak256(0x0c, 0x20)) } } /// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`. function allowance(address owner, address spender) public view virtual returns (uint256 result) { if (_givePermit2InfiniteAllowance()) { if (spender == _PERMIT2) return type(uint256).max; } /// @solidity memory-safe-assembly assembly { mstore(0x20, spender) mstore(0x0c, _ALLOWANCE_SLOT_SEED) mstore(0x00, owner) result := sload(keccak256(0x0c, 0x34)) } } /// @dev Sets `amount` as the allowance of `spender` over the caller's tokens. /// /// Emits a {Approval} event. function approve(address spender, uint256 amount) public virtual returns (bool) { if (_givePermit2InfiniteAllowance()) { /// @solidity memory-safe-assembly assembly { // If `spender == _PERMIT2 && amount != type(uint256).max`. if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) { mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`. revert(0x1c, 0x04) } } } /// @solidity memory-safe-assembly assembly { // Compute the allowance slot and store the amount. mstore(0x20, spender) mstore(0x0c, _ALLOWANCE_SLOT_SEED) mstore(0x00, caller()) sstore(keccak256(0x0c, 0x34), amount) // Emit the {Approval} event. mstore(0x00, amount) log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c))) } return true; } /// @dev Transfer `amount` tokens from the caller to `to`. /// /// Requirements: /// - `from` must at least have `amount`. /// /// Emits a {Transfer} event. function transfer(address to, uint256 amount) public virtual returns (bool) { _beforeTokenTransfer(msg.sender, to, amount); /// @solidity memory-safe-assembly assembly { // Compute the balance slot and load its value. mstore(0x0c, _BALANCE_SLOT_SEED) mstore(0x00, caller()) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Compute the balance slot of `to`. mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance of `to`. // Will not overflow because the sum of all user balances // cannot exceed the maximum uint256 value. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) } _afterTokenTransfer(msg.sender, to, amount); return true; } /// @dev Transfers `amount` tokens from `from` to `to`. /// /// Note: Does not update the allowance if it is the maximum uint256 value. /// /// Requirements: /// - `from` must at least have `amount`. /// - The caller must have at least `amount` of allowance to transfer the tokens of `from`. /// /// Emits a {Transfer} event. function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { _beforeTokenTransfer(from, to, amount); // Code duplication is for zero-cost abstraction if possible. if (_givePermit2InfiniteAllowance()) { /// @solidity memory-safe-assembly assembly { let from_ := shl(96, from) if iszero(eq(caller(), _PERMIT2)) { // Compute the allowance slot and load its value. mstore(0x20, caller()) mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) let allowanceSlot := keccak256(0x0c, 0x34) let allowance_ := sload(allowanceSlot) // If the allowance is not the maximum uint256 value. if not(allowance_) { // Revert if the amount to be transferred exceeds the allowance. if gt(amount, allowance_) { mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. revert(0x1c, 0x04) } // Subtract and store the updated allowance. sstore(allowanceSlot, sub(allowance_, amount)) } } // Compute the balance slot and load its value. mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Compute the balance slot of `to`. mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance of `to`. // Will not overflow because the sum of all user balances // cannot exceed the maximum uint256 value. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) } } else { /// @solidity memory-safe-assembly assembly { let from_ := shl(96, from) // Compute the allowance slot and load its value. mstore(0x20, caller()) mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED)) let allowanceSlot := keccak256(0x0c, 0x34) let allowance_ := sload(allowanceSlot) // If the allowance is not the maximum uint256 value. if not(allowance_) { // Revert if the amount to be transferred exceeds the allowance. if gt(amount, allowance_) { mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. revert(0x1c, 0x04) } // Subtract and store the updated allowance. sstore(allowanceSlot, sub(allowance_, amount)) } // Compute the balance slot and load its value. mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Compute the balance slot of `to`. mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance of `to`. // Will not overflow because the sum of all user balances // cannot exceed the maximum uint256 value. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) } } _afterTokenTransfer(from, to, amount); return true; } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* EIP-2612 */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev For more performance, override to return the constant value /// of `keccak256(bytes(name()))` if `name()` will never change. function _constantNameHash() internal view virtual returns (bytes32 result) {} /// @dev If you need a different value, override this function. function _versionHash() internal view virtual returns (bytes32 result) { result = _DEFAULT_VERSION_HASH; } /// @dev For inheriting contracts to increment the nonce. function _incrementNonce(address owner) internal virtual { /// @solidity memory-safe-assembly assembly { mstore(0x0c, _NONCES_SLOT_SEED) mstore(0x00, owner) let nonceSlot := keccak256(0x0c, 0x20) sstore(nonceSlot, add(1, sload(nonceSlot))) } } /// @dev Returns the current nonce for `owner`. /// This value is used to compute the signature for EIP-2612 permit. function nonces(address owner) public view virtual returns (uint256 result) { /// @solidity memory-safe-assembly assembly { // Compute the nonce slot and load its value. mstore(0x0c, _NONCES_SLOT_SEED) mstore(0x00, owner) result := sload(keccak256(0x0c, 0x20)) } } /// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`, /// authorized by a signed approval by `owner`. /// /// Emits a {Approval} event. function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { if (_givePermit2InfiniteAllowance()) { /// @solidity memory-safe-assembly assembly { // If `spender == _PERMIT2 && value != type(uint256).max`. if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(value)))) { mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`. revert(0x1c, 0x04) } } } bytes32 nameHash = _constantNameHash(); // We simply calculate it on-the-fly to allow for cases where the `name` may change. if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name())); bytes32 versionHash = _versionHash(); /// @solidity memory-safe-assembly assembly { // Revert if the block timestamp is greater than `deadline`. if gt(timestamp(), deadline) { mstore(0x00, 0x1a15a3cc) // `PermitExpired()`. revert(0x1c, 0x04) } let m := mload(0x40) // Grab the free memory pointer. // Clean the upper 96 bits. owner := shr(96, shl(96, owner)) spender := shr(96, shl(96, spender)) // Compute the nonce slot and load its value. mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX) mstore(0x00, owner) let nonceSlot := keccak256(0x0c, 0x20) let nonceValue := sload(nonceSlot) // Prepare the domain separator. mstore(m, _DOMAIN_TYPEHASH) mstore(add(m, 0x20), nameHash) mstore(add(m, 0x40), versionHash) mstore(add(m, 0x60), chainid()) mstore(add(m, 0x80), address()) mstore(0x2e, keccak256(m, 0xa0)) // Prepare the struct hash. mstore(m, _PERMIT_TYPEHASH) mstore(add(m, 0x20), owner) mstore(add(m, 0x40), spender) mstore(add(m, 0x60), value) mstore(add(m, 0x80), nonceValue) mstore(add(m, 0xa0), deadline) mstore(0x4e, keccak256(m, 0xc0)) // Prepare the ecrecover calldata. mstore(0x00, keccak256(0x2c, 0x42)) mstore(0x20, and(0xff, v)) mstore(0x40, r) mstore(0x60, s) let t := staticcall(gas(), 1, 0x00, 0x80, 0x20, 0x20) // If the ecrecover fails, the returndatasize will be 0x00, // `owner` will be checked if it equals the hash at 0x00, // which evaluates to false (i.e. 0), and we will revert. // If the ecrecover succeeds, the returndatasize will be 0x20, // `owner` will be compared against the returned address at 0x20. if iszero(eq(mload(returndatasize()), owner)) { mstore(0x00, 0xddafbaef) // `InvalidPermit()`. revert(0x1c, 0x04) } // Increment and store the updated nonce. sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds. // Compute the allowance slot and store the value. // The `owner` is already at slot 0x20. mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender)) sstore(keccak256(0x2c, 0x34), value) // Emit the {Approval} event. log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender) mstore(0x40, m) // Restore the free memory pointer. mstore(0x60, 0) // Restore the zero pointer. } } /// @dev Returns the EIP-712 domain separator for the EIP-2612 permit. function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) { bytes32 nameHash = _constantNameHash(); // We simply calculate it on-the-fly to allow for cases where the `name` may change. if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name())); bytes32 versionHash = _versionHash(); /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Grab the free memory pointer. mstore(m, _DOMAIN_TYPEHASH) mstore(add(m, 0x20), nameHash) mstore(add(m, 0x40), versionHash) mstore(add(m, 0x60), chainid()) mstore(add(m, 0x80), address()) result := keccak256(m, 0xa0) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL MINT FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Mints `amount` tokens to `to`, increasing the total supply. /// /// Emits a {Transfer} event. function _mint(address to, uint256 amount) internal virtual { _beforeTokenTransfer(address(0), to, amount); /// @solidity memory-safe-assembly assembly { let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT) let totalSupplyAfter := add(totalSupplyBefore, amount) // Revert if the total supply overflows. if lt(totalSupplyAfter, totalSupplyBefore) { mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`. revert(0x1c, 0x04) } // Store the updated total supply. sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter) // Compute the balance slot and load its value. mstore(0x0c, _BALANCE_SLOT_SEED) mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c))) } _afterTokenTransfer(address(0), to, amount); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL BURN FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Burns `amount` tokens from `from`, reducing the total supply. /// /// Emits a {Transfer} event. function _burn(address from, uint256 amount) internal virtual { _beforeTokenTransfer(from, address(0), amount); /// @solidity memory-safe-assembly assembly { // Compute the balance slot and load its value. mstore(0x0c, _BALANCE_SLOT_SEED) mstore(0x00, from) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Subtract and store the updated total supply. sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount)) // Emit the {Transfer} event. mstore(0x00, amount) log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0) } _afterTokenTransfer(from, address(0), amount); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL TRANSFER FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Moves `amount` of tokens from `from` to `to`. function _transfer(address from, address to, uint256 amount) internal virtual { _beforeTokenTransfer(from, to, amount); /// @solidity memory-safe-assembly assembly { let from_ := shl(96, from) // Compute the balance slot and load its value. mstore(0x0c, or(from_, _BALANCE_SLOT_SEED)) let fromBalanceSlot := keccak256(0x0c, 0x20) let fromBalance := sload(fromBalanceSlot) // Revert if insufficient balance. if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`. revert(0x1c, 0x04) } // Subtract and store the updated balance. sstore(fromBalanceSlot, sub(fromBalance, amount)) // Compute the balance slot of `to`. mstore(0x00, to) let toBalanceSlot := keccak256(0x0c, 0x20) // Add and store the updated balance of `to`. // Will not overflow because the sum of all user balances // cannot exceed the maximum uint256 value. sstore(toBalanceSlot, add(sload(toBalanceSlot), amount)) // Emit the {Transfer} event. mstore(0x20, amount) log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c))) } _afterTokenTransfer(from, to, amount); } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* INTERNAL ALLOWANCE FUNCTIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Updates the allowance of `owner` for `spender` based on spent `amount`. function _spendAllowance(address owner, address spender, uint256 amount) internal virtual { if (_givePermit2InfiniteAllowance()) { if (spender == _PERMIT2) return; // Do nothing, as allowance is infinite. } /// @solidity memory-safe-assembly assembly { // Compute the allowance slot and load its value. mstore(0x20, spender) mstore(0x0c, _ALLOWANCE_SLOT_SEED) mstore(0x00, owner) let allowanceSlot := keccak256(0x0c, 0x34) let allowance_ := sload(allowanceSlot) // If the allowance is not the maximum uint256 value. if not(allowance_) { // Revert if the amount to be transferred exceeds the allowance. if gt(amount, allowance_) { mstore(0x00, 0x13be252b) // `InsufficientAllowance()`. revert(0x1c, 0x04) } // Subtract and store the updated allowance. sstore(allowanceSlot, sub(allowance_, amount)) } } } /// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`. /// /// Emits a {Approval} event. function _approve(address owner, address spender, uint256 amount) internal virtual { if (_givePermit2InfiniteAllowance()) { /// @solidity memory-safe-assembly assembly { // If `spender == _PERMIT2 && amount != type(uint256).max`. if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) { mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`. revert(0x1c, 0x04) } } } /// @solidity memory-safe-assembly assembly { let owner_ := shl(96, owner) // Compute the allowance slot and store the amount. mstore(0x20, spender) mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED)) sstore(keccak256(0x0c, 0x34), amount) // Emit the {Approval} event. mstore(0x00, amount) log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c))) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* HOOKS TO OVERRIDE */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Hook that is called before any transfer of tokens. /// This includes minting and burning. function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {} /// @dev Hook that is called after any transfer of tokens. /// This includes minting and burning. function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {} /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* PERMIT2 */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Returns whether to fix the Permit2 contract's allowance at infinity. /// /// This value should be kept constant after contract initialization, /// or else the actual allowance values may not match with the {Approval} events. /// For best performance, return a compile-time constant for zero-cost abstraction. function _givePermit2InfiniteAllowance() internal view virtual returns (bool) { return false; } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) /// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol) /// /// @dev Note: /// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection. library SafeTransferLib { /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CUSTOM ERRORS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev The ETH transfer has failed. error ETHTransferFailed(); /// @dev The ERC20 `transferFrom` has failed. error TransferFromFailed(); /// @dev The ERC20 `transfer` has failed. error TransferFailed(); /// @dev The ERC20 `approve` has failed. error ApproveFailed(); /// @dev The Permit2 operation has failed. error Permit2Failed(); /// @dev The Permit2 amount must be less than `2**160 - 1`. error Permit2AmountOverflow(); /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* CONSTANTS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes. uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300; /// @dev Suggested gas stipend for contract receiving ETH to perform a few /// storage reads and writes, but low enough to prevent griefing. uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000; /// @dev The unique EIP-712 domain domain separator for the DAI token contract. bytes32 internal constant DAI_DOMAIN_SEPARATOR = 0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7; /// @dev The address for the WETH9 contract on Ethereum mainnet. address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2; /// @dev The canonical Permit2 address. /// [Github](https://github.com/Uniswap/permit2) /// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3) address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3; /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ETH OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ // If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants. // // The regular variants: // - Forwards all remaining gas to the target. // - Reverts if the target reverts. // - Reverts if the current contract has insufficient balance. // // The force variants: // - Forwards with an optional gas stipend // (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases). // - If the target reverts, or if the gas stipend is exhausted, // creates a temporary contract to force send the ETH via `SELFDESTRUCT`. // Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758. // - Reverts if the current contract has insufficient balance. // // The try variants: // - Forwards with a mandatory gas stipend. // - Instead of reverting, returns whether the transfer succeeded. /// @dev Sends `amount` (in wei) ETH to `to`. function safeTransferETH(address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } } } /// @dev Sends all the ETH in the current contract to `to`. function safeTransferAllETH(address to) internal { /// @solidity memory-safe-assembly assembly { // Transfer all the ETH and check if it succeeded or not. if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } } } /// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`. function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal { /// @solidity memory-safe-assembly assembly { if lt(selfbalance(), amount) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } if iszero(call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`. function forceSafeTransferAllETH(address to, uint256 gasStipend) internal { /// @solidity memory-safe-assembly assembly { if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`. function forceSafeTransferETH(address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { if lt(selfbalance(), amount) { mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`. revert(0x1c, 0x04) } if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`. function forceSafeTransferAllETH(address to) internal { /// @solidity memory-safe-assembly assembly { // forgefmt: disable-next-item if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, to) // Store the address in scratch space. mstore8(0x0b, 0x73) // Opcode `PUSH20`. mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`. if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation. } } } /// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`. function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00) } } /// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`. function trySafeTransferAllETH(address to, uint256 gasStipend) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00) } } /*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/ /* ERC20 OPERATIONS */ /*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/ /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. /// Reverts upon failure. /// /// The `from` account must have at least `amount` approved for /// the current contract to manage. function safeTransferFrom(address token, address from, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x60, amount) // Store the `amount` argument. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. /// /// The `from` account must have at least `amount` approved for the current contract to manage. function trySafeTransferFrom(address token, address from, address to, uint256 amount) internal returns (bool success) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x60, amount) // Store the `amount` argument. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`. success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { success := lt(or(iszero(extcodesize(token)), returndatasize()), success) } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends all of ERC20 `token` from `from` to `to`. /// Reverts upon failure. /// /// The `from` account must have their entire balance approved for the current contract to manage. function safeTransferAllFrom(address token, address from, address to) internal returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) // Cache the free memory pointer. mstore(0x40, to) // Store the `to` argument. mstore(0x2c, shl(96, from)) // Store the `from` argument. mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`. // Read the balance, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20) ) ) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } mstore(0x00, 0x23b872dd) // `transferFrom(address,address,uint256)`. amount := mload(0x60) // The `amount` is already at 0x60. We'll need to return it. // Perform the transfer, reverting upon failure. let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x7939f424) // `TransferFromFailed()`. revert(0x1c, 0x04) } } mstore(0x60, 0) // Restore the zero slot to zero. mstore(0x40, m) // Restore the free memory pointer. } } /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransfer(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. // Perform the transfer, reverting upon failure. let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sends all of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransferAll(address token, address to) internal returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x00, 0x70a08231) // Store the function selector of `balanceOf(address)`. mstore(0x20, address()) // Store the address of the current contract. // Read the balance, reverting upon failure. if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x1c, 0x24, 0x34, 0x20) ) ) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } mstore(0x14, to) // Store the `to` argument. amount := mload(0x34) // The `amount` is already at 0x34. We'll need to return it. mstore(0x00, 0xa9059cbb000000000000000000000000) // `transfer(address,uint256)`. // Perform the transfer, reverting upon failure. let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x90b8ec18) // `TransferFailed()`. revert(0x1c, 0x04) } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. /// Reverts upon failure. function safeApprove(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. revert(0x1c, 0x04) } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Sets `amount` of ERC20 `token` for `to` to manage on behalf of the current contract. /// If the initial attempt to approve fails, attempts to reset the approved amount to zero, /// then retries the approval again (some tokens, e.g. USDT, requires this). /// Reverts upon failure. function safeApproveWithRetry(address token, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { mstore(0x14, to) // Store the `to` argument. mstore(0x34, amount) // Store the `amount` argument. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. // Perform the approval, retrying upon failure. let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x34, 0) // Store 0 for the `amount`. mstore(0x00, 0x095ea7b3000000000000000000000000) // `approve(address,uint256)`. pop(call(gas(), token, 0, 0x10, 0x44, codesize(), 0x00)) // Reset the approval. mstore(0x34, amount) // Store back the original `amount`. // Retry the approval, reverting upon failure. success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { // Check the `extcodesize` again just in case the token selfdestructs lol. if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x3e3f8f73) // `ApproveFailed()`. revert(0x1c, 0x04) } } } } mstore(0x34, 0) // Restore the part of the free memory pointer that was overwritten. } } /// @dev Returns the amount of ERC20 `token` owned by `account`. /// Returns zero if the `token` does not exist. function balanceOf(address token, address account) internal view returns (uint256 amount) { /// @solidity memory-safe-assembly assembly { mstore(0x14, account) // Store the `account` argument. mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`. amount := mul( // The arguments of `mul` are evaluated from right to left. mload(0x20), and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x1f), // At least 32 bytes returned. staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20) ) ) } } /// @dev Sends `amount` of ERC20 `token` from `from` to `to`. /// If the initial attempt fails, try to use Permit2 to transfer the token. /// Reverts upon failure. /// /// The `from` account must have at least `amount` approved for the current contract to manage. function safeTransferFrom2(address token, address from, address to, uint256 amount) internal { if (!trySafeTransferFrom(token, from, to, amount)) { permit2TransferFrom(token, from, to, amount); } } /// @dev Sends `amount` of ERC20 `token` from `from` to `to` via Permit2. /// Reverts upon failure. function permit2TransferFrom(address token, address from, address to, uint256 amount) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) mstore(add(m, 0x74), shr(96, shl(96, token))) mstore(add(m, 0x54), amount) mstore(add(m, 0x34), to) mstore(add(m, 0x20), shl(96, from)) // `transferFrom(address,address,uint160,address)`. mstore(m, 0x36c78516000000000000000000000000) let p := PERMIT2 let exists := eq(chainid(), 1) if iszero(exists) { exists := iszero(iszero(extcodesize(p))) } if iszero( and( call(gas(), p, 0, add(m, 0x10), 0x84, codesize(), 0x00), lt(iszero(extcodesize(token)), exists) // Token has code and Permit2 exists. ) ) { mstore(0x00, 0x7939f4248757f0fd) // `TransferFromFailed()` or `Permit2AmountOverflow()`. revert(add(0x18, shl(2, iszero(iszero(shr(160, amount))))), 0x04) } } } /// @dev Permit a user to spend a given amount of /// another user's tokens via native EIP-2612 permit if possible, falling /// back to Permit2 if native permit fails or is not implemented on the token. function permit2( address token, address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { bool success; /// @solidity memory-safe-assembly assembly { for {} shl(96, xor(token, WETH9)) {} { mstore(0x00, 0x3644e515) // `DOMAIN_SEPARATOR()`. if iszero( and( // The arguments of `and` are evaluated from right to left. lt(iszero(mload(0x00)), eq(returndatasize(), 0x20)), // Returns 1 non-zero word. // Gas stipend to limit gas burn for tokens that don't refund gas when // an non-existing function is called. 5K should be enough for a SLOAD. staticcall(5000, token, 0x1c, 0x04, 0x00, 0x20) ) ) { break } // After here, we can be sure that token is a contract. let m := mload(0x40) mstore(add(m, 0x34), spender) mstore(add(m, 0x20), shl(96, owner)) mstore(add(m, 0x74), deadline) if eq(mload(0x00), DAI_DOMAIN_SEPARATOR) { mstore(0x14, owner) mstore(0x00, 0x7ecebe00000000000000000000000000) // `nonces(address)`. mstore(add(m, 0x94), staticcall(gas(), token, 0x10, 0x24, add(m, 0x54), 0x20)) mstore(m, 0x8fcbaf0c000000000000000000000000) // `IDAIPermit.permit`. // `nonces` is already at `add(m, 0x54)`. // `1` is already stored at `add(m, 0x94)`. mstore(add(m, 0xb4), and(0xff, v)) mstore(add(m, 0xd4), r) mstore(add(m, 0xf4), s) success := call(gas(), token, 0, add(m, 0x10), 0x104, codesize(), 0x00) break } mstore(m, 0xd505accf000000000000000000000000) // `IERC20Permit.permit`. mstore(add(m, 0x54), amount) mstore(add(m, 0x94), and(0xff, v)) mstore(add(m, 0xb4), r) mstore(add(m, 0xd4), s) success := call(gas(), token, 0, add(m, 0x10), 0xe4, codesize(), 0x00) break } } if (!success) simplePermit2(token, owner, spender, amount, deadline, v, r, s); } /// @dev Simple permit on the Permit2 contract. function simplePermit2( address token, address owner, address spender, uint256 amount, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) internal { /// @solidity memory-safe-assembly assembly { let m := mload(0x40) mstore(m, 0x927da105) // `allowance(address,address,address)`. { let addressMask := shr(96, not(0)) mstore(add(m, 0x20), and(addressMask, owner)) mstore(add(m, 0x40), and(addressMask, token)) mstore(add(m, 0x60), and(addressMask, spender)) mstore(add(m, 0xc0), and(addressMask, spender)) } let p := mul(PERMIT2, iszero(shr(160, amount))) if iszero( and( // The arguments of `and` are evaluated from right to left. gt(returndatasize(), 0x5f), // Returns 3 words: `amount`, `expiration`, `nonce`. staticcall(gas(), p, add(m, 0x1c), 0x64, add(m, 0x60), 0x60) ) ) { mstore(0x00, 0x6b836e6b8757f0fd) // `Permit2Failed()` or `Permit2AmountOverflow()`. revert(add(0x18, shl(2, iszero(p))), 0x04) } mstore(m, 0x2b67b570) // `Permit2.permit` (PermitSingle variant). // `owner` is already `add(m, 0x20)`. // `token` is already at `add(m, 0x40)`. mstore(add(m, 0x60), amount) mstore(add(m, 0x80), 0xffffffffffff) // `expiration = type(uint48).max`. // `nonce` is already at `add(m, 0xa0)`. // `spender` is already at `add(m, 0xc0)`. mstore(add(m, 0xe0), deadline) mstore(add(m, 0x100), 0x100) // `signature` offset. mstore(add(m, 0x120), 0x41) // `signature` length. mstore(add(m, 0x140), r) mstore(add(m, 0x160), s) mstore(add(m, 0x180), shl(248, v)) if iszero( // Revert if token does not have code, or if the call fails. mul(extcodesize(token), call(gas(), p, 0, add(m, 0x1c), 0x184, codesize(), 0x00))) { mstore(0x00, 0x6b836e6b) // `Permit2Failed()`. revert(0x1c, 0x04) } } } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; /// @notice Modern and gas efficient ERC20 + EIP-2612 implementation. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol) /// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol) /// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it. abstract contract ERC20 { /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ event Transfer(address indexed from, address indexed to, uint256 amount); event Approval(address indexed owner, address indexed spender, uint256 amount); /*////////////////////////////////////////////////////////////// METADATA STORAGE //////////////////////////////////////////////////////////////*/ string public name; string public symbol; uint8 public immutable decimals; /*////////////////////////////////////////////////////////////// ERC20 STORAGE //////////////////////////////////////////////////////////////*/ uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; /*////////////////////////////////////////////////////////////// EIP-2612 STORAGE //////////////////////////////////////////////////////////////*/ uint256 internal immutable INITIAL_CHAIN_ID; bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR; mapping(address => uint256) public nonces; /*////////////////////////////////////////////////////////////// CONSTRUCTOR //////////////////////////////////////////////////////////////*/ constructor(string memory _name, string memory _symbol, uint8 _decimals) { name = _name; symbol = _symbol; decimals = _decimals; INITIAL_CHAIN_ID = block.chainid; INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator(); } /*////////////////////////////////////////////////////////////// ERC20 LOGIC //////////////////////////////////////////////////////////////*/ function approve(address spender, uint256 amount) public virtual returns (bool) { allowance[msg.sender][spender] = amount; emit Approval(msg.sender, spender, amount); return true; } function transfer(address to, uint256 amount) public virtual returns (bool) { balanceOf[msg.sender] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(msg.sender, to, amount); return true; } function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) { uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals. if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount; balanceOf[from] -= amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(from, to, amount); return true; } /*////////////////////////////////////////////////////////////// EIP-2612 LOGIC //////////////////////////////////////////////////////////////*/ function permit( address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s ) public virtual { require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED"); // Unchecked because the only math done is incrementing // the owner's nonce which cannot realistically overflow. unchecked { address recoveredAddress = ecrecover( keccak256( abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR(), keccak256( abi.encode( keccak256( "Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)" ), owner, spender, value, nonces[owner]++, deadline ) ) ) ), v, r, s ); require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER"); allowance[recoveredAddress][spender] = value; } emit Approval(owner, spender, value); } function DOMAIN_SEPARATOR() public view virtual returns (bytes32) { return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator(); } function computeDomainSeparator() internal view virtual returns (bytes32) { return keccak256( abi.encode( keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), keccak256(bytes(name)), keccak256("1"), block.chainid, address(this) ) ); } /*////////////////////////////////////////////////////////////// INTERNAL MINT/BURN LOGIC //////////////////////////////////////////////////////////////*/ function _mint(address to, uint256 amount) internal virtual { totalSupply += amount; // Cannot overflow because the sum of all user // balances can't exceed the max uint256 value. unchecked { balanceOf[to] += amount; } emit Transfer(address(0), to, amount); } function _burn(address from, uint256 amount) internal virtual { balanceOf[from] -= amount; // Cannot underflow because a user's balance // will never be larger than the total supply. unchecked { totalSupply -= amount; } emit Transfer(from, address(0), amount); } }
// SPDX-License-Identifier: AGPL-3.0-only pragma solidity >=0.8.0; import {ERC20} from "../tokens/ERC20.sol"; /// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values. /// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol) /// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer. /// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller. library SafeTransferLib { /*////////////////////////////////////////////////////////////// ETH OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferETH(address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Transfer the ETH and store if it succeeded or not. success := call(gas(), to, amount, 0, 0, 0, 0) } require(success, "ETH_TRANSFER_FAILED"); } /*////////////////////////////////////////////////////////////// ERC20 OPERATIONS //////////////////////////////////////////////////////////////*/ function safeTransferFrom(ERC20 token, address from, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(from, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "from" argument. mstore(add(freeMemoryPointer, 36), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 100, 0, 32) ) } require(success, "TRANSFER_FROM_FAILED"); } function safeTransfer(ERC20 token, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "TRANSFER_FAILED"); } function safeApprove(ERC20 token, address to, uint256 amount) internal { bool success; /// @solidity memory-safe-assembly assembly { // Get a pointer to some free memory. let freeMemoryPointer := mload(0x40) // Write the abi-encoded calldata into memory, beginning with the function selector. mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000) mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append and mask the "to" argument. mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument. Masking not required as it's a full 32 byte type. success := and( // Set success to whether the call reverted, if not we check it either // returned exactly 1 (can't just be non-zero data), or had no return data. or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())), // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2. // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space. // Counterintuitively, this call must be positioned second to the or() call in the // surrounding and() call or else returndatasize() will be zero during the computation. call(gas(), token, 0, freeMemoryPointer, 68, 0, 32) ) } require(success, "APPROVE_FAILED"); } }
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; // error MofaSignatureInvalid(); error InsufficientNativeAmount(); error InvalidMultipleNativeTokens(); error FulfilmentChainInvalid(); error RequestAlreadyFulfilled(); error RouterNotRegistered(); error TransferFailed(); error CallerNotBungeeGateway(); error NoExecutionCacheFound(); error ExecutionCacheFailed(); error SwapOutputInsufficient(); error UnsupportedDestinationChainId(); error MinOutputNotMet(); error OnlyOwner(); error OnlyNominee(); error InvalidRequest(); error FulfilmentDeadlineNotMet(); error CallerNotDelegate(); error BungeeSiblingDoesNotExist(); error InvalidMsg(); error NotDelegate(); error RequestProcessed(); error RequestNotProcessed(); error InvalidSwitchboard(); error PromisedAmountNotMet(); error MsgReceiveFailed(); error RouterAlreadyWhitelisted(); error InvalidStake(); error RouterAlreadyRegistered(); error InvalidFulfil(); error InsufficientCapacity(); error ReleaseFundsNotImplemented();
// SPDX-License-Identifier: MIT pragma solidity ^0.8.17; contract BungeeEvents { /// @notice Emitted when a request is extracted /// @param requestHash hash of the request /// @param transmitter address of the transmitter /// @param execution encoded execution data event RequestExtracted(bytes32 indexed requestHash, uint8 implId, address transmitter, bytes execution); /// @notice Emitted when a request is fulfilled /// @param requestHash hash of the request /// @param fulfiller address of the fulfiller /// @param execution encoded execution data event RequestFulfilled(bytes32 indexed requestHash, uint8 implId, address fulfiller, bytes execution); // emitted on the source once settlement completes /// @param requestHash hash of the request event RequestSettled(bytes32 indexed requestHash); // emitted on the destination once settlement completes event RequestsSettledOnDestination( bytes32[] requestHashes, uint8 implId, address transmitter, uint256 outboundFees ); /// @notice Emitted on the originChain when a request is withdrawn beyond fulfilment deadline /// @param requestHash hash of the request /// @param token token being withdrawn /// @param amount amount being withdrawn /// @param to address of the recipient event WithdrawOnOrigin(bytes32 indexed requestHash, address token, uint256 amount, address to); /// @notice Emitted on the destinationChain when a request is withdrawn if transmitter fails to fulfil /// @param requestHash hash of the request /// @param token token being withdrawn /// @param amount amount being withdrawn /// @param to address of the recipient event WithdrawOnDestination(bytes32 indexed requestHash, address token, uint256 amount, address to); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; // Basic details in the request struct BasicRequest { // src chain id uint256 originChainId; // dest chain id uint256 destinationChainId; // deadline of the request uint256 deadline; // nonce used for uniqueness in signature uint256 nonce; // address of the user placing the request. address sender; // address of the receiver on destination chain address receiver; // delegate address that has some rights over the request signed address delegate; // address of bungee gateway, this address will have access to pull funds from the sender. address bungeeGateway; // id of the switchboard uint32 switchboardId; // address of the input token address inputToken; // amount of the input tokens uint256 inputAmount; // output token to be received on the destination. address outputToken; // minimum amount to be receive on the destination for the output token. uint256 minOutputAmount; // native token refuel amount on the destination chain uint256 refuelAmount; } // The Request which user signs struct Request { // basic details in the request. BasicRequest basicReq; // swap output token that the user is okay with swapping input token to. address swapOutputToken; // minimum swap output the user is okay with swapping the input token to. // Transmitter can choose or not choose to swap tokens. uint256 minSwapOutput; // any sort of metadata to be passed with the request bytes32 metadata; // fees of the affiliate if any bytes affiliateFees; } // Transmitter's origin chain execution details for a request with promisedAmounts. struct ExtractExec { // User signed Request Request request; // address of the router being used for the request. address router; // promised amount for output token on the destination uint256 promisedAmount; // promised amount for native token refuel on the destination uint256 promisedRefuelAmount; // RouterPayload (router specific data) + RouterValue (value required by the router) etc etc bytes routerData; // swapPayload 0x00 if no swap is involved. bytes swapPayload; // swapRouterAddress address swapRouter; // user signature against the request bytes userSignature; // address of the beneficiary submitted by the transmitter. // the beneficiary will be the one receiving locked tokens when a request is settled. address beneficiary; } // Transmitter's destination chain execution details with fulfil amounts. struct FulfilExec { // User Signed Request Request request; // address of the router address fulfilRouter; // amount to be sent to the receiver for output token. uint256 fulfilAmount; // amount to be sent to the receiver for native token refuel. uint256 refuelFulfilAmount; // extraPayload for router. bytes routerData; // total msg.value to be sent to fulfil native token output token uint256 msgValue; } struct ExtractedRequest { uint256 expiry; address router; address sender; address delegate; uint32 switchboardId; address token; address transmitter; // For stake capacity address beneficiary; // For Transmitter uint256 amount; uint256 promisedAmount; // For Transmitter uint256 promisedRefuelAmount; bytes affiliateFees; // For integrator } struct FulfilledRequest { uint256 fulfilledAmount; uint256 fulfilledRefuelAmount; bool processed; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; // Basic details in the request struct BasicRequest { // chain id uint256 chainId; // deadline of the request uint256 deadline; // nonce used for uniqueness in signature uint256 nonce; // address of the user placing the request. address sender; // address of the receiver on destination chain address receiver; // address of bungee gateway, this address will have access to pull funds from the sender. address bungeeGateway; // address of the input token address inputToken; // amount of the input tokens uint256 inputAmount; // array of output tokens to be received on the destination. address[] outputTokens; // array of minimum amounts to be receive on the destination for the output tokens array. uint256[] minOutputAmounts; } // The Request which user signs struct Request { // basic details in the request. BasicRequest basicReq; // array of addresses to check if request whitelists only certain transmitters address[] exclusiveTransmitters; // any sort of metadata to be passed with the request bytes32 metadata; // fees of the affiliate if any bytes affiliateFees; // calldata execution parameter. Only to be used when execution is required on destination. // minimum dest gas limit to execute calldata on destination uint256 minDestGas; // calldata to be executed on the destination // calldata can only be executed on the receiver in the request. bytes destinationPayload; } // Transmitter's origin chain execution details for a request with fulfilAmounts. struct SwapExec { // User signed Request Request request; // array of fulfil amounts for the corresponding output tokens on the destination uint256[] fulfilAmounts; // swapPayload 0x00 if no swap is involved. bytes swapPayload; // swapRouterAddress address swapRouter; // user signature against the request bytes userSignature; // address of the beneficiary submitted by the transmitter. // the beneficiary will be the one receiving locked tokens when a request is settled. address beneficiary; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import {ISwapExecutor} from "../interfaces/ISwapExecutor.sol"; import {ICalldataExecutor} from "../interfaces/ICalldataExecutor.sol"; import {ISwitchboardRouter} from "../interfaces/ISwitchboardRouter.sol"; import {SingleOutputRequestImpl} from "./SingleOutputRequestImpl.sol"; import {SwapRequestImpl} from "./SwapRequestImpl.sol"; import {BungeeGatewayBase} from "./BungeeGatewayBase.sol"; import {BungeeGatewayStorage} from "./BungeeGatewayStorage.sol"; import {InvalidMsg} from "../common/BungeeErrors.sol"; contract BungeeGateway is BungeeGatewayStorage, BungeeGatewayBase { SingleOutputRequestImpl public immutable SINGLE_OUTPUT_REQUEST_IMPL; SwapRequestImpl public immutable SWAP_REQUEST_IMPL; bytes4 immutable RECEIVE_MSG_FUNCTION_SIG = bytes4(keccak256("receiveMsg(bytes)")); /** * @notice Constructor. * @param _owner owner of the contract. * @param _mofaSigner address of the mofa signer. * @param _switchboardRouter address of the switchboard router. Switchboard router is responsible for sending and delivering messages between chains. * @param _calldataRouter address of the calldata executor contract. * @param _permit2 address of the permit 2 contract. * @param _expiryBuffer expiry buffer for the request fulfilment deadline. */ constructor( address _owner, address _mofaSigner, address _switchboardRouter, address _swapExecutor, address _calldataRouter, address _permit2, uint256 _expiryBuffer, address _singleOutputRequestImpl, address _swapOutputRequestImpl ) BungeeGatewayStorage(_owner, _permit2) { MOFA_SIGNER = _mofaSigner; SWITCHBOARD_ROUTER = ISwitchboardRouter(_switchboardRouter); SWAP_EXECUTOR = ISwapExecutor(_swapExecutor); CALLDATA_EXECUTOR = ICalldataExecutor(_calldataRouter); EXPIRY_BUFFER = _expiryBuffer; SINGLE_OUTPUT_REQUEST_IMPL = SingleOutputRequestImpl(_singleOutputRequestImpl); SWAP_REQUEST_IMPL = SwapRequestImpl(_swapOutputRequestImpl); } function executeSOR(bytes calldata data) external payable returns (bytes memory) { (bool success, bytes memory result) = address(SINGLE_OUTPUT_REQUEST_IMPL).delegatecall(data); if (!success) { assembly { revert(add(result, 32), mload(result)) } } return result; } function executeSR(bytes calldata data) external payable returns (bytes memory) { (bool success, bytes memory result) = address(SWAP_REQUEST_IMPL).delegatecall(data); if (!success) { assembly { revert(add(result, 32), mload(result)) } } return result; } function inboundMsgFromSwitchboard(uint8 implId, uint32, bytes calldata payload) external payable { address impl; if (implId == SINGLE_OUTPUT_IMPL_ID) impl = address(SINGLE_OUTPUT_REQUEST_IMPL); // there will be no inbound msg for swap request if (impl == address(0)) revert InvalidMsg(); (bool success, bytes memory result) = impl.delegatecall( abi.encodeWithSelector(RECEIVE_MSG_FUNCTION_SIG, payload) ); if (!success) { assembly { revert(add(result, 32), mload(result)) } } } /// @dev fallback function to receive native tokens /// @dev especially useful for receiving native token swap output in SwapRequest /// @dev this will be applied to BungeeGateway as a whole receive() external payable {} }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import {ERC20, SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {IBaseRouter} from "../interfaces/IBaseRouter.sol"; import {ISwapExecutor} from "../interfaces/ISwapExecutor.sol"; import {ICalldataExecutor} from "../interfaces/ICalldataExecutor.sol"; import {ISwitchboardRouter} from "../interfaces/ISwitchboardRouter.sol"; import {IStakeVault} from "../interfaces/IStakeVault.sol"; import {IFeeCollector} from "../interfaces/IFeeCollector.sol"; import { RouterAlreadyWhitelisted, RouterAlreadyRegistered, TransferFailed, InvalidStake, InsufficientCapacity, InvalidMsg } from "../common/BungeeErrors.sol"; import {BungeeGatewayStorage} from "./BungeeGatewayStorage.sol"; // @todo should this be renamed to ImplBase. These are implemented by all Impls, not BungeeGateway, but actually acts on BungeeGatewayStorage abstract contract BungeeGatewayBase is BungeeGatewayStorage { using SafeTransferLib for ERC20; /*////////////////////////////////////////////////////////////////////////// ADMIN FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ /** * @notice send funds to the provided address if stuck, can be called only by owner. * @param token address of the token * @param amount hash of the command. * @param to address, funds will be transferred to this address. */ function rescue(address token, address to, uint256 amount) external onlyOwner { _sendFundsFromContract(token, amount, to); } /** * @notice sets the new mofa signer address. Can only be called by the owner. * @param _mofaSigner address of the new mofa signer. */ function setMofaSigner(address _mofaSigner) external onlyOwner { MOFA_SIGNER = _mofaSigner; } /** * @notice sets the new switchboard router. Can only be called by the owner. * @param _switchboardRouter address of the new switchboard router. */ function setSwitchboardRouter(address _switchboardRouter) external onlyOwner { SWITCHBOARD_ROUTER = ISwitchboardRouter(_switchboardRouter); } /** * @notice sets the new fee collector. Can only be called by the owner. * @param _feeCollector address of the new switchboard router. */ function setFeeCollector(address _feeCollector) external onlyOwner { FEE_COLLECTOR = IFeeCollector(_feeCollector); } /** * @notice sets the new expiry buffer. Can only be called by the owner. * @param _expiryBuffer expiry buffer for the request fulfilment deadline. */ function setExpiryBuffer(uint256 _expiryBuffer) external onlyOwner { EXPIRY_BUFFER = _expiryBuffer; } /** * @notice sets the new swap executor contract. Can only be called by the owner. * @param _swapExecutor address of the new swap executor. */ function setSwapExecutor(address _swapExecutor) external onlyOwner { SWAP_EXECUTOR = ISwapExecutor(_swapExecutor); } /** * @notice sets the new calldata executor contract. Can only be called by the owner. * @param _calldataExecutor address of the new calldata executor. */ function setCalldataExecutor(address _calldataExecutor) external onlyOwner { CALLDATA_EXECUTOR = ICalldataExecutor(_calldataExecutor); } /** * @notice sets the new StakeVault contract. Can only be called by the owner. * @param _stakeVault address of the new calldata executor. */ function setStakeVault(address _stakeVault) external onlyOwner { STAKE_VAULT = IStakeVault(_stakeVault); } /// @notice register a whitelisted router function registerWhitelistedRouter(address whitelistedRouter) external onlyOwner { if (isWhitelisted[whitelistedRouter]) revert RouterAlreadyWhitelisted(); if (bungeeRouters[whitelistedRouter]) revert RouterAlreadyRegistered(); isWhitelisted[whitelistedRouter] = true; _addBungeeRouter(whitelistedRouter); } /// @notice register a staked router function registerStakedRouter(address stakedRouter) external onlyOwner { if (bungeeRouters[stakedRouter]) revert RouterAlreadyRegistered(); _addBungeeRouter(stakedRouter); } /// @notice Adds a new router to the protocol function _addBungeeRouter(address _bungeeRouter) internal { bungeeRouters[_bungeeRouter] = true; } function isBungeeRouter(address router) public view returns (bool) { return bungeeRouters[router]; } /** * @notice adds the new whitelisted receiver address against a router. Can only be called by the owner. * @param receiver address of the new whitelisted receiver contract. * @param destinationChainId destination chain id where the receiver will exist. * @param router router address from which the funs will be routed from. */ function setWhitelistedReceiver(address receiver, uint256 destinationChainId, address router) external onlyOwner { whitelistedReceivers[router][destinationChainId] = receiver; } /** * @notice gets the receiver address set for the router on the destination chain. * @param destinationChainId destination chain id where the receiver will exist. * @param router router address from which the funds will be routed from. */ function getWhitelistedReceiver(address router, uint256 destinationChainId) external view returns (address) { return whitelistedReceivers[router][destinationChainId]; } /** * @notice Transmitter can register and increment their stake against a token * @dev Transmitter would transfer their tokens for the stake */ function registerTransmitterStake(address token, uint256 capacity) external payable { transmitterCapacity[msg.sender][token] = transmitterCapacity[msg.sender][token] + capacity; if (token == NATIVE_TOKEN_ADDRESS) { if (msg.value != capacity) revert InvalidStake(); (bool success, ) = address(STAKE_VAULT).call{value: capacity, gas: 5000}(""); if (!success) revert TransferFailed(); } else { ERC20(token).safeTransferFrom(msg.sender, address(STAKE_VAULT), capacity); } } /** * @notice Transmitter can withdraw their stake against a token * @dev Transmitter would receive their tokens back * @dev Transmitter's capacity would be reduced */ function withdrawTransmitterStake(address token, uint256 capacity) external { transmitterCapacity[msg.sender][token] = transmitterCapacity[msg.sender][token] - capacity; STAKE_VAULT.withdrawStake(token, capacity, msg.sender); } function withdrawBeneficiarySettlement(address beneficiary, address router, address token) external { uint256 amount = beneficiarySettlements[beneficiary][router][token]; if (amount > 0) { beneficiarySettlements[beneficiary][router][token] = 0; // Transfer the tokens to the beneficiary IBaseRouter(router).releaseFunds(token, amount, beneficiary); } } /** * @notice extract the user requests and routes it via the respetive routers. * @notice the user requests can only be extracted if the mofa signature is valid. * @notice each request can be routed via a different router. * @dev if the switchboard id is not same as the user request, it will revert. * @dev if the fulfilled amounts is not equal to or greater than the promised amount, revert. * @dev mark the extracted hash deleted. * @param payload msg payload sent. */ function receiveMsg(bytes calldata payload) external payable { // If the msg sender is not switchboard router, revert. if (msg.sender != address(SWITCHBOARD_ROUTER)) revert InvalidMsg(); _receiveMsg(payload); } function _receiveMsg(bytes calldata payload) internal virtual {} /// @notice check capacity for a whitelisted router or staked transmitter /// @dev if the router is whitelisted, it has a max capacity /// @dev if the router is not whitelisted, return registered transmitter capacity function checkCapacity(address transmitter, address token) public view returns (uint256) { return transmitterCapacity[transmitter][token]; } function _increaseCapacity(address transmitter, address token, uint256 increaseBy) internal { transmitterCapacity[transmitter][token] = transmitterCapacity[transmitter][token] + increaseBy; } function _reduceCapacity(address transmitter, address token, uint256 reduceBy) internal { transmitterCapacity[transmitter][token] = transmitterCapacity[transmitter][token] - reduceBy; } function _validateAndReduceStake(uint256 inputAmount, address inputToken) internal { // check capacity before extraction if (checkCapacity(msg.sender, inputToken) < inputAmount) revert InsufficientCapacity(); _reduceCapacity(msg.sender, inputToken, inputAmount); } /** * @dev send funds to the provided address. * @param token address of the token * @param amount hash of the command. * @param to address, funds will be transferred to this address. */ function _sendFundsFromContract(address token, uint256 amount, address to) internal { /// native token case if (token == NATIVE_TOKEN_ADDRESS) { (bool success, ) = to.call{value: amount, gas: 5000}(""); if (!success) revert TransferFailed(); return; } /// ERC20 case ERC20(token).safeTransfer(to, amount); } /** * @dev send funds from an address to the provided address. * @param token address of the token * @param from atomic execution. * @param amount hash of the command. * @param to address, funds will be transferred to this address. */ function _sendFundsToReceiver(address token, address from, uint256 amount, address to) internal { /// native token case if (token == NATIVE_TOKEN_ADDRESS) { (bool success, ) = to.call{value: amount, gas: 5000}(""); if (!success) revert TransferFailed(); return; } /// ERC20 case ERC20(token).safeTransferFrom(from, to, amount); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; import {Ownable} from "../utils/Ownable.sol"; import {ISwapExecutor} from "../interfaces/ISwapExecutor.sol"; import {ICalldataExecutor} from "../interfaces/ICalldataExecutor.sol"; import {ISwitchboardRouter} from "../interfaces/ISwitchboardRouter.sol"; import {IStakeVault} from "../interfaces/IStakeVault.sol"; import {IFeeCollector} from "../interfaces/IFeeCollector.sol"; // SingleOutputRequest import { Request as SingleOutputRequest, ExtractExec as SingleOutputExtractExec, ExtractedRequest as SingleOutputExtractedRequest, FulfilledRequest as SingleOutputFulfilledRequest } from "../common/SingleOutputStructs.sol"; import {RequestLib as SingleOutputRequestLib} from "../lib/SingleOutputRequestLib.sol"; abstract contract BungeeGatewayStorage is Ownable { using SingleOutputRequestLib for SingleOutputRequest; using SingleOutputRequestLib for SingleOutputExtractExec; /// @dev the maximum capacity for whitelisted routers uint256 internal constant WHITELISTED_MAX_CAPACITY = type(uint256).max; /// @dev address used to identify native token address public constant NATIVE_TOKEN_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); /// @dev id used to identify single output implementation uint8 public constant SINGLE_OUTPUT_IMPL_ID = 1; /// @dev id used to identify swap request implementation uint8 public constant SWAP_REQUEST_IMPL_ID = 2; /// @notice address of the permit 2 contract ISignatureTransfer public immutable PERMIT2; /// @notice address of the protocol signer /// @dev this address signs on the request batch that transmitter submits to the protocol. address public MOFA_SIGNER; /// @notice address of the SwitchboardRouter /// @dev BungeeGateway uses this contract to handle cross-chain messages via Socket ISwitchboardRouter public SWITCHBOARD_ROUTER; /// @notice address of the SwapExecutor /// @dev BungeeGateway delegates swap executions to this contract. ISwapExecutor public SWAP_EXECUTOR; /// @notice address of the CalldataExecutor /// @dev BungeeGateway delegates calldata execution at destination chain to this contract. ICalldataExecutor public CALLDATA_EXECUTOR; /// @notice address of the FeeCollector /// @dev BungeeGateway collects fees from the users and transfers them to this contract. IFeeCollector public FEE_COLLECTOR; /// @notice address of the StakeVault /// @dev BungeeGateway transfers all stake to StakeVault /// @dev BungeeGateway triggers StakeVault to release stake funds IStakeVault public STAKE_VAULT; /// @notice this is the buffer time for expiry of any new request uint256 public EXPIRY_BUFFER; /// @notice this holds all the requests that have been fulfilled. mapping(bytes32 requestHash => SingleOutputFulfilledRequest request) internal singleOutputFulfilledRequests; /// @notice this holds all the requests that have been extracted. mapping(bytes32 requestHash => SingleOutputExtractedRequest request) internal singleOutputExtractedRequests; /// @notice this mapping holds all the receiver contracts, these contracts will receive funds. /// @dev bridged funds would reach receiver contracts first and then transmitter uses these funds to fulfil order. mapping(address router => mapping(uint256 toChainId => address whitelistedReceiver)) internal whitelistedReceivers; /// @notice this mapping holds all the addresses that are routers. /// @dev bungee sends funds from the users to these routers on the origin chain. /// @dev bungee calls these when fulfilment happens on the destination. mapping(address routers => bool supported) internal bungeeRouters; /// @dev this holds all the routers that are whitelisted. mapping(address router => bool whitelisted) public isWhitelisted; /// @notice this mapping holds capacity for a transmitter /// @dev token is checked against the inputToken or swapOutputToken of the request mapping(address transmitter => mapping(address token => uint256 capacity)) public transmitterCapacity; /// @notice this mapping stores orders that have been withdrawn on the originChain after Request expiry /// @dev Requests are deleted from the extractedRequests mapping when withdrawn on the origin chain /// @dev This mapping stores the withdrawn requests for external contracts to track mapping(bytes32 requestHash => bool withdrawn) public withdrawnRequests; /// @notice this mapping stores the settlement amounts for the beneficiaries /// @dev not all routers would have settlement, so these amounts may not be cleared for some routers mapping(address beneficiary => mapping(address router => mapping(address token => uint256 amount))) public beneficiarySettlements; /** * @notice Constructor. * @dev Defines all immutable variables & owner * @param _owner owner of the contract. * @param _permit2 address of the permit 2 contract. */ constructor(address _owner, address _permit2) Ownable(_owner) { PERMIT2 = ISignatureTransfer(_permit2); } /*////////////////////////////////////////////////////////////////////////// GETTERS //////////////////////////////////////////////////////////////////////////*/ function getSingleOutputExtractedRequest( bytes32 requestHash ) external view returns (SingleOutputExtractedRequest memory) { return singleOutputExtractedRequests[requestHash]; } function getSingleOutputFulfilledRequest( bytes32 requestHash ) external view returns (SingleOutputFulfilledRequest memory) { return singleOutputFulfilledRequests[requestHash]; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import {IBaseRouterSingleOutput as IBaseRouter} from "../interfaces/IBaseRouterSingleOutput.sol"; import {AuthenticationLib} from "../lib/AuthenticationLib.sol"; import {AffiliateFeesLib} from "../lib/AffiliateFeesLib.sol"; import { InsufficientNativeAmount, MofaSignatureInvalid, RouterNotRegistered, MinOutputNotMet, InvalidRequest, InvalidMsg, FulfilmentDeadlineNotMet, SwapOutputInsufficient, NotDelegate, RequestProcessed, InvalidSwitchboard, InvalidRequest, PromisedAmountNotMet, RequestNotProcessed } from "../common/BungeeErrors.sol"; import {Permit2Lib} from "../lib/Permit2Lib.sol"; import {BungeeEvents} from "../common/BungeeEvents.sol"; import {ERC20, SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {RequestLib} from "../lib/SingleOutputRequestLib.sol"; import {Request, FulfilExec, ExtractExec, ExtractedRequest, FulfilledRequest} from "../common/SingleOutputStructs.sol"; import {BungeeGatewayBase} from "./BungeeGatewayBase.sol"; import {BungeeGatewayStorage} from "./BungeeGatewayStorage.sol"; contract SingleOutputRequestImpl is BungeeGatewayStorage, BungeeGatewayBase, BungeeEvents { using RequestLib for Request; using RequestLib for ExtractExec[]; using SafeTransferLib for ERC20; constructor(address _owner, address _permit2) BungeeGatewayStorage(_owner, _permit2) {} /*////////////////////////////////////////////////////////////////////////// SOURCE FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ /** * @notice extract the user requests and routes it via the respetive routers. * @notice the user requests can only be extracted if the mofa signature is valid. * @notice each request can be routed via a different router. * @notice it would be assumed as a successful execution if the router call does not revert. * @dev state of the request would be saved against the requesthash created. * @dev funds from the user wallet will be pulled and sent to the router. * @dev if there is a swap involved then swapped funds would reach the router. * @param extractExecs batch of extractions submitted by the transmitter. * @param mofaSignature signature of mofa on the batch. */ function extractRequests(ExtractExec[] calldata extractExecs, bytes calldata mofaSignature) external { // Checks if batch has been authorised by MOFA _checkMofaSig(extractExecs, mofaSignature); // Iterate through extractExec unchecked { for (uint256 i = 0; i < extractExecs.length; i++) { // Check if the promised amount is more than the minOutputAmount if ( // check output amount (extractExecs[i].promisedAmount < extractExecs[i].request.basicReq.minOutputAmount) || // check refuel amount (extractExecs[i].promisedRefuelAmount < extractExecs[i].request.basicReq.refuelAmount) ) revert MinOutputNotMet(); // If a swap is involved the router would receive the swap output tokens specified in request. // If no swap is involved the router would receive the input tokens specified in request. if (extractExecs[i].swapPayload.length > 0) { _swapAndCallRouter(extractExecs[i]); } else { _callRouter(extractExecs[i]); } } } } /** * @notice extract the user requests and routes it via the respetive routers. * @notice the user requests can only be extracted if the mofa signature is valid. * @notice each request can be routed via a different router. * @dev if the switchboard id is not same as the user request, it will revert. * @dev if the fulfilled amounts is not equal to or greater than the promised amount, revert. * @dev mark the extracted hash deleted. * @param payload msg payload sent. */ function _receiveMsg(bytes calldata payload) internal override { uint32 switchboardId = uint32(bytes4(payload)); /// @dev fulfilledAmounts would be output token fulfilled for each request in the batch. hence a 1d array ( bytes32[] memory requestHashes, uint256[] memory fulfilledAmounts, uint256[] memory fulfilledRefuelAmounts ) = abi.decode(payload[4:], (bytes32[], uint256[], uint256[])); unchecked { for (uint256 i = 0; i < requestHashes.length; i++) { _validateAndReleaseSettleRequests( switchboardId, requestHashes[i], fulfilledAmounts[i], fulfilledRefuelAmounts[i] ); } } } /*////////////////////////////////////////////////////////////////////////// INTERNAL SOURCE FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ /** * @notice checks if the mofa signature is valid on the batch submitted by the transmitter. * @param extractExecs batch of extractions submitted by the transmitter. * @param mofaSignature signature of mofa on the batch. */ function _checkMofaSig(ExtractExec[] calldata extractExecs, bytes memory mofaSignature) internal view { // Create the hash of BatchHash bytes32 batchHash = extractExecs.hashOriginBatch(); // Get the signer address signer = AuthenticationLib.authenticate(batchHash, mofaSignature); // Check if addresses match if (signer != MOFA_SIGNER) revert MofaSignatureInvalid(); } /** * @notice this function is used when the transmitter submits a swap for a user request. * @notice assumption is that the receiver of the swap will be the router mentioned in the exec. * @dev Funds would be transferred to the swap executor first. * @dev Swap executor will be called with the swap payload. * @dev Funds after the swap should reach the router. * @dev If the swap does not give back enough, revert. * @dev If all is good, the router will be called with relevant details. * @dev Saves the extraction details against the requestHash. * @param extractExec execution submitted by the transmitter for the request. When a request is settled beneficiary will receive funds. */ function _swapAndCallRouter(ExtractExec memory extractExec) internal { // Create the request hash for the submitted request. bytes32 requestHash = extractExec.request.hashOriginRequest(); bool isNativeToken = extractExec.request.swapOutputToken == NATIVE_TOKEN_ADDRESS; // Get the initial balance of the router for the swap output token uint256 initialBalance = isNativeToken ? extractExec.router.balance : ERC20(extractExec.request.swapOutputToken).balanceOf(extractExec.router); // Calls Permit2 to transfer funds from user to swap executor. PERMIT2.permitWitnessTransferFrom( Permit2Lib.toPermit( extractExec.request.basicReq.inputToken, extractExec.request.basicReq.inputAmount, extractExec.request.basicReq.nonce, extractExec.request.basicReq.deadline ), /// @dev transfer tokens to SwapExecutor Permit2Lib.transferDetails(extractExec.request.basicReq.inputAmount, address(SWAP_EXECUTOR)), extractExec.request.basicReq.sender, requestHash, RequestLib.PERMIT2_ORDER_TYPE, extractExec.userSignature ); // Call the swap executor to execute the swap. /// @dev swap output tokens are expected to be sent to the router SWAP_EXECUTOR.executeSwap( extractExec.request.basicReq.inputToken, extractExec.request.basicReq.inputAmount, extractExec.swapRouter, extractExec.swapPayload ); // Get the final balance of the swap output token on the router uint256 swappedAmount = isNativeToken ? extractExec.router.balance - initialBalance : ERC20(extractExec.request.swapOutputToken).balanceOf(extractExec.router) - initialBalance; // Check if the minimum swap output is sufficed after the swap. If not revert. if (swappedAmount < extractExec.request.minSwapOutput) revert SwapOutputInsufficient(); if (!isWhitelisted[extractExec.router]) { _validateAndReduceStake(swappedAmount, extractExec.request.swapOutputToken); } uint256 expiry = block.timestamp + EXPIRY_BUFFER; // Call the router with relevant details IBaseRouter(extractExec.router).execute( swappedAmount, extractExec.request.swapOutputToken, requestHash, expiry, whitelistedReceivers[extractExec.router][extractExec.request.basicReq.destinationChainId], address(FEE_COLLECTOR), extractExec ); // Save the extraction details singleOutputExtractedRequests[requestHash] = ExtractedRequest({ expiry: expiry, router: extractExec.router, sender: extractExec.request.basicReq.sender, delegate: extractExec.request.basicReq.delegate, switchboardId: extractExec.request.basicReq.switchboardId, token: extractExec.request.swapOutputToken, amount: swappedAmount, affiliateFees: extractExec.request.affiliateFees, transmitter: msg.sender, beneficiary: extractExec.beneficiary, promisedAmount: extractExec.promisedAmount, promisedRefuelAmount: extractExec.promisedRefuelAmount }); // Emits Extraction Event emit RequestExtracted(requestHash, SINGLE_OUTPUT_IMPL_ID, msg.sender, abi.encode(extractExec)); } /** * @notice this function is used when the transmitter submits a request that does not involve a swap. * @dev funds would be transferred to the router directly from the user. * @dev Saves the extraction details against the requestHash. * @param extractExec execution submitted by the transmitter for the request. When a request is settled beneficiary will receive funds. */ function _callRouter(ExtractExec memory extractExec) internal { // If not whitelisted, valiate if the router is part of the protocol and reduce transmitter stake if (!isWhitelisted[extractExec.router]) { _validateAndReduceStake(extractExec.request.basicReq.inputAmount, extractExec.request.basicReq.inputToken); } // Create the request hash for the submitted request. bytes32 requestHash = extractExec.request.hashOriginRequest(); // Calls Permit2 to transfer funds from user to the router. PERMIT2.permitWitnessTransferFrom( Permit2Lib.toPermit( extractExec.request.basicReq.inputToken, extractExec.request.basicReq.inputAmount, extractExec.request.basicReq.nonce, extractExec.request.basicReq.deadline ), Permit2Lib.transferDetails(extractExec.request.basicReq.inputAmount, extractExec.router), extractExec.request.basicReq.sender, requestHash, RequestLib.PERMIT2_ORDER_TYPE, extractExec.userSignature ); uint256 expiry = block.timestamp + EXPIRY_BUFFER; // Call the router with relevant details IBaseRouter(extractExec.router).execute( extractExec.request.basicReq.inputAmount, extractExec.request.basicReq.inputToken, requestHash, expiry, whitelistedReceivers[extractExec.router][extractExec.request.basicReq.destinationChainId], address(FEE_COLLECTOR), extractExec ); // Save the extraction details singleOutputExtractedRequests[requestHash] = ExtractedRequest({ expiry: expiry, router: extractExec.router, sender: extractExec.request.basicReq.sender, delegate: extractExec.request.basicReq.delegate, switchboardId: extractExec.request.basicReq.switchboardId, token: extractExec.request.basicReq.inputToken, amount: extractExec.request.basicReq.inputAmount, affiliateFees: extractExec.request.affiliateFees, transmitter: msg.sender, beneficiary: extractExec.beneficiary, promisedAmount: extractExec.promisedAmount, promisedRefuelAmount: extractExec.promisedRefuelAmount }); // Emits Extraction Event emit RequestExtracted(requestHash, SINGLE_OUTPUT_IMPL_ID, msg.sender, abi.encode(extractExec)); } /** * @notice validates the settlement details against the request. * @param switchboardId id of the switchboard that received the msg. * @param requestHash hash of the request that needs to be settled. * @param fulfilledAmount amount sent to the receiver on the destination. */ function _validateAndReleaseSettleRequests( uint32 switchboardId, bytes32 requestHash, uint256 fulfilledAmount, uint256 fulfilledRefuelAmount ) internal { // Check if the extraction exists and the switchboard id is correct. ExtractedRequest memory eReq = singleOutputExtractedRequests[requestHash]; // Check if the request is valid. // Check if request has already been settled. if (eReq.sender == address(0)) revert InvalidRequest(); if (eReq.switchboardId != switchboardId) revert InvalidSwitchboard(); // Check if the fulfilment was done correctly. // Check if the request was fulfilled: output token & refuel amount if ((eReq.promisedAmount > fulfilledAmount) || (eReq.promisedRefuelAmount > fulfilledRefuelAmount)) revert PromisedAmountNotMet(); // Get the beneficiary amount to settle the request. // Checks if there was affiliate fees involved. uint256 beneficiaryAmount = AffiliateFeesLib.getAmountAfterFee(eReq.amount, eReq.affiliateFees); if (isWhitelisted[eReq.router]) { // Settle Fee. if (eReq.amount > beneficiaryAmount) { FEE_COLLECTOR.settleFee(requestHash); } // track the amount to be settled beneficiarySettlements[eReq.beneficiary][eReq.router][eReq.token] += beneficiaryAmount; } else { // replenish transmitter stake by input amount _increaseCapacity(eReq.transmitter, eReq.token, eReq.amount); } // Delete the origin execution. delete singleOutputExtractedRequests[requestHash]; // Emits Settlement event emit RequestSettled(requestHash); } /** * @notice this function can be called by user on source chain to revoke order after fulfil deadline has passed * @dev Asks router to send back escrowed funds to commander in case of RFQ. * @dev Slash the stake from the transmitter to pay back to the user. * @dev Funds routed via whitelisted routers cannot be withdrawn on source. Withdraw on destination can be done for whitelisted routes. * @param requestHash hash of the request */ function withdrawRequestOnOrigin(bytes32 requestHash) external { ExtractedRequest memory eReq = singleOutputExtractedRequests[requestHash]; // Check if the request is valid. if (eReq.sender == address(0)) revert InvalidRequest(); // Checks deadline of request fulfilment and if its exceeded, if (block.timestamp < eReq.expiry) revert FulfilmentDeadlineNotMet(); if (isWhitelisted[eReq.router]) { // Checks if there was affiliate fees involved. uint256 routerAmount = AffiliateFeesLib.getAmountAfterFee(eReq.amount, eReq.affiliateFees); FEE_COLLECTOR.refundFee(requestHash, eReq.sender); // Ask router to transfer funds back to the user IBaseRouter(eReq.router).releaseFunds(eReq.token, routerAmount, eReq.sender); } else { // Withdraw transmitter stake and give back to the user. /// @dev transmitter capacity would already be reduced when the request was extracted /// no need to change the capacity any way then STAKE_VAULT.withdrawStake(eReq.token, eReq.amount, eReq.sender); } // Emits Withdraw event emit WithdrawOnOrigin(requestHash, eReq.token, eReq.amount, eReq.sender); // Stores WithdrawnRequest withdrawnRequests[requestHash] = true; // Delete the origin execution delete singleOutputExtractedRequests[requestHash]; } /*////////////////////////////////////////////////////////////////////////// DESTINATION FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ /** * @notice Performs fulfilment of a batch of requests * @dev Called by transmitter to fulfil a request * @dev Calculates and tracks destination hash of fulfilled requests * @dev Can fulfil a batch of requests * @dev Checks if provided router contract is registered with Bungee protocol * @param fulfilExecs Array of FulfilExec */ function fulfilRequests(FulfilExec[] calldata fulfilExecs) external payable { // Will be used to check if the msg value was sufficient at the end. uint256 nativeAmount = 0; // Iterate through the array of fulfil execs. for (uint256 i = 0; i < fulfilExecs.length; i++) { FulfilExec memory fulfilExec = fulfilExecs[i]; // Calculate the request hash. Being tracked on singleOutputFulfilledRequests bytes32 requestHash = fulfilExec.request.hashDestinationRequest(); // 1.a Check if the command is already fulfilled / cancelled if (singleOutputFulfilledRequests[requestHash].processed) revert RequestProcessed(); // 1.c Check if the provided router address is part of the Bungee protocol if (!isBungeeRouter(fulfilExec.fulfilRouter)) revert RouterNotRegistered(); // 1.d Check if promisedOutput amounts are more than minOutput amounts if ( // check output amount (fulfilExec.fulfilAmount < fulfilExec.request.basicReq.minOutputAmount) || // check refuel amount (fulfilExec.refuelFulfilAmount < fulfilExec.request.basicReq.refuelAmount) ) revert MinOutputNotMet(); // 2. Call the fulfil function on the router IBaseRouter(fulfilExec.fulfilRouter).fulfil{value: fulfilExec.msgValue}( requestHash, fulfilExec, msg.sender ); nativeAmount += fulfilExec.msgValue; // 4. BungeeGateway stores order hash and its outputToken, promisedOutput singleOutputFulfilledRequests[requestHash] = FulfilledRequest({ fulfilledAmount: fulfilExec.fulfilAmount, fulfilledRefuelAmount: fulfilExec.refuelFulfilAmount, processed: true }); // Emits Fulfilment Event emit RequestFulfilled(requestHash, SINGLE_OUTPUT_IMPL_ID, msg.sender, abi.encode(fulfilExec)); } if (msg.value < nativeAmount) revert InsufficientNativeAmount(); } /** * @notice Sends a settlement message back towards source to settle the requests. * @param requestHashes Array of request hashes to be settled. * @param gasLimit Gas limit to be used on the message receiving chain. * @param chainSlug Chain slug used in Socket to send the message towards i.e, source chain id * @param switchboardId id of the switchboard to use. switchboardIds of all requests in the batch must match */ function settleRequests( bytes32[] calldata requestHashes, uint256 gasLimit, uint32 chainSlug, uint32 switchboardId ) external payable { // Create an empty array of fulfilled amounts. /// @dev fulfilledAmounts would be output token fulfilled for each request in the batch. hence a 1d array uint256[] memory fulfilledAmounts = new uint256[](requestHashes.length); uint256[] memory fulfilledRefuelAmounts = new uint256[](requestHashes.length); // Loop through the requestHashes and set fulfilled amounts unchecked { for (uint256 i = 0; i < requestHashes.length; i++) { FulfilledRequest memory fReq = singleOutputFulfilledRequests[requestHashes[i]]; // check if request already processed if (!fReq.processed) revert RequestNotProcessed(); // Get the amount send to he receiver of the command and push into array fulfilledAmounts[i] = fReq.fulfilledAmount; fulfilledRefuelAmounts[i] = fReq.fulfilledRefuelAmount; } } // Call the switchboard router to send the message. SWITCHBOARD_ROUTER.sendOutboundMsg{value: msg.value}( chainSlug, switchboardId, SINGLE_OUTPUT_IMPL_ID, gasLimit, abi.encode(requestHashes, fulfilledAmounts, fulfilledRefuelAmounts) ); emit RequestsSettledOnDestination(requestHashes, SINGLE_OUTPUT_IMPL_ID, msg.sender, msg.value); } function withdrawRequestOnDestination( address router, Request calldata request, bytes calldata withdrawRequestData ) external payable { // generate the requestHash bytes32 requestHash = request.hashDestinationRequest(); // checks if the caller is the delegate if (msg.sender != request.basicReq.delegate) revert NotDelegate(); // Check if the command is already fulfilled / cancelled if (singleOutputFulfilledRequests[requestHash].processed) revert RequestProcessed(); // mark request as cancelled singleOutputFulfilledRequests[requestHash] = FulfilledRequest({ fulfilledAmount: 0, fulfilledRefuelAmount: 0, processed: true }); // check router is in system if (!isBungeeRouter(router)) revert RouterNotRegistered(); /// @dev router should know if the request hash is not supposed to be handled by it IBaseRouter(router).withdrawRequestOnDestination(request, withdrawRequestData); // TODO : need to add an event here } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import {IBungeeExecutor} from "../interfaces/IBungeeExecutor.sol"; import {Request, SwapExec} from "../common/SwapRequestStructs.sol"; import {AuthenticationLib} from "../lib/AuthenticationLib.sol"; import {AffiliateFeesLib} from "../lib/AffiliateFeesLib.sol"; import {RequestLib} from "../lib/SwapRequestLib.sol"; import {MofaSignatureInvalid, MinOutputNotMet, InvalidMultipleNativeTokens} from "../common/BungeeErrors.sol"; import {Permit2Lib} from "../lib/Permit2Lib.sol"; import {ERC20} from "solmate/src/tokens/ERC20.sol"; import {BungeeEvents} from "../common/BungeeEvents.sol"; import {SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {IFeeCollector} from "../interfaces/IFeeCollector.sol"; import {BungeeGatewayBase} from "./BungeeGatewayBase.sol"; import {BungeeGatewayStorage} from "./BungeeGatewayStorage.sol"; contract SwapRequestImpl is BungeeGatewayStorage, BungeeGatewayBase, BungeeEvents { using RequestLib for Request; using RequestLib for SwapExec[]; using SafeTransferLib for ERC20; constructor(address _owner, address _permit2) BungeeGatewayStorage(_owner, _permit2) {} /*////////////////////////////////////////////////////////////////////////// SOURCE FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ /** * @notice extract the user requests and routes it via the respetive routers. * @notice the user requests can only be extracted if the mofa signature is valid. * @notice each request can be routed via a different router. * @notice it would be assumed as a successful execution if the router call does not revert. * @dev state of the request would be saved against the requesthash created. * @dev funds from the user wallet will be pulled and sent to the router. * @dev if there is a swap involved then swapped funds would reach the router. * @param swapExecs batch of extractions submitted by the transmitter. * @param mofaSignature signature of mofa on the batch. */ function extractAndSwap(SwapExec[] calldata swapExecs, bytes calldata mofaSignature) external payable { // Checks if batch has been authorised by MOFA _checkMofaSig(swapExecs, mofaSignature); // Iterate through swapExec unchecked { for (uint256 i = 0; i < swapExecs.length; i++) { SwapExec memory exec = swapExecs[i]; // Preventing multiple native tokens bool isNativeTokenUsed = false; // Check if the promised amount is more than the minOutputAmount for (uint256 j = 0; j < exec.request.basicReq.outputTokens.length; j++) { if (exec.fulfilAmounts[j] < exec.request.basicReq.minOutputAmounts[j]) revert MinOutputNotMet(); // Check if native token has already been used as an output token if (exec.request.basicReq.outputTokens[j] == NATIVE_TOKEN_ADDRESS) { if (isNativeTokenUsed) revert InvalidMultipleNativeTokens(); isNativeTokenUsed = true; } } // Create the request hash for the submitted request. bytes32 requestHash = exec.request.hashRequest(); // If swap payload is present solver wants to use user funds to perform swap // If swap payload is not present then output tokens are directly transferred from solver if (exec.swapPayload.length > 0) { _swapRequestExternal(requestHash, exec); } else { _fulfilRequest(requestHash, exec); } // calldata execution via Calldata Executor using Request.destinationPayload, Request.minDestGas _executeCalldata( exec.request.basicReq.receiver, exec.request.minDestGas, exec.fulfilAmounts, exec.request.basicReq.outputTokens, requestHash, exec.request.destinationPayload ); } } } /*////////////////////////////////////////////////////////////////////////// INTERNAL SOURCE FUNCTIONS //////////////////////////////////////////////////////////////////////////*/ /** * @notice checks if the mofa signature is valid on the batch submitted by the transmitter. * @param swapExecs batch of extractions submitted by the transmitter. * @param mofaSignature signature of mofa on the batch. */ function _checkMofaSig(SwapExec[] calldata swapExecs, bytes memory mofaSignature) internal view { // Create the hash of BatchHash bytes32 batchHash = swapExecs.hashBatch(); // Get the signer address signer = AuthenticationLib.authenticate(batchHash, mofaSignature); // Check if addresses match if (signer != MOFA_SIGNER) revert MofaSignatureInvalid(); } /** * @dev collects unlocked fee in input token and registers it with the FeeCollector */ function _collectFee(address token, uint256 amount, address feeTaker) internal { _sendFundsFromContract(token, amount, address(FEE_COLLECTOR)); IFeeCollector(FEE_COLLECTOR).registerFee(feeTaker, amount, token); } /** * @notice this function is used when the transmitter submits a swap for a user request. * @notice assumption is that the receiver of the swap will be the router mentioned in the exec. * @dev Funds would be transferred to the swap executor first. * @dev Swap executor will be called with the swap payload. * @dev Funds after the swap should reach directly to the user * @dev If the fulfilAmounts are not met, the transaction will revert. * @param swapExec execution submitted by the transmitter for the request. */ function _swapRequestExternal(bytes32 requestHash, SwapExec memory swapExec) internal { // Calls Permit2 to transfer funds from user to swap executor. PERMIT2.permitWitnessTransferFrom( Permit2Lib.toPermit( swapExec.request.basicReq.inputToken, swapExec.request.basicReq.inputAmount, swapExec.request.basicReq.nonce, swapExec.request.basicReq.deadline ), /// @dev transfer tokens to SwapExecutor Permit2Lib.transferDetails(swapExec.request.basicReq.inputAmount, address(SWAP_EXECUTOR)), swapExec.request.basicReq.sender, requestHash, RequestLib.PERMIT2_ORDER_TYPE, swapExec.userSignature ); // Check output token balances of receiver before swap uint256[] memory initialBalances = new uint256[](swapExec.request.basicReq.outputTokens.length); for (uint256 i = 0; i < swapExec.request.basicReq.outputTokens.length; i++) { if (swapExec.request.basicReq.outputTokens[i] == NATIVE_TOKEN_ADDRESS) { initialBalances[i] = swapExec.request.basicReq.receiver.balance; } else { initialBalances[i] = ERC20(swapExec.request.basicReq.outputTokens[i]).balanceOf( swapExec.request.basicReq.receiver ); } } // Check if fee is supposed to be deducted /// @dev fee has to be deducted from input token since swap may be for multiple output tokens (, uint256 feeAmount, address feeTaker) = AffiliateFeesLib.getAffiliateFees( swapExec.request.basicReq.inputAmount, swapExec.request.affiliateFees ); // Call the swap executor to execute the swap. /// @dev swap output tokens are expected to be sent directly to the user /// @dev expects swapPayload to perform a single, but multi output swap if (feeAmount > 0) { // Collect fee and execute swap SWAP_EXECUTOR.collectFeeAndExecuteSwap( swapExec.request.basicReq.inputToken, swapExec.request.basicReq.inputAmount, swapExec.swapRouter, swapExec.swapPayload, FEE_COLLECTOR, feeTaker, feeAmount ); } else { SWAP_EXECUTOR.executeSwap( swapExec.request.basicReq.inputToken, swapExec.request.basicReq.inputAmount, swapExec.swapRouter, swapExec.swapPayload ); } /// @dev fulfilAmounts are expected to be sent directly to user by the swap router // Get the final balances of receiver and check if the fulfilAmounts were met for (uint256 i = 0; i < swapExec.request.basicReq.outputTokens.length; i++) { uint256 finalBalance; if (swapExec.request.basicReq.outputTokens[i] == NATIVE_TOKEN_ADDRESS) { finalBalance = swapExec.request.basicReq.receiver.balance; } else { finalBalance = ERC20(swapExec.request.basicReq.outputTokens[i]).balanceOf( swapExec.request.basicReq.receiver ); } // Check if the fulfilAmounts were met if (finalBalance - initialBalances[i] < swapExec.fulfilAmounts[i]) revert MinOutputNotMet(); } emit RequestFulfilled(requestHash, SWAP_REQUEST_IMPL_ID, msg.sender, abi.encode(swapExec)); } /** * @notice this function is used when the transmitter submits a request that does not involve a swap. * @dev funds would be transferred to the router directly from the user. * @dev Saves the extraction details against the requestHash. * @param swapExec execution submitted by the transmitter for the request. When a request is settled beneficiary will receive funds. */ function _fulfilRequest(bytes32 requestHash, SwapExec memory swapExec) internal { // Calls Permit2 to transfer funds from user to BungeeGateway PERMIT2.permitWitnessTransferFrom( Permit2Lib.toPermit( swapExec.request.basicReq.inputToken, swapExec.request.basicReq.inputAmount, swapExec.request.basicReq.nonce, swapExec.request.basicReq.deadline ), Permit2Lib.transferDetails(swapExec.request.basicReq.inputAmount, address(this)), swapExec.request.basicReq.sender, requestHash, RequestLib.PERMIT2_ORDER_TYPE, swapExec.userSignature ); // Check if fee is supposed to be deducted (uint256 netInputAmount, uint256 feeAmount, address feeTaker) = AffiliateFeesLib.getAffiliateFees( swapExec.request.basicReq.inputAmount, swapExec.request.affiliateFees ); if (feeAmount > 0) { // @todo how to register BungeeGateway and SwapExecutor as router _collectFee(swapExec.request.basicReq.inputToken, feeAmount, feeTaker); } // Transfer fulfilAmounts from transmitter to user unchecked { for (uint256 i = 0; i < swapExec.fulfilAmounts.length; i++) { // Send the tokens in the exec to the receiver. _sendFundsToReceiver({ token: swapExec.request.basicReq.outputTokens[i], from: msg.sender, amount: swapExec.fulfilAmounts[i], to: swapExec.request.basicReq.receiver }); } } // Transfer input amount to beneficiary _sendFundsFromContract(address(swapExec.request.basicReq.inputToken), netInputAmount, swapExec.beneficiary); emit RequestFulfilled(requestHash, SWAP_REQUEST_IMPL_ID, msg.sender, abi.encode(swapExec)); } /// @dev delegates calldata execution to the CalldataExecutor contract /// @param to destination address /// @param minDestGasLimit minimum gas limit that should be used for the destination execution /// @param fulfilledAmounts array of amounts fulfilled on the destination in the request /// @param outputTokens array of output tokens in the request /// @param requestHash hash of the request /// @param executionData calldata to be executed on the destination function _executeCalldata( address to, uint256 minDestGasLimit, uint256[] memory fulfilledAmounts, address[] memory outputTokens, bytes32 requestHash, bytes memory executionData ) internal { // @review these checks & encoding must be here or in the CalldataExecutor contract? // Check and return with no action if the data is empty // Check and return with no action if the destination is invalid if (executionData.length == 0 || to == address(0) || to == address(this)) return; // Encodes request data in the payload bytes memory encodedData = abi.encodeCall( // @todo too many hops for destination calldata? BungeeGateway → CalldataExecutor → IBungeeExecutor → Aave deposit IBungeeExecutor.executeData, (fulfilledAmounts, requestHash, outputTokens, executionData) ); // Execute calldata CALLDATA_EXECUTOR.executeCalldata(to, encodedData, minDestGasLimit); } function _receiveMsg(bytes calldata payload) internal override { // Do nothing, not needed. } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; interface IBaseRouter { function releaseFunds(address token, uint256 amount, address recipient) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import {IBaseRouter} from "./IBaseRouter.sol"; import {FulfilExec, ExtractExec, Request} from "../common/SingleOutputStructs.sol"; interface IBaseRouterSingleOutput is IBaseRouter { function execute( uint256 amount, address inputToken, bytes32 requestHash, uint256 expiry, address receiverContract, address feeCollector, ExtractExec memory exec ) external; function fulfil(bytes32 requestHash, FulfilExec calldata fulfilExec, address transmitter) external payable; function withdrawRequestOnDestination(Request calldata request, bytes calldata withdrawRequestData) external; }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.17; interface IBungeeExecutor { function executeData( uint256[] calldata amounts, bytes32 commandHash, address[] calldata tokens, bytes memory callData ) external payable; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; interface ICalldataExecutor { function executeCalldata(address to, bytes memory encodedData, uint256 msgGasLimit) external returns (bool); }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; interface IFeeCollector { function registerFee(address feeTaker, uint256 feeAmount, address feeToken) external; function registerFee(address feeTaker, uint256 feeAmount, address feeToken, bytes32 requestHash) external; function settleFee(bytes32 requestHash) external; function refundFee(bytes32 requestHash, address to) external; }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.17; interface IStakeVault { function withdrawStake(address token, uint256 capacity, address transmitter) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import {IFeeCollector} from "./IFeeCollector.sol"; interface ISwapExecutor { function executeSwap(address token, uint256 amount, address swapRouter, bytes memory swapPayload) external; function executeSwapWithValue(address swapRouter, bytes memory swapPayload, uint256 msgValue) external; function collectFeeAndExecuteSwap( address token, uint256 amount, address swapRouter, bytes memory swapPayload, IFeeCollector feeCollector, address feeTaker, uint256 feeAmount ) external; }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; interface ISwitchboardRouter { function sendOutboundMsg( uint32 originChainId, uint32 switchboardId, uint8 msgId, uint256 destGasLimit, bytes calldata payload ) external payable; function receiveAndDeliverMsg(uint32 switchboardId, uint32 siblingChainId, bytes calldata payload) external; }
// SPDX-License-Identifier: GPL-2.0-or-later pragma solidity ^0.8.17; import {ERC20, SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; import {BytesLib} from "./BytesLib.sol"; /// @notice helpers for AffiliateFees struct library AffiliateFeesLib { /// @notice SafeTransferLib - library for safe and optimized operations on ERC20 tokens using SafeTransferLib for ERC20; /// @notice error when affiliate fee length is wrong error WrongAffiliateFeeLength(); /// @notice event emitted when affiliate fee is deducted event AffiliateFeeDeducted(address feeToken, address feeTakerAddress, uint256 feeAmount); // Precision used for affiliate fee calculation uint256 internal constant PRECISION = 10000000000000000; /** * @dev calculates & transfers fee to feeTakerAddress * @param bridgingAmount amount to be bridged * @param affiliateFees packed bytes containing feeTakerAddress and feeInBps * ensure the affiliateFees is packed as follows: * address feeTakerAddress (20 bytes) + uint48 feeInBps (6 bytes) = 26 bytes * @return bridgingAmount after deducting affiliate fees */ function getAffiliateFees( uint256 bridgingAmount, bytes memory affiliateFees ) internal pure returns (uint256, uint256, address) { address feeTakerAddress; uint256 feeAmount = 0; if (affiliateFees.length > 0) { uint48 feeInBps; if (affiliateFees.length != 26) revert WrongAffiliateFeeLength(); feeInBps = BytesLib.toUint48(affiliateFees, 20); feeTakerAddress = BytesLib.toAddress(affiliateFees, 0); if (feeInBps > 0) { // calculate fee feeAmount = ((bridgingAmount * feeInBps) / PRECISION); bridgingAmount -= feeAmount; } } return (bridgingAmount, feeAmount, feeTakerAddress); } function getAmountAfterFee(uint256 bridgingAmount, bytes memory affiliateFees) internal pure returns (uint256) { address feeTakerAddress; uint256 feeAmount = 0; if (affiliateFees.length > 0) { uint48 feeInBps; if (affiliateFees.length != 26) revert WrongAffiliateFeeLength(); feeInBps = BytesLib.toUint48(affiliateFees, 20); feeTakerAddress = BytesLib.toAddress(affiliateFees, 0); if (feeInBps > 0) { // calculate fee feeAmount = ((bridgingAmount * feeInBps) / PRECISION); bridgingAmount -= feeAmount; } } return (bridgingAmount); } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; // Library to authenticate the signer address. library AuthenticationLib { /// @notice authenticate a message hash signed by Bungee Protocol /// @param messageHash hash of the message /// @param signature signature of the message /// @return true if signature is valid function authenticate(bytes32 messageHash, bytes memory signature) internal pure returns (address) { bytes32 ethSignedMessageHash = getEthSignedMessageHash(messageHash); return recoverSigner(ethSignedMessageHash, signature); } function getEthSignedMessageHash(bytes32 _messageHash) public pure returns (bytes32) { /* Signature is produced by signing a keccak256 hash with the following format: "\x19Ethereum Signed Message\n" + len(msg) + msg */ return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", _messageHash)); } function recoverSigner(bytes32 _ethSignedMessageHash, bytes memory _signature) public pure returns (address) { (bytes32 r, bytes32 s, uint8 v) = splitSignature(_signature); return ecrecover(_ethSignedMessageHash, v, r, s); } function splitSignature(bytes memory sig) public pure returns (bytes32 r, bytes32 s, uint8 v) { require(sig.length == 65, "invalid signature length"); assembly { /* First 32 bytes stores the length of the signature add(sig, 32) = pointer of sig + 32 effectively, skips first 32 bytes of signature mload(p) loads next 32 bytes starting at the memory address p into memory */ // first 32 bytes, after the length prefix r := mload(add(sig, 32)) // second 32 bytes s := mload(add(sig, 64)) // final byte (first byte of the next 32 bytes) v := byte(0, mload(add(sig, 96))) } // implicitly return (r, s, v) } }
// SPDX-License-Identifier: Unlicense /* * @title Solidity Bytes Arrays Utils * @author Gonçalo Sá <[email protected]> * * @dev Bytes tightly packed arrays utility library for ethereum contracts written in Solidity. * The library lets you concatenate, slice and type cast bytes arrays both in memory and storage. */ pragma solidity >=0.8.4 <0.9.0; library BytesLib { function concat(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bytes memory) { bytes memory tempBytes; assembly { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // Store the length of the first bytes array at the beginning of // the memory for tempBytes. let length := mload(_preBytes) mstore(tempBytes, length) // Maintain a memory counter for the current write location in the // temp bytes array by adding the 32 bytes for the array length to // the starting location. let mc := add(tempBytes, 0x20) // Stop copying when the memory counter reaches the length of the // first bytes array. let end := add(mc, length) for { // Initialize a copy counter to the start of the _preBytes data, // 32 bytes into its memory. let cc := add(_preBytes, 0x20) } lt(mc, end) { // Increase both counters by 32 bytes each iteration. mc := add(mc, 0x20) cc := add(cc, 0x20) } { // Write the _preBytes data into the tempBytes memory 32 bytes // at a time. mstore(mc, mload(cc)) } // Add the length of _postBytes to the current length of tempBytes // and store it as the new length in the first 32 bytes of the // tempBytes memory. length := mload(_postBytes) mstore(tempBytes, add(length, mload(tempBytes))) // Move the memory counter back from a multiple of 0x20 to the // actual end of the _preBytes data. mc := end // Stop copying when the memory counter reaches the new combined // length of the arrays. end := add(mc, length) for { let cc := add(_postBytes, 0x20) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } // Update the free-memory pointer by padding our last write location // to 32 bytes: add 31 bytes to the end of tempBytes to move to the // next 32 byte block, then round down to the nearest multiple of // 32. If the sum of the length of the two arrays is zero then add // one before rounding down to leave a blank 32 bytes (the length block with 0). mstore( 0x40, and( add(add(end, iszero(add(length, mload(_preBytes)))), 31), not(31) // Round down to the nearest 32 bytes. ) ) } return tempBytes; } function concatStorage(bytes storage _preBytes, bytes memory _postBytes) internal { assembly { // Read the first 32 bytes of _preBytes storage, which is the length // of the array. (We don't need to use the offset into the slot // because arrays use the entire slot.) let fslot := sload(_preBytes.slot) // Arrays of 31 bytes or less have an even value in their slot, // while longer arrays have an odd value. The actual length is // the slot divided by two for odd values, and the lowest order // byte divided by two for even values. // If the slot is even, bitwise and the slot with 255 and divide by // two to get the length. If the slot is odd, bitwise and the slot // with -1 and divide by two. let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) let newlength := add(slength, mlength) // slength can contain both the length and contents of the array // if length < 32 bytes so let's prepare for that // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage switch add(lt(slength, 32), lt(newlength, 32)) case 2 { // Since the new array still fits in the slot, we just need to // update the contents of the slot. // uint256(bytes_storage) = uint256(bytes_storage) + uint256(bytes_memory) + new_length sstore( _preBytes.slot, // all the modifications to the slot are inside this // next block add( // we can just add to the slot contents because the // bytes we want to change are the LSBs fslot, add( mul( div( // load the bytes from memory mload(add(_postBytes, 0x20)), // zero all bytes to the right exp(0x100, sub(32, mlength)) ), // and now shift left the number of bytes to // leave space for the length in the slot exp(0x100, sub(32, newlength)) ), // increase length by the double of the memory // bytes length mul(mlength, 2) ) ) ) } case 1 { // The stored value fits in the slot, but the combined value // will exceed it. // get the keccak hash to get the contents of the array mstore(0x0, _preBytes.slot) let sc := add(keccak256(0x0, 0x20), div(slength, 32)) // save new length sstore(_preBytes.slot, add(mul(newlength, 2), 1)) // The contents of the _postBytes array start 32 bytes into // the structure. Our first read should obtain the `submod` // bytes that can fit into the unused space in the last word // of the stored array. To get this, we read 32 bytes starting // from `submod`, so the data we read overlaps with the array // contents by `submod` bytes. Masking the lowest-order // `submod` bytes allows us to add that value directly to the // stored value. let submod := sub(32, slength) let mc := add(_postBytes, submod) let end := add(_postBytes, mlength) let mask := sub(exp(0x100, submod), 1) sstore( sc, add( and(fslot, 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00), and(mload(mc), mask) ) ) for { mc := add(mc, 0x20) sc := add(sc, 1) } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) sstore(sc, mul(div(mload(mc), mask), mask)) } default { // get the keccak hash to get the contents of the array mstore(0x0, _preBytes.slot) // Start copying to the last used word of the stored array. let sc := add(keccak256(0x0, 0x20), div(slength, 32)) // save new length sstore(_preBytes.slot, add(mul(newlength, 2), 1)) // Copy over the first `submod` bytes of the new data as in // case 1 above. let slengthmod := mod(slength, 32) let mlengthmod := mod(mlength, 32) let submod := sub(32, slengthmod) let mc := add(_postBytes, submod) let end := add(_postBytes, mlength) let mask := sub(exp(0x100, submod), 1) sstore(sc, add(sload(sc), and(mload(mc), mask))) for { sc := add(sc, 1) mc := add(mc, 0x20) } lt(mc, end) { sc := add(sc, 1) mc := add(mc, 0x20) } { sstore(sc, mload(mc)) } mask := exp(0x100, sub(mc, end)) sstore(sc, mul(div(mload(mc), mask), mask)) } } } function slice(bytes memory _bytes, uint256 _start, uint256 _length) internal pure returns (bytes memory) { require(_length + 31 >= _length, "slice_overflow"); require(_bytes.length >= _start + _length, "slice_outOfBounds"); bytes memory tempBytes; assembly { switch iszero(_length) case 0 { // Get a location of some free memory and store it in tempBytes as // Solidity does for memory variables. tempBytes := mload(0x40) // The first word of the slice result is potentially a partial // word read from the original array. To read it, we calculate // the length of that partial word and start copying that many // bytes into the array. The first word we copy will start with // data we don't care about, but the last `lengthmod` bytes will // land at the beginning of the contents of the new array. When // we're done copying, we overwrite the full first word with // the actual length of the slice. let lengthmod := and(_length, 31) // The multiplication in the next line is necessary // because when slicing multiples of 32 bytes (lengthmod == 0) // the following copy loop was copying the origin's length // and then ending prematurely not copying everything it should. let mc := add(add(tempBytes, lengthmod), mul(0x20, iszero(lengthmod))) let end := add(mc, _length) for { // The multiplication in the next line has the same exact purpose // as the one above. let cc := add(add(add(_bytes, lengthmod), mul(0x20, iszero(lengthmod))), _start) } lt(mc, end) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { mstore(mc, mload(cc)) } mstore(tempBytes, _length) //update free-memory pointer //allocating the array padded to 32 bytes like the compiler does now mstore(0x40, and(add(mc, 31), not(31))) } //if we want a zero-length slice let's just return a zero-length array default { tempBytes := mload(0x40) //zero out the 32 bytes slice we are about to return //we need to do it because Solidity does not garbage collect mstore(tempBytes, 0) mstore(0x40, add(tempBytes, 0x20)) } } return tempBytes; } function toAddress(bytes memory _bytes, uint256 _start) internal pure returns (address) { require(_bytes.length >= _start + 20, "toAddress_outOfBounds"); address tempAddress; assembly { tempAddress := div(mload(add(add(_bytes, 0x20), _start)), 0x1000000000000000000000000) } return tempAddress; } function toUint8(bytes memory _bytes, uint256 _start) internal pure returns (uint8) { require(_bytes.length >= _start + 1, "toUint8_outOfBounds"); uint8 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x1), _start)) } return tempUint; } function toUint16(bytes memory _bytes, uint256 _start) internal pure returns (uint16) { require(_bytes.length >= _start + 2, "toUint16_outOfBounds"); uint16 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x2), _start)) } return tempUint; } function toUint32(bytes memory _bytes, uint256 _start) internal pure returns (uint32) { require(_bytes.length >= _start + 4, "toUint32_outOfBounds"); uint32 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x4), _start)) } return tempUint; } function toUint48(bytes memory _bytes, uint256 _start) internal pure returns (uint48) { require(_bytes.length >= _start + 6, "toUint48_outOfBounds"); uint48 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x6), _start)) } return tempUint; } function toUint64(bytes memory _bytes, uint256 _start) internal pure returns (uint64) { require(_bytes.length >= _start + 8, "toUint64_outOfBounds"); uint64 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x8), _start)) } return tempUint; } function toUint96(bytes memory _bytes, uint256 _start) internal pure returns (uint96) { require(_bytes.length >= _start + 12, "toUint96_outOfBounds"); uint96 tempUint; assembly { tempUint := mload(add(add(_bytes, 0xc), _start)) } return tempUint; } function toUint128(bytes memory _bytes, uint256 _start) internal pure returns (uint128) { require(_bytes.length >= _start + 16, "toUint128_outOfBounds"); uint128 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x10), _start)) } return tempUint; } function toUint256(bytes memory _bytes, uint256 _start) internal pure returns (uint256) { require(_bytes.length >= _start + 32, "toUint256_outOfBounds"); uint256 tempUint; assembly { tempUint := mload(add(add(_bytes, 0x20), _start)) } return tempUint; } function toBytes32(bytes memory _bytes, uint256 _start) internal pure returns (bytes32) { require(_bytes.length >= _start + 32, "toBytes32_outOfBounds"); bytes32 tempBytes32; assembly { tempBytes32 := mload(add(add(_bytes, 0x20), _start)) } return tempBytes32; } function equal(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { bool success = true; assembly { let length := mload(_preBytes) // if lengths don't match the arrays are not equal switch eq(length, mload(_postBytes)) case 1 { // cb is a circuit breaker in the for loop since there's // no said feature for inline assembly loops // cb = 1 - don't breaker // cb = 0 - break let cb := 1 let mc := add(_preBytes, 0x20) let end := add(mc, length) for { let cc := add(_postBytes, 0x20) // the next line is the loop condition: // while(uint256(mc < end) + cb == 2) } eq(add(lt(mc, end), cb), 2) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { // if any of these checks fails then arrays are not equal if iszero(eq(mload(mc), mload(cc))) { // unsuccess: success := 0 cb := 0 } } } default { // unsuccess: success := 0 } } return success; } function equal_nonAligned(bytes memory _preBytes, bytes memory _postBytes) internal pure returns (bool) { bool success = true; assembly { let length := mload(_preBytes) // if lengths don't match the arrays are not equal switch eq(length, mload(_postBytes)) case 1 { // cb is a circuit breaker in the for loop since there's // no said feature for inline assembly loops // cb = 1 - don't breaker // cb = 0 - break let cb := 1 let endMinusWord := add(_preBytes, length) let mc := add(_preBytes, 0x20) let cc := add(_postBytes, 0x20) for { // the next line is the loop condition: // while(uint256(mc < endWord) + cb == 2) } eq(add(lt(mc, endMinusWord), cb), 2) { mc := add(mc, 0x20) cc := add(cc, 0x20) } { // if any of these checks fails then arrays are not equal if iszero(eq(mload(mc), mload(cc))) { // unsuccess: success := 0 cb := 0 } } // Only if still successful // For <1 word tail bytes if gt(success, 0) { // Get the remainder of length/32 // length % 32 = AND(length, 32 - 1) let numTailBytes := and(length, 0x1f) let mcRem := mload(mc) let ccRem := mload(cc) for { let i := 0 // the next line is the loop condition: // while(uint256(i < numTailBytes) + cb == 2) } eq(add(lt(i, numTailBytes), cb), 2) { i := add(i, 1) } { if iszero(eq(byte(i, mcRem), byte(i, ccRem))) { // unsuccess: success := 0 cb := 0 } } } } default { // unsuccess: success := 0 } } return success; } function equalStorage(bytes storage _preBytes, bytes memory _postBytes) internal view returns (bool) { bool success = true; assembly { // we know _preBytes_offset is 0 let fslot := sload(_preBytes.slot) // Decode the length of the stored array like in concatStorage(). let slength := div(and(fslot, sub(mul(0x100, iszero(and(fslot, 1))), 1)), 2) let mlength := mload(_postBytes) // if lengths don't match the arrays are not equal switch eq(slength, mlength) case 1 { // slength can contain both the length and contents of the array // if length < 32 bytes so let's prepare for that // v. http://solidity.readthedocs.io/en/latest/miscellaneous.html#layout-of-state-variables-in-storage if iszero(iszero(slength)) { switch lt(slength, 32) case 1 { // blank the last byte which is the length fslot := mul(div(fslot, 0x100), 0x100) if iszero(eq(fslot, mload(add(_postBytes, 0x20)))) { // unsuccess: success := 0 } } default { // cb is a circuit breaker in the for loop since there's // no said feature for inline assembly loops // cb = 1 - don't breaker // cb = 0 - break let cb := 1 // get the keccak hash to get the contents of the array mstore(0x0, _preBytes.slot) let sc := keccak256(0x0, 0x20) let mc := add(_postBytes, 0x20) let end := add(mc, mlength) // the next line is the loop condition: // while(uint256(mc < end) + cb == 2) for { } eq(add(lt(mc, end), cb), 2) { sc := add(sc, 1) mc := add(mc, 0x20) } { if iszero(eq(sload(sc), mload(mc))) { // unsuccess: success := 0 cb := 0 } } } } } default { // unsuccess: success := 0 } } return success; } }
// SPDX-License-Identifier: UNLICENSED pragma solidity ^0.8.17; import {ISignatureTransfer} from "permit2/src/interfaces/ISignatureTransfer.sol"; // Library to get Permit 2 related data. library Permit2Lib { string public constant TOKEN_PERMISSIONS_TYPE = "TokenPermissions(address token,uint256 amount)"; function toPermit( address inputToken, uint256 inputAmount, uint256 nonce, uint256 deadline ) internal pure returns (ISignatureTransfer.PermitTransferFrom memory) { return ISignatureTransfer.PermitTransferFrom({ permitted: ISignatureTransfer.TokenPermissions({token: inputToken, amount: inputAmount}), nonce: nonce, deadline: deadline }); } function transferDetails( uint256 amount, address spender ) internal pure returns (ISignatureTransfer.SignatureTransferDetails memory) { return ISignatureTransfer.SignatureTransferDetails({to: spender, requestedAmount: amount}); } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.17; import {ERC20, SafeTransferLib} from "solmate/src/utils/SafeTransferLib.sol"; error ZeroAddress(); /** * @title RescueFundsLib * @dev A library that provides a function to rescue funds from a contract. */ library RescueFundsLib { /** * @dev The address used to identify ETH. */ address public constant ETH_ADDRESS = address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); /** * @dev thrown when the given token address don't have any code */ error InvalidTokenAddress(); /** * @dev Rescues funds from a contract. * @param token_ The address of the token contract. * @param rescueTo_ The address of the user. * @param amount_ The amount of tokens to be rescued. */ function rescueFunds(address token_, address rescueTo_, uint256 amount_) internal { if (rescueTo_ == address(0)) revert ZeroAddress(); if (token_ == ETH_ADDRESS) { SafeTransferLib.safeTransferETH(rescueTo_, amount_); } else { if (token_.code.length == 0) revert InvalidTokenAddress(); SafeTransferLib.safeTransfer(ERC20(token_), rescueTo_, amount_); } } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.17; import {BasicRequest, Request, ExtractExec} from "../common/SingleOutputStructs.sol"; import {Permit2Lib} from "./Permit2Lib.sol"; /// @notice helpers for handling BasicRequest library BasicRequestLib { bytes internal constant BASIC_REQUEST_TYPE = abi.encodePacked( "BasicRequest(", "uint256 originChainId,", "uint256 destinationChainId,", "uint256 deadline,", "uint256 nonce,", "address sender,", "address receiver,", "address delegate,", "address bungeeGateway,", "uint32 switchboardId,", "address inputToken,", "uint256 inputAmount,", "address outputToken,", "uint256 minOutputAmount," "uint256 refuelAmount)" ); bytes32 internal constant BASIC_REQUEST_TYPE_HASH = keccak256(BASIC_REQUEST_TYPE); /// @notice Hash of BasicRequest struct on the origin chain /// @dev enforces originChainId to be the current chainId. Resulting hash would be the same on all chains. /// @dev helps avoid extra checking of chainId in the contract /// @param basicReq BasicRequest object to be hashed function originHash(BasicRequest memory basicReq) internal view returns (bytes32) { return keccak256( abi.encodePacked( BASIC_REQUEST_TYPE_HASH, abi.encode( block.chainid, basicReq.destinationChainId, basicReq.deadline, basicReq.nonce, basicReq.sender, basicReq.receiver, basicReq.delegate, basicReq.bungeeGateway, basicReq.switchboardId, basicReq.inputToken, basicReq.inputAmount, basicReq.outputToken, basicReq.minOutputAmount, basicReq.refuelAmount ) ) ); } /// @notice Hash of BasicRequest struct on the destination chain /// @dev enforces destinationChain to be the current chainId. Resulting hash would be the same on all chains. /// @dev helps avoid extra checking of chainId in the contract /// @param basicReq BasicRequest object to be hashed function destinationHash(BasicRequest memory basicReq) internal view returns (bytes32) { return keccak256( abi.encodePacked( BASIC_REQUEST_TYPE_HASH, abi.encode( basicReq.originChainId, block.chainid, basicReq.deadline, basicReq.nonce, basicReq.sender, basicReq.receiver, basicReq.delegate, basicReq.bungeeGateway, basicReq.switchboardId, basicReq.inputToken, basicReq.inputAmount, basicReq.outputToken, basicReq.minOutputAmount, basicReq.refuelAmount ) ) ); } } /// @title Bungee Request Library. /// @author bungee protocol /// @notice This library is responsible for all the hashing related to Request object. library RequestLib { using BasicRequestLib for BasicRequest; // Permit 2 Witness Order Type. string internal constant PERMIT2_ORDER_TYPE = string( abi.encodePacked( "Request witness)", abi.encodePacked(BasicRequestLib.BASIC_REQUEST_TYPE, REQUEST_TYPE), Permit2Lib.TOKEN_PERMISSIONS_TYPE ) ); // REQUEST TYPE encode packed bytes internal constant REQUEST_TYPE = abi.encodePacked( "Request(", "BasicRequest basicReq,", "address swapOutputToken,", "uint256 minSwapOutput,", "bytes32 metadata,", "bytes affiliateFees)" ); // EXTRACT EXEC TYPE. bytes internal constant EXTRACT_EXEC_TYPE = abi.encodePacked( "ExtractExec(", "Request request,", "address router,", "uint256 promisedAmount,", "uint256 promisedRefuelAmount,", "bytes routerData,", "bytes swapPayload,", "address swapRouter,", "bytes userSignature,", "address beneficiary)" ); // BUNGEE_REQUEST_TYPE bytes internal constant BUNGEE_REQUEST_TYPE = abi.encodePacked(REQUEST_TYPE, BasicRequestLib.BASIC_REQUEST_TYPE); // Keccak Hash of BUNGEE_REQUEST_TYPE bytes32 internal constant BUNGEE_REQUEST_TYPE_HASH = keccak256(BUNGEE_REQUEST_TYPE); // Exec Type. bytes internal constant EXEC_TYPE = abi.encodePacked(EXTRACT_EXEC_TYPE, REQUEST_TYPE); // Keccak Hash of Exec Type. bytes32 internal constant EXTRACT_EXEC_TYPE_HASH = keccak256(EXEC_TYPE); /// @notice Hash of request on the origin chain /// @param request request that is signe by the user function hashOriginRequest(Request memory request) internal view returns (bytes32) { return keccak256( abi.encode( BUNGEE_REQUEST_TYPE_HASH, request.basicReq.originHash(), request.swapOutputToken, request.minSwapOutput, request.metadata, keccak256(request.affiliateFees) ) ); } /// @notice Hash of request on the destination chain /// @param request request signed by the user function hashDestinationRequest(Request memory request) internal view returns (bytes32) { return keccak256( abi.encode( BUNGEE_REQUEST_TYPE_HASH, request.basicReq.destinationHash(), request.swapOutputToken, request.minSwapOutput, request.metadata, keccak256(request.affiliateFees) ) ); } /// @notice Hash of Extract Exec on the origin chain /// @param execution Transmitter submitted extract exec object function hashOriginExtractExec(ExtractExec memory execution) internal view returns (bytes32) { return keccak256( abi.encode( EXTRACT_EXEC_TYPE_HASH, hashOriginRequest(execution.request), execution.router, execution.promisedAmount, execution.promisedRefuelAmount, keccak256(execution.routerData), keccak256(execution.swapPayload), execution.swapRouter, keccak256(execution.userSignature), execution.beneficiary ) ); } /// @notice hash a batch of extract execs /// @param extractExecs batch of extract execs to be hashed function hashOriginBatch(ExtractExec[] memory extractExecs) internal view returns (bytes32) { unchecked { bytes32 outputHash = keccak256("BUNGEE_EXTRACT_EXEC"); // Hash all of the extract execs present in the batch. for (uint256 i = 0; i < extractExecs.length; i++) { outputHash = keccak256(abi.encode(outputHash, hashOriginExtractExec(extractExecs[i]))); } return outputHash; } } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.17; import {BasicRequest, Request, SwapExec} from "../common/SwapRequestStructs.sol"; import {Permit2Lib} from "./Permit2Lib.sol"; /// @notice helpers for handling CommandInfo objects library BasicRequestLib { bytes internal constant BASIC_REQUEST_TYPE = abi.encodePacked( "BasicRequest(", "uint256 chainId,", "uint256 deadline,", "uint256 nonce,", "address sender,", "address receiver,", "address bungeeGateway,", "address inputToken,", "uint256 inputAmount,", "address[] outputTokens,", "uint256[] minOutputAmounts)" ); bytes32 internal constant BASIC_REQUEST_TYPE_HASH = keccak256(BASIC_REQUEST_TYPE); /// @notice Hash of BasicRequest struct on the swap chain /// @dev enforces chainId to be the current chainId /// @dev helps avoid extra checking of chainId in the contract /// @param basicReq BasicRequest object to be hashed function hash(BasicRequest memory basicReq) internal view returns (bytes32) { return keccak256( abi.encode( BASIC_REQUEST_TYPE_HASH, block.chainid, basicReq.deadline, basicReq.nonce, basicReq.sender, basicReq.receiver, basicReq.bungeeGateway, basicReq.inputToken, basicReq.inputAmount, keccak256(abi.encodePacked(basicReq.outputTokens)), keccak256(abi.encodePacked(basicReq.minOutputAmounts)) ) ); } } /// @title Bungee Request Library. /// @author bungee protocol /// @notice This library is responsible for all the hashing related to Request object. library RequestLib { using BasicRequestLib for BasicRequest; // Permit 2 Witness Order Type. string internal constant PERMIT2_ORDER_TYPE = string( abi.encodePacked( "Request witness)", abi.encodePacked(BasicRequestLib.BASIC_REQUEST_TYPE, REQUEST_TYPE), Permit2Lib.TOKEN_PERMISSIONS_TYPE ) ); // REQUEST TYPE encode packed bytes internal constant REQUEST_TYPE = abi.encodePacked( "Request(", "BasicRequest basicReq,", "address[] exclusiveTransmitters,", "bytes32 metadata,", "bytes affiliateFees,", "uint256 minDestGas,", "bytes destinationPayload)" ); // SWAP EXEC TYPE. // @review this lib again, make sure things are solid bytes internal constant SWAP_EXEC_TYPE = abi.encodePacked( "SwapExec(", "Request request,", "uint256[] fulfilAmounts,", "bytes swapPayload,", "address swapRouter,", "bytes userSignature,", "address beneficiary)" ); // BUNGEE_REQUEST_TYPE bytes internal constant BUNGEE_REQUEST_TYPE = abi.encodePacked(REQUEST_TYPE, BasicRequestLib.BASIC_REQUEST_TYPE); // Keccak Hash of BUNGEE_REQUEST_TYPE bytes32 internal constant BUNGEE_REQUEST_TYPE_HASH = keccak256(BUNGEE_REQUEST_TYPE); // Exec Type. bytes internal constant EXEC_TYPE = abi.encodePacked(SWAP_EXEC_TYPE, REQUEST_TYPE); // Keccak Hash of Exec Type. bytes32 internal constant SWAP_EXEC_TYPE_HASH = keccak256(EXEC_TYPE); /// @notice Hash of request on the swap chain /// @param request request that is signe by the user function hashRequest(Request memory request) internal view returns (bytes32) { return keccak256( abi.encode( BUNGEE_REQUEST_TYPE_HASH, request.basicReq.hash(), keccak256(abi.encodePacked(request.exclusiveTransmitters)), request.metadata, keccak256(request.affiliateFees), request.minDestGas, keccak256(request.destinationPayload) ) ); } /// @notice Hash of Swap Exec on the swap chain /// @param execution Transmitter submitted swap exec object function hashSwapExec(SwapExec memory execution) internal view returns (bytes32) { return keccak256( abi.encode( SWAP_EXEC_TYPE_HASH, hashRequest(execution.request), keccak256(abi.encodePacked(execution.fulfilAmounts)), keccak256(execution.swapPayload), execution.swapRouter, keccak256(execution.userSignature), execution.beneficiary ) ); } /// @notice hash a batch of swap execs /// @param swapExecs batch of swap execs to be hashed function hashBatch(SwapExec[] memory swapExecs) internal view returns (bytes32) { unchecked { bytes32 outputHash = keccak256("BUNGEE_SWAP_EXEC"); // Hash all of the swap execs present in the batch. for (uint256 i = 0; i < swapExecs.length; i++) { outputHash = keccak256(abi.encode(outputHash, hashSwapExec(swapExecs[i]))); } return outputHash; } } }
// SPDX-License-Identifier: GPL-3.0-only pragma solidity ^0.8.17; import {OnlyOwner, OnlyNominee} from "../common/BungeeErrors.sol"; abstract contract Ownable { address private _owner; address private _nominee; event OwnerNominated(address indexed nominee); event OwnerClaimed(address indexed claimer); constructor(address owner_) { _claimOwner(owner_); } modifier onlyOwner() { if (msg.sender != _owner) { revert OnlyOwner(); } _; } function owner() public view returns (address) { return _owner; } function nominee() public view returns (address) { return _nominee; } function nominateOwner(address nominee_) external { if (msg.sender != _owner) { revert OnlyOwner(); } _nominee = nominee_; emit OwnerNominated(_nominee); } function claimOwner() external { if (msg.sender != _nominee) { revert OnlyNominee(); } _claimOwner(msg.sender); } function _claimOwner(address claimer_) internal { _owner = claimer_; _nominee = address(0); emit OwnerClaimed(claimer_); } }
{ "optimizer": { "enabled": true, "runs": 200 }, "outputSelection": { "*": { "*": [ "evm.bytecode", "evm.deployedBytecode", "devdoc", "userdoc", "metadata", "abi" ] } }, "libraries": {} }
Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
[{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"address","name":"_solverSigner","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"ActionFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"ActionsFailed","type":"error"},{"inputs":[],"name":"InvalidNonce","type":"error"},{"inputs":[],"name":"InvalidSigner","type":"error"},{"inputs":[],"name":"InvalidSwapActions","type":"error"},{"inputs":[],"name":"InvalidTokenAddress","type":"error"},{"inputs":[],"name":"OnlyNominee","type":"error"},{"inputs":[],"name":"OnlyOwner","type":"error"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"SwapActionFailed","type":"error"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"}],"name":"SwapOutputInsufficient","type":"error"},{"inputs":[],"name":"ZeroAddress","type":"error"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"claimer","type":"address"}],"name":"OwnerClaimed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"nominee","type":"address"}],"name":"OwnerNominated","type":"event"},{"stateMutability":"payable","type":"fallback"},{"inputs":[],"name":"NATIVE_TOKEN_ADDRESS","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"claimOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"nominee_","type":"address"}],"name":"nominateOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nominee","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"name":"nonceUsed","outputs":[{"internalType":"bool","name":"isNonceUsed","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes[]","name":"approvals","type":"bytes[]"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Solver.Action[]","name":"actions","type":"tuple[]"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"performActions","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes[]","name":"approvals","type":"bytes[]"},{"internalType":"address","name":"bungeeGateway","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"components":[{"internalType":"uint256","name":"fulfilExecIndex","type":"uint256"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Solver.Action","name":"swapActionData","type":"tuple"}],"internalType":"struct Solver.SwapAction[]","name":"swapActions","type":"tuple[]"},{"components":[{"components":[{"components":[{"internalType":"uint256","name":"originChainId","type":"uint256"},{"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"delegate","type":"address"},{"internalType":"address","name":"bungeeGateway","type":"address"},{"internalType":"uint32","name":"switchboardId","type":"uint32"},{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"uint256","name":"inputAmount","type":"uint256"},{"internalType":"address","name":"outputToken","type":"address"},{"internalType":"uint256","name":"minOutputAmount","type":"uint256"},{"internalType":"uint256","name":"refuelAmount","type":"uint256"}],"internalType":"struct BasicRequest","name":"basicReq","type":"tuple"},{"internalType":"address","name":"swapOutputToken","type":"address"},{"internalType":"uint256","name":"minSwapOutput","type":"uint256"},{"internalType":"bytes32","name":"metadata","type":"bytes32"},{"internalType":"bytes","name":"affiliateFees","type":"bytes"}],"internalType":"struct Request","name":"request","type":"tuple"},{"internalType":"address","name":"fulfilRouter","type":"address"},{"internalType":"uint256","name":"fulfilAmount","type":"uint256"},{"internalType":"uint256","name":"refuelFulfilAmount","type":"uint256"},{"internalType":"bytes","name":"routerData","type":"bytes"},{"internalType":"uint256","name":"msgValue","type":"uint256"}],"internalType":"struct FulfilExec[]","name":"fulfilExecs","type":"tuple[]"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"performBatchFulfilment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Solver.Action","name":"action","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"performExtraction","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"bytes[]","name":"approvals","type":"bytes[]"},{"internalType":"address","name":"bungeeGateway","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Solver.Action","name":"swapActionData","type":"tuple"},{"components":[{"components":[{"components":[{"internalType":"uint256","name":"originChainId","type":"uint256"},{"internalType":"uint256","name":"destinationChainId","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint256","name":"nonce","type":"uint256"},{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"receiver","type":"address"},{"internalType":"address","name":"delegate","type":"address"},{"internalType":"address","name":"bungeeGateway","type":"address"},{"internalType":"uint32","name":"switchboardId","type":"uint32"},{"internalType":"address","name":"inputToken","type":"address"},{"internalType":"uint256","name":"inputAmount","type":"uint256"},{"internalType":"address","name":"outputToken","type":"address"},{"internalType":"uint256","name":"minOutputAmount","type":"uint256"},{"internalType":"uint256","name":"refuelAmount","type":"uint256"}],"internalType":"struct BasicRequest","name":"basicReq","type":"tuple"},{"internalType":"address","name":"swapOutputToken","type":"address"},{"internalType":"uint256","name":"minSwapOutput","type":"uint256"},{"internalType":"bytes32","name":"metadata","type":"bytes32"},{"internalType":"bytes","name":"affiliateFees","type":"bytes"}],"internalType":"struct Request","name":"request","type":"tuple"},{"internalType":"address","name":"fulfilRouter","type":"address"},{"internalType":"uint256","name":"fulfilAmount","type":"uint256"},{"internalType":"uint256","name":"refuelFulfilAmount","type":"uint256"},{"internalType":"bytes","name":"routerData","type":"bytes"},{"internalType":"uint256","name":"msgValue","type":"uint256"}],"internalType":"struct FulfilExec","name":"fulfilExec","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"performFulfilment","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"nonce","type":"uint256"},{"components":[{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"bytes","name":"data","type":"bytes"}],"internalType":"struct Solver.Action","name":"action","type":"tuple"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"performSettlement","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"token_","type":"address"},{"internalType":"address","name":"rescueTo_","type":"address"},{"internalType":"uint256","name":"amount_","type":"uint256"}],"name":"rescueFunds","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_solverSigner","type":"address"}],"name":"setSolverSigner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"messageHash","type":"bytes32"},{"internalType":"bytes","name":"signature","type":"bytes"}],"name":"verifySignature","outputs":[],"stateMutability":"view","type":"function"},{"stateMutability":"payable","type":"receive"}]
Contract Creation Code
60806040523480156200001157600080fd5b50604051620022f0380380620022f08339810160408190526200003491620000d8565b81620000408162000068565b50600280546001600160a01b0319166001600160a01b03929092169190911790555062000110565b600080546001600160a01b0383166001600160a01b0319918216811783556001805490921690915560405190917ffbe19c9b601f5ee90b44c7390f3fa2319eba01762d34ee372aeafd59b25c7f8791a250565b80516001600160a01b0381168114620000d357600080fd5b919050565b60008060408385031215620000ec57600080fd5b620000f783620000bb565b91506200010760208401620000bb565b90509250929050565b6121d080620001206000396000f3fe6080604052600436106100d55760003560e01c8063760ed2781161007957806398fc5c101161005657806398fc5c1014610248578063daca6f7814610268578063df2ebdbb14610288578063e4a9b20e1461016a57005b8063760ed278146101ca5780638da5cb5b146101ea57806394d0d3a61461020857005b8063444eac45116100b2578063444eac451461014a578063541e34151461016a5780635b94db271461018a5780636ccae054146101aa57005b806320f99c0a146100de57806333601e01146101155780633bd1adec1461013557005b366100dc57005b005b3480156100ea57600080fd5b506001546001600160a01b03165b6040516001600160a01b0390911681526020015b60405180910390f35b34801561012157600080fd5b506100dc610130366004611628565b6102b0565b34801561014157600080fd5b506100dc610740565b34801561015657600080fd5b506100dc610165366004611709565b610776565b34801561017657600080fd5b506100dc61018536600461172d565b6107c3565b34801561019657600080fd5b506100dc6101a5366004611709565b61082e565b3480156101b657600080fd5b506100dc6101c536600461179c565b6108a3565b3480156101d657600080fd5b506100dc6101e53660046117dd565b6108de565b3480156101f657600080fd5b506000546001600160a01b03166100f8565b34801561021457600080fd5b50610238610223366004611880565b60036020526000908152604090205460ff1681565b604051901515815260200161010c565b34801561025457600080fd5b506100dc610263366004611937565b610a11565b34801561027457600080fd5b506100dc610283366004611a1e565b610e2f565b34801561029457600080fd5b506100f873eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b6102f146308b8b8b8b8b8b8b6040516020016102d499989796959493929190611d72565b604051602081830303815290604052805190602001208383610e2f565b886000526003602052604060002060ff815416156103175763756688fe6000526004601cfd5b60019055861561038b5760005b878110156103895760008060008b8b8581811061034357610343611ded565b90506020028101906103559190611e03565b810190610362919061179c565b925092509250610373838383610e92565b505050808061038190611e5f565b915050610324565b505b600061039a6020860186611709565b6001600160a01b03161461055b5782515161016001516001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1460008161044c5784515161016001516040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015610423573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104479190611e78565b61044e565b475b9050600061045b87610ee2565b905080610483576040516387eda65f60e01b8152600060048201526024015b60405180910390fd5b60008361050a5786515161016001516040516370a0823160e01b815230600482015284916001600160a01b0316906370a0823190602401602060405180830381865afa1580156104d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fb9190611e78565b6105059190611e91565b610514565b6105148347611e91565b9050808760400151111561053e576040516370126e4560e01b81526000600482015260240161047a565b6040870181905283156105565760a087018190529750875b505050505b604080516001808252818301909252600091816020015b61064e6040805161032081019091526000610160820181815261018083018290526101a083018290526101c083018290526101e08301829052610200830182905261022083018290526102408301829052610260830182905261028083018290526102a083018290526102c083018290526102e08301829052610300830182905260c0830190815260e08301829052610100830182905261012083019190915260606101408301528190815260200160006001600160a01b03168152602001600081526020016000815260200160608152602001600081525090565b815260200190600190039081610572579050509050838160008151811061067757610677611ded565b6020026020010181905250866001600160a01b0316632a02224187836040516024016106a39190611eec565b60408051601f198184030181529181526020820180516001600160e01b031663cf376c1d60e01b179052516001600160e01b031960e085901b1681526106ec9190600401611eff565b60006040518083038185885af115801561070a573d6000803e3d6000fd5b50505050506040513d6000823e601f3d908101601f191682016040526107339190810190611f12565b5050505050505050505050565b6001546001600160a01b0316331461076b57604051637c91ccdd60e01b815260040160405180910390fd5b61077433610f0f565b565b6000546001600160a01b031633146107a157604051635fc483c560e01b815260040160405180910390fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6107dd463086866040516020016102d49493929190611f7f565b836000526003602052604060002060ff815416156108035763756688fe6000526004601cfd5b60019055600061081284610ee2565b9050806108275763080a1c276000526004601cfd5b5050505050565b6000546001600160a01b0316331461085957604051635fc483c560e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce2290600090a250565b6000546001600160a01b031633146108ce57604051635fc483c560e01b815260040160405180910390fd5b6108d9838383610f62565b505050565b6108fe463089898989896040516020016102d49796959493929190611fd5565b866000526003602052604060002060ff815416156109245763756688fe6000526004601cfd5b6001905584156109985760005b8581101561099657600080600089898581811061095057610950611ded565b90506020028101906109629190611e03565b81019061096f919061179c565b925092509250610980838383610e92565b505050808061098e90611e5f565b915050610931565b505b60005b83811015610a075760006109d18686848181106109ba576109ba611ded565b90506020028101906109cc9190612068565b610ee2565b9050806109f45760405163505737f760e11b81526004810183905260240161047a565b50806109ff81611e5f565b91505061099b565b5050505050505050565b610a3746308c8c8c8c8c8c8c8c6040516020016102d49a99989796959493929190612088565b896000526003602052604060002060ff81541615610a5d5763756688fe6000526004601cfd5b600190558715610ad15760005b88811015610acf5760008060008c8c85818110610a8957610a89611ded565b9050602002810190610a9b9190611e03565b810190610aa8919061179c565b925092509250610ab9838383610e92565b5050508080610ac790611e5f565b915050610a6a565b505b8251841415600085111615610aee576391433bb26000526004601cfd5b60005b84811015610e0d5736868683818110610b0c57610b0c611ded565b9050602002810190610b1e9190612171565b9050600073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b031686836000013581518110610b5657610b56611ded565b6020026020010151600001516000015161016001516001600160a01b0316149050600081610c145786836000013581518110610b9457610b94611ded565b6020908102919091010151515161016001516040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015610beb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c0f9190611e78565b610c16565b475b90506000610c2a6109cc6020860186612068565b905080610c4d576040516387eda65f60e01b81526004810186905260240161047a565b600083610cf5578289866000013581518110610c6b57610c6b611ded565b6020908102919091010151515161016001516040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015610cc2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ce69190611e78565b610cf09190611e91565b610cff565b610cff8347611e91565b90508089866000013581518110610d1857610d18611ded565b6020026020010151604001511115610d46576040516370126e4560e01b81526004810187905260240161047a565b8089866000013581518110610d5d57610d5d611ded565b602002602001015160400181815250508315610df55788856000013581518110610d8957610d89611ded565b602002602001015160a0015181610da09190611e91565b610daa908d612187565b9b5088856000013581518110610dc257610dc2611ded565b60200260200101516040015189866000013581518110610de457610de4611ded565b602002602001015160a00181815250505b50505050508080610e0590611e5f565b915050610af1565b50866001600160a01b0316632a02224187856040516024016106a39190611eec565b610e6f8383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610fee92505050565b6002546001600160a01b039081169116146108d95763815e1d646000526004601cfd5b816014528060345263095ea7b360601b60005260206000604460106000875af18060016000511416610ed757803d853b151710610ed757633e3f8f736000526004601cfd5b506000603452505050565b60006060820135604051816040850135602086010182376000808383602088013588355af1949350505050565b600080546001600160a01b0383166001600160a01b0319918216811783556001805490921690915560405190917ffbe19c9b601f5ee90b44c7390f3fa2319eba01762d34ee372aeafd59b25c7f8791a250565b6001600160a01b038216610f895760405163d92e233d60e01b815260040160405180910390fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed196001600160a01b03841601610fb8576108d9828261105e565b826001600160a01b03163b600003610fe357604051630f58058360e11b815260040160405180910390fd5b6108d98383836110af565b600080611048846040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c8101829052600090605c01604051602081830303815290604052805190602001209050919050565b90506110548184611136565b9150505b92915050565b600080600080600085875af19050806108d95760405162461bcd60e51b815260206004820152601360248201527211551217d514905394d1915497d19052531151606a1b604482015260640161047a565b600060405163a9059cbb60e01b81526001600160a01b0384166004820152826024820152602060006044836000895af13d15601f3d11600160005114161716915050806111305760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b604482015260640161047a565b50505050565b600080600080611145856111b5565b6040805160008152602081018083528b905260ff8316918101919091526060810184905260808101839052929550909350915060019060a0016020604051602081039080840390855afa1580156111a0573d6000803e3d6000fd5b5050604051601f190151979650505050505050565b6000806000835160411461120b5760405162461bcd60e51b815260206004820152601860248201527f696e76616c6964207369676e6174757265206c656e6774680000000000000000604482015260640161047a565b50505060208101516040820151606090920151909260009190911a90565b60008083601f84011261123b57600080fd5b5081356001600160401b0381111561125257600080fd5b6020830191508360208260051b850101111561126d57600080fd5b9250929050565b6001600160a01b038116811461128957600080fd5b50565b803561129781611274565b919050565b6000606082840312156112ae57600080fd5b50919050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b03811182821017156112ec576112ec6112b4565b60405290565b6040516101c081016001600160401b03811182821017156112ec576112ec6112b4565b60405160c081016001600160401b03811182821017156112ec576112ec6112b4565b604051601f8201601f191681016001600160401b038111828210171561135f5761135f6112b4565b604052919050565b803563ffffffff8116811461129757600080fd5b60006001600160401b03821115611394576113946112b4565b50601f01601f191660200190565b600082601f8301126113b357600080fd5b81356113c66113c18261137b565b611337565b8181528460208386010111156113db57600080fd5b816020850160208301376000918101602001919091529392505050565b600081830361024081121561140c57600080fd5b6114146112ca565b91506101c08082121561142657600080fd5b61142e6112f2565b91508335825260208401356020830152604084013560408301526060840135606083015261145e6080850161128c565b608083015261146f60a0850161128c565b60a083015261148060c0850161128c565b60c083015261149160e0850161128c565b60e08301526101006114a4818601611367565b908301526101206114b685820161128c565b9083015261014084810135908301526101606114d381860161128c565b9083015261018084810135908301526101a080850135908301528183526114fb81850161128c565b602084015250506101e0820135604082015261020082013560608201526102208201356001600160401b0381111561153257600080fd5b61153e848285016113a2565b60808301525092915050565b600060c0828403121561155c57600080fd5b611564611315565b905081356001600160401b038082111561157d57600080fd5b611589858386016113f8565b83526115976020850161128c565b6020840152604084013560408401526060840135606084015260808401359150808211156115c457600080fd5b506115d1848285016113a2565b60808301525060a082013560a082015292915050565b60008083601f8401126115f957600080fd5b5081356001600160401b0381111561161057600080fd5b60208301915083602082850101111561126d57600080fd5b600080600080600080600080600060e08a8c03121561164657600080fd5b8935985060208a01356001600160401b038082111561166457600080fd5b6116708d838e01611229565b909a50985088915061168460408d0161128c565b975060608c0135965060808c01359150808211156116a157600080fd5b6116ad8d838e0161129c565b955060a08c01359150808211156116c357600080fd5b6116cf8d838e0161154a565b945060c08c01359150808211156116e557600080fd5b506116f28c828d016115e7565b915080935050809150509295985092959850929598565b60006020828403121561171b57600080fd5b813561172681611274565b9392505050565b6000806000806060858703121561174357600080fd5b8435935060208501356001600160401b038082111561176157600080fd5b61176d8883890161129c565b9450604087013591508082111561178357600080fd5b50611790878288016115e7565b95989497509550505050565b6000806000606084860312156117b157600080fd5b83356117bc81611274565b925060208401356117cc81611274565b929592945050506040919091013590565b60008060008060008060006080888a0312156117f857600080fd5b8735965060208801356001600160401b038082111561181657600080fd5b6118228b838c01611229565b909850965060408a013591508082111561183b57600080fd5b6118478b838c01611229565b909650945060608a013591508082111561186057600080fd5b5061186d8a828b016115e7565b989b979a50959850939692959293505050565b60006020828403121561189257600080fd5b5035919050565b600082601f8301126118aa57600080fd5b813560206001600160401b03808311156118c6576118c66112b4565b8260051b6118d5838201611337565b93845285810183019383810190888611156118ef57600080fd5b84880192505b8583101561192b5782358481111561190d5760008081fd5b61191b8a87838c010161154a565b83525091840191908401906118f5565b98975050505050505050565b60008060008060008060008060008060e08b8d03121561195657600080fd5b8a35995060208b01356001600160401b038082111561197457600080fd5b6119808e838f01611229565b909b50995089915061199460408e0161128c565b985060608d0135975060808d01359150808211156119b157600080fd5b6119bd8e838f01611229565b909750955060a08d01359150808211156119d657600080fd5b6119e28e838f01611899565b945060c08d01359150808211156119f857600080fd5b50611a058d828e016115e7565b915080935050809150509295989b9194979a5092959850565b600080600060408486031215611a3357600080fd5b8335925060208401356001600160401b03811115611a5057600080fd5b611a5c868287016115e7565b9497909650939450505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000808335601e19843603018112611aa957600080fd5b83016020810192503590506001600160401b03811115611ac857600080fd5b80360382131561126d57600080fd5b81835260006020808501808196508560051b810191508460005b87811015611b27578284038952611b088288611a92565b611b13868284611a69565b9a87019a9550505090840190600101611af1565b5091979650505050505050565b60008135611b4181611274565b6001600160a01b0316835260208281013590840152611b636040830183611a92565b60606040860152611b78606086018284611a69565b95945050505050565b60005b83811015611b9c578181015183820152602001611b84565b50506000910152565b60008151808452611bbd816020860160208601611b81565b601f01601f19169290920160200192915050565b6000815160c084528051805160c0860152602081015160e08601526040810151610100818188015260608301519150610120828189015260808401519250610140611c26818a01856001600160a01b03169052565b60a08501519350610160611c44818b01866001600160a01b03169052565b60c08601519450610180611c62818c01876001600160a01b03169052565b60e087015195506101a0611c80818d01886001600160a01b03169052565b9487015163ffffffff166101c08c0152928601516001600160a01b039081166101e08c0152918601516102008b015285015181166102208a015290840151610240808a019190915293909101516102608801526020840151166102808701525060408201516102a086015260608201516102c08601526080909101516102e0850191909152611d13610300850182611ba5565b90506020830151611d2f60208601826001600160a01b03169052565b50604083015160408501526060830151606085015260808301518482036080860152611d5b8282611ba5565b91505060a083015160a08501528091505092915050565b8981526001600160a01b0389811660208301526040820189905261010060608301819052600091611da68483018a8c611ad7565b91508088166080850152508560a084015282810360c0840152611dc98186611b34565b905082810360e0840152611ddd8185611bd1565b9c9b505050505050505050505050565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112611e1a57600080fd5b8301803591506001600160401b03821115611e3457600080fd5b60200191503681900382131561126d57600080fd5b634e487b7160e01b600052601160045260246000fd5b600060018201611e7157611e71611e49565b5060010190565b600060208284031215611e8a57600080fd5b5051919050565b8181038181111561105857611058611e49565b600081518084526020808501808196508360051b8101915082860160005b85811015611b27578284038952611eda848351611bd1565b98850198935090840190600101611ec2565b6020815260006117266020830184611ea4565b6020815260006117266020830184611ba5565b600060208284031215611f2457600080fd5b81516001600160401b03811115611f3a57600080fd5b8201601f81018413611f4b57600080fd5b8051611f596113c18261137b565b818152856020838501011115611f6e57600080fd5b611b78826020830160208601611b81565b84815260018060a01b0384166020820152826040820152608060608201526000611fac6080830184611b34565b9695505050505050565b60008235605e19833603018112611fcc57600080fd5b90910192915050565b8781526000602060018060a01b0389168184015287604084015260a0606084015261200460a084018789611ad7565b8381036080850152848152818101600586901b820183018760005b8881101561205557848303601f190184526120438361203e848d611fb6565b611b34565b9386019392509085019060010161201f565b50909d9c50505050505050505050505050565b60008235605e1983360301811261207e57600080fd5b9190910192915050565b60006101008c8352602060018060a01b03808e168286015260408d818701528360608701526120ba8487018d8f611ad7565b918b16608087015260a086018a905285820360c087015287825290925081830190600588901b84018301896000805b8b81101561214657878403601f190186528235368e9003603e1901811261210e578283fd5b8d018035855261212088820182611fb6565b9050858886015261213386860182611b34565b96880196945050918601916001016120e9565b50505086810360e088015261215b8189611ea4565b955050505050509b9a5050505050505050505050565b60008235603e1983360301811261207e57600080fd5b8082018082111561105857611058611e4956fea264697066735822122005c8a05492389a1432aa05a7fcd87ac49f3bb6b89eb577aeddeb9af459d8012564736f6c634300081300330000000000000000000000000e1b5ab67af1c99f8c7ebc71f41f75d4d6211e53000000000000000000000000155e5912f02c6dcf524282e63079ec92a8656c60
Deployed Bytecode
0x6080604052600436106100d55760003560e01c8063760ed2781161007957806398fc5c101161005657806398fc5c1014610248578063daca6f7814610268578063df2ebdbb14610288578063e4a9b20e1461016a57005b8063760ed278146101ca5780638da5cb5b146101ea57806394d0d3a61461020857005b8063444eac45116100b2578063444eac451461014a578063541e34151461016a5780635b94db271461018a5780636ccae054146101aa57005b806320f99c0a146100de57806333601e01146101155780633bd1adec1461013557005b366100dc57005b005b3480156100ea57600080fd5b506001546001600160a01b03165b6040516001600160a01b0390911681526020015b60405180910390f35b34801561012157600080fd5b506100dc610130366004611628565b6102b0565b34801561014157600080fd5b506100dc610740565b34801561015657600080fd5b506100dc610165366004611709565b610776565b34801561017657600080fd5b506100dc61018536600461172d565b6107c3565b34801561019657600080fd5b506100dc6101a5366004611709565b61082e565b3480156101b657600080fd5b506100dc6101c536600461179c565b6108a3565b3480156101d657600080fd5b506100dc6101e53660046117dd565b6108de565b3480156101f657600080fd5b506000546001600160a01b03166100f8565b34801561021457600080fd5b50610238610223366004611880565b60036020526000908152604090205460ff1681565b604051901515815260200161010c565b34801561025457600080fd5b506100dc610263366004611937565b610a11565b34801561027457600080fd5b506100dc610283366004611a1e565b610e2f565b34801561029457600080fd5b506100f873eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee81565b6102f146308b8b8b8b8b8b8b6040516020016102d499989796959493929190611d72565b604051602081830303815290604052805190602001208383610e2f565b886000526003602052604060002060ff815416156103175763756688fe6000526004601cfd5b60019055861561038b5760005b878110156103895760008060008b8b8581811061034357610343611ded565b90506020028101906103559190611e03565b810190610362919061179c565b925092509250610373838383610e92565b505050808061038190611e5f565b915050610324565b505b600061039a6020860186611709565b6001600160a01b03161461055b5782515161016001516001600160a01b031673eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee1460008161044c5784515161016001516040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015610423573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104479190611e78565b61044e565b475b9050600061045b87610ee2565b905080610483576040516387eda65f60e01b8152600060048201526024015b60405180910390fd5b60008361050a5786515161016001516040516370a0823160e01b815230600482015284916001600160a01b0316906370a0823190602401602060405180830381865afa1580156104d7573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906104fb9190611e78565b6105059190611e91565b610514565b6105148347611e91565b9050808760400151111561053e576040516370126e4560e01b81526000600482015260240161047a565b6040870181905283156105565760a087018190529750875b505050505b604080516001808252818301909252600091816020015b61064e6040805161032081019091526000610160820181815261018083018290526101a083018290526101c083018290526101e08301829052610200830182905261022083018290526102408301829052610260830182905261028083018290526102a083018290526102c083018290526102e08301829052610300830182905260c0830190815260e08301829052610100830182905261012083019190915260606101408301528190815260200160006001600160a01b03168152602001600081526020016000815260200160608152602001600081525090565b815260200190600190039081610572579050509050838160008151811061067757610677611ded565b6020026020010181905250866001600160a01b0316632a02224187836040516024016106a39190611eec565b60408051601f198184030181529181526020820180516001600160e01b031663cf376c1d60e01b179052516001600160e01b031960e085901b1681526106ec9190600401611eff565b60006040518083038185885af115801561070a573d6000803e3d6000fd5b50505050506040513d6000823e601f3d908101601f191682016040526107339190810190611f12565b5050505050505050505050565b6001546001600160a01b0316331461076b57604051637c91ccdd60e01b815260040160405180910390fd5b61077433610f0f565b565b6000546001600160a01b031633146107a157604051635fc483c560e01b815260040160405180910390fd5b600280546001600160a01b0319166001600160a01b0392909216919091179055565b6107dd463086866040516020016102d49493929190611f7f565b836000526003602052604060002060ff815416156108035763756688fe6000526004601cfd5b60019055600061081284610ee2565b9050806108275763080a1c276000526004601cfd5b5050505050565b6000546001600160a01b0316331461085957604051635fc483c560e01b815260040160405180910390fd5b600180546001600160a01b0319166001600160a01b0383169081179091556040517f906a1c6bd7e3091ea86693dd029a831c19049ce77f1dce2ce0bab1cacbabce2290600090a250565b6000546001600160a01b031633146108ce57604051635fc483c560e01b815260040160405180910390fd5b6108d9838383610f62565b505050565b6108fe463089898989896040516020016102d49796959493929190611fd5565b866000526003602052604060002060ff815416156109245763756688fe6000526004601cfd5b6001905584156109985760005b8581101561099657600080600089898581811061095057610950611ded565b90506020028101906109629190611e03565b81019061096f919061179c565b925092509250610980838383610e92565b505050808061098e90611e5f565b915050610931565b505b60005b83811015610a075760006109d18686848181106109ba576109ba611ded565b90506020028101906109cc9190612068565b610ee2565b9050806109f45760405163505737f760e11b81526004810183905260240161047a565b50806109ff81611e5f565b91505061099b565b5050505050505050565b610a3746308c8c8c8c8c8c8c8c6040516020016102d49a99989796959493929190612088565b896000526003602052604060002060ff81541615610a5d5763756688fe6000526004601cfd5b600190558715610ad15760005b88811015610acf5760008060008c8c85818110610a8957610a89611ded565b9050602002810190610a9b9190611e03565b810190610aa8919061179c565b925092509250610ab9838383610e92565b5050508080610ac790611e5f565b915050610a6a565b505b8251841415600085111615610aee576391433bb26000526004601cfd5b60005b84811015610e0d5736868683818110610b0c57610b0c611ded565b9050602002810190610b1e9190612171565b9050600073eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee6001600160a01b031686836000013581518110610b5657610b56611ded565b6020026020010151600001516000015161016001516001600160a01b0316149050600081610c145786836000013581518110610b9457610b94611ded565b6020908102919091010151515161016001516040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015610beb573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c0f9190611e78565b610c16565b475b90506000610c2a6109cc6020860186612068565b905080610c4d576040516387eda65f60e01b81526004810186905260240161047a565b600083610cf5578289866000013581518110610c6b57610c6b611ded565b6020908102919091010151515161016001516040516370a0823160e01b81523060048201526001600160a01b03909116906370a0823190602401602060405180830381865afa158015610cc2573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610ce69190611e78565b610cf09190611e91565b610cff565b610cff8347611e91565b90508089866000013581518110610d1857610d18611ded565b6020026020010151604001511115610d46576040516370126e4560e01b81526004810187905260240161047a565b8089866000013581518110610d5d57610d5d611ded565b602002602001015160400181815250508315610df55788856000013581518110610d8957610d89611ded565b602002602001015160a0015181610da09190611e91565b610daa908d612187565b9b5088856000013581518110610dc257610dc2611ded565b60200260200101516040015189866000013581518110610de457610de4611ded565b602002602001015160a00181815250505b50505050508080610e0590611e5f565b915050610af1565b50866001600160a01b0316632a02224187856040516024016106a39190611eec565b610e6f8383838080601f016020809104026020016040519081016040528093929190818152602001838380828437600092019190915250610fee92505050565b6002546001600160a01b039081169116146108d95763815e1d646000526004601cfd5b816014528060345263095ea7b360601b60005260206000604460106000875af18060016000511416610ed757803d853b151710610ed757633e3f8f736000526004601cfd5b506000603452505050565b60006060820135604051816040850135602086010182376000808383602088013588355af1949350505050565b600080546001600160a01b0383166001600160a01b0319918216811783556001805490921690915560405190917ffbe19c9b601f5ee90b44c7390f3fa2319eba01762d34ee372aeafd59b25c7f8791a250565b6001600160a01b038216610f895760405163d92e233d60e01b815260040160405180910390fd5b73eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeed196001600160a01b03841601610fb8576108d9828261105e565b826001600160a01b03163b600003610fe357604051630f58058360e11b815260040160405180910390fd5b6108d98383836110af565b600080611048846040517f19457468657265756d205369676e6564204d6573736167653a0a3332000000006020820152603c8101829052600090605c01604051602081830303815290604052805190602001209050919050565b90506110548184611136565b9150505b92915050565b600080600080600085875af19050806108d95760405162461bcd60e51b815260206004820152601360248201527211551217d514905394d1915497d19052531151606a1b604482015260640161047a565b600060405163a9059cbb60e01b81526001600160a01b0384166004820152826024820152602060006044836000895af13d15601f3d11600160005114161716915050806111305760405162461bcd60e51b815260206004820152600f60248201526e1514905394d1915497d19052531151608a1b604482015260640161047a565b50505050565b600080600080611145856111b5565b6040805160008152602081018083528b905260ff8316918101919091526060810184905260808101839052929550909350915060019060a0016020604051602081039080840390855afa1580156111a0573d6000803e3d6000fd5b5050604051601f190151979650505050505050565b6000806000835160411461120b5760405162461bcd60e51b815260206004820152601860248201527f696e76616c6964207369676e6174757265206c656e6774680000000000000000604482015260640161047a565b50505060208101516040820151606090920151909260009190911a90565b60008083601f84011261123b57600080fd5b5081356001600160401b0381111561125257600080fd5b6020830191508360208260051b850101111561126d57600080fd5b9250929050565b6001600160a01b038116811461128957600080fd5b50565b803561129781611274565b919050565b6000606082840312156112ae57600080fd5b50919050565b634e487b7160e01b600052604160045260246000fd5b60405160a081016001600160401b03811182821017156112ec576112ec6112b4565b60405290565b6040516101c081016001600160401b03811182821017156112ec576112ec6112b4565b60405160c081016001600160401b03811182821017156112ec576112ec6112b4565b604051601f8201601f191681016001600160401b038111828210171561135f5761135f6112b4565b604052919050565b803563ffffffff8116811461129757600080fd5b60006001600160401b03821115611394576113946112b4565b50601f01601f191660200190565b600082601f8301126113b357600080fd5b81356113c66113c18261137b565b611337565b8181528460208386010111156113db57600080fd5b816020850160208301376000918101602001919091529392505050565b600081830361024081121561140c57600080fd5b6114146112ca565b91506101c08082121561142657600080fd5b61142e6112f2565b91508335825260208401356020830152604084013560408301526060840135606083015261145e6080850161128c565b608083015261146f60a0850161128c565b60a083015261148060c0850161128c565b60c083015261149160e0850161128c565b60e08301526101006114a4818601611367565b908301526101206114b685820161128c565b9083015261014084810135908301526101606114d381860161128c565b9083015261018084810135908301526101a080850135908301528183526114fb81850161128c565b602084015250506101e0820135604082015261020082013560608201526102208201356001600160401b0381111561153257600080fd5b61153e848285016113a2565b60808301525092915050565b600060c0828403121561155c57600080fd5b611564611315565b905081356001600160401b038082111561157d57600080fd5b611589858386016113f8565b83526115976020850161128c565b6020840152604084013560408401526060840135606084015260808401359150808211156115c457600080fd5b506115d1848285016113a2565b60808301525060a082013560a082015292915050565b60008083601f8401126115f957600080fd5b5081356001600160401b0381111561161057600080fd5b60208301915083602082850101111561126d57600080fd5b600080600080600080600080600060e08a8c03121561164657600080fd5b8935985060208a01356001600160401b038082111561166457600080fd5b6116708d838e01611229565b909a50985088915061168460408d0161128c565b975060608c0135965060808c01359150808211156116a157600080fd5b6116ad8d838e0161129c565b955060a08c01359150808211156116c357600080fd5b6116cf8d838e0161154a565b945060c08c01359150808211156116e557600080fd5b506116f28c828d016115e7565b915080935050809150509295985092959850929598565b60006020828403121561171b57600080fd5b813561172681611274565b9392505050565b6000806000806060858703121561174357600080fd5b8435935060208501356001600160401b038082111561176157600080fd5b61176d8883890161129c565b9450604087013591508082111561178357600080fd5b50611790878288016115e7565b95989497509550505050565b6000806000606084860312156117b157600080fd5b83356117bc81611274565b925060208401356117cc81611274565b929592945050506040919091013590565b60008060008060008060006080888a0312156117f857600080fd5b8735965060208801356001600160401b038082111561181657600080fd5b6118228b838c01611229565b909850965060408a013591508082111561183b57600080fd5b6118478b838c01611229565b909650945060608a013591508082111561186057600080fd5b5061186d8a828b016115e7565b989b979a50959850939692959293505050565b60006020828403121561189257600080fd5b5035919050565b600082601f8301126118aa57600080fd5b813560206001600160401b03808311156118c6576118c66112b4565b8260051b6118d5838201611337565b93845285810183019383810190888611156118ef57600080fd5b84880192505b8583101561192b5782358481111561190d5760008081fd5b61191b8a87838c010161154a565b83525091840191908401906118f5565b98975050505050505050565b60008060008060008060008060008060e08b8d03121561195657600080fd5b8a35995060208b01356001600160401b038082111561197457600080fd5b6119808e838f01611229565b909b50995089915061199460408e0161128c565b985060608d0135975060808d01359150808211156119b157600080fd5b6119bd8e838f01611229565b909750955060a08d01359150808211156119d657600080fd5b6119e28e838f01611899565b945060c08d01359150808211156119f857600080fd5b50611a058d828e016115e7565b915080935050809150509295989b9194979a5092959850565b600080600060408486031215611a3357600080fd5b8335925060208401356001600160401b03811115611a5057600080fd5b611a5c868287016115e7565b9497909650939450505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b6000808335601e19843603018112611aa957600080fd5b83016020810192503590506001600160401b03811115611ac857600080fd5b80360382131561126d57600080fd5b81835260006020808501808196508560051b810191508460005b87811015611b27578284038952611b088288611a92565b611b13868284611a69565b9a87019a9550505090840190600101611af1565b5091979650505050505050565b60008135611b4181611274565b6001600160a01b0316835260208281013590840152611b636040830183611a92565b60606040860152611b78606086018284611a69565b95945050505050565b60005b83811015611b9c578181015183820152602001611b84565b50506000910152565b60008151808452611bbd816020860160208601611b81565b601f01601f19169290920160200192915050565b6000815160c084528051805160c0860152602081015160e08601526040810151610100818188015260608301519150610120828189015260808401519250610140611c26818a01856001600160a01b03169052565b60a08501519350610160611c44818b01866001600160a01b03169052565b60c08601519450610180611c62818c01876001600160a01b03169052565b60e087015195506101a0611c80818d01886001600160a01b03169052565b9487015163ffffffff166101c08c0152928601516001600160a01b039081166101e08c0152918601516102008b015285015181166102208a015290840151610240808a019190915293909101516102608801526020840151166102808701525060408201516102a086015260608201516102c08601526080909101516102e0850191909152611d13610300850182611ba5565b90506020830151611d2f60208601826001600160a01b03169052565b50604083015160408501526060830151606085015260808301518482036080860152611d5b8282611ba5565b91505060a083015160a08501528091505092915050565b8981526001600160a01b0389811660208301526040820189905261010060608301819052600091611da68483018a8c611ad7565b91508088166080850152508560a084015282810360c0840152611dc98186611b34565b905082810360e0840152611ddd8185611bd1565b9c9b505050505050505050505050565b634e487b7160e01b600052603260045260246000fd5b6000808335601e19843603018112611e1a57600080fd5b8301803591506001600160401b03821115611e3457600080fd5b60200191503681900382131561126d57600080fd5b634e487b7160e01b600052601160045260246000fd5b600060018201611e7157611e71611e49565b5060010190565b600060208284031215611e8a57600080fd5b5051919050565b8181038181111561105857611058611e49565b600081518084526020808501808196508360051b8101915082860160005b85811015611b27578284038952611eda848351611bd1565b98850198935090840190600101611ec2565b6020815260006117266020830184611ea4565b6020815260006117266020830184611ba5565b600060208284031215611f2457600080fd5b81516001600160401b03811115611f3a57600080fd5b8201601f81018413611f4b57600080fd5b8051611f596113c18261137b565b818152856020838501011115611f6e57600080fd5b611b78826020830160208601611b81565b84815260018060a01b0384166020820152826040820152608060608201526000611fac6080830184611b34565b9695505050505050565b60008235605e19833603018112611fcc57600080fd5b90910192915050565b8781526000602060018060a01b0389168184015287604084015260a0606084015261200460a084018789611ad7565b8381036080850152848152818101600586901b820183018760005b8881101561205557848303601f190184526120438361203e848d611fb6565b611b34565b9386019392509085019060010161201f565b50909d9c50505050505050505050505050565b60008235605e1983360301811261207e57600080fd5b9190910192915050565b60006101008c8352602060018060a01b03808e168286015260408d818701528360608701526120ba8487018d8f611ad7565b918b16608087015260a086018a905285820360c087015287825290925081830190600588901b84018301896000805b8b81101561214657878403601f190186528235368e9003603e1901811261210e578283fd5b8d018035855261212088820182611fb6565b9050858886015261213386860182611b34565b96880196945050918601916001016120e9565b50505086810360e088015261215b8189611ea4565b955050505050509b9a5050505050505050505050565b60008235603e1983360301811261207e57600080fd5b8082018082111561105857611058611e4956fea264697066735822122005c8a05492389a1432aa05a7fcd87ac49f3bb6b89eb577aeddeb9af459d8012564736f6c63430008130033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000000e1b5ab67af1c99f8c7ebc71f41f75d4d6211e53000000000000000000000000155e5912f02c6dcf524282e63079ec92a8656c60
-----Decoded View---------------
Arg [0] : _owner (address): 0x0E1B5AB67aF1c99F8c7Ebc71f41f75D4D6211e53
Arg [1] : _solverSigner (address): 0x155E5912F02c6DCF524282e63079EC92A8656c60
-----Encoded View---------------
2 Constructor Arguments found :
Arg [0] : 0000000000000000000000000e1b5ab67af1c99f8c7ebc71f41f75d4d6211e53
Arg [1] : 000000000000000000000000155e5912f02c6dcf524282e63079ec92a8656c60
Loading...
Loading
Loading...
Loading
Multichain Portfolio | 30 Chains
Chain | Token | Portfolio % | Price | Amount | Value |
---|---|---|---|---|---|
BASE | 15.34% | $0.999953 | 46,504.3715 | $46,502.19 | |
BASE | 4.41% | $3,371.41 | 3.9681 | $13,378.06 | |
BASE | 0.64% | $3,369.53 | 0.573 | $1,930.6 | |
OP | 12.05% | $0.999938 | 36,537.3716 | $36,535.11 | |
OP | 4.37% | $3,370.75 | 3.931 | $13,250.42 | |
OP | 0.35% | $3,365.48 | 0.3152 | $1,060.74 | |
LINEA | 9.01% | $3,372.38 | 8.1004 | $27,317.73 | |
LINEA | 6.58% | $0.999996 | 19,950.1853 | $19,950.11 | |
LINEA | 0.11% | $3,369.33 | 0.0964 | $324.71 | |
ARB | 11.17% | $0.9999 | 33,864.5277 | $33,861.14 | |
ARB | 2.18% | $3,371.62 | 1.9638 | $6,621.07 | |
ARB | 0.96% | $3,371.77 | 0.8605 | $2,901.25 | |
GNO | 8.63% | $0.9999 | 26,184.8674 | $26,182.25 | |
GNO | 0.40% | $0.999686 | 1,210.3813 | $1,210 | |
GNO | 0.02% | $0.999686 | 53.9109 | $53.89 | |
BSC | 5.57% | $1 | 16,898.4696 | $16,898.62 | |
BSC | 1.43% | $697.18 | 6.2148 | $4,332.85 | |
BSC | 0.08% | $695.85 | 0.3304 | $229.91 | |
MANTLE | 5.57% | $1 | 16,885.8604 | $16,902.75 | |
MANTLE | 0.78% | $0.975433 | 2,434.2381 | $2,374.44 | |
MANTLE | 0.02% | $0.978655 | 59.6 | $58.33 | |
POL | 5.24% | $0.9999 | 15,886.0714 | $15,884.48 | |
POL | 0.36% | $0.467947 | 2,354.0679 | $1,101.58 | |
POL | 0.06% | $0.466337 | 413.7712 | $192.96 | |
AVAX | 3.97% | $0.9999 | 12,043.5999 | $12,042.4 | |
AVAX | 0.13% | $38.22 | 10.6421 | $406.73 | |
AVAX | <0.01% | $38.08 | 0.28 | $10.66 | |
ETH | 0.29% | $3,372.38 | 0.2614 | $881.37 | |
ETH | 0.27% | $0.999996 | 829.4746 | $829.47 |
[ Download: CSV Export ]
[ Download: CSV Export ]
A contract address hosts a smart contract, which is a set of code stored on the blockchain that runs when predetermined conditions are met. Learn more about addresses in our Knowledge Base.