diff --git a/.cache-1337.json b/.cache-1337.json new file mode 100644 index 0000000..5e6846c --- /dev/null +++ b/.cache-1337.json @@ -0,0 +1,46 @@ +{ + "forwarder": { + "txHash": "0xabceffacd191849a635434f71c6c4e0965eb4f05e13057f524d3704f029b3a15", + "address": "0x5FbDB2315678afecb367f032d93F642f64180aa3" + }, + "manager": { + "txHash": "0xc9d0611a94201902bb80b04482f93ad3cc6a45d483ccd0b655a9300dae717379", + "address": "0x9fE46736679d2D9a65F0992F2272dE9f3c7fa6e0" + }, + "redemption": { + "txHash": "0xca788e0f2bb12a67c857eca73431b059a35fea453294d6050fccbbb17ac7cf3c", + "address": "0xDc64a140Aa3E981100a9becA4E685f962f0cF6C9" + }, + "minter": { + "txHash": "0x8fa6badfd083a8a7efd8a6a0188d0535379be2372c386ffb95b76ad2019f6b94", + "address": "0x0165878A594ca255338adfa4d48449f69242Eb8F" + }, + "archiver": { + "txHash": "0xdc71d9a09c4dc2fc2e98780b5acb290a85bfeeff1f51f63a281a745d23557181", + "address": "0x2279B7A0a67DB372996a5FaB50D91eAA73d2eBe6" + }, + "token-EUTB": { + "txHash": "0x146d1c10933360a7f8014022314068194a4c6ae1bee886cbce22860ff029b0f5", + "address": "0x610178dA211FEF7D417bC0e6FeD39F05609AD788" + }, + "oracle-EUTB": { + "txHash": "0xa0c548c53c733beada35289131e930cf58a5d8320cbf070b4ad8aa60ef993d4e", + "address": "0xA51c1fc2f0D1a1b8494Ed1FE312d7C3a78Ed91C0" + }, + "token-USTB": { + "txHash": "0xd4723b4c25578d7217e2f42439e99b0d4bd453a909311b64ae92ad659e01e02f", + "address": "0x0DCd1Bf9A1b36cE34237eEaFef220932846BCD82" + }, + "oracle-USTB": { + "txHash": "0xd25749e9d7a74d0763adc3441b064b3e1a5b9a2a4285b54dfb3cfaba047d481b", + "address": "0x9A676e781A523b5d0C0e43731313A708CB607508" + }, + "token-eurUSTB": { + "txHash": "0xe5d58b270f1e5c9b6518a3a72d8a273423ff12463ef8194880af868ff17f80ad", + "address": "0x0B306BF915C4d645ff596e518fAf3F9669b97016" + }, + "oracle-eurUSTB": { + "txHash": "0x524103cbb7ae4d740021e86e3fb2b83722043557fd97023f06e60e3e84d98610", + "address": "0x959922bE3CAee4b8Cd9a407cc3ac1C251C2007B1" + } +} \ No newline at end of file diff --git a/contracts/archiver/Archiver.sol b/contracts/archiver/Archiver.sol new file mode 100644 index 0000000..f6cc2ed --- /dev/null +++ b/contracts/archiver/Archiver.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT + +pragma solidity ^0.8.20; + +import { IERC165 } from "@openzeppelin/contracts/utils/introspection/IERC165.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { IAuthority, PermissionManaged } from "../permissions/PermissionManaged.sol"; +import { EventArchiveAmendable } from "@spiko/erc-cross-chain-event-archive/src/EventArchiveAmendable.sol"; +import { IEventArchiveWriter } from "@spiko/erc-cross-chain-event-archive/src/interfaces/IEventArchiveWriter.sol"; +import { IEventArchiveAmender } from "@spiko/erc-cross-chain-event-archive/src/interfaces/IEventArchiveAmender.sol"; + +contract Archiver is EventArchiveAmendable, IEventArchiveWriter, PermissionManaged, UUPSUpgradeable, Multicall { + /// @custom:oz-upgrades-unsafe-allow constructor + constructor(IAuthority _authority) PermissionManaged(_authority) {} + + function archiveEvent( + string calldata sourceChainId, + bytes32 sourceTxHash, + bytes32 sourceAddress, + uint256 sourceLogIndex, + uint256 sourceBlockNumber, + bytes calldata payload + ) external restricted { + _archiveEvent(sourceChainId, sourceTxHash, sourceAddress, sourceLogIndex, sourceBlockNumber, payload); + } + + function amendEvent( + bytes32 sourceChainIdHash, + bytes32 sourceTxHash, + uint256 sourceLogIndex, + bytes calldata payload + ) external restricted { + _amendEvent(sourceChainIdHash, sourceTxHash, sourceLogIndex, payload); + } + + /// @inheritdoc IERC165 + function supportsInterface(bytes4 interfaceId) public view override returns (bool) { + return interfaceId == type(IEventArchiveWriter).interfaceId || super.supportsInterface(interfaceId); + } + + /**************************************************************************************************************** + * UUPS upgrade * + ****************************************************************************************************************/ + function _authorizeUpgrade(address) internal view override { + _checkRestricted(UUPSUpgradeable.upgradeToAndCall.selector); + } +} diff --git a/contracts/import.sol b/contracts/import.sol index fca0d81..efeac7f 100644 --- a/contracts/import.sol +++ b/contracts/import.sol @@ -2,4 +2,4 @@ pragma solidity ^0.8.20; -import { ERC2771Forwarder } from "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; \ No newline at end of file +import { ERC2771Forwarder } from "@openzeppelin/contracts/metatx/ERC2771Forwarder.sol"; diff --git a/contracts/mocks/ERC1363ReceiverMock.sol b/contracts/mocks/ERC1363ReceiverMock.sol index 0689825..469f9e9 100644 --- a/contracts/mocks/ERC1363ReceiverMock.sol +++ b/contracts/mocks/ERC1363ReceiverMock.sol @@ -3,7 +3,7 @@ pragma solidity ^0.8.0; import { IERC1363Receiver } from "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol"; -import { IERC1363Spender } from "@openzeppelin/contracts/interfaces/IERC1363Spender.sol"; +import { IERC1363Spender } from "@openzeppelin/contracts/interfaces/IERC1363Spender.sol"; contract ERC1363ReceiverMock is IERC1363Receiver, IERC1363Spender { event TransferReceived(address operator, address from, uint256 value, bytes data); @@ -28,11 +28,7 @@ contract ERC1363ReceiverMock is IERC1363Receiver, IERC1363Spender { return this.onTransferReceived.selector; } - function onApprovalReceived( - address owner, - uint256 value, - bytes memory data - ) external override returns (bytes4) { + function onApprovalReceived(address owner, uint256 value, bytes memory data) external override returns (bytes4) { if (data.length == 1) { if (data[0] == 0x00) { revert("onApprovalReceived revert"); @@ -45,4 +41,4 @@ contract ERC1363ReceiverMock is IERC1363Receiver, IERC1363Spender { emit ApprovalReceived(owner, value, data); return this.onApprovalReceived.selector; } -} \ No newline at end of file +} diff --git a/contracts/mocks/ERC20DecimalsMock.sol b/contracts/mocks/ERC20DecimalsMock.sol index d4b1f78..c60a3cb 100644 --- a/contracts/mocks/ERC20DecimalsMock.sol +++ b/contracts/mocks/ERC20DecimalsMock.sol @@ -7,7 +7,9 @@ import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; contract ERC20DecimalsMock is ERC20("Mock Token Decimals", "MockD") { uint8 internal immutable _decimals; - constructor(uint8 decimals_) { _decimals = decimals_; } + constructor(uint8 decimals_) { + _decimals = decimals_; + } function decimals() public view override returns (uint8) { return _decimals; @@ -20,4 +22,4 @@ contract ERC20DecimalsMock is ERC20("Mock Token Decimals", "MockD") { function burn(address to, uint256 value) external { _burn(to, value); } -} \ No newline at end of file +} diff --git a/contracts/mocks/ERC20Mock.sol b/contracts/mocks/ERC20Mock.sol index 5fa12b8..88a8f26 100644 --- a/contracts/mocks/ERC20Mock.sol +++ b/contracts/mocks/ERC20Mock.sol @@ -12,4 +12,4 @@ contract ERC20Mock is ERC20("Mock Token", "Mock") { function burn(address to, uint256 value) external { _burn(to, value); } -} \ No newline at end of file +} diff --git a/contracts/oracle/Oracle.sol b/contracts/oracle/Oracle.sol index 7dfd886..7cd758b 100644 --- a/contracts/oracle/Oracle.sol +++ b/contracts/oracle/Oracle.sol @@ -3,31 +3,25 @@ pragma solidity ^0.8.20; import { AggregatorV3Interface } from "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; -import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { Checkpoints } from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; -import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; -import { PermissionManaged } from "../permissions/PermissionManaged.sol"; +import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Checkpoints } from "@openzeppelin/contracts/utils/structs/Checkpoints.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { PermissionManaged } from "../permissions/PermissionManaged.sol"; /// @custom:security-contact security@spiko.tech -contract Oracle is - AggregatorV3Interface, - Initializable, - PermissionManaged, - UUPSUpgradeable, - Multicall -{ - using Checkpoints for Checkpoints.Trace208; - using SafeCast for *; - - IERC20Metadata public token; - uint256 public constant version = 0; - uint8 public decimals; - string public description; // set per-instance at initialization - Checkpoints.Trace208 private _history; +contract Oracle is AggregatorV3Interface, Initializable, PermissionManaged, UUPSUpgradeable, Multicall { + using Checkpoints for Checkpoints.Trace208; + using SafeCast for *; + + IERC20Metadata public token; + uint256 public constant version = 0; + uint8 public decimals; + string public description; // set per-instance at initialization + Checkpoints.Trace208 private _history; event Update(uint48 timepoint, int256 price, uint256 round); @@ -36,9 +30,9 @@ contract Oracle is _disableInitializers(); } - function initialize(IERC20Metadata _token, uint8 _decimals, string calldata denomination) public initializer() { - token = _token; - decimals = _decimals; + function initialize(IERC20Metadata _token, uint8 _decimals, string calldata denomination) public initializer { + token = _token; + decimals = _decimals; description = string.concat(_token.symbol(), " / ", denomination); } @@ -55,7 +49,7 @@ contract Oracle is // Note: we are not using block.timestamp for the timepoint because of the mining delay for the update transaction // and the fact that prices represent a value at a specific "update" time (according to regulation). - function publishPrice(uint48 timepoint, uint208 price) public restricted() returns (uint80) { + function publishPrice(uint48 timepoint, uint208 price) public restricted returns (uint80) { uint80 roundId = _history.length().toUint80(); _history.push(timepoint, price); @@ -71,16 +65,12 @@ contract Oracle is // function decimals() public pure returns (uint8); -- implemented by a public variable // function description() public view returns (string memory); -- implemented by a public variable - function getRoundData(uint80 _roundId) + function getRoundData( + uint80 _roundId + ) public view - returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { require(_roundId < _history.length(), "No checkpoint for roundId"); Checkpoints.Checkpoint208 memory ckpt = _history._checkpoints[_roundId]; @@ -97,13 +87,7 @@ contract Oracle is function latestRoundData() public view - returns ( - uint80 roundId, - int256 answer, - uint256 startedAt, - uint256 updatedAt, - uint80 answeredInRound - ) + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { return getRoundData(_history.length().toUint80() - 1); } diff --git a/contracts/permissions/PermissionManager.sol b/contracts/permissions/PermissionManager.sol index fab1c7a..df7b45c 100644 --- a/contracts/permissions/PermissionManager.sol +++ b/contracts/permissions/PermissionManager.sol @@ -2,30 +2,25 @@ pragma solidity ^0.8.20; -import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; -import { Masks } from "../utils/Masks.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { Masks } from "../utils/Masks.sol"; /// @custom:security-contact security@spiko.tech /// @custom:oz-upgrades-unsafe-allow state-variable-immutable -contract PermissionManager is - IAuthority, - Initializable, - UUPSUpgradeable, - Multicall -{ +contract PermissionManager is IAuthority, Initializable, UUPSUpgradeable, Multicall { using Masks for *; - uint8 public constant ADMIN = 0x00; - uint8 public constant PUBLIC = 0xFF; - Masks.Mask public immutable ADMIN_MASK = ADMIN.toMask(); + uint8 public constant ADMIN = 0x00; + uint8 public constant PUBLIC = 0xFF; + Masks.Mask public immutable ADMIN_MASK = ADMIN.toMask(); Masks.Mask public immutable PUBLIC_MASK = PUBLIC.toMask(); - mapping(address => Masks.Mask ) private _permissions; + mapping(address => Masks.Mask) private _permissions; mapping(address => mapping(bytes4 => Masks.Mask)) private _restrictions; - mapping(uint8 => Masks.Mask ) private _admin; + mapping(uint8 => Masks.Mask) private _admin; event GroupAdded(address indexed user, uint8 indexed group); event GroupRemoved(address indexed user, uint8 indexed group); @@ -49,7 +44,7 @@ contract PermissionManager is _disableInitializers(); } - function initialize(address admin) public initializer() { + function initialize(address admin) public initializer { _addGroup(admin, 0); } @@ -100,7 +95,11 @@ contract PermissionManager is } // Requirement management - function setRequirements(address target, bytes4[] calldata selectors, uint8[] calldata groups) public onlyRole(ADMIN_MASK) { + function setRequirements( + address target, + bytes4[] calldata selectors, + uint8[] calldata groups + ) public onlyRole(ADMIN_MASK) { Masks.Mask mask = groups.toMask(); for (uint256 i = 0; i < selectors.length; ++i) { _setRequirements(target, selectors[i], mask); diff --git a/contracts/token/ATM3.sol b/contracts/token/ATM3.sol index 11f8053..301fb4f 100644 --- a/contracts/token/ATM3.sol +++ b/contracts/token/ATM3.sol @@ -2,17 +2,17 @@ pragma solidity ^0.8.20; -import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; -import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; -import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol"; -import { Context } from "@openzeppelin/contracts/utils/Context.sol"; -import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; -import { Oracle } from "../oracle/Oracle.sol"; +import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; +import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol"; +import { Context } from "@openzeppelin/contracts/utils/Context.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { Oracle } from "../oracle/Oracle.sol"; import { PermissionManaged } from "../permissions/PermissionManaged.sol"; function tryFetchDecimals(IERC20 token) view returns (uint8) { @@ -24,37 +24,43 @@ function tryFetchDecimals(IERC20 token) view returns (uint8) { } /// @custom:security-contact security@spiko.tech -contract ATM3 is ERC2771Context, PermissionManaged, Multicall -{ - using Math for *; +contract ATM3 is ERC2771Context, PermissionManaged, Multicall { + using Math for *; using SafeCast for *; - IERC20 immutable public token; - IERC20 immutable public stable; - Oracle immutable public oracle; - uint256 immutable public oraclettl; - uint256 immutable private numerator; - uint256 immutable private denominator; + IERC20 public immutable token; + IERC20 public immutable stable; + Oracle public immutable oracle; + uint256 public immutable oraclettl; + uint256 private immutable numerator; + uint256 private immutable denominator; /// @custom:oz-upgrades-unsafe-allow constructor - constructor(Oracle _oracle, IERC20 _token, IERC20 _stable, IAuthority _authority, address _trustedForwarder, uint256 _oraclettl) - PermissionManaged(_authority) - ERC2771Context(_trustedForwarder) - { - token = _token; - stable = _stable; - oracle = _oracle; + constructor( + Oracle _oracle, + IERC20 _token, + IERC20 _stable, + IAuthority _authority, + address _trustedForwarder, + uint256 _oraclettl + ) PermissionManaged(_authority) ERC2771Context(_trustedForwarder) { + token = _token; + stable = _stable; + oracle = _oracle; oraclettl = _oraclettl; // rate correction uint256 x = tryFetchDecimals(token) + oracle.decimals(); uint256 y = tryFetchDecimals(stable); - numerator = x < y ? 10 ** (y - x) : 1; + numerator = x < y ? 10 ** (y - x) : 1; denominator = x > y ? 10 ** (x - y) : 1; } - function previewBuy(IERC20 input, uint256 inputAmount) public view virtual returns (uint256 tokenAmount, uint256 stableAmount) { - (,int256 price) = _getPrices(); // max + function previewBuy( + IERC20 input, + uint256 inputAmount + ) public view virtual returns (uint256 tokenAmount, uint256 stableAmount) { + (, int256 price) = _getPrices(); // max if (input == token) { return (inputAmount, _convertToStable(inputAmount, price, Math.Rounding.Ceil)); } else if (input == stable) { @@ -64,8 +70,11 @@ contract ATM3 is ERC2771Context, PermissionManaged, Multicall } } - function previewSell(IERC20 input, uint256 inputAmount) public view virtual returns (uint256 tokenAmount, uint256 stableAmount) { - (int256 price,) = _getPrices(); // min + function previewSell( + IERC20 input, + uint256 inputAmount + ) public view virtual returns (uint256 tokenAmount, uint256 stableAmount) { + (int256 price, ) = _getPrices(); // min if (input == token) { return (inputAmount, _convertToStable(inputAmount, price, Math.Rounding.Floor)); } else if (input == stable) { @@ -76,8 +85,8 @@ contract ATM3 is ERC2771Context, PermissionManaged, Multicall } function _getPrices() internal view virtual returns (int256 min, int256 max) { - (uint80 roundId, int256 latest,,,) = oracle.latestRoundData(); - (, int256 previous,,uint256 updatedAt,) = oracle.getRoundData(roundId - 1); + (uint80 roundId, int256 latest, , , ) = oracle.latestRoundData(); + (, int256 previous, , uint256 updatedAt, ) = oracle.getRoundData(roundId - 1); require(block.timestamp < updatedAt + oraclettl, "oracle value too old"); min = SignedMath.min(latest, previous); max = SignedMath.max(latest, previous); @@ -97,25 +106,27 @@ contract ATM3 is ERC2771Context, PermissionManaged, Multicall return (tokenAmount, stableAmount); } - function _convertToStable(uint256 tokenAmount, int256 price, Math.Rounding rounding) internal view virtual returns (uint256) { + function _convertToStable( + uint256 tokenAmount, + int256 price, + Math.Rounding rounding + ) internal view virtual returns (uint256) { return tokenAmount.mulDiv(numerator * price.toUint256(), denominator, rounding); } - function _convertToToken(uint256 stableAmount, int256 price, Math.Rounding rounding) internal view virtual returns (uint256) { + function _convertToToken( + uint256 stableAmount, + int256 price, + Math.Rounding rounding + ) internal view virtual returns (uint256) { return stableAmount.mulDiv(denominator, numerator * price.toUint256(), rounding); } /**************************************************************************************************************** * Admin drain * ****************************************************************************************************************/ - function drain(IERC20 _token, address _to, uint256 _amount) public virtual restricted() { - SafeERC20.safeTransfer( - _token, - _to, - _amount == type(uint256).max - ? _token.balanceOf(address(this)) - : _amount - ); + function drain(IERC20 _token, address _to, uint256 _amount) public virtual restricted { + SafeERC20.safeTransfer(_token, _to, _amount == type(uint256).max ? _token.balanceOf(address(this)) : _amount); } /**************************************************************************************************************** diff --git a/contracts/token/MultiATM.sol b/contracts/token/MultiATM.sol index 0146460..a5afe37 100644 --- a/contracts/token/MultiATM.sol +++ b/contracts/token/MultiATM.sol @@ -2,23 +2,22 @@ pragma solidity ^0.8.27; -import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; -import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; -import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { Hashes } from "@openzeppelin/contracts/utils/cryptography/Hashes.sol"; -import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol"; -import { Context } from "@openzeppelin/contracts/utils/Context.sol"; -import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; -import { Oracle } from "../oracle/Oracle.sol"; +import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; +import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Hashes } from "@openzeppelin/contracts/utils/cryptography/Hashes.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol"; +import { Context } from "@openzeppelin/contracts/utils/Context.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { Oracle } from "../oracle/Oracle.sol"; import { PermissionManaged } from "../permissions/PermissionManaged.sol"; -contract MultiATM is ERC2771Context, PermissionManaged, Multicall -{ - using Math for *; +contract MultiATM is ERC2771Context, PermissionManaged, Multicall { + using Math for *; using SafeCast for *; uint256 private constant _BASIS_POINT_SCALE = 1e4; @@ -54,8 +53,21 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall mapping(bytes32 id => Pair) private _pairs; uint256 public feeBasisPoints; - event SwapExact(IERC20 indexed input, IERC20 indexed output, uint256 inputAmount, uint256 outputAmount, address from, address to); - event PairUpdated(bytes32 indexed id, IERC20 indexed token1, IERC20 indexed token2, Oracle oracle, uint256 oracleTTL); + event SwapExact( + IERC20 indexed input, + IERC20 indexed output, + uint256 inputAmount, + uint256 outputAmount, + address from, + address to + ); + event PairUpdated( + bytes32 indexed id, + IERC20 indexed token1, + IERC20 indexed token2, + Oracle oracle, + uint256 oracleTTL + ); event PairRemoved(bytes32 indexed id); event FeeUpdated(uint256 newFeeBasisPoints); error OutputAmountTooLow(uint256 outputAmount, uint256 minOutputAmount); @@ -65,153 +77,207 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall error InvalidFee(uint256 feeBasisPoints); /// @custom:oz-upgrades-unsafe-allow constructor - constructor(IAuthority _authority, address _trustedForwarder) - PermissionManaged(_authority) - ERC2771Context(_trustedForwarder) - {} + constructor( + IAuthority _authority, + address _trustedForwarder + ) PermissionManaged(_authority) ERC2771Context(_trustedForwarder) {} /**************************************************************************************************************** * Getters * ****************************************************************************************************************/ - function viewPairDetails(IERC20 input, IERC20 output) public view virtual returns ( - bytes32 id, - IERC20 token1, - IERC20 token2, - Oracle oracle, - uint256 oracleTTL, - uint256 numerator, - uint256 denominator - ) { + function viewPairDetails( + IERC20 input, + IERC20 output + ) + public + view + virtual + returns ( + bytes32 id, + IERC20 token1, + IERC20 token2, + Oracle oracle, + uint256 oracleTTL, + uint256 numerator, + uint256 denominator + ) + { id = hashPair(input, output); Pair storage pair = _pairs[id]; - return ( - id, - pair.token1, - pair.token2, - pair.oracle, - pair.oracleTTL, - pair.numerator, - pair.denominator - ); + return (id, pair.token1, pair.token2, pair.oracle, pair.oracleTTL, pair.numerator, pair.denominator); } function hashPair(IERC20 input, IERC20 output) public view virtual returns (bytes32) { - return Hashes.commutativeKeccak256( - bytes32(uint256(uint160(address(input)))), - bytes32(uint256(uint160(address(output)))) - ); + return + Hashes.commutativeKeccak256( + bytes32(uint256(uint160(address(input)))), + bytes32(uint256(uint160(address(output)))) + ); } /**************************************************************************************************************** * Core - preview swaps * ****************************************************************************************************************/ - function previewExactInput(IERC20[] memory path, uint256 inputAmount) public view virtual returns (uint256 /*outputAmount*/) { + function previewExactInput( + IERC20[] memory path, + uint256 inputAmount + ) public view virtual returns (uint256 /*outputAmount*/) { uint256 outputAmount = inputAmount; for (uint256 i = 0; i < path.length - 1; ++i) { - outputAmount = _exactInput(path[i], path[i+1], outputAmount); + outputAmount = _exactInput(path[i], path[i + 1], outputAmount); } return outputAmount.mulDiv(_BASIS_POINT_SCALE - feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Floor); } - function previewExactOutput(IERC20[] memory path, uint256 outputAmount) public view virtual returns (uint256 /*inputAmount*/) { + function previewExactOutput( + IERC20[] memory path, + uint256 outputAmount + ) public view virtual returns (uint256 /*inputAmount*/) { uint256 inputAmount = outputAmount; for (uint256 i = path.length - 1; i > 0; --i) { - inputAmount = _exactOutput(path[i-1], path[i], inputAmount); + inputAmount = _exactOutput(path[i - 1], path[i], inputAmount); } return inputAmount.mulDiv(_BASIS_POINT_SCALE, _BASIS_POINT_SCALE - feeBasisPoints, Math.Rounding.Ceil); } - function previewExactInputSingle(IERC20 input, IERC20 output, uint256 inputAmount) public view virtual returns (uint256 /*outputAmount*/) { - return _exactInput(input, output, inputAmount).mulDiv(_BASIS_POINT_SCALE - feeBasisPoints, _BASIS_POINT_SCALE, Math.Rounding.Floor); + function previewExactInputSingle( + IERC20 input, + IERC20 output, + uint256 inputAmount + ) public view virtual returns (uint256 /*outputAmount*/) { + return + _exactInput(input, output, inputAmount).mulDiv( + _BASIS_POINT_SCALE - feeBasisPoints, + _BASIS_POINT_SCALE, + Math.Rounding.Floor + ); } - function previewExactOutputSingle(IERC20 input, IERC20 output, uint256 outputAmount) public view virtual returns (uint256 /*inputAmount*/) { - return _exactOutput(input, output, outputAmount).mulDiv(_BASIS_POINT_SCALE, _BASIS_POINT_SCALE - feeBasisPoints, Math.Rounding.Ceil); + function previewExactOutputSingle( + IERC20 input, + IERC20 output, + uint256 outputAmount + ) public view virtual returns (uint256 /*inputAmount*/) { + return + _exactOutput(input, output, outputAmount).mulDiv( + _BASIS_POINT_SCALE, + _BASIS_POINT_SCALE - feeBasisPoints, + Math.Rounding.Ceil + ); } - function _exactInput(IERC20 input, IERC20 output, uint256 inputAmount) internal view virtual returns (uint256 /*outputAmount*/) { - ( - , - IERC20 token1, - , - Oracle oracle, - uint256 oracleTTL, - uint256 numerator, - uint256 denominator - ) = viewPairDetails(input, output); + function _exactInput( + IERC20 input, + IERC20 output, + uint256 inputAmount + ) internal view virtual returns (uint256 /*outputAmount*/) { + (, IERC20 token1, , Oracle oracle, uint256 oracleTTL, uint256 numerator, uint256 denominator) = viewPairDetails( + input, + output + ); require(address(oracle) != address(0), UnknownPair(input, output)); (int256 minPrice, int256 maxPrice) = _getPrices(oracle, oracleTTL); - return inputAmount.mulDiv( - Math.ternary(input == token1, numerator * minPrice.toUint256(), denominator), - Math.ternary(input == token1, denominator, numerator * maxPrice.toUint256()), - Math.Rounding.Floor - ); + return + inputAmount.mulDiv( + Math.ternary(input == token1, numerator * minPrice.toUint256(), denominator), + Math.ternary(input == token1, denominator, numerator * maxPrice.toUint256()), + Math.Rounding.Floor + ); } - function _exactOutput(IERC20 input, IERC20 output, uint256 outputAmount) internal view virtual returns (uint256 /*inputAmount*/) { - ( - , - IERC20 token1, - , - Oracle oracle, - uint256 oracleTTL, - uint256 numerator, - uint256 denominator - ) = viewPairDetails(input, output); + function _exactOutput( + IERC20 input, + IERC20 output, + uint256 outputAmount + ) internal view virtual returns (uint256 /*inputAmount*/) { + (, IERC20 token1, , Oracle oracle, uint256 oracleTTL, uint256 numerator, uint256 denominator) = viewPairDetails( + input, + output + ); require(address(oracle) != address(0), UnknownPair(input, output)); (int256 minPrice, int256 maxPrice) = _getPrices(oracle, oracleTTL); - return outputAmount.mulDiv( - Math.ternary(input == token1, denominator, numerator * maxPrice.toUint256()), - Math.ternary(input == token1, numerator * minPrice.toUint256(), denominator), - Math.Rounding.Ceil - ); + return + outputAmount.mulDiv( + Math.ternary(input == token1, denominator, numerator * maxPrice.toUint256()), + Math.ternary(input == token1, numerator * minPrice.toUint256(), denominator), + Math.Rounding.Ceil + ); } /**************************************************************************************************************** * Core - execute swaps * ****************************************************************************************************************/ - function swapExactInput(IERC20[] memory path, uint256 inputAmount, address recipient, uint256 minOutputAmount) public virtual restricted() returns (uint256 /*outputAmount*/) { + function swapExactInput( + IERC20[] memory path, + uint256 inputAmount, + address recipient, + uint256 minOutputAmount + ) public virtual restricted returns (uint256 /*outputAmount*/) { uint256 outputAmount = previewExactInput(path, inputAmount); require(outputAmount >= minOutputAmount, OutputAmountTooLow(outputAmount, minOutputAmount)); _swapExact(path[0], path[path.length - 1], inputAmount, outputAmount, _msgSender(), recipient); return outputAmount; } - function swapExactInputSingle(IERC20 input, IERC20 output, uint256 inputAmount, address recipient, uint256 minOutputAmount) public virtual restricted() returns (uint256 /*outputAmount*/) { + function swapExactInputSingle( + IERC20 input, + IERC20 output, + uint256 inputAmount, + address recipient, + uint256 minOutputAmount + ) public virtual restricted returns (uint256 /*outputAmount*/) { uint256 outputAmount = previewExactInputSingle(input, output, inputAmount); require(outputAmount >= minOutputAmount, OutputAmountTooLow(outputAmount, minOutputAmount)); _swapExact(input, output, inputAmount, outputAmount, _msgSender(), recipient); return outputAmount; } - function swapExactOutput(IERC20[] memory path, uint256 outputAmount, address recipient, uint256 maxInputAmount) public virtual restricted() returns (uint256 /*inputAmount*/) { + function swapExactOutput( + IERC20[] memory path, + uint256 outputAmount, + address recipient, + uint256 maxInputAmount + ) public virtual restricted returns (uint256 /*inputAmount*/) { uint256 inputAmount = previewExactOutput(path, outputAmount); require(inputAmount <= maxInputAmount, InputAmountTooHigh(inputAmount, maxInputAmount)); _swapExact(path[0], path[path.length - 1], inputAmount, outputAmount, _msgSender(), recipient); return inputAmount; } - function swapExactOutputSingle(IERC20 input, IERC20 output, uint256 outputAmount, address recipient, uint256 maxInputAmount) public virtual restricted() returns (uint256 /*inputAmount*/) { + function swapExactOutputSingle( + IERC20 input, + IERC20 output, + uint256 outputAmount, + address recipient, + uint256 maxInputAmount + ) public virtual restricted returns (uint256 /*inputAmount*/) { uint256 inputAmount = previewExactOutputSingle(input, output, outputAmount); require(inputAmount <= maxInputAmount, InputAmountTooHigh(inputAmount, maxInputAmount)); _swapExact(input, output, inputAmount, outputAmount, _msgSender(), recipient); return inputAmount; } - function _swapExact(IERC20 input, IERC20 output, uint256 inputAmount, uint256 outputAmount, address from, address to) private { + function _swapExact( + IERC20 input, + IERC20 output, + uint256 inputAmount, + uint256 outputAmount, + address from, + address to + ) private { SafeERC20.safeTransferFrom(input, from, address(this), inputAmount); SafeERC20.safeTransfer(output, to, outputAmount); emit SwapExact(input, output, inputAmount, outputAmount, from, to); } function _getPrices(Oracle oracle, uint256 oracleTTL) internal view virtual returns (int256 min, int256 max) { - (uint80 roundId, int256 latest,,,) = oracle.latestRoundData(); - (, int256 previous,,uint256 updatedAt,) = oracle.getRoundData(roundId - 1); + (uint80 roundId, int256 latest, , , ) = oracle.latestRoundData(); + (, int256 previous, , uint256 updatedAt, ) = oracle.getRoundData(roundId - 1); require(block.timestamp < updatedAt + oracleTTL, OracleValueTooOld(oracle)); min = SignedMath.min(latest, previous); max = SignedMath.max(latest, previous); @@ -220,7 +286,12 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall /**************************************************************************************************************** * Admin actions * ****************************************************************************************************************/ - function setPair(IERC20Metadata token1, IERC20Metadata token2, Oracle oracle, uint256 oracleTTL) public virtual restricted() { + function setPair( + IERC20Metadata token1, + IERC20Metadata token2, + Oracle oracle, + uint256 oracleTTL + ) public virtual restricted { bytes32 id = hashPair(token1, token2); _pairs[id] = Pair({ token1: token1, @@ -234,27 +305,21 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall emit PairUpdated(id, token1, token2, oracle, oracleTTL); } - function removePair(IERC20 token1, IERC20 token2) public virtual restricted() { + function removePair(IERC20 token1, IERC20 token2) public virtual restricted { bytes32 id = hashPair(token1, token2); delete _pairs[id]; emit PairRemoved(id); } - function setFee(uint256 newFeeBasisPoints) public virtual restricted() { + function setFee(uint256 newFeeBasisPoints) public virtual restricted { require(newFeeBasisPoints <= 50, InvalidFee(newFeeBasisPoints)); // Max 0.5% feeBasisPoints = newFeeBasisPoints; emit FeeUpdated(newFeeBasisPoints); } - function withdraw(IERC20 _token, address _to, uint256 _amount) public virtual restricted() { - SafeERC20.safeTransfer( - _token, - _to, - _amount == type(uint256).max - ? _token.balanceOf(address(this)) - : _amount - ); + function withdraw(IERC20 _token, address _to, uint256 _amount) public virtual restricted { + SafeERC20.safeTransfer(_token, _to, _amount == type(uint256).max ? _token.balanceOf(address(this)) : _amount); } /**************************************************************************************************************** @@ -271,4 +336,4 @@ contract MultiATM is ERC2771Context, PermissionManaged, Multicall function _contextSuffixLength() internal view override(Context, ERC2771Context) returns (uint256) { return super._contextSuffixLength(); } -} \ No newline at end of file +} diff --git a/contracts/token/Redemption.sol b/contracts/token/Redemption.sol index d40ece9..a51fa98 100644 --- a/contracts/token/Redemption.sol +++ b/contracts/token/Redemption.sol @@ -2,26 +2,20 @@ pragma solidity ^0.8.20; -import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; -import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import { IERC1363Receiver } from "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol"; -import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; -import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { IERC1363Receiver } from "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol"; +import { Initializable } from "@openzeppelin/contracts/proxy/utils/Initializable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts/proxy/utils/UUPSUpgradeable.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { EnumerableSet } from "@openzeppelin/contracts/utils/structs/EnumerableSet.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; import { PermissionManaged } from "../permissions/PermissionManaged.sol"; -import { Token } from "./Token.sol"; +import { Token } from "./Token.sol"; /// @custom:security-contact security@spiko.tech -contract Redemption is - IERC1363Receiver, - Initializable, - PermissionManaged, - UUPSUpgradeable, - Multicall -{ +contract Redemption is IERC1363Receiver, Initializable, PermissionManaged, UUPSUpgradeable, Multicall { using EnumerableSet for EnumerableSet.AddressSet; using SafeCast for *; using SafeERC20 for Token; @@ -38,12 +32,19 @@ contract Redemption is uint48 deadline; } - uint48 public constant MAX_DELAY = 14 days; - mapping(bytes32 => Details ) public details; - mapping(IERC20 => uint256 ) public minimum; - mapping(IERC20 => EnumerableSet.AddressSet) private _outputs; + uint48 public constant MAX_DELAY = 14 days; + mapping(bytes32 => Details) public details; + mapping(IERC20 => uint256) public minimum; + mapping(IERC20 => EnumerableSet.AddressSet) private _outputs; - event RedemptionInitiated(bytes32 indexed id, address indexed user, IERC20 indexed input, address output, uint256 inputValue, bytes32 salt); + event RedemptionInitiated( + bytes32 indexed id, + address indexed user, + IERC20 indexed input, + address output, + uint256 inputValue, + bytes32 salt + ); event RedemptionExecuted(bytes32 indexed id, bytes data); event RedemptionCanceled(bytes32 indexed id); event EnableOutput(IERC20 indexed input, address output, bool enable); @@ -67,7 +68,13 @@ contract Redemption is /** * @dev HELPER: produce redemption request hash from the input parameters */ - function hashRedemptionId(address user, IERC20 input, address output, uint256 inputValue, bytes32 salt) public pure returns (bytes32) { + function hashRedemptionId( + address user, + IERC20 input, + address output, + uint256 inputValue, + bytes32 salt + ) public pure returns (bytes32) { return keccak256(abi.encodePacked(user, input, output, inputValue, salt)); } @@ -113,12 +120,12 @@ contract Redemption is */ function executeRedemption( address user, - Token input, + Token input, address output, uint256 inputValue, bytes32 salt, bytes calldata data - ) external restricted() { + ) external restricted { // Hash operation bytes32 id = hashRedemptionId(user, input, output, inputValue, salt); @@ -140,13 +147,7 @@ contract Redemption is * @dev Cancel a redemption if the execution delay has passed. Can be performed by anyone. Input tokens are * refunded to the user and the operation is marked as `CANCELED`. */ - function cancelRedemption( - address user, - Token input, - address output, - uint256 inputValue, - bytes32 salt - ) external { + function cancelRedemption(address user, Token input, address output, uint256 inputValue, bytes32 salt) external { // Hash operation bytes32 id = hashRedemptionId(user, input, output, inputValue, salt); @@ -170,7 +171,7 @@ contract Redemption is /** * @dev ADMIN: configure which output (stablecoin) is used for redemptions of a given input. */ - function registerOutput(IERC20 input, address output, bool enable) external restricted() { + function registerOutput(IERC20 input, address output, bool enable) external restricted { if (enable) { _outputs[input].add(output); } else { @@ -180,7 +181,7 @@ contract Redemption is emit EnableOutput(input, output, enable); } - function setMinimum(IERC20 input, uint256 amount) external restricted() { + function setMinimum(IERC20 input, uint256 amount) external restricted { minimum[input] = amount; emit MinimumUpdated(input, amount); @@ -192,4 +193,4 @@ contract Redemption is function _authorizeUpgrade(address) internal view override { _checkRestricted(UUPSUpgradeable.upgradeToAndCall.selector); } -} \ No newline at end of file +} diff --git a/contracts/token/Token.sol b/contracts/token/Token.sol index 13a627f..3a07dd8 100644 --- a/contracts/token/Token.sol +++ b/contracts/token/Token.sol @@ -2,18 +2,18 @@ pragma solidity ^0.8.20; -import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; -import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; +import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { OwnableUpgradeable } from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol"; import { ERC2771ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/metatx/ERC2771ContextUpgradeable.sol"; -import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; -import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; -import { ERC20PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol"; -import { ERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; -import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; -import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; -import { PermissionManaged } from "../permissions/PermissionManaged.sol"; -import { ERC1363Upgradeable } from "./extensions/ERC1363Upgradeable.sol"; +import { UUPSUpgradeable } from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol"; +import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; +import { ERC20PausableUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PausableUpgradeable.sol"; +import { ERC20PermitUpgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/ERC20PermitUpgradeable.sol"; +import { ContextUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/ContextUpgradeable.sol"; +import { MulticallUpgradeable } from "@openzeppelin/contracts-upgradeable/utils/MulticallUpgradeable.sol"; +import { PermissionManaged } from "../permissions/PermissionManaged.sol"; +import { ERC1363Upgradeable } from "./extensions/ERC1363Upgradeable.sol"; /// @custom:security-contact security@spiko.tech // Note that {ERC2771ContextUpgradeable} overrides the behavior of {Ownable}, {ERC20} & {ERC1363} but does not affect @@ -35,14 +35,14 @@ contract Token is uint8 private m_decimals; /// @custom:oz-upgrades-unsafe-allow constructor - constructor(IAuthority _authority, address _trustedForwarder) - PermissionManaged(_authority) - ERC2771ContextUpgradeable(_trustedForwarder) - { + constructor( + IAuthority _authority, + address _trustedForwarder + ) PermissionManaged(_authority) ERC2771ContextUpgradeable(_trustedForwarder) { _disableInitializers(); } - function initialize(string calldata _name, string calldata _symbol, uint8 _decimals) public initializer() { + function initialize(string calldata _name, string calldata _symbol, uint8 _decimals) public initializer { // __Ownable_init(); do not initialize ownership. By default owner is 0 until an admin sets it. __ERC20_init(_name, _symbol); __ERC20Permit_init(_name); @@ -56,33 +56,39 @@ contract Token is /**************************************************************************************************************** * Admin operations * ****************************************************************************************************************/ - function mint(address to, uint256 amount) public restricted() { + function mint(address to, uint256 amount) public restricted { _mint(to, amount); } - function burn(address from, uint256 amount) public restricted() { + function burn(address from, uint256 amount) public restricted { _burn(from, amount); } - function pause() public restricted() { + function pause() public restricted { _pause(); } - function unpause() public restricted() { + function unpause() public restricted { _unpause(); } // Admin has the ability to force reset ownership - function setOwnership(address newOwner) public restricted() { + function setOwnership(address newOwner) public restricted { _transferOwnership(newOwner); } /**************************************************************************************************************** * Token transfer whitelist * ****************************************************************************************************************/ - function _update(address from, address to, uint256 amount) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) { + function _update( + address from, + address to, + uint256 amount + ) internal override(ERC20Upgradeable, ERC20PausableUpgradeable) { // sender must be whitelisted, unless its a mint (sender is 0) or its a burn (admin can burn from non-whitelisted account) - if (from != address(0) && to != address(0) && !authority.canCall(from, address(this), IERC20.transfer.selector)) { + if ( + from != address(0) && to != address(0) && !authority.canCall(from, address(this), IERC20.transfer.selector) + ) { revert UnauthorizedFrom(address(this), from); } @@ -112,7 +118,12 @@ contract Token is return super._msgData(); } - function _contextSuffixLength() internal view override(ContextUpgradeable, ERC2771ContextUpgradeable) returns (uint256) { + function _contextSuffixLength() + internal + view + override(ContextUpgradeable, ERC2771ContextUpgradeable) + returns (uint256) + { return super._contextSuffixLength(); } } diff --git a/contracts/token/extensions/ERC1363Upgradeable.sol b/contracts/token/extensions/ERC1363Upgradeable.sol index 2986d4d..eb35907 100644 --- a/contracts/token/extensions/ERC1363Upgradeable.sol +++ b/contracts/token/extensions/ERC1363Upgradeable.sol @@ -2,11 +2,11 @@ pragma solidity ^0.8.20; -import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; -import { IERC1363 } from "@openzeppelin/contracts/interfaces/IERC1363.sol"; +import { IERC165 } from "@openzeppelin/contracts/interfaces/IERC165.sol"; +import { IERC1363 } from "@openzeppelin/contracts/interfaces/IERC1363.sol"; import { IERC1363Receiver } from "@openzeppelin/contracts/interfaces/IERC1363Receiver.sol"; -import { IERC1363Spender } from "@openzeppelin/contracts/interfaces/IERC1363Spender.sol"; -import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; +import { IERC1363Spender } from "@openzeppelin/contracts/interfaces/IERC1363Spender.sol"; +import { ERC165 } from "@openzeppelin/contracts/utils/introspection/ERC165.sol"; import { ERC20Upgradeable } from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol"; /// @custom:security-contact security@spiko.tech @@ -22,7 +22,10 @@ abstract contract ERC1363Upgradeable is IERC1363, ERC20Upgradeable, ERC165 { function transferAndCall(address to, uint256 value, bytes memory data) public override returns (bool) { require(transfer(to, value)); try IERC1363Receiver(to).onTransferReceived(_msgSender(), _msgSender(), value, data) returns (bytes4 selector) { - require(selector == IERC1363Receiver(to).onTransferReceived.selector, "ERC1363: onTransferReceived invalid result"); + require( + selector == IERC1363Receiver(to).onTransferReceived.selector, + "ERC1363: onTransferReceived invalid result" + ); } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC1363: onTransferReceived reverted without reason"); @@ -40,10 +43,18 @@ abstract contract ERC1363Upgradeable is IERC1363, ERC20Upgradeable, ERC165 { return transferFromAndCall(from, to, value, bytes("")); } - function transferFromAndCall(address from, address to, uint256 value, bytes memory data) public override returns (bool) { + function transferFromAndCall( + address from, + address to, + uint256 value, + bytes memory data + ) public override returns (bool) { require(transferFrom(from, to, value)); try IERC1363Receiver(to).onTransferReceived(_msgSender(), from, value, data) returns (bytes4 selector) { - require(selector == IERC1363Receiver(to).onTransferReceived.selector, "ERC1363: onTransferReceived invalid result"); + require( + selector == IERC1363Receiver(to).onTransferReceived.selector, + "ERC1363: onTransferReceived invalid result" + ); } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC1363: onTransferReceived reverted without reason"); @@ -64,7 +75,10 @@ abstract contract ERC1363Upgradeable is IERC1363, ERC20Upgradeable, ERC165 { function approveAndCall(address spender, uint256 value, bytes memory data) public override returns (bool) { require(approve(spender, value)); try IERC1363Spender(spender).onApprovalReceived(_msgSender(), value, data) returns (bytes4 selector) { - require(selector == IERC1363Spender(spender).onApprovalReceived.selector, "ERC1363: onApprovalReceived invalid result"); + require( + selector == IERC1363Spender(spender).onApprovalReceived.selector, + "ERC1363: onApprovalReceived invalid result" + ); } catch (bytes memory reason) { if (reason.length == 0) { revert("ERC1363: onApprovalReceived reverted without reason"); diff --git a/contracts/token/old/ATM.sol b/contracts/token/old/ATM.sol index e40e4c4..c36f93d 100644 --- a/contracts/token/old/ATM.sol +++ b/contracts/token/old/ATM.sol @@ -2,16 +2,16 @@ pragma solidity ^0.8.20; -import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; -import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; -import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; -import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; -import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { Context } from "@openzeppelin/contracts/utils/Context.sol"; -import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; -import { Oracle } from "../../oracle/Oracle.sol"; +import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { IERC20Metadata } from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol"; +import { ERC2771Context } from "@openzeppelin/contracts/metatx/ERC2771Context.sol"; +import { SafeERC20 } from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Context } from "@openzeppelin/contracts/utils/Context.sol"; +import { Multicall } from "@openzeppelin/contracts/utils/Multicall.sol"; +import { Oracle } from "../../oracle/Oracle.sol"; import { PermissionManaged } from "../../permissions/PermissionManaged.sol"; function tryFetchDecimals(IERC20 token) view returns (uint8) { @@ -23,34 +23,38 @@ function tryFetchDecimals(IERC20 token) view returns (uint8) { } /// @custom:security-contact security@spiko.tech -contract ATM is ERC2771Context, PermissionManaged, Multicall -{ - using Math for *; +contract ATM is ERC2771Context, PermissionManaged, Multicall { + using Math for *; using SafeCast for *; - IERC20 immutable public token; - IERC20 immutable public stable; - Oracle immutable public oracle; - uint256 immutable private numerator; - uint256 immutable private denominator; + IERC20 public immutable token; + IERC20 public immutable stable; + Oracle public immutable oracle; + uint256 private immutable numerator; + uint256 private immutable denominator; /// @custom:oz-upgrades-unsafe-allow constructor - constructor(Oracle _oracle, IERC20 _stable, IAuthority _authority, address _trustedForwarder) - PermissionManaged(_authority) - ERC2771Context(_trustedForwarder) - { - token = _oracle.token(); - stable = _stable; - oracle = _oracle; + constructor( + Oracle _oracle, + IERC20 _stable, + IAuthority _authority, + address _trustedForwarder + ) PermissionManaged(_authority) ERC2771Context(_trustedForwarder) { + token = _oracle.token(); + stable = _stable; + oracle = _oracle; // rate correction uint256 x = tryFetchDecimals(token) + oracle.decimals(); uint256 y = tryFetchDecimals(stable); - numerator = x < y ? 10 ** (y - x) : 1; + numerator = x < y ? 10 ** (y - x) : 1; denominator = x > y ? 10 ** (x - y) : 1; } - function previewBuy(IERC20 input, uint256 inputAmount) public view virtual returns (uint256 tokenAmount, uint256 stableAmount) { + function previewBuy( + IERC20 input, + uint256 inputAmount + ) public view virtual returns (uint256 tokenAmount, uint256 stableAmount) { int256 price = oracle.getHistoricalPrice(block.timestamp.toUint48()); if (input == token) { return (inputAmount, _convertToStable(inputAmount, price, Math.Rounding.Ceil)); @@ -61,7 +65,10 @@ contract ATM is ERC2771Context, PermissionManaged, Multicall } } - function previewSell(IERC20 input, uint256 inputAmount) public view virtual returns (uint256 tokenAmount, uint256 stableAmount) { + function previewSell( + IERC20 input, + uint256 inputAmount + ) public view virtual returns (uint256 tokenAmount, uint256 stableAmount) { int256 price = oracle.getHistoricalPrice(block.timestamp.toUint48()); if (input == token) { return (inputAmount, _convertToStable(inputAmount, price, Math.Rounding.Floor)); @@ -86,25 +93,27 @@ contract ATM is ERC2771Context, PermissionManaged, Multicall return (tokenAmount, stableAmount); } - function _convertToStable(uint256 tokenAmount, int256 price, Math.Rounding rounding) internal view virtual returns (uint256) { + function _convertToStable( + uint256 tokenAmount, + int256 price, + Math.Rounding rounding + ) internal view virtual returns (uint256) { return tokenAmount.mulDiv(numerator * price.toUint256(), denominator, rounding); } - function _convertToToken(uint256 stableAmount, int256 price, Math.Rounding rounding) internal view virtual returns (uint256) { + function _convertToToken( + uint256 stableAmount, + int256 price, + Math.Rounding rounding + ) internal view virtual returns (uint256) { return stableAmount.mulDiv(denominator, numerator * price.toUint256(), rounding); } /**************************************************************************************************************** * Admin drain * ****************************************************************************************************************/ - function drain(IERC20 _token, address _to, uint256 _amount) public virtual restricted() { - SafeERC20.safeTransfer( - _token, - _to, - _amount == type(uint256).max - ? _token.balanceOf(address(this)) - : _amount - ); + function drain(IERC20 _token, address _to, uint256 _amount) public virtual restricted { + SafeERC20.safeTransfer(_token, _to, _amount == type(uint256).max ? _token.balanceOf(address(this)) : _amount); } /**************************************************************************************************************** diff --git a/contracts/token/old/ATM2.sol b/contracts/token/old/ATM2.sol index 716f500..5dd4604 100644 --- a/contracts/token/old/ATM2.sol +++ b/contracts/token/old/ATM2.sol @@ -3,29 +3,35 @@ pragma solidity ^0.8.20; import { IAuthority } from "@openzeppelin/contracts/access/manager/IAuthority.sol"; -import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; -import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; +import { IERC20 } from "@openzeppelin/contracts/interfaces/IERC20.sol"; +import { Math } from "@openzeppelin/contracts/utils/math/Math.sol"; import { SignedMath } from "@openzeppelin/contracts/utils/math/SignedMath.sol"; -import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; -import { Oracle } from "../../oracle/Oracle.sol"; -import { ATM } from "./ATM.sol"; +import { SafeCast } from "@openzeppelin/contracts/utils/math/SafeCast.sol"; +import { Oracle } from "../../oracle/Oracle.sol"; +import { ATM } from "./ATM.sol"; /// @custom:security-contact security@spiko.tech -contract ATM2 is ATM -{ - using Math for *; +contract ATM2 is ATM { + using Math for *; using SafeCast for *; - uint256 immutable public oraclettl; + uint256 public immutable oraclettl; - constructor(Oracle _oracle, IERC20 _stable, IAuthority _authority, address _trustedForwarder, uint256 _oraclettl) - ATM(_oracle, _stable, _authority, _trustedForwarder) - { + constructor( + Oracle _oracle, + IERC20 _stable, + IAuthority _authority, + address _trustedForwarder, + uint256 _oraclettl + ) ATM(_oracle, _stable, _authority, _trustedForwarder) { oraclettl = _oraclettl; } - function previewBuy(IERC20 input, uint256 inputAmount) public view virtual override returns (uint256 tokenAmount, uint256 stableAmount) { - (,int256 price) = _getPrices(); // max + function previewBuy( + IERC20 input, + uint256 inputAmount + ) public view virtual override returns (uint256 tokenAmount, uint256 stableAmount) { + (, int256 price) = _getPrices(); // max if (input == token) { return (inputAmount, _convertToStable(inputAmount, price, Math.Rounding.Ceil)); } else if (input == stable) { @@ -35,8 +41,11 @@ contract ATM2 is ATM } } - function previewSell(IERC20 input, uint256 inputAmount) public view virtual override returns (uint256 tokenAmount, uint256 stableAmount) { - (int256 price,) = _getPrices(); // min + function previewSell( + IERC20 input, + uint256 inputAmount + ) public view virtual override returns (uint256 tokenAmount, uint256 stableAmount) { + (int256 price, ) = _getPrices(); // min if (input == token) { return (inputAmount, _convertToStable(inputAmount, price, Math.Rounding.Floor)); } else if (input == stable) { @@ -47,8 +56,8 @@ contract ATM2 is ATM } function _getPrices() internal view virtual returns (int256 min, int256 max) { - (uint80 roundId, int256 latest,,,) = oracle.latestRoundData(); - (, int256 previous,,uint256 updatedAt,) = oracle.getRoundData(roundId - 1); + (uint80 roundId, int256 latest, , , ) = oracle.latestRoundData(); + (, int256 previous, , uint256 updatedAt, ) = oracle.getRoundData(roundId - 1); require(block.timestamp < updatedAt + oraclettl, "oracle value too old"); min = SignedMath.min(latest, previous); max = SignedMath.max(latest, previous); diff --git a/package.json b/package.json index a164117..7beb4e3 100644 --- a/package.json +++ b/package.json @@ -27,8 +27,8 @@ "@chainlink/contracts": "^0.8.0", "@openzeppelin/contracts": "^5.4.0", "@openzeppelin/contracts-upgradeable": "^5.4.0", - "chai": "4.2.0", - "hardhat-chai-matchers@2.0.6": "link:nomicfoundation/hardhat-chai-matchers@2.0.6" + "@spiko/erc-cross-chain-event-archive": "github:spiko-tech/erc-cross-chain-event-archive#v0.1.0", + "chai": "4.2.0" }, "devDependencies": { "@amxx/hre": "^0.2.1", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 4ed64a7..5739dc9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -17,12 +17,12 @@ importers: '@openzeppelin/contracts-upgradeable': specifier: ^5.4.0 version: 5.4.0(@openzeppelin/contracts@5.4.0) + '@spiko/erc-cross-chain-event-archive': + specifier: github:spiko-tech/erc-cross-chain-event-archive#v0.1.0 + version: https://codeload.github.com/spiko-tech/erc-cross-chain-event-archive/tar.gz/9a703bccc7eff9ddcc2c8d3f298b67ab681a950b(@openzeppelin/contracts@5.4.0) chai: specifier: 4.2.0 version: 4.2.0 - hardhat-chai-matchers@2.0.6: - specifier: link:nomicfoundation/hardhat-chai-matchers@2.0.6 - version: link:nomicfoundation/hardhat-chai-matchers@2.0.6 devDependencies: '@amxx/hre': specifier: ^0.2.1 @@ -314,28 +314,24 @@ packages: engines: {node: '>= 18'} cpu: [arm64] os: [linux] - libc: [glibc] '@nomicfoundation/edr-linux-arm64-musl@0.3.5': resolution: {integrity: sha512-cVFRQjyABBlsbDj+XTczYBfrCHprZ6YNzN8gGGSqAh+UGIJkAIRomK6ar27GyJLNx3HkgbuDoi/9kA0zOo/95w==} engines: {node: '>= 18'} cpu: [arm64] os: [linux] - libc: [musl] '@nomicfoundation/edr-linux-x64-gnu@0.3.5': resolution: {integrity: sha512-CjOg85DfR1Vt0fQWn5U0qi26DATK9tVzo3YOZEyI0JBsnqvk43fUTPv3uUAWBrPIRg5O5kOc9xG13hSpCBBxBg==} engines: {node: '>= 18'} cpu: [x64] os: [linux] - libc: [glibc] '@nomicfoundation/edr-linux-x64-musl@0.3.5': resolution: {integrity: sha512-hvX8bBGpBydAVevzK8jsu2FlqVZK1RrCyTX6wGHnltgMuBaoGLHYtNHiFpteOaJw2byYMiORc2bvj+98LhJ0Ew==} engines: {node: '>= 18'} cpu: [x64] os: [linux] - libc: [musl] '@nomicfoundation/edr-win32-x64-msvc@0.3.5': resolution: {integrity: sha512-IJXjW13DY5UPsx/eG5DGfXtJ7Ydwrvw/BTZ2Y93lRLHzszVpSmeVmlxjZP5IW2afTSgMLaAAsqNw4NhppRGN8A==} @@ -444,28 +440,24 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [glibc] '@nomicfoundation/solidity-analyzer-linux-arm64-musl@0.1.1': resolution: {integrity: sha512-WJ3CE5Oek25OGE3WwzK7oaopY8xMw9Lhb0mlYuJl/maZVo+WtP36XoQTb7bW/i8aAdHW5Z+BqrHMux23pvxG3w==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] - libc: [musl] '@nomicfoundation/solidity-analyzer-linux-x64-gnu@0.1.1': resolution: {integrity: sha512-5WN7leSr5fkUBBjE4f3wKENUy9HQStu7HmWqbtknfXkkil+eNWiBV275IOlpXku7v3uLsXTOKpnnGHJYI2qsdA==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [glibc] '@nomicfoundation/solidity-analyzer-linux-x64-musl@0.1.1': resolution: {integrity: sha512-KdYMkJOq0SYPQMmErv/63CwGwMm5XHenEna9X9aB8mQmhDBrYrlAOSsIPgFCUSL0hjxE3xHP65/EPXR/InD2+w==} engines: {node: '>= 10'} cpu: [x64] os: [linux] - libc: [musl] '@nomicfoundation/solidity-analyzer-win32-arm64-msvc@0.1.1': resolution: {integrity: sha512-VFZASBfl4qiBYwW5xeY20exWhmv6ww9sWu/krWSesv3q5hA0o1JuzmPHR4LPN6SUZj5vcqci0O6JOL8BPw+APg==} @@ -527,9 +519,11 @@ packages: '@openzeppelin/defender-admin-client@1.54.1': resolution: {integrity: sha512-kRpSUdTsnSqntp4FOXIm95t+6VKHc8CUY2Si71VDuxs0q7HSPZkdpRPSntcolwEzWy9L4a8NS/QMwDF5NJ4X1g==} + deprecated: This package has been deprecated and will no longer be maintained, please use @openzeppelin/defender-sdk package instead. '@openzeppelin/defender-base-client@1.54.1': resolution: {integrity: sha512-DRGz/7KN3ZQwu28YWMOaojrC7jjPkz/uCwkC8/C8B11qwZhA5qIVvyhYHhhFOCl0J84+E3TNdvkPD2q3p2WaJw==} + deprecated: This package has been deprecated and will no longer be maintained, please use @openzeppelin/defender-sdk package instead. '@openzeppelin/defender-sdk-base-client@1.12.0': resolution: {integrity: sha512-6n5SwJWCjFciC+T7QhfXnkEmkNZGKK0efkCZDAfu/Kd8L1AICnIkWwVte71dnzCBln7wglMiZupSHL3fZqePmQ==} @@ -659,6 +653,12 @@ packages: '@solidity-parser/parser@0.20.2': resolution: {integrity: sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA==} + '@spiko/erc-cross-chain-event-archive@https://codeload.github.com/spiko-tech/erc-cross-chain-event-archive/tar.gz/9a703bccc7eff9ddcc2c8d3f298b67ab681a950b': + resolution: {tarball: https://codeload.github.com/spiko-tech/erc-cross-chain-event-archive/tar.gz/9a703bccc7eff9ddcc2c8d3f298b67ab681a950b} + version: 0.1.0 + peerDependencies: + '@openzeppelin/contracts': '>=5.0.0 <6.0.0' + '@tsconfig/node10@1.0.11': resolution: {integrity: sha512-DcRjDCujK/kCk/cUe8Xz8ZSpm8mS3mNNpta+jGCA6USEDfktlNvm1+IuZ9eTcDbNk41BHwpHHeW+N1lKCz4zOw==} @@ -1575,6 +1575,7 @@ packages: ethereum-bloom-filters@1.1.0: resolution: {integrity: sha512-J1gDRkLpuGNvWYzWslBQR9cDV4nd4kfvVTE/Wy4Kkm4yb3EYRSlyi0eB/inTsSTTVyA0+HyzHgbr95Fn/Z1fSw==} + deprecated: do not use this package use package versions above as this can miss some topics ethereum-cryptography@0.1.3: resolution: {integrity: sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==} @@ -1587,6 +1588,7 @@ packages: ethereumjs-abi@0.6.8: resolution: {integrity: sha512-Tx0r/iXI6r+lRsdvkFDlut0N08jWMnKRZ6Gkq+Nmw75lZe4e6o3EkSnkaBP5NF6+m5PTGAr9JP43N3LyeoglsA==} + deprecated: This library has been deprecated and usage is discouraged. ethereumjs-util@6.2.1: resolution: {integrity: sha512-W2Ktez4L01Vexijrm5EB6w7dg4n/TgpoYU4avuT5T3Vmnw/eCRtiBrJfQYS/DCSvDIOLn2k57GcHdeBcgVxAqw==} @@ -1808,19 +1810,24 @@ packages: glob@5.0.15: resolution: {integrity: sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.1.7: resolution: {integrity: sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.0: resolution: {integrity: sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@9.3.5: resolution: {integrity: sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q==} @@ -2005,6 +2012,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -2330,6 +2338,7 @@ packages: lodash.isequal@4.5.0: resolution: {integrity: sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==} + deprecated: This package is deprecated. Use require('node:util').isDeepStrictEqual instead. lodash.kebabcase@4.1.1: resolution: {integrity: sha512-N8XRTIMMqqDgSy4VLKPnJ/+hpGZN+PHQiJnSenYqPaVV/NCqEogTnAdZLQiGKhxX+JCs8waWq2t1XHWKOmlY8g==} @@ -2937,6 +2946,7 @@ packages: rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rimraf@3.0.2: @@ -3409,6 +3419,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -4584,6 +4595,10 @@ snapshots: '@solidity-parser/parser@0.20.2': {} + '@spiko/erc-cross-chain-event-archive@https://codeload.github.com/spiko-tech/erc-cross-chain-event-archive/tar.gz/9a703bccc7eff9ddcc2c8d3f298b67ab681a950b(@openzeppelin/contracts@5.4.0)': + dependencies: + '@openzeppelin/contracts': 5.4.0 + '@tsconfig/node10@1.0.11': {} '@tsconfig/node12@1.0.11': {} diff --git a/scripts/config-1.json b/scripts/config-1.json index fb6a04d..4d27efe 100644 --- a/scripts/config-1.json +++ b/scripts/config-1.json @@ -54,7 +54,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -100,6 +102,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-11155111.json b/scripts/config-11155111.json index 0739d1d..be79210 100644 --- a/scripts/config-11155111.json +++ b/scripts/config-11155111.json @@ -61,7 +61,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -140,6 +142,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-128123.json b/scripts/config-128123.json index 0739d1d..be79210 100644 --- a/scripts/config-128123.json +++ b/scripts/config-128123.json @@ -61,7 +61,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -140,6 +142,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-137.json b/scripts/config-137.json index da7afe4..cb6d117 100644 --- a/scripts/config-137.json +++ b/scripts/config-137.json @@ -54,7 +54,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -100,6 +102,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-42161.json b/scripts/config-42161.json index 951af1a..91f426a 100644 --- a/scripts/config-42161.json +++ b/scripts/config-42161.json @@ -54,7 +54,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -100,6 +102,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-421614.json b/scripts/config-421614.json index 0739d1d..be79210 100644 --- a/scripts/config-421614.json +++ b/scripts/config-421614.json @@ -61,7 +61,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -140,6 +142,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-42793.json b/scripts/config-42793.json index f2f2118..02cc845 100644 --- a/scripts/config-42793.json +++ b/scripts/config-42793.json @@ -54,7 +54,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -100,6 +102,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-80002.json b/scripts/config-80002.json index 0739d1d..be79210 100644 --- a/scripts/config-80002.json +++ b/scripts/config-80002.json @@ -61,7 +61,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -140,6 +142,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-8453.json b/scripts/config-8453.json index 5253a9c..16d0ab3 100644 --- a/scripts/config-8453.json +++ b/scripts/config-8453.json @@ -54,7 +54,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -100,6 +102,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-84532.json b/scripts/config-84532.json index 0739d1d..be79210 100644 --- a/scripts/config-84532.json +++ b/scripts/config-84532.json @@ -61,7 +61,9 @@ "redemption-executeRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -140,6 +142,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/config-default.json b/scripts/config-default.json index 263354f..ecf5b2d 100644 --- a/scripts/config-default.json +++ b/scripts/config-default.json @@ -31,7 +31,9 @@ "redemption-cancelRedemption": ["operator-daily"], "minter-initiateMint": ["mint-initiator"], "minter-approveMint": ["mint-approver"], - "minter-cancelMint": ["mint-approver"] + "minter-cancelMint": ["mint-approver"], + "archiver-archiveEvent": ["archiver"], + "archiver-amendEvent": ["archiver"] } }, "roles": { @@ -77,6 +79,10 @@ "mint-initiator": { "admins": ["admin"], "members": [] + }, + "archiver": { + "admins": ["admin"], + "members": [] } } } diff --git a/scripts/migrate.js b/scripts/migrate.js index 9a54320..39deebd 100644 --- a/scripts/migrate.js +++ b/scripts/migrate.js @@ -56,6 +56,7 @@ async function verifyContracts(contracts, chainName) { const permissionManagerAddress = contracts.manager.target; const redemptionAddress = contracts.redemption.target; const minterAddress = contracts.minter.target; + const archiverAddress = contracts.archiver.target; const forwarderAddress = contracts.forwarder.target; const tokenAddresses = Object.values(contracts.tokens).map((token) => token.target); // const oracleAddresses = Object.values(contracts.oracles).map(oracle => oracle.target); @@ -63,6 +64,7 @@ async function verifyContracts(contracts, chainName) { await verifyContract('PermissionManager', permissionManagerAddress, chainName); await verifyContract('Redemption', redemptionAddress, chainName, permissionManagerAddress); await verifyContract('Minter', minterAddress, chainName, permissionManagerAddress); + await verifyContract('Archiver', archiverAddress, chainName, permissionManagerAddress); for (const tokenAddress of tokenAddresses) { await verifyContract('Token', tokenAddress, chainName, permissionManagerAddress, forwarderAddress); @@ -81,6 +83,7 @@ const Role = { RedemptionExecutor: 'redemption-executor', MintInitiator: 'mint-initiator', MintApprover: 'mint-approver', + Archiver: 'archiver', }; const PermissionedContracts = { @@ -163,6 +166,15 @@ async function migrate(config = {}, opts = {}) { ); DEBUG(`minter: ${contracts.minter.target}`); + contracts.archiver = await ethers.getContractFactory('Archiver').then((factory) => + migration.migrate('archiver', factory, [], { + ...opts, + kind: 'uups', + constructorArgs: [contracts.manager.target], + }) + ); + DEBUG(`archiver: ${contracts.archiver.target}`); + for (const { name, symbol, decimals, oracle } of config?.contracts?.tokens || []) { // deploy token contracts.tokens[symbol] = await ethers.getContractFactory('Token').then((factory) => diff --git a/test/archiver.test.js b/test/archiver.test.js new file mode 100644 index 0000000..419da98 --- /dev/null +++ b/test/archiver.test.js @@ -0,0 +1,247 @@ +const { expect } = require('chai'); +const { ethers } = require('hardhat'); +const { loadFixture } = require('@nomicfoundation/hardhat-network-helpers'); +const { combine } = require('./helpers'); +const { fixture } = require('./fixtures'); + +describe('Archiver', function () { + const IFACE = { + IEventArchive: '0xcde9f047', + IEventArchiveWriter: '0xae70b7af', + IEventArchiveAmender: '0xf8d6065f', + IERC165: '0x01ffc9a7', + }; + + beforeEach(async function () { + await loadFixture(fixture).then((results) => Object.assign(this, results)); + + this.makeArchiveOp = (overrides = {}) => { + const sourceChainId = overrides.sourceChainId ?? 'eip155:1'; + return { + sourceChainId, + sourceChainIdHash: ethers.keccak256(ethers.toUtf8Bytes(sourceChainId)), + sourceTxHash: overrides.sourceTxHash ?? ethers.hexlify(ethers.randomBytes(32)), + sourceAddress: overrides.sourceAddress ?? ethers.zeroPadValue(ethers.hexlify(ethers.randomBytes(20)), 32), + sourceLogIndex: overrides.sourceLogIndex ?? 0, + sourceBlockNumber: overrides.sourceBlockNumber ?? 123456, + payload: overrides.payload ?? ethers.hexlify(ethers.randomBytes(64)), + }; + }; + + this.archive = (op, signer = this.accounts.archiver) => + this.contracts.archiver + .connect(signer) + .archiveEvent( + op.sourceChainId, + op.sourceTxHash, + op.sourceAddress, + op.sourceLogIndex, + op.sourceBlockNumber, + op.payload + ); + + this.amend = (op, payload, signer = this.accounts.archiver) => + this.contracts.archiver + .connect(signer) + .amendEvent(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex, payload); + }); + + it('functions have requirements', async function () { + expect( + await this.contracts.manager.getRequirements( + this.contracts.archiver, + this.contracts.archiver.interface.getFunction('upgradeToAndCall').selector + ) + ).to.equal(combine(this.MASKS.admin)); + expect( + await this.contracts.manager.getRequirements( + this.contracts.archiver, + this.contracts.archiver.interface.getFunction('archiveEvent').selector + ) + ).to.equal(combine(this.MASKS.admin, this.MASKS.archiver)); + expect( + await this.contracts.manager.getRequirements( + this.contracts.archiver, + this.contracts.archiver.interface.getFunction('amendEvent').selector + ) + ).to.equal(combine(this.MASKS.admin, this.MASKS.archiver)); + }); + + describe('archiveEvent', function () { + it('success - authorized archiver', async function () { + const op = this.makeArchiveOp(); + + await expect(this.contracts.archiver.isArchived(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex)).to + .eventually.be.false; + + await expect(this.archive(op)) + .to.emit(this.contracts.archiver, 'EventArchived') + .withArgs( + op.sourceChainIdHash, + op.sourceTxHash, + op.sourceAddress, + op.sourceChainId, + op.sourceLogIndex, + op.sourceBlockNumber, + op.payload + ); + + await expect(this.contracts.archiver.isArchived(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex)).to + .eventually.be.true; + // archived but never amended -> version 0 + await expect( + this.contracts.archiver.latestVersion(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex) + ).to.eventually.equal(0); + }); + + it('success - empty payload allowed', async function () { + const op = this.makeArchiveOp({ payload: '0x' }); + + await expect(this.archive(op)) + .to.emit(this.contracts.archiver, 'EventArchived') + .withArgs( + op.sourceChainIdHash, + op.sourceTxHash, + op.sourceAddress, + op.sourceChainId, + op.sourceLogIndex, + op.sourceBlockNumber, + '0x' + ); + }); + + it('success - admin can archive', async function () { + const op = this.makeArchiveOp(); + await expect(this.archive(op, this.accounts.admin)).to.emit(this.contracts.archiver, 'EventArchived'); + }); + + it('reverts - duplicate eventId', async function () { + const op = this.makeArchiveOp(); + await this.archive(op); + + // same (chainId, txHash, logIndex) even with different payload/address + const dup = this.makeArchiveOp({ + sourceChainId: op.sourceChainId, + sourceTxHash: op.sourceTxHash, + sourceLogIndex: op.sourceLogIndex, + }); + await expect(this.archive(dup)) + .to.be.revertedWithCustomError(this.contracts.archiver, 'AlreadyArchived') + .withArgs(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex); + }); + + it('reverts - unauthorized caller', async function () { + const op = this.makeArchiveOp(); + await expect(this.archive(op, this.accounts.other)) + .to.be.revertedWithCustomError(this.contracts.archiver, 'RestrictedAccess') + .withArgs( + this.accounts.other, + this.contracts.archiver, + this.contracts.archiver.interface.getFunction('archiveEvent').selector + ); + }); + }); + + describe('amendEvent', function () { + it('success - increments version and re-emits sourceAddress', async function () { + const op = this.makeArchiveOp(); + await this.archive(op); + + const payload1 = ethers.hexlify(ethers.randomBytes(32)); + await expect(this.amend(op, payload1)) + .to.emit(this.contracts.archiver, 'EventAmended') + .withArgs(op.sourceChainIdHash, op.sourceTxHash, op.sourceAddress, op.sourceLogIndex, 1, payload1); + await expect( + this.contracts.archiver.latestVersion(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex) + ).to.eventually.equal(1); + + const payload2 = ethers.hexlify(ethers.randomBytes(16)); + await expect(this.amend(op, payload2)) + .to.emit(this.contracts.archiver, 'EventAmended') + .withArgs(op.sourceChainIdHash, op.sourceTxHash, op.sourceAddress, op.sourceLogIndex, 2, payload2); + await expect( + this.contracts.archiver.latestVersion(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex) + ).to.eventually.equal(2); + }); + + it('success - empty amendment payload allowed', async function () { + const op = this.makeArchiveOp(); + await this.archive(op); + + await expect(this.amend(op, '0x')) + .to.emit(this.contracts.archiver, 'EventAmended') + .withArgs(op.sourceChainIdHash, op.sourceTxHash, op.sourceAddress, op.sourceLogIndex, 1, '0x'); + }); + + it('reverts - eventId never archived', async function () { + const op = this.makeArchiveOp(); + await expect(this.amend(op, '0x1234')) + .to.be.revertedWithCustomError(this.contracts.archiver, 'NotArchived') + .withArgs(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex); + }); + + it('reverts - unauthorized caller', async function () { + const op = this.makeArchiveOp(); + await this.archive(op); + + await expect(this.amend(op, '0x1234', this.accounts.other)) + .to.be.revertedWithCustomError(this.contracts.archiver, 'RestrictedAccess') + .withArgs( + this.accounts.other, + this.contracts.archiver, + this.contracts.archiver.interface.getFunction('amendEvent').selector + ); + }); + }); + + describe('latestVersion sentinel', function () { + it('returns type(uint256).max for a never-archived eventId', async function () { + const op = this.makeArchiveOp(); + await expect( + this.contracts.archiver.latestVersion(op.sourceChainIdHash, op.sourceTxHash, op.sourceLogIndex) + ).to.eventually.equal(ethers.MaxUint256); + await expect(this.contracts.archiver.VERSION_NOT_ARCHIVED()).to.eventually.equal(ethers.MaxUint256); + }); + }); + + describe('ERC165', function () { + it('supports the three archive interfaces and IERC165', async function () { + await expect(this.contracts.archiver.supportsInterface(IFACE.IEventArchive)).to.eventually.be.true; + await expect(this.contracts.archiver.supportsInterface(IFACE.IEventArchiveWriter)).to.eventually.be.true; + await expect(this.contracts.archiver.supportsInterface(IFACE.IEventArchiveAmender)).to.eventually.be.true; + await expect(this.contracts.archiver.supportsInterface(IFACE.IERC165)).to.eventually.be.true; + }); + + it('does not support 0xffffffff', async function () { + await expect(this.contracts.archiver.supportsInterface('0xffffffff')).to.eventually.be.false; + }); + }); + + describe('multicall', function () { + it('batches multiple archiveEvent calls', async function () { + const op1 = this.makeArchiveOp(); + const op2 = this.makeArchiveOp(); + + const data = [op1, op2].map((op) => + this.contracts.archiver.interface.encodeFunctionData('archiveEvent', [ + op.sourceChainId, + op.sourceTxHash, + op.sourceAddress, + op.sourceLogIndex, + op.sourceBlockNumber, + op.payload, + ]) + ); + + await expect(this.contracts.archiver.connect(this.accounts.archiver).multicall(data)).to.emit( + this.contracts.archiver, + 'EventArchived' + ); + + await expect(this.contracts.archiver.isArchived(op1.sourceChainIdHash, op1.sourceTxHash, op1.sourceLogIndex)).to + .eventually.be.true; + await expect(this.contracts.archiver.isArchived(op2.sourceChainIdHash, op2.sourceTxHash, op2.sourceLogIndex)).to + .eventually.be.true; + }); + }); +}); diff --git a/test/fixtures.js b/test/fixtures.js new file mode 100644 index 0000000..c17f979 --- /dev/null +++ b/test/fixtures.js @@ -0,0 +1,56 @@ +const { expect } = require('chai'); +const { ethers } = require('hardhat'); +const { migrate } = require('../scripts/migrate'); + +const getAddress = (account) => account.address ?? account.target ?? account; + +async function fixture() { + const accounts = await ethers.getSigners(); + accounts.admin = accounts.shift(); + accounts.operator = accounts.shift(); + accounts.whitelister = accounts.shift(); + accounts.alice = accounts.shift(); + accounts.bruce = accounts.shift(); + accounts.chris = accounts.shift(); + accounts.other = accounts.shift(); + accounts.archiver = accounts.shift(); + + const { contracts, config, roles } = await migrate( + { + deployer: accounts.admin, + roles: { + admin: { members: [accounts.admin].map(getAddress) }, + 'operator-exceptional': { members: [accounts.operator].map(getAddress) }, + 'operator-daily': { members: [accounts.operator, 'minter'].map(getAddress) }, + 'operator-oracle': { members: [accounts.operator].map(getAddress) }, + burner: { members: ['redemption'].map(getAddress) }, + whitelister: { members: [accounts.whitelister].map(getAddress) }, + whitelisted: { members: [accounts.alice, accounts.bruce, 'redemption'].map(getAddress) }, + 'mint-initiator': { members: [accounts.operator].map(getAddress) }, + 'mint-approver': { members: [accounts.admin].map(getAddress) }, + archiver: { members: [accounts.archiver].map(getAddress) }, + }, + }, + { noCache: true, noConfirm: true } + ); + + // get token + oracle + contracts.token = Object.values(contracts.tokens).find(Boolean); + contracts.oracle = Object.values(contracts.oracles).find(Boolean); + + await expect(contracts.oracle.token()).to.eventually.equal(contracts.token, 'Invalid configuration for testing'); + + return { + accounts, + contracts, + config, + tokenConfig: config.contracts.tokens.find(Boolean), + oracleConfig: config.contracts.tokens.find(Boolean).oracle, + ...roles, + }; +} + +module.exports = { + fixture, + getAddress, +}; diff --git a/test/main.test.js b/test/main.test.js index bce184b..b077f4e 100644 --- a/test/main.test.js +++ b/test/main.test.js @@ -2,60 +2,14 @@ const { expect } = require('chai'); const { ethers, upgrades } = require('hardhat'); const { loadFixture, time } = require('@nomicfoundation/hardhat-network-helpers'); const { deploy } = require('@amxx/hre/scripts'); -const { migrate } = require('../scripts/migrate'); const { Enum, toMask, combine, getDomain } = require('./helpers'); +const { fixture, getAddress } = require('./fixtures'); const STATUS = Enum('NULL', 'PENDING', 'EXECUTED', 'CANCELED'); -const getAddress = (account) => account.address ?? account.target ?? account; - const divUp = (numerator, denominator) => (numerator / denominator) * denominator < numerator ? numerator / denominator + 1n : numerator / denominator; -async function fixture() { - const accounts = await ethers.getSigners(); - accounts.admin = accounts.shift(); - accounts.operator = accounts.shift(); - accounts.whitelister = accounts.shift(); - accounts.alice = accounts.shift(); - accounts.bruce = accounts.shift(); - accounts.chris = accounts.shift(); - accounts.other = accounts.shift(); - - const { contracts, config, roles } = await migrate( - { - deployer: accounts.admin, - roles: { - admin: { members: [accounts.admin].map(getAddress) }, - 'operator-exceptional': { members: [accounts.operator].map(getAddress) }, - 'operator-daily': { members: [accounts.operator, 'minter'].map(getAddress) }, - 'operator-oracle': { members: [accounts.operator].map(getAddress) }, - burner: { members: ['redemption'].map(getAddress) }, - whitelister: { members: [accounts.whitelister].map(getAddress) }, - whitelisted: { members: [accounts.alice, accounts.bruce, 'redemption'].map(getAddress) }, - 'mint-initiator': { members: [accounts.operator].map(getAddress) }, - 'mint-approver': { members: [accounts.admin].map(getAddress) }, - }, - }, - { noCache: true, noConfirm: true } - ); - - // get token + oracle - contracts.token = Object.values(contracts.tokens).find(Boolean); - contracts.oracle = Object.values(contracts.oracles).find(Boolean); - - await expect(contracts.oracle.token()).to.eventually.equal(contracts.token, 'Invalid configuration for testing'); - - return { - accounts, - contracts, - config, - tokenConfig: config.contracts.tokens.find(Boolean), - oracleConfig: config.contracts.tokens.find(Boolean).oracle, - ...roles, - }; -} - describe('Main', function () { beforeEach(async function () { await loadFixture(fixture).then((results) => Object.assign(this, results));