Overview
Max Total Supply
26,001.929951831050228311 ZCHF
Holders
33 (0.00%)
Transfers
-
1
Market
Price
$1.29 @ 0.000490 ETH (-0.84%)
Onchain Market Cap
-
Circulating Supply Market Cap
$29,003,177.00
Other Info
Token Contract (WITH 18 Decimals)
Loading...
Loading
Loading...
Loading
Loading...
Loading
Contract Name:
BridgedFrankencoin
Compiler Version
v0.8.24+commit.e11b9ed9
Optimization Enabled:
Yes with 200 runs
Other Settings:
paris EvmVersion
Contract Source Code (Solidity Standard Json-Input format)
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../erc20/CrossChainReference.sol";
import "../erc20/ERC20PermitLight.sol";
import "../equity/Equity.sol";
import "../equity/IGovernance.sol";
import "../equity/BridgedGovernance.sol";
import "./IBasicFrankencoin.sol";
/**
* @title Bridged Frankencoin ERC-20 Token
*
* Like its mainnet counterpart, it has the capability to add minting modules. This allows to
* potentially add similar collateralized minting methods as in the mainnet Frankencoin.
*
* Minting modules are extremely powerful. They can mint, move, and burn Frankencoins on arbitrary addresses.
* A typical minting module is the CCIP Token Manager that mints tokens as they arrive from other chains or
* burns them when sent away again.
*
* The bridged Frankencoin relies on the bridged governance module to veto bad proposals for new minters.
*
* System income (e.g. from proposal fees) is accumualted on the governance address (this could be any address,
* but on mainnet, it is also the governance module that contains the equity capital). Furthermore, the contract
* keeps track of accumulated costs (losses), for example when the Savings module pays out interests.
*
* The accumulated profit or loss should be synchronized back to mainnet from time to time using a CCIP
* message.
*/
contract BridgedFrankencoin is CrossChainReference, ERC20PermitLight, IBasicFrankencoin {
/**
* @notice Minimal fee and application period when suggesting a new minter.
*/
uint256 public constant MIN_FEE = 1000 * (10 ** 18);
uint256 public immutable MIN_APPLICATION_PERIOD; // for example 10 days
address public immutable BRIDGE_ACCOUNTING;
uint64 public immutable MAINNET_CHAIN_SELECTOR;
address public immutable CCIP_ADMIN;
/**
* @notice The contract that holds the reserve.
*/
IGovernance public immutable override reserve;
/**
* @notice Map of minters to approval time stamps. If the time stamp is in the past, the minter contract is allowed
* to mint Frankencoins.
*/
mapping(address minter => uint256 validityStart) public minters;
/**
* @notice List of positions that are allowed to mint and the minter that registered them.
*
* This is not used in the bridged Frankencoins for now, but can be useful once we want to introduce collateralized
* minting like on mainnet.
*/
mapping(address position => address registeringMinter) public positions;
uint256 public accruedLoss;
bool public initialized;
event AccountingSynchronized(uint256 profit, uint256 losses);
event MinterApplied(address indexed minter, uint256 applicationPeriod, uint256 applicationFee, string message);
event MinterDenied(address indexed minter, string message);
event Loss(address indexed reportingMinter, uint256 amount);
event Profit(address indexed reportingMinter, uint256 amount);
error PeriodTooShort();
error FeeTooLow();
error AlreadyRegistered();
error NotMinter();
error TooLate();
error AlreadyInitialized();
error InvalidInput();
modifier minterOnly() {
if (!isMinter(msg.sender) && !isMinter(positions[msg.sender])) revert NotMinter();
_;
}
/**
* @notice Initiates the Frankencoin with the provided minimum application period for new plugins
* in seconds, for example 10 days, i.e. 3600*24*10 = 864000
*/
constructor(IGovernance reserve_, address router_, uint256 _minApplicationPeriod, address _linkToken, uint64 _mainnetChainSelector, address _bridgeAccounting, address _ccipAdmin) ERC20(18) CrossChainReference(router_, _linkToken) {
MIN_APPLICATION_PERIOD = _minApplicationPeriod;
reserve = reserve_;
MAINNET_CHAIN_SELECTOR = _mainnetChainSelector;
BRIDGE_ACCOUNTING = _bridgeAccounting;
CCIP_ADMIN = _ccipAdmin;
}
function name() external pure override returns (string memory) {
return "Frankencoin";
}
function symbol() external pure override returns (string memory) {
return "ZCHF";
}
/// @notice Initializes the bridged Frankencoin token.
/// @dev This function is only callable once.
/// @param _minters List of addresses that are allowed to mint Frankencoins.
/// @param _messages List of messages that are displayed to the user when the minter is applied.
function initialize(address[] calldata _minters, string[] calldata _messages) external {
if (initialized) revert AlreadyInitialized();
if (_minters.length != _messages.length) revert InvalidInput();
for (uint256 i = 0; i < _minters.length; i++) {
minters[_minters[i]] = block.timestamp;
emit MinterApplied(_minters[i], 0, 0, _messages[i]);
}
initialized = true;
}
/**
* @notice Publicly accessible method to suggest a new way of minting Frankencoin.
* @dev The caller has to pay an application fee that is irrevocably lost even if the new minter is vetoed.
* The caller must assume that someone will veto the new minter unless there is broad consensus that the new minter
* adds value to the Frankencoin system. Complex proposals should have application periods and applications fees
* above the minimum. It is assumed that over time, informal ways to coordinate on new minters emerge. The message
* parameter might be useful for initiating further communication. Maybe it contains a link to a website describing
* the proposed minter.
*
* @param _minter An address that is given the permission to mint Frankencoins
* @param _applicationPeriod The time others have to veto the suggestion, at least MIN_APPLICATION_PERIOD
* @param _applicationFee The fee paid by the caller, at least MIN_FEE
* @param _message An optional human readable message to everyone watching this contract
*/
function suggestMinter(address _minter, uint256 _applicationPeriod, uint256 _applicationFee, string calldata _message) external override {
if (_applicationPeriod < MIN_APPLICATION_PERIOD) revert PeriodTooShort();
if (_applicationFee < MIN_FEE) revert FeeTooLow();
if (minters[_minter] != 0) revert AlreadyRegistered();
_collectProfits(address(this), msg.sender, _applicationFee);
minters[_minter] = block.timestamp + _applicationPeriod;
emit MinterApplied(_minter, _applicationPeriod, _applicationFee, _message);
}
/**
* @notice Make the system more user friendly by skipping the allowance in many cases.
* @dev We trust minters and the positions they have created to mint and burn as they please, so
* giving them arbitrary allowances does not pose an additional risk.
*/
function _allowance(address owner, address spender) internal view override returns (uint256) {
uint256 explicit = super._allowance(owner, spender);
if (explicit > 0) {
return explicit; // don't waste gas checking minter
} else if (isMinter(spender) || isMinter(getPositionParent(spender)) || spender == address(reserve)) {
return INFINITY;
} else {
return 0;
}
}
/**
* @notice Allows minters to register collateralized debt positions, thereby giving them the ability to mint Frankencoins.
* @dev It is assumed that the responsible minter that registers the position ensures that the position can be trusted.
*/
function registerPosition(address _position) external override {
if (!isMinter(msg.sender)) revert NotMinter();
positions[_position] = msg.sender;
}
/**
* @notice Qualified pool share holders can deny minters during the application period.
* @dev Calling this function is relatively cheap thanks to the deletion of a storage slot.
*/
function denyMinter(address _minter, address[] calldata _helpers, string calldata _message) external override {
if (block.timestamp > minters[_minter]) revert TooLate();
reserve.checkQualified(msg.sender, _helpers);
delete minters[_minter];
emit MinterDenied(_minter, _message);
}
function mint(address _target, uint256 _amount) external override minterOnly {
_mint(_target, _amount);
}
/**
* Anyone is allowed to burn their ZCHF.
*/
function burn(uint256 _amount) external {
_burn(msg.sender, _amount);
}
/**
* @notice Burn someone elses ZCHF.
*/
function burnFrom(address _owner, uint256 _amount) external override minterOnly {
_burn(_owner, _amount);
}
function canMint(address _minterOrPosition) public view returns (bool) {
return isMinter(_minterOrPosition) || isMinter(positions[_minterOrPosition]);
}
/**
* @notice Notify the Frankencoin that a minter lost economic access to some coins. This does not mean that the coins are
* literally lost. It just means that some ZCHF will likely never be repaid and that in order to bring the system
* back into balance, the lost amount of ZCHF must be removed from the reserve instead.
*
* For example, if a minter printed 1 million ZCHF for a mortgage and the mortgage turned out to be unsound with
* the house only yielding 800'000 in the subsequent auction, there is a loss of 200'000 that needs to be covered
* by the reserve.
*/
function coverLoss(address source, uint256 _amount) external minterOnly {
uint256 reserveLeft = balanceOf(address(reserve));
if (_amount > reserveLeft) {
accruedLoss += (_amount - reserveLeft);
_mint(address(reserve), _amount - reserveLeft);
}
_transfer(address(reserve), source, _amount);
emit Loss(source, _amount);
}
function collectProfits(address source, uint256 _amount) external override minterOnly {
_collectProfits(msg.sender, source, _amount);
}
function _collectProfits(address minter, address source, uint256 _amount) internal {
_transfer(source, address(reserve), _amount);
if (accruedLoss > _amount) {
accruedLoss -= _amount;
_burn(address(reserve), _amount);
} else if (accruedLoss > 0) {
_burn(address(reserve), accruedLoss);
accruedLoss = 0;
}
emit Profit(minter, _amount);
}
function synchronizeAccounting() public payable {
synchronizeAccounting("");
}
function synchronizeAccounting(Client.EVMExtraArgsV2 calldata extraArgs) public payable {
synchronizeAccounting(Client._argsToBytes(extraArgs));
}
/**
* Uses a multichain call to send home all accrued profits, if any
*/
function synchronizeAccounting(bytes memory extraArgs) public payable {
(uint256 reserveLeft, uint256 _accruedLoss, Client.EVMTokenAmount[] memory tokenAmounts) = getSynchronizeAccountingData();
if (_accruedLoss > 0) {
accruedLoss = 0;
}
if (reserveLeft > 0) {
_transfer(address(reserve), address(this), reserveLeft);
_approve(address(this), address(ROUTER), reserveLeft);
}
Client.EVM2AnyMessage memory message = _constructMessage(_toReceiver(BRIDGE_ACCOUNTING), abi.encode(reserveLeft, _accruedLoss), tokenAmounts, extraArgs);
_send(MAINNET_CHAIN_SELECTOR, message);
emit AccountingSynchronized(reserveLeft, _accruedLoss);
}
/**
* @notice Returns the CCIP fee required to synchronize accounting.
*/
function getSynchronizeAccountingFee(bool nativeToken) public view returns (uint256) {
return getSynchronizeAccountingFee(nativeToken, "");
}
/**
* @notice Returns the CCIP fee required to synchronize accounting.
*/
function getSynchronizeAccountingFee(bool nativeToken, bytes memory extraArgs) public view returns (uint256) {
(uint256 reserveLeft, uint256 _accruedLoss, Client.EVMTokenAmount[] memory tokenAmounts) = getSynchronizeAccountingData();
Client.EVM2AnyMessage memory message = _constructMessage(_toReceiver(BRIDGE_ACCOUNTING), abi.encode(reserveLeft, _accruedLoss), tokenAmounts, nativeToken, extraArgs);
return _calculateFee(MAINNET_CHAIN_SELECTOR, message);
}
/**
* @notice Returns the data required to synchronize accounting. Including the tokenAmounts array.
*/
function getSynchronizeAccountingData() public view returns (uint256, uint256, Client.EVMTokenAmount[] memory) {
uint256 reserveLeft = balanceOf(address(reserve));
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](0);
if (reserveLeft > 0) {
tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount({token: address(this), amount: reserveLeft});
}
return (reserveLeft, accruedLoss, tokenAmounts);
}
/**
* @notice Returns true if the address is an approved minter.
*/
function isMinter(address _minter) public view returns (bool) {
return minters[_minter] != 0 && block.timestamp >= minters[_minter];
}
/**
* @notice Returns the address of the minter that created this position or null if the provided address is unknown.
*/
function getPositionParent(address _position) public view returns (address) {
return positions[_position];
}
/*
* @notice Used to register the token initially in the CCIP environment
*/
function getCCIPAdmin() external view returns (address) {
return CCIP_ADMIN;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {IAny2EVMMessageReceiver} from "../interfaces/IAny2EVMMessageReceiver.sol";
import {Client} from "../libraries/Client.sol";
import {IERC165} from "../../vendor/openzeppelin-solidity/v5.0.2/contracts/utils/introspection/IERC165.sol";
/// @title CCIPReceiver - Base contract for CCIP applications that can receive messages.
abstract contract CCIPReceiver is IAny2EVMMessageReceiver, IERC165 {
address internal immutable i_ccipRouter;
constructor(
address router
) {
if (router == address(0)) revert InvalidRouter(address(0));
i_ccipRouter = router;
}
/// @notice IERC165 supports an interfaceId
/// @param interfaceId The interfaceId to check
/// @return true if the interfaceId is supported
/// @dev Should indicate whether the contract implements IAny2EVMMessageReceiver
/// e.g. return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId
/// This allows CCIP to check if ccipReceive is available before calling it.
/// If this returns false or reverts, only tokens are transferred to the receiver.
/// If this returns true, tokens are transferred and ccipReceive is called atomically.
/// Additionally, if the receiver address does not have code associated with
/// it at the time of execution (EXTCODESIZE returns 0), only tokens will be transferred.
function supportsInterface(
bytes4 interfaceId
) public view virtual override returns (bool) {
return interfaceId == type(IAny2EVMMessageReceiver).interfaceId || interfaceId == type(IERC165).interfaceId;
}
/// @inheritdoc IAny2EVMMessageReceiver
function ccipReceive(
Client.Any2EVMMessage calldata message
) external virtual override onlyRouter {
_ccipReceive(message);
}
/// @notice Override this function in your implementation.
/// @param message Any2EVMMessage
function _ccipReceive(
Client.Any2EVMMessage memory message
) internal virtual;
/////////////////////////////////////////////////////////////////////
// Plumbing
/////////////////////////////////////////////////////////////////////
/// @notice Return the current router
/// @return CCIP router address
function getRouter() public view virtual returns (address) {
return address(i_ccipRouter);
}
error InvalidRouter(address router);
/// @dev only calls from the set router are accepted.
modifier onlyRouter() {
if (msg.sender != getRouter()) revert InvalidRouter(msg.sender);
_;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Client} from "../libraries/Client.sol";
/// @notice Application contracts that intend to receive messages from
/// the router should implement this interface.
interface IAny2EVMMessageReceiver {
/// @notice Called by the Router to deliver a message.
/// If this reverts, any token transfers also revert. The message
/// will move to a FAILED state and become available for manual execution.
/// @param message CCIP Message
/// @dev Note ensure you check the msg.sender is the OffRampRouter
function ccipReceive(
Client.Any2EVMMessage calldata message
) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
import {Client} from "../libraries/Client.sol";
interface IRouterClient {
error UnsupportedDestinationChain(uint64 destChainSelector);
error InsufficientFeeTokenAmount();
error InvalidMsgValue();
/// @notice Checks if the given chain ID is supported for sending/receiving.
/// @param destChainSelector The chain to check.
/// @return supported is true if it is supported, false if not.
function isChainSupported(
uint64 destChainSelector
) external view returns (bool supported);
/// @param destinationChainSelector The destination chainSelector
/// @param message The cross-chain CCIP message including data and/or tokens
/// @return fee returns execution fee for the message
/// delivery to destination chain, denominated in the feeToken specified in the message.
/// @dev Reverts with appropriate reason upon invalid message.
function getFee(
uint64 destinationChainSelector,
Client.EVM2AnyMessage memory message
) external view returns (uint256 fee);
/// @notice Request a message to be sent to the destination chain
/// @param destinationChainSelector The destination chain ID
/// @param message The cross-chain CCIP message including data and/or tokens
/// @return messageId The message ID
/// @dev Note if msg.value is larger than the required fee (from getFee) we accept
/// the overpayment with no refund.
/// @dev Reverts with appropriate reason upon invalid message.
function ccipSend(
uint64 destinationChainSelector,
Client.EVM2AnyMessage calldata message
) external payable returns (bytes32);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
// End consumer library.
library Client {
/// @dev RMN depends on this struct, if changing, please notify the RMN maintainers.
struct EVMTokenAmount {
address token; // token address on the local chain.
uint256 amount; // Amount of tokens.
}
struct Any2EVMMessage {
bytes32 messageId; // MessageId corresponding to ccipSend on source.
uint64 sourceChainSelector; // Source chain selector.
bytes sender; // abi.decode(sender) if coming from an EVM chain.
bytes data; // payload sent in original message.
EVMTokenAmount[] destTokenAmounts; // Tokens and their amounts in their destination chain representation.
}
// If extraArgs is empty bytes, the default is 200k gas limit.
struct EVM2AnyMessage {
bytes receiver; // abi.encode(receiver address) for dest EVM chains
bytes data; // Data payload
EVMTokenAmount[] tokenAmounts; // Token transfers
address feeToken; // Address of feeToken. address(0) means you will send msg.value.
bytes extraArgs; // Populate this with _argsToBytes(EVMExtraArgsV2)
}
// bytes4(keccak256("CCIP EVMExtraArgsV1"));
bytes4 public constant EVM_EXTRA_ARGS_V1_TAG = 0x97a657c9;
struct EVMExtraArgsV1 {
uint256 gasLimit;
}
function _argsToBytes(
EVMExtraArgsV1 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V1_TAG, extraArgs);
}
// bytes4(keccak256("CCIP EVMExtraArgsV2"));
bytes4 public constant EVM_EXTRA_ARGS_V2_TAG = 0x181dcf10;
/// @param gasLimit: gas limit for the callback on the destination chain.
/// @param allowOutOfOrderExecution: if true, it indicates that the message can be executed in any order relative to other messages from the same sender.
/// This value's default varies by chain. On some chains, a particular value is enforced, meaning if the expected value
/// is not set, the message request will revert.
struct EVMExtraArgsV2 {
uint256 gasLimit;
bool allowOutOfOrderExecution;
}
function _argsToBytes(
EVMExtraArgsV2 memory extraArgs
) internal pure returns (bytes memory bts) {
return abi.encodeWithSelector(EVM_EXTRA_ARGS_V2_TAG, extraArgs);
}
}// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/introspection/IERC165.sol)
pragma solidity ^0.8.20;
/**
* @dev Interface of the ERC165 standard, as defined in the
* https://eips.ethereum.org/EIPS/eip-165[EIP].
*
* Implementers can declare support of contract interfaces, which can then be
* queried by others ({ERC165Checker}).
*
* For an implementation, see {ERC165}.
*/
interface IERC165 {
/**
* @dev Returns true if this contract implements the interface defined by
* `interfaceId`. See the corresponding
* https://eips.ethereum.org/EIPS/eip-165#how-interfaces-are-identified[EIP section]
* to learn more about how these ids are created.
*
* This function call must use less than 30 000 gas.
*/
function supportsInterface(bytes4 interfaceId) external view returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {IERC20} from "../erc20/IERC20.sol";
abstract contract CCIPSender {
IRouterClient public immutable ROUTER;
address public immutable LINK;
error InsufficientFeeTokens(address token, uint256 fee);
error InsufficientFeeTokenAllowance(address token, uint256 fee);
constructor(IRouterClient router, address link) {
ROUTER = router;
LINK = link;
}
/// @notice Encodes the receiver correctly
/// @param target The target address
/// @return The encoded receiver
function _toReceiver(address target) internal pure returns (bytes memory) {
return abi.encode(target);
}
/// @notice Builds the message struct
/// @dev Guesses the fee token based on msg.value
/// @param receiver The receiver
/// @param payload The payload
/// @param tokenAmounts The token amounts
/// @param extraArgs The extra args such as gas limit and out-of-order execution
/// @return The message struct
function _constructMessage(bytes memory receiver, bytes memory payload, Client.EVMTokenAmount[] memory tokenAmounts, bytes memory extraArgs) internal view returns (Client.EVM2AnyMessage memory) {
return Client.EVM2AnyMessage(receiver, payload, tokenAmounts, _guessFeeToken(), extraArgs);
}
/// @notice Builds the message struct
/// @param receiver The receiver
/// @param payload The payload
/// @param tokenAmounts The token amounts
/// @param nativeToken Whether to use native token or LINK
/// @param extraArgs The extra args such as gas limit and out-of-order execution
/// @return The message struct
function _constructMessage(bytes memory receiver, bytes memory payload, Client.EVMTokenAmount[] memory tokenAmounts, bool nativeToken, bytes memory extraArgs) internal view returns (Client.EVM2AnyMessage memory) {
return Client.EVM2AnyMessage(receiver, payload, tokenAmounts, nativeToken ? address(0) : LINK, extraArgs);
}
/// @notice Gets the fee from the router for the given message and chain combination
/// @param chain The chain
/// @param message The message
/// @return The fee
function _calculateFee(uint64 chain, Client.EVM2AnyMessage memory message) internal view returns (uint256) {
return ROUTER.getFee(chain, message);
}
/// @notice Sends the message to the router and handles fee payment
/// @dev External call to msg.sender if fees are paid in native token. This function can lead to a potential reentrancy.
/// @param chain The chain
/// @param message The message
/// @return The message id and fee
function _send(uint64 chain, Client.EVM2AnyMessage memory message) internal returns (bytes32, uint256) {
uint256 fee = _calculateFee(chain, message);
bytes32 messageId;
if (message.feeToken != address(0)) {
// We trust the feeToken to be not malicious.
// ROUTER.getFee() verifies that the feeToken is supported by CCIP and thus vetted.
if (IERC20(message.feeToken).balanceOf(msg.sender) < fee) revert InsufficientFeeTokens(message.feeToken, fee);
if (IERC20(message.feeToken).allowance(msg.sender, address(this)) < fee) revert InsufficientFeeTokenAllowance(message.feeToken, fee);
IERC20(message.feeToken).transferFrom(msg.sender, address(this), fee);
IERC20(message.feeToken).approve(address(ROUTER), fee);
messageId = ROUTER.ccipSend(chain, message);
// Send back any leftover fee tokens
uint256 leftover = IERC20(message.feeToken).balanceOf(address(this));
if (leftover > 0) IERC20(message.feeToken).transfer(msg.sender, leftover);
} else {
if (msg.value < fee) revert InsufficientFeeTokens(message.feeToken, fee);
messageId = ROUTER.ccipSend{value: fee}(chain, message);
// return overpaid fee to sender. We don't care about the success of this call.
if(msg.value - fee > 0) payable(msg.sender).call{value: msg.value - fee}("");
}
return (messageId, fee);
}
/// @notice Guesses the fee token based on msg.value
/// @return The fee token
function _guessFeeToken() internal view returns (address) {
return (msg.value > 0) ? address(0) : LINK;
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {CCIPReceiver} from "@chainlink/contracts-ccip/src/v0.8/ccip/applications/CCIPReceiver.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {SyncVote, SyncMessage} from "./IGovernance.sol";
import {Governance} from "./Governance.sol";
/**
* This contract receives messages from the mainnet governance contract about the voting power of individual addresses
* as well as the total number of votes at the time of synchronization, and to whom they are delegating (if anyone).
*
* Accounts that alone or with the help of delegations reach 2% of the votes gain veto power and are considered 'qualified'.
* See also the 'checkQualified' method in the parent class and the IGovernance interface. The governance process is not
* done through majority votes, but by exercising veto power. Generally, anyone can make governance proposals that pass
* automatically after a grace period unless a qualified user vetoes the proposal, leading to a very light-weight and
* decentralized governance process.
*
* As the total number of votes increases with time (votes is the holding duration times the number of governance tokens
* held), the voting power of previously synchronized accounts tends to decrease with subsequent new synchronizations,
* leading to a natural and desired decay. Delegations can either be synchronized from mainnet or done locally on
* the current chain (with the risk of being overwritten when someone triggers a sync for the delegating account).
*/
contract BridgedGovernance is CCIPReceiver, Governance {
uint64 public immutable MAINNET_CHAIN_SELECTOR;
address public immutable MAINNET_GOVERNANCE_ADDRESS;
mapping(address => uint256) private _votes;
uint256 private _totalVotes;
event MessageReceived(bytes32 messageId, uint64 sourceChain, uint256 totalVotes, SyncVote[] syncedVotes);
error InvalidSourceChain();
error InvalidSender();
constructor(address router, uint64 mainnetChainSelector, address mainnetGovernanceAddress) CCIPReceiver(router) {
MAINNET_CHAIN_SELECTOR = mainnetChainSelector;
MAINNET_GOVERNANCE_ADDRESS = mainnetGovernanceAddress;
}
/// @notice Get the number of votes held by a holder.
/// @param holder The address to check.
function votes(address holder) public view override returns (uint256) {
return _votes[holder];
}
/// @notice Get the total number of votes.
function totalVotes() public view override returns (uint256) {
return _totalVotes;
}
/// @notice Process a sync message.
/// @param any2EvmMessage The message to process.
function _ccipReceive(Client.Any2EVMMessage memory any2EvmMessage) internal override {
if (any2EvmMessage.sourceChainSelector != MAINNET_CHAIN_SELECTOR) revert InvalidSourceChain();
if (abi.decode(any2EvmMessage.sender, (address)) != MAINNET_GOVERNANCE_ADDRESS) revert InvalidSender();
SyncMessage memory syncMessage = abi.decode(any2EvmMessage.data, (SyncMessage)); // abi-decoding of the sent text
_processSyncMessage(syncMessage);
emit MessageReceived(any2EvmMessage.messageId, any2EvmMessage.sourceChainSelector, syncMessage.totalVotes, syncMessage.votes);
}
/// @notice Updates internal state with received message
/// @param syncMessage The message to process.
function _processSyncMessage(SyncMessage memory syncMessage) internal {
_totalVotes = syncMessage.totalVotes;
// omitted unchecked optimization for readability
for (uint64 i = 0; i < syncMessage.votes.length; i++) {
SyncVote memory syncVote = syncMessage.votes[i];
_votes[syncVote.voter] = syncVote.votes;
delegate(syncVote.voter, syncVote.delegatee);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./Governance.sol";
import "../stablecoin/Frankencoin.sol";
import "../utils/MathUtil.sol";
import "../erc20/IERC677Receiver.sol";
/**
* @title Equity
* @notice If the Frankencoin system was a bank, this contract would represent the equity on its balance sheet.
* Like with a corporation, the owners of the equity capital are the shareholders, or in this case the holders
* of Frankencoin Pool Shares (FPS) tokens. Anyone can mint additional FPS tokens by adding Frankencoins to the
* reserve pool. Also, FPS tokens can be redeemed for Frankencoins again after a minimum holding period.
* Furthermore, the FPS shares come with some voting power. Anyone that held at least 3% of the holding-period-
* weighted reserve pool shares gains veto power and can veto new proposals.
*/
contract Equity is Governance, ERC20PermitLight, MathUtil {
/**
* The VALUATION_FACTOR determines the market cap of the reserve pool shares relative to the equity reserves.
* The following always holds: Market Cap = Valuation Factor * Equity Reserve = Price * Supply
*
* In the absence of profits and losses, the variables grow as follows when FPS tokens are minted:
*
* | Reserve | Market Cap | Price | Supply |
* | 1000 | 3000 | 3 | 1000 |
* | 1000000 | 3000000 | 300 | 10000 |
* | 1000000000 | 3000000000 | 30000 | 100000 |
* | 1000000000000 | 3000000000000 | 3000000 | 1000000 |
*
* I.e., the supply is proporational to the cubic root of the reserve and the price is proportional to the
* squared cubic root. When profits accumulate or losses materialize, the reserve, the market cap,
* and the price are adjusted proportionally, with the supply staying constant. In the absence of an extreme
* inflation of the Swiss franc, it is unlikely that there will ever be more than ten million FPS.
*/
uint32 public constant VALUATION_FACTOR = 3;
uint256 private constant MINIMUM_EQUITY = 1000 * ONE_DEC18;
/**
* @notice The number of digits to store the average holding time of share tokens.
*/
uint8 private constant TIME_RESOLUTION_BITS = 20;
/**
* @notice The minimum holding duration. You are not allowed to redeem your pool shares if you held them
* for less than the minimum holding duration at average. For example, if you have two pool shares on your
* address, one acquired 5 days ago and one acquired 105 days ago, you cannot redeem them as the average
* holding duration of your shares is only 55 days < 90 days.
*/
uint256 public constant MIN_HOLDING_DURATION = 90 days << TIME_RESOLUTION_BITS; // Set to 5 for local testing
Frankencoin public immutable zchf;
/**
* @dev To track the total number of votes we need to know the number of votes at the anchor time and when the
* anchor time was. This is (hopefully) stored in one 256 bit slot, with the anchor time taking 64 Bits and
* the total vote count 192 Bits. Given the sub-second resolution of 20 Bits, the implicit assumption is
* that the timestamp can always be stored in 44 Bits (i.e. it does not exceed half a million years). Further,
* given 18 decimals (about 60 Bits), this implies that the total supply cannot exceed
* 192 - 60 - 44 - 20 = 68 Bits
* Here, we are also save, as 68 Bits would imply more than a trillion outstanding shares. In fact,
* a limit of about 2**36 shares (that's about 2**96 Bits when taking into account the decimals) is imposed
* when minting. This means that the maximum supply is billions shares, which is could only be reached in
* a scenario with hyper inflation, in which case the stablecoin is worthless anyway.
*/
uint192 private totalVotesAtAnchor; // Total number of votes at the anchor time, see comment on the um
uint64 private totalVotesAnchorTime; // 44 Bit for the time stamp, 20 Bit sub-second time resolution
/**
* @notice A time stamp in the past such that: votes = balance * (time passed since anchor was set)
*/
mapping(address owner => uint64 timestamp) private voteAnchor; // 44 bits for time stamp, 20 subsecond resolution
event Trade(address who, int amount, uint totPrice, uint newprice); // amount pos or neg for mint or redemption
constructor(Frankencoin zchf_) ERC20(18) {
zchf = zchf_;
}
function name() external pure override returns (string memory) {
return "Frankencoin Pool Share";
}
function symbol() external pure override returns (string memory) {
return "FPS";
}
/**
* @notice Returns the price of one FPS in ZCHF with 18 decimals precision.
*/
function price() public view returns (uint256) {
uint256 equity = zchf.equity();
if (equity == 0 || totalSupply() == 0) {
return ONE_DEC18; // initial price is 1000 ZCHF for the first 1000 FPS
} else {
return (VALUATION_FACTOR * zchf.equity() * ONE_DEC18) / totalSupply();
}
}
function _beforeTokenTransfer(address from, address to, uint256 amount) internal override {
super._beforeTokenTransfer(from, to, amount);
if (amount > 0) {
// No need to adjust the sender votes. When they send out 10% of their shares, they also lose 10% of
// their votes so everything falls nicely into place. Recipient votes should stay the same, but grow
// faster in the future, requiring an adjustment of the anchor.
uint256 roundingLoss = _adjustRecipientVoteAnchor(to, amount);
// The total also must be adjusted and kept accurate by taking into account the rounding error.
_adjustTotalVotes(from, amount, roundingLoss);
}
}
/**
* @notice Returns whether the given address is allowed to redeem FPS, which is the
* case after their average holding duration is larger than the required minimum.
*/
function canRedeem(address owner) public view returns (bool) {
return _anchorTime() - voteAnchor[owner] >= MIN_HOLDING_DURATION;
}
/**
* @notice Decrease the total votes anchor when tokens lose their voting power due to being moved
* @param from sender
* @param amount amount to be sent
*/
function _adjustTotalVotes(address from, uint256 amount, uint256 roundingLoss) internal {
uint64 time = _anchorTime();
uint256 lostVotes = from == address(0x0) ? 0 : (time - voteAnchor[from]) * amount;
totalVotesAtAnchor = uint192(totalVotes() - roundingLoss - lostVotes);
totalVotesAnchorTime = time;
}
/**
* @notice the vote anchor of the recipient is moved forward such that the number of calculated
* votes does not change despite the higher balance.
* @param to receiver address
* @param amount amount to be received
* @return the number of votes lost due to rounding errors
*/
function _adjustRecipientVoteAnchor(address to, uint256 amount) internal returns (uint256) {
if (to != address(0x0)) {
uint256 recipientVotes = votes(to); // for example 21 if 7 shares were held for 3 seconds
uint256 newbalance = balanceOf(to) + amount; // for example 11 if 4 shares are added
// new example anchor is only 21 / 11 = 1 second in the past
voteAnchor[to] = uint64(_anchorTime() - recipientVotes / newbalance);
return recipientVotes % newbalance; // we have lost 21 % 11 = 10 votes
} else {
// optimization for burn, vote anchor of null address does not matter
return 0;
}
}
/**
* @notice Time stamp with some additional bits for higher resolution.
*/
function _anchorTime() internal view returns (uint64) {
return uint64(block.timestamp << TIME_RESOLUTION_BITS);
}
/**
* @notice The relative voting power of the address.
* @return A percentage with 1e18 being 100%
*/
function relativeVotes(address holder) external view returns (uint256) {
return (ONE_DEC18 * votes(holder)) / totalVotes();
}
/**
* @notice The votes of the holder, excluding votes from delegates.
*/
function votes(address holder) public override view returns (uint256) {
return balanceOf(holder) * (_anchorTime() - voteAnchor[holder]);
}
/**
* @notice How long the holder already held onto their average FPS in seconds.
*/
function holdingDuration(address holder) public view returns (uint256) {
return (_anchorTime() - voteAnchor[holder]) >> TIME_RESOLUTION_BITS;
}
/**
* @notice Total number of votes in the system.
*/
function totalVotes() public override view returns (uint256) {
return totalVotesAtAnchor + totalSupply() * (_anchorTime() - totalVotesAnchorTime);
}
/**
* @notice Since quorum is rather low, it is important to have a way to prevent malicious minority holders
* from blocking the whole system. This method provides a way for the good guys to team up and destroy
* the bad guy's votes (at the cost of also reducing their own votes). This mechanism potentially
* gives full control over the system to whoever has 51% of the votes.
*
* Since this is a rather aggressive measure, delegation is not supported. Every holder must call this
* method on their own.
* @param targets The target addresses to remove votes from
* @param votesToDestroy The maximum number of votes the caller is willing to sacrifice
*/
function kamikaze(address[] calldata targets, uint256 votesToDestroy) external {
uint256 budget = _reduceVotes(msg.sender, votesToDestroy);
uint256 destroyedVotes = 0;
for (uint256 i = 0; i < targets.length && destroyedVotes < budget; i++) {
destroyedVotes += _reduceVotes(targets[i], budget - destroyedVotes);
}
require(destroyedVotes > 0); // sanity check
totalVotesAtAnchor = uint192(totalVotes() - destroyedVotes - budget);
totalVotesAnchorTime = _anchorTime();
}
function _reduceVotes(address target, uint256 amount) internal returns (uint256) {
uint256 votesBefore = votes(target);
if (amount >= votesBefore) {
voteAnchor[target] = _anchorTime();
return votesBefore;
} else {
voteAnchor[target] = uint64(_anchorTime() - (votesBefore - amount) / balanceOf(target));
return votesBefore - votes(target);
}
}
/**
* @notice Call this method to obtain newly minted pool shares in exchange for Frankencoins.
* No allowance required (i.e. it is hardcoded in the Frankencoin token contract).
* Make sure to invest at least 10e-12 * market cap to avoid rounding losses.
*
* @dev If equity is close to zero or negative, you need to send enough ZCHF to bring equity back to 1000 ZCHF.
*
* @param amount Frankencoins to invest
* @param expectedShares Minimum amount of expected shares for frontrunning protection
*/
function invest(uint256 amount, uint256 expectedShares) external returns (uint256) {
zchf.transferFrom(msg.sender, address(this), amount);
uint256 equity = zchf.equity();
require(equity >= MINIMUM_EQUITY, "insuf equity"); // ensures that the initial deposit is at least 1000 ZCHF
uint256 shares = _calculateShares(equity <= amount ? 0 : equity - amount, amount);
require(shares >= expectedShares);
_mint(msg.sender, shares);
emit Trade(msg.sender, int(shares), amount, price());
// limit the total supply to a reasonable amount to guard against overflows with price and vote calculations
// the 36 bits are 68 bits for magnitude and 60 bits for precision, as calculated in an above comment
require(totalSupply() <= type(uint96).max, "total supply exceeded");
return shares;
}
/**
* @notice Calculate shares received when investing Frankencoins
* @param investment ZCHF to be invested
* @return shares to be received in return
*/
function calculateShares(uint256 investment) external view returns (uint256) {
return _calculateShares(zchf.equity(), investment);
}
function _calculateShares(uint256 capitalBefore, uint256 investment) internal view returns (uint256) {
uint256 totalShares = totalSupply();
uint256 investmentExFees = (investment * 997) / 1000; // remove 0.3% fee
// Assign 1000 FPS for the initial deposit, calculate the amount otherwise
uint256 newTotalShares = capitalBefore < MINIMUM_EQUITY || totalShares == 0
? totalShares + 1000 * ONE_DEC18
: _mulD18(totalShares, _cubicRoot(_divD18(capitalBefore + investmentExFees, capitalBefore)));
return newTotalShares - totalShares;
}
/**
* @notice Redeem the given amount of shares owned by the sender and transfer the proceeds to the target.
* @return The amount of ZCHF transferred to the target
*/
function redeem(address target, uint256 shares) external returns (uint256) {
return _redeemFrom(msg.sender, target, shares);
}
/**
* @notice Like redeem(...), but with an extra parameter to protect against frontrunning.
* @param expectedProceeds The minimum acceptable redemption proceeds.
*/
function redeemExpected(address target, uint256 shares, uint256 expectedProceeds) external returns (uint256) {
uint256 proceeds = _redeemFrom(msg.sender, target, shares);
require(proceeds >= expectedProceeds);
return proceeds;
}
/**
* @notice Redeem FPS based on an allowance from the owner to the caller.
* See also redeemExpected(...).
*/
function redeemFrom(
address owner,
address target,
uint256 shares,
uint256 expectedProceeds
) external returns (uint256) {
_useAllowance(owner, msg.sender, shares);
uint256 proceeds = _redeemFrom(owner, target, shares);
require(proceeds >= expectedProceeds);
return proceeds;
}
function _redeemFrom(address owner, address target, uint256 shares) internal returns (uint256) {
require(canRedeem(owner));
uint256 proceeds = calculateProceeds(shares);
_burn(owner, shares);
zchf.transfer(target, proceeds);
emit Trade(owner, -int(shares), proceeds, price());
return proceeds;
}
/**
* @notice Calculate ZCHF received when depositing shares
* @param shares number of shares we want to exchange for ZCHF,
* in dec18 format
* @return amount of ZCHF received for the shares
*/
function calculateProceeds(uint256 shares) public view returns (uint256) {
uint256 totalShares = totalSupply();
require(shares + ONE_DEC18 < totalShares, "too many shares"); // make sure there is always at least one share
uint256 capital = zchf.equity();
uint256 reductionAfterFees = (shares * 997) / 1000;
uint256 newCapital = _mulD18(capital, _power3(_divD18(totalShares - reductionAfterFees, totalShares)));
return capital - newCapital;
}
/**
* @notice If there is less than 1000 ZCHF in equity left (maybe even negative), the system is at risk
* and we should allow qualified FPS holders to restructure the system.
*
* Example: there was a devastating loss and equity stands at -1'000'000. Most shareholders have lost hope in the
* Frankencoin system except for a group of small FPS holders who still believes in it and is willing to provide
* 2'000'000 ZCHF to save it. These brave souls are essentially donating 1'000'000 to the minter reserve and it
* would be wrong to force them to share the other million with the passive FPS holders. Instead, they will get
* the possibility to bootstrap the system again owning 100% of all FPS shares.
*
* @param helpers A list of addresses that delegate to the caller in incremental order
* @param addressesToWipe A list of addresses whose FPS will be burned to zero
*/
function restructureCapTable(address[] calldata helpers, address[] calldata addressesToWipe) external {
require(zchf.equity() < MINIMUM_EQUITY);
checkQualified(msg.sender, helpers);
for (uint256 i = 0; i < addressesToWipe.length; i++) {
address current = addressesToWipe[i];
_burn(current, balanceOf(current));
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "./IGovernance.sol";
/**
* Contract to consult when checking if someone has veto power.
* Veto power is reached with 2% of the votes.
*/
abstract contract Governance is IGovernance {
/**
* @notice The quorum in basis points. 100 is 1%.
*/
uint32 private constant QUORUM = 200;
/**
* @notice Keeping track on who delegated votes to whom.
* Note that delegation does not mean you cannot vote / veto any more, it just means that the delegate can
* benefit from your votes when invoking a veto. Circular delegations are valid, do not help when voting.
*/
mapping(address owner => address delegate) public delegates;
event Delegation(address indexed from, address indexed to); // indicates a delegation
/**
* @notice The votes of the holder, excluding votes from delegates.
*/
function votes(address holder) virtual public view returns (uint256);
/**
* @notice Total number of votes in the system.
*/
function totalVotes() virtual public view returns (uint256);
/**
* @notice The number of votes the sender commands when taking the support of the helpers into account.
* @param sender The address whose total voting power is of interest
* @param helpers An incrementally sorted list of helpers without duplicates and without the sender.
* The call fails if the list contains an address that does not delegate to sender.
* For indirect delegates, i.e. a -> b -> c, both a and b must be included for both to count.
* @return The total number of votes of sender at the current point in time.
*/
function votesDelegated(address sender, address[] calldata helpers) public view returns (uint256) {
uint256 _votes = votes(sender);
require(_checkDuplicatesAndSorted(helpers));
for (uint i = 0; i < helpers.length; i++) {
address current = helpers[i];
require(current != sender);
require(_canVoteFor(sender, current));
_votes += votes(current);
}
return _votes;
}
function _checkDuplicatesAndSorted(address[] calldata helpers) internal pure returns (bool ok) {
if (helpers.length <= 1) {
return true;
} else {
address prevAddress = helpers[0];
for (uint i = 1; i < helpers.length; i++) {
if (helpers[i] <= prevAddress) {
return false;
}
prevAddress = helpers[i];
}
return true;
}
}
/**
* @notice Checks whether the sender address is qualified given a list of helpers that delegated their votes
* directly or indirectly to the sender. It is the responsiblity of the caller to figure out whether
* helps are necessary and to identify them by scanning the blockchain for Delegation events.
*/
function checkQualified(address sender, address[] calldata helpers) public view override {
uint256 _votes = votesDelegated(sender, helpers);
if (_votes * 10000 < QUORUM * totalVotes()) revert NotQualified();
}
error NotQualified();
/**
* @notice Increases the voting power of the delegate by your number of votes without taking away any voting power
* from the sender.
*/
function delegateVoteTo(address delegate_) external {
delegate(msg.sender, delegate_);
}
function delegate(address owner, address delegate_) internal {
delegates[owner] = delegate_;
emit Delegation(owner, delegate_);
}
function _canVoteFor(address delegate_, address owner) internal view returns (bool) {
if (owner == delegate_) {
return true;
} else if (owner == address(0x0)) {
return false;
} else {
return _canVoteFor(delegate_, delegates[owner]);
}
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IGovernance {
function checkQualified(address sender, address[] calldata helpers) external view;
}
struct SyncVote {
address voter;
uint256 votes;
address delegatee;
}
struct SyncMessage {
SyncVote[] votes;
uint256 totalVotes;
}// SPDX-License-Identifier: MIT
// Copied from https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol
// and modified it.
pragma solidity ^0.8.0;
import {ERC20} from "./ERC20.sol";
import {IRouterClient} from "@chainlink/contracts-ccip/src/v0.8/ccip/interfaces/IRouterClient.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
import {CCIPSender} from "../bridge/CCIPSender.sol";
abstract contract CrossChainERC20 is ERC20, CCIPSender {
event Transfer(address indexed from, uint64 toChain, bytes indexed to, uint256 value);
constructor(address router, address linkToken) CCIPSender(IRouterClient(router), linkToken) {}
/// @notice Transfers tokens to the target chain
/// @dev Requires the caller to approve this contract to spend fee tokens if the CCIP fee is not paid in the chain native token.
/// @param targetChain The chain selector of the destination chain.
/// @param target The address of the recipient on the destination chain.
/// @param amount The amount of tokens to transfer.
function transfer(uint64 targetChain, address target, uint256 amount) external payable {
transfer(targetChain, _toReceiver(target), amount, "");
}
/// @notice Transfers tokens to the target chain
/// @dev Requires the caller to approve this contract to spend fee tokens if the CCIP fee is not paid in the chain native token.
/// @param targetChain The chain selector of the destination chain.
/// @param target The address of the recipient on the destination chain.
/// @param amount The amount of tokens to transfer.
/// @param extraArgs Extra arguments for CCIP
function transfer(uint64 targetChain, address target, uint256 amount, Client.EVMExtraArgsV2 calldata extraArgs) external payable {
transfer(targetChain, _toReceiver(target), amount, Client._argsToBytes(extraArgs));
}
/// @notice Transfers tokens to the target chain
/// @dev Requires the caller to approve this contract to spend fee tokens if the CCIP fee is not paid in the chain native token.
/// @param targetChain The chain selector of the destination chain.
/// @param target The address of the recipient on the destination chain.
/// @param amount The amount of tokens to transfer.
/// @param extraArgs Extra arguments for CCIP
function transfer(uint64 targetChain, bytes memory target, uint256 amount, bytes memory extraArgs) public payable {
_transfer(msg.sender, address(this), amount);
_approve(address(this), address(ROUTER), amount);
_send(targetChain, constructTransferMessage(target, amount, extraArgs));
emit Transfer(msg.sender, targetChain, target, amount);
}
/// @notice Gets the CCIP fee for a transfer.
/// @param targetChain The chain selector of the destination chain.
/// @param target The address of the recipient on the destination chain.
/// @param amount The amount of tokens to transfer.
/// @param nativeToken Whether the token is a native token.
function getCCIPFee(uint64 targetChain, address target, uint256 amount, bool nativeToken) public view returns (uint256) {
return getCCIPFee(targetChain, _toReceiver(target), amount, nativeToken, "");
}
/// @notice Gets the CCIP fee for a transfer.
/// @param targetChain The chain selector of the destination chain.
/// @param target The address of the recipient on the destination chain.
/// @param amount The amount of tokens to transfer.
/// @param nativeToken Whether the token is a native token.
/// @param extraArgs Extra arguments for CCIP
function getCCIPFee(uint64 targetChain, address target, uint256 amount, bool nativeToken, bytes memory extraArgs) public view returns (uint256) {
return getCCIPFee(targetChain, _toReceiver(target), amount, nativeToken, extraArgs);
}
/// @notice Gets the CCIP fee for a transfer.
/// @param targetChain The chain selector of the destination chain.
/// @param target The address of the recipient on the destination chain.
/// @param amount The amount of tokens to transfer.
/// @param nativeToken Whether the token is a native token.
/// @param extraArgs Extra arguments for CCIP
function getCCIPFee(uint64 targetChain, bytes memory target, uint256 amount, bool nativeToken, bytes memory extraArgs) public view returns (uint256) {
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount(address(this), amount);
return _calculateFee(targetChain, _constructMessage(target, "", tokenAmounts, nativeToken, extraArgs));
}
/// @notice Construct a CCIP message.
/// @dev This function will create an EVM2AnyMessage struct with all the necessary information for tokens transfer.
/// @param receiver The address of the receiver.
/// @param amount The amount of the token to be transferred.
/// @return Client.EVM2AnyMessage Returns an EVM2AnyMessage struct which contains information for sending a CCIP message.
function constructTransferMessage(bytes memory receiver, uint256 amount, bytes memory extraArgs) internal view returns (Client.EVM2AnyMessage memory) {
Client.EVMTokenAmount[] memory tokenAmounts = new Client.EVMTokenAmount[](1);
tokenAmounts[0] = Client.EVMTokenAmount(address(this), amount);
return _constructMessage(receiver, "", tokenAmounts, extraArgs);
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import {CrossChainERC20} from "./CrossChainERC20.sol";
import {ERC20} from "./ERC20.sol";
import {Client} from "@chainlink/contracts-ccip/src/v0.8/ccip/libraries/Client.sol";
/**
* @dev A module for Frankencoin crosschain transfers with a reference number
*/
abstract contract CrossChainReference is CrossChainERC20 {
event Transfer(address indexed from, address indexed to, uint256 amount, string ref);
// @dev: **to** is type bytes to support arbitrary destination chains without risk of unsafe conversion
event CrossTransfer(address indexed sender, address indexed from, uint64 toChain, bytes indexed to, uint256 amount, string ref);
constructor(address router, address linkToken) CrossChainERC20(router, linkToken) {}
// transfer within the erc20 context
function transfer(address recipient, uint256 amount, string calldata ref) public returns (bool) {
_transfer(msg.sender, recipient, amount);
emit Transfer(msg.sender, recipient, amount, ref);
return true;
}
function transferFrom(address owner, address recipient, uint256 amount, string calldata ref) public returns (bool) {
_useAllowance(owner, msg.sender, amount);
_transfer(owner, recipient, amount);
emit Transfer(owner, recipient, amount, ref);
return true;
}
// transfer within the cross chain context
function transfer(uint64 targetChain, address recipient, uint256 amount, string calldata ref) public payable returns (bool) {
return transfer(targetChain, _toReceiver(recipient), amount, "", ref);
}
function transfer(uint64 targetChain, address recipient, uint256 amount, Client.EVMExtraArgsV2 calldata extraArgs, string calldata ref) public payable returns (bool) {
return transfer(targetChain, _toReceiver(recipient), amount, Client._argsToBytes(extraArgs), ref);
}
function transfer(uint64 targetChain, bytes memory recipient, uint256 amount, bytes memory extraArgs, string calldata ref) public payable returns (bool) {
_crossTransfer(targetChain, msg.sender, recipient, amount, extraArgs, ref);
return true;
}
// cross transfer from
function transferFrom(uint64 targetChain, address owner, address recipient, uint256 amount, string calldata ref) public payable returns (bool) {
return transferFrom(targetChain, owner, _toReceiver(recipient), amount, "", ref);
}
function transferFrom(uint64 targetChain, address owner, address recipient, uint256 amount, Client.EVMExtraArgsV2 calldata extraArgs, string calldata ref) public payable returns (bool) {
return transferFrom(targetChain, owner, _toReceiver(recipient), amount, Client._argsToBytes(extraArgs), ref);
}
function transferFrom(uint64 targetChain, address owner, bytes memory recipient, uint256 amount, bytes memory extraArgs, string calldata ref) public payable returns (bool) {
_useAllowance(owner, msg.sender, amount);
_crossTransfer(targetChain, owner, recipient, amount, extraArgs, ref);
return true;
}
function _crossTransfer(uint64 targetChain, address from, bytes memory target, uint256 amount, bytes memory extraArgs, string calldata ref) private {
_transfer(from, address(this), amount);
_approve(address(this), address(ROUTER), amount);
_send(targetChain, constructTransferMessage(target, amount, extraArgs));
emit CrossTransfer(msg.sender, from, targetChain, target, amount, ref); // @dev: target is type bytes
}
}// SPDX-License-Identifier: MIT
// Copied and adjusted from OpenZeppelin
// Adjustments:
// - modifications to support ERC-677
// - removed require messages to save space
// - removed unnecessary require statements
// - removed GSN Context
// - upgraded to 0.8 to drop SafeMath
// - let name() and symbol() be implemented by subclass
// - infinite allowance support, with 2^255 and above considered infinite
pragma solidity ^0.8.0;
import "./IERC20.sol";
import "./IERC677Receiver.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 `ERC20Mintable`.
*
* *For a detailed writeup see our guide [How to implement supply
* mechanisms](https://forum.zeppelin.solutions/t/how-to-implement-erc20-supply-mechanisms/226).*
*
* We have followed general OpenZeppelin guidelines: functions revert instead
* of 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.
*
*/
abstract contract ERC20 is IERC20 {
mapping(address account => uint256 balance) private _balances;
mapping(address account => mapping(address spender => uint256 allowance)) private _allowances;
uint256 internal constant INFINITY = (1 << 255);
uint256 private _totalSupply;
uint8 public immutable override decimals;
// Copied from https://github.com/OpenZeppelin/openzeppelin-contracts/pull/4139/files#diff-fa792f7d08644eebc519dac2c29b00a54afc4c6a76b9ef3bba56c8401fe674f6
// Indicates an error related to the current balance of a sender. Used in transfers.
error ERC20InsufficientBalance(address sender, uint256 balance, uint256 needed);
// Indicates a failure with the spender’s allowance. Used in transfers.
error ERC20InsufficientAllowance(address spender, uint256 allowance, uint256 needed);
constructor(uint8 _decimals) {
decimals = _decimals;
}
/**
* @dev See `IERC20.totalSupply`.
*/
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}
/**
* @dev See `IERC20.balanceOf`.
*/
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}
/**
* @dev See `IERC20.transfer`.
*
* Requirements:
*
* - `recipient` cannot be the zero address.
* - the caller must have a balance of at least `amount`.
*/
function transfer(address recipient, uint256 amount) public virtual override returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}
/**
* @dev See `IERC20.allowance`.
*/
function allowance(address owner, address spender) external view override returns (uint256) {
return _allowance(owner, spender);
}
function _allowance(address owner, address spender) internal view virtual returns (uint256) {
return _allowances[owner][spender];
}
/**
* @dev See `IERC20.approve`.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) external override returns (bool) {
_approve(msg.sender, spender, value);
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`;
*
* Requirements:
* - `sender` and `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `value`.
* - the caller must have allowance for `sender`'s tokens of at least
* `amount`.
*/
function transferFrom(address sender, address recipient, uint256 amount) external override returns (bool) {
_transfer(sender, recipient, amount);
_useAllowance(sender, msg.sender, amount);
return true;
}
function _useAllowance(address owner, address spender, uint256 amount) internal {
uint256 currentAllowance = _allowance(owner, spender);
if (currentAllowance < INFINITY) {
// Only decrease the allowance if it was not set to 'infinite'
// Documented in github.com/aktionariat/contracts/blob/master/doc/infiniteallowance.md
if (currentAllowance < amount) revert ERC20InsufficientAllowance(owner, currentAllowance, amount);
_approve(owner, spender, currentAllowance - amount);
}
}
/**
* @dev Moves tokens `amount` from `sender` to `recipient`.
*
* This is 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:
*
* - `sender` cannot be the zero address.
* - `recipient` cannot be the zero address.
* - `sender` must have a balance of at least `amount`.
*/
function _transfer(address sender, address recipient, uint256 amount) internal virtual {
require(recipient != address(0));
_beforeTokenTransfer(sender, recipient, amount);
if (_balances[sender] < amount) revert ERC20InsufficientBalance(sender, _balances[sender], amount);
_balances[sender] -= amount;
_balances[recipient] += amount;
emit Transfer(sender, recipient, 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
*
* - `to` cannot be the zero address.
*/
function _mint(address recipient, uint256 amount) internal virtual {
require(recipient != address(0));
_beforeTokenTransfer(address(0), recipient, amount);
_totalSupply += amount;
_balances[recipient] += amount;
emit Transfer(address(0), recipient, amount);
}
/**
* @dev Destroys `amount` tokens from `account`, reducing the
* total supply.
*
* Emits a `Transfer` event with `to` set to the zero address.
*/
function _burn(address account, uint256 amount) internal virtual {
_beforeTokenTransfer(account, address(0), amount);
_totalSupply -= amount;
_balances[account] -= amount;
emit Transfer(account, address(0), amount);
}
/**
* @dev Sets `amount` as the allowance of `spender` over the `owner`s tokens.
*
* This is internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an `Approval` event.
*/
function _approve(address owner, address spender, uint256 value) internal {
_allowances[owner][spender] = value;
emit Approval(owner, spender, value);
}
/**
* @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 to 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 {}
}// SPDX-License-Identifier: MIT
// Copied from https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol
// and modified it.
pragma solidity ^0.8.0;
import "./ERC20.sol";
abstract contract ERC20PermitLight is ERC20 {
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
mapping(address account => uint256 nonce) public nonces;
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(address owner, address spender, uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
unchecked {
// unchecked to save a little gas with the nonce increment...
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
// keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"),
bytes32(0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
_approve(recoveredAddress, spender, value);
}
}
function DOMAIN_SEPARATOR() public view returns (bytes32) {
return
keccak256(
abi.encode(
//keccak256("EIP712Domain(uint256 chainId,address verifyingContract)");
bytes32(0x47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a79469218),
block.chainid,
address(this)
)
);
}
}/**
* SPDX-License-Identifier: MIT
*
* Copyright (c) 2016-2019 zOS Global Limited
*
*/
pragma solidity ^0.8.0;
/**
* @dev Interface of the ERC20 standard as defined in the EIP. Does not include
* the optional functions; to access them see `ERC20Detailed`.
*/
interface IERC20 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
/**
* @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 `recipient`.
*
* Returns always true. Throws error on failure.
*
* Emits a `Transfer` event.
*/
function transfer(address recipient, 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 can change 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.
*
* > 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 `sender` to `recipient` using the
* allowance mechanism. `amount` is then deducted from the caller's
* allowance.
*
* Returns always true. Throws error on failure.
*
* Emits a `Transfer` event.
*/
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool);
/**
* @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);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface IERC677Receiver {
function onTokenTransfer(address from, uint256 amount, bytes calldata data) external returns (bool);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../erc20/ERC20PermitLight.sol";
import "../equity/IGovernance.sol";
import "../equity/Equity.sol";
import "./IBasicFrankencoin.sol";
import "./IFrankencoin.sol";
/**
* @title FrankenCoin
* @notice The Frankencoin (ZCHF) is an ERC-20 token that is designed to track the value of the Swiss franc.
* It is not upgradable, but open to arbitrary minting plugins. These are automatically accepted if none of the
* qualified pool share holders casts a veto, leading to a flexible but conservative governance.
*/
contract Frankencoin is ERC20PermitLight, IFrankencoin {
/**
* @notice Minimal fee and application period when suggesting a new minter.
*/
uint256 public constant MIN_FEE = 1000 * (10 ** 18);
uint256 public immutable MIN_APPLICATION_PERIOD; // for example 10 days
/**
* @notice The contract that holds the reserve.
*/
IGovernance public immutable override reserve;
/**
* @notice How much of the reserve belongs to the minters. Everything else belongs to the pool share holders.
* Stored with 6 additional digits of accuracy so no rounding is necessary when dealing with parts per
* million (ppm) in reserve calculations.
*/
uint256 private minterReserveE6;
/**
* @notice Map of minters to approval time stamps. If the time stamp is in the past, the minter contract is allowed
* to mint Frankencoins.
*/
mapping(address minter => uint256 validityStart) public minters;
/**
* @notice List of positions that are allowed to mint and the minter that registered them.
*/
mapping(address position => address registeringMinter) public positions;
event MinterApplied(address indexed minter, uint256 applicationPeriod, uint256 applicationFee, string message);
event MinterDenied(address indexed minter, string message);
event Loss(address indexed reportingMinter, uint256 amount);
event Profit(address indexed reportingMinter, uint256 amount);
error PeriodTooShort();
error FeeTooLow();
error AlreadyRegistered();
error NotMinter();
error TooLate();
modifier minterOnly() {
if (!isMinter(msg.sender) && !isMinter(positions[msg.sender])) revert NotMinter();
_;
}
/**
* @notice Initiates the Frankencoin with the provided minimum application period for new plugins
* in seconds, for example 10 days, i.e. 3600*24*10 = 864000
*/
constructor(uint256 _minApplicationPeriod) ERC20(18) {
MIN_APPLICATION_PERIOD = _minApplicationPeriod;
reserve = new Equity(this);
}
function name() external pure override returns (string memory) {
return "Frankencoin";
}
function symbol() external pure override returns (string memory) {
return "ZCHF";
}
function initialize(address _minter, string calldata _message) external {
require(totalSupply() == 0 && (Equity(address(reserve)).totalSupply() == 0));
minters[_minter] = block.timestamp;
emit MinterApplied(_minter, 0, 0, _message);
}
/**
* @notice Publicly accessible method to suggest a new way of minting Frankencoin.
* @dev The caller has to pay an application fee that is irrevocably lost even if the new minter is vetoed.
* The caller must assume that someone will veto the new minter unless there is broad consensus that the new minter
* adds value to the Frankencoin system. Complex proposals should have application periods and applications fees
* above the minimum. It is assumed that over time, informal ways to coordinate on new minters emerge. The message
* parameter might be useful for initiating further communication. Maybe it contains a link to a website describing
* the proposed minter.
*
* @param _minter An address that is given the permission to mint Frankencoins
* @param _applicationPeriod The time others have to veto the suggestion, at least MIN_APPLICATION_PERIOD
* @param _applicationFee The fee paid by the caller, at least MIN_FEE
* @param _message An optional human readable message to everyone watching this contract
*/
function suggestMinter(
address _minter,
uint256 _applicationPeriod,
uint256 _applicationFee,
string calldata _message
) external override {
if (_applicationPeriod < MIN_APPLICATION_PERIOD) revert PeriodTooShort();
if (_applicationFee < MIN_FEE) revert FeeTooLow();
if (minters[_minter] != 0) revert AlreadyRegistered();
_collectProfits(address(this), msg.sender, _applicationFee);
minters[_minter] = block.timestamp + _applicationPeriod;
emit MinterApplied(_minter, _applicationPeriod, _applicationFee, _message);
}
/**
* @notice Make the system more user friendly by skipping the allowance in many cases.
* @dev We trust minters and the positions they have created to mint and burn as they please, so
* giving them arbitrary allowances does not pose an additional risk.
*/
function _allowance(address owner, address spender) internal view override returns (uint256) {
uint256 explicit = super._allowance(owner, spender);
if (explicit > 0) {
return explicit; // don't waste gas checking minter
} else if (isMinter(spender) || isMinter(getPositionParent(spender)) || spender == address(reserve)) {
return INFINITY;
} else {
return 0;
}
}
/**
* @notice The reserve provided by the owners of collateralized positions.
* @dev The minter reserve can be used to cover losses after the equity holders have been wiped out.
*/
function minterReserve() public view returns (uint256) {
return minterReserveE6 / 1000000;
}
/**
* @notice Allows minters to register collateralized debt positions, thereby giving them the ability to mint Frankencoins.
* @dev It is assumed that the responsible minter that registers the position ensures that the position can be trusted.
*/
function registerPosition(address _position) external override {
if (!isMinter(msg.sender)) revert NotMinter();
positions[_position] = msg.sender;
}
/**
* @notice The amount of equity of the Frankencoin system in ZCHF, owned by the holders of Frankencoin Pool Shares.
* @dev Note that the equity contract technically holds both the minter reserve as well as the equity, so the minter
* reserve must be subtracted. All fees and other kind of income is added to the Equity contract and essentially
* constitutes profits attributable to the pool share holders.
*/
function equity() public view returns (uint256) {
uint256 balance = balanceOf(address(reserve));
uint256 minReserve = minterReserve();
if (balance <= minReserve) {
return 0;
} else {
return balance - minReserve;
}
}
/**
* @notice Qualified pool share holders can deny minters during the application period.
* @dev Calling this function is relatively cheap thanks to the deletion of a storage slot.
*/
function denyMinter(address _minter, address[] calldata _helpers, string calldata _message) external override {
if (block.timestamp > minters[_minter]) revert TooLate();
reserve.checkQualified(msg.sender, _helpers);
delete minters[_minter];
emit MinterDenied(_minter, _message);
}
/**
* @notice Mints the provided amount of ZCHF to the target address, automatically forwarding
* the minting fee and the reserve to the right place.
*/
function mintWithReserve(
address _target,
uint256 _amount,
uint32 _reservePPM,
uint32 _feesPPM
) external override minterOnly {
uint256 usableMint = (_amount * (1000_000 - _feesPPM - _reservePPM)) / 1000_000; // rounding down is fine
_mint(_target, usableMint);
_mint(address(reserve), _amount - usableMint); // rest goes to equity as reserves or as fees
minterReserveE6 += _amount * _reservePPM;
emit Profit(msg.sender, (_feesPPM * _amount) / 1000_000);
}
function mint(address _target, uint256 _amount) external override minterOnly {
_mint(_target, _amount);
}
/**
* Anyone is allowed to burn their ZCHF.
*/
function burn(uint256 _amount) external {
_burn(msg.sender, _amount);
}
/**
* @notice Burn someone elses ZCHF.
*/
function burnFrom(address _owner, uint256 _amount) external override minterOnly {
_burn(_owner, _amount);
}
/**
* @notice Burn that amount without reclaiming the reserve, but freeing it up and thereby essentially donating it to the
* pool share holders. This can make sense in combination with 'coverLoss', i.e. when it is the pool share
* holders that bear the risk and depending on the outcome they make a profit or a loss.
*
* Design rule: Minters calling this method are only allowed to so for tokens amounts they previously minted with
* the same _reservePPM amount.
*
* For example, if someone minted 50 ZCHF earlier with a 20% reserve requirement (200000 ppm), they got 40 ZCHF
* and paid 10 ZCHF into the reserve. Now they want to repay the debt by burning 50 ZCHF. When doing so using this
* method, 50 ZCHF get burned and on top of that, 10 ZCHF previously assigned to the minter's reserved are
* reassigned to the pool share holders.
*
* CS-ZCHF2-009: the Profit event can overstate profits in case there is no equity capital left.
*/
function burnWithoutReserve(uint256 amount, uint32 reservePPM) public override minterOnly {
_burn(msg.sender, amount);
uint256 reserveReduction = amount * reservePPM;
if (reserveReduction > minterReserveE6) {
emit Profit(msg.sender, minterReserveE6 / 1000_000);
minterReserveE6 = 0; // should never happen, but we want robust behavior in case it does
} else {
minterReserveE6 -= reserveReduction;
emit Profit(msg.sender, reserveReduction / 1000_000);
}
}
/**
* @notice Burns the provided number of tokens plus whatever reserves are associated with that amount given the reserve
* requirement. The caller is only allowed to use this method for tokens also minted through the caller with the
* same _reservePPM amount.
*
* Example: the calling contract has previously minted 100 ZCHF with a reserve ratio of 20% (i.e. 200000 ppm).
* Now they have 41 ZCHF that they do not need so they decide to repay that amount. Assuming the reserves are
* only 90% covered, the call to burnWithReserve will burn the 41 plus 9 from the reserve, reducing the outstanding
* 'debt' of the caller by 50 ZCHF in total. This total is returned by the method so the caller knows how much less
* they owe.
*/
function burnWithReserve(
uint256 _amountExcludingReserve,
uint32 _reservePPM
) external override minterOnly returns (uint256) {
uint256 freedAmount = calculateFreedAmount(_amountExcludingReserve, _reservePPM); // 50 in the example
minterReserveE6 -= freedAmount * _reservePPM; // reduce reserve requirements by original ratio
_transfer(address(reserve), msg.sender, freedAmount - _amountExcludingReserve); // collect assigned reserve
_burn(msg.sender, freedAmount); // burn the rest of the freed amount
return freedAmount;
}
/**
* @notice Burns the target amount taking the tokens to be burned from the payer and the payer's reserve.
* Only use this method for tokens also minted by the caller with the same _reservePPM.
*
* Example: the calling contract has previously minted 100 ZCHF with a reserve ratio of 20% (i.e. 200000 ppm).
* To burn half of that again, the minter calls burnFrom with a target amount of 50 ZCHF. Assuming that reserves
* are only 90% covered, this call will deduct 41 ZCHF from the payer's balance and 9 from the reserve, while
* reducing the minter reserve by 10.
*/
function burnFromWithReserve(
address payer,
uint256 targetTotalBurnAmount,
uint32 reservePPM
) external override minterOnly returns (uint256) {
uint256 assigned = calculateAssignedReserve(targetTotalBurnAmount, reservePPM);
_transfer(address(reserve), payer, assigned); // send reserve to owner
_burn(payer, targetTotalBurnAmount); // and burn the full amount from the owner's address
minterReserveE6 -= targetTotalBurnAmount * reservePPM; // reduce reserve requirements by original ratio
return assigned;
}
/**
* @notice Calculates the reserve attributable to someone who minted the given amount with the given reserve requirement.
* Under normal circumstances, this is just the reserve requirement multiplied by the amount. However, after a
* severe loss of capital that burned into the minter's reserve, this can also be less than that.
*/
function calculateAssignedReserve(uint256 mintedAmount, uint32 _reservePPM) public view returns (uint256) {
uint256 theoreticalReserve = (_reservePPM * mintedAmount) / 1000000;
uint256 currentReserve = balanceOf(address(reserve));
uint256 minterReserve_ = minterReserve();
if (currentReserve < minterReserve_) {
// not enough reserves, owner has to take a loss
return (theoreticalReserve * currentReserve) / minterReserve_;
} else {
return theoreticalReserve;
}
}
/**
* @notice Calculate the amount that is freed when returning amountExcludingReserve given a reserve ratio of reservePPM,
* taking into account potential losses. Example values in the comments.
*/
function calculateFreedAmount(
uint256 amountExcludingReserve /* 41 */,
uint32 reservePPM /* 20% */
) public view returns (uint256) {
uint256 currentReserve = balanceOf(address(reserve)); // 18, 10% below what we should have
uint256 minterReserve_ = minterReserve(); // 20
uint256 adjustedReservePPM = currentReserve < minterReserve_
? (reservePPM * currentReserve) / minterReserve_
: reservePPM; // 18%
return (1000000 * amountExcludingReserve) / (1000000 - adjustedReservePPM); // 41 / (1-18%) = 50
}
/**
* @notice Notify the Frankencoin that a minter lost economic access to some coins. This does not mean that the coins are
* literally lost. It just means that some ZCHF will likely never be repaid and that in order to bring the system
* back into balance, the lost amount of ZCHF must be removed from the reserve instead.
*
* For example, if a minter printed 1 million ZCHF for a mortgage and the mortgage turned out to be unsound with
* the house only yielding 800'000 in the subsequent auction, there is a loss of 200'000 that needs to be covered
* by the reserve.
*/
function coverLoss(address source, uint256 _amount) external override minterOnly {
uint256 reserveLeft = balanceOf(address(reserve));
if (reserveLeft >= _amount) {
_transfer(address(reserve), source, _amount);
} else {
_transfer(address(reserve), source, reserveLeft);
_mint(source, _amount - reserveLeft);
}
emit Loss(source, _amount);
}
function collectProfits(address source, uint256 _amount) external override minterOnly {
_collectProfits(msg.sender, source, _amount);
}
function _collectProfits(address minter, address source, uint256 _amount) internal {
_transfer(source, address(reserve), _amount);
emit Profit(minter, _amount);
}
/**
* @notice Returns true if the address is an approved minter.
*/
function isMinter(address _minter) public view override returns (bool) {
return minters[_minter] != 0 && block.timestamp >= minters[_minter];
}
/**
* @notice Returns the address of the minter that created this position or null if the provided address is unknown.
*/
function getPositionParent(address _position) public view override returns (address) {
return positions[_position];
}
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../erc20/IERC20.sol";
import "../equity/IGovernance.sol";
interface IBasicFrankencoin is IERC20 {
function MIN_FEE() external view returns (uint256);
function MIN_APPLICATION_PERIOD() external view returns (uint256);
function suggestMinter(
address _minter,
uint256 _applicationPeriod,
uint256 _applicationFee,
string calldata _message
) external;
function registerPosition(address position) external;
function denyMinter(address minter, address[] calldata helpers, string calldata message) external;
function reserve() external view returns (IGovernance);
function isMinter(address minter) external view returns (bool);
function getPositionParent(address position) external view returns (address);
function mint(address target, uint256 amount) external;
function burnFrom(address target, uint256 amount) external;
function burn(uint256 amount) external;
function coverLoss(address source, uint256 amount) external;
function collectProfits(address source, uint256 _amount) external;
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "../equity/IGovernance.sol";
import "./IBasicFrankencoin.sol";
interface IFrankencoin is IBasicFrankencoin {
function minterReserve() external view returns (uint256);
function calculateAssignedReserve(uint256 mintedAmount, uint32 _reservePPM) external view returns (uint256);
function calculateFreedAmount(uint256 amountExcludingReserve, uint32 reservePPM) external view returns (uint256);
function equity() external view returns (uint256);
function mintWithReserve(address target, uint256 amount, uint32 reservePPM, uint32 feePPM) external;
function burnWithoutReserve(uint256 amountIncludingReserve, uint32 reservePPM) external;
function burnFromWithReserve(address payer, uint256 targetTotalBurnAmount, uint32 _reservePPM) external returns (uint256);
function burnWithReserve(uint256 amountExcludingReserve, uint32 reservePPM) external returns (uint256);
}// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
/**
* @title Functions for share valuation
*/
contract MathUtil {
uint256 internal constant ONE_DEC18 = 10 ** 18;
// Let's go for 12 digits of precision (18-6)
uint256 internal constant THRESH_DEC18 = 10 ** 6;
/**
* @notice Cubic root with Halley approximation
* Number 1e18 decimal
* @param _v number for which we calculate x**(1/3)
* @return returns _v**(1/3)
*/
function _cubicRoot(uint256 _v) internal pure returns (uint256) {
// Good first guess for _v slightly above 1.0, which is often the case in the Frankencoin system
uint256 x = _v > ONE_DEC18 && _v < 10 ** 19 ? (_v - ONE_DEC18) / 3 + ONE_DEC18 : ONE_DEC18;
uint256 diff;
do {
uint256 powX3 = _mulD18(_mulD18(x, x), x);
uint256 xnew = x * (powX3 + 2 * _v) / (2 * powX3 + _v);
diff = xnew > x ? xnew - x : x - xnew;
x = xnew;
} while (diff > THRESH_DEC18);
return x;
}
function _mulD18(uint256 _a, uint256 _b) internal pure returns (uint256) {
return (_a * _b) / ONE_DEC18;
}
function _divD18(uint256 _a, uint256 _b) internal pure returns (uint256) {
return (_a * ONE_DEC18) / _b;
}
function _power3(uint256 _x) internal pure returns (uint256) {
return _mulD18(_mulD18(_x, _x), _x);
}
function _min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
}{
"optimizer": {
"enabled": true,
"runs": 200
},
"evmVersion": "paris",
"outputSelection": {
"*": {
"*": [
"evm.bytecode",
"evm.deployedBytecode",
"devdoc",
"userdoc",
"metadata",
"abi"
]
}
},
"metadata": {
"useLiteralContent": true
},
"libraries": {}
}Contract Security Audit
- No Contract Security Audit Submitted- Submit Audit Here
Contract ABI
API[{"inputs":[{"internalType":"contract IGovernance","name":"reserve_","type":"address"},{"internalType":"address","name":"router_","type":"address"},{"internalType":"uint256","name":"_minApplicationPeriod","type":"uint256"},{"internalType":"address","name":"_linkToken","type":"address"},{"internalType":"uint64","name":"_mainnetChainSelector","type":"uint64"},{"internalType":"address","name":"_bridgeAccounting","type":"address"},{"internalType":"address","name":"_ccipAdmin","type":"address"}],"stateMutability":"nonpayable","type":"constructor"},{"inputs":[],"name":"AlreadyInitialized","type":"error"},{"inputs":[],"name":"AlreadyRegistered","type":"error"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"allowance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"uint256","name":"balance","type":"uint256"},{"internalType":"uint256","name":"needed","type":"uint256"}],"name":"ERC20InsufficientBalance","type":"error"},{"inputs":[],"name":"FeeTooLow","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"InsufficientFeeTokenAllowance","type":"error"},{"inputs":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"fee","type":"uint256"}],"name":"InsufficientFeeTokens","type":"error"},{"inputs":[],"name":"InvalidInput","type":"error"},{"inputs":[],"name":"NotMinter","type":"error"},{"inputs":[],"name":"PeriodTooShort","type":"error"},{"inputs":[],"name":"TooLate","type":"error"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"profit","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"losses","type":"uint256"}],"name":"AccountingSynchronized","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint64","name":"toChain","type":"uint64"},{"indexed":true,"internalType":"bytes","name":"to","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"string","name":"ref","type":"string"}],"name":"CrossTransfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reportingMinter","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Loss","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"minter","type":"address"},{"indexed":false,"internalType":"uint256","name":"applicationPeriod","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"applicationFee","type":"uint256"},{"indexed":false,"internalType":"string","name":"message","type":"string"}],"name":"MinterApplied","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"minter","type":"address"},{"indexed":false,"internalType":"string","name":"message","type":"string"}],"name":"MinterDenied","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"reportingMinter","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"}],"name":"Profit","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":false,"internalType":"uint64","name":"toChain","type":"uint64"},{"indexed":true,"internalType":"bytes","name":"to","type":"bytes"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"string","name":"ref","type":"string"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"inputs":[],"name":"BRIDGE_ACCOUNTING","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CCIP_ADMIN","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"LINK","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MAINNET_CHAIN_SELECTOR","outputs":[{"internalType":"uint64","name":"","type":"uint64"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_APPLICATION_PERIOD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_FEE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"ROUTER","outputs":[{"internalType":"contract IRouterClient","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"accruedLoss","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"burn","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_owner","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"burnFrom","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_minterOrPosition","type":"address"}],"name":"canMint","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"source","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"collectProfits","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"source","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"coverLoss","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_minter","type":"address"},{"internalType":"address[]","name":"_helpers","type":"address[]"},{"internalType":"string","name":"_message","type":"string"}],"name":"denyMinter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"getCCIPAdmin","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"nativeToken","type":"bool"}],"name":"getCCIPFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"bytes","name":"target","type":"bytes"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"nativeToken","type":"bool"},{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"name":"getCCIPFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bool","name":"nativeToken","type":"bool"},{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"name":"getCCIPFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_position","type":"address"}],"name":"getPositionParent","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"getSynchronizeAccountingData","outputs":[{"internalType":"uint256","name":"","type":"uint256"},{"internalType":"uint256","name":"","type":"uint256"},{"components":[{"internalType":"address","name":"token","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"internalType":"struct Client.EVMTokenAmount[]","name":"","type":"tuple[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"nativeToken","type":"bool"},{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"name":"getSynchronizeAccountingFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bool","name":"nativeToken","type":"bool"}],"name":"getSynchronizeAccountingFee","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address[]","name":"_minters","type":"address[]"},{"internalType":"string[]","name":"_messages","type":"string[]"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_minter","type":"address"}],"name":"isMinter","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_target","type":"address"},{"internalType":"uint256","name":"_amount","type":"uint256"}],"name":"mint","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"minter","type":"address"}],"name":"minters","outputs":[{"internalType":"uint256","name":"validityStart","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"nonce","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"position","type":"address"}],"name":"positions","outputs":[{"internalType":"address","name":"registeringMinter","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_position","type":"address"}],"name":"registerPosition","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"reserve","outputs":[{"internalType":"contract IGovernance","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"_minter","type":"address"},{"internalType":"uint256","name":"_applicationPeriod","type":"uint256"},{"internalType":"uint256","name":"_applicationFee","type":"uint256"},{"internalType":"string","name":"_message","type":"string"}],"name":"suggestMinter","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"pure","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"bool","name":"allowOutOfOrderExecution","type":"bool"}],"internalType":"struct Client.EVMExtraArgsV2","name":"extraArgs","type":"tuple"}],"name":"synchronizeAccounting","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"name":"synchronizeAccounting","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"synchronizeAccounting","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"bytes","name":"recipient","type":"bytes"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"extraArgs","type":"bytes"},{"internalType":"string","name":"ref","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"components":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"bool","name":"allowOutOfOrderExecution","type":"bool"}],"internalType":"struct Client.EVMExtraArgsV2","name":"extraArgs","type":"tuple"}],"name":"transfer","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"bytes","name":"target","type":"bytes"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"extraArgs","type":"bytes"}],"name":"transfer","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"ref","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"components":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"bool","name":"allowOutOfOrderExecution","type":"bool"}],"internalType":"struct Client.EVMExtraArgsV2","name":"extraArgs","type":"tuple"},{"internalType":"string","name":"ref","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"target","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"ref","type":"string"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"ref","type":"string"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"bytes","name":"recipient","type":"bytes"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"bytes","name":"extraArgs","type":"bytes"},{"internalType":"string","name":"ref","type":"string"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"string","name":"ref","type":"string"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"uint64","name":"targetChain","type":"uint64"},{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"},{"components":[{"internalType":"uint256","name":"gasLimit","type":"uint256"},{"internalType":"bool","name":"allowOutOfOrderExecution","type":"bool"}],"internalType":"struct Client.EVMExtraArgsV2","name":"extraArgs","type":"tuple"},{"internalType":"string","name":"ref","type":"string"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"payable","type":"function"}]Contract Creation Code
6101806040523480156200001257600080fd5b50604051620039e5380380620039e5833981016040819052620000359162000094565b60126080526001600160a01b0395861660a05292851660c05260e093909352938316610160526001600160401b039093166101205281166101005216610140526200013e565b6001600160a01b03811681146200009157600080fd5b50565b600080600080600080600060e0888a031215620000b057600080fd5b8751620000bd816200007b565b6020890151909750620000d0816200007b565b604089015160608a01519197509550620000ea816200007b565b60808901519094506001600160401b03811681146200010857600080fd5b60a08901519093506200011b816200007b565b60c08901519092506200012e816200007b565b8091505092959891949750929550565b60805160a05160c05160e051610100516101205161014051610160516137926200025360003960008181610a220152818161119b0152818161139901528181611651015281816116b6015281816116e901528181611aab015281816126f60152818161274201528181612779015261289801526000818161043a01526107f301526000818161098301528181610f56015261144901526000818161084901528181610f0001526113f40152600081816104ca01526114f401526000818161051e0152818161261c01526128f40152600081816105b801528181610d88015281816113c501528181611c88015281816122aa0152818161233f0152818161250401526126780152600061057201526137926000f3fe6080604052600436106103765760003560e01c806379cc6790116101d1578063c2ba474411610102578063d1fa5e98116100a0578063e0fb2f781161006f578063e0fb2f7814610aff578063eca79fef14610b23578063f46eccc414610b36578063f507469014610b6357600080fd5b8063d1fa5e9814610a7f578063d505accf14610a9f578063dd62ed3e14610abf578063e043684214610adf57600080fd5b8063cd3293de116100dc578063cd3293de14610a10578063d15dcd6214610a44578063d1975e6a14610a57578063d1a15ff114610a5f57600080fd5b8063c2ba4744146109bd578063c3ccc9ef146109dd578063c6fede7d146109fd57600080fd5b80639b404da61161016f578063aa271e1a11610149578063aa271e1a146108f8578063aa5dd7f114610918578063b52c696d14610951578063c038e2981461097157600080fd5b80639b404da6146108985780639f3b3ea3146108b8578063a9059cbb146108d857600080fd5b80638fd6a6ac116101ab5780638fd6a6ac146107e4578063912fa6161461081757806394b936901461083757806395d89b411461086b57600080fd5b806379cc6790146107845780637ecebe00146107a4578063809eb368146107d157600080fd5b80633644e515116102ab578063604248ab116102495780636ebdb8ee116102235780636ebdb8ee146106fe57806370a082311461071e578063737079811461075457806376c7a3c71461076757600080fd5b8063604248ab146106b8578063642884df146106cb5780636b2242e1146106eb57600080fd5b806352d48abe1161028557806352d48abe1461062f57806353c521e61461064257806355f575101461066257806356b8c7241461069857600080fd5b80633644e515146105da57806340c10f19146105ef57806342966c681461060f57600080fd5b806319e3c428116103185780631b6b6d23116102f25780631b6b6d231461050c57806323b872dd14610540578063313ce5671461056057806332fe7b26146105a657600080fd5b806319e3c428146104a35780631a46c7e9146104b85780631af716ba146104ec57600080fd5b806310f9c77e1161035457806310f9c77e146104155780631183801014610428578063158ef93e1461047457806318160ddd1461048e57600080fd5b806306fdde031461037b578063095ea7b3146103c15780630e57bc7d146103f1575b600080fd5b34801561038757600080fd5b5060408051808201909152600b81526a233930b735b2b731b7b4b760a91b60208201525b6040516103b891906129a4565b60405180910390f35b3480156103cd57600080fd5b506103e16103dc3660046129d3565b610b76565b60405190151581526020016103b8565b3480156103fd57600080fd5b5061040760065481565b6040519081526020016103b8565b6103e1610423366004612af7565b610b8d565b34801561043457600080fd5b5061045c7f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160a01b0390911681526020016103b8565b34801561048057600080fd5b506007546103e19060ff1681565b34801561049a57600080fd5b50600254610407565b6104b66104b1366004612bb7565b610bab565b005b3480156104c457600080fd5b506104077f000000000000000000000000000000000000000000000000000000000000000081565b3480156104f857600080fd5b506103e1610507366004612c05565b610c1b565b34801561051857600080fd5b5061045c7f000000000000000000000000000000000000000000000000000000000000000081565b34801561054c57600080fd5b506103e161055b366004612c73565b610c8e565b34801561056c57600080fd5b506105947f000000000000000000000000000000000000000000000000000000000000000081565b60405160ff90911681526020016103b8565b3480156105b257600080fd5b5061045c7f000000000000000000000000000000000000000000000000000000000000000081565b3480156105e657600080fd5b50610407610cb0565b3480156105fb57600080fd5b506104b661060a3660046129d3565b610d09565b34801561061b57600080fd5b506104b661062a366004612caf565b610d6a565b6104b661063d366004612cc8565b610d77565b34801561064e57600080fd5b5061040761065d366004612d53565b610e22565b34801561066e57600080fd5b5061045c61067d366004612da2565b6005602052600090815260409020546001600160a01b031681565b3480156106a457600080fd5b506103e16106b3366004612dbd565b610e51565b6103e16106c6366004612e16565b610eb8565b3480156106d757600080fd5b506104076106e6366004612ecf565b610ee2565b6104b66106f9366004612f1e565b610f86565b34801561070a57600080fd5b506104b66107193660046129d3565b610f9b565b34801561072a57600080fd5b50610407610739366004612da2565b6001600160a01b031660009081526020819052604090205490565b6103e1610762366004612f3a565b610ff9565b34801561077357600080fd5b50610407683635c9adc5dea0000081565b34801561079057600080fd5b506104b661079f3660046129d3565b611022565b3480156107b057600080fd5b506104076107bf366004612da2565b60036020526000908152604090205481565b6103e16107df366004612fa7565b61107f565b3480156107f057600080fd5b507f000000000000000000000000000000000000000000000000000000000000000061045c565b34801561082357600080fd5b50610407610832366004613007565b6110a7565b34801561084357600080fd5b5061045c7f000000000000000000000000000000000000000000000000000000000000000081565b34801561087757600080fd5b506040805180820190915260048152632d21a42360e11b60208201526103ab565b3480156108a457600080fd5b506104b66108b33660046130dd565b61114b565b3480156108c457600080fd5b506104076108d336600461314c565b611260565b3480156108e457600080fd5b506103e16108f33660046129d3565b611281565b34801561090457600080fd5b506103e1610913366004612da2565b61128e565b34801561092457600080fd5b5061045c610933366004612da2565b6001600160a01b039081166000908152600560205260409020541690565b34801561095d57600080fd5b506104b661096c366004612da2565b6112cf565b34801561097d57600080fd5b506109a57f000000000000000000000000000000000000000000000000000000000000000081565b6040516001600160401b0390911681526020016103b8565b3480156109c957600080fd5b506103e16109d8366004612da2565b61131e565b3480156109e957600080fd5b506104076109f83660046131b9565b611353565b6104b6610a0b3660046131d6565b61136e565b348015610a1c57600080fd5b5061045c7f000000000000000000000000000000000000000000000000000000000000000081565b6104b6610a52366004613212565b6114b0565b6104b66114d8565b348015610a6b57600080fd5b506104b6610a7a366004613230565b6114f2565b348015610a8b57600080fd5b506104b6610a9a3660046129d3565b6115fc565b348015610aab57600080fd5b506104b6610aba36600461327a565b611757565b348015610acb57600080fd5b50610407610ada3660046132ed565b61194c565b348015610aeb57600080fd5b506104b6610afa366004613320565b61195f565b348015610b0b57600080fd5b50610b14611a9f565b6040516103b8939291906133cf565b6103e1610b313660046133ee565b611bb8565b348015610b4257600080fd5b50610407610b51366004612da2565b60046020526000908152604090205481565b6103e1610b7136600461340f565b611bdf565b6000610b83338484611c15565b5060015b92915050565b6000610b9e87338888888888611c77565b5060019695505050505050565b610c1584610bb885611d2e565b8461063d610bcb3687900387018761348c565b6040805182516024820152602092830151151560448083019190915282518083039091018152606490910190915290810180516001600160e01b0316630181dcf160e41b17905290565b50505050565b6000610c28863386611d5b565b610c33868686611dc3565b846001600160a01b0316866001600160a01b03167fcd6e659e4c2e75c3bfe47fecaccf39aeb368116a0ee52afb532e07f6cba6c0d1868686604051610c7a9392919061350a565b60405180910390a350600195945050505050565b6000610c9b848484611dc3565b610ca6843384611d5b565b5060019392505050565b604080517f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a794692186020820152469181019190915230606082015260009060800160405160208183030381529060405280519060200120905090565b610d123361128e565b158015610d3e575033600090815260056020526040902054610d3c906001600160a01b031661128e565b155b15610d5c57604051633e34a41b60e21b815260040160405180910390fd5b610d668282611eda565b5050565b610d743382611f77565b50565b610d82333084611dc3565b610dad307f000000000000000000000000000000000000000000000000000000000000000084611c15565b610dc184610dbc858585611ff9565b612098565b505082604051610dd19190613524565b604080519182900382206001600160401b0387168352602083018590529133917f7895812be6b9a01617f760055105f506b5e2f6dff79ff3170e4fdb3391fc0023910160405180910390a350505050565b6000610e4885610e3186611d2e565b8585604051806020016040528060008152506110a7565b95945050505050565b6000610e5e338686611dc3565b846001600160a01b0316336001600160a01b03167fcd6e659e4c2e75c3bfe47fecaccf39aeb368116a0ee52afb532e07f6cba6c0d1868686604051610ea59392919061350a565b60405180910390a3506001949350505050565b6000610ec5873387611d5b565b610ed488888888888888611c77565b506001979650505050505050565b600080600080610ef0611a9f565b9250925092506000610f4f610f247f0000000000000000000000000000000000000000000000000000000000000000611d2e565b6040805160208101889052908101869052606001604051602081830303815290604052848a8a6125f0565b9050610f7b7f00000000000000000000000000000000000000000000000000000000000000008261265e565b979650505050505050565b610d74610a0b610bcb3684900384018461348c565b610fa43361128e565b158015610fd0575033600090815260056020526040902054610fce906001600160a01b031661128e565b155b15610fee57604051633e34a41b60e21b815260040160405180910390fd5b610d663383836126f0565b6000610f7b8761100888611d2e565b8761101b610bcb368a90038a018a61348c565b8787610b8d565b61102b3361128e565b158015611057575033600090815260056020526040902054611055906001600160a01b031661128e565b155b1561107557604051633e34a41b60e21b815260040160405180910390fd5b610d668282611f77565b6000610f7b878761108f88611d2e565b87604051806020016040528060008152508888610eb8565b604080516001808252818301909252600091829190816020015b60408051808201909152600080825260208201528152602001906001900390816110c15790505090506040518060400160405280306001600160a01b03168152602001868152508160008151811061111b5761111b613540565b6020026020010181905250610f7b8761114688604051806020016040528060008152508589896125f0565b61265e565b6001600160a01b0385166000908152600460205260409020544211156111845760405163ecdd1c2960e01b815260040160405180910390fd5b60405163352e3a8360e01b81526001600160a01b037f0000000000000000000000000000000000000000000000000000000000000000169063352e3a83906111d490339088908890600401613556565b60006040518083038186803b1580156111ec57600080fd5b505afa158015611200573d6000803e3d6000fd5b505050506001600160a01b03851660008181526004602052604080822091909155517fd60c86e83346fdbe3124cb7d1cba32973a9f62d05c4ec801bfe6c09d103803819061125190859085906135b0565b60405180910390a25050505050565b60006112778661126f87611d2e565b8686866110a7565b9695505050505050565b6000610b83338484611dc3565b6001600160a01b03811660009081526004602052604081205415801590610b875750506001600160a01b031660009081526004602052604090205442101590565b6112d83361128e565b6112f557604051633e34a41b60e21b815260040160405180910390fd5b6001600160a01b0316600090815260056020526040902080546001600160a01b03191633179055565b60006113298261128e565b80610b8757506001600160a01b03808316600090815260056020526040902054610b87911661128e565b6000610b878260405180602001604052806000815250610ee2565b600080600061137b611a9f565b91945092509050811561138e5760006006555b82156113ea576113bf7f00000000000000000000000000000000000000000000000000000000000000003085611dc3565b6113ea307f000000000000000000000000000000000000000000000000000000000000000085611c15565b60006114426114187f0000000000000000000000000000000000000000000000000000000000000000611d2e565b604080516020810188905290810186905260600160405160208183030381529060405284886127e1565b905061146e7f000000000000000000000000000000000000000000000000000000000000000082612098565b505060408051858152602081018590527f780ad21fb95421fd83047aa9bb3506c2aea4c8d383daa4476e66d56606632dc2910160405180910390a15050505050565b6114d3836114bd84611d2e565b8360405180602001604052806000815250610d77565b505050565b6114f06040518060200160405280600081525061136e565b565b7f000000000000000000000000000000000000000000000000000000000000000084101561153357604051631260d0af60e31b815260040160405180910390fd5b683635c9adc5dea0000083101561155d5760405163732f941360e01b815260040160405180910390fd5b6001600160a01b0385166000908152600460205260409020541561159457604051630ea075bf60e21b815260040160405180910390fd5b61159f3033856126f0565b6115a984426135da565b6001600160a01b038616600081815260046020526040908190209290925590517f97326258efdae63280617ca33884e507791c2abeed7b82acd77f1853394ef94b906112519087908790879087906135ed565b6116053361128e565b15801561163157503360009081526005602052604090205461162f906001600160a01b031661128e565b155b1561164f57604051633e34a41b60e21b815260040160405180910390fd5b7f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316600090815260208190526040902054808211156116e45761169a818361360d565b600660008282546116ab91906135da565b909155506116e490507f00000000000000000000000000000000000000000000000000000000000000006116df838561360d565b611eda565b61170f7f00000000000000000000000000000000000000000000000000000000000000008484611dc3565b826001600160a01b03167f72fba0ba07d937c660a3130fca36005c0e476cb97b6f00de413976e37eba95018360405161174a91815260200190565b60405180910390a2505050565b428410156117ac5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b600060016117b8610cb0565b6001600160a01b038a811660008181526003602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa1580156118c4573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906118fa5750876001600160a01b0316816001600160a01b0316145b6119375760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b60448201526064016117a3565b611942818888611c15565b5050505050505050565b60006119588383612828565b9392505050565b60075460ff16156119825760405162dc149f60e41b815260040160405180910390fd5b8281146119a25760405163b4fa3fb360e01b815260040160405180910390fd5b60005b83811015611a8b5742600460008787858181106119c4576119c4613540565b90506020020160208101906119d99190612da2565b6001600160a01b03168152602081019190915260400160002055848482818110611a0557611a05613540565b9050602002016020810190611a1a9190612da2565b6001600160a01b03167f97326258efdae63280617ca33884e507791c2abeed7b82acd77f1853394ef94b600080868686818110611a5957611a59613540565b9050602002810190611a6b9190613620565b604051611a7b94939291906135ed565b60405180910390a26001016119a5565b50506007805460ff19166001179055505050565b60008060606000611ae57f00000000000000000000000000000000000000000000000000000000000000006001600160a01b031660009081526020819052604090205490565b604080516000808252602082019092529192509081611b26565b6040805180820190915260008082526020820152815260200190600190039081611aff5790505b5090508115611baa5760408051600180825281830190925290816020015b6040805180820190915260008082526020820152815260200190600190039081611b445790505090506040518060400160405280306001600160a01b031681526020018381525081600081518110611b9e57611b9e613540565b60200260200101819052505b600654919591945092509050565b600061127786611bc787611d2e565b86604051806020016040528060008152508787610b8d565b6000611c098888611bef89611d2e565b88611c02610bcb368b90038b018b61348c565b8888610eb8565b98975050505050505050565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b611c82863086611dc3565b611cad307f000000000000000000000000000000000000000000000000000000000000000086611c15565b611cbc87610dbc878787611ff9565b505084604051611ccc9190613524565b6040518091039020866001600160a01b0316336001600160a01b03167f06666856ab7ec8eca6666144744c8bdd710c19b0aba4a36495361c82a01099288a888787604051611d1d9493929190613666565b60405180910390a450505050505050565b604080516001600160a01b0383166020820152606091016040516020818303038152906040529050919050565b6000611d678484612828565b9050600160ff1b811015610c155781811015611daf57604051637dc7a0d960e11b81526001600160a01b038516600482015260248101829052604481018390526064016117a3565b610c158484611dbe858561360d565b611c15565b6001600160a01b038216611dd657600080fd5b6001600160a01b038316600090815260208190526040902054811115611e39576001600160a01b0383166000818152602081905260409081902054905163391434e360e21b815260048101929092526024820152604481018290526064016117a3565b6001600160a01b03831660009081526020819052604081208054839290611e6190849061360d565b90915550506001600160a01b03821660009081526020819052604081208054839290611e8e9084906135da565b92505081905550816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611c6a91815260200190565b6001600160a01b038216611eed57600080fd5b8060026000828254611eff91906135da565b90915550506001600160a01b03821660009081526020819052604081208054839290611f2c9084906135da565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020015b60405180910390a35050565b8060026000828254611f89919061360d565b90915550506001600160a01b03821660009081526020819052604081208054839290611fb690849061360d565b90915550506040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001611f6b565b61200161291c565b604080516001808252818301909252600091816020015b60408051808201909152600080825260208201528152602001906001900390816120185790505090506040518060400160405280306001600160a01b03168152602001858152508160008151811061207257612072613540565b6020026020010181905250610e48856040518060200160405280600081525083866127e1565b60008060006120a7858561265e565b60608501519091506000906001600160a01b0316156124b45760608501516040516370a0823160e01b815233600482015283916001600160a01b0316906370a0823190602401602060405180830381865afa15801561210a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061212e919061368f565b10156121655760608501516040516346d55cf960e01b81526001600160a01b039091166004820152602481018390526044016117a3565b6060850151604051636eb1769f60e11b815233600482015230602482015283916001600160a01b03169063dd62ed3e90604401602060405180830381865afa1580156121b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121d9919061368f565b101561221057606085015160405163def222f760e01b81526001600160a01b039091166004820152602481018390526044016117a3565b60608501516040516323b872dd60e01b8152336004820152306024820152604481018490526001600160a01b03909116906323b872dd906064016020604051808303816000875af1158015612269573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061228d91906136a8565b50606085015160405163095ea7b360e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000081166004830152602482018590529091169063095ea7b3906044016020604051808303816000875af1158015612303573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061232791906136a8565b506040516396f4e9f960e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906396f4e9f99061237690899089906004016136c5565b6020604051808303816000875af1158015612395573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123b9919061368f565b60608601516040516370a0823160e01b81523060048201529192506000916001600160a01b03909116906370a0823190602401602060405180830381865afa158015612409573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061242d919061368f565b905080156124ae57606086015160405163a9059cbb60e01b8152336004820152602481018390526001600160a01b039091169063a9059cbb906044016020604051808303816000875af1158015612488573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124ac91906136a8565b505b506125e4565b813410156124ed5760608501516040516346d55cf960e01b81526001600160a01b039091166004820152602481018390526044016117a3565b6040516396f4e9f960e01b81526001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906396f4e9f990849061253d908a908a906004016136c5565b60206040518083038185885af115801561255b573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612580919061368f565b9050600061258e833461360d565b11156125e4573361259f833461360d565b604051600081818185875af1925050503d80600081146125db576040519150601f19603f3d011682016040523d82523d6000602084013e6125e0565b606091505b5050505b925090505b9250929050565b6125f861291c565b6040518060a0016040528087815260200186815260200185815260200184612640577f0000000000000000000000000000000000000000000000000000000000000000612643565b60005b6001600160a01b031681526020019290925250949350505050565b6040516320487ded60e01b81526000906001600160a01b037f000000000000000000000000000000000000000000000000000000000000000016906320487ded906126af90869086906004016136c5565b602060405180830381865afa1580156126cc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611958919061368f565b61271b827f000000000000000000000000000000000000000000000000000000000000000083611dc3565b80600654111561276c578060066000828254612737919061360d565b9091555061276790507f000000000000000000000000000000000000000000000000000000000000000082611f77565b6127a6565b600654156127a6576127a07f0000000000000000000000000000000000000000000000000000000000000000600654611f77565b60006006555b826001600160a01b03167f5314098314219d6e1ce8e41fc5e6ec1ce2f06a9d583079fb6619af9bf6efdf418260405161174a91815260200190565b6127e961291c565b6040518060a0016040528086815260200185815260200184815260200161280e6128e8565b6001600160a01b0316815260200192909252509392505050565b6001600160a01b038083166000908152600160209081526040808320938516835292905290812054801561285d579050610b87565b6128668361128e565b8061289057506001600160a01b03808416600090815260056020526040902054612890911661128e565b806128cc57507f00000000000000000000000000000000000000000000000000000000000000006001600160a01b0316836001600160a01b0316145b156128de57600160ff1b915050610b87565b6000915050610b87565b600080341161291657507f000000000000000000000000000000000000000000000000000000000000000090565b50600090565b6040518060a0016040528060608152602001606081526020016060815260200160006001600160a01b03168152602001606081525090565b60005b8381101561296f578181015183820152602001612957565b50506000910152565b60008151808452612990816020860160208601612954565b601f01601f19169290920160200192915050565b6020815260006119586020830184612978565b80356001600160a01b03811681146129ce57600080fd5b919050565b600080604083850312156129e657600080fd5b6129ef836129b7565b946020939093013593505050565b80356001600160401b03811681146129ce57600080fd5b634e487b7160e01b600052604160045260246000fd5b600082601f830112612a3b57600080fd5b81356001600160401b0380821115612a5557612a55612a14565b604051601f8301601f19908116603f01168101908282118183101715612a7d57612a7d612a14565b81604052838152866020858801011115612a9657600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008083601f840112612ac857600080fd5b5081356001600160401b03811115612adf57600080fd5b6020830191508360208285010111156125e957600080fd5b60008060008060008060a08789031215612b1057600080fd5b612b19876129fd565b955060208701356001600160401b0380821115612b3557600080fd5b612b418a838b01612a2a565b9650604089013595506060890135915080821115612b5e57600080fd5b612b6a8a838b01612a2a565b94506080890135915080821115612b8057600080fd5b50612b8d89828a01612ab6565b979a9699509497509295939492505050565b600060408284031215612bb157600080fd5b50919050565b60008060008060a08587031215612bcd57600080fd5b612bd6856129fd565b9350612be4602086016129b7565b925060408501359150612bfa8660608701612b9f565b905092959194509250565b600080600080600060808688031215612c1d57600080fd5b612c26866129b7565b9450612c34602087016129b7565b93506040860135925060608601356001600160401b03811115612c5657600080fd5b612c6288828901612ab6565b969995985093965092949392505050565b600080600060608486031215612c8857600080fd5b612c91846129b7565b9250612c9f602085016129b7565b9150604084013590509250925092565b600060208284031215612cc157600080fd5b5035919050565b60008060008060808587031215612cde57600080fd5b612ce7856129fd565b935060208501356001600160401b0380821115612d0357600080fd5b612d0f88838901612a2a565b9450604087013593506060870135915080821115612d2c57600080fd5b50612d3987828801612a2a565b91505092959194509250565b8015158114610d7457600080fd5b60008060008060808587031215612d6957600080fd5b612d72856129fd565b9350612d80602086016129b7565b9250604085013591506060850135612d9781612d45565b939692955090935050565b600060208284031215612db457600080fd5b611958826129b7565b60008060008060608587031215612dd357600080fd5b612ddc856129b7565b93506020850135925060408501356001600160401b03811115612dfe57600080fd5b612e0a87828801612ab6565b95989497509550505050565b600080600080600080600060c0888a031215612e3157600080fd5b612e3a886129fd565b9650612e48602089016129b7565b955060408801356001600160401b0380821115612e6457600080fd5b612e708b838c01612a2a565b965060608a0135955060808a0135915080821115612e8d57600080fd5b612e998b838c01612a2a565b945060a08a0135915080821115612eaf57600080fd5b50612ebc8a828b01612ab6565b989b979a50959850939692959293505050565b60008060408385031215612ee257600080fd5b8235612eed81612d45565b915060208301356001600160401b03811115612f0857600080fd5b612f1485828601612a2a565b9150509250929050565b600060408284031215612f3057600080fd5b6119588383612b9f565b60008060008060008060c08789031215612f5357600080fd5b612f5c876129fd565b9550612f6a602088016129b7565b945060408701359350612f808860608901612b9f565b925060a08701356001600160401b03811115612f9b57600080fd5b612b8d89828a01612ab6565b60008060008060008060a08789031215612fc057600080fd5b612fc9876129fd565b9550612fd7602088016129b7565b9450612fe5604088016129b7565b93506060870135925060808701356001600160401b03811115612f9b57600080fd5b600080600080600060a0868803121561301f57600080fd5b613028866129fd565b945060208601356001600160401b038082111561304457600080fd5b61305089838a01612a2a565b9550604088013594506060880135915061306982612d45565b9092506080870135908082111561307f57600080fd5b5061308c88828901612a2a565b9150509295509295909350565b60008083601f8401126130ab57600080fd5b5081356001600160401b038111156130c257600080fd5b6020830191508360208260051b85010111156125e957600080fd5b6000806000806000606086880312156130f557600080fd5b6130fe866129b7565b945060208601356001600160401b038082111561311a57600080fd5b61312689838a01613099565b9096509450604088013591508082111561313f57600080fd5b50612c6288828901612ab6565b600080600080600060a0868803121561316457600080fd5b61316d866129fd565b945061317b602087016129b7565b935060408601359250606086013561319281612d45565b915060808601356001600160401b038111156131ad57600080fd5b61308c88828901612a2a565b6000602082840312156131cb57600080fd5b813561195881612d45565b6000602082840312156131e857600080fd5b81356001600160401b038111156131fe57600080fd5b61320a84828501612a2a565b949350505050565b60008060006060848603121561322757600080fd5b612c91846129fd565b60008060008060006080868803121561324857600080fd5b613251866129b7565b9450602086013593506040860135925060608601356001600160401b03811115612c5657600080fd5b600080600080600080600060e0888a03121561329557600080fd5b61329e886129b7565b96506132ac602089016129b7565b95506040880135945060608801359350608088013560ff811681146132d057600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561330057600080fd5b613309836129b7565b9150613317602084016129b7565b90509250929050565b6000806000806040858703121561333657600080fd5b84356001600160401b038082111561334d57600080fd5b61335988838901613099565b9096509450602087013591508082111561337257600080fd5b50612e0a87828801613099565b60008151808452602080850194506020840160005b838110156133c457815180516001600160a01b031688528301518388015260409096019590820190600101613394565b509495945050505050565b838152826020820152606060408201526000610e48606083018461337f565b60008060008060006080868803121561340657600080fd5b612c26866129fd565b600080600080600080600060e0888a03121561342a57600080fd5b613433886129fd565b9650613441602089016129b7565b955061344f604089016129b7565b9450606088013593506134658960808a01612b9f565b925060c08801356001600160401b0381111561348057600080fd5b612ebc8a828b01612ab6565b60006040828403121561349e57600080fd5b604051604081018181106001600160401b03821117156134c0576134c0612a14565b6040528235815260208301356134d581612d45565b60208201529392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b838152604060208201526000610e486040830184866134e1565b60008251613536818460208701612954565b9190910192915050565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b03848116825260406020808401829052908301849052600091859160608501845b878110156135a35783613590866129b7565b168252938201939082019060010161357e565b5098975050505050505050565b60208152600061320a6020830184866134e1565b634e487b7160e01b600052601160045260246000fd5b80820180821115610b8757610b876135c4565b8481528360208201526060604082015260006112776060830184866134e1565b81810381811115610b8757610b876135c4565b6000808335601e1984360301811261363757600080fd5b8301803591506001600160401b0382111561365157600080fd5b6020019150368190038213156125e957600080fd5b6001600160401b03851681528360208201526060604082015260006112776060830184866134e1565b6000602082840312156136a157600080fd5b5051919050565b6000602082840312156136ba57600080fd5b815161195881612d45565b6001600160401b0383168152604060208201526000825160a060408401526136f060e0840182612978565b90506020840151603f198085840301606086015261370e8383612978565b9250604086015191508085840301608086015261372b838361337f565b60608701516001600160a01b031660a0870152608087015186820390920160c087015292509050611277828261297856fea264697066735822122059880cb44474e46b205765e35b637564f41b1d223977ff64e49d4cebc931931d64736f6c634300081800330000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde80000000000000000000000000000000000000000000000000000000000127500000000000000000000000000f97f4df75117a78c1a5a0dbb814af92458539fb400000000000000000000000000000000000000000000000045849994fc9c7b1500000000000000000000000088fd2ecd0b9250f203e99e80eb78b0c32b8adb16000000000000000000000000de4cb79a62fd4036cadf6d71d23240dc4d7a484e
Deployed Bytecode
0x6080604052600436106103765760003560e01c806379cc6790116101d1578063c2ba474411610102578063d1fa5e98116100a0578063e0fb2f781161006f578063e0fb2f7814610aff578063eca79fef14610b23578063f46eccc414610b36578063f507469014610b6357600080fd5b8063d1fa5e9814610a7f578063d505accf14610a9f578063dd62ed3e14610abf578063e043684214610adf57600080fd5b8063cd3293de116100dc578063cd3293de14610a10578063d15dcd6214610a44578063d1975e6a14610a57578063d1a15ff114610a5f57600080fd5b8063c2ba4744146109bd578063c3ccc9ef146109dd578063c6fede7d146109fd57600080fd5b80639b404da61161016f578063aa271e1a11610149578063aa271e1a146108f8578063aa5dd7f114610918578063b52c696d14610951578063c038e2981461097157600080fd5b80639b404da6146108985780639f3b3ea3146108b8578063a9059cbb146108d857600080fd5b80638fd6a6ac116101ab5780638fd6a6ac146107e4578063912fa6161461081757806394b936901461083757806395d89b411461086b57600080fd5b806379cc6790146107845780637ecebe00146107a4578063809eb368146107d157600080fd5b80633644e515116102ab578063604248ab116102495780636ebdb8ee116102235780636ebdb8ee146106fe57806370a082311461071e578063737079811461075457806376c7a3c71461076757600080fd5b8063604248ab146106b8578063642884df146106cb5780636b2242e1146106eb57600080fd5b806352d48abe1161028557806352d48abe1461062f57806353c521e61461064257806355f575101461066257806356b8c7241461069857600080fd5b80633644e515146105da57806340c10f19146105ef57806342966c681461060f57600080fd5b806319e3c428116103185780631b6b6d23116102f25780631b6b6d231461050c57806323b872dd14610540578063313ce5671461056057806332fe7b26146105a657600080fd5b806319e3c428146104a35780631a46c7e9146104b85780631af716ba146104ec57600080fd5b806310f9c77e1161035457806310f9c77e146104155780631183801014610428578063158ef93e1461047457806318160ddd1461048e57600080fd5b806306fdde031461037b578063095ea7b3146103c15780630e57bc7d146103f1575b600080fd5b34801561038757600080fd5b5060408051808201909152600b81526a233930b735b2b731b7b4b760a91b60208201525b6040516103b891906129a4565b60405180910390f35b3480156103cd57600080fd5b506103e16103dc3660046129d3565b610b76565b60405190151581526020016103b8565b3480156103fd57600080fd5b5061040760065481565b6040519081526020016103b8565b6103e1610423366004612af7565b610b8d565b34801561043457600080fd5b5061045c7f000000000000000000000000de4cb79a62fd4036cadf6d71d23240dc4d7a484e81565b6040516001600160a01b0390911681526020016103b8565b34801561048057600080fd5b506007546103e19060ff1681565b34801561049a57600080fd5b50600254610407565b6104b66104b1366004612bb7565b610bab565b005b3480156104c457600080fd5b506104077f000000000000000000000000000000000000000000000000000000000012750081565b3480156104f857600080fd5b506103e1610507366004612c05565b610c1b565b34801561051857600080fd5b5061045c7f000000000000000000000000f97f4df75117a78c1a5a0dbb814af92458539fb481565b34801561054c57600080fd5b506103e161055b366004612c73565b610c8e565b34801561056c57600080fd5b506105947f000000000000000000000000000000000000000000000000000000000000001281565b60405160ff90911681526020016103b8565b3480156105b257600080fd5b5061045c7f000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde881565b3480156105e657600080fd5b50610407610cb0565b3480156105fb57600080fd5b506104b661060a3660046129d3565b610d09565b34801561061b57600080fd5b506104b661062a366004612caf565b610d6a565b6104b661063d366004612cc8565b610d77565b34801561064e57600080fd5b5061040761065d366004612d53565b610e22565b34801561066e57600080fd5b5061045c61067d366004612da2565b6005602052600090815260409020546001600160a01b031681565b3480156106a457600080fd5b506103e16106b3366004612dbd565b610e51565b6103e16106c6366004612e16565b610eb8565b3480156106d757600080fd5b506104076106e6366004612ecf565b610ee2565b6104b66106f9366004612f1e565b610f86565b34801561070a57600080fd5b506104b66107193660046129d3565b610f9b565b34801561072a57600080fd5b50610407610739366004612da2565b6001600160a01b031660009081526020819052604090205490565b6103e1610762366004612f3a565b610ff9565b34801561077357600080fd5b50610407683635c9adc5dea0000081565b34801561079057600080fd5b506104b661079f3660046129d3565b611022565b3480156107b057600080fd5b506104076107bf366004612da2565b60036020526000908152604090205481565b6103e16107df366004612fa7565b61107f565b3480156107f057600080fd5b507f000000000000000000000000de4cb79a62fd4036cadf6d71d23240dc4d7a484e61045c565b34801561082357600080fd5b50610407610832366004613007565b6110a7565b34801561084357600080fd5b5061045c7f00000000000000000000000088fd2ecd0b9250f203e99e80eb78b0c32b8adb1681565b34801561087757600080fd5b506040805180820190915260048152632d21a42360e11b60208201526103ab565b3480156108a457600080fd5b506104b66108b33660046130dd565b61114b565b3480156108c457600080fd5b506104076108d336600461314c565b611260565b3480156108e457600080fd5b506103e16108f33660046129d3565b611281565b34801561090457600080fd5b506103e1610913366004612da2565b61128e565b34801561092457600080fd5b5061045c610933366004612da2565b6001600160a01b039081166000908152600560205260409020541690565b34801561095d57600080fd5b506104b661096c366004612da2565b6112cf565b34801561097d57600080fd5b506109a57f00000000000000000000000000000000000000000000000045849994fc9c7b1581565b6040516001600160401b0390911681526020016103b8565b3480156109c957600080fd5b506103e16109d8366004612da2565b61131e565b3480156109e957600080fd5b506104076109f83660046131b9565b611353565b6104b6610a0b3660046131d6565b61136e565b348015610a1c57600080fd5b5061045c7f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab81565b6104b6610a52366004613212565b6114b0565b6104b66114d8565b348015610a6b57600080fd5b506104b6610a7a366004613230565b6114f2565b348015610a8b57600080fd5b506104b6610a9a3660046129d3565b6115fc565b348015610aab57600080fd5b506104b6610aba36600461327a565b611757565b348015610acb57600080fd5b50610407610ada3660046132ed565b61194c565b348015610aeb57600080fd5b506104b6610afa366004613320565b61195f565b348015610b0b57600080fd5b50610b14611a9f565b6040516103b8939291906133cf565b6103e1610b313660046133ee565b611bb8565b348015610b4257600080fd5b50610407610b51366004612da2565b60046020526000908152604090205481565b6103e1610b7136600461340f565b611bdf565b6000610b83338484611c15565b5060015b92915050565b6000610b9e87338888888888611c77565b5060019695505050505050565b610c1584610bb885611d2e565b8461063d610bcb3687900387018761348c565b6040805182516024820152602092830151151560448083019190915282518083039091018152606490910190915290810180516001600160e01b0316630181dcf160e41b17905290565b50505050565b6000610c28863386611d5b565b610c33868686611dc3565b846001600160a01b0316866001600160a01b03167fcd6e659e4c2e75c3bfe47fecaccf39aeb368116a0ee52afb532e07f6cba6c0d1868686604051610c7a9392919061350a565b60405180910390a350600195945050505050565b6000610c9b848484611dc3565b610ca6843384611d5b565b5060019392505050565b604080517f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a794692186020820152469181019190915230606082015260009060800160405160208183030381529060405280519060200120905090565b610d123361128e565b158015610d3e575033600090815260056020526040902054610d3c906001600160a01b031661128e565b155b15610d5c57604051633e34a41b60e21b815260040160405180910390fd5b610d668282611eda565b5050565b610d743382611f77565b50565b610d82333084611dc3565b610dad307f000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde884611c15565b610dc184610dbc858585611ff9565b612098565b505082604051610dd19190613524565b604080519182900382206001600160401b0387168352602083018590529133917f7895812be6b9a01617f760055105f506b5e2f6dff79ff3170e4fdb3391fc0023910160405180910390a350505050565b6000610e4885610e3186611d2e565b8585604051806020016040528060008152506110a7565b95945050505050565b6000610e5e338686611dc3565b846001600160a01b0316336001600160a01b03167fcd6e659e4c2e75c3bfe47fecaccf39aeb368116a0ee52afb532e07f6cba6c0d1868686604051610ea59392919061350a565b60405180910390a3506001949350505050565b6000610ec5873387611d5b565b610ed488888888888888611c77565b506001979650505050505050565b600080600080610ef0611a9f565b9250925092506000610f4f610f247f00000000000000000000000088fd2ecd0b9250f203e99e80eb78b0c32b8adb16611d2e565b6040805160208101889052908101869052606001604051602081830303815290604052848a8a6125f0565b9050610f7b7f00000000000000000000000000000000000000000000000045849994fc9c7b158261265e565b979650505050505050565b610d74610a0b610bcb3684900384018461348c565b610fa43361128e565b158015610fd0575033600090815260056020526040902054610fce906001600160a01b031661128e565b155b15610fee57604051633e34a41b60e21b815260040160405180910390fd5b610d663383836126f0565b6000610f7b8761100888611d2e565b8761101b610bcb368a90038a018a61348c565b8787610b8d565b61102b3361128e565b158015611057575033600090815260056020526040902054611055906001600160a01b031661128e565b155b1561107557604051633e34a41b60e21b815260040160405180910390fd5b610d668282611f77565b6000610f7b878761108f88611d2e565b87604051806020016040528060008152508888610eb8565b604080516001808252818301909252600091829190816020015b60408051808201909152600080825260208201528152602001906001900390816110c15790505090506040518060400160405280306001600160a01b03168152602001868152508160008151811061111b5761111b613540565b6020026020010181905250610f7b8761114688604051806020016040528060008152508589896125f0565b61265e565b6001600160a01b0385166000908152600460205260409020544211156111845760405163ecdd1c2960e01b815260040160405180910390fd5b60405163352e3a8360e01b81526001600160a01b037f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab169063352e3a83906111d490339088908890600401613556565b60006040518083038186803b1580156111ec57600080fd5b505afa158015611200573d6000803e3d6000fd5b505050506001600160a01b03851660008181526004602052604080822091909155517fd60c86e83346fdbe3124cb7d1cba32973a9f62d05c4ec801bfe6c09d103803819061125190859085906135b0565b60405180910390a25050505050565b60006112778661126f87611d2e565b8686866110a7565b9695505050505050565b6000610b83338484611dc3565b6001600160a01b03811660009081526004602052604081205415801590610b875750506001600160a01b031660009081526004602052604090205442101590565b6112d83361128e565b6112f557604051633e34a41b60e21b815260040160405180910390fd5b6001600160a01b0316600090815260056020526040902080546001600160a01b03191633179055565b60006113298261128e565b80610b8757506001600160a01b03808316600090815260056020526040902054610b87911661128e565b6000610b878260405180602001604052806000815250610ee2565b600080600061137b611a9f565b91945092509050811561138e5760006006555b82156113ea576113bf7f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab3085611dc3565b6113ea307f000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde885611c15565b60006114426114187f00000000000000000000000088fd2ecd0b9250f203e99e80eb78b0c32b8adb16611d2e565b604080516020810188905290810186905260600160405160208183030381529060405284886127e1565b905061146e7f00000000000000000000000000000000000000000000000045849994fc9c7b1582612098565b505060408051858152602081018590527f780ad21fb95421fd83047aa9bb3506c2aea4c8d383daa4476e66d56606632dc2910160405180910390a15050505050565b6114d3836114bd84611d2e565b8360405180602001604052806000815250610d77565b505050565b6114f06040518060200160405280600081525061136e565b565b7f000000000000000000000000000000000000000000000000000000000012750084101561153357604051631260d0af60e31b815260040160405180910390fd5b683635c9adc5dea0000083101561155d5760405163732f941360e01b815260040160405180910390fd5b6001600160a01b0385166000908152600460205260409020541561159457604051630ea075bf60e21b815260040160405180910390fd5b61159f3033856126f0565b6115a984426135da565b6001600160a01b038616600081815260046020526040908190209290925590517f97326258efdae63280617ca33884e507791c2abeed7b82acd77f1853394ef94b906112519087908790879087906135ed565b6116053361128e565b15801561163157503360009081526005602052604090205461162f906001600160a01b031661128e565b155b1561164f57604051633e34a41b60e21b815260040160405180910390fd5b7f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab6001600160a01b0316600090815260208190526040902054808211156116e45761169a818361360d565b600660008282546116ab91906135da565b909155506116e490507f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab6116df838561360d565b611eda565b61170f7f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab8484611dc3565b826001600160a01b03167f72fba0ba07d937c660a3130fca36005c0e476cb97b6f00de413976e37eba95018360405161174a91815260200190565b60405180910390a2505050565b428410156117ac5760405162461bcd60e51b815260206004820152601760248201527f5045524d49545f444541444c494e455f4558504952454400000000000000000060448201526064015b60405180910390fd5b600060016117b8610cb0565b6001600160a01b038a811660008181526003602090815260409182902080546001810190915582517f6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c98184015280840194909452938d166060840152608083018c905260a083019390935260c08083018b90528151808403909101815260e08301909152805192019190912061190160f01b6101008301526101028201929092526101228101919091526101420160408051601f198184030181528282528051602091820120600084529083018083525260ff871690820152606081018590526080810184905260a0016020604051602081039080840390855afa1580156118c4573d6000803e3d6000fd5b5050604051601f1901519150506001600160a01b038116158015906118fa5750876001600160a01b0316816001600160a01b0316145b6119375760405162461bcd60e51b815260206004820152600e60248201526d24a72b20a624a22fa9a4a3a722a960911b60448201526064016117a3565b611942818888611c15565b5050505050505050565b60006119588383612828565b9392505050565b60075460ff16156119825760405162dc149f60e41b815260040160405180910390fd5b8281146119a25760405163b4fa3fb360e01b815260040160405180910390fd5b60005b83811015611a8b5742600460008787858181106119c4576119c4613540565b90506020020160208101906119d99190612da2565b6001600160a01b03168152602081019190915260400160002055848482818110611a0557611a05613540565b9050602002016020810190611a1a9190612da2565b6001600160a01b03167f97326258efdae63280617ca33884e507791c2abeed7b82acd77f1853394ef94b600080868686818110611a5957611a59613540565b9050602002810190611a6b9190613620565b604051611a7b94939291906135ed565b60405180910390a26001016119a5565b50506007805460ff19166001179055505050565b60008060606000611ae57f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab6001600160a01b031660009081526020819052604090205490565b604080516000808252602082019092529192509081611b26565b6040805180820190915260008082526020820152815260200190600190039081611aff5790505b5090508115611baa5760408051600180825281830190925290816020015b6040805180820190915260008082526020820152815260200190600190039081611b445790505090506040518060400160405280306001600160a01b031681526020018381525081600081518110611b9e57611b9e613540565b60200260200101819052505b600654919591945092509050565b600061127786611bc787611d2e565b86604051806020016040528060008152508787610b8d565b6000611c098888611bef89611d2e565b88611c02610bcb368b90038b018b61348c565b8888610eb8565b98975050505050505050565b6001600160a01b0383811660008181526001602090815260408083209487168084529482529182902085905590518481527f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92591015b60405180910390a3505050565b611c82863086611dc3565b611cad307f000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde886611c15565b611cbc87610dbc878787611ff9565b505084604051611ccc9190613524565b6040518091039020866001600160a01b0316336001600160a01b03167f06666856ab7ec8eca6666144744c8bdd710c19b0aba4a36495361c82a01099288a888787604051611d1d9493929190613666565b60405180910390a450505050505050565b604080516001600160a01b0383166020820152606091016040516020818303038152906040529050919050565b6000611d678484612828565b9050600160ff1b811015610c155781811015611daf57604051637dc7a0d960e11b81526001600160a01b038516600482015260248101829052604481018390526064016117a3565b610c158484611dbe858561360d565b611c15565b6001600160a01b038216611dd657600080fd5b6001600160a01b038316600090815260208190526040902054811115611e39576001600160a01b0383166000818152602081905260409081902054905163391434e360e21b815260048101929092526024820152604481018290526064016117a3565b6001600160a01b03831660009081526020819052604081208054839290611e6190849061360d565b90915550506001600160a01b03821660009081526020819052604081208054839290611e8e9084906135da565b92505081905550816001600160a01b0316836001600160a01b03167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef83604051611c6a91815260200190565b6001600160a01b038216611eed57600080fd5b8060026000828254611eff91906135da565b90915550506001600160a01b03821660009081526020819052604081208054839290611f2c9084906135da565b90915550506040518181526001600160a01b038316906000907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef906020015b60405180910390a35050565b8060026000828254611f89919061360d565b90915550506001600160a01b03821660009081526020819052604081208054839290611fb690849061360d565b90915550506040518181526000906001600160a01b038416907fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef90602001611f6b565b61200161291c565b604080516001808252818301909252600091816020015b60408051808201909152600080825260208201528152602001906001900390816120185790505090506040518060400160405280306001600160a01b03168152602001858152508160008151811061207257612072613540565b6020026020010181905250610e48856040518060200160405280600081525083866127e1565b60008060006120a7858561265e565b60608501519091506000906001600160a01b0316156124b45760608501516040516370a0823160e01b815233600482015283916001600160a01b0316906370a0823190602401602060405180830381865afa15801561210a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061212e919061368f565b10156121655760608501516040516346d55cf960e01b81526001600160a01b039091166004820152602481018390526044016117a3565b6060850151604051636eb1769f60e11b815233600482015230602482015283916001600160a01b03169063dd62ed3e90604401602060405180830381865afa1580156121b5573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906121d9919061368f565b101561221057606085015160405163def222f760e01b81526001600160a01b039091166004820152602481018390526044016117a3565b60608501516040516323b872dd60e01b8152336004820152306024820152604481018490526001600160a01b03909116906323b872dd906064016020604051808303816000875af1158015612269573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061228d91906136a8565b50606085015160405163095ea7b360e01b81526001600160a01b037f000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde881166004830152602482018590529091169063095ea7b3906044016020604051808303816000875af1158015612303573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061232791906136a8565b506040516396f4e9f960e01b81526001600160a01b037f000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde816906396f4e9f99061237690899089906004016136c5565b6020604051808303816000875af1158015612395573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906123b9919061368f565b60608601516040516370a0823160e01b81523060048201529192506000916001600160a01b03909116906370a0823190602401602060405180830381865afa158015612409573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061242d919061368f565b905080156124ae57606086015160405163a9059cbb60e01b8152336004820152602481018390526001600160a01b039091169063a9059cbb906044016020604051808303816000875af1158015612488573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906124ac91906136a8565b505b506125e4565b813410156124ed5760608501516040516346d55cf960e01b81526001600160a01b039091166004820152602481018390526044016117a3565b6040516396f4e9f960e01b81526001600160a01b037f000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde816906396f4e9f990849061253d908a908a906004016136c5565b60206040518083038185885af115801561255b573d6000803e3d6000fd5b50505050506040513d601f19601f82011682018060405250810190612580919061368f565b9050600061258e833461360d565b11156125e4573361259f833461360d565b604051600081818185875af1925050503d80600081146125db576040519150601f19603f3d011682016040523d82523d6000602084013e6125e0565b606091505b5050505b925090505b9250929050565b6125f861291c565b6040518060a0016040528087815260200186815260200185815260200184612640577f000000000000000000000000f97f4df75117a78c1a5a0dbb814af92458539fb4612643565b60005b6001600160a01b031681526020019290925250949350505050565b6040516320487ded60e01b81526000906001600160a01b037f000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde816906320487ded906126af90869086906004016136c5565b602060405180830381865afa1580156126cc573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611958919061368f565b61271b827f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab83611dc3565b80600654111561276c578060066000828254612737919061360d565b9091555061276790507f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab82611f77565b6127a6565b600654156127a6576127a07f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab600654611f77565b60006006555b826001600160a01b03167f5314098314219d6e1ce8e41fc5e6ec1ce2f06a9d583079fb6619af9bf6efdf418260405161174a91815260200190565b6127e961291c565b6040518060a0016040528086815260200185815260200184815260200161280e6128e8565b6001600160a01b0316815260200192909252509392505050565b6001600160a01b038083166000908152600160209081526040808320938516835292905290812054801561285d579050610b87565b6128668361128e565b8061289057506001600160a01b03808416600090815260056020526040902054612890911661128e565b806128cc57507f0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab6001600160a01b0316836001600160a01b0316145b156128de57600160ff1b915050610b87565b6000915050610b87565b600080341161291657507f000000000000000000000000f97f4df75117a78c1a5a0dbb814af92458539fb490565b50600090565b6040518060a0016040528060608152602001606081526020016060815260200160006001600160a01b03168152602001606081525090565b60005b8381101561296f578181015183820152602001612957565b50506000910152565b60008151808452612990816020860160208601612954565b601f01601f19169290920160200192915050565b6020815260006119586020830184612978565b80356001600160a01b03811681146129ce57600080fd5b919050565b600080604083850312156129e657600080fd5b6129ef836129b7565b946020939093013593505050565b80356001600160401b03811681146129ce57600080fd5b634e487b7160e01b600052604160045260246000fd5b600082601f830112612a3b57600080fd5b81356001600160401b0380821115612a5557612a55612a14565b604051601f8301601f19908116603f01168101908282118183101715612a7d57612a7d612a14565b81604052838152866020858801011115612a9657600080fd5b836020870160208301376000602085830101528094505050505092915050565b60008083601f840112612ac857600080fd5b5081356001600160401b03811115612adf57600080fd5b6020830191508360208285010111156125e957600080fd5b60008060008060008060a08789031215612b1057600080fd5b612b19876129fd565b955060208701356001600160401b0380821115612b3557600080fd5b612b418a838b01612a2a565b9650604089013595506060890135915080821115612b5e57600080fd5b612b6a8a838b01612a2a565b94506080890135915080821115612b8057600080fd5b50612b8d89828a01612ab6565b979a9699509497509295939492505050565b600060408284031215612bb157600080fd5b50919050565b60008060008060a08587031215612bcd57600080fd5b612bd6856129fd565b9350612be4602086016129b7565b925060408501359150612bfa8660608701612b9f565b905092959194509250565b600080600080600060808688031215612c1d57600080fd5b612c26866129b7565b9450612c34602087016129b7565b93506040860135925060608601356001600160401b03811115612c5657600080fd5b612c6288828901612ab6565b969995985093965092949392505050565b600080600060608486031215612c8857600080fd5b612c91846129b7565b9250612c9f602085016129b7565b9150604084013590509250925092565b600060208284031215612cc157600080fd5b5035919050565b60008060008060808587031215612cde57600080fd5b612ce7856129fd565b935060208501356001600160401b0380821115612d0357600080fd5b612d0f88838901612a2a565b9450604087013593506060870135915080821115612d2c57600080fd5b50612d3987828801612a2a565b91505092959194509250565b8015158114610d7457600080fd5b60008060008060808587031215612d6957600080fd5b612d72856129fd565b9350612d80602086016129b7565b9250604085013591506060850135612d9781612d45565b939692955090935050565b600060208284031215612db457600080fd5b611958826129b7565b60008060008060608587031215612dd357600080fd5b612ddc856129b7565b93506020850135925060408501356001600160401b03811115612dfe57600080fd5b612e0a87828801612ab6565b95989497509550505050565b600080600080600080600060c0888a031215612e3157600080fd5b612e3a886129fd565b9650612e48602089016129b7565b955060408801356001600160401b0380821115612e6457600080fd5b612e708b838c01612a2a565b965060608a0135955060808a0135915080821115612e8d57600080fd5b612e998b838c01612a2a565b945060a08a0135915080821115612eaf57600080fd5b50612ebc8a828b01612ab6565b989b979a50959850939692959293505050565b60008060408385031215612ee257600080fd5b8235612eed81612d45565b915060208301356001600160401b03811115612f0857600080fd5b612f1485828601612a2a565b9150509250929050565b600060408284031215612f3057600080fd5b6119588383612b9f565b60008060008060008060c08789031215612f5357600080fd5b612f5c876129fd565b9550612f6a602088016129b7565b945060408701359350612f808860608901612b9f565b925060a08701356001600160401b03811115612f9b57600080fd5b612b8d89828a01612ab6565b60008060008060008060a08789031215612fc057600080fd5b612fc9876129fd565b9550612fd7602088016129b7565b9450612fe5604088016129b7565b93506060870135925060808701356001600160401b03811115612f9b57600080fd5b600080600080600060a0868803121561301f57600080fd5b613028866129fd565b945060208601356001600160401b038082111561304457600080fd5b61305089838a01612a2a565b9550604088013594506060880135915061306982612d45565b9092506080870135908082111561307f57600080fd5b5061308c88828901612a2a565b9150509295509295909350565b60008083601f8401126130ab57600080fd5b5081356001600160401b038111156130c257600080fd5b6020830191508360208260051b85010111156125e957600080fd5b6000806000806000606086880312156130f557600080fd5b6130fe866129b7565b945060208601356001600160401b038082111561311a57600080fd5b61312689838a01613099565b9096509450604088013591508082111561313f57600080fd5b50612c6288828901612ab6565b600080600080600060a0868803121561316457600080fd5b61316d866129fd565b945061317b602087016129b7565b935060408601359250606086013561319281612d45565b915060808601356001600160401b038111156131ad57600080fd5b61308c88828901612a2a565b6000602082840312156131cb57600080fd5b813561195881612d45565b6000602082840312156131e857600080fd5b81356001600160401b038111156131fe57600080fd5b61320a84828501612a2a565b949350505050565b60008060006060848603121561322757600080fd5b612c91846129fd565b60008060008060006080868803121561324857600080fd5b613251866129b7565b9450602086013593506040860135925060608601356001600160401b03811115612c5657600080fd5b600080600080600080600060e0888a03121561329557600080fd5b61329e886129b7565b96506132ac602089016129b7565b95506040880135945060608801359350608088013560ff811681146132d057600080fd5b9699959850939692959460a0840135945060c09093013592915050565b6000806040838503121561330057600080fd5b613309836129b7565b9150613317602084016129b7565b90509250929050565b6000806000806040858703121561333657600080fd5b84356001600160401b038082111561334d57600080fd5b61335988838901613099565b9096509450602087013591508082111561337257600080fd5b50612e0a87828801613099565b60008151808452602080850194506020840160005b838110156133c457815180516001600160a01b031688528301518388015260409096019590820190600101613394565b509495945050505050565b838152826020820152606060408201526000610e48606083018461337f565b60008060008060006080868803121561340657600080fd5b612c26866129fd565b600080600080600080600060e0888a03121561342a57600080fd5b613433886129fd565b9650613441602089016129b7565b955061344f604089016129b7565b9450606088013593506134658960808a01612b9f565b925060c08801356001600160401b0381111561348057600080fd5b612ebc8a828b01612ab6565b60006040828403121561349e57600080fd5b604051604081018181106001600160401b03821117156134c0576134c0612a14565b6040528235815260208301356134d581612d45565b60208201529392505050565b81835281816020850137506000828201602090810191909152601f909101601f19169091010190565b838152604060208201526000610e486040830184866134e1565b60008251613536818460208701612954565b9190910192915050565b634e487b7160e01b600052603260045260246000fd5b6001600160a01b03848116825260406020808401829052908301849052600091859160608501845b878110156135a35783613590866129b7565b168252938201939082019060010161357e565b5098975050505050505050565b60208152600061320a6020830184866134e1565b634e487b7160e01b600052601160045260246000fd5b80820180821115610b8757610b876135c4565b8481528360208201526060604082015260006112776060830184866134e1565b81810381811115610b8757610b876135c4565b6000808335601e1984360301811261363757600080fd5b8301803591506001600160401b0382111561365157600080fd5b6020019150368190038213156125e957600080fd5b6001600160401b03851681528360208201526060604082015260006112776060830184866134e1565b6000602082840312156136a157600080fd5b5051919050565b6000602082840312156136ba57600080fd5b815161195881612d45565b6001600160401b0383168152604060208201526000825160a060408401526136f060e0840182612978565b90506020840151603f198085840301606086015261370e8383612978565b9250604086015191508085840301608086015261372b838361337f565b60608701516001600160a01b031660a0870152608087015186820390920160c087015292509050611277828261297856fea264697066735822122059880cb44474e46b205765e35b637564f41b1d223977ff64e49d4cebc931931d64736f6c63430008180033
Constructor Arguments (ABI-Encoded and is the last bytes of the Contract Creation Code above)
0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde80000000000000000000000000000000000000000000000000000000000127500000000000000000000000000f97f4df75117a78c1a5a0dbb814af92458539fb400000000000000000000000000000000000000000000000045849994fc9c7b1500000000000000000000000088fd2ecd0b9250f203e99e80eb78b0c32b8adb16000000000000000000000000de4cb79a62fd4036cadf6d71d23240dc4d7a484e
-----Decoded View---------------
Arg [0] : reserve_ (address): 0x4fF458f3Aa2c5cd970891909d72CF029939313ab
Arg [1] : router_ (address): 0x141fa059441E0ca23ce184B6A78bafD2A517DdE8
Arg [2] : _minApplicationPeriod (uint256): 1209600
Arg [3] : _linkToken (address): 0xf97f4df75117a78c1A5a0DBb814Af92458539FB4
Arg [4] : _mainnetChainSelector (uint64): 5009297550715157269
Arg [5] : _bridgeAccounting (address): 0x88fd2ECD0B9250F203e99E80eb78b0C32B8AdB16
Arg [6] : _ccipAdmin (address): 0xdE4cB79A62fd4036Cadf6D71D23240dc4d7a484E
-----Encoded View---------------
7 Constructor Arguments found :
Arg [0] : 0000000000000000000000004ff458f3aa2c5cd970891909d72cf029939313ab
Arg [1] : 000000000000000000000000141fa059441e0ca23ce184b6a78bafd2a517dde8
Arg [2] : 0000000000000000000000000000000000000000000000000000000000127500
Arg [3] : 000000000000000000000000f97f4df75117a78c1a5a0dbb814af92458539fb4
Arg [4] : 00000000000000000000000000000000000000000000000045849994fc9c7b15
Arg [5] : 00000000000000000000000088fd2ecd0b9250f203e99e80eb78b0c32b8adb16
Arg [6] : 000000000000000000000000de4cb79a62fd4036cadf6d71d23240dc4d7a484e
Loading...
Loading
Loading...
Loading
[ Download: CSV Export ]
[ Download: CSV Export ]
A token is a representation of an on-chain or off-chain asset. The token page shows information such as price, total supply, holders, transfers and social links. Learn more about this page in our Knowledge Base.
Add Token to MetaMask (Web3)