// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;
// Sources flattened with hardhat v2.19.4 https://hardhat.org
// File @openzeppelin/contracts/token/ERC20/IERC20.sol@v5.0.1
// Original license: SPDX_License_Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/IERC20.sol)
/**
* @dev Interface of the ERC20 standard as defined in the EIP.
*/
interface IERC20 {
/**
* @dev Emitted when `value` tokens are moved from one account (`from`) to
* another (`to`).
*
* Note that `value` may be zero.
*/
event Transfer(address indexed from, address indexed to, uint256 value);
/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(address indexed owner, address indexed spender, uint256 value);
/**
* @dev Returns the value of tokens in existence.
*/
function totalSupply() external view returns (uint256);
/**
* @dev Returns the value of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);
/**
* @dev Moves a `value` amount of tokens from the caller's account to `to`.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transfer(address to, uint256 value) external returns (bool);
/**
* @dev Returns the remaining number of tokens that `spender` will be
* allowed to spend on behalf of `owner` through {transferFrom}. This is
* zero by default.
*
* This value changes when {approve} or {transferFrom} are called.
*/
function allowance(address owner, address spender) external view returns (uint256);
/**
* @dev Sets a `value` amount of tokens as the allowance of `spender` over the
* caller's tokens.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* IMPORTANT: Beware that changing an allowance with this method brings the risk
* that someone may use both the old and the new allowance by unfortunate
* transaction ordering. One possible solution to mitigate this race
* condition is to first reduce the spender's allowance to 0 and set the
* desired value afterwards:
* https://github.com/ethereum/EIPs/issues/20#issuecomment-263524729
*
* Emits an {Approval} event.
*/
function approve(address spender, uint256 value) external returns (bool);
/**
* @dev Moves a `value` amount of tokens from `from` to `to` using the
* allowance mechanism. `value` is then deducted from the caller's
* allowance.
*
* Returns a boolean value indicating whether the operation succeeded.
*
* Emits a {Transfer} event.
*/
function transferFrom(address from, address to, uint256 value) external returns (bool);
}
// File @uniswap/v3-periphery/contracts/libraries/TransferHelper.sol@v1.4.4
// Original license: SPDX_License_Identifier: GPL-2.0-or-later
library TransferHelper {
/// @notice Transfers tokens from the targeted address to the given destination
/// @notice Errors with 'STF' if transfer fails
/// @param token The contract address of the token to be transferred
/// @param from The originating address from which the tokens will be transferred
/// @param to The destination address of the transfer
/// @param value The amount to be transferred
function safeTransferFrom(
address token,
address from,
address to,
uint256 value
) internal {
(bool success, bytes memory data) =
token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'STF');
}
/// @notice Transfers tokens from msg.sender to a recipient
/// @dev Errors with ST if transfer fails
/// @param token The contract address of the token which will be transferred
/// @param to The recipient of the transfer
/// @param value The value of the transfer
function safeTransfer(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'ST');
}
/// @notice Approves the stipulated contract to spend the given allowance in the given token
/// @dev Errors with 'SA' if transfer fails
/// @param token The contract address of the token to be approved
/// @param to The target of the approval
/// @param value The amount of the given token the target will be allowed to spend
function safeApprove(
address token,
address to,
uint256 value
) internal {
(bool success, bytes memory data) = token.call(abi.encodeWithSelector(IERC20.approve.selector, to, value));
require(success && (data.length == 0 || abi.decode(data, (bool))), 'SA');
}
/// @notice Transfers ETH to the recipient address
/// @dev Fails with `STE`
/// @param to The destination of the transfer
/// @param value The value to be transferred
function safeTransferETH(address to, uint256 value) internal {
(bool success, ) = to.call{value: value}(new bytes(0));
require(success, 'STE');
}
}
// File @openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol@v5.0.1
// Original license: SPDX_License_Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (token/ERC20/extensions/IERC20Permit.sol)
/**
* @dev Interface of the ERC20 Permit extension allowing approvals to be made via signatures, as defined in
* https://eips.ethereum.org/EIPS/eip-2612[EIP-2612].
*
* Adds the {permit} method, which can be used to change an account's ERC20 allowance (see {IERC20-allowance}) by
* presenting a message signed by the account. By not relying on {IERC20-approve}, the token holder account doesn't
* need to send a transaction, and thus is not required to hold Ether at all.
*
* ==== Security Considerations
*
* There are two important considerations concerning the use of `permit`. The first is that a valid permit signature
* expresses an allowance, and it should not be assumed to convey additional meaning. In particular, it should not be
* considered as an intention to spend the allowance in any specific way. The second is that because permits have
* built-in replay protection and can be submitted by anyone, they can be frontrun. A protocol that uses permits should
* take this into consideration and allow a `permit` call to fail. Combining these two aspects, a pattern that may be
* generally recommended is:
*
* ```solidity
* function doThingWithPermit(..., uint256 value, uint256 deadline, uint8 v, bytes32 r, bytes32 s) public {
* try token.permit(msg.sender, address(this), value, deadline, v, r, s) {} catch {}
* doThing(..., value);
* }
*
* function doThing(..., uint256 value) public {
* token.safeTransferFrom(msg.sender, address(this), value);
* ...
* }
* ```
*
* Observe that: 1) `msg.sender` is used as the owner, leaving no ambiguity as to the signer intent, and 2) the use of
* `try/catch` allows the permit to fail and makes the code tolerant to frontrunning. (See also
* {SafeERC20-safeTransferFrom}).
*
* Additionally, note that smart contract wallets (such as Argent or Safe) are not able to produce permit signatures, so
* contracts should have entry points that don't rely on permit.
*/
interface IERC20Permit {
/**
* @dev Sets `value` as the allowance of `spender` over ``owner``'s tokens,
* given ``owner``'s signed approval.
*
* IMPORTANT: The same issues {IERC20-approve} has related to transaction
* ordering also apply here.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `spender` cannot be the zero address.
* - `deadline` must be a timestamp in the future.
* - `v`, `r` and `s` must be a valid `secp256k1` signature from `owner`
* over the EIP712-formatted function arguments.
* - the signature must use ``owner``'s current nonce (see {nonces}).
*
* For more information on the signature format, see the
* https://eips.ethereum.org/EIPS/eip-2612#specification[relevant EIP
* section].
*
* CAUTION: See Security Considerations above.
*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) external;
/**
* @dev Returns the current nonce for `owner`. This value must be
* included whenever a signature is generated for {permit}.
*
* Every successful call to {permit} increases ``owner``'s nonce by one. This
* prevents a signature from being used multiple times.
*/
function nonces(address owner) external view returns (uint256);
/**
* @dev Returns the domain separator used in the encoding of the signature for {permit}, as defined by {EIP712}.
*/
// solhint-disable-next-line func-name-mixedcase
function DOMAIN_SEPARATOR() external view returns (bytes32);
}
// File @openzeppelin/contracts/utils/math/Math.sol@v5.0.1
// Original license: SPDX_License_Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (utils/math/Math.sol)
/**
* @dev Standard math utilities missing in the Solidity language.
*/
library Math {
/**
* @dev Muldiv operation overflow.
*/
error MathOverflowedMulDiv();
enum Rounding {
Floor, // Toward negative infinity
Ceil, // Toward positive infinity
Trunc, // Toward zero
Expand // Away from zero
}
/**
* @dev Returns the addition of two unsigned integers, with an overflow flag.
*/
function tryAdd(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
uint256 c = a + b;
if (c < a) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the subtraction of two unsigned integers, with an overflow flag.
*/
function trySub(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b > a) return (false, 0);
return (true, a - b);
}
}
/**
* @dev Returns the multiplication of two unsigned integers, with an overflow flag.
*/
function tryMul(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
if (a == 0) return (true, 0);
uint256 c = a * b;
if (c / a != b) return (false, 0);
return (true, c);
}
}
/**
* @dev Returns the division of two unsigned integers, with a division by zero flag.
*/
function tryDiv(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a / b);
}
}
/**
* @dev Returns the remainder of dividing two unsigned integers, with a division by zero flag.
*/
function tryMod(uint256 a, uint256 b) internal pure returns (bool, uint256) {
unchecked {
if (b == 0) return (false, 0);
return (true, a % b);
}
}
/**
* @dev Returns the largest of two numbers.
*/
function max(uint256 a, uint256 b) internal pure returns (uint256) {
return a > b ? a : b;
}
/**
* @dev Returns the smallest of two numbers.
*/
function min(uint256 a, uint256 b) internal pure returns (uint256) {
return a < b ? a : b;
}
/**
* @dev Returns the average of two numbers. The result is rounded towards
* zero.
*/
function average(uint256 a, uint256 b) internal pure returns (uint256) {
// (a + b) / 2 can overflow.
return (a & b) + (a ^ b) / 2;
}
/**
* @dev Returns the ceiling of the division of two numbers.
*
* This differs from standard division with `/` in that it rounds towards infinity instead
* of rounding towards zero.
*/
function ceilDiv(uint256 a, uint256 b) internal pure returns (uint256) {
if (b == 0) {
// Guarantee the same behavior as in a regular Solidity division.
return a / b;
}
// (a + b - 1) / b can overflow on addition, so we distribute.
return a == 0 ? 0 : (a - 1) / b + 1;
}
/**
* @notice Calculates floor(x * y / denominator) with full precision. Throws if result overflows a uint256 or
* denominator == 0.
* @dev Original credit to Remco Bloemen under MIT license (https://xn--2-umb.com/21/muldiv) with further edits by
* Uniswap Labs also under MIT license.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator) internal pure returns (uint256 result) {
unchecked {
// 512-bit multiply [prod1 prod0] = x * y. Compute the product mod 2^256 and mod 2^256 - 1, then use
// use the Chinese Remainder Theorem to reconstruct the 512 bit result. The result is stored in two 256
// variables such that product = prod1 * 2^256 + prod0.
uint256 prod0 = x * y; // Least significant 256 bits of the product
uint256 prod1; // Most significant 256 bits of the product
assembly {
let mm := mulmod(x, y, not(0))
prod1 := sub(sub(mm, prod0), lt(mm, prod0))
}
// Handle non-overflow cases, 256 by 256 division.
if (prod1 == 0) {
// Solidity will revert if denominator == 0, unlike the div opcode on its own.
// The surrounding unchecked block does not change this fact.
// See https://docs.soliditylang.org/en/latest/control-structures.html#checked-or-unchecked-arithmetic.
return prod0 / denominator;
}
// Make sure the result is less than 2^256. Also prevents denominator == 0.
if (denominator <= prod1) {
revert MathOverflowedMulDiv();
}
///////////////////////////////////////////////
// 512 by 256 division.
///////////////////////////////////////////////
// Make division exact by subtracting the remainder from [prod1 prod0].
uint256 remainder;
assembly {
// Compute remainder using mulmod.
remainder := mulmod(x, y, denominator)
// Subtract 256 bit number from 512 bit number.
prod1 := sub(prod1, gt(remainder, prod0))
prod0 := sub(prod0, remainder)
}
// Factor powers of two out of denominator and compute largest power of two divisor of denominator.
// Always >= 1. See https://cs.stackexchange.com/q/138556/92363.
uint256 twos = denominator & (0 - denominator);
assembly {
// Divide denominator by twos.
denominator := div(denominator, twos)
// Divide [prod1 prod0] by twos.
prod0 := div(prod0, twos)
// Flip twos such that it is 2^256 / twos. If twos is zero, then it becomes one.
twos := add(div(sub(0, twos), twos), 1)
}
// Shift in bits from prod1 into prod0.
prod0 |= prod1 * twos;
// Invert denominator mod 2^256. Now that denominator is an odd number, it has an inverse modulo 2^256 such
// that denominator * inv = 1 mod 2^256. Compute the inverse by starting with a seed that is correct for
// four bits. That is, denominator * inv = 1 mod 2^4.
uint256 inverse = (3 * denominator) ^ 2;
// Use the Newton-Raphson iteration to improve the precision. Thanks to Hensel's lifting lemma, this also
// works in modular arithmetic, doubling the correct bits in each step.
inverse *= 2 - denominator * inverse; // inverse mod 2^8
inverse *= 2 - denominator * inverse; // inverse mod 2^16
inverse *= 2 - denominator * inverse; // inverse mod 2^32
inverse *= 2 - denominator * inverse; // inverse mod 2^64
inverse *= 2 - denominator * inverse; // inverse mod 2^128
inverse *= 2 - denominator * inverse; // inverse mod 2^256
// Because the division is now exact we can divide by multiplying with the modular inverse of denominator.
// This will give us the correct result modulo 2^256. Since the preconditions guarantee that the outcome is
// less than 2^256, this is the final result. We don't need to compute the high bits of the result and prod1
// is no longer required.
result = prod0 * inverse;
return result;
}
}
/**
* @notice Calculates x * y / denominator with full precision, following the selected rounding direction.
*/
function mulDiv(uint256 x, uint256 y, uint256 denominator, Rounding rounding) internal pure returns (uint256) {
uint256 result = mulDiv(x, y, denominator);
if (unsignedRoundsUp(rounding) && mulmod(x, y, denominator) > 0) {
result += 1;
}
return result;
}
/**
* @dev Returns the square root of a number. If the number is not a perfect square, the value is rounded
* towards zero.
*
* Inspired by Henry S. Warren, Jr.'s "Hacker's Delight" (Chapter 11).
*/
function sqrt(uint256 a) internal pure returns (uint256) {
if (a == 0) {
return 0;
}
// For our first guess, we get the biggest power of 2 which is smaller than the square root of the target.
//
// We know that the "msb" (most significant bit) of our target number `a` is a power of 2 such that we have
// `msb(a) <= a < 2*msb(a)`. This value can be written `msb(a)=2**k` with `k=log2(a)`.
//
// This can be rewritten `2**log2(a) <= a < 2**(log2(a) + 1)`
// → `sqrt(2**k) <= sqrt(a) < sqrt(2**(k+1))`
// → `2**(k/2) <= sqrt(a) < 2**((k+1)/2) <= 2**(k/2 + 1)`
//
// Consequently, `2**(log2(a) / 2)` is a good first approximation of `sqrt(a)` with at least 1 correct bit.
uint256 result = 1 << (log2(a) >> 1);
// At this point `result` is an estimation with one bit of precision. We know the true value is a uint128,
// since it is the square root of a uint256. Newton's method converges quadratically (precision doubles at
// every iteration). We thus need at most 7 iteration to turn our partial result with one bit of precision
// into the expected uint128 result.
unchecked {
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
result = (result + a / result) >> 1;
return min(result, a / result);
}
}
/**
* @notice Calculates sqrt(a), following the selected rounding direction.
*/
function sqrt(uint256 a, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = sqrt(a);
return result + (unsignedRoundsUp(rounding) && result * result < a ? 1 : 0);
}
}
/**
* @dev Return the log in base 2 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log2(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 128;
}
if (value >> 64 > 0) {
value >>= 64;
result += 64;
}
if (value >> 32 > 0) {
value >>= 32;
result += 32;
}
if (value >> 16 > 0) {
value >>= 16;
result += 16;
}
if (value >> 8 > 0) {
value >>= 8;
result += 8;
}
if (value >> 4 > 0) {
value >>= 4;
result += 4;
}
if (value >> 2 > 0) {
value >>= 2;
result += 2;
}
if (value >> 1 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 2, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log2(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log2(value);
return result + (unsignedRoundsUp(rounding) && 1 << result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 10 of a positive value rounded towards zero.
* Returns 0 if given 0.
*/
function log10(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >= 10 ** 64) {
value /= 10 ** 64;
result += 64;
}
if (value >= 10 ** 32) {
value /= 10 ** 32;
result += 32;
}
if (value >= 10 ** 16) {
value /= 10 ** 16;
result += 16;
}
if (value >= 10 ** 8) {
value /= 10 ** 8;
result += 8;
}
if (value >= 10 ** 4) {
value /= 10 ** 4;
result += 4;
}
if (value >= 10 ** 2) {
value /= 10 ** 2;
result += 2;
}
if (value >= 10 ** 1) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 10, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log10(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log10(value);
return result + (unsignedRoundsUp(rounding) && 10 ** result < value ? 1 : 0);
}
}
/**
* @dev Return the log in base 256 of a positive value rounded towards zero.
* Returns 0 if given 0.
*
* Adding one to the result gives the number of pairs of hex symbols needed to represent `value` as a hex string.
*/
function log256(uint256 value) internal pure returns (uint256) {
uint256 result = 0;
unchecked {
if (value >> 128 > 0) {
value >>= 128;
result += 16;
}
if (value >> 64 > 0) {
value >>= 64;
result += 8;
}
if (value >> 32 > 0) {
value >>= 32;
result += 4;
}
if (value >> 16 > 0) {
value >>= 16;
result += 2;
}
if (value >> 8 > 0) {
result += 1;
}
}
return result;
}
/**
* @dev Return the log in base 256, following the selected rounding direction, of a positive value.
* Returns 0 if given 0.
*/
function log256(uint256 value, Rounding rounding) internal pure returns (uint256) {
unchecked {
uint256 result = log256(value);
return result + (unsignedRoundsUp(rounding) && 1 << (result << 3) < value ? 1 : 0);
}
}
/**
* @dev Returns whether a provided rounding mode is considered rounding up for unsigned integers.
*/
function unsignedRoundsUp(Rounding rounding) internal pure returns (bool) {
return uint8(rounding) % 2 == 1;
}
}
// File contracts/Fraxferry/Fraxferry.sol
// ====================================================================
// | ______ _______ |
// | / _____________ __ __ / ____(_____ ____ _____ ________ |
// | / /_ / ___/ __ `| |/_/ / /_ / / __ \/ __ `/ __ \/ ___/ _ \ |
// | / __/ / / / /_/ _> < / __/ / / / / / /_/ / / / / /__/ __/ |
// | /_/ /_/ \__,_/_/|_| /_/ /_/_/ /_/\__,_/_/ /_/\___/\___/ |
// | |
// ====================================================================
// ============================ Fraxferry =============================
// ====================================================================
// Ferry that can be used to ship tokens between chains
// Frax Finance: https://github.com/FraxFinance
// Primary Author(s)
// Dennis: https://github.com/denett
/*
** Modus operandi:
** - User sends tokens to the contract. This transaction is stored in the contract.
** - Captain queries the source chain for transactions to ship.
** - Captain sends batch (start, end, hash) to start the trip,
** - Crewmembers check the batch and can dispute it if it is invalid.
** - Non disputed batches can be executed by the first officer by providing the transactions as calldata.
** - Hash of the transactions must be equal to the hash in the batch. User receives their tokens on the other chain.
** - In case there was a fraudulent transaction (a hacker for example), the owner can cancel a single transaction, such that it will not be executed.
** - The owner can manually manage the tokens in the contract and must make sure it has enough funds.
**
** What must happen for a false batch to be executed:
** - Captain is tricked into proposing a batch with a false hash
** - All crewmembers bots are offline/censured/compromised and no one disputes the proposal
**
** Other risks:
** - Reorgs on the source chain. Avoided, by only returning the transactions on the source chain that are at least one hour old.
** - Rollbacks of optimistic rollups. Avoided by running a node.
** - Operators do not have enough time to pause the chain after a fake proposal. Avoided by requiring a minimal amount of time between sending the proposal and executing it.
*/
contract Fraxferry {
IERC20 immutable public token;
IERC20 immutable public targetToken;
uint immutable public chainid;
uint immutable public targetChain;
address public owner;
address public nominatedOwner;
address public captain;
address public firstOfficer;
mapping(address => bool) public crewmembers;
mapping(address => bool) public fee_exempt_addrs;
bool public paused;
uint public MIN_WAIT_PERIOD_ADD=3600; // Minimal 1 hour waiting
uint public MIN_WAIT_PERIOD_EXECUTE=79200; // Minimal 22 hour waiting
uint public FEE_RATE=10; // 0.1% fee
uint public FEE_MIN=5*1e18; // 5 token min fee
uint public FEE_MAX=100*1e18; // 100 token max fee
uint constant MAX_FEE_RATE=100; // Max fee rate is 1%
uint constant MAX_FEE_MIN=100e18; // Max minimum fee is 100 tokens
uint constant MAX_FEE_MAX=1000e18; // Max fee is 1000 tokens
uint constant public REDUCED_DECIMALS=1e10;
Transaction[] public transactions;
mapping(uint => bool) public cancelled;
uint public executeIndex;
Batch[] public batches;
struct Transaction {
address user;
uint64 amount;
uint32 timestamp;
}
struct Batch {
uint64 start;
uint64 end;
uint64 departureTime;
uint64 status;
bytes32 hash;
}
struct BatchData {
uint startTransactionNo;
Transaction[] transactions;
}
constructor(address _token, uint _chainid, address _targetToken, uint _targetChain) {
//require (block.chainid==_chainid,"Wrong chain");
chainid=_chainid;
token = IERC20(_token);
targetToken = IERC20(_targetToken);
owner = msg.sender;
targetChain = _targetChain;
}
// ############## Events ##############
event Embark(address indexed sender, uint index, uint amount, uint amountAfterFee, uint timestamp);
event Disembark(uint start, uint end, bytes32 hash);
event Depart(uint batchNo,uint start,uint end,bytes32 hash);
event RemoveBatch(uint batchNo);
event DisputeBatch(uint batchNo, bytes32 hash);
event Cancelled(uint index, bool cancel);
event Pause(bool paused);
event OwnerNominated(address indexed newOwner);
event OwnerChanged(address indexed previousOwner,address indexed newOwner);
event SetCaptain(address indexed previousCaptain, address indexed newCaptain);
event SetFirstOfficer(address indexed previousFirstOfficer, address indexed newFirstOfficer);
event SetCrewmember(address indexed crewmember,bool set);
event SetFee(uint previousFeeRate, uint feeRate,uint previousFeeMin, uint feeMin,uint previousFeeMax, uint feeMax);
event SetMinWaitPeriods(uint previousMinWaitAdd,uint previousMinWaitExecute,uint minWaitAdd,uint minWaitExecute);
event FeeExemptToggled(address addr,bool is_fee_exempt);
// ############## Modifiers ##############
modifier isOwner() {
require (msg.sender==owner,"Not owner");
_;
}
modifier isCaptain() {
require (msg.sender==captain,"Not captain");
_;
}
modifier isFirstOfficer() {
require (msg.sender==firstOfficer,"Not first officer");
_;
}
modifier isCrewmember() {
require (crewmembers[msg.sender] || msg.sender==owner || msg.sender==captain || msg.sender==firstOfficer,"Not crewmember");
_;
}
modifier notPaused() {
require (!paused,"Paused");
_;
}
// ############## Ferry actions ##############
function embarkWithRecipient(uint amount, address recipient) public notPaused {
amount = (amount/REDUCED_DECIMALS)*REDUCED_DECIMALS; // Round amount to fit in data structure
uint fee;
if(fee_exempt_addrs[msg.sender]) fee = 0;
else {
fee = Math.min(Math.max(FEE_MIN,amount*FEE_RATE/10000),FEE_MAX);
}
require (amount>fee,"Amount too low");
require (amount/REDUCED_DECIMALS<=type(uint64).max,"Amount too high");
TransferHelper.safeTransferFrom(address(token),msg.sender,address(this),amount);
uint64 amountAfterFee = uint64((amount-fee)/REDUCED_DECIMALS);
emit Embark(recipient,transactions.length,amount,amountAfterFee*REDUCED_DECIMALS,block.timestamp);
transactions.push(Transaction(recipient,amountAfterFee,uint32(block.timestamp)));
}
function embark(uint amount) public {
embarkWithRecipient(amount, msg.sender) ;
}
function embarkWithSignature(
uint256 _amount,
address recipient,
uint256 deadline,
bool approveMax,
uint8 v,
bytes32 r,
bytes32 s
) public {
uint amount = approveMax ? type(uint256).max : _amount;
IERC20Permit(address(token)).permit(msg.sender, address(this), amount, deadline, v, r, s);
embarkWithRecipient(amount,recipient);
}
function depart(uint start, uint end, bytes32 hash) external notPaused isCaptain {
require ((batches.length==0 && start==0) || (batches.length>0 && start==batches[batches.length-1].end+1),"Wrong start");
require (end>=start && end<type(uint64).max,"Wrong end");
batches.push(Batch(uint64(start),uint64(end),uint64(block.timestamp),0,hash));
emit Depart(batches.length-1,start,end,hash);
}
function disembark(BatchData calldata batchData) external notPaused isFirstOfficer {
Batch memory batch = batches[executeIndex++];
require (batch.status==0,"Batch disputed");
require (batch.start==batchData.startTransactionNo,"Wrong start");
require (batch.start+batchData.transactions.length-1==batch.end,"Wrong size");
require (block.timestamp-batch.departureTime>=MIN_WAIT_PERIOD_EXECUTE,"Too soon");
bytes32 hash = keccak256(abi.encodePacked(targetChain, targetToken, chainid, token, batch.start));
for (uint i=0;i<batchData.transactions.length;++i) {
if (!cancelled[batch.start+i]) {
TransferHelper.safeTransfer(address(token),batchData.transactions[i].user,batchData.transactions[i].amount*REDUCED_DECIMALS);
}
hash = keccak256(abi.encodePacked(hash, batchData.transactions[i].user,batchData.transactions[i].amount));
}
require (batch.hash==hash,"Wrong hash");
emit Disembark(batch.start,batch.end,hash);
}
function removeBatches(uint batchNo) external isOwner {
require (executeIndex<=batchNo,"Batch already executed");
while (batches.length>batchNo) batches.pop();
emit RemoveBatch(batchNo);
}
function disputeBatch(uint batchNo, bytes32 hash) external isCrewmember {
require (batches[batchNo].hash==hash,"Wrong hash");
require (executeIndex<=batchNo,"Batch already executed");
require (batches[batchNo].status==0,"Batch already disputed");
batches[batchNo].status=1; // Set status on disputed
_pause(true);
emit DisputeBatch(batchNo,hash);
}
function pause() external isCrewmember {
_pause(true);
}
function unPause() external isOwner {
_pause(false);
}
function _pause(bool _paused) internal {
paused=_paused;
emit Pause(_paused);
}
function _jettison(uint index, bool cancel) internal {
require (executeIndex==0 || index>batches[executeIndex-1].end,"Transaction already executed");
cancelled[index]=cancel;
emit Cancelled(index,cancel);
}
function jettison(uint index, bool cancel) external isOwner {
_jettison(index,cancel);
}
function jettisonGroup(uint[] calldata indexes, bool cancel) external isOwner {
for (uint i=0;i<indexes.length;++i) {
_jettison(indexes[i],cancel);
}
}
// ############## Parameters management ##############
function setFee(uint _FEE_RATE, uint _FEE_MIN, uint _FEE_MAX) external isOwner {
require(_FEE_RATE<MAX_FEE_RATE);
require(_FEE_MIN<MAX_FEE_MIN);
require(_FEE_MAX<MAX_FEE_MAX);
emit SetFee(FEE_RATE,_FEE_RATE,FEE_MIN,_FEE_MIN,FEE_MAX,_FEE_MAX);
FEE_RATE=_FEE_RATE;
FEE_MIN=_FEE_MIN;
FEE_MAX=_FEE_MAX;
}
function setMinWaitPeriods(uint _MIN_WAIT_PERIOD_ADD, uint _MIN_WAIT_PERIOD_EXECUTE) external isOwner {
require(_MIN_WAIT_PERIOD_ADD>=3600 && _MIN_WAIT_PERIOD_EXECUTE>=3600,"Period too short");
emit SetMinWaitPeriods(MIN_WAIT_PERIOD_ADD, MIN_WAIT_PERIOD_EXECUTE,_MIN_WAIT_PERIOD_ADD, _MIN_WAIT_PERIOD_EXECUTE);
MIN_WAIT_PERIOD_ADD=_MIN_WAIT_PERIOD_ADD;
MIN_WAIT_PERIOD_EXECUTE=_MIN_WAIT_PERIOD_EXECUTE;
}
// ############## Roles management ##############
function nominateNewOwner(address newOwner) external isOwner {
nominatedOwner = newOwner;
emit OwnerNominated(newOwner);
}
function acceptOwnership() external {
require(msg.sender == nominatedOwner, "You must be nominated before you can accept ownership");
emit OwnerChanged(owner, nominatedOwner);
owner = nominatedOwner;
nominatedOwner = address(0);
}
function setCaptain(address newCaptain) external isOwner {
emit SetCaptain(captain,newCaptain);
captain=newCaptain;
}
function setFirstOfficer(address newFirstOfficer) external isOwner {
emit SetFirstOfficer(firstOfficer,newFirstOfficer);
firstOfficer=newFirstOfficer;
}
function setCrewmember(address crewmember, bool set) external isOwner {
crewmembers[crewmember]=set;
emit SetCrewmember(crewmember,set);
}
function toggleFeeExemptAddr(address addr) external isOwner {
fee_exempt_addrs[addr] = !fee_exempt_addrs[addr];
emit FeeExemptToggled(addr,fee_exempt_addrs[addr]);
}
// ############## Token management ##############
function sendTokens(address receiver, uint amount) external isOwner {
require (receiver!=address(0),"Zero address not allowed");
TransferHelper.safeTransfer(address(token),receiver,amount);
}
// Generic proxy
function execute(address _to, uint256 _value, bytes calldata _data) external isOwner returns (bool, bytes memory) {
require(_data.length==0 || _to.code.length>0,"Can not call a function on a EOA");
(bool success, bytes memory result) = _to.call{value:_value}(_data);
return (success, result);
}
// ############## Views ##############
function getNextBatch(uint _start, uint max) public view returns (uint start, uint end, bytes32 hash) {
uint cutoffTime = block.timestamp-MIN_WAIT_PERIOD_ADD;
if (_start<transactions.length && transactions[_start].timestamp<cutoffTime) {
start=_start;
end=start+max-1;
if (end>=transactions.length) end=transactions.length-1;
while(transactions[end].timestamp>=cutoffTime) end--;
hash = getTransactionsHash(start,end);
}
}
function getBatchData(uint start, uint end) public view returns (BatchData memory data) {
data.startTransactionNo = start;
data.transactions = new Transaction[](end-start+1);
for (uint i=start;i<=end;++i) {
data.transactions[i-start]=transactions[i];
}
}
function getBatchAmount(uint start, uint end) public view returns (uint totalAmount) {
for (uint i=start;i<=end;++i) {
totalAmount+=transactions[i].amount;
}
totalAmount*=REDUCED_DECIMALS;
}
function getTransactionsHash(uint start, uint end) public view returns (bytes32) {
bytes32 result = keccak256(abi.encodePacked(chainid, token, targetChain, targetToken, uint64(start)));
for (uint i=start;i<=end;++i) {
result = keccak256(abi.encodePacked(result, transactions[i].user,transactions[i].amount));
}
return result;
}
function noTransactions() public view returns (uint) {
return transactions.length;
}
function noBatches() public view returns (uint) {
return batches.length;
}
}
{
"compilationTarget": {
"Fraxferry.sol": "Fraxferry"
},
"evmVersion": "paris",
"libraries": {},
"metadata": {
"bytecodeHash": "ipfs"
},
"optimizer": {
"enabled": true,
"runs": 1000000
},
"remappings": []
}
[{"inputs":[{"internalType":"address","name":"_token","type":"address"},{"internalType":"uint256","name":"_chainid","type":"uint256"},{"internalType":"address","name":"_targetToken","type":"address"},{"internalType":"uint256","name":"_targetChain","type":"uint256"}],"stateMutability":"nonpayable","type":"constructor"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"bool","name":"cancel","type":"bool"}],"name":"Cancelled","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"batchNo","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"start","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"end","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"Depart","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"start","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"end","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"Disembark","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"batchNo","type":"uint256"},{"indexed":false,"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"DisputeBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"sender","type":"address"},{"indexed":false,"internalType":"uint256","name":"index","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amount","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"amountAfterFee","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"timestamp","type":"uint256"}],"name":"Embark","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"addr","type":"address"},{"indexed":false,"internalType":"bool","name":"is_fee_exempt","type":"bool"}],"name":"FeeExemptToggled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousOwner","type":"address"},{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newOwner","type":"address"}],"name":"OwnerNominated","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"bool","name":"paused","type":"bool"}],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"batchNo","type":"uint256"}],"name":"RemoveBatch","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousCaptain","type":"address"},{"indexed":true,"internalType":"address","name":"newCaptain","type":"address"}],"name":"SetCaptain","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"crewmember","type":"address"},{"indexed":false,"internalType":"bool","name":"set","type":"bool"}],"name":"SetCrewmember","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousFeeRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeRate","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"previousFeeMin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeMin","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"previousFeeMax","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"feeMax","type":"uint256"}],"name":"SetFee","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"previousFirstOfficer","type":"address"},{"indexed":true,"internalType":"address","name":"newFirstOfficer","type":"address"}],"name":"SetFirstOfficer","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"uint256","name":"previousMinWaitAdd","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"previousMinWaitExecute","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minWaitAdd","type":"uint256"},{"indexed":false,"internalType":"uint256","name":"minWaitExecute","type":"uint256"}],"name":"SetMinWaitPeriods","type":"event"},{"inputs":[],"name":"FEE_MAX","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_MIN","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"FEE_RATE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_WAIT_PERIOD_ADD","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"MIN_WAIT_PERIOD_EXECUTE","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"REDUCED_DECIMALS","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"acceptOwnership","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"batches","outputs":[{"internalType":"uint64","name":"start","type":"uint64"},{"internalType":"uint64","name":"end","type":"uint64"},{"internalType":"uint64","name":"departureTime","type":"uint64"},{"internalType":"uint64","name":"status","type":"uint64"},{"internalType":"bytes32","name":"hash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"cancelled","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"captain","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"chainid","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"crewmembers","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"},{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"depart","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"components":[{"internalType":"uint256","name":"startTransactionNo","type":"uint256"},{"components":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct Fraxferry.Transaction[]","name":"transactions","type":"tuple[]"}],"internalType":"struct Fraxferry.BatchData","name":"batchData","type":"tuple"}],"name":"disembark","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"batchNo","type":"uint256"},{"internalType":"bytes32","name":"hash","type":"bytes32"}],"name":"disputeBatch","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"embark","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"}],"name":"embarkWithRecipient","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_amount","type":"uint256"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"bool","name":"approveMax","type":"bool"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"embarkWithSignature","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"_to","type":"address"},{"internalType":"uint256","name":"_value","type":"uint256"},{"internalType":"bytes","name":"_data","type":"bytes"}],"name":"execute","outputs":[{"internalType":"bool","name":"","type":"bool"},{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"executeIndex","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"","type":"address"}],"name":"fee_exempt_addrs","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"firstOfficer","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"getBatchAmount","outputs":[{"internalType":"uint256","name":"totalAmount","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"getBatchData","outputs":[{"components":[{"internalType":"uint256","name":"startTransactionNo","type":"uint256"},{"components":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"internalType":"struct Fraxferry.Transaction[]","name":"transactions","type":"tuple[]"}],"internalType":"struct Fraxferry.BatchData","name":"data","type":"tuple"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"_start","type":"uint256"},{"internalType":"uint256","name":"max","type":"uint256"}],"name":"getNextBatch","outputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"},{"internalType":"bytes32","name":"hash","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"start","type":"uint256"},{"internalType":"uint256","name":"end","type":"uint256"}],"name":"getTransactionsHash","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"index","type":"uint256"},{"internalType":"bool","name":"cancel","type":"bool"}],"name":"jettison","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256[]","name":"indexes","type":"uint256[]"},{"internalType":"bool","name":"cancel","type":"bool"}],"name":"jettisonGroup","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"noBatches","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"noTransactions","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"newOwner","type":"address"}],"name":"nominateNewOwner","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"nominatedOwner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"owner","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"batchNo","type":"uint256"}],"name":"removeBatches","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"receiver","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"sendTokens","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newCaptain","type":"address"}],"name":"setCaptain","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"crewmember","type":"address"},{"internalType":"bool","name":"set","type":"bool"}],"name":"setCrewmember","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_FEE_RATE","type":"uint256"},{"internalType":"uint256","name":"_FEE_MIN","type":"uint256"},{"internalType":"uint256","name":"_FEE_MAX","type":"uint256"}],"name":"setFee","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"newFirstOfficer","type":"address"}],"name":"setFirstOfficer","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"_MIN_WAIT_PERIOD_ADD","type":"uint256"},{"internalType":"uint256","name":"_MIN_WAIT_PERIOD_EXECUTE","type":"uint256"}],"name":"setMinWaitPeriods","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"targetChain","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"targetToken","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"addr","type":"address"}],"name":"toggleFeeExemptAddr","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"token","outputs":[{"internalType":"contract IERC20","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"uint256","name":"","type":"uint256"}],"name":"transactions","outputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"uint64","name":"amount","type":"uint64"},{"internalType":"uint32","name":"timestamp","type":"uint32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"unPause","outputs":[],"stateMutability":"nonpayable","type":"function"}]