diff --git a/client/src/containers/Header.js b/client/src/containers/Header.js index f9b94e192..e7fcbe993 100644 --- a/client/src/containers/Header.js +++ b/client/src/containers/Header.js @@ -1,5 +1,5 @@ import React from "react"; -import onClickOutside from 'react-onclickoutside' +import onClickOutside from "react-onclickoutside"; import { connect } from "react-redux"; import { withRouter } from "../hoc/withRouter"; import { Link } from "react-router-dom"; @@ -223,7 +223,7 @@ class Header extends React.Component { handleClickOutside = () => { this.closeDropdown(); - } + }; render() { let strings = loadTranslations(this.state.lang); @@ -239,8 +239,9 @@ class Header extends React.Component { ru: strings.russian, ar: strings.arabic, tr: strings.turkish, + vi: strings.vietnamese, }; - + const ddOpen = Boolean(this.state.multiDDOpen); return (
this.closeDropdown()}> @@ -316,14 +317,14 @@ class Header extends React.Component {
{window.location.pathname === constants.PATH_ROOT && !!this.props.web3 && ( - this.toggleDropdownState()} - to={constants.PATH_LEADERBOARD}> -
- + this.toggleDropdownState()} + to={constants.PATH_LEADERBOARD} + > +
+
- )} { @@ -345,26 +346,29 @@ class Header extends React.Component { {strings.Networks}

- {Object.values(constants.NETWORKS_INGAME).map((network, index) => { - if (network && network.name !== "local") { - if (Number(network.id) === this.state.chainId) - return false; // filter out current network - return ( -
{ - e.preventDefault(); - this.changeNetwork(network); - }} - className="dropdown-pill" - > - - {network.name} - -
- ); + {Object.values(constants.NETWORKS_INGAME).map( + (network, index) => { + if (network && network.name !== "local") { + if (Number(network.id) === this.state.chainId) + return false; // filter out current network + return ( +
{ + e.preventDefault(); + this.changeNetwork(network); + }} + className="dropdown-pill" + > + + {network.name} + +
+ ); + } + return null; } - return null; - })} + )}
@@ -375,7 +379,8 @@ class Header extends React.Component {

{Object.keys(LANGUAGES_MAP).map((languageString, index) => ( -
{ this.changeLanguage(e, languageString); }} @@ -455,4 +460,6 @@ function mapDispatchToProps(dispatch) { ); } -export default withRouter(connect(mapStateToProps, mapDispatchToProps)(onClickOutside(Header))); +export default withRouter( + connect(mapStateToProps, mapDispatchToProps)(onClickOutside(Header)) +); diff --git a/client/src/gamedata/ar/strings.json b/client/src/gamedata/ar/strings.json index 16f79acfb..2130547d0 100644 --- a/client/src/gamedata/ar/strings.json +++ b/client/src/gamedata/ar/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "العب الان!", "toggleNavigation": "تصفح", "levelCompleted": "انتهي المستوى!", @@ -110,5 +111,4 @@ "Languages": "اللغات", "PageNotFoundTitle": "خطأ 404 - الصفحة غير موجودة", "PageNotFoundText": "عفوًا! لا يمكن العثور على الصفحة التي تبحث عنها." - } diff --git a/client/src/gamedata/en/strings.json b/client/src/gamedata/en/strings.json index 331462fc4..9240a7e3b 100644 --- a/client/src/gamedata/en/strings.json +++ b/client/src/gamedata/en/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "Play now!", "toggleNavigation": "Toggle navigation", "levelCompleted": "Level completed!", diff --git a/client/src/gamedata/es/strings.json b/client/src/gamedata/es/strings.json index 8b3f5bfe6..6acc6d9bb 100644 --- a/client/src/gamedata/es/strings.json +++ b/client/src/gamedata/es/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "Juega ahora!", "toggleNavigation": "Despliega la navegación", "levelCompleted": "Nivel completado!", diff --git a/client/src/gamedata/fr/strings.json b/client/src/gamedata/fr/strings.json index 3fa13c08f..cabe080a9 100644 --- a/client/src/gamedata/fr/strings.json +++ b/client/src/gamedata/fr/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "Jouer maintenant!", "toggleNavigation": "Basculer la navigation", "levelCompleted": "Niveau terminé!", diff --git a/client/src/gamedata/ja/strings.json b/client/src/gamedata/ja/strings.json index dac3bddd3..5c059d73e 100644 --- a/client/src/gamedata/ja/strings.json +++ b/client/src/gamedata/ja/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "プレイ!", "toggleNavigation": "トグルナビゲーション", "levelCompleted": "レベル クリア!", diff --git a/client/src/gamedata/pt_br/strings.json b/client/src/gamedata/pt_br/strings.json index dd74eebcd..371a44f18 100644 --- a/client/src/gamedata/pt_br/strings.json +++ b/client/src/gamedata/pt_br/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "Jogue agora!", "toggleNavigation": "Exibir a navegação", "levelCompleted": "Nível completado!", diff --git a/client/src/gamedata/ru/strings.json b/client/src/gamedata/ru/strings.json index 74c6d6500..2cfcf9718 100644 --- a/client/src/gamedata/ru/strings.json +++ b/client/src/gamedata/ru/strings.json @@ -19,6 +19,7 @@ "french": "Français", "russian": "Русский", "playNow": "Играть!", + "vietnamese": "Vietnamese", "toggleNavigation": "Показать или скрыть панель навигации", "levelCompleted": "Уровень пройден!", "sources": "Исходный код", diff --git a/client/src/gamedata/tr/strings.json b/client/src/gamedata/tr/strings.json index 56eed471c..5bc790a16 100644 --- a/client/src/gamedata/tr/strings.json +++ b/client/src/gamedata/tr/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "Şimdi oyna!", "toggleNavigation": "Gezinmeyi Aç/Kapa", "levelCompleted": "Seviye tamamlandı!", diff --git a/client/src/gamedata/vi/descriptions/levels/aliencodex.md b/client/src/gamedata/vi/descriptions/levels/aliencodex.md new file mode 100644 index 000000000..061e47c5d --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/aliencodex.md @@ -0,0 +1,7 @@ +You've uncovered an Alien contract. Claim ownership to complete the level. + +  +Things that might help +* Understanding how array storage works +* Understanding [ABI specifications](https://solidity.readthedocs.io/en/v0.4.21/abi-spec.html) +* Using a very `underhanded` approach diff --git a/client/src/gamedata/vi/descriptions/levels/aliencodex_complete.md b/client/src/gamedata/vi/descriptions/levels/aliencodex_complete.md new file mode 100644 index 000000000..99479e714 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/aliencodex_complete.md @@ -0,0 +1,5 @@ +This level exploits the fact that the EVM doesn't validate an array's ABI-encoded length vs its actual payload. + +Additionally, it exploits the arithmetic underflow of array length, by expanding the array's bounds to the entire storage area of `2^256`. The user is then able to modify all contract storage. + +Both vulnerabilities are inspired by 2017's [Underhanded coding contest](https://medium.com/@weka/announcing-the-winners-of-the-first-underhanded-solidity-coding-contest-282563a87079) \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/coinflip.md b/client/src/gamedata/vi/descriptions/levels/coinflip.md new file mode 100644 index 000000000..64a2d9bc0 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/coinflip.md @@ -0,0 +1,5 @@ +This is a coin flipping game where you need to build up your winning streak by guessing the outcome of a coin flip. To complete this level you'll need to use your psychic abilities to guess the correct outcome 10 times in a row. + +  +Things that might help +* See the ["?"](https://ethernaut.openzeppelin.com/help) page above in the top right corner menu, section "Beyond the console" \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/coinflip_complete.md b/client/src/gamedata/vi/descriptions/levels/coinflip_complete.md new file mode 100644 index 000000000..47710b6d5 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/coinflip_complete.md @@ -0,0 +1,6 @@ +Generating random numbers in solidity can be tricky. There currently isn't a native way to generate them, and everything you use in smart contracts is publicly visible, including the local variables and state variables marked as private. Miners also have control over things like blockhashes, timestamps, and whether to include certain transactions - which allows them to bias these values in their favor. + +To get cryptographically proven random numbers, you can use [Chainlink VRF](https://docs.chain.link/docs/get-a-random-number), which uses an oracle, the LINK token, and an on-chain contract to verify that the number is truly random. + +Some other options include using Bitcoin block headers (verified through [BTC Relay](http://btcrelay.org)), [RANDAO](https://github.com/randao/randao), or [Oraclize](http://www.oraclize.it/)). + diff --git a/client/src/gamedata/vi/descriptions/levels/delegate.md b/client/src/gamedata/vi/descriptions/levels/delegate.md new file mode 100644 index 000000000..7a8fed34a --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/delegate.md @@ -0,0 +1,7 @@ +The goal of this level is for you to claim ownership of the instance you are given. + +  +Things that might help +* Look into Solidity's documentation on the `delegatecall` low level function, how it works, how it can be used to delegate operations to on-chain libraries, and what implications it has on execution scope. +* Fallback methods +* Method ids diff --git a/client/src/gamedata/vi/descriptions/levels/delegate_complete.md b/client/src/gamedata/vi/descriptions/levels/delegate_complete.md new file mode 100644 index 000000000..42e2f87ef --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/delegate_complete.md @@ -0,0 +1,5 @@ +Usage of `delegatecall` is particularly risky and has been used as an attack vector on multiple historic hacks. With it, your contract is practically saying "here, -other contract- or -other library-, do whatever you want with my state". Delegates have complete access to your contract's state. The `delegatecall` function is a powerful feature, but a dangerous one, and must be used with extreme care. + + +Please refer to the [The Parity Wallet Hack Explained](https://blog.openzeppelin.com/on-the-parity-wallet-multisig-hack-405a8c12e8f7) article for an accurate explanation of how this idea was used to steal 30M USD. + diff --git a/client/src/gamedata/vi/descriptions/levels/denial.md b/client/src/gamedata/vi/descriptions/levels/denial.md new file mode 100644 index 000000000..330d3e0ff --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/denial.md @@ -0,0 +1,5 @@ +This is a simple wallet that drips funds over time. You can withdraw the funds +slowly by becoming a withdrawing partner. + +If you can deny the owner from withdrawing funds when they call `withdraw()` +(whilst the contract still has funds, and the transaction is of 1M gas or less) you will win this level. diff --git a/client/src/gamedata/vi/descriptions/levels/denial_complete.md b/client/src/gamedata/vi/descriptions/levels/denial_complete.md new file mode 100644 index 000000000..43970a1c7 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/denial_complete.md @@ -0,0 +1,12 @@ +This level demonstrates that external calls to unknown contracts can still +create denial of service attack vectors if a fixed amount of gas is not +specified. + +If you are using a low level `call` to continue executing in the event an external call reverts, ensure that you specify a fixed gas stipend. For example `call.gas(100000).value()`. + +Typically one should follow the [checks-effects-interactions](http://solidity.readthedocs.io/en/latest/security-considerations.html#use-the-checks-effects-interactions-pattern) pattern to avoid reentrancy attacks, there can be other circumstances (such as multiple external calls at the end of a function) where issues such as this can arise. + +*Note*: An external `CALL` can use at most 63/64 of the gas currently available +at the time of the `CALL`. Thus, depending on how much gas is required to +complete a transaction, a transaction of sufficiently high gas (i.e. one such +that 1/64 of the gas is capable of completing the remaining opcodes in the parent call) can be used to mitigate this particular attack. \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/dex.md b/client/src/gamedata/vi/descriptions/levels/dex.md new file mode 100644 index 000000000..3abcb1d22 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/dex.md @@ -0,0 +1,18 @@ +The goal of this level is for you to hack the basic [DEX](https://en.wikipedia.org/wiki/Decentralized_exchange) contract below and steal the funds by price manipulation. + +You will start with 10 tokens of `token1` and 10 of `token2`. The DEX contract starts with 100 of each token. + +You will be successful in this level if you manage to drain all of at least 1 of the 2 tokens from the contract, and allow the contract to report a "bad" price of the assets. + +  +### Quick note +Normally, when you make a swap with an ERC20 token, you have to `approve` the contract to spend your tokens for you. To keep with the syntax of the game, we've just added the `approve` method to the contract itself. So feel free to use `contract.approve(contract.address, )` instead of calling the tokens directly, and it will automatically approve spending the two tokens by the desired amount. Feel free to ignore the `SwappableToken` contract otherwise. + +  +Things that might help: +* How is the price of the token calculated? +* How does the `swap` method work? +* How do you `approve` a transaction of an ERC20? +* Theres more than one way to interact with a contract! +* Remix might help +* What does "At Address" do? diff --git a/client/src/gamedata/vi/descriptions/levels/dex2.md b/client/src/gamedata/vi/descriptions/levels/dex2.md new file mode 100644 index 000000000..7a4a2a56d --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/dex2.md @@ -0,0 +1,9 @@ +This level will ask you to break `DexTwo`, a subtlely modified `Dex` contract from the previous level, in a different way. + +You need to drain all balances of token1 and token2 from the `DexTwo` contract to succeed in this level. + +You will still start with 10 tokens of `token1` and 10 of `token2`. The DEX contract still starts with 100 of each token. + +  +Things that might help: +* How has the `swap` method been modified? diff --git a/client/src/gamedata/vi/descriptions/levels/dex2_complete.md b/client/src/gamedata/vi/descriptions/levels/dex2_complete.md new file mode 100644 index 000000000..64cc56917 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/dex2_complete.md @@ -0,0 +1,9 @@ +As we've repeatedly seen, interaction between contracts can be a source of unexpected behavior. + +Just because a contract claims to implement the [ERC20 spec](https://eips.ethereum.org/EIPS/eip-20) does not mean it's trust worthy. + +Some tokens deviate from the ERC20 spec by not returning a boolean value from their `transfer` methods. See [Missing return value bug - At least 130 tokens affected](https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca). + +Other ERC20 tokens, especially those designed by adversaries could behave more maliciously. + +If you design a DEX where anyone could list their own tokens without the permission of a central authority, then the correctness of the DEX could depend on the interaction of the DEX contract and the token contracts being traded. diff --git a/client/src/gamedata/vi/descriptions/levels/dex_complete.md b/client/src/gamedata/vi/descriptions/levels/dex_complete.md new file mode 100644 index 000000000..a9af3062f --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/dex_complete.md @@ -0,0 +1,58 @@ +The integer math portion aside, getting prices or any sort of data from any single source is a massive attack vector in smart contracts. + +You can clearly see from this example, that someone with a lot of capital could manipulate the price in one fell swoop, and cause any applications relying on it to use the the wrong price. + +The exchange itself is decentralized, but the price of the asset is centralized, since it comes from 1 dex. However, if we were to consider tokens that represent actual assets rather than fictitious ones, most of them would have exchange pairs in several dexes and networks. This would decrease the effect on the asset's price in case a specific dex is targeted by an attack like this. + +[Oracles](https://betterprogramming.pub/what-is-a-blockchain-oracle-f5ccab8dbd72?source=friends_link&sk=d921a38466df8a9176ed8dd767d8c77d) are used to get data into and out of smart contracts. + +[Chainlink Data Feeds](https://docs.chain.link/docs/get-the-latest-price) are a secure, reliable, way to get decentralized data into your smart contracts. They have a vast library of many different sources, and also offer [secure randomness](https://docs.chain.link/docs/chainlink-vrf), ability to make [any API call](https://docs.chain.link/docs/make-a-http-get-request), [modular oracle network creation](https://docs.chain.link/docs/architecture-decentralized-model), [upkeep, actions, and maintainance](https://docs.chain.link/docs/kovan-keeper-network-beta), and unlimited customization. + +[Uniswap TWAP Oracles](https://docs.uniswap.org/contracts/v2/concepts/core-concepts/oracles) relies on a time weighted price model called [TWAP](https://en.wikipedia.org/wiki/Time-weighted_average_price#). While the design can be attractive, this protocol heavily depends on the liquidity of the DEX protocol, and if this is too low, prices can be easily manipulated. + + +Here is an example of getting the price of Bitcoin in USD from a Chainlink data feed (on the Sepolia testnet): + +``` +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.7; + +import "@chainlink/contracts/src/v0.8/interfaces/AggregatorV3Interface.sol"; + +contract PriceConsumerV3 { + AggregatorV3Interface internal priceFeed; + + /** + * Network: Sepolia + * Aggregator: BTC/USD + * Address: 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 + */ + constructor() { + priceFeed = AggregatorV3Interface( + 0x1b44F3514812d835EB1BDB0acB33d3fA3351Ee43 + ); + } + + /** + * Returns the latest price. + */ + function getLatestPrice() public view returns (int) { + // prettier-ignore + ( + /* uint80 roundID */, + int price, + /*uint startedAt*/, + /*uint timeStamp*/, + /*uint80 answeredInRound*/ + ) = priceFeed.latestRoundData(); + return price; + } +} + +``` +[Try it on Remix](https://remix.ethereum.org/#url=https://docs.chain.link/samples/PriceFeeds/PriceConsumerV3.sol) + +Check the Chainlink feed [page](https://data.chain.link/ethereum/mainnet/crypto-usd/btc-usd) to see that the price of Bitcoin is queried from up to 31 different sources. + +You can check also, the [list](https://docs.chain.link/data-feeds/price-feeds/addresses/) all Chainlink price feeds addresses. + diff --git a/client/src/gamedata/vi/descriptions/levels/doubleentrypoint.md b/client/src/gamedata/vi/descriptions/levels/doubleentrypoint.md new file mode 100644 index 000000000..4cadf9de9 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/doubleentrypoint.md @@ -0,0 +1,10 @@ +This level features a `CryptoVault` with special functionality, the `sweepToken` function. This is a common function used to retrieve tokens stuck in a contract. The `CryptoVault` operates with an `underlying` token that can't be swept, as it is an important core logic component of the `CryptoVault`. Any other tokens can be swept. + +The underlying token is an instance of the DET token implemented in the `DoubleEntryPoint` contract definition and the `CryptoVault` holds 100 units of it. Additionally the `CryptoVault` also holds 100 of `LegacyToken LGT`. + +In this level you should figure out where the bug is in `CryptoVault` and protect it from being drained out of tokens. + +The contract features a `Forta` contract where any user can register its own `detection bot` contract. Forta is a decentralized, community-based monitoring network to detect threats and anomalies on DeFi, NFT, governance, bridges and other Web3 systems as quickly as possible. Your job is to implement a `detection bot` and register it in the `Forta` contract. The bot's implementation will need to raise correct alerts to prevent potential attacks or bug exploits. + +Things that might help: +- How does a double entry point work for a token contract? diff --git a/client/src/gamedata/vi/descriptions/levels/doubleentrypoint_complete.md b/client/src/gamedata/vi/descriptions/levels/doubleentrypoint_complete.md new file mode 100644 index 000000000..5137f9b31 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/doubleentrypoint_complete.md @@ -0,0 +1,13 @@ +Congratulations! + +This is the first experience you have with a [Forta bot](https://docs.forta.network/en/latest/). + +Forta comprises a decentralized network of independent node operators who scan all transactions and block-by-block state changes for outlier transactions and threats. When an issue is detected, node operators send alerts to subscribers of potential risks, which enables them to take action. + +The presented example is just for educational purpose since Forta bot is not modeled into smart contracts. In Forta, a bot is a code script to detect specific conditions or events, but when an alert is emitted it does not trigger automatic actions - at least not yet. In this level, the bot's alert effectively trigger a revert in the transaction, deviating from the intended Forta's bot design. + +Detection bots heavily depends on contract's final implementations and some might be upgradeable and break bot's integrations, but to mitigate that you can even create a specific bot to look for contract upgrades and react to it. Learn how to do it [here](https://docs.forta.network/en/latest/quickstart/). + +You have also passed through a recent security issue that has been uncovered during OpenZeppelin's latest [collaboration with Compound protocol](https://compound.finance/governance/proposals/76). + +Having tokens that present a double entry point is a non-trivial pattern that might affect many protocols. This is because it is commonly assumed to have one contract per token. But it was not the case this time :) You can read the entire details of what happened [here](https://blog.openzeppelin.com/compound-tusd-integration-issue-retrospective/). diff --git a/client/src/gamedata/vi/descriptions/levels/elevator.md b/client/src/gamedata/vi/descriptions/levels/elevator.md new file mode 100644 index 000000000..b6fda6f57 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/elevator.md @@ -0,0 +1,5 @@ +This elevator won't let you reach the top of your building. Right? + +##### Things that might help: +* Sometimes solidity is not good at keeping promises. +* This `Elevator` expects to be used from a `Building`. \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/elevator_complete.md b/client/src/gamedata/vi/descriptions/levels/elevator_complete.md new file mode 100644 index 000000000..3d3f059b8 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/elevator_complete.md @@ -0,0 +1,4 @@ +You can use the `view` function modifier on an interface in order to prevent state modifications. The `pure` modifier also prevents functions from modifying the state. +Make sure you read [Solidity's documentation](http://solidity.readthedocs.io/en/develop/contracts.html#view-functions) and learn its caveats. + +An alternative way to solve this level is to build a view function which returns different results depends on input data but don't modify state, e.g. `gasleft()`. \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/fallback.md b/client/src/gamedata/vi/descriptions/levels/fallback.md new file mode 100644 index 000000000..d80e7e8cb --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/fallback.md @@ -0,0 +1,12 @@ +Look carefully at the contract's code below. + +You will beat this level if +1) you claim ownership of the contract +2) you reduce its balance to 0 + +  +Things that might help +* How to send ether when interacting with an ABI +* How to send ether outside of the ABI +* Converting to and from wei/ether units (see `help()` command) +* Fallback methods diff --git a/client/src/gamedata/vi/descriptions/levels/fallback_complete.md b/client/src/gamedata/vi/descriptions/levels/fallback_complete.md new file mode 100644 index 000000000..941136c47 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/fallback_complete.md @@ -0,0 +1,5 @@ +You know the basics of how ether goes in and out of contracts, including the usage of the fallback method. + +You've also learnt about OpenZeppelin's Ownable contract, and how it can be used to restrict the usage of some methods to a privileged address. + +Move on to the next level when you're ready! diff --git a/client/src/gamedata/vi/descriptions/levels/fallout.md b/client/src/gamedata/vi/descriptions/levels/fallout.md new file mode 100644 index 000000000..4063c71dc --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/fallout.md @@ -0,0 +1,5 @@ +Claim ownership of the contract below to complete this level. + +  +Things that might help +* Solidity Remix IDE diff --git a/client/src/gamedata/vi/descriptions/levels/fallout_complete.md b/client/src/gamedata/vi/descriptions/levels/fallout_complete.md new file mode 100644 index 000000000..ebbfbdbe6 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/fallout_complete.md @@ -0,0 +1,15 @@ +That was silly wasn't it? Real world contracts must be much more secure than this and so must it be much harder to hack them right? + +Well... Not quite. + +The story of Rubixi is a very well known case in the Ethereum ecosystem. The company changed its name from 'Dynamic Pyramid' to 'Rubixi' but somehow they didn't rename the constructor method of its contract: + +``` +contract Rubixi { + address private owner; + function DynamicPyramid() { owner = msg.sender; } + function collectAllFees() { owner.transfer(this.balance) } + ... +``` + +This allowed the attacker to call the old constructor and claim ownership of the contract, and steal some funds. Yep. Big mistakes can be made in smartcontractland. diff --git a/client/src/gamedata/vi/descriptions/levels/force.md b/client/src/gamedata/vi/descriptions/levels/force.md new file mode 100644 index 000000000..896868f23 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/force.md @@ -0,0 +1,9 @@ +Some contracts will simply not take your money `¯\_(ツ)_/¯` + +The goal of this level is to make the balance of the contract greater than zero. + +  +Things that might help: +* Fallback methods +* Sometimes the best way to attack a contract is with another contract. +* See the ["?"](https://ethernaut.openzeppelin.com/help) page above, section "Beyond the console" diff --git a/client/src/gamedata/vi/descriptions/levels/force_complete.md b/client/src/gamedata/vi/descriptions/levels/force_complete.md new file mode 100644 index 000000000..e70b2366c --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/force_complete.md @@ -0,0 +1,3 @@ +In solidity, for a contract to be able to receive ether, the fallback function must be marked `payable`. + +However, there is no way to stop an attacker from sending ether to a contract by self destroying. Hence, it is important not to count on the invariant `address(this).balance == 0` for any contract logic. diff --git a/client/src/gamedata/vi/descriptions/levels/gatekeeper1.md b/client/src/gamedata/vi/descriptions/levels/gatekeeper1.md new file mode 100644 index 000000000..68e521bb2 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/gatekeeper1.md @@ -0,0 +1,5 @@ +Make it past the gatekeeper and register as an entrant to pass this level. + +##### Things that might help: +* Remember what you've learned from the Telephone and Token levels. +* You can learn more about the special function `gasleft()`, in Solidity's documentation (see [here](https://docs.soliditylang.org/en/v0.8.3/units-and-global-variables.html) and [here](https://docs.soliditylang.org/en/v0.8.3/control-structures.html#external-function-calls)). diff --git a/client/src/gamedata/vi/descriptions/levels/gatekeeper1_complete.md b/client/src/gamedata/vi/descriptions/levels/gatekeeper1_complete.md new file mode 100644 index 000000000..3b24cfaff --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/gatekeeper1_complete.md @@ -0,0 +1 @@ +Well done! Next, try your hand with the second gatekeeper... \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/gatekeeper2.md b/client/src/gamedata/vi/descriptions/levels/gatekeeper2.md new file mode 100644 index 000000000..5f7acfc64 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/gatekeeper2.md @@ -0,0 +1,6 @@ +This gatekeeper introduces a few new challenges. Register as an entrant to pass this level. + +##### Things that might help: +* Remember what you've learned from getting past the first gatekeeper - the first gate is the same. +* The `assembly` keyword in the second gate allows a contract to access functionality that is not native to vanilla Solidity. See [here](http://solidity.readthedocs.io/en/v0.4.23/assembly.html) for more information. The `extcodesize` call in this gate will get the size of a contract's code at a given address - you can learn more about how and when this is set in section 7 of the [yellow paper](https://ethereum.github.io/yellowpaper/paper.pdf). +* The `^` character in the third gate is a bitwise operation (XOR), and is used here to apply another common bitwise operation (see [here](http://solidity.readthedocs.io/en/v0.4.23/miscellaneous.html#cheatsheet)). The Coin Flip level is also a good place to start when approaching this challenge. \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/gatekeeper2_complete.md b/client/src/gamedata/vi/descriptions/levels/gatekeeper2_complete.md new file mode 100644 index 000000000..f1fa92bc6 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/gatekeeper2_complete.md @@ -0,0 +1 @@ +Way to go! Now that you can get past the gatekeeper, you have what it takes to join [theCyber](https://etherscan.io/address/thecyber.eth#code), a decentralized club on the Ethereum mainnet. Get a passphrase by contacting the creator on [reddit](https://www.reddit.com/user/0age) or via [email](mailto:0age@protonmail.com) and use it to register with the contract at [gatekeepertwo.thecyber.eth](https://etherscan.io/address/gatekeepertwo.thecyber.eth#code) (be aware that only the first 128 entrants will be accepted by the contract). \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/gatekeeper3.md b/client/src/gamedata/vi/descriptions/levels/gatekeeper3.md new file mode 100644 index 000000000..e58998e60 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/gatekeeper3.md @@ -0,0 +1,6 @@ +Cope with gates and become an entrant. + +##### Things that might help: +* Recall return values of low-level functions. +* Be attentive with semantic. +* Refresh how storage works in Ethereum. diff --git a/client/src/gamedata/vi/descriptions/levels/gatekeeper3_complete.md b/client/src/gamedata/vi/descriptions/levels/gatekeeper3_complete.md new file mode 100644 index 000000000..e57bd7eb6 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/gatekeeper3_complete.md @@ -0,0 +1 @@ +Nice job! For more information read [this](https://web3js.readthedocs.io/en/v1.2.9/web3-eth.html?highlight=getStorageAt#getstorageat) and [this](https://medium.com/loom-network/ethereum-solidity-memory-vs-storage-how-to-initialize-an-array-inside-a-struct-184baf6aa2eb). \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/goodsamaritan.md b/client/src/gamedata/vi/descriptions/levels/goodsamaritan.md new file mode 100644 index 000000000..6035a7d30 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/goodsamaritan.md @@ -0,0 +1,7 @@ +This instance represents a Good Samaritan that is wealthy and ready to donate some coins to anyone requesting it. + +Would you be able to drain all the balance from his Wallet? + +Things that might help: + +- [Solidity Custom Errors](https://blog.soliditylang.org/2021/04/21/custom-errors/) \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/goodsamaritan_complete.md b/client/src/gamedata/vi/descriptions/levels/goodsamaritan_complete.md new file mode 100644 index 000000000..f154ee434 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/goodsamaritan_complete.md @@ -0,0 +1,3 @@ +Congratulations! + +Custom errors in Solidity are identified by their 4-byte ‘selector’, the same as a function call. They are bubbled up through the call chain until they are caught by a catch statement in a try-catch block, as seen in the GoodSamaritan's `requestDonation()` function. For these reasons, it is not safe to assume that the error was thrown by the immediate target of the contract call (i.e., Wallet in this case). Any other contract further down in the call chain can declare the same error and throw it at an unexpected location, such as in the `notify(uint256 amount)` function in your attacker contract. diff --git a/client/src/gamedata/vi/descriptions/levels/instances.md b/client/src/gamedata/vi/descriptions/levels/instances.md new file mode 100644 index 000000000..796a20877 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/instances.md @@ -0,0 +1,69 @@ +Cấp độ này sẽ hướng dẫn bạn những điều cơ bản về cách chơi trò chơi. + +  +#### 1. Thiết lập MetaMask +Nếu bạn chưa có thì hãy cài đặt [tiện ích mở rộng trình duyệt MetaMask](https://metamask.io/) (trong Chrome, Firefox, Brave hoặc Opera trên máy tính để bàn của bạn). +Thiết lập ví của tiện ích mở rộng và sử dụng bộ chọn mạng để trỏ đến mạng ưa thích ở phía trên bên trái giao diện của tiện ích mở rộng. Ngoài ra, bạn có thể sử dụng nút UI để chuyển đổi giữa các mạng. Nếu bạn chọn mạng không được hỗ trợ, trò chơi sẽ thông báo cho bạn và đưa bạn đến mạng thử nghiệm Sepolia mặc định. + +#### 2. Mở bảng điều khiển của trình duyệt +Mở bảng điều khiển của trình duyệt của bạn: `Tools > Developer Tools`. + +Bạn sẽ thấy một vài thông báo từ trò chơi. Một trong số họ sẽ nêu địa chỉ người chơi của bạn. Điều này sẽ rất quan trọng trong trò chơi! Bạn luôn có thể xem địa chỉ người chơi của mình bằng cách nhập lệnh sau: + +`player` + +Hãy để ý đến các cảnh báo và lỗi vì chúng có thể cung cấp thông tin quan trọng trong quá trình chơi trò chơi. + +#### 3. Sử dụng trợ giúp của bảng điều khiển + +Bạn cũng có thể xem số dư ether hiện tại của mình bằng cách nhập: + +`getBalance(player)` + +###### LƯU Ý: Mở rộng lời hứa để xem giá trị thực tế, ngay cả khi nó ghi "đang chờ xử lý". Nếu đang sử dụng Chrome v62, bạn có thể sử dụng `await getBalance(player)` để có trải nghiệm bảng điều khiển rõ ràng hơn. + +Tuyệt vời! Để xem bạn có những chức năng tiện ích nào khác trong loại bảng điều khiển: + +`help()` + +Những thứ này sẽ cực kỳ tiện dụng trong quá trình chơi game. + +#### 4. Hợp đồng ethernaut +Nhập lệnh sau vào bảng điều khiển: + +`ethernaut` + +Đây là hợp đồng thông minh chính của trò chơi. Bạn không cần phải tương tác trực tiếp với nó thông qua bảng điều khiển (vì ứng dụng này sẽ làm điều đó cho bạn) nhưng bạn có thể làm điều đó nếu muốn. Chơi đùa với đối tượng này bây giờ là một cách tuyệt vời để tìm hiểu cách tương tác với các hợp đồng thông minh khác của trò chơi. + +Hãy tiếp tục và mở rộng đối tượng ethernaut để xem có gì bên trong. + +#### 5. Tương tác với ABI +`ethernaut` là một đối tượng `TruffleContract` bao bọc hợp đồng `Ethernaut.sol` đã được triển khai trên blockchain. + +Trong số những thứ khác, ABI của hợp đồng tiết lộ tất cả các phương thức công khai của `Ethernaut.sol`, chẳng hạn như `chủ sở hữu`. Nhập lệnh sau đây chẳng hạn: + +`ethernaut.owner()` hoặc `await ethernaut.owner()` nếu bạn đang sử dụng Chrome v62. + +Bạn có thể xem chủ sở hữu của hợp đồng ethernaut là ai. + +#### 6. Nhận ether thử nghiệm +Để chơi trò chơi, bạn sẽ cần ether thử nghiệm. Cách dễ nhất để nhận một số ether testnet là thông qua một vòi hợp lệ cho mạng bạn đã chọn. + +Khi bạn thấy một số xu trong số dư của mình, hãy chuyển sang bước tiếp theo. + +#### 7. Lấy một phiên bản cấp độ +Khi chơi một cấp độ, bạn không tương tác trực tiếp với hợp đồng ethernaut. Thay vào đó, bạn yêu cầu nó tạo một **phiên bản cấp độ** cho bạn. Để làm như vậy, hãy nhấp vào nút "Nhận phiên bản mới" ở cuối trang. Hãy làm điều đó ngay bây giờ và quay lại! + +Bạn sẽ được MetaMask nhắc nhở ủy quyền giao dịch. Làm như vậy và bạn sẽ thấy một số thông báo trong bảng điều khiển. Lưu ý rằng quá trình này đang triển khai một hợp đồng mới trong chuỗi khối và có thể mất vài giây, vì vậy hãy kiên nhẫn khi yêu cầu các phiên bản cấp mới! + +#### 8. Kiểm tra hợp đồng +Giống như bạn đã làm với hợp đồng ethernaut, bạn có thể kiểm tra ABI của hợp đồng này thông qua bảng điều khiển bằng cách sử dụng biến `hợp đồng`. + +#### 9. Tương tác với hợp đồng để hoàn thành cấp độ +Hãy xem phương thức thông tin của cấp độ `contract.info()` hoặc `await Contract.info()` nếu bạn đang sử dụng Chrome v62. +Bạn nên có tất cả những gì bạn cần để hoàn thành cấp độ trong hợp đồng. +Khi bạn biết mình đã hoàn thành cấp độ, hãy gửi hợp đồng bằng nút gửi ở cuối trang. +Thao tác này sẽ gửi phiên bản của bạn trở lại ethernaut, điều này sẽ xác định xem bạn đã hoàn thành nó chưa. + + +##### Mẹo: Đừng quên rằng bạn luôn có thể xem ABI của hợp đồng! diff --git a/client/src/gamedata/vi/descriptions/levels/instances_complete.md b/client/src/gamedata/vi/descriptions/levels/instances_complete.md new file mode 100644 index 000000000..d65b9d350 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/instances_complete.md @@ -0,0 +1,6 @@ +Chúc mừng! Bạn đã hoàn thành phần hướng dẫn. +Hãy xem mã Solidity cho hợp đồng bạn vừa tương tác bên dưới. + +Bây giờ bạn đã sẵn sàng để hoàn thành trò chơi và tiếp theo, bạn sẽ phải tự làm một mình. + +Chúc may mắn!! diff --git a/client/src/gamedata/vi/descriptions/levels/king.md b/client/src/gamedata/vi/descriptions/levels/king.md new file mode 100644 index 000000000..2a93f20fa --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/king.md @@ -0,0 +1,5 @@ +The contract below represents a very simple game: whoever sends it an amount of ether that is larger than the current prize becomes the new king. On such an event, the overthrown king gets paid the new prize, making a bit of ether in the process! As ponzi as it gets xD + +Such a fun game. Your goal is to break it. + +When you submit the instance back to the level, the level is going to reclaim kingship. You will beat the level if you can avoid such a self proclamation. diff --git a/client/src/gamedata/vi/descriptions/levels/king_complete.md b/client/src/gamedata/vi/descriptions/levels/king_complete.md new file mode 100644 index 000000000..f040a0689 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/king_complete.md @@ -0,0 +1,3 @@ +Most of Ethernaut's levels try to expose (in an oversimplified form of course) something that actually happened — a real hack or a real bug. + +In this case, see: [King of the Ether](https://www.kingoftheether.com/thrones/kingoftheether/index.html) and [King of the Ether Postmortem](http://www.kingoftheether.com/postmortem.html). diff --git a/client/src/gamedata/vi/descriptions/levels/magicnum.md b/client/src/gamedata/vi/descriptions/levels/magicnum.md new file mode 100644 index 000000000..9e7374f82 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/magicnum.md @@ -0,0 +1,11 @@ +To solve this level, you only need to provide the Ethernaut with a `Solver`, a contract that responds to `whatIsTheMeaningOfLife()` with the right number. + +Easy right? +Well... there's a catch. + +The solver's code needs to be really tiny. Really reaaaaaallly tiny. Like freakin' really really itty-bitty tiny: 10 opcodes at most. + +Hint: Perhaps its time to leave the comfort of the Solidity compiler momentarily, and build this one by hand O_o. +That's right: Raw EVM bytecode. + +Good luck! diff --git a/client/src/gamedata/vi/descriptions/levels/magicnum_complete.md b/client/src/gamedata/vi/descriptions/levels/magicnum_complete.md new file mode 100644 index 000000000..20503aac2 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/magicnum_complete.md @@ -0,0 +1,3 @@ +Congratulations! If you solved this level, consider yourself a Master of the Universe. + +Go ahead and pierce a random object in the room with your Magnum look. Now, try to move it from afar; Your telekinesis habilities might have just started working. diff --git a/client/src/gamedata/vi/descriptions/levels/motorbike.md b/client/src/gamedata/vi/descriptions/levels/motorbike.md new file mode 100644 index 000000000..3335a510a --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/motorbike.md @@ -0,0 +1,9 @@ +Ethernaut's motorbike has a brand new upgradeable engine design. + +Would you be able to `selfdestruct` its engine and make the motorbike unusable ? + +Things that might help: + +- [EIP-1967](https://eips.ethereum.org/EIPS/eip-1967) +- [UUPS](https://forum.openzeppelin.com/t/uups-proxies-tutorial-solidity-javascript/7786) upgradeable pattern +- [Initializable](https://github.com/OpenZeppelin/openzeppelin-upgrades/blob/master/packages/core/contracts/Initializable.sol) contract \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/motorbike_complete.md b/client/src/gamedata/vi/descriptions/levels/motorbike_complete.md new file mode 100644 index 000000000..741035dea --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/motorbike_complete.md @@ -0,0 +1,9 @@ +The advantage of following an UUPS pattern is to have very minimal proxy to be deployed. The proxy acts as storage layer so any state modification in the implementation contract normally doesn't produce side effects to systems using it, since only the logic is used through delegatecalls. + +This doesn't mean that you shouldn't watch out for vulnerabilities that can be exploited if we leave an implementation contract uninitialized. + +This was a slightly simplified version of what has really been discovered after months of the release of UUPS pattern. + +Takeways: never leave implementation contracts uninitialized ;) + +If you're interested in what happened, read more [here](https://forum.openzeppelin.com/t/uupsupgradeable-vulnerability-post-mortem/15680). \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/naughtcoin.md b/client/src/gamedata/vi/descriptions/levels/naughtcoin.md new file mode 100644 index 000000000..d4f680dca --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/naughtcoin.md @@ -0,0 +1,6 @@ +NaughtCoin is an ERC20 token and you're already holding all of them. The catch is that you'll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0. + +  +Things that might help +* The [ERC20](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-20.md) Spec +* The [OpenZeppelin](https://github.com/OpenZeppelin/zeppelin-solidity/tree/master/contracts) codebase diff --git a/client/src/gamedata/vi/descriptions/levels/naughtcoin_complete.md b/client/src/gamedata/vi/descriptions/levels/naughtcoin_complete.md new file mode 100644 index 000000000..c5d39755b --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/naughtcoin_complete.md @@ -0,0 +1 @@ +When using code that's not your own, it's a good idea to familiarize yourself with it to get a good understanding of how everything fits together. This can be particularly important when there are multiple levels of imports (your imports have imports) or when you are implementing authorization controls, e.g. when you're allowing or disallowing people from doing things. In this example, a developer might scan through the code and think that `transfer` is the only way to move tokens around, low and behold there are other ways of performing the same operation with a different implementation. \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/preservation.md b/client/src/gamedata/vi/descriptions/levels/preservation.md new file mode 100644 index 000000000..aea6b23e4 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/preservation.md @@ -0,0 +1,13 @@ +This contract utilizes a library to store two different times for two different +timezones. The constructor creates two instances of the library for each time +to be stored. + +The goal of this level is for you to claim ownership of the instance you are given. + +  Things that might help +* Look into Solidity's documentation on the `delegatecall` low level function, + how it works, how it can be used to delegate operations to on-chain. + libraries, and what implications it has on execution scope. +* Understanding what it means for `delegatecall` to be context-preserving. +* Understanding how storage variables are stored and accessed. +* Understanding how casting works between different data types. diff --git a/client/src/gamedata/vi/descriptions/levels/preservation_complete.md b/client/src/gamedata/vi/descriptions/levels/preservation_complete.md new file mode 100644 index 000000000..30ad0781a --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/preservation_complete.md @@ -0,0 +1,6 @@ +As the previous level, `delegate` mentions, the use of `delegatecall` to call +libraries can be risky. This is particularly true for contract libraries that +have their own state. This example demonstrates why the `library` keyword +should be used for building libraries, as it prevents the libraries from +storing and accessing state variables. + diff --git a/client/src/gamedata/vi/descriptions/levels/privacy.md b/client/src/gamedata/vi/descriptions/levels/privacy.md new file mode 100644 index 000000000..ed4b8bc20 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/privacy.md @@ -0,0 +1,11 @@ +The creator of this contract was careful enough to protect the sensitive areas of its storage. + +Unlock this contract to beat the level. + +Things that might help: +* Understanding how storage works +* Understanding how parameter parsing works +* Understanding how casting works + +Tips: +* Remember that metamask is just a commodity. Use another tool if it is presenting problems. Advanced gameplay could involve using remix, or your own web3 provider. diff --git a/client/src/gamedata/vi/descriptions/levels/privacy_complete.md b/client/src/gamedata/vi/descriptions/levels/privacy_complete.md new file mode 100644 index 000000000..478506f22 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/privacy_complete.md @@ -0,0 +1,3 @@ +Nothing in the ethereum blockchain is private. The keyword private is merely an artificial construct of the Solidity language. Web3's `getStorageAt(...)` can be used to read anything from storage. It can be tricky to read what you want though, since several optimization rules and techniques are used to compact the storage as much as possible. + +It can't get much more complicated than what was exposed in this level. For more, check out this excellent article by "Darius": [How to read Ethereum contract storage](https://medium.com/aigang-network/how-to-read-ethereum-contract-storage-44252c8af925) diff --git a/client/src/gamedata/vi/descriptions/levels/puzzle_wallet.md b/client/src/gamedata/vi/descriptions/levels/puzzle_wallet.md new file mode 100644 index 000000000..3c3689564 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/puzzle_wallet.md @@ -0,0 +1,18 @@ +Nowadays, paying for DeFi operations is impossible, fact. + +A group of friends discovered how to slightly decrease the cost of performing multiple transactions by batching them in one transaction, so they developed a smart contract for doing this. + +They needed this contract to be upgradeable in case the code contained a bug, and they also wanted to prevent people from outside the group from using it. To do so, they voted and assigned two people with special roles in the system: +The admin, which has the power of updating the logic of the smart contract. +The owner, which controls the whitelist of addresses allowed to use the contract. +The contracts were deployed, and the group was whitelisted. Everyone cheered for their accomplishments against evil miners. + +Little did they know, their lunch money was at risk… + +  +You'll need to hijack this wallet to become the admin of the proxy. + +  +Things that might help: +* Understanding how `delegatecall` works and how `msg.sender` and `msg.value` behaves when performing one. +* Knowing about proxy patterns and the way they handle storage variables. diff --git a/client/src/gamedata/vi/descriptions/levels/puzzle_wallet_complete.md b/client/src/gamedata/vi/descriptions/levels/puzzle_wallet_complete.md new file mode 100644 index 000000000..c98af3a7d --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/puzzle_wallet_complete.md @@ -0,0 +1,7 @@ +Next time, those friends will request an audit before depositing any money on a contract. Congrats! + +Frequently, using proxy contracts is highly recommended to bring upgradeability features and reduce the deployment's gas cost. However, developers must be careful not to introduce storage collisions, as seen in this level. + +Furthermore, iterating over operations that consume ETH can lead to issues if it is not handled correctly. Even if ETH is spent, `msg.value` will remain the same, so the developer must manually keep track of the actual remaining amount on each iteration. This can also lead to issues when using a multi-call pattern, as performing multiple `delegatecall`s to a function that looks safe on its own could lead to unwanted transfers of ETH, as `delegatecall`s keep the original `msg.value` sent to the contract. + +Move on to the next level when you're ready! diff --git a/client/src/gamedata/vi/descriptions/levels/recovery.md b/client/src/gamedata/vi/descriptions/levels/recovery.md new file mode 100644 index 000000000..4ed9a93da --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/recovery.md @@ -0,0 +1,4 @@ + +A contract creator has built a very simple token factory contract. Anyone can create new tokens with ease. After deploying the first token contract, the creator sent `0.001` ether to obtain more tokens. They have since lost the contract address. + +This level will be completed if you can recover (or remove) the `0.001` ether from the lost contract address. diff --git a/client/src/gamedata/vi/descriptions/levels/recovery_complete.md b/client/src/gamedata/vi/descriptions/levels/recovery_complete.md new file mode 100644 index 000000000..37eebb07f --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/recovery_complete.md @@ -0,0 +1,10 @@ + +Contract addresses are deterministic and are calculated by `keccak256(address, nonce)` where the `address` is the address of the contract (or ethereum address that created the transaction) and `nonce` is the number of contracts the spawning contract has created (or the transaction nonce, for regular transactions). + +Because of this, one can send ether to a pre-determined address (which has no private key) and later create a contract at that address which recovers the ether. This is a non-intuitive and somewhat secretive way to (dangerously) store ether without holding a private key. + +An interesting [blog post](https://swende.se/blog/Ethereum_quirks_and_vulns.html) by Martin Swende details potential use cases of this. + +If you're going to implement this technique, make sure you don't miss the nonce, or your funds will be lost forever. + + diff --git a/client/src/gamedata/vi/descriptions/levels/reentrancy.md b/client/src/gamedata/vi/descriptions/levels/reentrancy.md new file mode 100644 index 000000000..0e70cf36a --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/reentrancy.md @@ -0,0 +1,9 @@ +The goal of this level is for you to steal all the funds from the contract. + +  +Things that might help: +* Untrusted contracts can execute code where you least expect it. +* Fallback methods +* Throw/revert bubbling +* Sometimes the best way to attack a contract is with another contract. +* See the ["?"](https://ethernaut.openzeppelin.com/help) page above, section "Beyond the console" diff --git a/client/src/gamedata/vi/descriptions/levels/reentrancy_complete.md b/client/src/gamedata/vi/descriptions/levels/reentrancy_complete.md new file mode 100644 index 000000000..461a5f208 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/reentrancy_complete.md @@ -0,0 +1,12 @@ +In order to prevent re-entrancy attacks when moving funds out of your contract, use the [Checks-Effects-Interactions pattern](https://solidity.readthedocs.io/en/develop/security-considerations.html#use-the-checks-effects-interactions-pattern) being aware that `call` will only return false without interrupting the execution flow. Solutions such as [ReentrancyGuard](https://docs.openzeppelin.com/contracts/2.x/api/utils#ReentrancyGuard) or [PullPayment](https://docs.openzeppelin.com/contracts/2.x/api/payment#PullPayment) can also be used. + +`transfer` and `send` are no longer recommended solutions as they can potentially break contracts after the Istanbul hard fork [Source 1](https://diligence.consensys.net/blog/2019/09/stop-using-soliditys-transfer-now/) [Source 2](https://forum.openzeppelin.com/t/reentrancy-after-istanbul/1742). + +Always assume that the receiver of the funds you are sending can be another contract, not just a regular address. Hence, it can execute code in its payable fallback method and *re-enter* your contract, possibly messing up your state/logic. + +Re-entrancy is a common attack. You should always be prepared for it! + +  +#### The DAO Hack + +The famous DAO hack used reentrancy to extract a huge amount of ether from the victim contract. See [15 lines of code that could have prevented TheDAO Hack](https://blog.openzeppelin.com/15-lines-of-code-that-could-have-prevented-thedao-hack-782499e00942). diff --git a/client/src/gamedata/vi/descriptions/levels/shop.md b/client/src/gamedata/vi/descriptions/levels/shop.md new file mode 100644 index 000000000..ab86e5079 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/shop.md @@ -0,0 +1,5 @@ +Сan you get the item from the shop for less than the price asked? + +##### Things that might help: +* `Shop` expects to be used from a `Buyer` +* Understanding restrictions of view functions diff --git a/client/src/gamedata/vi/descriptions/levels/shop_complete.md b/client/src/gamedata/vi/descriptions/levels/shop_complete.md new file mode 100644 index 000000000..0bc6ca34d --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/shop_complete.md @@ -0,0 +1,3 @@ +Contracts can manipulate data seen by other contracts in any way they want. + +It's unsafe to change the state based on external and untrusted contracts logic. diff --git a/client/src/gamedata/vi/descriptions/levels/switch.md b/client/src/gamedata/vi/descriptions/levels/switch.md new file mode 100644 index 000000000..1ece812ff --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/switch.md @@ -0,0 +1,4 @@ +Just have to flip the switch. Can't be that hard, right? + +##### Things that might help: +Understanding how `CALLDATA` is encoded. diff --git a/client/src/gamedata/vi/descriptions/levels/switch_complete.md b/client/src/gamedata/vi/descriptions/levels/switch_complete.md new file mode 100644 index 000000000..d392b177a --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/switch_complete.md @@ -0,0 +1 @@ +Assuming positions in `CALLDATA` with dynamic types can be erroneous, especially when using hard-coded `CALLDATA` positions. diff --git a/client/src/gamedata/vi/descriptions/levels/telephone.md b/client/src/gamedata/vi/descriptions/levels/telephone.md new file mode 100644 index 000000000..0f9a0dedd --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/telephone.md @@ -0,0 +1,5 @@ +Claim ownership of the contract below to complete this level. + +  +Things that might help +* See the ["?"](https://ethernaut.openzeppelin.com/help) page above, section "Beyond the console" diff --git a/client/src/gamedata/vi/descriptions/levels/telephone_complete.md b/client/src/gamedata/vi/descriptions/levels/telephone_complete.md new file mode 100644 index 000000000..f19d3b627 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/telephone_complete.md @@ -0,0 +1,21 @@ +While this example may be simple, confusing `tx.origin` with `msg.sender` can lead to phishing-style attacks, such as [this](https://blog.ethereum.org/2016/06/24/security-alert-smart-contract-wallets-created-in-frontier-are-vulnerable-to-phishing-attacks/). + +An example of a possible attack is outlined below. + +1) Use `tx.origin` to determine whose tokens to transfer, e.g. + +``` +function transfer(address _to, uint _value) { + tokens[tx.origin] -= _value; + tokens[_to] += _value; +} +``` +2) Attacker gets victim to send funds to a malicious contract that calls the transfer function of the token contract, e.g. + +``` +function () payable { + token.transfer(attackerAddress, 10000); +} +``` + +3) In this scenario, `tx.origin` will be the victim's address (while `msg.sender` will be the malicious contract's address), resulting in the funds being transferred from the victim to the attacker. diff --git a/client/src/gamedata/vi/descriptions/levels/token.md b/client/src/gamedata/vi/descriptions/levels/token.md new file mode 100644 index 000000000..9b90a1ca2 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/token.md @@ -0,0 +1,7 @@ +The goal of this level is for you to hack the basic token contract below. + +You are given 20 tokens to start with and you will beat the level if you somehow manage to get your hands on any additional tokens. Preferably a very large amount of tokens. + +  +Things that might help: +* What is an odometer? \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/token_complete.md b/client/src/gamedata/vi/descriptions/levels/token_complete.md new file mode 100644 index 000000000..5c3254d2b --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/token_complete.md @@ -0,0 +1,12 @@ +Overflows are very common in solidity and must be checked for with control statements such as: +``` +if(a + c > a) { + a = a + c; +} +``` + +An easier alternative is to use OpenZeppelin's SafeMath library that automatically checks for overflows in all the mathematical operators. The resulting code looks like this: +``` +a = a.add(c); +``` +If there is an overflow, the code will revert. \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/unstoppable.md b/client/src/gamedata/vi/descriptions/levels/unstoppable.md new file mode 100644 index 000000000..cc0a1a19f --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/unstoppable.md @@ -0,0 +1,5 @@ +There's a lending pool with a million tokens in balance, offering flash loans for free. + +If only there was a way to attack and stop the pool from offering flash loans ... + +You start with 100 tokens in balance. diff --git a/client/src/gamedata/vi/descriptions/levels/vault.md b/client/src/gamedata/vi/descriptions/levels/vault.md new file mode 100644 index 000000000..8f0400e45 --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/vault.md @@ -0,0 +1 @@ +Unlock the vault to pass the level! \ No newline at end of file diff --git a/client/src/gamedata/vi/descriptions/levels/vault_complete.md b/client/src/gamedata/vi/descriptions/levels/vault_complete.md new file mode 100644 index 000000000..cb6af991a --- /dev/null +++ b/client/src/gamedata/vi/descriptions/levels/vault_complete.md @@ -0,0 +1,3 @@ +It's important to remember that marking a variable as private only prevents other contracts from accessing it. State variables marked as private and local variables are still publicly accessible. + +To ensure that data is private, it needs to be encrypted before being put onto the blockchain. In this scenario, the decryption key should never be sent on-chain, as it will then be visible to anyone who looks for it. [zk-SNARKs](https://blog.ethereum.org/2016/12/05/zksnarks-in-a-nutshell/) provide a way to determine whether someone possesses a secret parameter, without ever having to reveal the parameter. \ No newline at end of file diff --git a/client/src/gamedata/vi/strings.json b/client/src/gamedata/vi/strings.json new file mode 100644 index 000000000..eeb63a50f --- /dev/null +++ b/client/src/gamedata/vi/strings.json @@ -0,0 +1,113 @@ +{ + "ethernaut": "Ethernaut", + "title": "The Ethernaut", + "hiring": "Chung tôi đang cần tuyển dụng!", + "info": "

Ethernaut là một trò chơi lập trình sử dụng Web3/Solidity lấy cảm hứng từ overthewire.org, được chơi trong Máy ảo Ethereum. Mỗi cấp độ là một hợp đồng thông minh cần được “hack”. 100% trò chơi là mã nguồn mở và tất cả các cấp độ đều được đóng góp bởi những người chơi khác. Bạn có ý tưởng muốn đóng góp? Mọi PR đều được

", + "home": "Trang chủ", + "help": "Trợ giúp", + "stats": "Thống kê", + "ethernautHelp": "Trợ giúp", + "footer": "được phát triển với bởi đội ngũ OpenZeppelin", + "english": "English", + "arabic": "عربي", + "spanish": "Español", + "portuguese": "Português", + "japanese": "日本語", + "turkish": "Türkçe", + "chinese_simplified": "簡體中文", + "chinese_traditional": "正體中文", + "french": "Français", + "russian": "Русский", + "vietnamese": "Vietnamese", + "playNow": "Bắt đầu!", + "toggleNavigation": "Bật tắt điều hướng", + "levelCompleted": "Hoàn thành!", + "sources": "Nguồn", + "submitInstance": "Nộp bài", + "getNewInstance": "Tạo phiên bản mới", + "deployMessageTitle": "Trò chơi chưa được triển khai", + "deployMessage": "Hiện tại game chỉ hỗ trợ các mạng này:", + "deployConfirmation": "Bạn muốn triển khai các hợp đồng trên mạng này hay chuyển sang mạng Sepolia?", + "deployNote": "Lưu ý: Nếu bạn triển khai tất cả các cấp độ, chúng tôi sẽ hướng dẫn bạn gửi toàn bộ trò chơi đã triển khai trên mạng này.", + "deployGame": "Triển khai trò chơi", + "switchToSepolia": "Chuyển sang Sepolia", + "deployLevel": "Triển khai cấp độ", + "helperDeployAllContracts": "Triển khai tất cả các hợp đồng còn lại trên mạng hiện tại.", + "confirmMainnetDeploy": "Bạn đang ở trên mạng chính, trò chơi không có giá trị tiền tệ, bạn không nên triển khai trên mạng này.", + "submitLevelFooter": "Triển khai tất cả các cấp độ (deployAllContracts() in console) để thêm mạng hiện tại vào Github repository dưới dạng mạng được hỗ trợ mới.", + "submitGameFooter": "Tuyệt vời! Toàn bộ trò chơi được triển khai trên mạng này. Nhấp vào đây để tạo vấn đề GitHub và gửi cho chúng tôi :)", + "nextLevel": "Đi đến cấp độ tiếp theo", + "uLevels": "Cấp độ", + "lLevels": "cấp độ", + "refresh": "Làm mới", + "uCompleted": "Hoàn thành", + "lCompleted": "hoàn thành", + "players": "người chơi", + "player": "Người chơi", + "levelName": "Tên cấp độ", + "levelAddress": "Địa chỉ của cấp độ", + "blockNum": "Block num", + "uCreated": "Tạo", + "lCreated": "tạo", + "instance": "Instance", + "numberOf": "Number of", + "warning": "Cảnh báo", + "warningMessage": "Không có nhà cung cấp Web3 và trò chơi ở chế độ chỉ đọc", + "levelAuthor": "Cấp độ tác giả:", + "pleaseWait": "VUI LÒNG CHỜ", + "donate": "Bài tập này có dạy cho bạn điều gì hữu ích không? Đóng góp cho tác giả của bài tập (trên mainnet):", + "openConsole": "MỞ CONSOLE CỦA BẠN ĐỂ CHƠI", + "difficulty": "Độ khó", + "error": "Lỗi", + "retrying": "thử lại...", + "typeHelpMessage": "Nhập help() để có danh sách các tiện ích bổ sung web3 tùy chỉnh", + "slowNetworkMessage": "Thông báo 'Phát hiện mạng chậm' gây khó chịu? Hãy thử cài đặt Công cụ dành cho nhà phát triển -> Chỉ tin nhắn của người dùng hoặc tắt 'chrome://flags/#enable-webfonts-intervention-v2", + "notContractSetMessage": "Không có hợp đồng nào được sử dụng, hãy chuyển đến một cấp độ và nhấp vào 'Tạo phiên bản mới'", + "levelAddressMessage": "Địa chỉ cấp độ", + "instanceAddressMessage": "Instance address", + "playerAddressMessage": "Địa chỉ người chơi", + "selectedNetworkMessage": "Mạng hiện tại: ", + "ethernautAddressMessage": "Địa chỉ Ethernaut", + "noLevelsDataMessage": "Không thể tìm thấy dữ liệu cấp độ", + "ethernautNotFoundMessage": "Hợp đồng Ethernaut không được tìm thấy trong mạng hiện tại. Vui lòng đảm bảo (1) rằng bạn đang sử dụng metamask, (2) nó nằm trên mạng được hỗ trợ, (3) nó đã được mở khóa, (4 tùy chọn) Từ ngày 2 tháng 11, bạn có thể BẬT chế độ bảo mật (TẮT theo mặc định) trong Metamask cài đặt nếu bạn không muốn hiển thị thông tin của mình theo mặc định. (5 tùy chọn) Nếu chế độ bảo mật được BẬT, bạn phải ủy quyền cho siêu mặt nạ để sử dụng trang này. và (6) sau đó làm mới.", + "requestingNewInstanceMessage": "Yêu cầu hợp đồng mới từ bài tập...", + "unableToRetrieveLevelMessage": "Không thể tạo được hợp đồng mới cho bài tập! Hãy kiểm tra gas và thử lại lần nữa.", + "transactionNoLogsMessage": "Giao dịch không được lưu lại", + "noPlayerAddressMessage": "Không có địa chỉ người chơi được phát hiện! Đảm bảo rằng 1) Bạn đã cài đặt tiện ích mở rộng trình duyệt metamask và 2) nó đã được mở khóa. 3 tùy chọn) Từ ngày 2 tháng 11, bạn có thể BẬT chế độ bảo mật (TẮT theo mặc định) trong cài đặt nếu bạn không muốn tiết lộ thông tin của mình theo mặc định. 4 tùy chọn) Nếu chế độ riêng tư được BẬT, bạn phải ủy quyền cho Metamask sử dụng trang này. 5) sau đó làm mới", + "submitLevelMessage": "Nộp bài...", + "wellDoneMessage": "Rất tốt", + "completedLevelMessage": "Bạn đã hoàn thành cấp độ này!!!", + "uncompletedLevelMessage": "Ối! Có vẻ như bạn chưa vượt qua được cấp độ này", + "metamaskKnownIssue": "Ôi! Có vẻ như bạn đã gặp phải sự cố Metamask đã biết. Hãy thử tắt và bật lại plugin metamask của bạn. Nếu cách đó không hiệu quả, tôi e rằng bạn cần phải đóng tất cả các quy trình của Chrome và khởi động lại để xử lý plugin cho đến khi bản sửa lỗi được đưa ra. Đừng lo lắng, sau khi khởi động lại, bạn sẽ không thấy thông báo này nữa.", + "eventsCompletionMessage": "Lỗi khi xem sự kiện hoàn thành:", + "unexpectedAddressMessage": "Địa chỉ không mong đợi trong sự kiện LevelCompletedLog (bỏ qua):", + "helperPlayer": "địa chỉ người chơi hiện tại", + "helperEthernaut": "hợp đồng trò chơi chính", + "helperLevel": "địa chỉ hợp đồng cấp hiện tại", + "helperContract": "phiên bản hợp đồng cấp hiện tại (nếu được tạo)", + "helperInstance": "địa chỉ hợp đồng cấp hiện tại (nếu được tạo)", + "helperVersion": "phiên bản trò chơi hiện tại", + "helperGetBalance": "lấy số dư địa chỉ trong ether", + "helperGetBlockNumber": "lấy số khối mạng hiện tại", + "helperSendTransaction": "gửi giao dịch", + "helperGetNetworkId": "lấy id mạng ethereum", + "helperToWei": "chuyển đổi đơn vị ether sang wei", + "helperFromWei": "chuyển đổi đơn vị wei sang ether", + "levelNotTranslated": "Cấp độ này chưa được dịch hoặc bản dịch chưa đầy đủ.", + "contributeTranslation": "Bấm vào đây để cải thiện bản dịch", + "usingConsole": "Hầu hết tương tác trò chơi đều thông qua bảng điều khiển của trình duyệt: `Dev Tools -> Console`. Mở bảng điều khiển và nhập lệnh: \n\n`help()`\n\n để xem danh sách các đối tượng và chức năng được trò chơi đưa vào bảng điều khiển. Vì hầu hết các tương tác đều không đồng bộ, chúng tôi khuyên bạn nên sử dụng Chrome v62 để bật từ khóa `async/await` trong bảng điều khiển, thay vì viết: \n\n`getBalance(player)> PROMISE`\n\n và mở lời hứa. Với chờ đợi/không đồng bộ, bạn có thể viết:\n\n`await getBalance(player) > '1.11002387' `\n\n", + "gameMechanics": "Trò chơi sử dụng hợp đồng chính `Ethernaut.sol` để quản lý tiến trình của người chơi và ủy quyền tương tác với việc triển khai `Level.sol`. Mỗi hợp đồng cấp độ sẽ tạo ra các phiên bản để người chơi thao tác, phá vỡ, phá hủy, sửa chữa, v.v. Người chơi yêu cầu một phiên bản, thao túng nó và trả lại nó cho trò chơi để đánh giá mức độ hoàn thành cấp độ.\n\n Cả hai yêu cầu phiên bản và gửi lại phiên bản vào trò chơi được thực hiện bằng các nút trong giao diện người dùng ở mỗi cấp độ. Khi ứng dụng này truy xuất một phiên bản từ `Ethernaut.sol`, nó sẽ gói phiên bản đó trong một đối tượng `TruffleContract` và hiển thị phiên bản đó trong bảng điều khiển của trình duyệt. Xem cấp độ đầu tiên để có hướng dẫn đầy đủ về cách chơi trò chơi.", + "beyondConsole": "Một số cấp độ sẽ yêu cầu làm việc bên ngoài bảng điều khiển trình duyệt. Tức là viết mã vững chắc và triển khai nó trong mạng để tấn công hợp đồng cá thể của cấp độ đó với một hợp đồng khác. Việc này có thể được thực hiện theo nhiều cách, ví dụ: \n\n1) Sử dụng Remix để viết mã và triển khai mã đó trong mạng tương ứng Xem [Remix Solidity IDE](https://remix.ethereum.org/). \n\n2) Thiết lập dự án truffle cục bộ để phát triển và triển khai các hợp đồng tấn công. Xem [Truffle Framework](http://truffleframework.com/).", + "troubleshooting": "Đôi khi \n\n(a) trạng thái ứng dụng hoặc (b) trạng thái plugin MetaMask \n\ncó thể hơi rối, đặc biệt là sau khi chuyển mạng, mở khóa, v.v. Nếu những gì bạn đang thấy không có nhiều ý nghĩa , hãy thử làm mới ứng dụng, làm mới ứng dụng, tắt và bật lại plugin metamask hoặc thậm chí khởi động lại trình duyệt của bạn. \n\nNếu bạn gặp vấn đề, vui lòng cho chúng tôi biết theo địa chỉ ethernaut@zeppelin.solutions", + "poweredBy": "được cung cấp bởi ", + "setupMetamask": "Nếu bạn chưa có nó, hãy cài đặt [MetaMask browser extension](https://metamask.io/) (trong Chrome, Firefox, Brave hoặc Opera trên máy tính để bàn của bạn). \n\nThiết lập ví của tiện ích mở rộng và sử dụng bộ chọn mạng để trỏ đến mạng ưa thích ở phía trên bên trái giao diện của tiện ích mở rộng. Ngoài ra bạn có thể sử dụng nút UI để chuyển đổi giữa các mạng. Nếu bạn chọn mạng không được hỗ trợ, trò chơi sẽ thông báo cho bạn và đưa bạn đến mạng thử nghiệm Sepolia mặc định. \n\nSau khi hoàn tất, hãy quay lại đây và tải lại trang web", + "FifthyPercentMessage": "Công việc tuyệt vời! Bạn đã đi được nửa chặng đường của Ethernaut và đang khá giỏi trong việc phá vỡ mọi thứ. Làm việc với tư cách là Nhà nghiên cứu bảo mật Blockchain tại OpenZeppelin có thể rất thú vị... https://grnh.se/fdbf1c043us", + "SeventyFivePercentMessage": "75%: Làm tốt lắm…bây giờ bạn đang ở sâu trong hố thỏ…ai biết nó sẽ đưa bạn đến đâu… https://grnh.se/d4a786e43us", + "NinetyPercentMessage": "90%: Bạn sắp đến đích rồi! Chỉ còn một vài thử thách nữa là bạn sẽ hoàn thành Ethernaut! Bạn đã cân nhắc sự nghiệp trong lĩnh vực Bảo mật Blockchain chưa?https://grnh.se/cfcca8c83us", + "HundredPercentMessage": "100%: Xin chúc mừng! Cuộc hành trình của bạn xuống hố thỏ Web3 thật ấn tượng và đáng được tôn vinh! Bây giờ bạn có kỹ năng để phá vỡ hợp đồng thông minh! Điều gì tiếp theo từ đây? Đăng ký làm Nhà nghiên cứu bảo mật Blockchain tại OpenZeppelin và góp phần bảo mật các giao thức hàng đầu trong Web3! https://grnh.se/26c05aac3us", + "Menu": "Menu", + "Networks": "Mạng", + "Languages": "Ngôn ngữ", + "PageNotFoundTitle": "404 Lỗi - Không tìm thấy trang", + "PageNotFoundText": "Ối! Trang bạn đang tìm kiếm không thể được tìm thấy." +} diff --git a/client/src/gamedata/zh_cn/strings.json b/client/src/gamedata/zh_cn/strings.json index 981a5429a..514495294 100644 --- a/client/src/gamedata/zh_cn/strings.json +++ b/client/src/gamedata/zh_cn/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "开始!", "toggleNavigation": "切换导航", "levelCompleted": "完成关卡!", @@ -95,9 +96,9 @@ "helperFromWei": "从wei转换到ether", "levelNotTranslated": "这一关卡还没有被翻译或是翻译不完全 ", "contributeTranslation": "来帮助我们翻译吧", - "usingConsole": "大多数的游戏互动时通过浏览器的控制台: `Dev Tools -> Console`. 打开控制台然后输入这个命令:\n\n`help()`\n\n 可以得到一个列表, 包含了被这个游戏放入控制台的对象和函数. 鉴于多数的互动是异步的, 我们推荐使用Chrome v62 并且使用 `async`/`await` 关键词, 所以相比于使用:\n\n`getBalance(player)> PROMISE`\n\n我们推荐使用 await/async, 你可以这样使用: \n\n`await getBalance(player)> 1.11002387`\n\n", - "gameMechanics": "这个游戏使用主合约 `Ethernaut.sol` 来管理玩家进度, 代理玩家和 `Level.sol` 互动. 每一关的合约会产生一个实例供玩家操作, 攻破, 摧毁, 修复等等. 玩家请求一个实例, 操作之后返回,然后将会被评判完成度.\n\n 请求和提交实例都是通过每一关界面上的按钮. 当这个 app 从 `Ethernaut.sol` 取回实例时, 会把它包装在 `TruffleContract` 对象中, 然后暴露在浏览器的控制台里. 尝试第一关来看看怎么玩这个游戏.", - "beyondConsole": "有些关卡需要在控制台之外的操作. 比如, 用 solidity 写一些代码, 部署合约在网络上, 然后攻击实例. 这可以通过很多方式完成, 比如: \n\n1) 使用 Remix 写代码并部署在相应的网络上 参见 [Remix Solidity IDE](https://remix.ethereum.org/).\n\n2) 设置一个本地 truffle 项目, 开发并部署攻击合约. 参见 [Truffle Framework](http://truffleframework.com/).", + "usingConsole": "大多数的游戏互动时通过浏览器的控制台: `Dev Tools -> Console`. 打开控制台然后输入这个命令:\n\n`help()`\n\n 可以得到一个列表, 包含了被这个游戏放入控制台的对象和函数. 鉴于多数的互动是异步的, 我们推荐使用Chrome v62 并且使用 `async`/`await` 关键词, 所以相比于使用:\n\n`getBalance(player)> PROMISE`\n\n我们推荐使用 await/async, 你可以这样使用: \n\n`await getBalance(player)> 1.11002387`\n\n", + "gameMechanics": "这个游戏使用主合约 `Ethernaut.sol` 来管理玩家进度, 代理玩家和 `Level.sol` 互动. 每一关的合约会产生一个实例供玩家操作, 攻破, 摧毁, 修复等等. 玩家请求一个实例, 操作之后返回,然后将会被评判完成度.\n\n 请求和提交实例都是通过每一关界面上的按钮. 当这个 app 从 `Ethernaut.sol` 取回实例时, 会把它包装在 `TruffleContract` 对象中, 然后暴露在浏览器的控制台里. 尝试第一关来看看怎么玩这个游戏.", + "beyondConsole": "有些关卡需要在控制台之外的操作. 比如, 用 solidity 写一些代码, 部署合约在网络上, 然后攻击实例. 这可以通过很多方式完成, 比如: \n\n1) 使用 Remix 写代码并部署在相应的网络上 参见 [Remix Solidity IDE](https://remix.ethereum.org/).\n\n2) 设置一个本地 truffle 项目, 开发并部署攻击合约. 参见 [Truffle Framework](http://truffleframework.com/).", "troubleshooting": "有的时候, \n\napp 或者 MetaMask 插件会有点问题, 特别是当你转换网络和解锁时. \n\n如果你遇到什么莫名其妙的问题, 尝试刷新 app, 多次刷新. 重启 MetaMask 插件, 甚至是重启浏览器.\n\n如果你发现其他问题, 欢迎提交给我们 ethernaut@zeppelin.solutions", "poweredBy": "powered by ", "setupMetamask": "如果你还没有,请安装[MetaMask浏览器扩展](https://metamask.io/)(在电脑上的Chrome、Firefox、Brave或Opera浏览器中)。\n\n配置好浏览器扩展的钱包,然后在扩展界面的左上角选择偏好的网络,或者你也可以用网页界面的按钮来切换网络。\n\n如果你选择了一个不支持的网络,这个游戏将提示你并且为你切换到默认的Sepolia测试网络", diff --git a/client/src/gamedata/zh_tw/strings.json b/client/src/gamedata/zh_tw/strings.json index ce97df2b9..9ec04fc1d 100644 --- a/client/src/gamedata/zh_tw/strings.json +++ b/client/src/gamedata/zh_tw/strings.json @@ -18,6 +18,7 @@ "chinese_traditional": "正體中文", "french": "Français", "russian": "Русский", + "vietnamese": "Vietnamese", "playNow": "開始!", "toggleNavigation": "切換導覽", "levelCompleted": "過關!",