Transaction Hash:
Block:
19257992 at Feb-18-2024 11:39:35 PM +UTC
Transaction Fee:
0.004263761485660887 ETH
$12.59
Gas Used:
232,139 Gas / 18.367277733 Gwei
Emitted Events:
| 67 |
WETH9.Transfer( src=ConnextDiamond, dst=Unwrapper, wad=290217300967204450 )
|
| 68 |
WETH9.Withdrawal( src=Unwrapper, wad=290217300967204450 )
|
| 69 |
Unwrapper.FundsDelivered( recipient=0x338f625e0a9a156f5171b90c15e792d7bc54fe0d, asset=0x00000000...000000000, amount=290217300967204450 )
|
| 70 |
ConnextDiamond.0xb1a4ab59facaedd6d3a71da3902e0a1fa5b99750c0e20cd878334378a41cb335( 0xb1a4ab59facaedd6d3a71da3902e0a1fa5b99750c0e20cd878334378a41cb335, 0x1c03fd7609b5f27da0ad6589a47de45c2d795562697dadb1bff4dd3814d64dd0, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000040, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000000 )
|
| 71 |
ConnextDiamond.0x0b07a8b0b083f8976b3c832b720632f49cb8ba1e7a99e1b145f51a47d3391cb7( 0x0b07a8b0b083f8976b3c832b720632f49cb8ba1e7a99e1b145f51a47d3391cb7, 0x1c03fd7609b5f27da0ad6589a47de45c2d795562697dadb1bff4dd3814d64dd0, 0x000000000000000000000000268682b7d9992ae7e2ca4a8bcc9d9655fb06056f, 0x000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, 0000000000000000000000000000000000000000000000000000000000000080, 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, 00000000000000000000000000000000000000000000000004070f18fbd2a662, 000000000000000000000000f9d64d54d32ee2bdceaabfa60c4c438e224427d0, 00000000000000000000000000000000000000000000000000000000000000a0, 0000000000000000000000000000000000000000000000000000000000000280, 00000000000000000000000000000000000000000000000000000000000002c0, 0000000000000000000000004ffa5968857a6c8242e4a6ded2418155d33e82e7, 0000000000000000000000000000000000000000000000000000000000000380, 000000000000000000000000000000000000000000000000000000006172626f, 0000000000000000000000000000000000000000000000000000000000657468, 0000000000000000000000000000000000000000000000000000000000657468, 000000000000000000000000268682b7d9992ae7e2ca4a8bcc9d9655fb06056f, 000000000000000000000000338f625e0a9a156f5171b90c15e792d7bc54fe0d, 0000000000000000000000000000000000000000000000000000000000000000, 00000000000000000000000000000000000000000000000000000000000001a0, 0000000000000000000000000000000000000000000000000000000000000064, 00000000000000000000000020c2a9430baa7251eb9143cbe80ac3968893d06c, 00000000000000000000000000000000000000000000000004079323a0207d7d, 000000000000000000000000000000000000000000000000040776fe1303194b, 0000000000000000000000000000000000000000000000000000000000016911, 000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2, 0000000000000000000000000000000000000000000000000000000000000020, 000000000000000000000000338f625e0a9a156f5171b90c15e792d7bc54fe0d, 0000000000000000000000000000000000000000000000000000000000000001, 000000000000000000000000c4ae07f276768a3b74ae8c47bc108a2af0e40eba, 0000000000000000000000000000000000000000000000000000000000000001, 0000000000000000000000000000000000000000000000000000000000000020, 0000000000000000000000000000000000000000000000000000000000000041, d156c5cabb8859049831162be157210b9e72bc4355ac812782fad405eddd7525, 146296452e44919914bd80e59a8fa9aa54ae9ed42507186a7f7bba5e07fe00dc, 1c00000000000000000000000000000000000000000000000000000000000000, 0000000000000000000000000000000000000000000000000000000000000041, de068a3ad4fb35a0481674f4e7f36e770aa8293f90fc75c0d921f371041cf20e, 5c65e29ffc1e0599fd634beee2bdd6a17ac7b3bbc9a9471667aa4de7d87e9832, 1b00000000000000000000000000000000000000000000000000000000000000 )
|
| 72 |
GelatoRelay1BalanceV2.LogUseGelato1BalanceV2( )
|
Account State Difference:
| Address | Before | After | State Difference | ||
|---|---|---|---|---|---|
| 0x338f625e...7bc54fe0D | 0.069188704815293118 Eth | 0.359406005782497568 Eth | 0.29021730096720445 | ||
| 0x8898B472...a5d0e63C6 | (Connext: Bridge) | ||||
|
0x95222290...5CC4BAfe5
Miner
| (beaverbuild) | 15.921813259865048486 Eth | 15.921820718365299148 Eth | 0.000007458500250662 | |
| 0xC02aaA39...83C756Cc2 | 3,129,420.738290427079502833 Eth | 3,129,420.448073126112298383 Eth | 0.29021730096720445 | ||
| 0xC4E7263D...547B16888 |
6.692714709364261695 Eth
Nonce: 7649
|
6.688450947878600808 Eth
Nonce: 7650
| 0.004263761485660887 |
Execution Trace
GelatoRelay1BalanceV2.sponsoredCallV2( _target=0x8898B472C54c31894e3B9bb83cEA802a5d0e63C6, _data=0x63E3E7D2000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000A0000000000000000000000000000000000000000000000000000000000000028000000000000000000000000000000000000000000000000000000000000002C00000000000000000000000004FFA5968857A6C8242E4A6DED2418155D33E82E70000000000000000000000000000000000000000000000000000000000000380000000000000000000000000000000000000000000000000000000006172626F00000000000000000000000000000000000000000000000000000000006574680000000000000000000000000000000000000000000000000000000000657468000000000000000000000000268682B7D9992AE7E2CA4A8BCC9D9655FB06056F000000000000000000000000338F625E0A9A156F5171B90C15E792D7BC54FE0D000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001A0000000000000000000000000000000000000000000000000000000000000006400000000000000000000000020C2A9430BAA7251EB9143CBE80AC3968893D06C00000000000000000000000000000000000000000000000004079323A0207D7D000000000000000000000000000000000000000000000000040776FE1303194B0000000000000000000000000000000000000000000000000000000000016911000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC20000000000000000000000000000000000000000000000000000000000000020000000000000000000000000338F625E0A9A156F5171B90C15E792D7BC54FE0D0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000C4AE07F276768A3B74AE8C47BC108A2AF0E40EBA000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000041D156C5CABB8859049831162BE157210B9E72BC4355AC812782FAD405EDDD7525146296452E44919914BD80E59A8FA9AA54AE9ED42507186A7F7BBA5E07FE00DC1C000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041DE068A3AD4FB35A0481674F4E7F36E770AA8293F90FC75C0D921F371041CF20E5C65E29FFC1E0599FD634BEEE2BDD6A17AC7B3BBC9A9471667AA4DE7D87E98321B00000000000000000000000000000000000000000000000000000000000000, _correlationId=A7EA4C0537266A95EB7006254CAE9254A8951D8C39F950194C4BDAD77A7D3EFF, _r=DC454CA1E3C5A34AC89C7C5FE1AD348419D5596DF1238020FDF7312BB0704410, _vs=C02E0D8D606D193B06F23A37D8F7AC4150A9EF1F1649D2E725FCF7F8B66714DE )
ConnextDiamond.63e3e7d2( )BridgeFacet.execute( _args=[{name:params, type:tuple, order:1, indexed:false, value:[{name:originDomain, type:uint32, order:1, indexed:false, value:1634886255, valueString:1634886255}, {name:destinationDomain, type:uint32, order:2, indexed:false, value:6648936, valueString:6648936}, {name:canonicalDomain, type:uint32, order:3, indexed:false, value:6648936, valueString:6648936}, {name:to, type:address, order:4, indexed:false, value:0x268682b7D9992aE7e2ca4A8bCc9D9655FB06056F, valueString:0x268682b7D9992aE7e2ca4A8bCc9D9655FB06056F}, {name:delegate, type:address, order:5, indexed:false, value:0x338f625e0a9A156f5171B90C15e792d7bc54fe0D, valueString:0x338f625e0a9A156f5171B90C15e792d7bc54fe0D}, {name:receiveLocal, type:bool, order:6, indexed:false, value:false, valueString:False}, {name:callData, type:bytes, order:7, indexed:false, value:0x000000000000000000000000338F625E0A9A156F5171B90C15E792D7BC54FE0D, valueString:0x000000000000000000000000338F625E0A9A156F5171B90C15E792D7BC54FE0D}, {name:slippage, type:uint256, order:8, indexed:false, value:100, valueString:100}, {name:originSender, type:address, order:9, indexed:false, value:0x20C2A9430BAa7251Eb9143Cbe80aC3968893D06C, valueString:0x20C2A9430BAa7251Eb9143Cbe80aC3968893D06C}, {name:bridgedAmt, type:uint256, order:10, indexed:false, value:290362482208308605, valueString:290362482208308605}, {name:normalizedIn, type:uint256, order:11, indexed:false, value:290331534601427275, valueString:290331534601427275}, {name:nonce, type:uint256, order:12, indexed:false, value:92433, valueString:92433}, {name:canonicalId, type:bytes32, order:13, indexed:false, value:000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2, valueString:000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2}], valueString:[{name:originDomain, type:uint32, order:1, indexed:false, value:1634886255, valueString:1634886255}, {name:destinationDomain, type:uint32, order:2, indexed:false, value:6648936, valueString:6648936}, {name:canonicalDomain, type:uint32, order:3, indexed:false, value:6648936, valueString:6648936}, {name:to, type:address, order:4, indexed:false, value:0x268682b7D9992aE7e2ca4A8bCc9D9655FB06056F, valueString:0x268682b7D9992aE7e2ca4A8bCc9D9655FB06056F}, {name:delegate, type:address, order:5, indexed:false, value:0x338f625e0a9A156f5171B90C15e792d7bc54fe0D, valueString:0x338f625e0a9A156f5171B90C15e792d7bc54fe0D}, {name:receiveLocal, type:bool, order:6, indexed:false, value:false, valueString:False}, {name:callData, type:bytes, order:7, indexed:false, value:0x000000000000000000000000338F625E0A9A156F5171B90C15E792D7BC54FE0D, valueString:0x000000000000000000000000338F625E0A9A156F5171B90C15E792D7BC54FE0D}, {name:slippage, type:uint256, order:8, indexed:false, value:100, valueString:100}, {name:originSender, type:address, order:9, indexed:false, value:0x20C2A9430BAa7251Eb9143Cbe80aC3968893D06C, valueString:0x20C2A9430BAa7251Eb9143Cbe80aC3968893D06C}, {name:bridgedAmt, type:uint256, order:10, indexed:false, value:290362482208308605, valueString:290362482208308605}, {name:normalizedIn, type:uint256, order:11, indexed:false, value:290331534601427275, valueString:290331534601427275}, {name:nonce, type:uint256, order:12, indexed:false, value:92433, valueString:92433}, {name:canonicalId, type:bytes32, order:13, indexed:false, value:000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2, valueString:000000000000000000000000C02AAA39B223FE8D0A0E5C4F27EAD9083C756CC2}]}, {name:routers, type:address[], order:2, indexed:false, value:[0xC4Ae07F276768A3b74AE8c47bc108a2aF0e40eBa], valueString:[0xC4Ae07F276768A3b74AE8c47bc108a2aF0e40eBa]}, {name:routerSignatures, type:bytes[], order:3, indexed:false, value:[0VbFyruIWQSYMRYr4VchC55yvENVrIEngvrUBe3ddSUUYpZFLkSRmRS9gOWaj6mqVK6e1CUHGGp/e7peB/4A3Bw=], valueString:[0VbFyruIWQSYMRYr4VchC55yvENVrIEngvrUBe3ddSUUYpZFLkSRmRS9gOWaj6mqVK6e1CUHGGp/e7peB/4A3Bw=]}, {name:sequencer, type:address, order:4, indexed:false, value:0x4fFA5968857a6C8242E4A6Ded2418155D33e82E7, valueString:0x4fFA5968857a6C8242E4A6Ded2418155D33e82E7}, {name:sequencerSignature, type:bytes, order:5, indexed:false, value:0xDE068A3AD4FB35A0481674F4E7F36E770AA8293F90FC75C0D921F371041CF20E5C65E29FFC1E0599FD634BEEE2BDD6A17AC7B3BBC9A9471667AA4DE7D87E98321B, valueString:0xDE068A3AD4FB35A0481674F4E7F36E770AA8293F90FC75C0D921F371041CF20E5C65E29FFC1E0599FD634BEEE2BDD6A17AC7B3BBC9A9471667AA4DE7D87E98321B}] ) => ( 1C03FD7609B5F27DA0AD6589A47DE45C2D795562697DADB1BFF4DD3814D64DD0 )-
Null: 0x000...001.e0c5a2fd( ) -
Null: 0x000...001.c049c9a9( ) -
WETH9.transfer( dst=0x268682b7D9992aE7e2ca4A8bCc9D9655FB06056F, wad=290217300967204450 ) => ( True )
Unwrapper.xReceive( 1C03FD7609B5F27DA0AD6589A47DE45C2D795562697DADB1BFF4DD3814D64DD0, amount=290217300967204450, asset=0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2, 0x0000000000000000000000000000000000000000, 1634886255, callData=0x000000000000000000000000338F625E0A9A156F5171B90C15E792D7BC54FE0D ) => ( 0x )
WETH9.withdraw( wad=290217300967204450 )
- ETH 0.29021730096720445
Unwrapper.CALL( )
- ETH 0.29021730096720445
- ETH 0.29021730096720445
0x338f625e0a9a156f5171b90c15e792d7bc54fe0d.CALL( )
-
sponsoredCallV2[GelatoRelay1BalanceV2 (ln:23)]
revertingContractCall[GelatoRelay1BalanceV2 (ln:35)]LogUseGelato1BalanceV2[GelatoRelay1BalanceV2 (ln:36)]
File 1 of 5: GelatoRelay1BalanceV2
File 2 of 5: ConnextDiamond
File 3 of 5: Unwrapper
File 4 of 5: WETH9
File 5 of 5: BridgeFacet
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {IGelatoRelay1BalanceV2} from "./interfaces/IGelatoRelay1BalanceV2.sol";
import {IGelato1BalanceV2} from "./interfaces/IGelato1BalanceV2.sol";
import {GelatoCallUtils} from "./lib/GelatoCallUtils.sol";
/// @title Gelato Relay V2 contract
/// @notice This contract deals with Gelato 1Balance payments
/// @dev This contract must NEVER hold funds!
/// @dev Maliciously crafted transaction payloads could wipe out any funds left here
// solhint-disable-next-line max-states-count
contract GelatoRelay1BalanceV2 is IGelatoRelay1BalanceV2, IGelato1BalanceV2 {
using GelatoCallUtils for address;
/// @notice Relay call + One Balance payment - with sponsor authentication
/// @dev This method can be called directly without passing through the diamond
/// @dev The validity of the emitted LogUseGelato1BalanceV2 event must be verified off-chain
/// @dev Payment is handled with off-chain accounting using Gelato's 1Balance system
/// @param _target Relay call target
/// @param _data Relay call data
/// @param _correlationId Unique task identifier generated by gelato
/// Signature is split into `r` and `vs` - See https://eips.ethereum.org/EIPS/eip-2098
/// @param _r Checker signature
/// @param _vs Checker signature
function sponsoredCallV2(
address _target,
bytes calldata _data,
bytes32 _correlationId,
bytes32 _r,
bytes32 _vs
) external {
// These parameters are decoded from calldata
(_correlationId);
(_r);
(_vs);
// INTERACTIONS
_target.revertingContractCall(_data, "GelatoRelay.sponsoredCallV2:");
emit LogUseGelato1BalanceV2();
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IGelato1BalanceV2 {
event LogUseGelato1BalanceV2();
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
interface IGelatoRelay1BalanceV2 {
function sponsoredCallV2(
address _target,
bytes calldata _data,
bytes32 _correlationId,
bytes32 _r,
bytes32 _vs
) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
library GelatoBytes {
function calldataSliceSelector(
bytes calldata _bytes
) internal pure returns (bytes4 selector) {
selector =
_bytes[0] |
(bytes4(_bytes[1]) >> 8) |
(bytes4(_bytes[2]) >> 16) |
(bytes4(_bytes[3]) >> 24);
}
function memorySliceSelector(
bytes memory _bytes
) internal pure returns (bytes4 selector) {
selector =
_bytes[0] |
(bytes4(_bytes[1]) >> 8) |
(bytes4(_bytes[2]) >> 16) |
(bytes4(_bytes[3]) >> 24);
}
function revertWithError(
bytes memory _bytes,
string memory _tracingInfo
) internal pure {
// 68: 32-location, 32-length, 4-ErrorSelector, UTF-8 err
if (_bytes.length % 32 == 4) {
bytes4 selector;
assembly {
selector := mload(add(0x20, _bytes))
}
if (selector == 0x08c379a0) {
// Function selector for Error(string)
assembly {
_bytes := add(_bytes, 68)
}
revert(string(abi.encodePacked(_tracingInfo, string(_bytes))));
} else {
revert(
string(abi.encodePacked(_tracingInfo, "NoErrorSelector"))
);
}
} else {
revert(
string(abi.encodePacked(_tracingInfo, "UnexpectedReturndata"))
);
}
}
function returnError(
bytes memory _bytes,
string memory _tracingInfo
) internal pure returns (string memory) {
// 68: 32-location, 32-length, 4-ErrorSelector, UTF-8 err
if (_bytes.length % 32 == 4) {
bytes4 selector;
assembly {
selector := mload(add(0x20, _bytes))
}
if (selector == 0x08c379a0) {
// Function selector for Error(string)
assembly {
_bytes := add(_bytes, 68)
}
return string(abi.encodePacked(_tracingInfo, string(_bytes)));
} else {
return
string(abi.encodePacked(_tracingInfo, "NoErrorSelector"));
}
} else {
return
string(abi.encodePacked(_tracingInfo, "UnexpectedReturndata"));
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.20;
import {GelatoBytes} from "./GelatoBytes.sol";
library GelatoCallUtils {
using GelatoBytes for bytes;
function revertingContractCall(
address _contract,
bytes memory _data,
string memory _errorMsg
) internal returns (bytes memory returndata) {
bool success;
(success, returndata) = _contract.call(_data);
// solhint-disable-next-line max-line-length
// https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/f9b6fc3fdab7aca33a9cfa8837c5cd7f67e176be/contracts/utils/AddressUpgradeable.sol#L177
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(
isContract(_contract),
string(abi.encodePacked(_errorMsg, "Call to non contract"))
);
}
} else {
returndata.revertWithError(_errorMsg);
}
}
// solhint-disable-next-line max-line-length
// https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable/blob/f9b6fc3fdab7aca33a9cfa8837c5cd7f67e176be/contracts/utils/AddressUpgradeable.sol#L36
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
}
File 2 of 5: ConnextDiamond
// SPDX-License-Identifier: MIT pragma solidity 0.8.17; /******************************************************************************\\ * Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen) * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 * * Implementation of a diamond. /******************************************************************************/ import {LibDiamond} from "../libraries/LibDiamond.sol"; import {IDiamondCut} from "../interfaces/IDiamondCut.sol"; contract ConnextDiamond { struct Initialization { address initContract; bytes initData; } /// @notice This construct a diamond contract /// @param _contractOwner the owner of the contract. With default DiamondCutFacet, this is the sole address allowed to make further cuts. /// @param _diamondCut the list of facet to add /// @param _initializations the list of initialization pair to execute. This allow to setup a contract with multiple level of independent initialization. constructor( address _contractOwner, IDiamondCut.FacetCut[] memory _diamondCut, Initialization[] memory _initializations ) payable { if (_contractOwner != address(0)) { LibDiamond.setContractOwner(_contractOwner); } LibDiamond.diamondCut(_diamondCut, address(0), ""); uint256 len = _initializations.length; for (uint256 i = 0; i < len; ) { LibDiamond.initializeDiamondCut(_initializations[i].initContract, _initializations[i].initData); unchecked { ++i; } } } // Find facet for function that is called and execute the // function if a facet is found and return any value. fallback() external payable { LibDiamond.DiamondStorage storage ds; bytes32 position = LibDiamond.DIAMOND_STORAGE_POSITION; // get diamond storage assembly { ds.slot := position } // get facet from function selector address facet = ds.selectorToFacetAndPosition[msg.sig].facetAddress; require(facet != address(0), "Diamond: Function does not exist"); // Execute external function from facet using delegatecall and return any value. assembly { // copy function selector and any arguments calldatacopy(0, 0, calldatasize()) // execute function call using the facet let result := delegatecall(gas(), facet, 0, calldatasize(), 0, 0) // get any return value returndatacopy(0, 0, returndatasize()) // return any return value or error back to the caller switch result case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } receive() external payable {} } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; /******************************************************************************\\ * Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen) * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 /******************************************************************************/ interface IDiamondCut { enum FacetCutAction { Add, Replace, Remove } // Add=0, Replace=1, Remove=2 struct FacetCut { address facetAddress; FacetCutAction action; bytes4[] functionSelectors; } /// @notice Propose to add/replace/remove any number of functions and optionally execute /// a function with delegatecall /// @param _diamondCut Contains the facet addresses and function selectors /// @param _init The address of the contract or facet to execute _calldata /// @param _calldata A function call, including function selector and arguments /// _calldata is executed with delegatecall on _init function proposeDiamondCut( FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata ) external; event DiamondCutProposed(FacetCut[] _diamondCut, address _init, bytes _calldata, uint256 deadline); /// @notice Add/replace/remove any number of functions and optionally execute /// a function with delegatecall /// @param _diamondCut Contains the facet addresses and function selectors /// @param _init The address of the contract or facet to execute _calldata /// @param _calldata A function call, including function selector and arguments /// _calldata is executed with delegatecall on _init function diamondCut( FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata ) external; event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata); /// @notice Propose to add/replace/remove any number of functions and optionally execute /// a function with delegatecall /// @param _diamondCut Contains the facet addresses and function selectors /// @param _init The address of the contract or facet to execute _calldata /// @param _calldata A function call, including function selector and arguments /// _calldata is executed with delegatecall on _init function rescindDiamondCut( FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata ) external; /** * @notice Returns the acceptance time for a given proposal * @param _diamondCut Contains the facet addresses and function selectors * @param _init The address of the contract or facet to execute _calldata * @param _calldata A function call, including function selector and arguments _calldata is * executed with delegatecall on _init */ function getAcceptanceTime( FacetCut[] calldata _diamondCut, address _init, bytes calldata _calldata ) external returns (uint256); event DiamondCutRescinded(FacetCut[] _diamondCut, address _init, bytes _calldata); } // SPDX-License-Identifier: MIT pragma solidity 0.8.17; /******************************************************************************\\ * Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen) * EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535 /******************************************************************************/ import {IDiamondCut} from "../interfaces/IDiamondCut.sol"; // Remember to add the loupe functions from DiamondLoupeFacet to the diamond. // The loupe functions are required by the EIP2535 Diamonds standard library LibDiamond { bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage"); struct FacetAddressAndPosition { address facetAddress; uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array } struct FacetFunctionSelectors { bytes4[] functionSelectors; uint256 facetAddressPosition; // position of facetAddress in facetAddresses array } struct DiamondStorage { // maps function selector to the facet address and // the position of the selector in the facetFunctionSelectors.selectors array mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition; // maps facet addresses to function selectors mapping(address => FacetFunctionSelectors) facetFunctionSelectors; // facet addresses address[] facetAddresses; // Used to query if a contract implements an interface. // Used to implement ERC-165. mapping(bytes4 => bool) supportedInterfaces; // owner of the contract address contractOwner; // hash of proposed facets => acceptance time mapping(bytes32 => uint256) acceptanceTimes; // acceptance delay for upgrading facets uint256 acceptanceDelay; } function diamondStorage() internal pure returns (DiamondStorage storage ds) { bytes32 position = DIAMOND_STORAGE_POSITION; assembly { ds.slot := position } } event OwnershipTransferred(address indexed previousOwner, address indexed newOwner); function setContractOwner(address _newOwner) internal { DiamondStorage storage ds = diamondStorage(); emit OwnershipTransferred(ds.contractOwner, _newOwner); ds.contractOwner = _newOwner; } function contractOwner() internal view returns (address contractOwner_) { contractOwner_ = diamondStorage().contractOwner; } function acceptanceDelay() internal view returns (uint256) { return diamondStorage().acceptanceDelay; } function acceptanceTime(bytes32 _key) internal view returns (uint256) { return diamondStorage().acceptanceTimes[_key]; } function enforceIsContractOwner() internal view { require(msg.sender == diamondStorage().contractOwner, "LibDiamond: !contract owner"); } event DiamondCutProposed(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata, uint256 deadline); function proposeDiamondCut( IDiamondCut.FacetCut[] memory _diamondCut, address _init, bytes memory _calldata ) internal { // NOTE: to save gas, verification that `proposeDiamondCut` and `diamondCut` are not // included is performed in `diamondCut`, where there is already a loop over facets. // In the case where these cuts are performed, admins must call `rescindDiamondCut` DiamondStorage storage ds = diamondStorage(); uint256 acceptance = block.timestamp + ds.acceptanceDelay; ds.acceptanceTimes[keccak256(abi.encode(_diamondCut, _init, _calldata))] = acceptance; emit DiamondCutProposed(_diamondCut, _init, _calldata, acceptance); } event DiamondCutRescinded(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); function rescindDiamondCut( IDiamondCut.FacetCut[] memory _diamondCut, address _init, bytes memory _calldata ) internal { // NOTE: you can always rescind a proposed facet cut as the owner, even if outside of the validity // period or befor the delay elpases delete diamondStorage().acceptanceTimes[keccak256(abi.encode(_diamondCut, _init, _calldata))]; emit DiamondCutRescinded(_diamondCut, _init, _calldata); } event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata); // Internal function version of diamondCut function diamondCut( IDiamondCut.FacetCut[] memory _diamondCut, address _init, bytes memory _calldata ) internal { DiamondStorage storage ds = diamondStorage(); bytes32 key = keccak256(abi.encode(_diamondCut, _init, _calldata)); if (ds.facetAddresses.length != 0) { uint256 time = ds.acceptanceTimes[key]; require(time != 0 && time <= block.timestamp, "LibDiamond: delay not elapsed"); // Reset the acceptance time to ensure the same set of updates cannot be replayed // without going through a proposal window // NOTE: the only time this will not be set to 0 is when there are no // existing facet addresses (on initialization, or when starting after a bad upgrade, // for example). // The only relevant case is the initial case, which has no acceptance time. otherwise, // there is no way to update the facet selector mapping to call `diamondCut`. // Avoiding setting the empty value will save gas on the initial deployment. delete ds.acceptanceTimes[key]; } // Otherwise, this is the first instance of deployment and it can be set automatically uint256 len = _diamondCut.length; for (uint256 facetIndex; facetIndex < len; ) { IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action; if (action == IDiamondCut.FacetCutAction.Add) { addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); } else if (action == IDiamondCut.FacetCutAction.Replace) { replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); } else if (action == IDiamondCut.FacetCutAction.Remove) { removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors); } else { revert("LibDiamondCut: Incorrect FacetCutAction"); } unchecked { ++facetIndex; } } emit DiamondCut(_diamondCut, _init, _calldata); initializeDiamondCut(_init, _calldata); } function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { require(_functionSelectors.length != 0, "LibDiamondCut: No selectors in facet to cut"); DiamondStorage storage ds = diamondStorage(); require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length); // add new facet address if it does not exist if (selectorPosition == 0) { addFacet(ds, _facetAddress); } uint256 len = _functionSelectors.length; for (uint256 selectorIndex; selectorIndex < len; ) { bytes4 selector = _functionSelectors[selectorIndex]; address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists"); addFunction(ds, selector, selectorPosition, _facetAddress); selectorPosition++; unchecked { ++selectorIndex; } } } function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { uint256 len = _functionSelectors.length; require(len != 0, "LibDiamondCut: No selectors in facet to cut"); DiamondStorage storage ds = diamondStorage(); require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)"); uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length); // add new facet address if it does not exist if (selectorPosition == 0) { addFacet(ds, _facetAddress); } for (uint256 selectorIndex; selectorIndex < len; ) { bytes4 selector = _functionSelectors[selectorIndex]; address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function"); removeFunction(ds, oldFacetAddress, selector); addFunction(ds, selector, selectorPosition, _facetAddress); selectorPosition++; unchecked { ++selectorIndex; } } } function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal { require(_functionSelectors.length != 0, "LibDiamondCut: No selectors in facet to cut"); DiamondStorage storage ds = diamondStorage(); // get the propose and cut selectors -- can never remove these bytes4 proposeSelector = IDiamondCut.proposeDiamondCut.selector; bytes4 cutSelector = IDiamondCut.diamondCut.selector; // if function does not exist then do nothing and return require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)"); uint256 len = _functionSelectors.length; for (uint256 selectorIndex; selectorIndex < len; ) { bytes4 selector = _functionSelectors[selectorIndex]; require(selector != proposeSelector && selector != cutSelector, "LibDiamondCut: Cannot remove cut selectors"); address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress; removeFunction(ds, oldFacetAddress, selector); unchecked { ++selectorIndex; } } } function addFacet(DiamondStorage storage ds, address _facetAddress) internal { enforceHasContractCode(_facetAddress, "LibDiamondCut: New facet has no code"); ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds.facetAddresses.length; ds.facetAddresses.push(_facetAddress); } function addFunction( DiamondStorage storage ds, bytes4 _selector, uint96 _selectorPosition, address _facetAddress ) internal { ds.selectorToFacetAndPosition[_selector].functionSelectorPosition = _selectorPosition; ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(_selector); ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress; } function removeFunction( DiamondStorage storage ds, address _facetAddress, bytes4 _selector ) internal { require(_facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist"); // an immutable function is a function defined directly in a diamond require(_facetAddress != address(this), "LibDiamondCut: Can't remove immutable function"); // replace selector with last selector, then delete last selector uint256 selectorPosition = ds.selectorToFacetAndPosition[_selector].functionSelectorPosition; uint256 lastSelectorPosition = ds.facetFunctionSelectors[_facetAddress].functionSelectors.length - 1; // if not the same then replace _selector with lastSelector if (selectorPosition != lastSelectorPosition) { bytes4 lastSelector = ds.facetFunctionSelectors[_facetAddress].functionSelectors[lastSelectorPosition]; ds.facetFunctionSelectors[_facetAddress].functionSelectors[selectorPosition] = lastSelector; ds.selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint96(selectorPosition); } // delete the last selector ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop(); delete ds.selectorToFacetAndPosition[_selector]; // if no more selectors for facet address then delete the facet address if (lastSelectorPosition == 0) { // replace facet address with last facet address and delete last facet address uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1; uint256 facetAddressPosition = ds.facetFunctionSelectors[_facetAddress].facetAddressPosition; if (facetAddressPosition != lastFacetAddressPosition) { address lastFacetAddress = ds.facetAddresses[lastFacetAddressPosition]; ds.facetAddresses[facetAddressPosition] = lastFacetAddress; ds.facetFunctionSelectors[lastFacetAddress].facetAddressPosition = facetAddressPosition; } ds.facetAddresses.pop(); delete ds.facetFunctionSelectors[_facetAddress].facetAddressPosition; } } function initializeDiamondCut(address _init, bytes memory _calldata) internal { if (_init == address(0)) { require(_calldata.length == 0, "LibDiamondCut: _init is address(0) but_calldata is not empty"); } else { require(_calldata.length != 0, "LibDiamondCut: _calldata is empty but _init is not address(0)"); if (_init != address(this)) { enforceHasContractCode(_init, "LibDiamondCut: _init address has no code"); } (bool success, bytes memory error) = _init.delegatecall(_calldata); if (!success) { if (error.length != 0) { // bubble up the error revert(string(error)); } else { revert("LibDiamondCut: _init function reverted"); } } } } function enforceHasContractCode(address _contract, string memory _errorMessage) internal view { require(_contract.code.length != 0, _errorMessage); } }
File 3 of 5: Unwrapper
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
interface IXReceiver {
function xReceive(
bytes32 _transferId,
uint256 _amount,
address _asset,
address _originSender,
uint32 _origin,
bytes memory _callData
) external returns (bytes memory);
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ProposedOwnable} from "../../shared/ProposedOwnable.sol";
import {IXReceiver} from "../connext/interfaces/IXReceiver.sol";
interface IWrapper {
function withdraw(uint256 wad) external;
function transfer(address dst, uint256 wad) external returns (bool);
}
/**
* @notice A utility contract for unwrapping native tokens at the destination.
*
* @dev The `xreceive` function of contract may fail in the following ways:
* - unwrapping fails
* - the wrong asset is delivered, and transferring that fails
* - sending the native asset fails
* - the caller is not connext
* - the amount is zero
* - balance of the contract != amount
*
* In the event of these failures, funds for the crosschain transfer will be sent
* to this contract and will be held here. To rescue them, the owner of this contract
* can call `sweep` or `unwrapAndSweep` to transfer assets from this contract to a
* specified address.
*
* It is unlikely failures of these types will occur, so ownership of this contract
* should be renounced after a suitable trial period on mainnet.
*
* @dev Ownership of this contract is governed using the same ProposedOwnable setup
* that is prevalent throughout the system.
*/
contract Unwrapper is ProposedOwnable, IXReceiver {
// ============ Libraries ============
using SafeERC20 for IERC20;
// ============ Events ============
/**
* @notice Emitted if the wrong wrapper asset is sent.
* @param recipient - The target recipient address.
* @param asset - The asset sent.
*/
event WrongAsset(address recipient, address asset);
/**
* @notice Emitted when funds are sent from this contract
* @param recipient - The target recipient address.
* @param asset - The asset sent.
* @param amount - The amount of the asset sent
*/
event FundsDelivered(address recipient, address asset, uint256 amount);
// ============ Properties ============
/**
* @notice Connext (diamond proxy) address, the only address permissioned to call `xReceive`.
*/
address public immutable CONNEXT;
/**
* @notice The wrapper contract that this contract will always use for unwrapping native token.
*/
IWrapper public immutable WRAPPER;
// ============= Modifiers ==============
/**
* @notice Ensure caller is only the designated CONNEXT bridge address.
*/
modifier onlyConnext() {
require(msg.sender == CONNEXT, "unwrap: !connext");
_;
}
// ============ Constructor ============
/**
* @dev The initial owner is set to the `msg.sender` of the contract.
*/
constructor(address connext, address wrapper) ProposedOwnable() {
_setOwner(msg.sender);
WRAPPER = IWrapper(wrapper);
CONNEXT = connext;
}
// ============ Admin Functions ============
/**
* @notice Sweeps the provided token from this address to a designated recipient.
* @dev Only the owner of this contract can call this function.
*
* @dev Funds will end up on this contract IFF the external call on the Connext contract
* fails and the transfer is reconciled. The `xreceive` function can fail in the following
* cases:
* - unwrapping fails
* - transferring the asset fails
* - `connext` is not the caller (should not sweep in this case)
* - amount is zero (should not sweep in this case).
*
* It is left to the admin to determine the proper amounts to sweep.
*
* @param recipient The address to send the funds to
* @param asset The asset to send from the contract to recipient
* @param amount Amount of asset to sweep from contract
*/
function sweep(
address recipient,
address asset,
uint256 amount
) public onlyOwner {
// Sanity check: amount is non-zero (otherwise, what are we unwrapping?).
require(amount != 0, "sweep: !amount");
// Send funds to recipient
_sweep(recipient, asset, amount);
}
/**
* @notice Unwraps and sweeps the provided token from this address to a designated recipient.
* @dev Only the owner of this contract can call this function.
*
* @dev Funds will end up on this contract IFF the external call on the Connext contract
* fails and the transfer is reconciled. The `xreceive` function can fail in the following
* cases:
* - unwrapping fails
* - transferring the asset fails
* - `connext` is not the caller (should not sweep in this case)
* - amount is zero (should not sweep in this case).
*
* It is left to the admin to determine the proper amounts to sweep.
*
* @param recipient The address to send the funds to
* @param amount Amount of asset to sweep from contract
*/
function unwrapAndSweep(address recipient, uint256 amount) public onlyOwner {
// Sanity check: amount is non-zero (otherwise, what are we unwrapping?).
require(amount != 0, "unwrapAndSweep: !amount");
// Withdraw from wrapper
WRAPPER.withdraw(amount);
// Send funds to recipient
_sweep(recipient, address(0), amount);
}
// ============ Public Functions ============
/**
* @notice xReceive implementation for receiving cross-chain calls from Connext.
* @dev We mostly ignore `originSender` argument: this could be a contract or EOA, but our
* recipient should be specified in our `callData`! We only fallback to using `originSender` IFF
* recipient argument is missing.
* @dev If unwrapping (i.e. `withdraw`) fails, will emit UnwrappingFailed event! We will attempt
* to transfer the wrapped tokens to the
*
* @param amount - The amount to transfer. Should NOT be 0, or this call will revert.
* @param asset - This *should be* the wrapper contract address, an ERC20 token approved by the
* Connext bridge. IFF this does NOT match the WRAPPER contract address stored in this contract,
* we'll try to `IERC20.transfer` the assets to the intended recipient.
* @param callData - Should be a tuple of just `(address)`. The address is the intended
* recipient of the unwrapped native tokens. Whether it's ether or wether (i.e. whether it's
* wrapped native tokens or native tokens) depends on whether we succeeded in the unwrapping
* process.
*/
function xReceive(
bytes32, // transferId
uint256 amount,
address asset,
address, // originSender
uint32, // origin domain
bytes memory callData
) external onlyConnext returns (bytes memory) {
// Sanity check: amount is non-zero (otherwise, what are we unwrapping?).
require(amount != 0, "unwrap: !amount");
// Get the target recipient, which should be in the callData.
// NOTE: If recipient is the zero address, funds will be burned!
address recipient = abi.decode(callData, (address));
// Sanity check: asset we've received matches our target wrapper.
if (asset != address(WRAPPER)) {
emit WrongAsset(recipient, asset);
// If the delivered asset does not match our target wrapper, we try sending it anyway.
_sweep(recipient, asset, amount);
return bytes("");
}
// We've received wrapped native tokens; withdraw native tokens from the wrapper contract.
WRAPPER.withdraw(amount);
// Send to recipient
_sweep(recipient, address(0), amount);
}
/**
* @notice Fallback function so this contract can receive the funds from WETH
*/
receive() external payable {}
// ============ Internal Functions ============
/**
* @notice Sweeps the provided token from this address to a designated recipient.
* @dev Emits the `FundsDelivered` event
*
* @param recipient The address to send the funds to
* @param asset The asset (or address(0) for native) to send from the contract to recipient
* @param amount Amount of asset to sweep from contract
*/
function _sweep(
address recipient,
address asset,
uint256 amount
) internal {
if (asset == address(0)) {
Address.sendValue(payable(recipient), amount);
} else {
IERC20(asset).transfer(recipient, amount);
}
emit FundsDelivered(recipient, asset, amount);
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
/**
* @title IProposedOwnable
* @notice Defines a minimal interface for ownership with a two step proposal and acceptance
* process
*/
interface IProposedOwnable {
/**
* @dev This emits when change in ownership of a contract is proposed.
*/
event OwnershipProposed(address indexed proposedOwner);
/**
* @dev This emits when ownership of a contract changes.
*/
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @notice Get the address of the owner
* @return owner_ The address of the owner.
*/
function owner() external view returns (address owner_);
/**
* @notice Get the address of the proposed owner
* @return proposed_ The address of the proposed.
*/
function proposed() external view returns (address proposed_);
/**
* @notice Set the address of the proposed owner of the contract
* @param newlyProposed The proposed new owner of the contract
*/
function proposeNewOwner(address newlyProposed) external;
/**
* @notice Set the address of the proposed owner of the contract
*/
function acceptProposedOwner() external;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {IProposedOwnable} from "./interfaces/IProposedOwnable.sol";
/**
* @title ProposedOwnable
* @notice Contract module which provides a basic access control mechanism,
* where there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed via a two step process:
* 1. Call `proposeOwner`
* 2. Wait out the delay period
* 3. Call `acceptOwner`
*
* @dev This module is used through inheritance. It will make available the
* modifier `onlyOwner`, which can be applied to your functions to restrict
* their use to the owner.
*
* @dev The majority of this code was taken from the openzeppelin Ownable
* contract
*
*/
abstract contract ProposedOwnable is IProposedOwnable {
// ========== Custom Errors ===========
error ProposedOwnable__onlyOwner_notOwner();
error ProposedOwnable__onlyProposed_notProposedOwner();
error ProposedOwnable__ownershipDelayElapsed_delayNotElapsed();
error ProposedOwnable__proposeNewOwner_invalidProposal();
error ProposedOwnable__proposeNewOwner_noOwnershipChange();
error ProposedOwnable__renounceOwnership_noProposal();
error ProposedOwnable__renounceOwnership_invalidProposal();
// ============ Properties ============
address private _owner;
address private _proposed;
uint256 private _proposedOwnershipTimestamp;
uint256 private constant _delay = 7 days;
// ======== Getters =========
/**
* @notice Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @notice Returns the address of the proposed owner.
*/
function proposed() public view virtual returns (address) {
return _proposed;
}
/**
* @notice Returns the address of the proposed owner.
*/
function proposedTimestamp() public view virtual returns (uint256) {
return _proposedOwnershipTimestamp;
}
/**
* @notice Returns the delay period before a new owner can be accepted.
*/
function delay() public view virtual returns (uint256) {
return _delay;
}
/**
* @notice Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
if (_owner != msg.sender) revert ProposedOwnable__onlyOwner_notOwner();
_;
}
/**
* @notice Throws if called by any account other than the proposed owner.
*/
modifier onlyProposed() {
if (_proposed != msg.sender) revert ProposedOwnable__onlyProposed_notProposedOwner();
_;
}
/**
* @notice Throws if the ownership delay has not elapsed
*/
modifier ownershipDelayElapsed() {
// Ensure delay has elapsed
if ((block.timestamp - _proposedOwnershipTimestamp) <= _delay)
revert ProposedOwnable__ownershipDelayElapsed_delayNotElapsed();
_;
}
/**
* @notice Indicates if the ownership has been renounced() by
* checking if current owner is address(0)
*/
function renounced() public view returns (bool) {
return _owner == address(0);
}
// ======== External =========
/**
* @notice Sets the timestamp for an owner to be proposed, and sets the
* newly proposed owner as step 1 in a 2-step process
*/
function proposeNewOwner(address newlyProposed) public virtual onlyOwner {
// Contract as source of truth
if (_proposed == newlyProposed && _proposedOwnershipTimestamp != 0)
revert ProposedOwnable__proposeNewOwner_invalidProposal();
// Sanity check: reasonable proposal
if (_owner == newlyProposed) revert ProposedOwnable__proposeNewOwner_noOwnershipChange();
_setProposed(newlyProposed);
}
/**
* @notice Renounces ownership of the contract after a delay
*/
function renounceOwnership() public virtual onlyOwner ownershipDelayElapsed {
// Ensure there has been a proposal cycle started
if (_proposedOwnershipTimestamp == 0) revert ProposedOwnable__renounceOwnership_noProposal();
// Require proposed is set to 0
if (_proposed != address(0)) revert ProposedOwnable__renounceOwnership_invalidProposal();
// Emit event, set new owner, reset timestamp
_setOwner(address(0));
}
/**
* @notice Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function acceptProposedOwner() public virtual onlyProposed ownershipDelayElapsed {
// NOTE: no need to check if _owner == _proposed, because the _proposed
// is 0-d out and this check is implicitly enforced by modifier
// NOTE: no need to check if _proposedOwnershipTimestamp > 0 because
// the only time this would happen is if the _proposed was never
// set (will fail from modifier) or if the owner == _proposed (checked
// above)
// Emit event, set new owner, reset timestamp
_setOwner(_proposed);
}
// ======== Internal =========
function _setOwner(address newOwner) internal {
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
delete _proposedOwnershipTimestamp;
delete _proposed;
}
function _setProposed(address newlyProposed) private {
_proposedOwnershipTimestamp = block.timestamp;
_proposed = newlyProposed;
emit OwnershipProposed(newlyProposed);
}
}
File 4 of 5: WETH9
// Copyright (C) 2015, 2016, 2017 Dapphub
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <http://www.gnu.org/licenses/>.
pragma solidity ^0.4.18;
contract WETH9 {
string public name = "Wrapped Ether";
string public symbol = "WETH";
uint8 public decimals = 18;
event Approval(address indexed src, address indexed guy, uint wad);
event Transfer(address indexed src, address indexed dst, uint wad);
event Deposit(address indexed dst, uint wad);
event Withdrawal(address indexed src, uint wad);
mapping (address => uint) public balanceOf;
mapping (address => mapping (address => uint)) public allowance;
function() public payable {
deposit();
}
function deposit() public payable {
balanceOf[msg.sender] += msg.value;
Deposit(msg.sender, msg.value);
}
function withdraw(uint wad) public {
require(balanceOf[msg.sender] >= wad);
balanceOf[msg.sender] -= wad;
msg.sender.transfer(wad);
Withdrawal(msg.sender, wad);
}
function totalSupply() public view returns (uint) {
return this.balance;
}
function approve(address guy, uint wad) public returns (bool) {
allowance[msg.sender][guy] = wad;
Approval(msg.sender, guy, wad);
return true;
}
function transfer(address dst, uint wad) public returns (bool) {
return transferFrom(msg.sender, dst, wad);
}
function transferFrom(address src, address dst, uint wad)
public
returns (bool)
{
require(balanceOf[src] >= wad);
if (src != msg.sender && allowance[src][msg.sender] != uint(-1)) {
require(allowance[src][msg.sender] >= wad);
allowance[src][msg.sender] -= wad;
}
balanceOf[src] -= wad;
balanceOf[dst] += wad;
Transfer(src, dst, wad);
return true;
}
}
/*
GNU GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The GNU General Public License is a free, copyleft license for
software and other kinds of works.
The licenses for most software and other practical works are designed
to take away your freedom to share and change the works. By contrast,
the GNU General Public License is intended to guarantee your freedom to
share and change all versions of a program--to make sure it remains free
software for all its users. We, the Free Software Foundation, use the
GNU General Public License for most of our software; it applies also to
any other work released this way by its authors. You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
them if you wish), that you receive source code or can get it if you
want it, that you can change the software or use pieces of it in new
free programs, and that you know you can do these things.
To protect your rights, we need to prevent others from denying you
these rights or asking you to surrender the rights. Therefore, you have
certain responsibilities if you distribute copies of the software, or if
you modify it: responsibilities to respect the freedom of others.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must pass on to the recipients the same
freedoms that you received. You must make sure that they, too, receive
or can get the source code. And you must show them these terms so they
know their rights.
Developers that use the GNU GPL protect your rights with two steps:
(1) assert copyright on the software, and (2) offer you this License
giving you legal permission to copy, distribute and/or modify it.
For the developers' and authors' protection, the GPL clearly explains
that there is no warranty for this free software. For both users' and
authors' sake, the GPL requires that modified versions be marked as
changed, so that their problems will not be attributed erroneously to
authors of previous versions.
Some devices are designed to deny users access to install or run
modified versions of the software inside them, although the manufacturer
can do so. This is fundamentally incompatible with the aim of
protecting users' freedom to change the software. The systematic
pattern of such abuse occurs in the area of products for individuals to
use, which is precisely where it is most unacceptable. Therefore, we
have designed this version of the GPL to prohibit the practice for those
products. If such problems arise substantially in other domains, we
stand ready to extend this provision to those domains in future versions
of the GPL, as needed to protect the freedom of users.
Finally, every program is threatened constantly by software patents.
States should not allow patents to restrict development and use of
software on general-purpose computers, but in those that do, we wish to
avoid the special danger that patents applied to a free program could
make it effectively proprietary. To prevent this, the GPL assures that
patents cannot be used to render the program non-free.
The precise terms and conditions for copying, distribution and
modification follow.
TERMS AND CONDITIONS
0. Definitions.
"This License" refers to version 3 of the GNU General Public License.
"Copyright" also means copyright-like laws that apply to other kinds of
works, such as semiconductor masks.
"The Program" refers to any copyrightable work licensed under this
License. Each licensee is addressed as "you". "Licensees" and
"recipients" may be individuals or organizations.
To "modify" a work means to copy from or adapt all or part of the work
in a fashion requiring copyright permission, other than the making of an
exact copy. The resulting work is called a "modified version" of the
earlier work or a work "based on" the earlier work.
A "covered work" means either the unmodified Program or a work based
on the Program.
To "propagate" a work means to do anything with it that, without
permission, would make you directly or secondarily liable for
infringement under applicable copyright law, except executing it on a
computer or modifying a private copy. Propagation includes copying,
distribution (with or without modification), making available to the
public, and in some countries other activities as well.
To "convey" a work means any kind of propagation that enables other
parties to make or receive copies. Mere interaction with a user through
a computer network, with no transfer of a copy, is not conveying.
An interactive user interface displays "Appropriate Legal Notices"
to the extent that it includes a convenient and prominently visible
feature that (1) displays an appropriate copyright notice, and (2)
tells the user that there is no warranty for the work (except to the
extent that warranties are provided), that licensees may convey the
work under this License, and how to view a copy of this License. If
the interface presents a list of user commands or options, such as a
menu, a prominent item in the list meets this criterion.
1. Source Code.
The "source code" for a work means the preferred form of the work
for making modifications to it. "Object code" means any non-source
form of a work.
A "Standard Interface" means an interface that either is an official
standard defined by a recognized standards body, or, in the case of
interfaces specified for a particular programming language, one that
is widely used among developers working in that language.
The "System Libraries" of an executable work include anything, other
than the work as a whole, that (a) is included in the normal form of
packaging a Major Component, but which is not part of that Major
Component, and (b) serves only to enable use of the work with that
Major Component, or to implement a Standard Interface for which an
implementation is available to the public in source code form. A
"Major Component", in this context, means a major essential component
(kernel, window system, and so on) of the specific operating system
(if any) on which the executable work runs, or a compiler used to
produce the work, or an object code interpreter used to run it.
The "Corresponding Source" for a work in object code form means all
the source code needed to generate, install, and (for an executable
work) run the object code and to modify the work, including scripts to
control those activities. However, it does not include the work's
System Libraries, or general-purpose tools or generally available free
programs which are used unmodified in performing those activities but
which are not part of the work. For example, Corresponding Source
includes interface definition files associated with source files for
the work, and the source code for shared libraries and dynamically
linked subprograms that the work is specifically designed to require,
such as by intimate data communication or control flow between those
subprograms and other parts of the work.
The Corresponding Source need not include anything that users
can regenerate automatically from other parts of the Corresponding
Source.
The Corresponding Source for a work in source code form is that
same work.
2. Basic Permissions.
All rights granted under this License are granted for the term of
copyright on the Program, and are irrevocable provided the stated
conditions are met. This License explicitly affirms your unlimited
permission to run the unmodified Program. The output from running a
covered work is covered by this License only if the output, given its
content, constitutes a covered work. This License acknowledges your
rights of fair use or other equivalent, as provided by copyright law.
You may make, run and propagate covered works that you do not
convey, without conditions so long as your license otherwise remains
in force. You may convey covered works to others for the sole purpose
of having them make modifications exclusively for you, or provide you
with facilities for running those works, provided that you comply with
the terms of this License in conveying all material for which you do
not control copyright. Those thus making or running the covered works
for you must do so exclusively on your behalf, under your direction
and control, on terms that prohibit them from making any copies of
your copyrighted material outside their relationship with you.
Conveying under any other circumstances is permitted solely under
the conditions stated below. Sublicensing is not allowed; section 10
makes it unnecessary.
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
No covered work shall be deemed part of an effective technological
measure under any applicable law fulfilling obligations under article
11 of the WIPO copyright treaty adopted on 20 December 1996, or
similar laws prohibiting or restricting circumvention of such
measures.
When you convey a covered work, you waive any legal power to forbid
circumvention of technological measures to the extent such circumvention
is effected by exercising rights under this License with respect to
the covered work, and you disclaim any intention to limit operation or
modification of the work as a means of enforcing, against the work's
users, your or third parties' legal rights to forbid circumvention of
technological measures.
4. Conveying Verbatim Copies.
You may convey verbatim copies of the Program's source code as you
receive it, in any medium, provided that you conspicuously and
appropriately publish on each copy an appropriate copyright notice;
keep intact all notices stating that this License and any
non-permissive terms added in accord with section 7 apply to the code;
keep intact all notices of the absence of any warranty; and give all
recipients a copy of this License along with the Program.
You may charge any price or no price for each copy that you convey,
and you may offer support or warranty protection for a fee.
5. Conveying Modified Source Versions.
You may convey a work based on the Program, or the modifications to
produce it from the Program, in the form of source code under the
terms of section 4, provided that you also meet all of these conditions:
a) The work must carry prominent notices stating that you modified
it, and giving a relevant date.
b) The work must carry prominent notices stating that it is
released under this License and any conditions added under section
7. This requirement modifies the requirement in section 4 to
"keep intact all notices".
c) You must license the entire work, as a whole, under this
License to anyone who comes into possession of a copy. This
License will therefore apply, along with any applicable section 7
additional terms, to the whole of the work, and all its parts,
regardless of how they are packaged. This License gives no
permission to license the work in any other way, but it does not
invalidate such permission if you have separately received it.
d) If the work has interactive user interfaces, each must display
Appropriate Legal Notices; however, if the Program has interactive
interfaces that do not display Appropriate Legal Notices, your
work need not make them do so.
A compilation of a covered work with other separate and independent
works, which are not by their nature extensions of the covered work,
and which are not combined with it such as to form a larger program,
in or on a volume of a storage or distribution medium, is called an
"aggregate" if the compilation and its resulting copyright are not
used to limit the access or legal rights of the compilation's users
beyond what the individual works permit. Inclusion of a covered work
in an aggregate does not cause this License to apply to the other
parts of the aggregate.
6. Conveying Non-Source Forms.
You may convey a covered work in object code form under the terms
of sections 4 and 5, provided that you also convey the
machine-readable Corresponding Source under the terms of this License,
in one of these ways:
a) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by the
Corresponding Source fixed on a durable physical medium
customarily used for software interchange.
b) Convey the object code in, or embodied in, a physical product
(including a physical distribution medium), accompanied by a
written offer, valid for at least three years and valid for as
long as you offer spare parts or customer support for that product
model, to give anyone who possesses the object code either (1) a
copy of the Corresponding Source for all the software in the
product that is covered by this License, on a durable physical
medium customarily used for software interchange, for a price no
more than your reasonable cost of physically performing this
conveying of source, or (2) access to copy the
Corresponding Source from a network server at no charge.
c) Convey individual copies of the object code with a copy of the
written offer to provide the Corresponding Source. This
alternative is allowed only occasionally and noncommercially, and
only if you received the object code with such an offer, in accord
with subsection 6b.
d) Convey the object code by offering access from a designated
place (gratis or for a charge), and offer equivalent access to the
Corresponding Source in the same way through the same place at no
further charge. You need not require recipients to copy the
Corresponding Source along with the object code. If the place to
copy the object code is a network server, the Corresponding Source
may be on a different server (operated by you or a third party)
that supports equivalent copying facilities, provided you maintain
clear directions next to the object code saying where to find the
Corresponding Source. Regardless of what server hosts the
Corresponding Source, you remain obligated to ensure that it is
available for as long as needed to satisfy these requirements.
e) Convey the object code using peer-to-peer transmission, provided
you inform other peers where the object code and Corresponding
Source of the work are being offered to the general public at no
charge under subsection 6d.
A separable portion of the object code, whose source code is excluded
from the Corresponding Source as a System Library, need not be
included in conveying the object code work.
A "User Product" is either (1) a "consumer product", which means any
tangible personal property which is normally used for personal, family,
or household purposes, or (2) anything designed or sold for incorporation
into a dwelling. In determining whether a product is a consumer product,
doubtful cases shall be resolved in favor of coverage. For a particular
product received by a particular user, "normally used" refers to a
typical or common use of that class of product, regardless of the status
of the particular user or of the way in which the particular user
actually uses, or expects or is expected to use, the product. A product
is a consumer product regardless of whether the product has substantial
commercial, industrial or non-consumer uses, unless such uses represent
the only significant mode of use of the product.
"Installation Information" for a User Product means any methods,
procedures, authorization keys, or other information required to install
and execute modified versions of a covered work in that User Product from
a modified version of its Corresponding Source. The information must
suffice to ensure that the continued functioning of the modified object
code is in no case prevented or interfered with solely because
modification has been made.
If you convey an object code work under this section in, or with, or
specifically for use in, a User Product, and the conveying occurs as
part of a transaction in which the right of possession and use of the
User Product is transferred to the recipient in perpetuity or for a
fixed term (regardless of how the transaction is characterized), the
Corresponding Source conveyed under this section must be accompanied
by the Installation Information. But this requirement does not apply
if neither you nor any third party retains the ability to install
modified object code on the User Product (for example, the work has
been installed in ROM).
The requirement to provide Installation Information does not include a
requirement to continue to provide support service, warranty, or updates
for a work that has been modified or installed by the recipient, or for
the User Product in which it has been modified or installed. Access to a
network may be denied when the modification itself materially and
adversely affects the operation of the network or violates the rules and
protocols for communication across the network.
Corresponding Source conveyed, and Installation Information provided,
in accord with this section must be in a format that is publicly
documented (and with an implementation available to the public in
source code form), and must require no special password or key for
unpacking, reading or copying.
7. Additional Terms.
"Additional permissions" are terms that supplement the terms of this
License by making exceptions from one or more of its conditions.
Additional permissions that are applicable to the entire Program shall
be treated as though they were included in this License, to the extent
that they are valid under applicable law. If additional permissions
apply only to part of the Program, that part may be used separately
under those permissions, but the entire Program remains governed by
this License without regard to the additional permissions.
When you convey a copy of a covered work, you may at your option
remove any additional permissions from that copy, or from any part of
it. (Additional permissions may be written to require their own
removal in certain cases when you modify the work.) You may place
additional permissions on material, added by you to a covered work,
for which you have or can give appropriate copyright permission.
Notwithstanding any other provision of this License, for material you
add to a covered work, you may (if authorized by the copyright holders of
that material) supplement the terms of this License with terms:
a) Disclaiming warranty or limiting liability differently from the
terms of sections 15 and 16 of this License; or
b) Requiring preservation of specified reasonable legal notices or
author attributions in that material or in the Appropriate Legal
Notices displayed by works containing it; or
c) Prohibiting misrepresentation of the origin of that material, or
requiring that modified versions of such material be marked in
reasonable ways as different from the original version; or
d) Limiting the use for publicity purposes of names of licensors or
authors of the material; or
e) Declining to grant rights under trademark law for use of some
trade names, trademarks, or service marks; or
f) Requiring indemnification of licensors and authors of that
material by anyone who conveys the material (or modified versions of
it) with contractual assumptions of liability to the recipient, for
any liability that these contractual assumptions directly impose on
those licensors and authors.
All other non-permissive additional terms are considered "further
restrictions" within the meaning of section 10. If the Program as you
received it, or any part of it, contains a notice stating that it is
governed by this License along with a term that is a further
restriction, you may remove that term. If a license document contains
a further restriction but permits relicensing or conveying under this
License, you may add to a covered work material governed by the terms
of that license document, provided that the further restriction does
not survive such relicensing or conveying.
If you add terms to a covered work in accord with this section, you
must place, in the relevant source files, a statement of the
additional terms that apply to those files, or a notice indicating
where to find the applicable terms.
Additional terms, permissive or non-permissive, may be stated in the
form of a separately written license, or stated as exceptions;
the above requirements apply either way.
8. Termination.
You may not propagate or modify a covered work except as expressly
provided under this License. Any attempt otherwise to propagate or
modify it is void, and will automatically terminate your rights under
this License (including any patent licenses granted under the third
paragraph of section 11).
However, if you cease all violation of this License, then your
license from a particular copyright holder is reinstated (a)
provisionally, unless and until the copyright holder explicitly and
finally terminates your license, and (b) permanently, if the copyright
holder fails to notify you of the violation by some reasonable means
prior to 60 days after the cessation.
Moreover, your license from a particular copyright holder is
reinstated permanently if the copyright holder notifies you of the
violation by some reasonable means, this is the first time you have
received notice of violation of this License (for any work) from that
copyright holder, and you cure the violation prior to 30 days after
your receipt of the notice.
Termination of your rights under this section does not terminate the
licenses of parties who have received copies or rights from you under
this License. If your rights have been terminated and not permanently
reinstated, you do not qualify to receive new licenses for the same
material under section 10.
9. Acceptance Not Required for Having Copies.
You are not required to accept this License in order to receive or
run a copy of the Program. Ancillary propagation of a covered work
occurring solely as a consequence of using peer-to-peer transmission
to receive a copy likewise does not require acceptance. However,
nothing other than this License grants you permission to propagate or
modify any covered work. These actions infringe copyright if you do
not accept this License. Therefore, by modifying or propagating a
covered work, you indicate your acceptance of this License to do so.
10. Automatic Licensing of Downstream Recipients.
Each time you convey a covered work, the recipient automatically
receives a license from the original licensors, to run, modify and
propagate that work, subject to this License. You are not responsible
for enforcing compliance by third parties with this License.
An "entity transaction" is a transaction transferring control of an
organization, or substantially all assets of one, or subdividing an
organization, or merging organizations. If propagation of a covered
work results from an entity transaction, each party to that
transaction who receives a copy of the work also receives whatever
licenses to the work the party's predecessor in interest had or could
give under the previous paragraph, plus a right to possession of the
Corresponding Source of the work from the predecessor in interest, if
the predecessor has it or can get it with reasonable efforts.
You may not impose any further restrictions on the exercise of the
rights granted or affirmed under this License. For example, you may
not impose a license fee, royalty, or other charge for exercise of
rights granted under this License, and you may not initiate litigation
(including a cross-claim or counterclaim in a lawsuit) alleging that
any patent claim is infringed by making, using, selling, offering for
sale, or importing the Program or any portion of it.
11. Patents.
A "contributor" is a copyright holder who authorizes use under this
License of the Program or a work on which the Program is based. The
work thus licensed is called the contributor's "contributor version".
A contributor's "essential patent claims" are all patent claims
owned or controlled by the contributor, whether already acquired or
hereafter acquired, that would be infringed by some manner, permitted
by this License, of making, using, or selling its contributor version,
but do not include claims that would be infringed only as a
consequence of further modification of the contributor version. For
purposes of this definition, "control" includes the right to grant
patent sublicenses in a manner consistent with the requirements of
this License.
Each contributor grants you a non-exclusive, worldwide, royalty-free
patent license under the contributor's essential patent claims, to
make, use, sell, offer for sale, import and otherwise run, modify and
propagate the contents of its contributor version.
In the following three paragraphs, a "patent license" is any express
agreement or commitment, however denominated, not to enforce a patent
(such as an express permission to practice a patent or covenant not to
sue for patent infringement). To "grant" such a patent license to a
party means to make such an agreement or commitment not to enforce a
patent against the party.
If you convey a covered work, knowingly relying on a patent license,
and the Corresponding Source of the work is not available for anyone
to copy, free of charge and under the terms of this License, through a
publicly available network server or other readily accessible means,
then you must either (1) cause the Corresponding Source to be so
available, or (2) arrange to deprive yourself of the benefit of the
patent license for this particular work, or (3) arrange, in a manner
consistent with the requirements of this License, to extend the patent
license to downstream recipients. "Knowingly relying" means you have
actual knowledge that, but for the patent license, your conveying the
covered work in a country, or your recipient's use of the covered work
in a country, would infringe one or more identifiable patents in that
country that you have reason to believe are valid.
If, pursuant to or in connection with a single transaction or
arrangement, you convey, or propagate by procuring conveyance of, a
covered work, and grant a patent license to some of the parties
receiving the covered work authorizing them to use, propagate, modify
or convey a specific copy of the covered work, then the patent license
you grant is automatically extended to all recipients of the covered
work and works based on it.
A patent license is "discriminatory" if it does not include within
the scope of its coverage, prohibits the exercise of, or is
conditioned on the non-exercise of one or more of the rights that are
specifically granted under this License. You may not convey a covered
work if you are a party to an arrangement with a third party that is
in the business of distributing software, under which you make payment
to the third party based on the extent of your activity of conveying
the work, and under which the third party grants, to any of the
parties who would receive the covered work from you, a discriminatory
patent license (a) in connection with copies of the covered work
conveyed by you (or copies made from those copies), or (b) primarily
for and in connection with specific products or compilations that
contain the covered work, unless you entered into that arrangement,
or that patent license was granted, prior to 28 March 2007.
Nothing in this License shall be construed as excluding or limiting
any implied license or other defenses to infringement that may
otherwise be available to you under applicable patent law.
12. No Surrender of Others' Freedom.
If conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot convey a
covered work so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you may
not convey it at all. For example, if you agree to terms that obligate you
to collect a royalty for further conveying from those to whom you convey
the Program, the only way you could satisfy both those terms and this
License would be to refrain entirely from conveying the Program.
13. Use with the GNU Affero General Public License.
Notwithstanding any other provision of this License, you have
permission to link or combine any covered work with a work licensed
under version 3 of the GNU Affero General Public License into a single
combined work, and to convey the resulting work. The terms of this
License will continue to apply to the part which is the covered work,
but the special requirements of the GNU Affero General Public License,
section 13, concerning interaction through a network will apply to the
combination as such.
14. Revised Versions of this License.
The Free Software Foundation may publish revised and/or new versions of
the GNU General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the
Program specifies that a certain numbered version of the GNU General
Public License "or any later version" applies to it, you have the
option of following the terms and conditions either of that numbered
version or of any later version published by the Free Software
Foundation. If the Program does not specify a version number of the
GNU General Public License, you may choose any version ever published
by the Free Software Foundation.
If the Program specifies that a proxy can decide which future
versions of the GNU General Public License can be used, that proxy's
public statement of acceptance of a version permanently authorizes you
to choose that version for the Program.
Later license versions may give you additional or different
permissions. However, no additional obligations are imposed on any
author or copyright holder as a result of your choosing to follow a
later version.
15. Disclaimer of Warranty.
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
16. Limitation of Liability.
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
SUCH DAMAGES.
17. Interpretation of Sections 15 and 16.
If the disclaimer of warranty and limitation of liability provided
above cannot be given local legal effect according to their terms,
reviewing courts shall apply local law that most closely approximates
an absolute waiver of all civil liability in connection with the
Program, unless a warranty or assumption of liability accompanies a
copy of the Program in return for a fee.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
state the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) <year> <name of author>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
Also add information on how to contact you by electronic and paper mail.
If the program does terminal interaction, make it output a short
notice like this when it starts in an interactive mode:
<program> Copyright (C) <year> <name of author>
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, your program's commands
might be different; for a GUI interface, you would use an "about box".
You should also get your employer (if you work as a programmer) or school,
if any, to sign a "copyright disclaimer" for the program, if necessary.
For more information on this, and how to apply and follow the GNU GPL, see
<http://www.gnu.org/licenses/>.
The GNU General Public License does not permit incorporating your program
into proprietary programs. If your program is a subroutine library, you
may consider it more useful to permit linking proprietary applications with
the library. If this is what you want to do, use the GNU Lesser General
Public License instead of this License. But first, please read
<http://www.gnu.org/philosophy/why-not-lgpl.html>.
*/File 5 of 5: BridgeFacet
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/ContextUpgradeable.sol";
import "../proxy/utils/Initializable.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract OwnableUpgradeable is Initializable, ContextUpgradeable {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
function __Ownable_init() internal onlyInitializing {
__Ownable_init_unchained();
}
function __Ownable_init_unchained() internal onlyInitializing {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions anymore. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby removing any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[49] private __gap;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (proxy/utils/Initializable.sol)
pragma solidity ^0.8.2;
import "../../utils/AddressUpgradeable.sol";
/**
* @dev This is a base contract to aid in writing upgradeable contracts, or any kind of contract that will be deployed
* behind a proxy. Since proxied contracts do not make use of a constructor, it's common to move constructor logic to an
* external initializer function, usually called `initialize`. It then becomes necessary to protect this initializer
* function so it can only be called once. The {initializer} modifier provided by this contract will have this effect.
*
* The initialization functions use a version number. Once a version number is used, it is consumed and cannot be
* reused. This mechanism prevents re-execution of each "step" but allows the creation of new initialization steps in
* case an upgrade adds a module that needs to be initialized.
*
* For example:
*
* [.hljs-theme-light.nopadding]
* ```
* contract MyToken is ERC20Upgradeable {
* function initialize() initializer public {
* __ERC20_init("MyToken", "MTK");
* }
* }
* contract MyTokenV2 is MyToken, ERC20PermitUpgradeable {
* function initializeV2() reinitializer(2) public {
* __ERC20Permit_init("MyToken");
* }
* }
* ```
*
* TIP: To avoid leaving the proxy in an uninitialized state, the initializer function should be called as early as
* possible by providing the encoded function call as the `_data` argument to {ERC1967Proxy-constructor}.
*
* CAUTION: When used with inheritance, manual care must be taken to not invoke a parent initializer twice, or to ensure
* that all initializers are idempotent. This is not verified automatically as constructors are by Solidity.
*
* [CAUTION]
* ====
* Avoid leaving a contract uninitialized.
*
* An uninitialized contract can be taken over by an attacker. This applies to both a proxy and its implementation
* contract, which may impact the proxy. To prevent the implementation contract from being used, you should invoke
* the {_disableInitializers} function in the constructor to automatically lock it when it is deployed:
*
* [.hljs-theme-light.nopadding]
* ```
* /// @custom:oz-upgrades-unsafe-allow constructor
* constructor() {
* _disableInitializers();
* }
* ```
* ====
*/
abstract contract Initializable {
/**
* @dev Indicates that the contract has been initialized.
* @custom:oz-retyped-from bool
*/
uint8 private _initialized;
/**
* @dev Indicates that the contract is in the process of being initialized.
*/
bool private _initializing;
/**
* @dev Triggered when the contract has been initialized or reinitialized.
*/
event Initialized(uint8 version);
/**
* @dev A modifier that defines a protected initializer function that can be invoked at most once. In its scope,
* `onlyInitializing` functions can be used to initialize parent contracts.
*
* Similar to `reinitializer(1)`, except that functions marked with `initializer` can be nested in the context of a
* constructor.
*
* Emits an {Initialized} event.
*/
modifier initializer() {
bool isTopLevelCall = !_initializing;
require(
(isTopLevelCall && _initialized < 1) || (!AddressUpgradeable.isContract(address(this)) && _initialized == 1),
"Initializable: contract is already initialized"
);
_initialized = 1;
if (isTopLevelCall) {
_initializing = true;
}
_;
if (isTopLevelCall) {
_initializing = false;
emit Initialized(1);
}
}
/**
* @dev A modifier that defines a protected reinitializer function that can be invoked at most once, and only if the
* contract hasn't been initialized to a greater version before. In its scope, `onlyInitializing` functions can be
* used to initialize parent contracts.
*
* A reinitializer may be used after the original initialization step. This is essential to configure modules that
* are added through upgrades and that require initialization.
*
* When `version` is 1, this modifier is similar to `initializer`, except that functions marked with `reinitializer`
* cannot be nested. If one is invoked in the context of another, execution will revert.
*
* Note that versions can jump in increments greater than 1; this implies that if multiple reinitializers coexist in
* a contract, executing them in the right order is up to the developer or operator.
*
* WARNING: setting the version to 255 will prevent any future reinitialization.
*
* Emits an {Initialized} event.
*/
modifier reinitializer(uint8 version) {
require(!_initializing && _initialized < version, "Initializable: contract is already initialized");
_initialized = version;
_initializing = true;
_;
_initializing = false;
emit Initialized(version);
}
/**
* @dev Modifier to protect an initialization function so that it can only be invoked by functions with the
* {initializer} and {reinitializer} modifiers, directly or indirectly.
*/
modifier onlyInitializing() {
require(_initializing, "Initializable: contract is not initializing");
_;
}
/**
* @dev Locks the contract, preventing any future reinitialization. This cannot be part of an initializer call.
* Calling this in the constructor of a contract will prevent that contract from being initialized or reinitialized
* to any version. It is recommended to use this to lock implementation contracts that are designed to be called
* through proxies.
*
* Emits an {Initialized} event the first time it is successfully executed.
*/
function _disableInitializers() internal virtual {
require(!_initializing, "Initializable: contract is initializing");
if (_initialized < type(uint8).max) {
_initialized = type(uint8).max;
emit Initialized(type(uint8).max);
}
}
/**
* @dev Internal function that returns the initialized version. Returns `_initialized`
*/
function _getInitializedVersion() internal view returns (uint8) {
return _initialized;
}
/**
* @dev Internal function that returns the initialized version. Returns `_initializing`
*/
function _isInitializing() internal view returns (bool) {
return _initializing;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.0;
import "./IERC20Upgradeable.sol";
import "./extensions/IERC20MetadataUpgradeable.sol";
import "../../utils/ContextUpgradeable.sol";
import "../../proxy/utils/Initializable.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
* For a generic mechanism see {ERC20PresetMinterPauser}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC20
* applications.
*
* Additionally, an {Approval} event is emitted on calls to {transferFrom}.
* This allows applications to reconstruct the allowance for all accounts just
* by listening to said events. Other implementations of the EIP may not emit
* these events, as it isn't required by the specification.
*
* Finally, the non-standard {decreaseAllowance} and {increaseAllowance}
* functions have been added to mitigate the well-known issues around setting
* allowances. See {IERC20-approve}.
*/
contract ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20Upgradeable, IERC20MetadataUpgradeable {
mapping(address => uint256) private _balances;
mapping(address => mapping(address => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* The default value of {decimals} is 18. To select a different value for
* {decimals} you should overload it.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
__ERC20_init_unchained(name_, symbol_);
}
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual override returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the value {ERC20} uses, unless this function is
* overridden;
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual override returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual override returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual override returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address to, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_transfer(owner, to, amount);
return true;
}
/**
* @dev See {IERC20-allowance}.
*/
function allowance(address owner, address spender) public view virtual override returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `amount` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 amount) public virtual override returns (bool) {
address owner = _msgSender();
_approve(owner, spender, amount);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Emits an {Approval} event indicating the updated allowance. This is not
* required by the EIP. See the note at the beginning of {ERC20}.
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
* - the caller must have allowance for ``from``'s tokens of at least
* `amount`.
*/
function transferFrom(
address from,
address to,
uint256 amount
) public virtual override returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, amount);
_transfer(from, to, amount);
return true;
}
/**
* @dev Atomically increases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function increaseAllowance(address spender, uint256 addedValue) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, allowance(owner, spender) + addedValue);
return true;
}
/**
* @dev Atomically decreases the allowance granted to `spender` by the caller.
*
* This is an alternative to {approve} that can be used as a mitigation for
* problems described in {IERC20-approve}.
*
* Emits an {Approval} event indicating the updated allowance.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `spender` must have allowance for the caller of at least
* `subtractedValue`.
*/
function decreaseAllowance(address spender, uint256 subtractedValue) public virtual returns (bool) {
address owner = _msgSender();
uint256 currentAllowance = allowance(owner, spender);
require(currentAllowance >= subtractedValue, "ERC20: decreased allowance below zero");
unchecked {
_approve(owner, spender, currentAllowance - subtractedValue);
}
return true;
}
/**
* @dev Moves `amount` of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* Requirements:
*
* - `from` cannot be the zero address.
* - `to` cannot be the zero address.
* - `from` must have a balance of at least `amount`.
*/
function _transfer(
address from,
address to,
uint256 amount
) internal virtual {
require(from != address(0), "ERC20: transfer from the zero address");
require(to != address(0), "ERC20: transfer to the zero address");
_beforeTokenTransfer(from, to, amount);
uint256 fromBalance = _balances[from];
require(fromBalance >= amount, "ERC20: transfer amount exceeds balance");
unchecked {
_balances[from] = fromBalance - amount;
// Overflow not possible: the sum of all balances is capped by totalSupply, and the sum is preserved by
// decrementing then incrementing.
_balances[to] += amount;
}
emit Transfer(from, to, amount);
_afterTokenTransfer(from, to, amount);
}
/** @dev Creates `amount` tokens and assigns them to `account`, increasing
* the total supply.
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
*/
function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_beforeTokenTransfer(address(0), account, amount);
_totalSupply += amount;
unchecked {
// Overflow not possible: balance + amount is at most totalSupply + amount, which is checked above.
_balances[account] += amount;
}
emit Transfer(address(0), account, amount);
_afterTokenTransfer(address(0), account, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* Requirements:
*
* - `account` cannot be the zero address.
* - `account` must have at least `amount` tokens.
*/
function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_beforeTokenTransfer(account, address(0), amount);
uint256 accountBalance = _balances[account];
require(accountBalance >= amount, "ERC20: burn amount exceeds balance");
unchecked {
_balances[account] = accountBalance - amount;
// Overflow not possible: amount <= accountBalance <= totalSupply.
_totalSupply -= amount;
}
emit Transfer(account, address(0), amount);
_afterTokenTransfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner` s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*/
function _approve(
address owner,
address spender,
uint256 amount
) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
emit Approval(owner, spender, amount);
}
/**
* @dev Updates `owner` s allowance for `spender` based on spent `amount`.
*
* Does not update the allowance amount in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Might emit an {Approval} event.
*/
function _spendAllowance(
address owner,
address spender,
uint256 amount
) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance != type(uint256).max) {
require(currentAllowance >= amount, "ERC20: insufficient allowance");
unchecked {
_approve(owner, spender, currentAllowance - amount);
}
}
}
/**
* @dev Hook that is called before any transfer of tokens. This includes
* minting and burning.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* will be transferred to `to`.
* - when `from` is zero, `amount` tokens will be minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens will be burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
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.
*
* Calling conditions:
*
* - when `from` and `to` are both non-zero, `amount` of ``from``'s tokens
* has been transferred to `to`.
* - when `from` is zero, `amount` tokens have been minted for `to`.
* - when `to` is zero, `amount` of ``from``'s tokens have been burned.
* - `from` and `to` are never both zero.
*
* To learn more about hooks, head to xref:ROOT:extending-contracts.adoc#using-hooks[Using Hooks].
*/
function _afterTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual {}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[45] private __gap;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.5.0) (token/ERC20/extensions/ERC20Burnable.sol)
pragma solidity ^0.8.0;
import "../ERC20Upgradeable.sol";
import "../../../utils/ContextUpgradeable.sol";
import "../../../proxy/utils/Initializable.sol";
/**
* @dev Extension of {ERC20} that allows token holders to destroy both their own
* tokens and those that they have an allowance for, in a way that can be
* recognized off-chain (via event analysis).
*/
abstract contract ERC20BurnableUpgradeable is Initializable, ContextUpgradeable, ERC20Upgradeable {
function __ERC20Burnable_init() internal onlyInitializing {
}
function __ERC20Burnable_init_unchained() internal onlyInitializing {
}
/**
* @dev Destroys `amount` tokens from the caller.
*
* See {ERC20-_burn}.
*/
function burn(uint256 amount) public virtual {
_burn(_msgSender(), amount);
}
/**
* @dev Destroys `amount` tokens from `account`, deducting from the caller's
* allowance.
*
* See {ERC20-_burn} and {ERC20-allowance}.
*
* Requirements:
*
* - the caller must have allowance for ``accounts``'s tokens of at least
* `amount`.
*/
function burnFrom(address account, uint256 amount) public virtual {
_spendAllowance(account, _msgSender(), amount);
_burn(account, amount);
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20Upgradeable.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20MetadataUpgradeable is IERC20Upgradeable {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20Upgradeable {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library AddressUpgradeable {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (utils/Context.sol)
pragma solidity ^0.8.0;
import "../proxy/utils/Initializable.sol";
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract ContextUpgradeable is Initializable {
function __Context_init() internal onlyInitializing {
}
function __Context_init_unchained() internal onlyInitializing {
}
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[50] private __gap;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/draft-IERC20Permit.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts v4.4.1 (token/ERC20/extensions/IERC20Metadata.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
/**
* @dev Interface for the optional metadata functions from the ERC20 standard.
*
* _Available since v4.1._
*/
interface IERC20Metadata is IERC20 {
/**
* @dev Returns the name of the token.
*/
function name() external view returns (string memory);
/**
* @dev Returns the symbol of the token.
*/
function symbol() external view returns (string memory);
/**
* @dev Returns the decimals places of the token.
*/
function decimals() external view returns (uint8);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.6.0) (token/ERC20/IERC20.sol)
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves `amount` tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 amount) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 amount) external returns (bool);
/**
* @dev Moves `amount` tokens from `from` to `to` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(
address from,
address to,
uint256 amount
) external returns (bool);
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (token/ERC20/utils/SafeERC20.sol)
pragma solidity ^0.8.0;
import "../IERC20.sol";
import "../extensions/draft-IERC20Permit.sol";
import "../../../utils/Address.sol";
/**
* @title SafeERC20
* @dev Wrappers around ERC20 operations that throw on failure (when the token
* contract returns false). Tokens that return no value (and instead revert or
* throw on failure) are also supported, non-reverting calls are assumed to be
* successful.
* To use this library you can add a `using SafeERC20 for IERC20;` statement to your contract,
* which allows you to call the safe operations as `token.safeTransfer(...)`, etc.
*/
library SafeERC20 {
using Address for address;
function safeTransfer(
IERC20 token,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transfer.selector, to, value));
}
function safeTransferFrom(
IERC20 token,
address from,
address to,
uint256 value
) internal {
_callOptionalReturn(token, abi.encodeWithSelector(token.transferFrom.selector, from, to, value));
}
/**
* @dev Deprecated. This function has issues similar to the ones found in
* {IERC20-approve}, and its usage is discouraged.
*
* Whenever possible, use {safeIncreaseAllowance} and
* {safeDecreaseAllowance} instead.
*/
function safeApprove(
IERC20 token,
address spender,
uint256 value
) internal {
// safeApprove should only be called when setting an initial allowance,
// or when resetting it to zero. To increase and decrease it, use
// 'safeIncreaseAllowance' and 'safeDecreaseAllowance'
require(
(value == 0) || (token.allowance(address(this), spender) == 0),
"SafeERC20: approve from non-zero to non-zero allowance"
);
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, value));
}
function safeIncreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
uint256 newAllowance = token.allowance(address(this), spender) + value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
function safeDecreaseAllowance(
IERC20 token,
address spender,
uint256 value
) internal {
unchecked {
uint256 oldAllowance = token.allowance(address(this), spender);
require(oldAllowance >= value, "SafeERC20: decreased allowance below zero");
uint256 newAllowance = oldAllowance - value;
_callOptionalReturn(token, abi.encodeWithSelector(token.approve.selector, spender, newAllowance));
}
}
function safePermit(
IERC20Permit token,
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) internal {
uint256 nonceBefore = token.nonces(owner);
token.permit(owner, spender, value, deadline, v, r, s);
uint256 nonceAfter = token.nonces(owner);
require(nonceAfter == nonceBefore + 1, "SafeERC20: permit did not succeed");
}
/**
* @dev Imitates a Solidity high-level call (i.e. a regular function call to a contract), relaxing the requirement
* on the return value: the return value is optional (but if data is returned, it must not be false).
* @param token The token targeted by the call.
* @param data The call data (encoded using abi.encode or one of its variants).
*/
function _callOptionalReturn(IERC20 token, bytes memory data) private {
// We need to perform a low level call here, to bypass Solidity's return data size checking mechanism, since
// we're implementing it ourselves. We use {Address-functionCall} to perform this call, which verifies that
// the target address contains contract code and also asserts for success in the low-level call.
bytes memory returndata = address(token).functionCall(data, "SafeERC20: low-level call failed");
if (returndata.length > 0) {
// Return data is optional
require(abi.decode(returndata, (bool)), "SafeERC20: ERC20 operation did not succeed");
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Address.sol)
pragma solidity ^0.8.1;
/**
* @dev Collection of functions related to the address type
*/
library Address {
/**
* @dev Returns true if `account` is a contract.
*
* [IMPORTANT]
* ====
* It is unsafe to assume that an address for which this function returns
* false is an externally-owned account (EOA) and not a contract.
*
* Among others, `isContract` will return false for the following
* types of addresses:
*
* - an externally-owned account
* - a contract in construction
* - an address where a contract will be created
* - an address where a contract lived, but was destroyed
* ====
*
* [IMPORTANT]
* ====
* You shouldn't rely on `isContract` to protect against flash loan attacks!
*
* Preventing calls from contracts is highly discouraged. It breaks composability, breaks support for smart wallets
* like Gnosis Safe, and does not provide security since it can be circumvented by calling from a contract
* constructor.
* ====
*/
function isContract(address account) internal view returns (bool) {
// This method relies on extcodesize/address.code.length, which returns 0
// for contracts in construction, since the code is only stored at the end
// of the constructor execution.
return account.code.length > 0;
}
/**
* @dev Replacement for Solidity's `transfer`: sends `amount` wei to
* `recipient`, forwarding all available gas and reverting on errors.
*
* https://eips.ethereum.org/EIPS/eip-1884[EIP1884] increases the gas cost
* of certain opcodes, possibly making contracts go over the 2300 gas limit
* imposed by `transfer`, making them unable to receive funds via
* `transfer`. {sendValue} removes this limitation.
*
* https://diligence.consensys.net/posts/2019/09/stop-using-soliditys-transfer-now/[Learn more].
*
* IMPORTANT: because control is transferred to `recipient`, care must be
* taken to not create reentrancy vulnerabilities. Consider using
* {ReentrancyGuard} or the
* https://solidity.readthedocs.io/en/v0.5.11/security-considerations.html#use-the-checks-effects-interactions-pattern[checks-effects-interactions pattern].
*/
function sendValue(address payable recipient, uint256 amount) internal {
require(address(this).balance >= amount, "Address: insufficient balance");
(bool success, ) = recipient.call{value: amount}("");
require(success, "Address: unable to send value, recipient may have reverted");
}
/**
* @dev Performs a Solidity function call using a low level `call`. A
* plain `call` is an unsafe replacement for a function call: use this
* function instead.
*
* If `target` reverts with a revert reason, it is bubbled up by this
* function (like regular Solidity function calls).
*
* Returns the raw returned data. To convert to the expected return value,
* use https://solidity.readthedocs.io/en/latest/units-and-global-variables.html?highlight=abi.decode#abi-encoding-and-decoding-functions[`abi.decode`].
*
* Requirements:
*
* - `target` must be a contract.
* - calling `target` with `data` must not revert.
*
* _Available since v3.1._
*/
function functionCall(address target, bytes memory data) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, "Address: low-level call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`], but with
* `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
return functionCallWithValue(target, data, 0, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but also transferring `value` wei to `target`.
*
* Requirements:
*
* - the calling contract must have an ETH balance of at least `value`.
* - the called Solidity function must be `payable`.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value
) internal returns (bytes memory) {
return functionCallWithValue(target, data, value, "Address: low-level call with value failed");
}
/**
* @dev Same as {xref-Address-functionCallWithValue-address-bytes-uint256-}[`functionCallWithValue`], but
* with `errorMessage` as a fallback revert reason when `target` reverts.
*
* _Available since v3.1._
*/
function functionCallWithValue(
address target,
bytes memory data,
uint256 value,
string memory errorMessage
) internal returns (bytes memory) {
require(address(this).balance >= value, "Address: insufficient balance for call");
(bool success, bytes memory returndata) = target.call{value: value}(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(address target, bytes memory data) internal view returns (bytes memory) {
return functionStaticCall(target, data, "Address: low-level static call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a static call.
*
* _Available since v3.3._
*/
function functionStaticCall(
address target,
bytes memory data,
string memory errorMessage
) internal view returns (bytes memory) {
(bool success, bytes memory returndata) = target.staticcall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(address target, bytes memory data) internal returns (bytes memory) {
return functionDelegateCall(target, data, "Address: low-level delegate call failed");
}
/**
* @dev Same as {xref-Address-functionCall-address-bytes-string-}[`functionCall`],
* but performing a delegate call.
*
* _Available since v3.4._
*/
function functionDelegateCall(
address target,
bytes memory data,
string memory errorMessage
) internal returns (bytes memory) {
(bool success, bytes memory returndata) = target.delegatecall(data);
return verifyCallResultFromTarget(target, success, returndata, errorMessage);
}
/**
* @dev Tool to verify that a low level call to smart-contract was successful, and revert (either by bubbling
* the revert reason or using the provided one) in case of unsuccessful call or if target was not a contract.
*
* _Available since v4.8._
*/
function verifyCallResultFromTarget(
address target,
bool success,
bytes memory returndata,
string memory errorMessage
) internal view returns (bytes memory) {
if (success) {
if (returndata.length == 0) {
// only check isContract if the call was successful and the return data is empty
// otherwise we already know that it was a contract
require(isContract(target), "Address: call to non-contract");
}
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
/**
* @dev Tool to verify that a low level call was successful, and revert if it wasn't, either by bubbling the
* revert reason or using the provided one.
*
* _Available since v4.3._
*/
function verifyCallResult(
bool success,
bytes memory returndata,
string memory errorMessage
) internal pure returns (bytes memory) {
if (success) {
return returndata;
} else {
_revert(returndata, errorMessage);
}
}
function _revert(bytes memory returndata, string memory errorMessage) private pure {
// Look for revert reason and bubble it up if present
if (returndata.length > 0) {
// The easiest way to bubble the revert reason is using memory via assembly
/// @solidity memory-safe-assembly
assembly {
let returndata_size := mload(returndata)
revert(add(32, returndata), returndata_size)
}
} else {
revert(errorMessage);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/cryptography/ECDSA.sol)
pragma solidity ^0.8.0;
import "../Strings.sol";
/**
* @dev Elliptic Curve Digital Signature Algorithm (ECDSA) operations.
*
* These functions can be used to verify that a message was signed by the holder
* of the private keys of a given address.
*/
library ECDSA {
enum RecoverError {
NoError,
InvalidSignature,
InvalidSignatureLength,
InvalidSignatureS,
InvalidSignatureV // Deprecated in v4.8
}
function _throwError(RecoverError error) private pure {
if (error == RecoverError.NoError) {
return; // no error: do nothing
} else if (error == RecoverError.InvalidSignature) {
revert("ECDSA: invalid signature");
} else if (error == RecoverError.InvalidSignatureLength) {
revert("ECDSA: invalid signature length");
} else if (error == RecoverError.InvalidSignatureS) {
revert("ECDSA: invalid signature 's' value");
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature` or error string. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*
* Documentation for signature generation:
* - with https://web3js.readthedocs.io/en/v1.3.4/web3-eth-accounts.html#sign[Web3.js]
* - with https://docs.ethers.io/v5/api/signer/#Signer-signMessage[ethers]
*
* _Available since v4.3._
*/
function tryRecover(bytes32 hash, bytes memory signature) internal pure returns (address, RecoverError) {
if (signature.length == 65) {
bytes32 r;
bytes32 s;
uint8 v;
// ecrecover takes the signature parameters, and the only way to get them
// currently is to use assembly.
/// @solidity memory-safe-assembly
assembly {
r := mload(add(signature, 0x20))
s := mload(add(signature, 0x40))
v := byte(0, mload(add(signature, 0x60)))
}
return tryRecover(hash, v, r, s);
} else {
return (address(0), RecoverError.InvalidSignatureLength);
}
}
/**
* @dev Returns the address that signed a hashed message (`hash`) with
* `signature`. This address can then be used for verification purposes.
*
* The `ecrecover` EVM opcode allows for malleable (non-unique) signatures:
* this function rejects them by requiring the `s` value to be in the lower
* half order, and the `v` value to be either 27 or 28.
*
* IMPORTANT: `hash` _must_ be the result of a hash operation for the
* verification to be secure: it is possible to craft signatures that
* recover to arbitrary addresses for non-hashed data. A safe way to ensure
* this is by receiving a hash of the original message (which may otherwise
* be too long), and then calling {toEthSignedMessageHash} on it.
*/
function recover(bytes32 hash, bytes memory signature) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, signature);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `r` and `vs` short-signature fields separately.
*
* See https://eips.ethereum.org/EIPS/eip-2098[EIP-2098 short signatures]
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address, RecoverError) {
bytes32 s = vs & bytes32(0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff);
uint8 v = uint8((uint256(vs) >> 255) + 27);
return tryRecover(hash, v, r, s);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `r and `vs` short-signature fields separately.
*
* _Available since v4.2._
*/
function recover(
bytes32 hash,
bytes32 r,
bytes32 vs
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, r, vs);
_throwError(error);
return recovered;
}
/**
* @dev Overload of {ECDSA-tryRecover} that receives the `v`,
* `r` and `s` signature fields separately.
*
* _Available since v4.3._
*/
function tryRecover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address, RecoverError) {
// EIP-2 still allows signature malleability for ecrecover(). Remove this possibility and make the signature
// unique. Appendix F in the Ethereum Yellow paper (https://ethereum.github.io/yellowpaper/paper.pdf), defines
// the valid range for s in (301): 0 < s < secp256k1n ÷ 2 + 1, and for v in (302): v ∈ {27, 28}. Most
// signatures from current libraries generate a unique signature with an s-value in the lower half order.
//
// If your library generates malleable signatures, such as s-values in the upper range, calculate a new s-value
// with 0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFEBAAEDCE6AF48A03BBFD25E8CD0364141 - s1 and flip v from 27 to 28 or
// vice versa. If your library also generates signatures with 0/1 for v instead 27/28, add 27 to v to accept
// these malleable signatures as well.
if (uint256(s) > 0x7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF5D576E7357A4501DDFE92F46681B20A0) {
return (address(0), RecoverError.InvalidSignatureS);
}
// If the signature is valid (and not malleable), return the signer address
address signer = ecrecover(hash, v, r, s);
if (signer == address(0)) {
return (address(0), RecoverError.InvalidSignature);
}
return (signer, RecoverError.NoError);
}
/**
* @dev Overload of {ECDSA-recover} that receives the `v`,
* `r` and `s` signature fields separately.
*/
function recover(
bytes32 hash,
uint8 v,
bytes32 r,
bytes32 s
) internal pure returns (address) {
(address recovered, RecoverError error) = tryRecover(hash, v, r, s);
_throwError(error);
return recovered;
}
/**
* @dev Returns an Ethereum Signed Message, created from a `hash`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes32 hash) internal pure returns (bytes32) {
// 32 is the length in bytes of hash,
// enforced by the type signature above
return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
32", hash));
}
/**
* @dev Returns an Ethereum Signed Message, created from `s`. This
* produces hash corresponding to the one signed with the
* https://eth.wiki/json-rpc/API#eth_sign[`eth_sign`]
* JSON-RPC method as part of EIP-191.
*
* See {recover}.
*/
function toEthSignedMessageHash(bytes memory s) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\\x19Ethereum Signed Message:\
", Strings.toString(s.length), s));
}
/**
* @dev Returns an Ethereum Signed Typed Data, created from a
* `domainSeparator` and a `structHash`. This produces hash corresponding
* to the one signed with the
* https://eips.ethereum.org/EIPS/eip-712[`eth_signTypedData`]
* JSON-RPC method as part of EIP-712.
*
* See {recover}.
*/
function toTypedDataHash(bytes32 domainSeparator, bytes32 structHash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\\x19\\x01", domainSeparator, structHash));
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/math/Math.sol)
pragma solidity ^0.8.0;
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
enum Rounding {
Down, // Toward negative infinity
Up, // Toward infinity
Zero // Toward zero
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds up instead
* of rounding down.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or denominator == 0
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv)
* with further edits by Uniswap Labs also under MIT license.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator
) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod0 := mul(x, y)
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
require(denominator > prod1);
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator. Always >= 1.
// See https://cs.stackexchange.com/q/138556/92363.
// Does not overflow because the denominator cannot be zero at this stage in the function.
uint256 twos = denominator & (~denominator + 1);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also works
// in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(
uint256 x,
uint256 y,
uint256 denominator,
Rounding rounding
) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (rounding == Rounding.Up && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded down.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (rounding == Rounding.Up && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (rounding == Rounding.Up && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10, rounded down, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10**64) {
value /= 10**64;
result += 64;
}
if (value >= 10**32) {
value /= 10**32;
result += 32;
}
if (value >= 10**16) {
value /= 10**16;
result += 16;
}
if (value >= 10**8) {
value /= 10**8;
result += 8;
}
if (value >= 10**4) {
value /= 10**4;
result += 4;
}
if (value >= 10**2) {
value /= 10**2;
result += 2;
}
if (value >= 10**1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (rounding == Rounding.Up && 10**result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256, rounded down, of a positive value.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (rounding == Rounding.Up && 1 << (result * 8) < value ? 1 : 0);
}
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.8.0) (utils/Strings.sol)
pragma solidity ^0.8.0;
import "./math/Math.sol";
/**
* @dev String operations.
*/
library Strings {
bytes16 private constant _SYMBOLS = "0123456789abcdef";
uint8 private constant _ADDRESS_LENGTH = 20;
/**
* @dev Converts a `uint256` to its ASCII `string` decimal representation.
*/
function toString(uint256 value) internal pure returns (string memory) {
unchecked {
uint256 length = Math.log10(value) + 1;
string memory buffer = new string(length);
uint256 ptr;
/// @solidity memory-safe-assembly
assembly {
ptr := add(buffer, add(32, length))
}
while (true) {
ptr--;
/// @solidity memory-safe-assembly
assembly {
mstore8(ptr, byte(mod(value, 10), _SYMBOLS))
}
value /= 10;
if (value == 0) break;
}
return buffer;
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation.
*/
function toHexString(uint256 value) internal pure returns (string memory) {
unchecked {
return toHexString(value, Math.log256(value) + 1);
}
}
/**
* @dev Converts a `uint256` to its ASCII `string` hexadecimal representation with fixed length.
*/
function toHexString(uint256 value, uint256 length) internal pure returns (string memory) {
bytes memory buffer = new bytes(2 * length + 2);
buffer[0] = "0";
buffer[1] = "x";
for (uint256 i = 2 * length + 1; i > 1; --i) {
buffer[i] = _SYMBOLS[value & 0xf];
value >>= 4;
}
require(value == 0, "Strings: hex length insufficient");
return string(buffer);
}
/**
* @dev Converts an `address` with fixed length of 20 bytes to its not checksummed ASCII `string` hexadecimal representation.
*/
function toHexString(address addr) internal pure returns (string memory) {
return toHexString(uint256(uint160(addr)), _ADDRESS_LENGTH);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {TransferInfo, AppStorage, Role} from "../libraries/LibConnextStorage.sol";
import {LibDiamond} from "../libraries/LibDiamond.sol";
import {AssetLogic} from "../libraries/AssetLogic.sol";
import {TokenId} from "../libraries/TokenId.sol";
import {Constants} from "../libraries/Constants.sol";
contract BaseConnextFacet {
AppStorage internal s;
// ========== Custom Errors ===========
error BaseConnextFacet__onlyOwner_notOwner();
error BaseConnextFacet__onlyProposed_notProposedOwner();
error BaseConnextFacet__onlyOwnerOrRouter_notOwnerOrRouter();
error BaseConnextFacet__onlyOwnerOrWatcher_notOwnerOrWatcher();
error BaseConnextFacet__onlyOwnerOrAdmin_notOwnerOrAdmin();
error BaseConnextFacet__whenNotPaused_paused();
error BaseConnextFacet__nonReentrant_reentrantCall();
error BaseConnextFacet__nonXCallReentrant_reentrantCall();
error BaseConnextFacet__getAdoptedAsset_assetNotFound();
error BaseConnextFacet__getApprovedCanonicalId_notAllowlisted();
// ============ Modifiers ============
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
// On the first call to nonReentrant, _notEntered will be true
if (s._status == Constants.ENTERED) revert BaseConnextFacet__nonReentrant_reentrantCall();
// Any calls to nonReentrant after this point will fail
s._status = Constants.ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
s._status = Constants.NOT_ENTERED;
}
modifier nonXCallReentrant() {
// On the first call to nonReentrant, _notEntered will be true
if (s._xcallStatus == Constants.ENTERED) revert BaseConnextFacet__nonXCallReentrant_reentrantCall();
// Any calls to nonReentrant after this point will fail
s._xcallStatus = Constants.ENTERED;
_;
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
s._xcallStatus = Constants.NOT_ENTERED;
}
/**
* @notice Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
if (LibDiamond.contractOwner() != msg.sender) revert BaseConnextFacet__onlyOwner_notOwner();
_;
}
/**
* @notice Throws if called by any account other than the proposed owner.
*/
modifier onlyProposed() {
if (s._proposed != msg.sender) revert BaseConnextFacet__onlyProposed_notProposedOwner();
_;
}
/**
* @notice Throws if called by any account other than the owner and router role.
*/
modifier onlyOwnerOrRouter() {
if (LibDiamond.contractOwner() != msg.sender && s.roles[msg.sender] != Role.RouterAdmin)
revert BaseConnextFacet__onlyOwnerOrRouter_notOwnerOrRouter();
_;
}
/**
* @notice Throws if called by any account other than the owner and watcher role.
*/
modifier onlyOwnerOrWatcher() {
if (LibDiamond.contractOwner() != msg.sender && s.roles[msg.sender] != Role.Watcher)
revert BaseConnextFacet__onlyOwnerOrWatcher_notOwnerOrWatcher();
_;
}
/**
* @notice Throws if called by any account other than the owner and admin role.
*/
modifier onlyOwnerOrAdmin() {
if (LibDiamond.contractOwner() != msg.sender && s.roles[msg.sender] != Role.Admin)
revert BaseConnextFacet__onlyOwnerOrAdmin_notOwnerOrAdmin();
_;
}
/**
* @notice Throws if all functionality is paused
*/
modifier whenNotPaused() {
if (s._paused) revert BaseConnextFacet__whenNotPaused_paused();
_;
}
// ============ Internal functions ============
/**
* @notice Indicates if the router allowlist has been removed
*/
function _isRouterAllowlistRemoved() internal view returns (bool) {
return LibDiamond.contractOwner() == address(0) || s._routerAllowlistRemoved;
}
/**
* @notice Returns the adopted assets for given canonical information
*/
function _getAdoptedAsset(bytes32 _key) internal view returns (address) {
address adopted = AssetLogic.getConfig(_key).adopted;
if (adopted == address(0)) {
revert BaseConnextFacet__getAdoptedAsset_assetNotFound();
}
return adopted;
}
/**
* @notice Returns the adopted assets for given canonical information
*/
function _getRepresentationAsset(bytes32 _key) internal view returns (address) {
address representation = AssetLogic.getConfig(_key).representation;
// If this is address(0), then there is no mintable token for this asset on this
// domain
return representation;
}
/**
* @notice Calculates a transferId
*/
function _calculateTransferId(TransferInfo memory _params) internal pure returns (bytes32) {
return keccak256(abi.encode(_params));
}
/**
* @notice Internal utility function that combines
* `_origin` and `_nonce`.
* @dev Both origin and nonce should be less than 2^32 - 1
* @param _origin Domain of chain where the transfer originated
* @param _nonce The unique identifier for the message from origin to destination
* @return Returns (`_origin` << 32) & `_nonce`
*/
function _originAndNonce(uint32 _origin, uint32 _nonce) internal pure returns (uint64) {
return (uint64(_origin) << 32) | _nonce;
}
function _getLocalAsset(
bytes32 _key,
bytes32 _id,
uint32 _domain
) internal view returns (address) {
return AssetLogic.getLocalAsset(_key, _id, _domain, s);
}
function _getCanonicalTokenId(address _candidate) internal view returns (TokenId memory) {
return AssetLogic.getCanonicalTokenId(_candidate, s);
}
function _getLocalAndAdoptedToken(
bytes32 _key,
bytes32 _id,
uint32 _domain
) internal view returns (address, address) {
address _local = AssetLogic.getLocalAsset(_key, _id, _domain, s);
address _adopted = _getAdoptedAsset(_key);
return (_local, _adopted);
}
function _isLocalOrigin(address _token) internal view returns (bool) {
return AssetLogic.isLocalOrigin(_token, s);
}
function _getApprovedCanonicalId(address _candidate) internal view returns (TokenId memory, bytes32) {
TokenId memory _canonical = _getCanonicalTokenId(_candidate);
bytes32 _key = AssetLogic.calculateCanonicalHash(_canonical.id, _canonical.domain);
if (!AssetLogic.getConfig(_key).approval) {
revert BaseConnextFacet__getApprovedCanonicalId_notAllowlisted();
}
return (_canonical, _key);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ECDSA} from "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
import {Address} from "@openzeppelin/contracts/utils/Address.sol";
import {ExcessivelySafeCall} from "../../../shared/libraries/ExcessivelySafeCall.sol";
import {TypedMemView} from "../../../shared/libraries/TypedMemView.sol";
import {TypeCasts} from "../../../shared/libraries/TypeCasts.sol";
import {IOutbox} from "../../../messaging/interfaces/IOutbox.sol";
import {IConnectorManager} from "../../../messaging/interfaces/IConnectorManager.sol";
import {BaseConnextFacet} from "./BaseConnextFacet.sol";
import {AssetLogic} from "../libraries/AssetLogic.sol";
import {ExecuteArgs, TransferInfo, DestinationTransferStatus, TokenConfig} from "../libraries/LibConnextStorage.sol";
import {BridgeMessage} from "../libraries/BridgeMessage.sol";
import {Constants} from "../libraries/Constants.sol";
import {TokenId} from "../libraries/TokenId.sol";
import {IXReceiver} from "../interfaces/IXReceiver.sol";
import {IAavePool} from "../interfaces/IAavePool.sol";
import {IBridgeToken} from "../interfaces/IBridgeToken.sol";
/**
* @notice Defines the fields needed for an asset transfer
* @param asset - The address of the asset
* @param amount - The amount of the asset
*/
struct AssetTransfer {
address asset;
uint256 amount;
}
contract BridgeFacet is BaseConnextFacet {
// ============ Libraries ============
using TypedMemView for bytes;
using TypedMemView for bytes29;
using BridgeMessage for bytes29;
using SafeERC20 for IERC20Metadata;
// ========== Custom Errors ===========
error BridgeFacet__addRemote_invalidRouter();
error BridgeFacet__addRemote_invalidDomain();
error BridgeFacet__onlyDelegate_notDelegate();
error BridgeFacet__addSequencer_invalidSequencer();
error BridgeFacet__addSequencer_alreadyApproved();
error BridgeFacet__removeSequencer_notApproved();
error BridgeFacet__setXAppConnectionManager_domainsDontMatch();
error BridgeFacet__xcall_nativeAssetNotSupported();
error BridgeFacet__xcall_emptyTo();
error BridgeFacet__xcall_invalidSlippage();
error BridgeFacet_xcall__emptyLocalAsset();
error BridgeFacet__xcall_capReached();
error BridgeFacet__execute_unapprovedSender();
error BridgeFacet__execute_wrongDomain();
error BridgeFacet__execute_notSupportedSequencer();
error BridgeFacet__execute_invalidSequencerSignature();
error BridgeFacet__execute_maxRoutersExceeded();
error BridgeFacet__execute_notSupportedRouter();
error BridgeFacet__execute_invalidRouterSignature();
error BridgeFacet__execute_notApprovedForPortals();
error BridgeFacet__execute_badFastLiquidityStatus();
error BridgeFacet__execute_notReconciled();
error BridgeFacet__execute_externalCallFailed();
error BridgeFacet__excecute_insufficientGas();
error BridgeFacet__executePortalTransfer_insufficientAmountWithdrawn();
error BridgeFacet__bumpTransfer_valueIsZero();
error BridgeFacet__bumpTransfer_noRelayerVault();
error BridgeFacet__forceUpdateSlippage_invalidSlippage();
error BridgeFacet__forceUpdateSlippage_notDestination();
error BridgeFacet__forceReceiveLocal_notDestination();
error BridgeFacet__mustHaveRemote_destinationNotSupported();
// ============ Properties ============
// ============ Events ============
/**
* @notice Emitted when `xcall` is called on the origin domain of a transfer.
* @param transferId - The unique identifier of the crosschain transfer.
* @param nonce - The bridge nonce of the transfer on the origin domain.
* @param messageHash - The hash of the message bytes (containing all transfer info) that were bridged.
* @param params - The `TransferInfo` provided to the function.
* @param asset - The asset sent in with xcall
* @param amount - The amount sent in with xcall
* @param local - The local asset that is controlled by the bridge and can be burned/minted
*/
event XCalled(
bytes32 indexed transferId,
uint256 indexed nonce,
bytes32 indexed messageHash,
TransferInfo params,
address asset,
uint256 amount,
address local,
bytes messageBody
);
/**
* @notice Emitted when a transfer has its external data executed
* @param transferId - The unique identifier of the crosschain transfer.
* @param success - Whether calldata succeeded
* @param returnData - Return bytes from the IXReceiver
*/
event ExternalCalldataExecuted(bytes32 indexed transferId, bool success, bytes returnData);
/**
* @notice Emitted when `execute` is called on the destination domain of a transfer.
* @dev `execute` may be called when providing fast liquidity or when processing a reconciled (slow) transfer.
* @param transferId - The unique identifier of the crosschain transfer.
* @param to - The recipient `TransferInfo.to` provided, created as indexed parameter.
* @param asset - The asset the recipient is given or the external call is executed with. Should be the
* adopted asset on that chain.
* @param args - The `ExecuteArgs` provided to the function.
* @param local - The local asset that was either supplied by the router for a fast-liquidity transfer or
* minted by the bridge in a reconciled (slow) transfer. Could be the same as the adopted `asset` param.
* @param amount - The amount of transferring asset the recipient address receives or the external call is
* executed with.
* @param caller - The account that called the function.
*/
event Executed(
bytes32 indexed transferId,
address indexed to,
address indexed asset,
ExecuteArgs args,
address local,
uint256 amount,
address caller
);
/**
* @notice Emitted when `_bumpTransfer` is called by an user on the origin domain both in
* `xcall` and `bumpTransfer`
* @param transferId - The unique identifier of the crosschain transaction
* @param increase - The additional amount fees increased by
* @param asset - The asset the fee was increased with
* @param caller - The account that called the function
*/
event TransferRelayerFeesIncreased(bytes32 indexed transferId, uint256 increase, address asset, address caller);
/**
* @notice Emitted when `forceUpdateSlippage` is called by user-delegated EOA
* on the destination domain
* @param transferId - The unique identifier of the crosschain transaction
* @param slippage - The updated slippage boundary
*/
event SlippageUpdated(bytes32 indexed transferId, uint256 slippage);
/**
* @notice Emitted when `forceReceiveLocal` is called by a user-delegated EOA
* on the destination domain
* @param transferId - The unique identifier of the crosschain transaction
*/
event ForceReceiveLocal(bytes32 indexed transferId);
/**
* @notice Emitted when a router used Aave Portal liquidity for fast transfer
* @param transferId - The unique identifier of the crosschain transaction
* @param router - The authorized router that used Aave Portal liquidity
* @param asset - The asset that was provided by Aave Portal
* @param amount - The amount of asset that was provided by Aave Portal
*/
event AavePortalMintUnbacked(bytes32 indexed transferId, address indexed router, address asset, uint256 amount);
/**
* @notice Emitted when a new remote instance is added
* @param domain - The domain the remote instance is on
* @param remote - The address of the remote instance
* @param caller - The account that called the function
*/
event RemoteAdded(uint32 domain, address remote, address caller);
/**
* @notice Emitted when a sequencer is added or removed from allowlists
* @param sequencer - The sequencer address to be added or removed
* @param caller - The account that called the function
*/
event SequencerAdded(address sequencer, address caller);
/**
* @notice Emitted when a sequencer is added or removed from allowlists
* @param sequencer - The sequencer address to be added or removed
* @param caller - The account that called the function
*/
event SequencerRemoved(address sequencer, address caller);
/**
* @notice Emitted `xAppConnectionManager` is updated
* @param updated - The updated address
* @param caller - The account that called the function
*/
event XAppConnectionManagerSet(address updated, address caller);
// ============ Modifiers ============
/**
* @notice Only accept a transfer's designated delegate.
* @param _params The TransferInfo of the transfer.
*/
modifier onlyDelegate(TransferInfo calldata _params) {
if (_params.delegate != msg.sender) revert BridgeFacet__onlyDelegate_notDelegate();
_;
}
// ============ Getters ============
function routedTransfers(bytes32 _transferId) public view returns (address[] memory) {
return s.routedTransfers[_transferId];
}
function transferStatus(bytes32 _transferId) public view returns (DestinationTransferStatus) {
return s.transferStatus[_transferId];
}
function remote(uint32 _domain) public view returns (address) {
return TypeCasts.bytes32ToAddress(s.remotes[_domain]);
}
function domain() public view returns (uint32) {
return s.domain;
}
function nonce() public view returns (uint256) {
return s.nonce;
}
function approvedSequencers(address _sequencer) external view returns (bool) {
return s.approvedSequencers[_sequencer];
}
function xAppConnectionManager() public view returns (address) {
return address(s.xAppConnectionManager);
}
// ============ Admin Functions ==============
/**
* @notice Used to add an approved sequencer to the allowlist.
* @param _sequencer - The sequencer address to add.
*/
function addSequencer(address _sequencer) external onlyOwnerOrAdmin {
if (_sequencer == address(0)) revert BridgeFacet__addSequencer_invalidSequencer();
if (s.approvedSequencers[_sequencer]) revert BridgeFacet__addSequencer_alreadyApproved();
s.approvedSequencers[_sequencer] = true;
emit SequencerAdded(_sequencer, msg.sender);
}
/**
* @notice Used to remove an approved sequencer from the allowlist.
* @param _sequencer - The sequencer address to remove.
*/
function removeSequencer(address _sequencer) external onlyOwnerOrAdmin {
if (!s.approvedSequencers[_sequencer]) revert BridgeFacet__removeSequencer_notApproved();
delete s.approvedSequencers[_sequencer];
emit SequencerRemoved(_sequencer, msg.sender);
}
/**
* @notice Modify the contract the xApp uses to validate Replica contracts
* @param _xAppConnectionManager The address of the xAppConnectionManager contract
*/
function setXAppConnectionManager(address _xAppConnectionManager) external onlyOwnerOrAdmin {
IConnectorManager manager = IConnectorManager(_xAppConnectionManager);
if (manager.localDomain() != s.domain) {
revert BridgeFacet__setXAppConnectionManager_domainsDontMatch();
}
emit XAppConnectionManagerSet(_xAppConnectionManager, msg.sender);
s.xAppConnectionManager = manager;
}
/**
* @notice Register the address of a Router contract for the same xApp on a remote chain
* @param _domain The domain of the remote xApp Router
* @param _router The address of the remote xApp Router
*/
function enrollRemoteRouter(uint32 _domain, bytes32 _router) external onlyOwnerOrAdmin {
if (_router == bytes32("")) revert BridgeFacet__addRemote_invalidRouter();
// Make sure we aren't setting the current domain (or an empty one) as the connextion.
if (_domain == 0 || _domain == s.domain) {
revert BridgeFacet__addRemote_invalidDomain();
}
s.remotes[_domain] = _router;
emit RemoteAdded(_domain, TypeCasts.bytes32ToAddress(_router), msg.sender);
}
// ============ Public Functions: Bridge ==============
function xcall(
uint32 _destination,
address _to,
address _asset,
address _delegate,
uint256 _amount,
uint256 _slippage,
bytes calldata _callData
) external payable nonXCallReentrant returns (bytes32) {
// NOTE: Here, we fill in as much information as we can for the TransferInfo.
// Some info is left blank and will be assigned in the internal `_xcall` function (e.g.
// `normalizedIn`, `bridgedAmt`, canonical info, etc).
TransferInfo memory params = TransferInfo({
to: _to,
callData: _callData,
originDomain: s.domain,
destinationDomain: _destination,
delegate: _delegate,
// `receiveLocal: false` indicates we should always deliver the adopted asset on the
// destination chain, swapping from the local asset if needed.
receiveLocal: false,
slippage: _slippage,
originSender: msg.sender,
// The following values should be assigned in _xcall.
nonce: 0,
canonicalDomain: 0,
bridgedAmt: 0,
normalizedIn: 0,
canonicalId: bytes32(0)
});
return _xcall(params, AssetTransfer(_asset, _amount), AssetTransfer(address(0), msg.value));
}
function xcallIntoLocal(
uint32 _destination,
address _to,
address _asset,
address _delegate,
uint256 _amount,
uint256 _slippage,
bytes calldata _callData
) external payable nonXCallReentrant returns (bytes32) {
// NOTE: Here, we fill in as much information as we can for the TransferInfo.
// Some info is left blank and will be assigned in the internal `_xcall` function (e.g.
// `normalizedIn`, `bridgedAmt`, canonical info, etc).
TransferInfo memory params = TransferInfo({
to: _to,
callData: _callData,
originDomain: s.domain,
destinationDomain: _destination,
delegate: _delegate,
// `receiveLocal: true` indicates we should always deliver the local asset on the
// destination chain, and NOT swap into any adopted assets.
receiveLocal: true,
slippage: _slippage,
originSender: msg.sender,
// The following values should be assigned in _xcall.
nonce: 0,
canonicalDomain: 0,
bridgedAmt: 0,
normalizedIn: 0,
canonicalId: bytes32(0)
});
return _xcall(params, AssetTransfer(_asset, _amount), AssetTransfer(address(0), msg.value));
}
function xcall(
uint32 _destination,
address _to,
address _asset,
address _delegate,
uint256 _amount,
uint256 _slippage,
bytes calldata _callData,
uint256 _relayerFee
) external nonXCallReentrant returns (bytes32) {
// NOTE: Here, we fill in as much information as we can for the TransferInfo.
// Some info is left blank and will be assigned in the internal `_xcall` function (e.g.
// `normalizedIn`, `bridgedAmt`, canonical info, etc).
TransferInfo memory params = TransferInfo({
to: _to,
callData: _callData,
originDomain: s.domain,
destinationDomain: _destination,
delegate: _delegate,
// `receiveLocal: false` indicates we should always deliver the adopted asset on the
// destination chain, swapping from the local asset if needed.
receiveLocal: false,
slippage: _slippage,
originSender: msg.sender,
// The following values should be assigned in _xcall.
nonce: 0,
canonicalDomain: 0,
bridgedAmt: 0,
normalizedIn: 0,
canonicalId: bytes32(0)
});
return _xcall(params, AssetTransfer(_asset, _amount), AssetTransfer(_asset, _relayerFee));
}
function xcallIntoLocal(
uint32 _destination,
address _to,
address _asset,
address _delegate,
uint256 _amount,
uint256 _slippage,
bytes calldata _callData,
uint256 _relayerFee
) external nonXCallReentrant returns (bytes32) {
// NOTE: Here, we fill in as much information as we can for the TransferInfo.
// Some info is left blank and will be assigned in the internal `_xcall` function (e.g.
// `normalizedIn`, `bridgedAmt`, canonical info, etc).
TransferInfo memory params = TransferInfo({
to: _to,
callData: _callData,
originDomain: s.domain,
destinationDomain: _destination,
delegate: _delegate,
// `receiveLocal: true` indicates we should always deliver the local asset on the
// destination chain, and NOT swap into any adopted assets.
receiveLocal: true,
slippage: _slippage,
originSender: msg.sender,
// The following values should be assigned in _xcall.
nonce: 0,
canonicalDomain: 0,
bridgedAmt: 0,
normalizedIn: 0,
canonicalId: bytes32(0)
});
return _xcall(params, AssetTransfer(_asset, _amount), AssetTransfer(_asset, _relayerFee));
}
/**
* @notice Called on a destination domain to disburse correct assets to end recipient and execute any included
* calldata.
*
* @dev Can be called before or after `handle` [reconcile] is called (regarding the same transfer), depending on
* whether the fast liquidity route (i.e. funds provided by routers) is being used for this transfer. As a result,
* executed calldata (including properties like `originSender`) may or may not be verified depending on whether the
* reconcile has been completed (i.e. the optimistic confirmation period has elapsed).
*
* @param _args - ExecuteArgs arguments.
* @return bytes32 - The transfer ID of the crosschain transfer. Should match the xcall's transfer ID in order for
* reconciliation to occur.
*/
function execute(ExecuteArgs calldata _args) external nonReentrant whenNotPaused returns (bytes32) {
(bytes32 transferId, DestinationTransferStatus status) = _executeSanityChecks(_args);
DestinationTransferStatus updated = status == DestinationTransferStatus.Reconciled
? DestinationTransferStatus.Completed
: DestinationTransferStatus.Executed;
s.transferStatus[transferId] = updated;
// Supply assets to target recipient. Use router liquidity when this is a fast transfer, or mint bridge tokens
// when this is a slow transfer.
// NOTE: Asset will be adopted unless specified to `receiveLocal` in params.
(uint256 amountOut, address asset, address local) = _handleExecuteLiquidity(
transferId,
AssetLogic.calculateCanonicalHash(_args.params.canonicalId, _args.params.canonicalDomain),
updated != DestinationTransferStatus.Completed,
_args
);
// Execute the transaction using the designated calldata.
uint256 amount = _handleExecuteTransaction(
_args,
amountOut,
asset,
transferId,
updated == DestinationTransferStatus.Completed
);
// Emit event.
emit Executed(transferId, _args.params.to, asset, _args, local, amount, msg.sender);
return transferId;
}
/**
* @notice Anyone can call this function on the origin domain to increase the relayer fee for a transfer.
* @param _transferId - The unique identifier of the crosschain transaction
*/
function bumpTransfer(bytes32 _transferId) external payable nonReentrant whenNotPaused {
if (msg.value == 0) revert BridgeFacet__bumpTransfer_valueIsZero();
_bumpTransfer(_transferId, address(0), msg.value);
}
/**
* @notice Anyone can call this function on the origin domain t o increase the relayer fee for
* a given transfer using a specific asset.
* @param _transferId - The unique identifier of the crosschain transaction
* @param _relayerFeeAsset - The asset you are bumping fee with
* @param _relayerFee - The amount you want to bump transfer fee with
*/
function bumpTransfer(
bytes32 _transferId,
address _relayerFeeAsset,
uint256 _relayerFee
) external nonReentrant whenNotPaused {
if (_relayerFee == 0) revert BridgeFacet__bumpTransfer_valueIsZero();
// check that the asset is whitelisted (the following reverts if asset
// is not approved)
_getApprovedCanonicalId(_relayerFeeAsset);
// handle transferring asset to the relayer fee vault
_bumpTransfer(_transferId, _relayerFeeAsset, _relayerFee);
}
/**
* @notice Allows a user-specified account to update the slippage they are willing
* to take on destination transfers.
*
* @param _params TransferInfo associated with the transfer
* @param _slippage The updated slippage
*/
function forceUpdateSlippage(TransferInfo calldata _params, uint256 _slippage) external onlyDelegate(_params) {
// Sanity check slippage
if (_slippage > Constants.BPS_FEE_DENOMINATOR) {
revert BridgeFacet__forceUpdateSlippage_invalidSlippage();
}
// Should only be called on destination domain
if (_params.destinationDomain != s.domain) {
revert BridgeFacet__forceUpdateSlippage_notDestination();
}
// Get transferId
bytes32 transferId = _calculateTransferId(_params);
// Store overrides
s.slippage[transferId] = _slippage;
// Emit event
emit SlippageUpdated(transferId, _slippage);
}
/**
* @notice Allows a user-specified account to withdraw the local asset directly
* @dev Calldata will still be executed with the local asset. `IXReceiver` contracts
* should be able to handle local assets in event of failures.
* @param _params TransferInfo associated with the transfer
*/
function forceReceiveLocal(TransferInfo calldata _params) external onlyDelegate(_params) {
// Should only be called on destination domain
if (_params.destinationDomain != s.domain) {
revert BridgeFacet__forceReceiveLocal_notDestination();
}
// Get transferId
bytes32 transferId = _calculateTransferId(_params);
// Store overrides
s.receiveLocalOverride[transferId] = true;
// Emit event
emit ForceReceiveLocal(transferId);
}
// ============ Internal: Bridge ============
/**
* @notice Initiates a cross-chain transfer of funds and/or calldata
*
* @dev For ERC20 transfers, this contract must have approval to transfer the input (transacting) assets. The adopted
* assets will be swapped for their local asset counterparts (i.e. bridgeable tokens) via the configured AMM if
* necessary. In the event that the adopted assets *are* local bridge assets, no swap is needed. The local tokens will
* then be sent via the bridge router. If the local assets are representational for an asset on another chain, we will
* burn the tokens here. If the local assets are canonical (meaning that the adopted<>local asset pairing is native
* to this chain), we will custody the tokens here.
*
* @param _params - The TransferInfo arguments.
* @return bytes32 - The transfer ID of the newly created crosschain transfer.
*/
function _xcall(
TransferInfo memory _params,
AssetTransfer memory _asset,
AssetTransfer memory _relayer
)
internal
// address _asset,
// uint256 _amount,
// address _relayerFeeAsset,
// uint256 _relayerFee
whenNotPaused
returns (bytes32)
{
// Sanity checks.
bytes32 remoteInstance;
{
// Not native asset.
// NOTE: We support using address(0) as an intuitive default if you are sending a 0-value
// transfer. In that edge case, address(0) will not be registered as a supported asset, but should
// pass the `isLocalOrigin` check
if (_asset.asset == address(0) && _asset.amount != 0) {
revert BridgeFacet__xcall_nativeAssetNotSupported();
}
// Destination domain is supported.
// NOTE: This check implicitly also checks that `_params.destinationDomain != s.domain`, because the index
// `s.domain` of `s.remotes` should always be `bytes32(0)`.
remoteInstance = _mustHaveRemote(_params.destinationDomain);
// Recipient defined.
if (_params.to == address(0)) {
revert BridgeFacet__xcall_emptyTo();
}
if (_params.slippage > Constants.BPS_FEE_DENOMINATOR) {
revert BridgeFacet__xcall_invalidSlippage();
}
}
// NOTE: The local asset will stay address(0) if input asset is address(0) in the event of a
// 0-value transfer. Otherwise, the local address will be retrieved below
address local;
bytes32 transferId;
TokenId memory canonical;
bool isCanonical;
{
// Check that the asset is supported -- can be either adopted or local.
// NOTE: Above we check that you can only have `address(0)` as the input asset if this is a
// 0-value transfer. Because 0-value transfers short-circuit all checks on mappings keyed on
// hash(canonicalId, canonicalDomain), this is safe even when the address(0) asset is not
// allowlisted.
if (_asset.asset != address(0)) {
// Retrieve the canonical token information.
bytes32 key;
(canonical, key) = _getApprovedCanonicalId(_asset.asset);
// Get the token config.
TokenConfig storage config = AssetLogic.getConfig(key);
// Set boolean flag
isCanonical = _params.originDomain == canonical.domain;
// Get the local address
local = isCanonical ? TypeCasts.bytes32ToAddress(canonical.id) : config.representation;
if (local == address(0)) {
revert BridgeFacet_xcall__emptyLocalAsset();
}
{
// Enforce liquidity caps.
// NOTE: Safe to do this before the swap because canonical domains do
// not hit the AMMs (local == canonical).
uint256 cap = config.cap;
if (isCanonical && cap > 0) {
// NOTE: this method includes router liquidity as part of the caps,
// not only the minted amount
uint256 newCustodiedAmount = config.custodied + _asset.amount;
if (newCustodiedAmount > cap) {
revert BridgeFacet__xcall_capReached();
}
s.tokenConfigs[key].custodied = newCustodiedAmount;
}
}
// Update TransferInfo to reflect the canonical token information.
_params.canonicalDomain = canonical.domain;
_params.canonicalId = canonical.id;
if (_asset.amount > 0) {
// Transfer funds of input asset to the contract from the user.
AssetLogic.handleIncomingAsset(_asset.asset, _asset.amount);
// Swap to the local asset from adopted if applicable.
_params.bridgedAmt = AssetLogic.swapToLocalAssetIfNeeded(
key,
_asset.asset,
local,
_asset.amount,
_params.slippage
);
// Get the normalized amount in (amount sent in by user in 18 decimals).
// NOTE: when getting the decimals from `_asset`, you don't know if you are looking for
// adopted or local assets
_params.normalizedIn = AssetLogic.normalizeDecimals(
_asset.asset == local ? config.representationDecimals : config.adoptedDecimals,
Constants.DEFAULT_NORMALIZED_DECIMALS,
_asset.amount
);
}
}
// Calculate the transfer ID.
_params.nonce = s.nonce++;
transferId = _calculateTransferId(_params);
}
// Handle the relayer fee.
// NOTE: This has to be done *after* transferring in + swapping assets because
// the transfer id uses the amount that is bridged (i.e. amount in local asset).
if (_relayer.amount > 0) {
_bumpTransfer(transferId, _relayer.asset, _relayer.amount);
}
// Send the crosschain message.
_sendMessageAndEmit(
transferId,
_params,
_asset.asset,
_asset.amount,
remoteInstance,
canonical,
local,
isCanonical
);
return transferId;
}
/**
* @notice An internal function to handle the bumping of transfers
* @param _transferId - The unique identifier of the crosschain transaction
* @param _relayerFeeAsset - The asset you are bumping fee with
* @param _relayerFee - The amount you want to bump transfer fee with
*/
function _bumpTransfer(bytes32 _transferId, address _relayerFeeAsset, uint256 _relayerFee) internal {
address relayerVault = s.relayerFeeVault;
if (relayerVault == address(0)) revert BridgeFacet__bumpTransfer_noRelayerVault();
if (_relayerFeeAsset == address(0)) {
Address.sendValue(payable(relayerVault), _relayerFee);
} else {
// Pull funds from user to this contract
// NOTE: could transfer to `relayerFeeVault`, but that would be unintuitive for user
// approvals
AssetLogic.handleIncomingAsset(_relayerFeeAsset, _relayerFee);
// Transfer asset to relayerVault.
AssetLogic.handleOutgoingAsset(_relayerFeeAsset, relayerVault, _relayerFee);
}
emit TransferRelayerFeesIncreased(_transferId, _relayerFee, _relayerFeeAsset, msg.sender);
}
/**
* @notice Holds the logic to recover the signer from an encoded payload.
* @dev Will hash and convert to an eth signed message.
* @param _signed The hash that was signed.
* @param _sig The signature from which we will recover the signer.
*/
function _recoverSignature(bytes32 _signed, bytes calldata _sig) internal pure returns (address) {
// Recover
return ECDSA.recover(ECDSA.toEthSignedMessageHash(_signed), _sig);
}
/**
* @notice Performs some sanity checks for `execute`.
* @dev Need this to prevent stack too deep.
* @param _args ExecuteArgs that were passed in to the `execute` call.
*/
function _executeSanityChecks(ExecuteArgs calldata _args) private view returns (bytes32, DestinationTransferStatus) {
// If the sender is not approved relayer, revert
if (!s.approvedRelayers[msg.sender] && msg.sender != _args.params.delegate) {
revert BridgeFacet__execute_unapprovedSender();
}
// If this is not the destination domain revert
if (_args.params.destinationDomain != s.domain) {
revert BridgeFacet__execute_wrongDomain();
}
// Path length refers to the number of facilitating routers. A transfer is considered 'multipath'
// if multiple routers provide liquidity (in even 'shares') for it.
uint256 pathLength = _args.routers.length;
// Derive transfer ID based on given arguments.
bytes32 transferId = _calculateTransferId(_args.params);
// Retrieve the reconciled record.
DestinationTransferStatus status = s.transferStatus[transferId];
if (pathLength != 0) {
// Make sure number of routers is below the configured maximum.
if (pathLength > s.maxRoutersPerTransfer) revert BridgeFacet__execute_maxRoutersExceeded();
// Check to make sure the transfer has not been reconciled (no need for routers if the transfer is
// already reconciled; i.e. if there are routers provided, the transfer must *not* be reconciled).
if (status != DestinationTransferStatus.None) revert BridgeFacet__execute_badFastLiquidityStatus();
// NOTE: The sequencer address may be empty and no signature needs to be provided in the case of the
// slow liquidity route (i.e. no routers involved). Additionally, the sequencer does not need to be the
// msg.sender.
// Check to make sure the sequencer address provided is approved
if (!s.approvedSequencers[_args.sequencer]) {
revert BridgeFacet__execute_notSupportedSequencer();
}
// Check to make sure the sequencer provided did sign the transfer ID and router path provided.
// NOTE: when caps are enforced, this signature also acts as protection from malicious routers looking
// to block the network. routers could `execute` a fake transaction, and use up the rest of the `custodied`
// bandwidth, causing future `execute`s to fail. this would also cause a break in the accounting, where the
// `custodied` balance no longer tracks representation asset minting / burning
if (
_args.sequencer != _recoverSignature(keccak256(abi.encode(transferId, _args.routers)), _args.sequencerSignature)
) {
revert BridgeFacet__execute_invalidSequencerSignature();
}
// Hash the payload for which each router should have produced a signature.
// Each router should have signed the `transferId` (which implicitly signs call params,
// amount, and tokenId) as well as the `pathLength`, or the number of routers with which
// they are splitting liquidity provision.
bytes32 routerHash = keccak256(abi.encode(transferId, pathLength));
for (uint256 i; i < pathLength; ) {
// Make sure the router is approved, if applicable.
// If router ownership is renounced (_RouterOwnershipRenounced() is true), then the router allowlist
// no longer applies and we can skip this approval step.
if (!_isRouterAllowlistRemoved() && !s.routerConfigs[_args.routers[i]].approved) {
revert BridgeFacet__execute_notSupportedRouter();
}
// Validate the signature. We'll recover the signer's address using the expected payload and basic ECDSA
// signature scheme recovery. The address for each signature must match the router's address.
if (_args.routers[i] != _recoverSignature(routerHash, _args.routerSignatures[i])) {
revert BridgeFacet__execute_invalidRouterSignature();
}
unchecked {
++i;
}
}
} else {
// If there are no routers for this transfer, this `execute` must be a slow liquidity route; in which
// case, we must make sure the transfer's been reconciled.
if (status != DestinationTransferStatus.Reconciled) revert BridgeFacet__execute_notReconciled();
}
return (transferId, status);
}
/**
* @notice Calculates fast transfer amount.
* @param _amount Transfer amount
* @param _numerator Numerator
* @param _denominator Denominator
*/
function _muldiv(uint256 _amount, uint256 _numerator, uint256 _denominator) private pure returns (uint256) {
return (_amount * _numerator) / _denominator;
}
/**
* @notice Execute liquidity process used when calling `execute`.
* @dev Will revert with underflow if any router in the path has insufficient liquidity to provide
* for the transfer.
* @dev Need this to prevent stack too deep.
*/
function _handleExecuteLiquidity(
bytes32 _transferId,
bytes32 _key,
bool _isFast,
ExecuteArgs calldata _args
) private returns (uint256, address, address) {
// Save the addresses of all routers providing liquidity for this transfer.
s.routedTransfers[_transferId] = _args.routers;
// Get the local asset contract address (if applicable).
address local;
if (_args.params.canonicalDomain != 0) {
local = _getLocalAsset(_key, _args.params.canonicalId, _args.params.canonicalDomain);
}
// If this is a zero-value transfer, short-circuit remaining logic.
if (_args.params.bridgedAmt == 0) {
return (0, local, local);
}
// Get the receive local status
bool receiveLocal = _args.params.receiveLocal || s.receiveLocalOverride[_transferId];
uint256 toSwap = _args.params.bridgedAmt;
// If this is a fast liquidity path, we should handle deducting from applicable routers' liquidity.
// If this is a slow liquidity path, the transfer must have been reconciled (if we've reached this point),
// and the funds would have been custodied in this contract. The exact custodied amount is untracked in state
// (since the amount is hashed in the transfer ID itself) - thus, no updates are required.
if (_isFast) {
uint256 pathLen = _args.routers.length;
// Calculate amount that routers will provide with the fast-liquidity fee deducted.
toSwap = _muldiv(_args.params.bridgedAmt, s.LIQUIDITY_FEE_NUMERATOR, Constants.BPS_FEE_DENOMINATOR);
if (pathLen == 1) {
// If router does not have enough liquidity, try to use Aave Portals.
// NOTE: Only one router should be responsible for taking on this credit risk, and it should only deal
// with transfers expecting adopted assets (to avoid introducing runtime slippage).
if (!receiveLocal && s.routerBalances[_args.routers[0]][local] < toSwap && s.aavePool != address(0)) {
if (!s.routerConfigs[_args.routers[0]].portalApproved) revert BridgeFacet__execute_notApprovedForPortals();
// Portals deliver the adopted asset directly; return after portal execution is completed.
(uint256 portalDeliveredAmount, address adoptedAsset) = _executePortalTransfer(
_transferId,
_key,
toSwap,
_args.routers[0]
);
return (portalDeliveredAmount, adoptedAsset, local);
} else {
// Decrement the router's liquidity.
s.routerBalances[_args.routers[0]][local] -= toSwap;
}
} else {
// For each router, assert they are approved, and deduct liquidity.
uint256 routerAmount = toSwap / pathLen;
for (uint256 i; i < pathLen - 1; ) {
// Decrement router's liquidity.
// NOTE: If any router in the path has insufficient liquidity, this will revert with an underflow error.
s.routerBalances[_args.routers[i]][local] -= routerAmount;
unchecked {
++i;
}
}
// The last router in the multipath will sweep the remaining balance to account for remainder dust.
uint256 toSweep = routerAmount + (toSwap % pathLen);
s.routerBalances[_args.routers[pathLen - 1]][local] -= toSweep;
}
}
// If it is the canonical domain, decrease custodied value
if (s.domain == _args.params.canonicalDomain && AssetLogic.getConfig(_key).cap > 0) {
// NOTE: safe to use the amount here instead of post-swap because there are no
// AMMs on the canonical domain (assuming canonical == adopted on canonical domain)
s.tokenConfigs[_key].custodied -= toSwap;
}
// If the local asset is specified, or the adopted asset was overridden (e.g. when user facing slippage
// conditions outside of their boundaries), exit without swapping.
if (receiveLocal) {
// Delete override
delete s.receiveLocalOverride[_transferId];
return (toSwap, local, local);
}
// Swap out of representational asset into adopted asset if needed.
uint256 slippageOverride = s.slippage[_transferId];
// delete for gas refund
delete s.slippage[_transferId];
(uint256 amount, address adopted) = AssetLogic.swapFromLocalAssetIfNeeded(
_key,
local,
toSwap,
slippageOverride != 0 ? slippageOverride : _args.params.slippage,
_args.params.normalizedIn
);
return (amount, adopted, local);
}
/**
* @notice Process the transfer, and calldata if needed, when calling `execute`
* @dev Need this to prevent stack too deep
*/
function _handleExecuteTransaction(
ExecuteArgs calldata _args,
uint256 _amountOut,
address _asset, // adopted (or local if specified)
bytes32 _transferId,
bool _reconciled
) private returns (uint256) {
// transfer funds to recipient
AssetLogic.handleOutgoingAsset(_asset, _args.params.to, _amountOut);
// execute the calldata
_executeCalldata(_transferId, _amountOut, _asset, _reconciled, _args.params);
return _amountOut;
}
/**
* @notice Executes external calldata.
*
* @dev Once a transfer is reconciled (i.e. data is authenticated), external calls will
* fail gracefully. This means errors will be emitted in an event, but the function itself
* will not revert.
* In the case where a transaction is *not* reconciled (i.e. data is unauthenticated), this
* external call will fail loudly. This allows all functions that rely on authenticated data
* (using a specific check on the origin sender), to be forced into the slow path for
* execution to succeed.
*
*/
function _executeCalldata(
bytes32 _transferId,
uint256 _amount,
address _asset,
bool _reconciled,
TransferInfo calldata _params
) internal {
// execute the calldata
if (keccak256(_params.callData) == Constants.EMPTY_HASH) {
// no call data, return amount out
return;
}
(bool success, bytes memory returnData) = ExcessivelySafeCall.excessivelySafeCall(
_params.to,
gasleft() - Constants.EXECUTE_CALLDATA_RESERVE_GAS,
0, // native asset value (always 0)
Constants.DEFAULT_COPY_BYTES, // only copy 256 bytes back as calldata
abi.encodeWithSelector(
IXReceiver.xReceive.selector,
_transferId,
_amount,
_asset,
_reconciled ? _params.originSender : address(0), // use passed in value iff authenticated
_params.originDomain,
_params.callData
)
);
if (!_reconciled && !success) {
// See above devnote, reverts if unsuccessful on fast path
revert BridgeFacet__execute_externalCallFailed();
}
emit ExternalCalldataExecuted(_transferId, success, returnData);
}
/**
* @notice Uses Aave Portals to provide fast liquidity
*/
function _executePortalTransfer(
bytes32 _transferId,
bytes32 _key,
uint256 _fastTransferAmount,
address _router
) internal returns (uint256, address) {
// Calculate local to adopted swap output if needed
address adopted = _getAdoptedAsset(_key);
IAavePool(s.aavePool).mintUnbacked(adopted, _fastTransferAmount, address(this), Constants.AAVE_REFERRAL_CODE);
// Improvement: Instead of withdrawing to address(this), withdraw directly to the user or executor to save 1 transfer
uint256 amountWithdrawn = IAavePool(s.aavePool).withdraw(adopted, _fastTransferAmount, address(this));
if (amountWithdrawn < _fastTransferAmount) revert BridgeFacet__executePortalTransfer_insufficientAmountWithdrawn();
// Store principle debt
s.portalDebt[_transferId] = _fastTransferAmount;
// Store fee debt
s.portalFeeDebt[_transferId] = (s.aavePortalFeeNumerator * _fastTransferAmount) / Constants.BPS_FEE_DENOMINATOR;
emit AavePortalMintUnbacked(_transferId, _router, adopted, _fastTransferAmount);
return (_fastTransferAmount, adopted);
}
// ============ Internal: Send & Emit Xcalled============
/**
* @notice Format and send transfer message to a remote chain.
*
* @param _transferId Unique identifier for the transfer.
* @param _params The TransferInfo.
* @param _connextion The connext instance on the destination domain.
* @param _canonical The canonical token ID/domain info.
* @param _local The local token address.
* @param _amount The token amount.
* @param _isCanonical Whether or not the local token is the canonical asset (i.e. this is the token's
* "home" chain).
*/
function _sendMessageAndEmit(
bytes32 _transferId,
TransferInfo memory _params,
address _asset,
uint256 _amount,
bytes32 _connextion,
TokenId memory _canonical,
address _local,
bool _isCanonical
) private {
// Remove tokens from circulation on this chain if applicable.
uint256 bridgedAmt = _params.bridgedAmt;
if (bridgedAmt > 0) {
if (!_isCanonical) {
// If the token originates on a remote chain, burn the representational tokens on this chain.
IBridgeToken(_local).burn(address(this), bridgedAmt);
}
// IFF the token IS the canonical token (i.e. originates on this chain), we lock the input tokens in escrow
// in this contract, as an equal amount of representational assets will be minted on the destination chain.
// NOTE: The tokens should be in the contract already at this point from xcall.
}
bytes memory _messageBody = abi.encodePacked(
_canonical.domain,
_canonical.id,
BridgeMessage.Types.Transfer,
bridgedAmt,
_transferId
);
// Send message to destination chain bridge router.
// return message hash and unhashed body
(bytes32 messageHash, bytes memory messageBody) = IOutbox(s.xAppConnectionManager.home()).dispatch(
_params.destinationDomain,
_connextion,
_messageBody
);
// emit event
emit XCalled(_transferId, _params.nonce, messageHash, _params, _asset, _amount, _local, messageBody);
}
/**
* @notice Assert that the given domain has a xApp Router registered and return its address
* @param _domain The domain of the chain for which to get the xApp Router
* @return _remote The address of the remote xApp Router on _domain
*/
function _mustHaveRemote(uint32 _domain) internal view returns (bytes32 _remote) {
_remote = s.remotes[_domain];
if (_remote == bytes32(0)) {
revert BridgeFacet__mustHaveRemote_destinationNotSupported();
}
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20BurnableUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
/**
* @title Liquidity Provider Token
* @notice This token is an ERC20 detailed token with added capability to be minted by the owner.
* It is used to represent user's shares when providing liquidity to swap contracts.
* @dev Only Swap contracts should initialize and own LPToken contracts.
*/
contract LPToken is ERC20Upgradeable, OwnableUpgradeable {
// ============ Storage ============
// ============ Initializer ============
/**
* @notice Initializes this LPToken contract with the given name and symbol
* @dev The caller of this function will become the owner. A Swap contract should call this
* in its initializer function.
* @param name name of this token
* @param symbol symbol of this token
*/
function initialize(string memory name, string memory symbol) external initializer returns (bool) {
__Context_init_unchained();
__ERC20_init_unchained(name, symbol);
__Ownable_init_unchained();
return true;
}
// ============ External functions ============
/**
* @notice Mints the given amount of LPToken to the recipient.
* @dev only owner can call this mint function
* @param recipient address of account to receive the tokens
* @param amount amount of tokens to mint
*/
function mint(address recipient, uint256 amount) external onlyOwner {
require(amount != 0, "LPToken: cannot mint 0");
_mint(recipient, amount);
}
/**
* @notice Burns the given amount of LPToken from provided account
* @dev only owner can call this burn function
* @param account address of account from which to burn token
* @param amount amount of tokens to mint
*/
function burnFrom(address account, uint256 amount) external onlyOwner {
require(amount != 0, "LPToken: cannot burn 0");
_burn(account, amount);
}
// ============ Internal functions ============
/**
* @dev Overrides ERC20._beforeTokenTransfer() which get called on every transfers including
* minting and burning. This ensures that Swap.updateUserWithdrawFees are called everytime.
* This assumes the owner is set to a Swap contract's address.
*/
function _beforeTokenTransfer(
address from,
address to,
uint256 amount
) internal virtual override(ERC20Upgradeable) {
super._beforeTokenTransfer(from, to, amount);
require(to != address(this), "LPToken: cannot send to itself");
}
// ============ Upgrade Gap ============
uint256[50] private __GAP; // gap for upgrade safety
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
interface IAavePool {
/**
* @dev Mints an `amount` of aTokens to the `onBehalfOf`
* @param asset The address of the underlying asset to mint
* @param amount The amount to mint
* @param onBehalfOf The address that will receive the aTokens
* @param referralCode Code used to register the integrator originating the operation, for potential rewards.
* 0 if the action is executed directly by the user, without any middle-man
**/
function mintUnbacked(
address asset,
uint256 amount,
address onBehalfOf,
uint16 referralCode
) external;
/**
* @dev Back the current unbacked underlying with `amount` and pay `fee`.
* @param asset The address of the underlying asset to back
* @param amount The amount to back
* @param fee The amount paid in fees
**/
function backUnbacked(
address asset,
uint256 amount,
uint256 fee
) external;
/**
* @notice Withdraws an `amount` of underlying asset from the reserve, burning the equivalent aTokens owned
* E.g. User has 100 aUSDC, calls withdraw() and receives 100 USDC, burning the 100 aUSDC
* @param asset The address of the underlying asset to withdraw
* @param amount The underlying amount to be withdrawn
* - Send the value type(uint256).max in order to withdraw the whole aToken balance
* @param to The address that will receive the underlying, same as msg.sender if the user
* wants to receive it on his own wallet, or a different address if the beneficiary is a
* different wallet
* @return The final amount withdrawn
**/
function withdraw(
address asset,
uint256 amount,
address to
) external returns (uint256);
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
interface IBridgeToken is IERC20Metadata {
function burn(address _from, uint256 _amnt) external;
function mint(address _to, uint256 _amnt) external;
function setDetails(string calldata _name, string calldata _symbol) external;
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
/******************************************************************************\\
* Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen)
* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
/******************************************************************************/
interface IDiamondCut {
enum FacetCutAction {
Add,
Replace,
Remove
}
// Add=0, Replace=1, Remove=2
struct FacetCut {
address facetAddress;
FacetCutAction action;
bytes4[] functionSelectors;
}
/// @notice Propose to add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function proposeDiamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
event DiamondCutProposed(FacetCut[] _diamondCut, address _init, bytes _calldata, uint256 deadline);
/// @notice Add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function diamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
event DiamondCut(FacetCut[] _diamondCut, address _init, bytes _calldata);
/// @notice Propose to add/replace/remove any number of functions and optionally execute
/// a function with delegatecall
/// @param _diamondCut Contains the facet addresses and function selectors
/// @param _init The address of the contract or facet to execute _calldata
/// @param _calldata A function call, including function selector and arguments
/// _calldata is executed with delegatecall on _init
function rescindDiamondCut(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external;
/**
* @notice Returns the acceptance time for a given proposal
* @param _diamondCut Contains the facet addresses and function selectors
* @param _init The address of the contract or facet to execute _calldata
* @param _calldata A function call, including function selector and arguments _calldata is
* executed with delegatecall on _init
*/
function getAcceptanceTime(
FacetCut[] calldata _diamondCut,
address _init,
bytes calldata _calldata
) external returns (uint256);
event DiamondCutRescinded(FacetCut[] _diamondCut, address _init, bytes _calldata);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
interface IStableSwap {
/*** EVENTS ***/
// events replicated from SwapUtils to make the ABI easier for dumb
// clients
event TokenSwap(address indexed buyer, uint256 tokensSold, uint256 tokensBought, uint128 soldId, uint128 boughtId);
event AddLiquidity(
address indexed provider,
uint256[] tokenAmounts,
uint256[] fees,
uint256 invariant,
uint256 lpTokenSupply
);
event RemoveLiquidity(address indexed provider, uint256[] tokenAmounts, uint256 lpTokenSupply);
event RemoveLiquidityOne(
address indexed provider,
uint256 lpTokenAmount,
uint256 lpTokenSupply,
uint256 boughtId,
uint256 tokensBought
);
event RemoveLiquidityImbalance(
address indexed provider,
uint256[] tokenAmounts,
uint256[] fees,
uint256 invariant,
uint256 lpTokenSupply
);
event NewAdminFee(uint256 newAdminFee);
event NewSwapFee(uint256 newSwapFee);
event NewWithdrawFee(uint256 newWithdrawFee);
event RampA(uint256 oldA, uint256 newA, uint256 initialTime, uint256 futureTime);
event StopRampA(uint256 currentA, uint256 time);
function swap(
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dx,
uint256 minDy,
uint256 deadline
) external returns (uint256);
function swapExact(
uint256 amountIn,
address assetIn,
address assetOut,
uint256 minAmountOut,
uint256 deadline
) external payable returns (uint256);
function swapExactOut(
uint256 amountOut,
address assetIn,
address assetOut,
uint256 maxAmountIn,
uint256 deadline
) external payable returns (uint256);
function getA() external view returns (uint256);
function getToken(uint8 index) external view returns (IERC20);
function getTokenIndex(address tokenAddress) external view returns (uint8);
function getTokenBalance(uint8 index) external view returns (uint256);
function getVirtualPrice() external view returns (uint256);
// min return calculation functions
function calculateSwap(
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dx
) external view returns (uint256);
function calculateSwapOut(
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dy
) external view returns (uint256);
function calculateSwapFromAddress(
address assetIn,
address assetOut,
uint256 amountIn
) external view returns (uint256);
function calculateSwapOutFromAddress(
address assetIn,
address assetOut,
uint256 amountOut
) external view returns (uint256);
function calculateTokenAmount(uint256[] calldata amounts, bool deposit) external view returns (uint256);
function calculateRemoveLiquidity(uint256 amount) external view returns (uint256[] memory);
function calculateRemoveLiquidityOneToken(uint256 tokenAmount, uint8 tokenIndex)
external
view
returns (uint256 availableTokenAmount);
// state modifying functions
function initialize(
IERC20[] memory pooledTokens,
uint8[] memory decimals,
string memory lpTokenName,
string memory lpTokenSymbol,
uint256 a,
uint256 fee,
uint256 adminFee,
address lpTokenTargetAddress
) external;
function addLiquidity(
uint256[] calldata amounts,
uint256 minToMint,
uint256 deadline
) external returns (uint256);
function removeLiquidity(
uint256 amount,
uint256[] calldata minAmounts,
uint256 deadline
) external returns (uint256[] memory);
function removeLiquidityOneToken(
uint256 tokenAmount,
uint8 tokenIndex,
uint256 minAmount,
uint256 deadline
) external returns (uint256);
function removeLiquidityImbalance(
uint256[] calldata amounts,
uint256 maxBurnAmount,
uint256 deadline
) external returns (uint256);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
interface IXReceiver {
function xReceive(
bytes32 _transferId,
uint256 _amount,
address _asset,
address _originSender,
uint32 _origin,
bytes memory _callData
) external returns (bytes memory);
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {SwapUtils} from "./SwapUtils.sol";
import {Constants} from "./Constants.sol";
/**
* @title AmplificationUtils library
* @notice A library to calculate and ramp the A parameter of a given `SwapUtils.Swap` struct.
* This library assumes the struct is fully validated.
*/
library AmplificationUtils {
event RampA(uint256 oldA, uint256 newA, uint256 initialTime, uint256 futureTime);
event StopRampA(uint256 currentA, uint256 time);
/**
* @notice Return A, the amplification coefficient * n ** (n - 1)
* @dev See the StableSwap paper for details
* @param self Swap struct to read from
* @return A parameter
*/
function getA(SwapUtils.Swap storage self) internal view returns (uint256) {
return _getAPrecise(self) / Constants.A_PRECISION;
}
/**
* @notice Return A in its raw precision
* @dev See the StableSwap paper for details
* @param self Swap struct to read from
* @return A parameter in its raw precision form
*/
function getAPrecise(SwapUtils.Swap storage self) internal view returns (uint256) {
return _getAPrecise(self);
}
/**
* @notice Return A in its raw precision
* @dev See the StableSwap paper for details
* @param self Swap struct to read from
* @return currentA A parameter in its raw precision form
*/
function _getAPrecise(SwapUtils.Swap storage self) internal view returns (uint256 currentA) {
uint256 t1 = self.futureATime; // time when ramp is finished
currentA = self.futureA; // final A value when ramp is finished
uint256 a0 = self.initialA; // initial A value when ramp is started
if (a0 != currentA && block.timestamp < t1) {
uint256 t0 = self.initialATime; // time when ramp is started
assembly {
currentA := div(add(mul(a0, sub(t1, timestamp())), mul(currentA, sub(timestamp(), t0))), sub(t1, t0))
}
}
}
/**
* @notice Start ramping up or down A parameter towards given futureA_ and futureTime_
* Checks if the change is too rapid, and commits the new A value only when it falls under
* the limit range.
* @param self Swap struct to update
* @param futureA_ the new A to ramp towards
* @param futureTime_ timestamp when the new A should be reached
*/
function rampA(
SwapUtils.Swap storage self,
uint256 futureA_,
uint256 futureTime_
) internal {
require(block.timestamp >= self.initialATime + Constants.MIN_RAMP_DELAY, "Wait 1 day before starting ramp");
require(futureTime_ >= block.timestamp + Constants.MIN_RAMP_TIME, "Insufficient ramp time");
require(futureA_ != 0 && futureA_ < Constants.MAX_A, "futureA_ must be > 0 and < MAX_A");
uint256 initialAPrecise = _getAPrecise(self);
uint256 futureAPrecise = futureA_ * Constants.A_PRECISION;
require(initialAPrecise != futureAPrecise, "!valid ramp");
if (futureAPrecise < initialAPrecise) {
require(futureAPrecise * Constants.MAX_A_CHANGE >= initialAPrecise, "futureA_ is too small");
} else {
require(futureAPrecise <= initialAPrecise * Constants.MAX_A_CHANGE, "futureA_ is too large");
}
self.initialA = initialAPrecise;
self.futureA = futureAPrecise;
self.initialATime = block.timestamp;
self.futureATime = futureTime_;
emit RampA(initialAPrecise, futureAPrecise, block.timestamp, futureTime_);
}
/**
* @notice Stops ramping A immediately. Once this function is called, rampA()
* cannot be called for another 24 hours
* @param self Swap struct to update
*/
function stopRampA(SwapUtils.Swap storage self) internal {
require(self.futureATime > block.timestamp, "Ramp is already stopped");
uint256 currentA = _getAPrecise(self);
self.initialA = currentA;
self.futureA = currentA;
self.initialATime = block.timestamp;
self.futureATime = block.timestamp;
emit StopRampA(currentA, block.timestamp);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {TypeCasts} from "../../../shared/libraries/TypeCasts.sol";
import {IStableSwap} from "../interfaces/IStableSwap.sol";
import {LibConnextStorage, AppStorage, TokenConfig} from "./LibConnextStorage.sol";
import {SwapUtils} from "./SwapUtils.sol";
import {Constants} from "./Constants.sol";
import {TokenId} from "./TokenId.sol";
library AssetLogic {
// ============ Libraries ============
using SwapUtils for SwapUtils.Swap;
using SafeERC20 for IERC20Metadata;
// ============ Errors ============
error AssetLogic__handleIncomingAsset_nativeAssetNotSupported();
error AssetLogic__handleIncomingAsset_feeOnTransferNotSupported();
error AssetLogic__handleOutgoingAsset_notNative();
error AssetLogic__getTokenIndexFromStableSwapPool_notExist();
error AssetLogic__getConfig_notRegistered();
error AssetLogic__swapAsset_externalStableSwapPoolDoesNotExist();
// ============ Internal: Handle Transfer ============
function getConfig(bytes32 _key) internal view returns (TokenConfig storage) {
AppStorage storage s = LibConnextStorage.connextStorage();
TokenConfig storage config = s.tokenConfigs[_key];
// Sanity check: not empty
// NOTE: adopted decimals will *always* be nonzero (or reflect what is onchain
// for the asset). The same is not true for the representation assets, which
// will always have 0 decimals on the canonical domain
if (config.adoptedDecimals < 1) {
revert AssetLogic__getConfig_notRegistered();
}
return config;
}
/**
* @notice Handles transferring funds from msg.sender to the Connext contract.
* @dev Does NOT work with fee-on-transfer tokens: will revert.
*
* @param _asset - The address of the ERC20 token to transfer.
* @param _amount - The specified amount to transfer.
*/
function handleIncomingAsset(address _asset, uint256 _amount) internal {
// Sanity check: if amount is 0, do nothing.
if (_amount == 0) {
return;
}
// Sanity check: asset address is not zero.
if (_asset == address(0)) {
revert AssetLogic__handleIncomingAsset_nativeAssetNotSupported();
}
IERC20Metadata asset = IERC20Metadata(_asset);
// Record starting amount to validate correct amount is transferred.
uint256 starting = asset.balanceOf(address(this));
// Transfer asset to contract.
asset.safeTransferFrom(msg.sender, address(this), _amount);
// Ensure correct amount was transferred (i.e. this was not a fee-on-transfer token).
if (asset.balanceOf(address(this)) - starting != _amount) {
revert AssetLogic__handleIncomingAsset_feeOnTransferNotSupported();
}
}
/**
* @notice Handles transferring funds from the Connext contract to a specified address
* @param _asset - The address of the ERC20 token to transfer.
* @param _to - The recipient address that will receive the funds.
* @param _amount - The amount to withdraw from contract.
*/
function handleOutgoingAsset(
address _asset,
address _to,
uint256 _amount
) internal {
// Sanity check: if amount is 0, do nothing.
if (_amount == 0) {
return;
}
// Sanity check: asset address is not zero.
if (_asset == address(0)) revert AssetLogic__handleOutgoingAsset_notNative();
// Transfer ERC20 asset to target recipient.
SafeERC20.safeTransfer(IERC20Metadata(_asset), _to, _amount);
}
// ============ Internal: StableSwap Pools ============
/**
* @notice Return the index of the given token address. Reverts if no matching
* token is found.
* @param key the hash of the canonical id and domain
* @param tokenAddress address of the token
* @return the index of the given token address
*/
function getTokenIndexFromStableSwapPool(bytes32 key, address tokenAddress) internal view returns (uint8) {
AppStorage storage s = LibConnextStorage.connextStorage();
uint8 index = s.tokenIndexes[key][tokenAddress];
if (address(s.swapStorages[key].pooledTokens[index]) != tokenAddress)
revert AssetLogic__getTokenIndexFromStableSwapPool_notExist();
return index;
}
// ============ Internal: Handle Swap ============
/**
* @notice Swaps an adopted asset to the local (representation or canonical) asset.
* @dev Will not swap if the asset passed in is the local asset.
* @param _key - The hash of canonical id and domain.
* @param _asset - The address of the adopted asset to swap into the local asset.
* @param _amount - The amount of the adopted asset to swap.
* @param _slippage - The maximum amount of slippage user will take on from _amount in BPS.
* @return uint256 The amount of local asset received from swap.
*/
function swapToLocalAssetIfNeeded(
bytes32 _key,
address _asset,
address _local,
uint256 _amount,
uint256 _slippage
) internal returns (uint256) {
// If there's no amount, no need to swap.
if (_amount == 0) {
return 0;
}
// Check the case where the adopted asset *is* the local asset. If so, no need to swap.
if (_local == _asset) {
return _amount;
}
// Get the configs.
TokenConfig storage config = getConfig(_key);
// Swap the asset to the proper local asset.
(uint256 out, ) = _swapAsset(
_key,
_asset,
_local,
_amount,
calculateSlippageBoundary(config.adoptedDecimals, config.representationDecimals, _amount, _slippage)
);
return out;
}
/**
* @notice Swaps a local bridge asset for the adopted asset using the stored stable swap
* @dev Will not swap if the asset passed in is the adopted asset
* @param _key the hash of the canonical id and domain
* @param _asset - The address of the local asset to swap into the adopted asset
* @param _amount - The amount of the local asset to swap
* @param _slippage - The minimum amount of slippage user will take on from _amount in BPS
* @param _normalizedIn - The amount sent in on xcall to take the slippage from, in 18 decimals
* by convention
* @return The amount of adopted asset received from swap
* @return The address of asset received post-swap
*/
function swapFromLocalAssetIfNeeded(
bytes32 _key,
address _asset,
uint256 _amount,
uint256 _slippage,
uint256 _normalizedIn
) internal returns (uint256, address) {
// Get the token config.
TokenConfig storage config = getConfig(_key);
address adopted = config.adopted;
// If the adopted asset is the local asset, no need to swap.
if (adopted == _asset) {
return (_amount, adopted);
}
// If there's no amount, no need to swap.
if (_amount == 0) {
return (_amount, adopted);
}
// Swap the asset to the proper local asset
return
_swapAsset(
_key,
_asset,
adopted,
_amount,
// NOTE: To get the slippage boundary here, you must take the slippage % off of the
// normalized amount in (at 18 decimals by convention), then convert that amount
// to the proper decimals of adopted.
calculateSlippageBoundary(
Constants.DEFAULT_NORMALIZED_DECIMALS,
config.adoptedDecimals,
_normalizedIn,
_slippage
)
);
}
/**
* @notice Swaps a local bridge asset for the adopted asset using the stored stable swap
* @dev Will not swap if the asset passed in is the adopted asset
* @param _key the hash of the canonical id and domain
* @param _asset - The address of the local asset to swap into the adopted asset
* @param _amount - The exact amount to receive out of the swap
* @param _maxIn - The most you will supply to the swap
* @return The amount of local asset put into swap
* @return The address of asset received post-swap
*/
function swapFromLocalAssetIfNeededForExactOut(
bytes32 _key,
address _asset,
uint256 _amount,
uint256 _maxIn
) internal returns (uint256, address) {
TokenConfig storage config = getConfig(_key);
// If the adopted asset is the local asset, no need to swap.
address adopted = config.adopted;
if (adopted == _asset) {
return (_amount, adopted);
}
return _swapAssetOut(_key, _asset, adopted, _amount, _maxIn);
}
/**
* @notice Swaps assetIn to assetOut using the stored stable swap or internal swap pool.
* @dev Will not swap if the asset passed in is the adopted asset
* @param _key - The hash of canonical id and domain.
* @param _assetIn - The address of the from asset
* @param _assetOut - The address of the to asset
* @param _amount - The amount of the local asset to swap
* @param _minOut - The minimum amount of `_assetOut` the user will accept
* @return The amount of asset received
* @return The address of asset received
*/
function _swapAsset(
bytes32 _key,
address _assetIn,
address _assetOut,
uint256 _amount,
uint256 _minOut
) internal returns (uint256, address) {
AppStorage storage s = LibConnextStorage.connextStorage();
// Retrieve internal swap pool reference.
SwapUtils.Swap storage ipool = s.swapStorages[_key];
if (ipool.exists()) {
// Swap via the internal pool.
return (
ipool.swapInternal(
getTokenIndexFromStableSwapPool(_key, _assetIn),
getTokenIndexFromStableSwapPool(_key, _assetOut),
_amount,
_minOut
),
_assetOut
);
} else {
// Otherwise, swap via external stableswap pool.
IStableSwap pool = IStableSwap(getConfig(_key).adoptedToLocalExternalPools);
IERC20Metadata assetIn = IERC20Metadata(_assetIn);
assetIn.safeApprove(address(pool), 0);
assetIn.safeIncreaseAllowance(address(pool), _amount);
// NOTE: If pool is not registered here, then this call will revert.
return (
pool.swapExact(_amount, _assetIn, _assetOut, _minOut, block.timestamp + Constants.DEFAULT_DEADLINE_EXTENSION),
_assetOut
);
}
}
/**
* @notice Swaps assetIn to assetOut using the stored stable swap or internal swap pool.
* @param _key - The hash of the canonical id and domain.
* @param _assetIn - The address of the from asset.
* @param _assetOut - The address of the to asset.
* @param _amountOut - The amount of the _assetOut to swap.
* @param _maxIn - The most you will supply to the swap.
* @return amountIn The amount of assetIn. Will be 0 if the swap was unsuccessful (slippage
* too high).
* @return assetOut The address of asset received.
*/
function _swapAssetOut(
bytes32 _key,
address _assetIn,
address _assetOut,
uint256 _amountOut,
uint256 _maxIn
) internal returns (uint256, address) {
AppStorage storage s = LibConnextStorage.connextStorage();
// Retrieve internal swap pool reference. If it doesn't exist, we'll resort to using an
// external stableswap below.
SwapUtils.Swap storage ipool = s.swapStorages[_key];
// Swap the asset to the proper local asset.
// NOTE: IFF slippage was too high to perform swap in either case: success = false, amountIn = 0
if (ipool.exists()) {
// Swap via the internal pool.
return (
ipool.swapInternalOut(
getTokenIndexFromStableSwapPool(_key, _assetIn),
getTokenIndexFromStableSwapPool(_key, _assetOut),
_amountOut,
_maxIn
),
_assetOut
);
} else {
// Otherwise, swap via external stableswap pool.
// NOTE: This call will revert if the external stableswap pool doesn't exist.
IStableSwap pool = IStableSwap(getConfig(_key).adoptedToLocalExternalPools);
address poolAddress = address(pool);
// Perform the swap.
// Edge case with some tokens: Example USDT in ETH Mainnet, after the backUnbacked call
// there could be a remaining allowance if not the whole amount is pulled by aave.
// Later, if we try to increase the allowance it will fail. USDT demands if allowance
// is not 0, it has to be set to 0 first.
// Example: https://github.com/aave/aave-v3-periphery/blob/ca184e5278bcbc10d28c3dbbc604041d7cfac50b/contracts/adapters/paraswap/ParaSwapRepayAdapter.sol#L138-L140
IERC20Metadata assetIn = IERC20Metadata(_assetIn);
assetIn.safeApprove(poolAddress, 0);
assetIn.safeIncreaseAllowance(poolAddress, _maxIn);
uint256 out = pool.swapExactOut(
_amountOut,
_assetIn,
_assetOut,
_maxIn,
block.timestamp + Constants.DEFAULT_DEADLINE_EXTENSION
);
// Reset allowance
assetIn.safeApprove(poolAddress, 0);
return (out, _assetOut);
}
}
/**
* @notice Calculate amount of tokens you receive on a local bridge asset for the adopted asset
* using the stored stable swap
* @dev Will not use the stored stable swap if the asset passed in is the local asset
* @param _key - The hash of the canonical id and domain
* @param _asset - The address of the local asset to swap into the local asset
* @param _amount - The amount of the local asset to swap
* @return The amount of local asset received from swap
* @return The address of asset received post-swap
*/
function calculateSwapFromLocalAssetIfNeeded(
bytes32 _key,
address _asset,
uint256 _amount
) internal view returns (uint256, address) {
AppStorage storage s = LibConnextStorage.connextStorage();
// If the adopted asset is the local asset, no need to swap.
TokenConfig storage config = getConfig(_key);
address adopted = config.adopted;
if (adopted == _asset) {
return (_amount, adopted);
}
SwapUtils.Swap storage ipool = s.swapStorages[_key];
// Calculate the swap using the appropriate pool.
if (ipool.exists()) {
// Calculate with internal swap pool.
uint8 tokenIndexIn = getTokenIndexFromStableSwapPool(_key, _asset);
uint8 tokenIndexOut = getTokenIndexFromStableSwapPool(_key, adopted);
return (ipool.calculateSwap(tokenIndexIn, tokenIndexOut, _amount), adopted);
} else {
// Otherwise, try to calculate with external pool.
IStableSwap pool = IStableSwap(config.adoptedToLocalExternalPools);
// NOTE: This call will revert if no external pool exists.
return (pool.calculateSwapFromAddress(_asset, adopted, _amount), adopted);
}
}
/**
* @notice Calculate amount of tokens you receive of a local bridge asset for the adopted asset
* using the stored stable swap
* @dev Will not use the stored stable swap if the asset passed in is the local asset
* @param _asset - The address of the asset to swap into the local asset
* @param _amount - The amount of the asset to swap
* @return The amount of local asset received from swap
* @return The address of asset received post-swap
*/
function calculateSwapToLocalAssetIfNeeded(
bytes32 _key,
address _asset,
address _local,
uint256 _amount
) internal view returns (uint256, address) {
AppStorage storage s = LibConnextStorage.connextStorage();
// If the asset is the local asset, no swap needed
if (_asset == _local) {
return (_amount, _local);
}
SwapUtils.Swap storage ipool = s.swapStorages[_key];
// Calculate the swap using the appropriate pool.
if (ipool.exists()) {
// if internal swap pool exists
uint8 tokenIndexIn = getTokenIndexFromStableSwapPool(_key, _asset);
uint8 tokenIndexOut = getTokenIndexFromStableSwapPool(_key, _local);
return (ipool.calculateSwap(tokenIndexIn, tokenIndexOut, _amount), _local);
} else {
IStableSwap pool = IStableSwap(getConfig(_key).adoptedToLocalExternalPools);
return (pool.calculateSwapFromAddress(_asset, _local, _amount), _local);
}
}
// ============ Internal: Token ID Helpers ============
/**
* @notice Gets the canonical information for a given candidate.
* @dev First checks the `address(0)` convention, then checks if the asset given is the
* adopted asset, then calculates the local address.
* @return TokenId The canonical token ID information for the given candidate.
*/
function getCanonicalTokenId(address _candidate, AppStorage storage s) internal view returns (TokenId memory) {
TokenId memory _canonical;
// If candidate is address(0), return an empty `_canonical`.
if (_candidate == address(0)) {
return _canonical;
}
// Check to see if candidate is an adopted asset.
_canonical = s.adoptedToCanonical[_candidate];
if (_canonical.domain != 0) {
// Candidate is an adopted asset, return canonical info.
return _canonical;
}
// Candidate was not adopted; it could be the local address.
// IFF this domain is the canonical domain, then the local == canonical.
// Otherwise, it will be the representation asset.
if (isLocalOrigin(_candidate, s)) {
// The token originates on this domain, canonical information is the information
// of the candidate
_canonical.domain = s.domain;
_canonical.id = TypeCasts.addressToBytes32(_candidate);
} else {
// on a remote domain, return the representation
_canonical = s.representationToCanonical[_candidate];
}
return _canonical;
}
/**
* @notice Determine if token is of local origin (i.e. it is a locally originating contract,
* and NOT a token deployed by the bridge).
* @param s AppStorage instance.
* @return bool true if token is locally originating, false otherwise.
*/
function isLocalOrigin(address _token, AppStorage storage s) internal view returns (bool) {
// If the token contract WAS deployed by the bridge, it will be stored in this mapping.
// If so, the token is NOT of local origin.
if (s.representationToCanonical[_token].domain != 0) {
return false;
}
// If the contract was NOT deployed by the bridge, but the contract does exist, then it
// IS of local origin. Returns true if code exists at `_addr`.
return _token.code.length != 0;
}
/**
* @notice Get the local asset address for a given canonical key, id, and domain.
* @param _key - The hash of canonical id and domain.
* @param _id Canonical ID.
* @param _domain Canonical domain.
* @param s AppStorage instance.
* @return address of the the local asset.
*/
function getLocalAsset(
bytes32 _key,
bytes32 _id,
uint32 _domain,
AppStorage storage s
) internal view returns (address) {
if (_domain == s.domain) {
// Token is of local origin
return TypeCasts.bytes32ToAddress(_id);
} else {
// Token is a representation of a token of remote origin
return getConfig(_key).representation;
}
}
/**
* @notice Calculates the hash of canonical ID and domain.
* @dev This hash is used as the key for many asset-related mappings.
* @param _id Canonical ID.
* @param _domain Canonical domain.
* @return bytes32 Canonical hash, used as key for accessing token info from mappings.
*/
function calculateCanonicalHash(bytes32 _id, uint32 _domain) internal pure returns (bytes32) {
return keccak256(abi.encode(_id, _domain));
}
// ============ Internal: Math ============
/**
* @notice This function calculates slippage as a %age of the amount in, and normalizes
* That to the `_out` decimals.
*
* @dev This *ONLY* works for 1:1 assets
*
* @param _in The decimals of the asset in / amount in
* @param _out The decimals of the target asset
* @param _amountIn The starting amount for the swap
* @param _slippage The slippage allowed for the swap, in BPS
* @return uint256 The minimum amount out for the swap
*/
function calculateSlippageBoundary(
uint8 _in,
uint8 _out,
uint256 _amountIn,
uint256 _slippage
) internal pure returns (uint256) {
if (_amountIn == 0) {
return 0;
}
// Get the min recieved (in same decimals as _amountIn)
uint256 min = (_amountIn * (Constants.BPS_FEE_DENOMINATOR - _slippage)) / Constants.BPS_FEE_DENOMINATOR;
return normalizeDecimals(_in, _out, min);
}
/**
* @notice This function translates the _amount in _in decimals
* to _out decimals
*
* @param _in The decimals of the asset in / amount in
* @param _out The decimals of the target asset
* @param _amount The value to normalize to the `_out` decimals
* @return uint256 Normalized decimals.
*/
function normalizeDecimals(
uint8 _in,
uint8 _out,
uint256 _amount
) internal pure returns (uint256) {
if (_in == _out) {
return _amount;
}
// Convert this value to the same decimals as _out
uint256 normalized;
if (_in < _out) {
normalized = _amount * (10**(_out - _in));
} else {
normalized = _amount / (10**(_in - _out));
}
return normalized;
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
// ============ External Imports ============
import {TypedMemView} from "../../../shared/libraries/TypedMemView.sol";
import {TokenId} from "./TokenId.sol";
library BridgeMessage {
// ============ Libraries ============
using TypedMemView for bytes;
using TypedMemView for bytes29;
// ============ Enums ============
// WARNING: do NOT re-write the numbers / order
// of message types in an upgrade;
// will cause in-flight messages to be mis-interpreted
// The Types enum it defines the types of `views` that we use in BridgeMessage. A view
// points to a specific part of the memory and can slice bytes out of it. When we give a `type` to a view,
// we define the structure of the data it points to, so that we can do easy runtime assertions without
// having to fetch the whole data from memory and check for ourselves. In BridgeMessage.sol
// the types of `data` we can have are defined in this enum and may belong to different taxonomies.
// For example, a `Message` includes a `TokenId` and an Action (a `Transfer`).
// The Message is a different TYPE of data than a TokenId or Transfer, as TokenId and Transfer live inside
// the message. For that reason, we define them as different data types and we add them to the same enum
// for ease of use.
enum Types {
Invalid, // 0
TokenId, // 1
Message, // 2
Transfer // 3
}
// ============ Constants ============
uint256 private constant TOKEN_ID_LEN = 36; // 4 bytes domain + 32 bytes id
uint256 private constant IDENTIFIER_LEN = 1;
uint256 private constant TRANSFER_LEN = 65; // 1 byte identifier + 32 bytes amount + 32 bytes transfer id
// ============ Modifiers ============
/**
* @notice Asserts a message is of type `_t`
* @param _view The message
* @param _t The expected type
*/
modifier typeAssert(bytes29 _view, Types _t) {
_view.assertType(uint40(_t));
_;
}
// ============ Internal Functions ============
/**
* @notice Checks that Action is valid type
* @param _action The action
* @return TRUE if action is valid
*/
function isValidAction(bytes29 _action) internal pure returns (bool) {
return isTransfer(_action);
}
/**
* @notice Checks that view is a valid message length
* @param _view The bytes string
* @return TRUE if message is valid
*/
function isValidMessageLength(bytes29 _view) internal pure returns (bool) {
uint256 _len = _view.len();
return _len == TOKEN_ID_LEN + TRANSFER_LEN;
}
/**
* @notice Formats an action message
* @param _tokenId The token ID
* @param _action The action
* @return The formatted message
*/
function formatMessage(bytes29 _tokenId, bytes29 _action)
internal
view
typeAssert(_tokenId, Types.TokenId)
returns (bytes memory)
{
require(isValidAction(_action), "!action");
bytes29[] memory _views = new bytes29[](2);
_views[0] = _tokenId;
_views[1] = _action;
return TypedMemView.join(_views);
}
/**
* @notice Returns the type of the message
* @param _view The message
* @return The type of the message
*/
function messageType(bytes29 _view) internal pure returns (Types) {
return Types(uint8(_view.typeOf()));
}
/**
* @notice Checks that the message is of the specified type
* @param _type the type to check for
* @param _action The message
* @return True if the message is of the specified type
*/
function isType(bytes29 _action, Types _type) internal pure returns (bool) {
return actionType(_action) == uint8(_type) && messageType(_action) == _type;
}
/**
* @notice Checks that the message is of type Transfer
* @param _action The message
* @return True if the message is of type Transfer
*/
function isTransfer(bytes29 _action) internal pure returns (bool) {
return isType(_action, Types.Transfer);
}
/**
* @notice Formats Transfer
* @param _amnt The transfer amount
* @param _transferId The unique identifier of the transfer
* @return
*/
function formatTransfer(uint256 _amnt, bytes32 _transferId) internal pure returns (bytes29) {
return abi.encodePacked(Types.Transfer, _amnt, _transferId).ref(uint40(Types.Transfer));
}
/**
* @notice Serializes a Token ID struct
* @param _tokenId The token id struct
* @return The formatted Token ID
*/
function formatTokenId(TokenId memory _tokenId) internal pure returns (bytes29) {
return formatTokenId(_tokenId.domain, _tokenId.id);
}
/**
* @notice Creates a serialized Token ID from components
* @param _domain The domain
* @param _id The ID
* @return The formatted Token ID
*/
function formatTokenId(uint32 _domain, bytes32 _id) internal pure returns (bytes29) {
return abi.encodePacked(_domain, _id).ref(uint40(Types.TokenId));
}
/**
* @notice Retrieves the domain from a TokenID
* @param _tokenId The message
* @return The domain
*/
function domain(bytes29 _tokenId) internal pure typeAssert(_tokenId, Types.TokenId) returns (uint32) {
return uint32(_tokenId.indexUint(0, 4));
}
/**
* @notice Retrieves the ID from a TokenID
* @param _tokenId The message
* @return The ID
*/
function id(bytes29 _tokenId) internal pure typeAssert(_tokenId, Types.TokenId) returns (bytes32) {
// before = 4 bytes domain
return _tokenId.index(4, 32);
}
/**
* @notice Retrieves the EVM ID
* @param _tokenId The message
* @return The EVM ID
*/
function evmId(bytes29 _tokenId) internal pure typeAssert(_tokenId, Types.TokenId) returns (address) {
// before = 4 bytes domain + 12 bytes empty to trim for address
return _tokenId.indexAddress(16);
}
/**
* @notice Retrieves the action identifier from message
* @param _message The action
* @return The message type
*/
function msgType(bytes29 _message) internal pure returns (uint8) {
return uint8(_message.indexUint(TOKEN_ID_LEN, 1));
}
/**
* @notice Retrieves the identifier from action
* @param _action The action
* @return The action type
*/
function actionType(bytes29 _action) internal pure returns (uint8) {
return uint8(_action.indexUint(0, 1));
}
/**
* @notice Retrieves the amount from a Transfer
* @param _transferAction The message
* @return The amount
*/
function amnt(bytes29 _transferAction) internal pure returns (uint256) {
// before = 1 byte identifier = 1 bytes
return _transferAction.indexUint(1, 32);
}
/**
* @notice Retrieves the transfer id from a Transfer
* @param _transferAction The message
* @return The id
*/
function transferId(bytes29 _transferAction) internal pure returns (bytes32) {
// before = 1 byte identifier + 32 bytes amount = 33 bytes
return _transferAction.index(33, 32);
}
/**
* @notice Retrieves the token ID from a Message
* @param _message The message
* @return The ID
*/
function tokenId(bytes29 _message) internal pure typeAssert(_message, Types.Message) returns (bytes29) {
return _message.slice(0, TOKEN_ID_LEN, uint40(Types.TokenId));
}
/**
* @notice Retrieves the action data from a Message
* @param _message The message
* @return The action
*/
function action(bytes29 _message) internal pure typeAssert(_message, Types.Message) returns (bytes29) {
uint256 _actionLen = _message.len() - TOKEN_ID_LEN;
uint40 _type = uint40(msgType(_message));
return _message.slice(TOKEN_ID_LEN, _actionLen, _type);
}
/**
* @notice Converts to a Message
* @param _message The message
* @return The newly typed message
*/
function tryAsMessage(bytes29 _message) internal pure returns (bytes29) {
if (isValidMessageLength(_message)) {
return _message.castTo(uint40(Types.Message));
}
return TypedMemView.nullView();
}
/**
* @notice Asserts that the message is of type Message
* @param _view The message
* @return The message
*/
function mustBeMessage(bytes29 _view) internal pure returns (bytes29) {
return tryAsMessage(_view).assertValid();
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
library Constants {
// ============= Initial Values =============
/**
* @notice Sets the initial lp fee at 5 bps
*/
uint256 public constant INITIAL_LIQUIDITY_FEE_NUMERATOR = 9_995;
/**
* @notice Sets the initial max routers per transfer
*/
uint256 public constant INITIAL_MAX_ROUTERS = 5;
/**
* @notice Sets the initial max routers per transfer
*/
uint16 public constant INITIAL_AAVE_REFERRAL_CODE = 0;
// =============
// ============= Unchangeable Values =============
// ============= Facets
/**
* @notice Reentrancy modifier for diamond
*/
uint256 internal constant NOT_ENTERED = 1;
/**
* @notice Reentrancy modifier for diamond
*/
uint256 internal constant ENTERED = 2;
/**
* @notice Contains hash of empty bytes
*/
bytes32 internal constant EMPTY_HASH = keccak256("");
/**
* @notice Denominator for BPS values
*/
uint256 public constant BPS_FEE_DENOMINATOR = 10_000;
/**
* @notice Value for delay used on governance
*/
uint256 public constant GOVERNANCE_DELAY = 7 days;
/**
* @notice Required gas amount to be leftover after passing in `gasleft` when
* executing calldata (see `_executeCalldata` method).
*/
uint256 public constant EXECUTE_CALLDATA_RESERVE_GAS = 10_000;
/**
* @notice Portal referral code
*/
uint16 public constant AAVE_REFERRAL_CODE = 0;
// ============= ConnextPriceOracle
/**
* @notice Valid period for a price delivered by the price oracle
*/
uint256 public constant ORACLE_VALID_PERIOD = 1 minutes;
/**
* @notice Valid wiggle room for future timestamps (3s) used by `setDirectPrice`
*/
uint256 public constant FUTURE_TIME_BUFFER = 3;
/**
* @notice Defalt decimals values are normalized to
*/
uint8 public constant DEFAULT_NORMALIZED_DECIMALS = uint8(18);
/**
* @notice Bytes of return data copied back when using `excessivelySafeCall`
*/
uint16 public constant DEFAULT_COPY_BYTES = 256;
/**
* @notice Valid deadline extension used when swapping (1hr)
*/
uint256 public constant DEFAULT_DEADLINE_EXTENSION = 3600;
// ============= Swaps
/**
* @notice the precision all pools tokens will be converted to
* @dev stored here to keep easily in sync between `SwapUtils` and `SwapUtilsExternal`
*
* The minimum in a pool is 2 (nextUSDC, USDC), and the maximum allowed is 16. While
* we do not have pools supporting this number of token, allowing a larger value leaves
* the possibility open to pool multiple stable local/adopted pairs, garnering greater
* capital efficiency. 16 specifically was chosen as a bit of a sweet spot between the
* default of 32 and what we will realistically host in pools.
*/
uint256 public constant MINIMUM_POOLED_TOKENS = 2;
uint256 public constant MAXIMUM_POOLED_TOKENS = 16;
/**
* @notice the precision all pools tokens will be converted to
* @dev stored here to keep easily in sync between `SwapUtils` and `SwapUtilsExternal`
*/
uint8 public constant POOL_PRECISION_DECIMALS = 18;
/**
* @notice the denominator used to calculate admin and LP fees. For example, an
* LP fee might be something like tradeAmount.mul(fee).div(FEE_DENOMINATOR)
* @dev stored here to keep easily in sync between `SwapUtils` and `SwapUtilsExternal`
*/
uint256 public constant FEE_DENOMINATOR = 1e10;
/**
* @notice Max swap fee is 1% or 100bps of each swap
* @dev stored here to keep easily in sync between `SwapUtils` and `SwapUtilsExternal`
*/
uint256 public constant MAX_SWAP_FEE = 1e8;
/**
* @notice Max adminFee is 100% of the swapFee. adminFee does not add additional fee on top of swapFee.
* Instead it takes a certain % of the swapFee. Therefore it has no impact on the
* users but only on the earnings of LPs
* @dev stored here to keep easily in sync between `SwapUtils` and `SwapUtilsExternal`
*/
uint256 public constant MAX_ADMIN_FEE = 1e10;
/**
* @notice constant value used as max loop limit
* @dev stored here to keep easily in sync between `SwapUtils` and `SwapUtilsExternal`
*/
uint256 public constant MAX_LOOP_LIMIT = 256;
// Constant value used as max delay time for removing swap after disabled
uint256 internal constant REMOVE_DELAY = 7 days;
/**
* @notice constant values used in ramping A calculations
* @dev stored here to keep easily in sync between `SwapUtils` and `SwapUtilsExternal`
*/
uint256 public constant A_PRECISION = 100;
uint256 public constant MAX_A = 10**6;
uint256 public constant MAX_A_CHANGE = 2;
uint256 public constant MIN_RAMP_TIME = 14 days;
uint256 public constant MIN_RAMP_DELAY = 1 days;
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {IStableSwap} from "../interfaces/IStableSwap.sol";
import {IConnectorManager} from "../../../messaging/interfaces/IConnectorManager.sol";
import {SwapUtils} from "./SwapUtils.sol";
import {TokenId} from "./TokenId.sol";
/**
* @notice THIS FILE DEFINES OUR STORAGE LAYOUT AND ID GENERATION SCHEMA. IT CAN ONLY BE MODIFIED FREELY FOR FRESH
* DEPLOYS. If you are modifiying this file for an upgrade, you must **CAREFULLY** ensure
* the contract storage layout is not impacted.
*
* BE VERY CAREFUL MODIFYING THE VALUES IN THIS FILE!
*/
// ============= Enum =============
/// @notice Enum representing address role
// Returns uint
// None - 0
// Router - 1
// Watcher - 2
// Admin - 3
enum Role {
None,
RouterAdmin,
Watcher,
Admin
}
/**
* @notice Enum representing status of destination transfer
* @dev Status is only assigned on the destination domain, will always be "none" for the
* origin domains
* @return uint - Index of value in enum
*/
enum DestinationTransferStatus {
None, // 0
Reconciled, // 1
Executed, // 2
Completed // 3 - executed + reconciled
}
/**
* @notice These are the parameters that will remain constant between the
* two chains. They are supplied on `xcall` and should be asserted on `execute`
* @property to - The account that receives funds, in the event of a crosschain call,
* will receive funds if the call fails.
*
* @param originDomain - The originating domain (i.e. where `xcall` is called)
* @param destinationDomain - The final domain (i.e. where `execute` / `reconcile` are called)\\
* @param canonicalDomain - The canonical domain of the asset you are bridging
* @param to - The address you are sending funds (and potentially data) to
* @param delegate - An address who can execute txs on behalf of `to`, in addition to allowing relayers
* @param receiveLocal - If true, will use the local asset on the destination instead of adopted.
* @param callData - The data to execute on the receiving chain. If no crosschain call is needed, then leave empty.
* @param slippage - Slippage user is willing to accept from original amount in expressed in BPS (i.e. if
* a user takes 1% slippage, this is expressed as 1_000)
* @param originSender - The msg.sender of the xcall
* @param bridgedAmt - The amount sent over the bridge (after potential AMM on xcall)
* @param normalizedIn - The amount sent to `xcall`, normalized to 18 decimals
* @param nonce - The nonce on the origin domain used to ensure the transferIds are unique
* @param canonicalId - The unique identifier of the canonical token corresponding to bridge assets
*/
struct TransferInfo {
uint32 originDomain;
uint32 destinationDomain;
uint32 canonicalDomain;
address to;
address delegate;
bool receiveLocal;
bytes callData;
uint256 slippage;
address originSender;
uint256 bridgedAmt;
uint256 normalizedIn;
uint256 nonce;
bytes32 canonicalId;
}
/**
* @notice
* @param params - The TransferInfo. These are consistent across sending and receiving chains.
* @param routers - The routers who you are sending the funds on behalf of.
* @param routerSignatures - Signatures belonging to the routers indicating permission to use funds
* for the signed transfer ID.
* @param sequencer - The sequencer who assigned the router path to this transfer.
* @param sequencerSignature - Signature produced by the sequencer for path assignment accountability
* for the path that was signed.
*/
struct ExecuteArgs {
TransferInfo params;
address[] routers;
bytes[] routerSignatures;
address sequencer;
bytes sequencerSignature;
}
/**
* @notice Contains configs for each router
* @param approved Whether the router is allowlisted, settable by admin
* @param portalApproved Whether the router is allowlisted for portals, settable by admin
* @param routerOwners The address that can update the `recipient`
* @param proposedRouterOwners Owner candidates
* @param proposedRouterTimestamp When owner candidate was proposed (there is a delay to acceptance)
*/
struct RouterConfig {
bool approved;
bool portalApproved;
address owner;
address recipient;
address proposed;
uint256 proposedTimestamp;
}
/**
* @notice Contains configurations for tokens
* @dev Struct will be stored on the hash of the `canonicalId` and `canonicalDomain`. There are also
* two separate reverse lookups, that deliver plaintext information based on the passed in address (can
* either be representation or adopted address passed in).
*
* If the decimals are updated in a future token upgrade, the transfers should fail. If that happens, the
* asset and swaps must be removed, and then they can be readded
*
* @param representation Address of minted asset on this domain. If the token is of local origin (meaning it was
* originally deployed on this chain), this MUST map to address(0).
* @param representationDecimals Decimals of minted asset on this domain
* @param adopted Address of adopted asset on this domain
* @param adoptedDecimals Decimals of adopted asset on this domain
* @param adoptedToLocalExternalPools Holds the AMMs for swapping in and out of local assets
* @param approval Allowed assets
* @param cap Liquidity caps of whitelisted assets. If 0, no cap is enforced.
* @param custodied Custodied balance by address
*/
struct TokenConfig {
address representation;
uint8 representationDecimals;
address adopted;
uint8 adoptedDecimals;
address adoptedToLocalExternalPools;
bool approval;
uint256 cap;
uint256 custodied;
}
struct AppStorage {
//
// 0
bool initialized;
//
// Connext
//
// 1
uint256 LIQUIDITY_FEE_NUMERATOR;
/**
* @notice The local address that is custodying relayer fees
*/
// 2
address relayerFeeVault;
/**
* @notice Nonce for the contract, used to keep unique transfer ids.
* @dev Assigned at first interaction (xcall on origin domain).
*/
// 3
uint256 nonce;
/**
* @notice The domain this contract exists on.
* @dev Must match the domain identifier, which is distinct from the "chainId".
*/
// 4
uint32 domain;
/**
* @notice Mapping of adopted to canonical asset information.
*/
// 5
mapping(address => TokenId) adoptedToCanonical;
/**
* @notice Mapping of representation to canonical asset information.
*/
// 6
mapping(address => TokenId) representationToCanonical;
/**
* @notice Mapping of hash(canonicalId, canonicalDomain) to token config on this domain.
*/
// 7
mapping(bytes32 => TokenConfig) tokenConfigs;
/**
* @notice Mapping to track transfer status on destination domain
*/
// 8
mapping(bytes32 => DestinationTransferStatus) transferStatus;
/**
* @notice Mapping holding router address that provided fast liquidity.
*/
// 9
mapping(bytes32 => address[]) routedTransfers;
/**
* @notice Mapping of router to available balance of an asset.
* @dev Routers should always store liquidity that they can expect to receive via the bridge on
* this domain (the local asset).
*/
// 10
mapping(address => mapping(address => uint256)) routerBalances;
/**
* @notice Mapping of approved relayers
* @dev Send relayer fee if msg.sender is approvedRelayer; otherwise revert.
*/
// 11
mapping(address => bool) approvedRelayers;
/**
* @notice The max amount of routers a payment can be routed through.
*/
// 12
uint256 maxRoutersPerTransfer;
/**
* @notice Stores a mapping of transfer id to slippage overrides.
*/
// 13
mapping(bytes32 => uint256) slippage;
/**
* @notice Stores a mapping of transfer id to receive local overrides.
*/
// 14
mapping(bytes32 => bool) receiveLocalOverride;
/**
* @notice Stores a mapping of remote routers keyed on domains.
* @dev Addresses are cast to bytes32.
* This mapping is required because the Connext now contains the BridgeRouter and must implement
* the remotes interface.
*/
// 15
mapping(uint32 => bytes32) remotes;
//
// ProposedOwnable
//
// 17
address _proposed;
// 18
uint256 _proposedOwnershipTimestamp;
// 19
bool _routerAllowlistRemoved;
// 20
uint256 _routerAllowlistTimestamp;
/**
* @notice Stores a mapping of address to Roles
* @dev returns uint representing the enum Role value
*/
// 21
mapping(address => Role) roles;
//
// RouterFacet
//
// 22
mapping(address => RouterConfig) routerConfigs;
//
// ReentrancyGuard
//
// 23
uint256 _status;
// 24
uint256 _xcallStatus;
//
// StableSwap
//
/**
* @notice Mapping holding the AMM storages for swapping in and out of local assets
* @dev Swaps for an adopted asset <> local asset (i.e. POS USDC <> nextUSDC on polygon)
* Struct storing data responsible for automatic market maker functionalities. In order to
* access this data, this contract uses SwapUtils library. For more details, see SwapUtils.sol.
*/
// 25
mapping(bytes32 => SwapUtils.Swap) swapStorages;
/**
* @notice Maps token address to an index in the pool. Used to prevent duplicate tokens in the pool.
* @dev getTokenIndex function also relies on this mapping to retrieve token index.
*/
// 26
mapping(bytes32 => mapping(address => uint8)) tokenIndexes;
/**
* The address of an existing LPToken contract to use as a target
* this target must be the address which connext deployed on this chain.
*/
// 27
address lpTokenTargetAddress;
/**
* @notice Stores whether or not bribing, AMMs, have been paused.
*/
// 28
bool _paused;
//
// AavePortals
//
/**
* @notice Address of Aave Pool contract.
*/
// 29
address aavePool;
/**
* @notice Fee percentage numerator for using Portal liquidity.
* @dev Assumes the same basis points as the liquidity fee.
*/
// 30
uint256 aavePortalFeeNumerator;
/**
* @notice Mapping to store the transfer liquidity amount provided by Aave Portals.
*/
// 31
mapping(bytes32 => uint256) portalDebt;
/**
* @notice Mapping to store the transfer liquidity amount provided by Aave Portals.
*/
// 32
mapping(bytes32 => uint256) portalFeeDebt;
/**
* @notice Mapping of approved sequencers
* @dev Sequencer address provided must belong to an approved sequencer in order to call `execute`
* for the fast liquidity route.
*/
// 33
mapping(address => bool) approvedSequencers;
/**
* @notice Remote connection manager for xapp.
*/
// 34
IConnectorManager xAppConnectionManager;
}
library LibConnextStorage {
function connextStorage() internal pure returns (AppStorage storage ds) {
assembly {
ds.slot := 0
}
}
}
// SPDX-License-Identifier: MIT
pragma solidity 0.8.17;
/******************************************************************************\\
* Author: Nick Mudge <[email protected]> (https://twitter.com/mudgen)
* EIP-2535 Diamonds: https://eips.ethereum.org/EIPS/eip-2535
/******************************************************************************/
import {IDiamondCut} from "../interfaces/IDiamondCut.sol";
// Remember to add the loupe functions from DiamondLoupeFacet to the diamond.
// The loupe functions are required by the EIP2535 Diamonds standard
library LibDiamond {
bytes32 constant DIAMOND_STORAGE_POSITION = keccak256("diamond.standard.diamond.storage");
struct FacetAddressAndPosition {
address facetAddress;
uint96 functionSelectorPosition; // position in facetFunctionSelectors.functionSelectors array
}
struct FacetFunctionSelectors {
bytes4[] functionSelectors;
uint256 facetAddressPosition; // position of facetAddress in facetAddresses array
}
struct DiamondStorage {
// maps function selector to the facet address and
// the position of the selector in the facetFunctionSelectors.selectors array
mapping(bytes4 => FacetAddressAndPosition) selectorToFacetAndPosition;
// maps facet addresses to function selectors
mapping(address => FacetFunctionSelectors) facetFunctionSelectors;
// facet addresses
address[] facetAddresses;
// Used to query if a contract implements an interface.
// Used to implement ERC-165.
mapping(bytes4 => bool) supportedInterfaces;
// owner of the contract
address contractOwner;
// hash of proposed facets => acceptance time
mapping(bytes32 => uint256) acceptanceTimes;
// acceptance delay for upgrading facets
uint256 acceptanceDelay;
}
function diamondStorage() internal pure returns (DiamondStorage storage ds) {
bytes32 position = DIAMOND_STORAGE_POSITION;
assembly {
ds.slot := position
}
}
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
function setContractOwner(address _newOwner) internal {
DiamondStorage storage ds = diamondStorage();
emit OwnershipTransferred(ds.contractOwner, _newOwner);
ds.contractOwner = _newOwner;
}
function contractOwner() internal view returns (address contractOwner_) {
contractOwner_ = diamondStorage().contractOwner;
}
function acceptanceDelay() internal view returns (uint256) {
return diamondStorage().acceptanceDelay;
}
function acceptanceTime(bytes32 _key) internal view returns (uint256) {
return diamondStorage().acceptanceTimes[_key];
}
function enforceIsContractOwner() internal view {
require(msg.sender == diamondStorage().contractOwner, "LibDiamond: !contract owner");
}
event DiamondCutProposed(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata, uint256 deadline);
function proposeDiamondCut(
IDiamondCut.FacetCut[] memory _diamondCut,
address _init,
bytes memory _calldata
) internal {
// NOTE: to save gas, verification that `proposeDiamondCut` and `diamondCut` are not
// included is performed in `diamondCut`, where there is already a loop over facets.
// In the case where these cuts are performed, admins must call `rescindDiamondCut`
DiamondStorage storage ds = diamondStorage();
uint256 acceptance = block.timestamp + ds.acceptanceDelay;
ds.acceptanceTimes[keccak256(abi.encode(_diamondCut, _init, _calldata))] = acceptance;
emit DiamondCutProposed(_diamondCut, _init, _calldata, acceptance);
}
event DiamondCutRescinded(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata);
function rescindDiamondCut(
IDiamondCut.FacetCut[] memory _diamondCut,
address _init,
bytes memory _calldata
) internal {
// NOTE: you can always rescind a proposed facet cut as the owner, even if outside of the validity
// period or befor the delay elpases
delete diamondStorage().acceptanceTimes[keccak256(abi.encode(_diamondCut, _init, _calldata))];
emit DiamondCutRescinded(_diamondCut, _init, _calldata);
}
event DiamondCut(IDiamondCut.FacetCut[] _diamondCut, address _init, bytes _calldata);
// Internal function version of diamondCut
function diamondCut(
IDiamondCut.FacetCut[] memory _diamondCut,
address _init,
bytes memory _calldata
) internal {
DiamondStorage storage ds = diamondStorage();
bytes32 key = keccak256(abi.encode(_diamondCut, _init, _calldata));
if (ds.facetAddresses.length != 0) {
uint256 time = ds.acceptanceTimes[key];
require(time != 0 && time <= block.timestamp, "LibDiamond: delay not elapsed");
// Reset the acceptance time to ensure the same set of updates cannot be replayed
// without going through a proposal window
// NOTE: the only time this will not be set to 0 is when there are no
// existing facet addresses (on initialization, or when starting after a bad upgrade,
// for example).
// The only relevant case is the initial case, which has no acceptance time. otherwise,
// there is no way to update the facet selector mapping to call `diamondCut`.
// Avoiding setting the empty value will save gas on the initial deployment.
delete ds.acceptanceTimes[key];
} // Otherwise, this is the first instance of deployment and it can be set automatically
uint256 len = _diamondCut.length;
for (uint256 facetIndex; facetIndex < len; ) {
IDiamondCut.FacetCutAction action = _diamondCut[facetIndex].action;
if (action == IDiamondCut.FacetCutAction.Add) {
addFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else if (action == IDiamondCut.FacetCutAction.Replace) {
replaceFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else if (action == IDiamondCut.FacetCutAction.Remove) {
removeFunctions(_diamondCut[facetIndex].facetAddress, _diamondCut[facetIndex].functionSelectors);
} else {
revert("LibDiamondCut: Incorrect FacetCutAction");
}
unchecked {
++facetIndex;
}
}
emit DiamondCut(_diamondCut, _init, _calldata);
initializeDiamondCut(_init, _calldata);
}
function addFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
require(_functionSelectors.length != 0, "LibDiamondCut: No selectors in facet to cut");
DiamondStorage storage ds = diamondStorage();
require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)");
uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
// add new facet address if it does not exist
if (selectorPosition == 0) {
addFacet(ds, _facetAddress);
}
uint256 len = _functionSelectors.length;
for (uint256 selectorIndex; selectorIndex < len; ) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
require(oldFacetAddress == address(0), "LibDiamondCut: Can't add function that already exists");
addFunction(ds, selector, selectorPosition, _facetAddress);
selectorPosition++;
unchecked {
++selectorIndex;
}
}
}
function replaceFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
uint256 len = _functionSelectors.length;
require(len != 0, "LibDiamondCut: No selectors in facet to cut");
DiamondStorage storage ds = diamondStorage();
require(_facetAddress != address(0), "LibDiamondCut: Add facet can't be address(0)");
uint96 selectorPosition = uint96(ds.facetFunctionSelectors[_facetAddress].functionSelectors.length);
// add new facet address if it does not exist
if (selectorPosition == 0) {
addFacet(ds, _facetAddress);
}
for (uint256 selectorIndex; selectorIndex < len; ) {
bytes4 selector = _functionSelectors[selectorIndex];
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
require(oldFacetAddress != _facetAddress, "LibDiamondCut: Can't replace function with same function");
removeFunction(ds, oldFacetAddress, selector);
addFunction(ds, selector, selectorPosition, _facetAddress);
selectorPosition++;
unchecked {
++selectorIndex;
}
}
}
function removeFunctions(address _facetAddress, bytes4[] memory _functionSelectors) internal {
require(_functionSelectors.length != 0, "LibDiamondCut: No selectors in facet to cut");
DiamondStorage storage ds = diamondStorage();
// get the propose and cut selectors -- can never remove these
bytes4 proposeSelector = IDiamondCut.proposeDiamondCut.selector;
bytes4 cutSelector = IDiamondCut.diamondCut.selector;
// if function does not exist then do nothing and return
require(_facetAddress == address(0), "LibDiamondCut: Remove facet address must be address(0)");
uint256 len = _functionSelectors.length;
for (uint256 selectorIndex; selectorIndex < len; ) {
bytes4 selector = _functionSelectors[selectorIndex];
require(selector != proposeSelector && selector != cutSelector, "LibDiamondCut: Cannot remove cut selectors");
address oldFacetAddress = ds.selectorToFacetAndPosition[selector].facetAddress;
removeFunction(ds, oldFacetAddress, selector);
unchecked {
++selectorIndex;
}
}
}
function addFacet(DiamondStorage storage ds, address _facetAddress) internal {
enforceHasContractCode(_facetAddress, "LibDiamondCut: New facet has no code");
ds.facetFunctionSelectors[_facetAddress].facetAddressPosition = ds.facetAddresses.length;
ds.facetAddresses.push(_facetAddress);
}
function addFunction(
DiamondStorage storage ds,
bytes4 _selector,
uint96 _selectorPosition,
address _facetAddress
) internal {
ds.selectorToFacetAndPosition[_selector].functionSelectorPosition = _selectorPosition;
ds.facetFunctionSelectors[_facetAddress].functionSelectors.push(_selector);
ds.selectorToFacetAndPosition[_selector].facetAddress = _facetAddress;
}
function removeFunction(
DiamondStorage storage ds,
address _facetAddress,
bytes4 _selector
) internal {
require(_facetAddress != address(0), "LibDiamondCut: Can't remove function that doesn't exist");
// an immutable function is a function defined directly in a diamond
require(_facetAddress != address(this), "LibDiamondCut: Can't remove immutable function");
// replace selector with last selector, then delete last selector
uint256 selectorPosition = ds.selectorToFacetAndPosition[_selector].functionSelectorPosition;
uint256 lastSelectorPosition = ds.facetFunctionSelectors[_facetAddress].functionSelectors.length - 1;
// if not the same then replace _selector with lastSelector
if (selectorPosition != lastSelectorPosition) {
bytes4 lastSelector = ds.facetFunctionSelectors[_facetAddress].functionSelectors[lastSelectorPosition];
ds.facetFunctionSelectors[_facetAddress].functionSelectors[selectorPosition] = lastSelector;
ds.selectorToFacetAndPosition[lastSelector].functionSelectorPosition = uint96(selectorPosition);
}
// delete the last selector
ds.facetFunctionSelectors[_facetAddress].functionSelectors.pop();
delete ds.selectorToFacetAndPosition[_selector];
// if no more selectors for facet address then delete the facet address
if (lastSelectorPosition == 0) {
// replace facet address with last facet address and delete last facet address
uint256 lastFacetAddressPosition = ds.facetAddresses.length - 1;
uint256 facetAddressPosition = ds.facetFunctionSelectors[_facetAddress].facetAddressPosition;
if (facetAddressPosition != lastFacetAddressPosition) {
address lastFacetAddress = ds.facetAddresses[lastFacetAddressPosition];
ds.facetAddresses[facetAddressPosition] = lastFacetAddress;
ds.facetFunctionSelectors[lastFacetAddress].facetAddressPosition = facetAddressPosition;
}
ds.facetAddresses.pop();
delete ds.facetFunctionSelectors[_facetAddress].facetAddressPosition;
}
}
function initializeDiamondCut(address _init, bytes memory _calldata) internal {
if (_init == address(0)) {
require(_calldata.length == 0, "LibDiamondCut: _init is address(0) but_calldata is not empty");
} else {
require(_calldata.length != 0, "LibDiamondCut: _calldata is empty but _init is not address(0)");
if (_init != address(this)) {
enforceHasContractCode(_init, "LibDiamondCut: _init address has no code");
}
(bool success, bytes memory error) = _init.delegatecall(_calldata);
if (!success) {
if (error.length != 0) {
// bubble up the error
revert(string(error));
} else {
revert("LibDiamondCut: _init function reverted");
}
}
}
}
function enforceHasContractCode(address _contract, string memory _errorMessage) internal view {
require(_contract.code.length != 0, _errorMessage);
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
/**
* @title MathUtils library
* @notice A library to be used in conjunction with SafeMath. Contains functions for calculating
* differences between two uint256.
*/
library MathUtils {
/**
* @notice Compares a and b and returns true if the difference between a and b
* is less than 1 or equal to each other.
* @param a uint256 to compare with
* @param b uint256 to compare with
* @return True if the difference between a and b is less than 1 or equal,
* otherwise return false
*/
function within1(uint256 a, uint256 b) internal pure returns (bool) {
return (difference(a, b) < 1 + 1); // instead of <=1
}
/**
* @notice Calculates absolute difference between a and b
* @param a uint256 to compare with
* @param b uint256 to compare with
* @return Difference between a and b
*/
function difference(uint256 a, uint256 b) internal pure returns (uint256) {
if (a > b) {
return a - b;
}
return b - a;
}
}
// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.17;
import {SafeERC20, IERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {LPToken} from "../helpers/LPToken.sol";
import {AmplificationUtils} from "./AmplificationUtils.sol";
import {MathUtils} from "./MathUtils.sol";
import {AssetLogic} from "./AssetLogic.sol";
import {Constants} from "./Constants.sol";
/**
* @title SwapUtils library
* @notice A library to be used within Swap.sol. Contains functions responsible for custody and AMM functionalities.
* @dev Contracts relying on this library must initialize SwapUtils.Swap struct then use this library
* for SwapUtils.Swap struct. Note that this library contains both functions called by users and admins.
* Admin functions should be protected within contracts using this library.
*/
library SwapUtils {
using SafeERC20 for IERC20;
using MathUtils for uint256;
/*** EVENTS ***/
event TokenSwap(
bytes32 indexed key,
address indexed buyer,
uint256 tokensSold,
uint256 tokensBought,
uint128 soldId,
uint128 boughtId
);
event AddLiquidity(
bytes32 indexed key,
address indexed provider,
uint256[] tokenAmounts,
uint256[] fees,
uint256 invariant,
uint256 lpTokenSupply
);
event RemoveLiquidity(bytes32 indexed key, address indexed provider, uint256[] tokenAmounts, uint256 lpTokenSupply);
event RemoveLiquidityOne(
bytes32 indexed key,
address indexed provider,
uint256 lpTokenAmount,
uint256 lpTokenSupply,
uint256 boughtId,
uint256 tokensBought
);
event RemoveLiquidityImbalance(
bytes32 indexed key,
address indexed provider,
uint256[] tokenAmounts,
uint256[] fees,
uint256 invariant,
uint256 lpTokenSupply
);
event NewAdminFee(bytes32 indexed key, uint256 newAdminFee);
event NewSwapFee(bytes32 indexed key, uint256 newSwapFee);
struct Swap {
// variables around the ramp management of A,
// the amplification coefficient * n ** (n - 1)
// see Curve stableswap paper for details
bytes32 key;
uint256 initialA;
uint256 futureA;
uint256 initialATime;
uint256 futureATime;
// fee calculation
uint256 swapFee;
uint256 adminFee;
LPToken lpToken;
// contract references for all tokens being pooled
IERC20[] pooledTokens;
// multipliers for each pooled token's precision to get to Constants.POOL_PRECISION_DECIMALS
// for example, TBTC has 18 decimals, so the multiplier should be 1. WBTC
// has 8, so the multiplier should be 10 ** 18 / 10 ** 8 => 10 ** 10
uint256[] tokenPrecisionMultipliers;
// the pool balance of each token, in the token's precision
// the contract's actual token balance might differ
uint256[] balances;
// the admin fee balance of each token, in the token's precision
uint256[] adminFees;
// the flag if this pool disabled by admin. once disabled, only remove liquidity will work.
bool disabled;
// once pool disabled, admin can remove pool after passed removeTime. and reinitialize.
uint256 removeTime;
}
// Struct storing variables used in calculations in the
// calculateWithdrawOneTokenDY function to avoid stack too deep errors
struct CalculateWithdrawOneTokenDYInfo {
uint256 d0;
uint256 d1;
uint256 newY;
uint256 feePerToken;
uint256 preciseA;
}
// Struct storing variables used in calculations in the
// {add,remove}Liquidity functions to avoid stack too deep errors
struct ManageLiquidityInfo {
uint256 d0;
uint256 d1;
uint256 d2;
uint256 preciseA;
LPToken lpToken;
uint256 totalSupply;
uint256[] balances;
uint256[] multipliers;
}
/*** VIEW & PURE FUNCTIONS ***/
function _getAPrecise(Swap storage self) private view returns (uint256) {
return AmplificationUtils._getAPrecise(self);
}
/**
* @notice Calculate the dy, the amount of selected token that user receives and
* the fee of withdrawing in one token
* @param tokenAmount the amount to withdraw in the pool's precision
* @param tokenIndex which token will be withdrawn
* @param self Swap struct to read from
* @return the amount of token user will receive
*/
function calculateWithdrawOneToken(
Swap storage self,
uint256 tokenAmount,
uint8 tokenIndex
) internal view returns (uint256) {
(uint256 availableTokenAmount, ) = _calculateWithdrawOneToken(
self,
tokenAmount,
tokenIndex,
self.lpToken.totalSupply()
);
return availableTokenAmount;
}
function _calculateWithdrawOneToken(
Swap storage self,
uint256 tokenAmount,
uint8 tokenIndex,
uint256 totalSupply
) private view returns (uint256, uint256) {
uint256 dy;
uint256 newY;
uint256 currentY;
(dy, newY, currentY) = calculateWithdrawOneTokenDY(self, tokenIndex, tokenAmount, totalSupply);
// dy_0 (without fees)
// dy, dy_0 - dy
uint256 dySwapFee = (currentY - newY) / self.tokenPrecisionMultipliers[tokenIndex] - dy;
return (dy, dySwapFee);
}
/**
* @notice Calculate the dy of withdrawing in one token
* @param self Swap struct to read from
* @param tokenIndex which token will be withdrawn
* @param tokenAmount the amount to withdraw in the pools precision
* @return the d and the new y after withdrawing one token
*/
function calculateWithdrawOneTokenDY(
Swap storage self,
uint8 tokenIndex,
uint256 tokenAmount,
uint256 totalSupply
)
internal
view
returns (
uint256,
uint256,
uint256
)
{
// Get the current D, then solve the stableswap invariant
// y_i for D - tokenAmount
uint256[] memory xp = _xp(self);
require(tokenIndex < xp.length, "index out of range");
CalculateWithdrawOneTokenDYInfo memory v = CalculateWithdrawOneTokenDYInfo(0, 0, 0, 0, 0);
v.preciseA = _getAPrecise(self);
v.d0 = getD(xp, v.preciseA);
v.d1 = v.d0 - ((tokenAmount * v.d0) / totalSupply);
require(tokenAmount <= xp[tokenIndex], "exceeds available");
v.newY = getYD(v.preciseA, tokenIndex, xp, v.d1);
uint256[] memory xpReduced = new uint256[](xp.length);
v.feePerToken = _feePerToken(self.swapFee, xp.length);
// TODO: Set a length variable (at top) instead of reading xp.length on each loop.
uint256 len = xp.length;
for (uint256 i; i < len; ) {
uint256 xpi = xp[i];
// if i == tokenIndex, dxExpected = xp[i] * d1 / d0 - newY
// else dxExpected = xp[i] - (xp[i] * d1 / d0)
// xpReduced[i] -= dxExpected * fee / Constants.FEE_DENOMINATOR
xpReduced[i] =
xpi -
((((i == tokenIndex) ? ((xpi * v.d1) / v.d0 - v.newY) : (xpi - (xpi * v.d1) / v.d0)) * v.feePerToken) /
Constants.FEE_DENOMINATOR);
unchecked {
++i;
}
}
uint256 dy = xpReduced[tokenIndex] - getYD(v.preciseA, tokenIndex, xpReduced, v.d1);
dy = (dy - 1) / (self.tokenPrecisionMultipliers[tokenIndex]);
return (dy, v.newY, xp[tokenIndex]);
}
/**
* @notice Calculate the price of a token in the pool with given
* precision-adjusted balances and a particular D.
*
* @dev This is accomplished via solving the invariant iteratively.
* See the StableSwap paper and Curve.fi implementation for further details.
*
* x_1**2 + x1 * (sum' - (A*n**n - 1) * D / (A * n**n)) = D ** (n + 1) / (n ** (2 * n) * prod' * A)
* x_1**2 + b*x_1 = c
* x_1 = (x_1**2 + c) / (2*x_1 + b)
*
* @param a the amplification coefficient * n ** (n - 1). See the StableSwap paper for details.
* @param tokenIndex Index of token we are calculating for.
* @param xp a precision-adjusted set of pool balances. Array should be
* the same cardinality as the pool.
* @param d the stableswap invariant
* @return the price of the token, in the same precision as in xp
*/
function getYD(
uint256 a,
uint8 tokenIndex,
uint256[] memory xp,
uint256 d
) internal pure returns (uint256) {
uint256 numTokens = xp.length;
require(tokenIndex < numTokens, "Token not found");
uint256 c = d;
uint256 s;
uint256 nA = a * numTokens;
for (uint256 i; i < numTokens; ) {
if (i != tokenIndex) {
s += xp[i];
c = (c * d) / (xp[i] * numTokens);
// If we were to protect the division loss we would have to keep the denominator separate
// and divide at the end. However this leads to overflow with large numTokens or/and D.
// c = c * D * D * D * ... overflow!
}
unchecked {
++i;
}
}
c = (c * d * Constants.A_PRECISION) / (nA * numTokens);
uint256 b = s + ((d * Constants.A_PRECISION) / nA);
uint256 yPrev;
// Select d as the starting point of the Newton method. Because y < D
// D is the best option as the starting point in case the pool is very imbalanced.
uint256 y = d;
for (uint256 i; i < Constants.MAX_LOOP_LIMIT; ) {
yPrev = y;
y = ((y * y) + c) / ((y * 2) + b - d);
if (y.within1(yPrev)) {
return y;
}
unchecked {
++i;
}
}
revert("Approximation did not converge");
}
/**
* @notice Get D, the StableSwap invariant, based on a set of balances and a particular A.
* @param xp a precision-adjusted set of pool balances. Array should be the same cardinality
* as the pool.
* @param a the amplification coefficient * n ** (n - 1) in A_PRECISION.
* See the StableSwap paper for details
* @return the invariant, at the precision of the pool
*/
function getD(uint256[] memory xp, uint256 a) internal pure returns (uint256) {
uint256 numTokens = xp.length;
uint256 s;
for (uint256 i; i < numTokens; ) {
s += xp[i];
unchecked {
++i;
}
}
if (s == 0) {
return 0;
}
uint256 prevD;
uint256 d = s;
uint256 nA = a * numTokens;
for (uint256 i; i < Constants.MAX_LOOP_LIMIT; ) {
uint256 dP = d;
for (uint256 j; j < numTokens; ) {
dP = (dP * d) / (xp[j] * numTokens);
// If we were to protect the division loss we would have to keep the denominator separate
// and divide at the end. However this leads to overflow with large numTokens or/and D.
// dP = dP * D * D * D * ... overflow!
unchecked {
++j;
}
}
prevD = d;
d =
(((nA * s) / Constants.A_PRECISION + dP * numTokens) * d) /
((((nA - Constants.A_PRECISION) * d) / Constants.A_PRECISION + (numTokens + 1) * dP));
if (d.within1(prevD)) {
return d;
}
unchecked {
++i;
}
}
// Convergence should occur in 4 loops or less. If this is reached, there may be something wrong
// with the pool. If this were to occur repeatedly, LPs should withdraw via `removeLiquidity()`
// function which does not rely on D.
revert("D does not converge");
}
/**
* @notice Given a set of balances and precision multipliers, return the
* precision-adjusted balances.
*
* @param balances an array of token balances, in their native precisions.
* These should generally correspond with pooled tokens.
*
* @param precisionMultipliers an array of multipliers, corresponding to
* the amounts in the balances array. When multiplied together they
* should yield amounts at the pool's precision.
*
* @return an array of amounts "scaled" to the pool's precision
*/
function _xp(uint256[] memory balances, uint256[] memory precisionMultipliers)
internal
pure
returns (uint256[] memory)
{
uint256 numTokens = balances.length;
require(numTokens == precisionMultipliers.length, "mismatch multipliers");
uint256[] memory xp = new uint256[](numTokens);
for (uint256 i; i < numTokens; ) {
xp[i] = balances[i] * precisionMultipliers[i];
unchecked {
++i;
}
}
return xp;
}
/**
* @notice Return the precision-adjusted balances of all tokens in the pool
* @param self Swap struct to read from
* @return the pool balances "scaled" to the pool's precision, allowing
* them to be more easily compared.
*/
function _xp(Swap storage self) internal view returns (uint256[] memory) {
return _xp(self.balances, self.tokenPrecisionMultipliers);
}
/**
* @notice Get the virtual price, to help calculate profit
* @param self Swap struct to read from
* @return the virtual price, scaled to precision of Constants.POOL_PRECISION_DECIMALS
*/
function getVirtualPrice(Swap storage self) internal view returns (uint256) {
uint256 d = getD(_xp(self), _getAPrecise(self));
LPToken lpToken = self.lpToken;
uint256 supply = lpToken.totalSupply();
if (supply != 0) {
return (d * (10**uint256(Constants.POOL_PRECISION_DECIMALS))) / supply;
}
return 0;
}
/**
* @notice Calculate the new balances of the tokens given the indexes of the token
* that is swapped from (FROM) and the token that is swapped to (TO).
* This function is used as a helper function to calculate how much TO token
* the user should receive on swap.
*
* @param preciseA precise form of amplification coefficient
* @param tokenIndexFrom index of FROM token
* @param tokenIndexTo index of TO token
* @param x the new total amount of FROM token
* @param xp balances of the tokens in the pool
* @return the amount of TO token that should remain in the pool
*/
function getY(
uint256 preciseA,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 x,
uint256[] memory xp
) internal pure returns (uint256) {
uint256 numTokens = xp.length;
require(tokenIndexFrom != tokenIndexTo, "compare token to itself");
require(tokenIndexFrom < numTokens && tokenIndexTo < numTokens, "token not found");
uint256 d = getD(xp, preciseA);
uint256 c = d;
uint256 s;
uint256 nA = numTokens * preciseA;
uint256 _x;
for (uint256 i; i < numTokens; ) {
if (i == tokenIndexFrom) {
_x = x;
} else if (i != tokenIndexTo) {
_x = xp[i];
} else {
unchecked {
++i;
}
continue;
}
s += _x;
c = (c * d) / (_x * numTokens);
// If we were to protect the division loss we would have to keep the denominator separate
// and divide at the end. However this leads to overflow with large numTokens or/and D.
// c = c * D * D * D * ... overflow!
unchecked {
++i;
}
}
c = (c * d * Constants.A_PRECISION) / (nA * numTokens);
uint256 b = s + ((d * Constants.A_PRECISION) / nA);
uint256 yPrev;
uint256 y = d;
// iterative approximation
for (uint256 i; i < Constants.MAX_LOOP_LIMIT; ) {
yPrev = y;
y = ((y * y) + c) / ((y * 2) + b - d);
if (y.within1(yPrev)) {
return y;
}
unchecked {
++i;
}
}
revert("Approximation did not converge");
}
/**
* @notice Externally calculates a swap between two tokens.
* @param self Swap struct to read from
* @param tokenIndexFrom the token to sell
* @param tokenIndexTo the token to buy
* @param dx the number of tokens to sell. If the token charges a fee on transfers,
* use the amount that gets transferred after the fee.
* @return dy the number of tokens the user will get
*/
function calculateSwap(
Swap storage self,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dx
) internal view returns (uint256 dy) {
(dy, ) = _calculateSwap(self, tokenIndexFrom, tokenIndexTo, dx, self.balances);
}
/**
* @notice Externally calculates a swap between two tokens.
* @param self Swap struct to read from
* @param tokenIndexFrom the token to sell
* @param tokenIndexTo the token to buy
* @param dy the number of tokens to buy.
* @return dx the number of tokens the user have to transfer + fee
*/
function calculateSwapInv(
Swap storage self,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dy
) internal view returns (uint256 dx) {
(dx, ) = _calculateSwapInv(self, tokenIndexFrom, tokenIndexTo, dy, self.balances);
}
/**
* @notice Internally calculates a swap between two tokens.
*
* @dev The caller is expected to transfer the actual amounts (dx and dy)
* using the token contracts.
*
* @param self Swap struct to read from
* @param tokenIndexFrom the token to sell
* @param tokenIndexTo the token to buy
* @param dx the number of tokens to sell. If the token charges a fee on transfers,
* use the amount that gets transferred after the fee.
* @return dy the number of tokens the user will get in the token's precision. ex WBTC -> 8
* @return dyFee the associated fee in multiplied precision (Constants.POOL_PRECISION_DECIMALS)
*/
function _calculateSwap(
Swap storage self,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dx,
uint256[] memory balances
) internal view returns (uint256 dy, uint256 dyFee) {
uint256[] memory multipliers = self.tokenPrecisionMultipliers;
uint256[] memory xp = _xp(balances, multipliers);
require(tokenIndexFrom < xp.length && tokenIndexTo < xp.length, "index out of range");
uint256 x = dx * multipliers[tokenIndexFrom] + xp[tokenIndexFrom];
uint256 y = getY(_getAPrecise(self), tokenIndexFrom, tokenIndexTo, x, xp);
dy = xp[tokenIndexTo] - y - 1;
dyFee = (dy * self.swapFee) / Constants.FEE_DENOMINATOR;
dy = (dy - dyFee) / multipliers[tokenIndexTo];
}
/**
* @notice Internally calculates a swap between two tokens.
*
* @dev The caller is expected to transfer the actual amounts (dx and dy)
* using the token contracts.
*
* @param self Swap struct to read from
* @param tokenIndexFrom the token to sell
* @param tokenIndexTo the token to buy
* @param dy the number of tokens to buy. If the token charges a fee on transfers,
* use the amount that gets transferred after the fee.
* @return dx the number of tokens the user have to deposit in the token's precision. ex WBTC -> 8
* @return dxFee the associated fee in multiplied precision (Constants.POOL_PRECISION_DECIMALS)
*/
function _calculateSwapInv(
Swap storage self,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dy,
uint256[] memory balances
) internal view returns (uint256 dx, uint256 dxFee) {
require(tokenIndexFrom != tokenIndexTo, "compare token to itself");
uint256[] memory multipliers = self.tokenPrecisionMultipliers;
uint256[] memory xp = _xp(balances, multipliers);
require(tokenIndexFrom < xp.length && tokenIndexTo < xp.length, "index out of range");
uint256 a = _getAPrecise(self);
uint256 d0 = getD(xp, a);
xp[tokenIndexTo] = xp[tokenIndexTo] - (dy * multipliers[tokenIndexTo]);
uint256 x = getYD(a, tokenIndexFrom, xp, d0);
dx = (x + 1) - xp[tokenIndexFrom];
dxFee = (dx * self.swapFee) / Constants.FEE_DENOMINATOR;
dx = (dx + dxFee) / multipliers[tokenIndexFrom];
}
/**
* @notice A simple method to calculate amount of each underlying
* tokens that is returned upon burning given amount of
* LP tokens
*
* @param amount the amount of LP tokens that would to be burned on
* withdrawal
* @return array of amounts of tokens user will receive
*/
function calculateRemoveLiquidity(Swap storage self, uint256 amount) internal view returns (uint256[] memory) {
return _calculateRemoveLiquidity(self.balances, amount, self.lpToken.totalSupply());
}
function _calculateRemoveLiquidity(
uint256[] memory balances,
uint256 amount,
uint256 totalSupply
) internal pure returns (uint256[] memory) {
require(amount <= totalSupply, "exceed total supply");
uint256 numBalances = balances.length;
uint256[] memory amounts = new uint256[](numBalances);
for (uint256 i; i < numBalances; ) {
amounts[i] = (balances[i] * amount) / totalSupply;
unchecked {
++i;
}
}
return amounts;
}
/**
* @notice A simple method to calculate prices from deposits or
* withdrawals, excluding fees but including slippage. This is
* helpful as an input into the various "min" parameters on calls
* to fight front-running
*
* @dev This shouldn't be used outside frontends for user estimates.
*
* @param self Swap struct to read from
* @param amounts an array of token amounts to deposit or withdrawal,
* corresponding to pooledTokens. The amount should be in each
* pooled token's native precision. If a token charges a fee on transfers,
* use the amount that gets transferred after the fee.
* @param deposit whether this is a deposit or a withdrawal
* @return if deposit was true, total amount of lp token that will be minted and if
* deposit was false, total amount of lp token that will be burned
*/
function calculateTokenAmount(
Swap storage self,
uint256[] calldata amounts,
bool deposit
) internal view returns (uint256) {
uint256[] memory balances = self.balances;
uint256 numBalances = balances.length;
require(amounts.length == numBalances, "invalid length of amounts");
uint256 a = _getAPrecise(self);
uint256[] memory multipliers = self.tokenPrecisionMultipliers;
uint256 d0 = getD(_xp(balances, multipliers), a);
for (uint256 i; i < numBalances; ) {
if (deposit) {
balances[i] = balances[i] + amounts[i];
} else {
balances[i] = balances[i] - amounts[i];
}
unchecked {
++i;
}
}
uint256 d1 = getD(_xp(balances, multipliers), a);
uint256 totalSupply = self.lpToken.totalSupply();
if (deposit) {
return ((d1 - d0) * totalSupply) / d0;
} else {
return ((d0 - d1) * totalSupply) / d0;
}
}
/**
* @notice return accumulated amount of admin fees of the token with given index
* @param self Swap struct to read from
* @param index Index of the pooled token
* @return admin balance in the token's precision
*/
function getAdminBalance(Swap storage self, uint256 index) internal view returns (uint256) {
require(index < self.pooledTokens.length, "index out of range");
return self.adminFees[index];
}
/**
* @notice internal helper function to calculate fee per token multiplier used in
* swap fee calculations
* @param swapFee swap fee for the tokens
* @param numTokens number of tokens pooled
*/
function _feePerToken(uint256 swapFee, uint256 numTokens) internal pure returns (uint256) {
return (swapFee * numTokens) / ((numTokens - 1) * 4);
}
/*** STATE MODIFYING FUNCTIONS ***/
/**
* @notice swap two tokens in the pool
* @param self Swap struct to read from and write to
* @param tokenIndexFrom the token the user wants to sell
* @param tokenIndexTo the token the user wants to buy
* @param dx the amount of tokens the user wants to sell
* @param minDy the min amount the user would like to receive, or revert.
* @return amount of token user received on swap
*/
function swap(
Swap storage self,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dx,
uint256 minDy
) internal returns (uint256) {
require(!self.disabled, "disabled pool");
{
IERC20 tokenFrom = self.pooledTokens[tokenIndexFrom];
require(dx <= tokenFrom.balanceOf(msg.sender), "swap more than you own");
// Reverts for fee on transfer
AssetLogic.handleIncomingAsset(address(tokenFrom), dx);
}
uint256 dy;
uint256 dyFee;
uint256[] memory balances = self.balances;
(dy, dyFee) = _calculateSwap(self, tokenIndexFrom, tokenIndexTo, dx, balances);
require(dy >= minDy, "dy < minDy");
uint256 dyAdminFee = (dyFee * self.adminFee) /
Constants.FEE_DENOMINATOR /
self.tokenPrecisionMultipliers[tokenIndexTo];
self.balances[tokenIndexFrom] = balances[tokenIndexFrom] + dx;
self.balances[tokenIndexTo] = balances[tokenIndexTo] - dy - dyAdminFee;
if (dyAdminFee != 0) {
self.adminFees[tokenIndexTo] = self.adminFees[tokenIndexTo] + dyAdminFee;
}
AssetLogic.handleOutgoingAsset(address(self.pooledTokens[tokenIndexTo]), msg.sender, dy);
emit TokenSwap(self.key, msg.sender, dx, dy, tokenIndexFrom, tokenIndexTo);
return dy;
}
/**
* @notice swap two tokens in the pool
* @param self Swap struct to read from and write to
* @param tokenIndexFrom the token the user wants to sell
* @param tokenIndexTo the token the user wants to buy
* @param dy the amount of tokens the user wants to buy
* @param maxDx the max amount the user would like to send.
* @return amount of token user have to transfer on swap
*/
function swapOut(
Swap storage self,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dy,
uint256 maxDx
) internal returns (uint256) {
require(!self.disabled, "disabled pool");
require(dy <= self.balances[tokenIndexTo], ">pool balance");
uint256 dx;
uint256 dxFee;
uint256[] memory balances = self.balances;
(dx, dxFee) = _calculateSwapInv(self, tokenIndexFrom, tokenIndexTo, dy, balances);
require(dx <= maxDx, "dx > maxDx");
uint256 dxAdminFee = (dxFee * self.adminFee) /
Constants.FEE_DENOMINATOR /
self.tokenPrecisionMultipliers[tokenIndexFrom];
self.balances[tokenIndexFrom] = balances[tokenIndexFrom] + dx - dxAdminFee;
self.balances[tokenIndexTo] = balances[tokenIndexTo] - dy;
if (dxAdminFee != 0) {
self.adminFees[tokenIndexFrom] = self.adminFees[tokenIndexFrom] + dxAdminFee;
}
{
IERC20 tokenFrom = self.pooledTokens[tokenIndexFrom];
require(dx <= tokenFrom.balanceOf(msg.sender), "more than you own");
// Reverts for fee on transfer
AssetLogic.handleIncomingAsset(address(tokenFrom), dx);
}
AssetLogic.handleOutgoingAsset(address(self.pooledTokens[tokenIndexTo]), msg.sender, dy);
emit TokenSwap(self.key, msg.sender, dx, dy, tokenIndexFrom, tokenIndexTo);
return dx;
}
/**
* @notice swap two tokens in the pool internally
* @param self Swap struct to read from and write to
* @param tokenIndexFrom the token the user wants to sell
* @param tokenIndexTo the token the user wants to buy
* @param dx the amount of tokens the user wants to sell
* @param minDy the min amount the user would like to receive, or revert.
* @return amount of token user received on swap
*/
function swapInternal(
Swap storage self,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dx,
uint256 minDy
) internal returns (uint256) {
require(!self.disabled, "disabled pool");
require(dx <= self.balances[tokenIndexFrom], "more than pool balance");
uint256 dy;
uint256 dyFee;
uint256[] memory balances = self.balances;
(dy, dyFee) = _calculateSwap(self, tokenIndexFrom, tokenIndexTo, dx, balances);
require(dy >= minDy, "dy < minDy");
uint256 dyAdminFee = (dyFee * self.adminFee) /
Constants.FEE_DENOMINATOR /
self.tokenPrecisionMultipliers[tokenIndexTo];
self.balances[tokenIndexFrom] = balances[tokenIndexFrom] + dx;
self.balances[tokenIndexTo] = balances[tokenIndexTo] - dy - dyAdminFee;
if (dyAdminFee != 0) {
self.adminFees[tokenIndexTo] = self.adminFees[tokenIndexTo] + dyAdminFee;
}
emit TokenSwap(self.key, msg.sender, dx, dy, tokenIndexFrom, tokenIndexTo);
return dy;
}
/**
* @notice Should get exact amount out of AMM for asset put in
*/
function swapInternalOut(
Swap storage self,
uint8 tokenIndexFrom,
uint8 tokenIndexTo,
uint256 dy,
uint256 maxDx
) internal returns (uint256) {
require(!self.disabled, "disabled pool");
require(dy <= self.balances[tokenIndexTo], "more than pool balance");
uint256 dx;
uint256 dxFee;
uint256[] memory balances = self.balances;
(dx, dxFee) = _calculateSwapInv(self, tokenIndexFrom, tokenIndexTo, dy, balances);
require(dx <= maxDx, "dx > maxDx");
uint256 dxAdminFee = (dxFee * self.adminFee) /
Constants.FEE_DENOMINATOR /
self.tokenPrecisionMultipliers[tokenIndexFrom];
self.balances[tokenIndexFrom] = balances[tokenIndexFrom] + dx - dxAdminFee;
self.balances[tokenIndexTo] = balances[tokenIndexTo] - dy;
if (dxAdminFee != 0) {
self.adminFees[tokenIndexFrom] = self.adminFees[tokenIndexFrom] + dxAdminFee;
}
emit TokenSwap(self.key, msg.sender, dx, dy, tokenIndexFrom, tokenIndexTo);
return dx;
}
/**
* @notice Add liquidity to the pool
* @param self Swap struct to read from and write to
* @param amounts the amounts of each token to add, in their native precision
* @param minToMint the minimum LP tokens adding this amount of liquidity
* should mint, otherwise revert. Handy for front-running mitigation
* allowed addresses. If the pool is not in the guarded launch phase, this parameter will be ignored.
* @return amount of LP token user received
*/
function addLiquidity(
Swap storage self,
uint256[] memory amounts,
uint256 minToMint
) internal returns (uint256) {
require(!self.disabled, "disabled pool");
uint256 numTokens = self.pooledTokens.length;
require(amounts.length == numTokens, "mismatch pooled tokens");
// current state
ManageLiquidityInfo memory v = ManageLiquidityInfo(
0,
0,
0,
_getAPrecise(self),
self.lpToken,
0,
self.balances,
self.tokenPrecisionMultipliers
);
v.totalSupply = v.lpToken.totalSupply();
if (v.totalSupply != 0) {
v.d0 = getD(_xp(v.balances, v.multipliers), v.preciseA);
}
uint256[] memory newBalances = new uint256[](numTokens);
for (uint256 i; i < numTokens; ) {
require(v.totalSupply != 0 || amounts[i] != 0, "!supply all tokens");
// Transfer tokens first to see if a fee was charged on transfer
if (amounts[i] != 0) {
IERC20 token = self.pooledTokens[i];
// Reverts for fee on transfer
AssetLogic.handleIncomingAsset(address(token), amounts[i]);
}
newBalances[i] = v.balances[i] + amounts[i];
unchecked {
++i;
}
}
// invariant after change
v.d1 = getD(_xp(newBalances, v.multipliers), v.preciseA);
require(v.d1 > v.d0, "D should increase");
// updated to reflect fees and calculate the user's LP tokens
v.d2 = v.d1;
uint256[] memory fees = new uint256[](numTokens);
if (v.totalSupply != 0) {
uint256 feePerToken = _feePerToken(self.swapFee, numTokens);
for (uint256 i; i < numTokens; ) {
uint256 idealBalance = (v.d1 * v.balances[i]) / v.d0;
fees[i] = (feePerToken * (idealBalance.difference(newBalances[i]))) / Constants.FEE_DENOMINATOR;
uint256 adminFee = (fees[i] * self.adminFee) / Constants.FEE_DENOMINATOR;
self.balances[i] = newBalances[i] - adminFee;
self.adminFees[i] = self.adminFees[i] + adminFee;
newBalances[i] = newBalances[i] - fees[i];
unchecked {
++i;
}
}
v.d2 = getD(_xp(newBalances, v.multipliers), v.preciseA);
} else {
// the initial depositor doesn't pay fees
self.balances = newBalances;
}
uint256 toMint;
if (v.totalSupply == 0) {
toMint = v.d1;
} else {
toMint = ((v.d2 - v.d0) * v.totalSupply) / v.d0;
}
require(toMint >= minToMint, "mint < min");
// mint the user's LP tokens
v.lpToken.mint(msg.sender, toMint);
emit AddLiquidity(self.key, msg.sender, amounts, fees, v.d1, v.totalSupply + toMint);
return toMint;
}
/**
* @notice Burn LP tokens to remove liquidity from the pool.
* @dev Liquidity can always be removed, even when the pool is paused.
* @param self Swap struct to read from and write to
* @param amount the amount of LP tokens to burn
* @param minAmounts the minimum amounts of each token in the pool
* acceptable for this burn. Useful as a front-running mitigation
* @return amounts of tokens the user received
*/
function removeLiquidity(
Swap storage self,
uint256 amount,
uint256[] calldata minAmounts
) internal returns (uint256[] memory) {
LPToken lpToken = self.lpToken;
require(amount <= lpToken.balanceOf(msg.sender), ">LP.balanceOf");
uint256 numTokens = self.pooledTokens.length;
require(minAmounts.length == numTokens, "mismatch poolTokens");
uint256[] memory balances = self.balances;
uint256 totalSupply = lpToken.totalSupply();
uint256[] memory amounts = _calculateRemoveLiquidity(balances, amount, totalSupply);
uint256 numAmounts = amounts.length;
for (uint256 i; i < numAmounts; ) {
require(amounts[i] >= minAmounts[i], "amounts[i] < minAmounts[i]");
self.balances[i] = balances[i] - amounts[i];
AssetLogic.handleOutgoingAsset(address(self.pooledTokens[i]), msg.sender, amounts[i]);
unchecked {
++i;
}
}
lpToken.burnFrom(msg.sender, amount);
emit RemoveLiquidity(self.key, msg.sender, amounts, totalSupply - amount);
return amounts;
}
/**
* @notice Remove liquidity from the pool all in one token.
* @param self Swap struct to read from and write to
* @param tokenAmount the amount of the lp tokens to burn
* @param tokenIndex the index of the token you want to receive
* @param minAmount the minimum amount to withdraw, otherwise revert
* @return amount chosen token that user received
*/
function removeLiquidityOneToken(
Swap storage self,
uint256 tokenAmount,
uint8 tokenIndex,
uint256 minAmount
) internal returns (uint256) {
LPToken lpToken = self.lpToken;
require(tokenAmount <= lpToken.balanceOf(msg.sender), ">LP.balanceOf");
uint256 numTokens = self.pooledTokens.length;
require(tokenIndex < numTokens, "not found");
uint256 totalSupply = lpToken.totalSupply();
(uint256 dy, uint256 dyFee) = _calculateWithdrawOneToken(self, tokenAmount, tokenIndex, totalSupply);
require(dy >= minAmount, "dy < minAmount");
uint256 adminFee = (dyFee * self.adminFee) / Constants.FEE_DENOMINATOR;
self.balances[tokenIndex] = self.balances[tokenIndex] - (dy + adminFee);
if (adminFee != 0) {
self.adminFees[tokenIndex] = self.adminFees[tokenIndex] + adminFee;
}
lpToken.burnFrom(msg.sender, tokenAmount);
AssetLogic.handleOutgoingAsset(address(self.pooledTokens[tokenIndex]), msg.sender, dy);
emit RemoveLiquidityOne(self.key, msg.sender, tokenAmount, totalSupply, tokenIndex, dy);
return dy;
}
/**
* @notice Remove liquidity from the pool, weighted differently than the
* pool's current balances.
*
* @param self Swap struct to read from and write to
* @param amounts how much of each token to withdraw
* @param maxBurnAmount the max LP token provider is willing to pay to
* remove liquidity. Useful as a front-running mitigation.
* @return actual amount of LP tokens burned in the withdrawal
*/
function removeLiquidityImbalance(
Swap storage self,
uint256[] memory amounts,
uint256 maxBurnAmount
) internal returns (uint256) {
ManageLiquidityInfo memory v = ManageLiquidityInfo(
0,
0,
0,
_getAPrecise(self),
self.lpToken,
0,
self.balances,
self.tokenPrecisionMultipliers
);
v.totalSupply = v.lpToken.totalSupply();
uint256 numTokens = self.pooledTokens.length;
uint256 numAmounts = amounts.length;
require(numAmounts == numTokens, "mismatch pool tokens");
require(maxBurnAmount <= v.lpToken.balanceOf(msg.sender) && maxBurnAmount != 0, ">LP.balanceOf");
uint256 feePerToken = _feePerToken(self.swapFee, numTokens);
uint256[] memory fees = new uint256[](numTokens);
{
uint256[] memory balances1 = new uint256[](numTokens);
v.d0 = getD(_xp(v.balances, v.multipliers), v.preciseA);
for (uint256 i; i < numTokens; ) {
require(v.balances[i] >= amounts[i], "withdraw more than available");
unchecked {
balances1[i] = v.balances[i] - amounts[i];
++i;
}
}
v.d1 = getD(_xp(balances1, v.multipliers), v.preciseA);
for (uint256 i; i < numTokens; ) {
{
uint256 idealBalance = (v.d1 * v.balances[i]) / v.d0;
uint256 difference = idealBalance.difference(balances1[i]);
fees[i] = (feePerToken * difference) / Constants.FEE_DENOMINATOR;
}
uint256 adminFee = (fees[i] * self.adminFee) / Constants.FEE_DENOMINATOR;
self.balances[i] = balances1[i] - adminFee;
self.adminFees[i] = self.adminFees[i] + adminFee;
balances1[i] = balances1[i] - fees[i];
unchecked {
++i;
}
}
v.d2 = getD(_xp(balances1, v.multipliers), v.preciseA);
}
uint256 tokenAmount = ((v.d0 - v.d2) * v.totalSupply) / v.d0;
require(tokenAmount != 0, "!zero amount");
tokenAmount = tokenAmount + 1;
require(tokenAmount <= maxBurnAmount, "tokenAmount > maxBurnAmount");
v.lpToken.burnFrom(msg.sender, tokenAmount);
for (uint256 i; i < numTokens; ) {
AssetLogic.handleOutgoingAsset(address(self.pooledTokens[i]), msg.sender, amounts[i]);
unchecked {
++i;
}
}
emit RemoveLiquidityImbalance(self.key, msg.sender, amounts, fees, v.d1, v.totalSupply - tokenAmount);
return tokenAmount;
}
/**
* @notice withdraw all admin fees to a given address
* @param self Swap struct to withdraw fees from
* @param to Address to send the fees to
*/
function withdrawAdminFees(Swap storage self, address to) internal {
uint256 numTokens = self.pooledTokens.length;
for (uint256 i; i < numTokens; ) {
IERC20 token = self.pooledTokens[i];
uint256 balance = self.adminFees[i];
if (balance != 0) {
delete self.adminFees[i];
AssetLogic.handleOutgoingAsset(address(token), to, balance);
}
unchecked {
++i;
}
}
}
/**
* @notice Sets the admin fee
* @dev adminFee cannot be higher than 100% of the swap fee
* @param self Swap struct to update
* @param newAdminFee new admin fee to be applied on future transactions
*/
function setAdminFee(Swap storage self, uint256 newAdminFee) internal {
require(newAdminFee < Constants.MAX_ADMIN_FEE + 1, "too high");
self.adminFee = newAdminFee;
emit NewAdminFee(self.key, newAdminFee);
}
/**
* @notice update the swap fee
* @dev fee cannot be higher than 1% of each swap
* @param self Swap struct to update
* @param newSwapFee new swap fee to be applied on future transactions
*/
function setSwapFee(Swap storage self, uint256 newSwapFee) internal {
require(newSwapFee < Constants.MAX_SWAP_FEE + 1, "too high");
self.swapFee = newSwapFee;
emit NewSwapFee(self.key, newSwapFee);
}
/**
* @notice Check if this stableswap pool exists and is valid (i.e. has been
* initialized and tokens have been added).
* @return bool true if this stableswap pool is valid, false if not.
*/
function exists(Swap storage self) internal view returns (bool) {
return !self.disabled && self.pooledTokens.length != 0;
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
// ============= Structs =============
// Tokens are identified by a TokenId:
// domain - 4 byte chain ID of the chain from which the token originates
// id - 32 byte identifier of the token address on the origin chain, in that chain's address format
struct TokenId {
uint32 domain;
bytes32 id;
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
import {IOutbox} from "./IOutbox.sol";
/**
* @notice Each router extends the `XAppConnectionClient` contract. This contract
* allows an admin to call `setXAppConnectionManager` to update the underlying
* pointers to the messaging inboxes (Replicas) and outboxes (Homes).
*
* @dev This interface only contains the functions needed for the `XAppConnectionClient`
* will interface with.
*/
interface IConnectorManager {
/**
* @notice Get the local inbox contract from the xAppConnectionManager
* @return The local inbox contract
* @dev The local inbox contract is a SpokeConnector with AMBs, and a
* Home contract with nomad
*/
function home() external view returns (IOutbox);
/**
* @notice Determine whether _potentialReplica is an enrolled Replica from the xAppConnectionManager
* @return True if _potentialReplica is an enrolled Replica
*/
function isReplica(address _potentialReplica) external view returns (bool);
/**
* @notice Get the local domain from the xAppConnectionManager
* @return The local domain
*/
function localDomain() external view returns (uint32);
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
/**
* @notice Interface for all contracts sending messages originating on their
* current domain.
*
* @dev These are the Home.sol interface methods used by the `Router`
* and exposed via `home()` on the `XAppConnectionClient`
*/
interface IOutbox {
/**
* @notice Emitted when a new message is added to an outbound message merkle root
* @param leafIndex Index of message's leaf in merkle tree
* @param destinationAndNonce Destination and destination-specific
* nonce combined in single field ((destination << 32) & nonce)
* @param messageHash Hash of message; the leaf inserted to the Merkle tree for the message
* @param committedRoot the latest notarized root submitted in the last signed Update
* @param message Raw bytes of message
*/
event Dispatch(
bytes32 indexed messageHash,
uint256 indexed leafIndex,
uint64 indexed destinationAndNonce,
bytes32 committedRoot,
bytes message
);
/**
* @notice Dispatch the message it to the destination domain & recipient
* @dev Format the message, insert its hash into Merkle tree,
* enqueue the new Merkle root, and emit `Dispatch` event with message information.
* @param _destinationDomain Domain of destination chain
* @param _recipientAddress Address of recipient on destination chain as bytes32
* @param _messageBody Raw bytes content of message
* @return bytes32 The leaf added to the tree
*/
function dispatch(
uint32 _destinationDomain,
bytes32 _recipientAddress,
bytes memory _messageBody
) external returns (bytes32, bytes memory);
/**
* @notice domain => next available nonce for the domain.
*/
function nonces(uint32 domain) external returns (uint32);
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
// Taken from: https://github.com/nomad-xyz/ExcessivelySafeCall
// NOTE: There is a difference between npm latest and github main versions
// where the latest github version allows you to specify an ether value.
library ExcessivelySafeCall {
uint256 constant LOW_28_MASK = 0x00000000ffffffffffffffffffffffffffffffffffffffffffffffffffffffff;
/// @notice Use when you _really_ really _really_ don't trust the called
/// contract. This prevents the called contract from causing reversion of
/// the caller in as many ways as we can.
/// @dev The main difference between this and a solidity low-level call is
/// that we limit the number of bytes that the callee can cause to be
/// copied to caller memory. This prevents stupid things like malicious
/// contracts returning 10,000,000 bytes causing a local OOG when copying
/// to memory.
/// @param _target The address to call
/// @param _gas The amount of gas to forward to the remote contract
/// @param _value The value in wei to send to the remote contract
/// @param _maxCopy The maximum number of bytes of returndata to copy
/// to memory.
/// @param _calldata The data to send to the remote contract
/// @return success and returndata, as `.call()`. Returndata is capped to
/// `_maxCopy` bytes.
function excessivelySafeCall(
address _target,
uint256 _gas,
uint256 _value,
uint16 _maxCopy,
bytes memory _calldata
) internal returns (bool, bytes memory) {
// set up for assembly call
uint256 _toCopy;
bool _success;
bytes memory _returnData = new bytes(_maxCopy);
// dispatch message to recipient
// by assembly calling "handle" function
// we call via assembly to avoid memcopying a very large returndata
// returned by a malicious contract
assembly {
_success := call(
_gas, // gas
_target, // recipient
_value, // ether value
add(_calldata, 0x20), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
// limit our copy to 256 bytes
_toCopy := returndatasize()
if gt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytes
mstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]
returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}
/// @notice Use when you _really_ really _really_ don't trust the called
/// contract. This prevents the called contract from causing reversion of
/// the caller in as many ways as we can.
/// @dev The main difference between this and a solidity low-level call is
/// that we limit the number of bytes that the callee can cause to be
/// copied to caller memory. This prevents stupid things like malicious
/// contracts returning 10,000,000 bytes causing a local OOG when copying
/// to memory.
/// @param _target The address to call
/// @param _gas The amount of gas to forward to the remote contract
/// @param _maxCopy The maximum number of bytes of returndata to copy
/// to memory.
/// @param _calldata The data to send to the remote contract
/// @return success and returndata, as `.call()`. Returndata is capped to
/// `_maxCopy` bytes.
function excessivelySafeStaticCall(
address _target,
uint256 _gas,
uint16 _maxCopy,
bytes memory _calldata
) internal view returns (bool, bytes memory) {
// set up for assembly call
uint256 _toCopy;
bool _success;
bytes memory _returnData = new bytes(_maxCopy);
// dispatch message to recipient
// by assembly calling "handle" function
// we call via assembly to avoid memcopying a very large returndata
// returned by a malicious contract
assembly {
_success := staticcall(
_gas, // gas
_target, // recipient
add(_calldata, 0x20), // inloc
mload(_calldata), // inlen
0, // outloc
0 // outlen
)
// limit our copy to 256 bytes
_toCopy := returndatasize()
if gt(_toCopy, _maxCopy) {
_toCopy := _maxCopy
}
// Store the length of the copied bytes
mstore(_returnData, _toCopy)
// copy the bytes from returndata[0:_toCopy]
returndatacopy(add(_returnData, 0x20), 0, _toCopy)
}
return (_success, _returnData);
}
/**
* @notice Swaps function selectors in encoded contract calls
* @dev Allows reuse of encoded calldata for functions with identical
* argument types but different names. It simply swaps out the first 4 bytes
* for the new selector. This function modifies memory in place, and should
* only be used with caution.
* @param _newSelector The new 4-byte selector
* @param _buf The encoded contract args
*/
function swapSelector(bytes4 _newSelector, bytes memory _buf) internal pure {
require(_buf.length > 4 - 1);
uint256 _mask = LOW_28_MASK;
assembly {
// load the first word of
let _word := mload(add(_buf, 0x20))
// mask out the top 4 bytes
// /x
_word := and(_word, _mask)
_word := or(_newSelector, _word)
mstore(add(_buf, 0x20), _word)
}
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
import {TypedMemView} from "./TypedMemView.sol";
library TypeCasts {
using TypedMemView for bytes;
using TypedMemView for bytes29;
// alignment preserving cast
function addressToBytes32(address _addr) internal pure returns (bytes32) {
return bytes32(uint256(uint160(_addr)));
}
// alignment preserving cast
function bytes32ToAddress(bytes32 _buf) internal pure returns (address) {
return address(uint160(uint256(_buf)));
}
}
// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity 0.8.17;
library TypedMemView {
// Why does this exist?
// the solidity `bytes memory` type has a few weaknesses.
// 1. You can't index ranges effectively
// 2. You can't slice without copying
// 3. The underlying data may represent any type
// 4. Solidity never deallocates memory, and memory costs grow
// superlinearly
// By using a memory view instead of a `bytes memory` we get the following
// advantages:
// 1. Slices are done on the stack, by manipulating the pointer
// 2. We can index arbitrary ranges and quickly convert them to stack types
// 3. We can insert type info into the pointer, and typecheck at runtime
// This makes `TypedMemView` a useful tool for efficient zero-copy
// algorithms.
// Why bytes29?
// We want to avoid confusion between views, digests, and other common
// types so we chose a large and uncommonly used odd number of bytes
//
// Note that while bytes are left-aligned in a word, integers and addresses
// are right-aligned. This means when working in assembly we have to
// account for the 3 unused bytes on the righthand side
//
// First 5 bytes are a type flag.
// - ff_ffff_fffe is reserved for unknown type.
// - ff_ffff_ffff is reserved for invalid types/errors.
// next 12 are memory address
// next 12 are len
// bottom 3 bytes are empty
// Assumptions:
// - non-modification of memory.
// - No Solidity updates
// - - wrt free mem point
// - - wrt bytes representation in memory
// - - wrt memory addressing in general
// Usage:
// - create type constants
// - use `assertType` for runtime type assertions
// - - unfortunately we can't do this at compile time yet :(
// - recommended: implement modifiers that perform type checking
// - - e.g.
// - - `uint40 constant MY_TYPE = 3;`
// - - ` modifer onlyMyType(bytes29 myView) { myView.assertType(MY_TYPE); }`
// - instantiate a typed view from a bytearray using `ref`
// - use `index` to inspect the contents of the view
// - use `slice` to create smaller views into the same memory
// - - `slice` can increase the offset
// - - `slice can decrease the length`
// - - must specify the output type of `slice`
// - - `slice` will return a null view if you try to overrun
// - - make sure to explicitly check for this with `notNull` or `assertType`
// - use `equal` for typed comparisons.
// The null view
bytes29 public constant NULL = hex"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffff";
uint256 constant LOW_12_MASK = 0xffffffffffffffffffffffff;
uint256 constant TWENTY_SEVEN_BYTES = 8 * 27;
uint256 private constant _27_BYTES_IN_BITS = 8 * 27; // <--- also used this named constant where ever 216 is used.
uint256 private constant LOW_27_BYTES_MASK = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffff; // (1 << _27_BYTES_IN_BITS) - 1;
// ========== Custom Errors ===========
error TypedMemView__assertType_typeAssertionFailed(uint256 actual, uint256 expected);
error TypedMemView__index_overrun(uint256 loc, uint256 len, uint256 index, uint256 slice);
error TypedMemView__index_indexMoreThan32Bytes();
error TypedMemView__unsafeCopyTo_nullPointer();
error TypedMemView__unsafeCopyTo_invalidPointer();
error TypedMemView__unsafeCopyTo_identityOOG();
error TypedMemView__assertValid_validityAssertionFailed();
/**
* @notice Changes the endianness of a uint256.
* @dev https://graphics.stanford.edu/~seander/bithacks.html#ReverseParallel
* @param _b The unsigned integer to reverse
* @return v - The reversed value
*/
function reverseUint256(uint256 _b) internal pure returns (uint256 v) {
v = _b;
// swap bytes
v =
((v >> 8) & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) |
((v & 0x00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF00FF) << 8);
// swap 2-byte long pairs
v =
((v >> 16) & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) |
((v & 0x0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF0000FFFF) << 16);
// swap 4-byte long pairs
v =
((v >> 32) & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) |
((v & 0x00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF00000000FFFFFFFF) << 32);
// swap 8-byte long pairs
v =
((v >> 64) & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) |
((v & 0x0000000000000000FFFFFFFFFFFFFFFF0000000000000000FFFFFFFFFFFFFFFF) << 64);
// swap 16-byte long pairs
v = (v >> 128) | (v << 128);
}
/**
* @notice Create a mask with the highest `_len` bits set.
* @param _len The length
* @return mask - The mask
*/
function leftMask(uint8 _len) private pure returns (uint256 mask) {
// ugly. redo without assembly?
assembly {
// solhint-disable-previous-line no-inline-assembly
mask := sar(sub(_len, 1), 0x8000000000000000000000000000000000000000000000000000000000000000)
}
}
/**
* @notice Return the null view.
* @return bytes29 - The null view
*/
function nullView() internal pure returns (bytes29) {
return NULL;
}
/**
* @notice Check if the view is null.
* @return bool - True if the view is null
*/
function isNull(bytes29 memView) internal pure returns (bool) {
return memView == NULL;
}
/**
* @notice Check if the view is not null.
* @return bool - True if the view is not null
*/
function notNull(bytes29 memView) internal pure returns (bool) {
return !isNull(memView);
}
/**
* @notice Check if the view is of a invalid type and points to a valid location
* in memory.
* @dev We perform this check by examining solidity's unallocated memory
* pointer and ensuring that the view's upper bound is less than that.
* @param memView The view
* @return ret - True if the view is invalid
*/
function isNotValid(bytes29 memView) internal pure returns (bool ret) {
if (typeOf(memView) == 0xffffffffff) {
return true;
}
uint256 _end = end(memView);
assembly {
// solhint-disable-previous-line no-inline-assembly
ret := gt(_end, mload(0x40))
}
}
/**
* @notice Require that a typed memory view be valid.
* @dev Returns the view for easy chaining.
* @param memView The view
* @return bytes29 - The validated view
*/
function assertValid(bytes29 memView) internal pure returns (bytes29) {
if (isNotValid(memView)) revert TypedMemView__assertValid_validityAssertionFailed();
return memView;
}
/**
* @notice Return true if the memview is of the expected type. Otherwise false.
* @param memView The view
* @param _expected The expected type
* @return bool - True if the memview is of the expected type
*/
function isType(bytes29 memView, uint40 _expected) internal pure returns (bool) {
return typeOf(memView) == _expected;
}
/**
* @notice Require that a typed memory view has a specific type.
* @dev Returns the view for easy chaining.
* @param memView The view
* @param _expected The expected type
* @return bytes29 - The view with validated type
*/
function assertType(bytes29 memView, uint40 _expected) internal pure returns (bytes29) {
if (!isType(memView, _expected)) {
revert TypedMemView__assertType_typeAssertionFailed(uint256(typeOf(memView)), uint256(_expected));
}
return memView;
}
/**
* @notice Return an identical view with a different type.
* @param memView The view
* @param _newType The new type
* @return newView - The new view with the specified type
*/
function castTo(bytes29 memView, uint40 _newType) internal pure returns (bytes29 newView) {
// then | in the new type
assembly {
// solhint-disable-previous-line no-inline-assembly
// shift off the top 5 bytes
newView := or(and(memView, LOW_27_BYTES_MASK), shl(_27_BYTES_IN_BITS, _newType))
}
}
/**
* @notice Unsafe raw pointer construction. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @dev Unsafe raw pointer construction. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @param _type The type
* @param _loc The memory address
* @param _len The length
* @return newView - The new view with the specified type, location and length
*/
function unsafeBuildUnchecked(
uint256 _type,
uint256 _loc,
uint256 _len
) private pure returns (bytes29 newView) {
uint256 _uint96Bits = 96;
uint256 _emptyBits = 24;
// Cast params to ensure input is of correct length
uint96 len_ = uint96(_len);
uint96 loc_ = uint96(_loc);
require(len_ == _len && loc_ == _loc, "!truncated");
assembly {
// solium-disable-previous-line security/no-inline-assembly
newView := shl(_uint96Bits, _type) // insert type
newView := shl(_uint96Bits, or(newView, loc_)) // insert loc
newView := shl(_emptyBits, or(newView, len_)) // empty bottom 3 bytes
}
}
/**
* @notice Instantiate a new memory view. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @dev Instantiate a new memory view. This should generally not be called
* directly. Prefer `ref` wherever possible.
* @param _type The type
* @param _loc The memory address
* @param _len The length
* @return newView - The new view with the specified type, location and length
*/
function build(
uint256 _type,
uint256 _loc,
uint256 _len
) internal pure returns (bytes29 newView) {
uint256 _end = _loc + _len;
assembly {
// solhint-disable-previous-line no-inline-assembly
if gt(_end, mload(0x40)) {
_end := 0
}
}
if (_end == 0) {
return NULL;
}
newView = unsafeBuildUnchecked(_type, _loc, _len);
}
/**
* @notice Instantiate a memory view from a byte array.
* @dev Note that due to Solidity memory representation, it is not possible to
* implement a deref, as the `bytes` type stores its len in memory.
* @param arr The byte array
* @param newType The type
* @return bytes29 - The memory view
*/
function ref(bytes memory arr, uint40 newType) internal pure returns (bytes29) {
uint256 _len = arr.length;
uint256 _loc;
assembly {
// solhint-disable-previous-line no-inline-assembly
_loc := add(arr, 0x20) // our view is of the data, not the struct
}
return build(newType, _loc, _len);
}
/**
* @notice Return the associated type information.
* @param memView The memory view
* @return _type - The type associated with the view
*/
function typeOf(bytes29 memView) internal pure returns (uint40 _type) {
assembly {
// solhint-disable-previous-line no-inline-assembly
// 216 == 256 - 40
_type := shr(_27_BYTES_IN_BITS, memView) // shift out lower 24 bytes
}
}
/**
* @notice Return the memory address of the underlying bytes.
* @param memView The view
* @return _loc - The memory address
*/
function loc(bytes29 memView) internal pure returns (uint96 _loc) {
uint256 _mask = LOW_12_MASK; // assembly can't use globals
assembly {
// solhint-disable-previous-line no-inline-assembly
// 120 bits = 12 bytes (the encoded loc) + 3 bytes (empty low space)
_loc := and(shr(120, memView), _mask)
}
}
/**
* @notice The number of memory words this memory view occupies, rounded up.
* @param memView The view
* @return uint256 - The number of memory words
*/
function words(bytes29 memView) internal pure returns (uint256) {
return (uint256(len(memView)) + 31) / 32;
}
/**
* @notice The in-memory footprint of a fresh copy of the view.
* @param memView The view
* @return uint256 - The in-memory footprint of a fresh copy of the view.
*/
function footprint(bytes29 memView) internal pure returns (uint256) {
return words(memView) * 32;
}
/**
* @notice The number of bytes of the view.
* @param memView The view
* @return _len - The length of the view
*/
function len(bytes29 memView) internal pure returns (uint96 _len) {
uint256 _mask = LOW_12_MASK; // assembly can't use globals
assembly {
// solhint-disable-previous-line no-inline-assembly
_len := and(shr(24, memView), _mask)
}
}
/**
* @notice Returns the endpoint of `memView`.
* @param memView The view
* @return uint256 - The endpoint of `memView`
*/
function end(bytes29 memView) internal pure returns (uint256) {
unchecked {
return loc(memView) + len(memView);
}
}
/**
* @notice Safe slicing without memory modification.
* @param memView The view
* @param _index The start index
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function slice(
bytes29 memView,
uint256 _index,
uint256 _len,
uint40 newType
) internal pure returns (bytes29) {
uint256 _loc = loc(memView);
// Ensure it doesn't overrun the view
if (_loc + _index + _len > end(memView)) {
return NULL;
}
_loc = _loc + _index;
return build(newType, _loc, _len);
}
/**
* @notice Shortcut to `slice`. Gets a view representing the first `_len` bytes.
* @param memView The view
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function prefix(
bytes29 memView,
uint256 _len,
uint40 newType
) internal pure returns (bytes29) {
return slice(memView, 0, _len, newType);
}
/**
* @notice Shortcut to `slice`. Gets a view representing the last `_len` byte.
* @param memView The view
* @param _len The length
* @param newType The new type
* @return bytes29 - The new view
*/
function postfix(
bytes29 memView,
uint256 _len,
uint40 newType
) internal pure returns (bytes29) {
return slice(memView, uint256(len(memView)) - _len, _len, newType);
}
/**
* @notice Load up to 32 bytes from the view onto the stack.
* @dev Returns a bytes32 with only the `_bytes` highest bytes set.
* This can be immediately cast to a smaller fixed-length byte array.
* To automatically cast to an integer, use `indexUint`.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The 32 byte result
*/
function index(
bytes29 memView,
uint256 _index,
uint8 _bytes
) internal pure returns (bytes32 result) {
if (_bytes == 0) {
return bytes32(0);
}
if (_index + _bytes > len(memView)) {
// "TypedMemView/index - Overran the view. Slice is at {loc} with length {len}. Attempted to index at offset {index} with length {slice},
revert TypedMemView__index_overrun(loc(memView), len(memView), _index, uint256(_bytes));
}
if (_bytes > 32) revert TypedMemView__index_indexMoreThan32Bytes();
uint8 bitLength;
unchecked {
bitLength = _bytes * 8;
}
uint256 _loc = loc(memView);
uint256 _mask = leftMask(bitLength);
assembly {
// solhint-disable-previous-line no-inline-assembly
result := and(mload(add(_loc, _index)), _mask)
}
}
/**
* @notice Parse an unsigned integer from the view at `_index`.
* @dev Requires that the view have >= `_bytes` bytes following that index.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The unsigned integer
*/
function indexUint(
bytes29 memView,
uint256 _index,
uint8 _bytes
) internal pure returns (uint256 result) {
return uint256(index(memView, _index, _bytes)) >> ((32 - _bytes) * 8);
}
/**
* @notice Parse an unsigned integer from LE bytes.
* @param memView The view
* @param _index The index
* @param _bytes The bytes
* @return result - The unsigned integer
*/
function indexLEUint(
bytes29 memView,
uint256 _index,
uint8 _bytes
) internal pure returns (uint256 result) {
return reverseUint256(uint256(index(memView, _index, _bytes)));
}
/**
* @notice Parse an address from the view at `_index`. Requires that the view have >= 20 bytes
* following that index.
* @param memView The view
* @param _index The index
* @return address - The address
*/
function indexAddress(bytes29 memView, uint256 _index) internal pure returns (address) {
return address(uint160(indexUint(memView, _index, 20)));
}
/**
* @notice Return the keccak256 hash of the underlying memory
* @param memView The view
* @return digest - The keccak256 hash of the underlying memory
*/
function keccak(bytes29 memView) internal pure returns (bytes32 digest) {
uint256 _loc = loc(memView);
uint256 _len = len(memView);
assembly {
// solhint-disable-previous-line no-inline-assembly
digest := keccak256(_loc, _len)
}
}
/**
* @notice Return true if the underlying memory is equal. Else false.
* @param left The first view
* @param right The second view
* @return bool - True if the underlying memory is equal
*/
function untypedEqual(bytes29 left, bytes29 right) internal pure returns (bool) {
return (loc(left) == loc(right) && len(left) == len(right)) || keccak(left) == keccak(right);
}
/**
* @notice Return false if the underlying memory is equal. Else true.
* @param left The first view
* @param right The second view
* @return bool - False if the underlying memory is equal
*/
function untypedNotEqual(bytes29 left, bytes29 right) internal pure returns (bool) {
return !untypedEqual(left, right);
}
/**
* @notice Compares type equality.
* @dev Shortcuts if the pointers are identical, otherwise compares type and digest.
* @param left The first view
* @param right The second view
* @return bool - True if the types are the same
*/
function equal(bytes29 left, bytes29 right) internal pure returns (bool) {
return left == right || (typeOf(left) == typeOf(right) && keccak(left) == keccak(right));
}
/**
* @notice Compares type inequality.
* @dev Shortcuts if the pointers are identical, otherwise compares type and digest.
* @param left The first view
* @param right The second view
* @return bool - True if the types are not the same
*/
function notEqual(bytes29 left, bytes29 right) internal pure returns (bool) {
return !equal(left, right);
}
/**
* @notice Copy the view to a location, return an unsafe memory reference
* @dev Super Dangerous direct memory access.
*
* This reference can be overwritten if anything else modifies memory (!!!).
* As such it MUST be consumed IMMEDIATELY.
* This function is private to prevent unsafe usage by callers.
* @param memView The view
* @param _newLoc The new location
* @return written - the unsafe memory reference
*/
function unsafeCopyTo(bytes29 memView, uint256 _newLoc) private view returns (bytes29 written) {
if (isNull(memView)) revert TypedMemView__unsafeCopyTo_nullPointer();
if (isNotValid(memView)) revert TypedMemView__unsafeCopyTo_invalidPointer();
uint256 _len = len(memView);
uint256 _oldLoc = loc(memView);
uint256 ptr;
bool res;
assembly {
// solhint-disable-previous-line no-inline-assembly
ptr := mload(0x40)
// revert if we're writing in occupied memory
if gt(ptr, _newLoc) {
revert(0x60, 0x20) // empty revert message
}
// use the identity precompile to copy
// guaranteed not to fail, so pop the success
res := staticcall(gas(), 4, _oldLoc, _len, _newLoc, _len)
}
if (!res) revert TypedMemView__unsafeCopyTo_identityOOG();
written = unsafeBuildUnchecked(typeOf(memView), _newLoc, _len);
}
/**
* @notice Copies the referenced memory to a new loc in memory, returning a `bytes` pointing to
* the new memory
* @dev Shortcuts if the pointers are identical, otherwise compares type and digest.
* @param memView The view
* @return ret - The view pointing to the new memory
*/
function clone(bytes29 memView) internal view returns (bytes memory ret) {
uint256 ptr;
uint256 _len = len(memView);
assembly {
// solhint-disable-previous-line no-inline-assembly
ptr := mload(0x40) // load unused memory pointer
ret := ptr
}
unchecked {
unsafeCopyTo(memView, ptr + 0x20);
}
assembly {
// solhint-disable-previous-line no-inline-assembly
mstore(0x40, add(add(ptr, _len), 0x20)) // write new unused pointer
mstore(ptr, _len) // write len of new array (in bytes)
}
}
/**
* @notice Join the views in memory, return an unsafe reference to the memory.
* @dev Super Dangerous direct memory access.
*
* This reference can be overwritten if anything else modifies memory (!!!).
* As such it MUST be consumed IMMEDIATELY.
* This function is private to prevent unsafe usage by callers.
* @param memViews The views
* @return unsafeView - The conjoined view pointing to the new memory
*/
function unsafeJoin(bytes29[] memory memViews, uint256 _location) private view returns (bytes29 unsafeView) {
assembly {
// solhint-disable-previous-line no-inline-assembly
let ptr := mload(0x40)
// revert if we're writing in occupied memory
if gt(ptr, _location) {
revert(0x60, 0x20) // empty revert message
}
}
uint256 _offset = 0;
uint256 _len = memViews.length;
for (uint256 i = 0; i < _len; ) {
bytes29 memView = memViews[i];
unchecked {
unsafeCopyTo(memView, _location + _offset);
_offset += len(memView);
++i;
}
}
unsafeView = unsafeBuildUnchecked(0, _location, _offset);
}
/**
* @notice Produce the keccak256 digest of the concatenated contents of multiple views.
* @param memViews The views
* @return bytes32 - The keccak256 digest
*/
function joinKeccak(bytes29[] memory memViews) internal view returns (bytes32) {
uint256 ptr;
assembly {
// solhint-disable-previous-line no-inline-assembly
ptr := mload(0x40) // load unused memory pointer
}
return keccak(unsafeJoin(memViews, ptr));
}
/**
* @notice copies all views, joins them into a new bytearray.
* @param memViews The views
* @return ret - The new byte array
*/
function join(bytes29[] memory memViews) internal view returns (bytes memory ret) {
uint256 ptr;
assembly {
// solhint-disable-previous-line no-inline-assembly
ptr := mload(0x40) // load unused memory pointer
}
bytes29 _newView;
unchecked {
_newView = unsafeJoin(memViews, ptr + 0x20);
}
uint256 _written = len(_newView);
uint256 _footprint = footprint(_newView);
assembly {
// solhint-disable-previous-line no-inline-assembly
// store the legnth
mstore(ptr, _written)
// new pointer is old + 0x20 + the footprint of the body
mstore(0x40, add(add(ptr, _footprint), 0x20))
ret := ptr
}
}
}