From 0215923989450d726750f7be6b29a3660412b4d2 Mon Sep 17 00:00:00 2001 From: Bradley Woolf Date: Tue, 11 Oct 2022 15:19:02 -0700 Subject: [PATCH 1/5] =?UTF-8?q?=E2=9C=A8init=20for=20linear?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CypherEscrow.sol | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/CypherEscrow.sol b/src/CypherEscrow.sol index 35e0de5..2f6139a 100644 --- a/src/CypherEscrow.sol +++ b/src/CypherEscrow.sol @@ -43,6 +43,7 @@ contract CypherEscrow is ReentrancyGuard { address dst; address asset; uint256 amount; + uint256 blockNumber; } /*////////////////////////////////////////////////////////////// @@ -111,6 +112,9 @@ contract CypherEscrow is ReentrancyGuard { /// @param origin The address of the user who initiated the withdraw /// @param dst The address of the user who will receive the ETH function escrowETH(address origin, address dst) external payable nonReentrant { + // prevent multi-hack under the radar + // check if it is in the same block && the sum of all recent tx's is greater than the specified amount + // check if the stop has been overwritten by protocol owner on the frontend // if (msg.sender != sourceContract) revert NotSourceContract(); if (origin == address(0) || dst == address(0)) revert NotValidAddress(); @@ -122,6 +126,7 @@ contract CypherEscrow is ReentrancyGuard { // if they are whitelisted or amount is less than threshold, just transfer the tokens if (amount < tokenThreshold || isWhitelisted[dst]) { + // need to check if they have gone through before (bool success, ) = address(dst).call{value: amount}(""); if (!success) revert TransferFailed(); } else if (getTransactionInfo[key].origin == address(0)) { From ec4444b92fca270ccd85294fb5125f222f4993f3 Mon Sep 17 00:00:00 2001 From: Bradley Woolf Date: Wed, 12 Oct 2022 12:55:51 -0700 Subject: [PATCH 2/5] =?UTF-8?q?=F0=9F=91=B7add=20state=20vars=20for=20unde?= =?UTF-8?q?r-the-radar=20hack?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CypherEscrow.sol | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/CypherEscrow.sol b/src/CypherEscrow.sol index 2f6139a..daca1c2 100644 --- a/src/CypherEscrow.sol +++ b/src/CypherEscrow.sol @@ -46,6 +46,28 @@ contract CypherEscrow is ReentrancyGuard { uint256 blockNumber; } + /*////////////////////////////////////////////////////////////// + EXPERIMENT + //////////////////////////////////////////////////////////////*/ + /* + Limits: 10,000 tokens per tx + 1. hacker withdraws 2000 tokens in 5 transactions + 2. hacker withdraws 5000 tokens in 2 transactions + */ + /// @notice Number of blocks to allow repeat transactions + uint acceptableBlockLimts = 5; + + /// @notice Transaction history of hacker, specified by their address + mapping(address => UserTransactions) public transactionHistory; + + /// @notice User transaction struct with information on total transactions + struct UserTransactions { + address sender; + uint totalAmount; + uint prevBlockNumber; + uint prevAmount; + } + /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ From 50ec00e338ddde352af4134814aeabd610a678c8 Mon Sep 17 00:00:00 2001 From: Bradley Woolf Date: Thu, 13 Oct 2022 22:50:06 -0700 Subject: [PATCH 3/5] =?UTF-8?q?=20=E2=9C=A8wip=20skeleton=20for=20on=20cha?= =?UTF-8?q?in=20radar=20protection?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CypherEscrow.sol | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/CypherEscrow.sol b/src/CypherEscrow.sol index daca1c2..4b46925 100644 --- a/src/CypherEscrow.sol +++ b/src/CypherEscrow.sol @@ -68,6 +68,26 @@ contract CypherEscrow is ReentrancyGuard { uint prevAmount; } + /// we want a sliding scale for the amount of tokens that can be withdrawn. Newer users can withdraw less than older users. Maybe they make it custom? 3 tiers? + uint tier1TokenLimit = 1000; + uint tier2TokenLimit = 5000; + uint tier3TokenLimit = 10000; + + uint tier1TimeLimit = 10 minutes; + uint tier2TimeLimit = 20 minutes; + uint tier3TimeLimit = 30 minutes; + + /// @dev utility function to store the specific user + /// @param _sender The address of the user + /// @param _amount The amount of tokens being withdrawn + function storeUser(address _sender, uint _amount) internal { + UserTransactions storage user = transactionHistory[_sender]; + user.sender = _sender; + user.totalAmount += _amount; + user.prevBlockNumber = block.number; + user.prevAmount = _amount; + } + /*////////////////////////////////////////////////////////////// EVENTS //////////////////////////////////////////////////////////////*/ From 037f33b0bd3d816d7161692f191fd5e00b860e02 Mon Sep 17 00:00:00 2001 From: Bradley Woolf Date: Sun, 16 Oct 2022 22:09:59 -0700 Subject: [PATCH 4/5] =?UTF-8?q?=F0=9F=91=B7prettier=20config=20and=20add?= =?UTF-8?q?=20gitignore?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 5 +- yarn.lock | 162 ++++++++++++++++++++++++++--------------------------- 2 files changed, 85 insertions(+), 82 deletions(-) diff --git a/.gitignore b/.gitignore index 5dfe93f..d0d28c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ /cache /node_modules -/out \ No newline at end of file +/out +package-lock.json +/broadcast +.env \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index bb31320..106b11f 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3,95 +3,95 @@ "@solidity-parser/parser@^0.14.3": - version "0.14.3" - resolved "https://registry.yarnpkg.com/@solidity-parser/parser/-/parser-0.14.3.tgz#0d627427b35a40d8521aaa933cc3df7d07bfa36f" - integrity sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw== + "integrity" "sha512-29g2SZ29HtsqA58pLCtopI1P/cPy5/UAzlcAXO6T/CNJimG6yA8kx4NaseMyJULiC+TEs02Y9/yeHzClqoA0hw==" + "resolved" "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.3.tgz" + "version" "0.14.3" dependencies: - antlr4ts "^0.5.0-alpha.4" - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -antlr4ts@^0.5.0-alpha.4: - version "0.5.0-alpha.4" - resolved "https://registry.yarnpkg.com/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz#71702865a87478ed0b40c0709f422cf14d51652a" - integrity sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ== - -emoji-regex@^10.1.0: - version "10.2.1" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-10.2.1.tgz#a41c330d957191efd3d9dfe6e1e8e1e9ab048b3f" - integrity sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA== - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -escape-string-regexp@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" - integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -lru-cache@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94" - integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA== + "antlr4ts" "^0.5.0-alpha.4" + +"ansi-regex@^5.0.1": + "integrity" "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + "resolved" "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz" + "version" "5.0.1" + +"antlr4ts@^0.5.0-alpha.4": + "integrity" "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==" + "resolved" "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz" + "version" "0.5.0-alpha.4" + +"emoji-regex@^10.1.0": + "integrity" "sha512-97g6QgOk8zlDRdgq1WxwgTMgEWGVAQvB5Fdpgc1MkNy56la5SKP9GsMXKDOdqwn90/41a8yPwIGk1Y6WVbeMQA==" + "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.2.1.tgz" + "version" "10.2.1" + +"emoji-regex@^8.0.0": + "integrity" "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + "resolved" "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz" + "version" "8.0.0" + +"escape-string-regexp@^4.0.0": + "integrity" "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + "resolved" "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz" + "version" "4.0.0" + +"is-fullwidth-code-point@^3.0.0": + "integrity" "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + "resolved" "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz" + "version" "3.0.0" + +"lru-cache@^6.0.0": + "integrity" "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==" + "resolved" "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz" + "version" "6.0.0" dependencies: - yallist "^4.0.0" + "yallist" "^4.0.0" -prettier-plugin-solidity@^1.0.0-beta.24: - version "1.0.0-beta.24" - resolved "https://registry.yarnpkg.com/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.24.tgz#67573ca87098c14f7ccff3639ddd8a4cab2a87eb" - integrity sha512-6JlV5BBTWzmDSq4kZ9PTXc3eLOX7DF5HpbqmmaF+kloyUwOZbJ12hIYsUaZh2fVgZdV2t0vWcvY6qhILhlzgqg== +"prettier-plugin-solidity@^1.0.0-beta.24": + "integrity" "sha512-6JlV5BBTWzmDSq4kZ9PTXc3eLOX7DF5HpbqmmaF+kloyUwOZbJ12hIYsUaZh2fVgZdV2t0vWcvY6qhILhlzgqg==" + "resolved" "https://registry.npmjs.org/prettier-plugin-solidity/-/prettier-plugin-solidity-1.0.0-beta.24.tgz" + "version" "1.0.0-beta.24" dependencies: "@solidity-parser/parser" "^0.14.3" - emoji-regex "^10.1.0" - escape-string-regexp "^4.0.0" - semver "^7.3.7" - solidity-comments-extractor "^0.0.7" - string-width "^4.2.3" - -prettier@^2.7.1: - version "2.7.1" - resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64" - integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g== - -semver@^7.3.7: - version "7.3.8" - resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798" - integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A== + "emoji-regex" "^10.1.0" + "escape-string-regexp" "^4.0.0" + "semver" "^7.3.7" + "solidity-comments-extractor" "^0.0.7" + "string-width" "^4.2.3" + +"prettier@^2.3.0", "prettier@^2.7.1": + "integrity" "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==" + "resolved" "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz" + "version" "2.7.1" + +"semver@^7.3.7": + "integrity" "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==" + "resolved" "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz" + "version" "7.3.8" dependencies: - lru-cache "^6.0.0" + "lru-cache" "^6.0.0" -solidity-comments-extractor@^0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz#99d8f1361438f84019795d928b931f4e5c39ca19" - integrity sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw== +"solidity-comments-extractor@^0.0.7": + "integrity" "sha512-wciNMLg/Irp8OKGrh3S2tfvZiZ0NEyILfcRCXCD4mp7SgK/i9gzLfhY2hY7VMCQJ3kH9UB9BzNdibIVMchzyYw==" + "resolved" "https://registry.npmjs.org/solidity-comments-extractor/-/solidity-comments-extractor-0.0.7.tgz" + "version" "0.0.7" -string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== +"string-width@^4.2.3": + "integrity" "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==" + "resolved" "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + "version" "4.2.3" dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + "emoji-regex" "^8.0.0" + "is-fullwidth-code-point" "^3.0.0" + "strip-ansi" "^6.0.1" + +"strip-ansi@^6.0.1": + "integrity" "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==" + "resolved" "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + "version" "6.0.1" dependencies: - ansi-regex "^5.0.1" + "ansi-regex" "^5.0.1" -yallist@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" - integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +"yallist@^4.0.0": + "integrity" "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + "resolved" "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz" + "version" "4.0.0" From 59a98051b85637aafc283c9c1ea77115292e999e Mon Sep 17 00:00:00 2001 From: Bradley Woolf Date: Mon, 17 Oct 2022 12:20:44 -0700 Subject: [PATCH 5/5] =?UTF-8?q?=E2=9C=A8=20add=20modular=20tiers?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/CypherEscrow.sol | 55 +++++++++++++++++++++++++++++++++----------- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/src/CypherEscrow.sol b/src/CypherEscrow.sol index 4b46925..e6d00f6 100644 --- a/src/CypherEscrow.sol +++ b/src/CypherEscrow.sol @@ -55,7 +55,7 @@ contract CypherEscrow is ReentrancyGuard { 2. hacker withdraws 5000 tokens in 2 transactions */ /// @notice Number of blocks to allow repeat transactions - uint acceptableBlockLimts = 5; + uint256 acceptableBlockLimts = 5; /// @notice Transaction history of hacker, specified by their address mapping(address => UserTransactions) public transactionHistory; @@ -63,24 +63,37 @@ contract CypherEscrow is ReentrancyGuard { /// @notice User transaction struct with information on total transactions struct UserTransactions { address sender; - uint totalAmount; - uint prevBlockNumber; - uint prevAmount; + uint256 totalAmount; + uint256 prevBlockNumber; + uint256 prevAmount; } - /// we want a sliding scale for the amount of tokens that can be withdrawn. Newer users can withdraw less than older users. Maybe they make it custom? 3 tiers? - uint tier1TokenLimit = 1000; - uint tier2TokenLimit = 5000; - uint tier3TokenLimit = 10000; + /// @notice We want a sliding scale for the amount of tokens that can be withdrawn. Newer users can withdraw less than older users. Maybe they make it custom? 3 tiers? + uint256 tier1TokenLimit = 1000; + uint256 tier2TokenLimit = 5000; + uint256 tier3TokenLimit = 10000; - uint tier1TimeLimit = 10 minutes; - uint tier2TimeLimit = 20 minutes; - uint tier3TimeLimit = 30 minutes; + uint256 tier1TimeLimit = 10 minutes; + uint256 tier2TimeLimit = 20 minutes; + uint256 tier3TimeLimit = 30 minutes; + + /// @dev Tiers mapping: 1 => {tier1TokenLimit, tier1TimeLimit}, etc + mapping(uint256 => Tier) public tiers; + + /// @dev Tier struct + struct Tier { + uint256 tokenLimit; + uint256 timeLimit; + } + + /*////////////////////////////////////////////////////////////// + EXPERIMENT END + //////////////////////////////////////////////////////////////*/ /// @dev utility function to store the specific user /// @param _sender The address of the user /// @param _amount The amount of tokens being withdrawn - function storeUser(address _sender, uint _amount) internal { + function storeUser(address _sender, uint256 _amount) internal { UserTransactions storage user = transactionHistory[_sender]; user.sender = _sender; user.totalAmount += _amount; @@ -106,6 +119,7 @@ contract CypherEscrow is ReentrancyGuard { event OracleAdded(address indexed user, address oracle); event TimeLimitSet(uint256 timeLimit); event AddressAddedToWhitelist(address indexed user, address whitelist); + event TiersAndLimitsSet(uint256[], uint256[]); /*////////////////////////////////////////////////////////////// ERRORS @@ -156,7 +170,7 @@ contract CypherEscrow is ReentrancyGuard { function escrowETH(address origin, address dst) external payable nonReentrant { // prevent multi-hack under the radar // check if it is in the same block && the sum of all recent tx's is greater than the specified amount - + // check if the stop has been overwritten by protocol owner on the frontend // if (msg.sender != sourceContract) revert NotSourceContract(); if (origin == address(0) || dst == address(0)) revert NotValidAddress(); @@ -306,6 +320,21 @@ contract CypherEscrow is ReentrancyGuard { emit OracleAdded(msg.sender, _oracle); } + /// @dev Set limits for the different tiers + /// @param _tiers The array of tiers + /// @param _limits The array of limits associated with the tiers + function setTiersAndLimits(uint256[] memory _tiers, uint256[] memory _limits) external onlyOracle { + require(_tiers.length == _limits.length, "Tiers and limits must be the same length"); + + for (uint256 i = 0; i < _tiers.length; i++) { + /// @notice Start at 1 because 0 is the default + Tier memory tier; + tiers[i + 1] = new Tier(_tiers[i], _limits[i]); + } + + emit TiersAndLimitsSet(_tiers, _limits); + } + /*////////////////////////////////////////////////////////////// GETTERS //////////////////////////////////////////////////////////////*/