diff --git a/README.md b/README.md index cd4ae4b..314a6b1 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Puzzl3s šŸ§© is an engaging collection of web3-based puzzles and Capture The Fla The following challenges are being or have been solved: - [Damn Vulnerable DeFi V4](docs/DamnVulnerableDeFiV4.md) -- [EthernautCTF](docs/EthernautCTF.md) +- [EthernautCTF](docs/ethernaut-ctf/EthernautCTF.md) - [QuillCTF](docs/QuillCTF.md) ## Exploit diff --git a/docs/EthernautCTF.md b/docs/EthernautCTF.md deleted file mode 100644 index d87885f..0000000 --- a/docs/EthernautCTF.md +++ /dev/null @@ -1,39 +0,0 @@ -# [EthernautCTF](https://ethernaut.openzeppelin.com/) - -![ethernaut](./ethernaut.png) - -| NĀ° | Contract | Done | Exploit PoC | Description | -| --- | ---------------------------------------------------------- | :--: | --------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| 01 | [Fallback](../src/EthernautCTF/Fallback.sol) | āœ… | [FallbackExploit](../test/EthernautCTF/FallbackExploit.t.sol) | Call a contract with some value to target the `receive` method. | -| 02 | [Fallout](../src/EthernautCTF/Fallout.sol) | āœ… | [FalloutExploit](../test/EthernautCTF/FalloutExploit.t.sol) | Typo in the constructor name. | -| 03 | [CoinFlip](../src/EthernautCTF/CoinFlip.sol) | āœ… | [CoinFlipExploit](../test/EthernautCTF/CoinFlipExploit.t.sol) | The contract relies on `block.number` to generate a random value. | -| 04 | [Telephone](../src/EthernautCTF/Telephone.sol) | āœ… | [TelephoneExploit](../test/EthernautCTF/TelephoneExploit.t.sol) | Use a helper contract to make sure `tx.origin` and `msg.sender` are different. | -| 05 | [Token](../src/EthernautCTF/Token.sol) | āœ… | [TokenExploit](../test/EthernautCTF/TokenExploit.t.sol) | Exploit overflows and underflows of the `0.6.0` solidity compiler. | -| 06 | [Delegation](../src/EthernautCTF/Delegation.sol) | āŒ | [DelegationExploit](../test/EthernautCTF/DelegationExploit.t.sol) | Make use of the `delegatecall` to overwrite the storage of the main contract. | -| 07 | [Force](../src/EthernautCTF/Force.sol) | āœ… | [ForceExploit](../test/EthernautCTF/ForceExploit.t.sol) | Create a contract, fund it with some ether and use the `selfdestruct` method to send the contract balance to any other contract (e.g. a contract without any implementation). | -| 08 | [Vault](../src/EthernautCTF/Vault.sol) | āœ… | [VaultExploit](../test/EthernautCTF/VaultExploit.t.sol) | Read the password from the contract storage. | -| 09 | [King](../src/EthernautCTF/King.sol) | āœ… | [KingExploit](../test/EthernautCTF/KingExploit.t.sol) | Implement a helper contract that reverts when receiving ether. | -| 10 | [Reentrance](../src/EthernautCTF/Reentrance.sol) | āœ… | [ReentranceExploit](../test/EthernautCTF/ReentranceExploit.t.sol) | Perform all state changes before making external calls to avoid re-entrancy exploits. | -| 11 | [Elevator](../src/EthernautCTF/Elevator.sol) | āœ… | [ElevatorExploit](../test/EthernautCTF/ElevatorExploit.t.sol) | When calling an external contract, always check the returned value before using it! | -| 12 | [Privacy](../src/EthernautCTF/Privacy.sol) | āœ… | [PrivacyExploit](../test/EthernautCTF/PrivacyExploit.t.sol) | Read the key from the contract storage | -| 13 | [GatekeeperOne](../src/EthernautCTF/GatekeeperOne.sol) | āœ… | [GatekeeperOneExploit](../test/EthernautCTF/GatekeeperOneExploit.t.sol) | - Estimate the amount of gas a contract call would take using `gasleft` and binary search (dichotomy).
- Another method is to use a `while` loop and to consume the gas tiny bits by tiny bits until the call succeeds.
- Perform operations using bit masks. | -| 14 | [GatekeeperTwo](../src/EthernautCTF/GatekeeperTwo.sol) | āœ… | [GatekeeperTwoExploit](../test/EthernautCTF/GatekeeperTwoExploit.t.sol) | - Create a contract that has a size equal to zero by putting all the logic inside the constructor. Indeed, a contract does not have source code available during construction.
- Solidity does not support bitwise negation, but a simple way to perform the operation is to use the XOR operation (`^`) with `0xff` (ones). | -| 15 | [NaughtCoin](../src/EthernautCTF/NaughtCoin.sol) | āœ… | [NaughtCoinExploit](../test/EthernautCTF/NaughtCoinExploit.t.sol) | Use the ERC20 `allowance` and `transferFrom` methods to send tokens on behalf of a nother address. | -| 16 | [Preservation](../src/EthernautCTF/Preservation.sol) (\*) | āœ… | [PreservationExploit](../test/EthernautCTF/PreservationExploit.t.sol) | Make use of the `delegatecall` to overwrite the storage of the main contract. This time it involved a bit more creativity as it required to overwrite an address (20 bytes) using a uint256 (32 bytes). | -| 17 | [Recovery](../src/EthernautCTF/Recovery.sol) | āœ… | [RecoveryExploit](../test/EthernautCTF/RecoveryExploit.t.sol) | The address of an Ethereum contract is deterministically computed from the address of its creator (sender) and its nonce (how many transactions the creator has sent). The sender and nonce are RLP-encoded and then hashed with keccak256. For a Solidity implementation, check the exploit code. | -| 18 | [MagicNumber](../src/EthernautCTF/MagicNumber.sol) | āœ… | [MagicNumberExploit](../test/EthernautCTF/MagicNumberExploit.t.sol) | - Use raw bytecode to create the smallest possible contract.
- Learn about initialization code to be able to run any runtime code.
- Learn about `create` to create a contract from the initialization code. | -| 19 | [AlienCode](../src/EthernautCTF/AlienCodex.sol) | āœ… | [AlienCodeExploit](../test/EthernautCTF/AlienCodexExploit.t.sol.txt) | Take advantage of an array underflow vulnerability in the contract, to allow the attacker to manipulate the contract's storage. | -| 20 | [Denial](../src/EthernautCTF/Denial.sol) | āœ… | [DenialExploit](../test/EthernautCTF/DenialExploit.t.sol) | - Always set the amount of gas when using a low-level call. It will prevent the external contract to consume all the gas.
- Check the return value of low-level calls, especially when the address is controlled by someone else. | -| 21 | [Shop](../src/EthernautCTF/Shop.sol) | āœ… | [ShopExploit](../test/EthernautCTF/ShopExploit.t.sol) | - When calling an external contract, always check the returned value before using it!
- This challenge is very similar to challenge 11. | -| 22 | [Dex](../src/EthernautCTF/Dex.sol) | āœ… | [DexExploit](../test/EthernautCTF/DexExploit.t.sol) | The contract uses a division operation to compute the swap amount which can be exploited because of a precision loss. Indeed, Solidity does not support floating points. | -| 23 | [DexTwo](../src/EthernautCTF/DexTwo.sol) | āœ… | [DexTwoExploit](../test/EthernautCTF/DexTwoExploit.t.sol) | The `swap` method does not check the addresses of the ERC20 tokens. This is a very bad practice since an exploiter can manipulate the balances of those tokens. Indeed, the swap amount is computed based on the token balances, so anyone can drain the tokens of the contract. | -| 24 | [PuzzleWallet](../src/EthernautCTF/PuzzleWallet.sol) | āœ… | [PuzzleWalletExploit](../test/EthernautCTF/PuzzleWalletExploit.t.sol) | When writing a Proxy contract, and more generally any contract that uses `delegatecall`, always make sure that the sensible storage values are not colliding with other values. The storage layout should be identical, for those values, on both the proxy and the implementation contracts. | -| 25 | [Motorbike](../src/EthernautCTF/Motorbike.sol) | āŒ | | | -| 26 | [DoubleEntry](../src/EthernautCTF/DoubleEntry.sol) | āŒ | | | -| 27 | GoodSamaritan | āŒ | | | -| 28 | [GatekeeperThree](../src/EthernautCTF/GatekeeperThree.sol) | āœ… | [GatekeeperThreeExploit](../test/EthernautCTF/GatekeeperThreeExploit.t.sol) | - Make sure the `constructor` method is spelled properly.
- Do not use `tx.origin` to check the origin of the caller.
- Private variables are not private on a public blockchain.
- It's easy to implement a contract that do not accepts ether. | -| 29 | [Switch](../src/EthernautCTF/Switch.sol) | āŒ | | | -| 30 | [HigherOrder](../src/EthernautCTF/HigherOrder.sol) | āœ… | [HigherOrderExploit](../test/EthernautCTF/HigherOrderExploit.t.sol) | Reading function parameters (or any other value) using `calldataload` is dangerous because it will always return a 32-byte value and not the expected type of the variable. | -| 31 | [Stake](../src/EthernautCTF/Stake.sol) | āœ… | [StakeExploit](../test/EthernautCTF/StakeExploit.t.sol) | The contract updates the balance of the user before making sure the tokens have been transfered to the contract. | - -(\*) I opened a [PR](https://github.com/OpenZeppelin/ethernaut/pull/756) to prevent cheating in challenge 16. diff --git a/docs/ethernaut-ctf/EthernautCTF.md b/docs/ethernaut-ctf/EthernautCTF.md new file mode 100644 index 0000000..95a5715 --- /dev/null +++ b/docs/ethernaut-ctf/EthernautCTF.md @@ -0,0 +1,45 @@ +# [EthernautCTF](https://ethernaut.openzeppelin.com/) + +![ethernaut](./ethernaut.png) + +| NĀ° | Contract | Done | Exploit PoC | Description | +| --- | ------------------------------------------------------------- | :--: | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 01 | [Fallback](../../src/EthernautCTF/Fallback.sol) | āœ… | [FallbackExploit](../../test/EthernautCTF/FallbackExploit.t.sol) | Call a contract with some value to target the `receive` method. | +| 02 | [Fallout](../../src/EthernautCTF/Fallout.sol) | āœ… | [FalloutExploit](../../test/EthernautCTF/FalloutExploit.t.sol) | Typo in the constructor name. | +| 03 | [CoinFlip](../../src/EthernautCTF/CoinFlip.sol) | āœ… | [CoinFlipExploit](../../test/EthernautCTF/CoinFlipExploit.t.sol) | The contract relies on `block.number` to generate a random value. | +| 04 | [Telephone](../../src/EthernautCTF/Telephone.sol) | āœ… | [TelephoneExploit](../../test/EthernautCTF/TelephoneExploit.t.sol) | Use a helper contract to make sure `tx.origin` and `msg.sender` are different. | +| 05 | [Token](../../src/EthernautCTF/Token.sol) | āœ… | [TokenExploit](../../test/EthernautCTF/TokenExploit.t.sol) | Exploit overflows and underflows of the `0.6.0` solidity compiler. | +| 06 | [Delegation](../../src/EthernautCTF/Delegation.sol) | āŒ | [DelegationExploit](../../test/EthernautCTF/DelegationExploit.t.sol) | Make use of the `delegatecall` to overwrite the storage of the main contract. | +| 07 | [Force](../../src/EthernautCTF/Force.sol) | āœ… | [ForceExploit](../../test/EthernautCTF/ForceExploit.t.sol) | Create a contract, fund it with some ether and use the `selfdestruct` method to send the contract balance to any other contract (e.g. a contract without any implementation). | +| 08 | [Vault](../../src/EthernautCTF/Vault.sol) | āœ… | [VaultExploit](../../test/EthernautCTF/VaultExploit.t.sol) | Read the password from the contract storage. | +| 09 | [King](../../src/EthernautCTF/King.sol) | āœ… | [KingExploit](../../test/EthernautCTF/KingExploit.t.sol) | Implement a helper contract that reverts when receiving ether. | +| 10 | [Reentrance](../../src/EthernautCTF/Reentrance.sol) | āœ… | [ReentranceExploit](../../test/EthernautCTF/ReentranceExploit.t.sol) | Perform all state changes before making external calls to avoid re-entrancy exploits. | +| 11 | [Elevator](../../src/EthernautCTF/Elevator.sol) | āœ… | [ElevatorExploit](../../test/EthernautCTF/ElevatorExploit.t.sol) | When calling an external contract, always check the returned value before using it! | +| 12 | [Privacy](../../src/EthernautCTF/Privacy.sol) | āœ… | [PrivacyExploit](../../test/EthernautCTF/PrivacyExploit.t.sol) | Read the key from the contract storage | +| 13 | [GatekeeperOne](../../src/EthernautCTF/GatekeeperOne.sol) | āœ… | [GatekeeperOneExploit](../../test/EthernautCTF/GatekeeperOneExploit.t.sol) | - Estimate the amount of gas a contract call would take using `gasleft` and binary search (dichotomy).
- Another method is to use a `while` loop and to consume the gas tiny bits by tiny bits until the call succeeds.
- Perform operations using bit masks. | +| 14 | [GatekeeperTwo](../../src/EthernautCTF/GatekeeperTwo.sol) | āœ… | [GatekeeperTwoExploit](../../test/EthernautCTF/GatekeeperTwoExploit.t.sol) | - Create a contract that has a size equal to zero by putting all the logic inside the constructor. Indeed, a contract does not have source code available during construction.
- Solidity does not support bitwise negation, but a simple way to perform the operation is to use the XOR operation (`^`) with `0xff` (ones). | +| 15 | [NaughtCoin](../../src/EthernautCTF/NaughtCoin.sol) | āœ… | [NaughtCoinExploit](../../test/EthernautCTF/NaughtCoinExploit.t.sol) | Use the ERC20 `allowance` and `transferFrom` methods to send tokens on behalf of a nother address. | +| 16 | [Preservation](../../src/EthernautCTF/Preservation.sol) (\*) | āœ… | [PreservationExploit](../../test/EthernautCTF/PreservationExploit.t.sol) | Make use of the `delegatecall` to overwrite the storage of the main contract. This time it involved a bit more creativity as it required to overwrite an address (20 bytes) using a uint256 (32 bytes). | +| 17 | [Recovery](../../src/EthernautCTF/Recovery.sol) | āœ… | [RecoveryExploit](../../test/EthernautCTF/RecoveryExploit.t.sol) | The address of an Ethereum contract is deterministically computed from the address of its creator (sender) and its nonce (how many transactions the creator has sent). The sender and nonce are RLP-encoded and then hashed with keccak256. For a Solidity implementation, check the exploit code. | +| 18 | [MagicNumber](../../src/EthernautCTF/MagicNumber.sol) | āœ… | [MagicNumberExploit](../../test/EthernautCTF/MagicNumberExploit.t.sol) | - Use raw bytecode to create the smallest possible contract.
- Learn about initialization code to be able to run any runtime code.
- Learn about `create` to create a contract from the initialization code. | +| 19 | [AlienCode](../../src/EthernautCTF/AlienCodex.sol) | āœ… | [AlienCodeExploit](../../test/EthernautCTF/AlienCodexExploit.t.sol.txt) | Take advantage of an array underflow vulnerability in the contract, to allow the attacker to manipulate the contract's storage. | +| 20 | [Denial](../../src/EthernautCTF/Denial.sol) | āœ… | [DenialExploit](../../test/EthernautCTF/DenialExploit.t.sol) | - Always set the amount of gas when using a low-level call. It will prevent the external contract to consume all the gas.
- Check the return value of low-level calls, especially when the address is controlled by someone else. | +| 21 | [Shop](../../src/EthernautCTF/Shop.sol) | āœ… | [ShopExploit](../../test/EthernautCTF/ShopExploit.t.sol) | - When calling an external contract, always check the returned value before using it!
- This challenge is very similar to challenge 11. | +| 22 | [Dex](../../src/EthernautCTF/Dex.sol) | āœ… | [DexExploit](../../test/EthernautCTF/DexExploit.t.sol) | The contract uses a division operation to compute the swap amount which can be exploited because of a precision loss. Indeed, Solidity does not support floating points. | +| 23 | [DexTwo](../../src/EthernautCTF/DexTwo.sol) | āœ… | [DexTwoExploit](../../test/EthernautCTF/DexTwoExploit.t.sol) | The `swap` method does not check the addresses of the ERC20 tokens. This is a very bad practice since an exploiter can manipulate the balances of those tokens. Indeed, the swap amount is computed based on the token balances, so anyone can drain the tokens of the contract. | +| 24 | [PuzzleWallet](../../src/EthernautCTF/PuzzleWallet.sol) | āœ… | [PuzzleWalletExploit](../../test/EthernautCTF/PuzzleWalletExploit.t.sol) | When writing a Proxy contract, and more generally any contract that uses `delegatecall`, always make sure that the sensible storage values are not colliding with other values. The storage layout should be identical, for those values, on both the proxy and the implementation contracts. | +| 25 | [Motorbike](../../src/EthernautCTF/Motorbike.sol) | āŒ | | | +| 26 | [DoubleEntry](../../src/EthernautCTF/DoubleEntry.sol) | āœ… | [DoubleEntryExploit](../../test/EthernautCTF/DoubleEntryExploit.t.sol) | - When delegating calls from a deprecated token to another token (or any other contract), avoid sending new tokens in place of old tokens.
- This level is made of a lot of different contracts, you can find an architecture diagram [below](#level-26). | +| 27 | GoodSamaritan | āŒ | | | +| 28 | [GatekeeperThree](../../src/EthernautCTF/GatekeeperThree.sol) | āœ… | [GatekeeperThreeExploit](../../test/EthernautCTF/GatekeeperThreeExploit.t.sol) | - Make sure the `constructor` method is spelled properly.
- Do not use `tx.origin` to check the origin of the caller.
- Private variables are not private on a public blockchain.
- It's easy to implement a contract that do not accepts ether. | +| 29 | [Switch](../../src/EthernautCTF/Switch.sol) | āŒ | | | +| 30 | [HigherOrder](../../src/EthernautCTF/HigherOrder.sol) | āœ… | [HigherOrderExploit](../../test/EthernautCTF/HigherOrderExploit.t.sol) | Reading function parameters (or any other value) using `calldataload` is dangerous because it will always return a 32-byte value and not the expected type of the variable. | +| 31 | [Stake](../../src/EthernautCTF/Stake.sol) | āœ… | [StakeExploit](../../test/EthernautCTF/StakeExploit.t.sol) | The contract updates the balance of the user before making sure the tokens have been transfered to the contract. | + +(\*) I opened a [PR](https://github.com/OpenZeppelin/ethernaut/pull/756) to prevent cheating in challenge 16. + +## Architecture Diagrams + +### Level 26 + +![Level 26 Architecture Diagram](./architecture-diagrams/ethernaut-lvl-26.png) diff --git a/docs/ethernaut-ctf/architecture-diagrams/ethernaut-lvl-26.excalidraw b/docs/ethernaut-ctf/architecture-diagrams/ethernaut-lvl-26.excalidraw new file mode 100644 index 0000000..ce14582 --- /dev/null +++ b/docs/ethernaut-ctf/architecture-diagrams/ethernaut-lvl-26.excalidraw @@ -0,0 +1,2733 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "https://excalidraw.com", + "elements": [ + { + "type": "rectangle", + "version": 593, + "versionNonce": 1632213552, + "index": "bfW", + "isDeleted": false, + "id": "rP_idqv8cApLA9hD977zM", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3907.7214756767908, + "y": 5710.364990052536, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 851.7862726772837, + "height": 105.82467122203292, + "seed": 1141920816, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "48_uCXQmmYIb23V-NmOJI" + }, + { + "id": "z_SzqkwP_77zPg7OyYMMO", + "type": "arrow" + } + ], + "updated": 1728377800529, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 568, + "versionNonce": 182770224, + "index": "bfX", + "isDeleted": false, + "id": "48_uCXQmmYIb23V-NmOJI", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3604.4123008249653, + "y": 5745.777325663553, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 245.1679229736328, + "height": 35, + "seed": 290868784, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800531, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "šŸ” CryptoVault SC", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "rP_idqv8cApLA9hD977zM", + "originalText": "šŸ” CryptoVault SC", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 986, + "versionNonce": 58119216, + "index": "bfY", + "isDeleted": false, + "id": "sHR13uObvzhSDjq_krx6a", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3895.460042610329, + "y": 5845.5281208663255, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 924.0557250976562, + "height": 315, + "seed": 2009925680, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "rk3r3iu9sOyP4IqJaPRMV", + "type": "arrow" + } + ], + "updated": 1728377800529, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "- constructor(address): set the tokens recipient\n\n- setUnderlying(address): set the underlying token\n=> This function is public but it can only be called once.\n\n- sweepToken(IERC20): call the transfer method to send the\nfull IERC20 token (!= underlying) balance of the CryptoVault SC to\n the tokens recipient.\n=> This function can be called by anyone.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- constructor(address): set the tokens recipient\n\n- setUnderlying(address): set the underlying token\n=> This function is public but it can only be called once.\n\n- sweepToken(IERC20): call the transfer method to send the\nfull IERC20 token (!= underlying) balance of the CryptoVault SC to\n the tokens recipient.\n=> This function can be called by anyone.", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 780, + "versionNonce": 859031248, + "index": "bfZ", + "isDeleted": false, + "id": "Xs_5j2iXwJr6ocM2AJ23t", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2613.3146081485183, + "y": 5804.932515608616, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 362.6862283122974, + "height": 105.82467122203292, + "seed": 324829744, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "YblOkvWU42Ux83gg6xu6l" + }, + { + "id": "rk3r3iu9sOyP4IqJaPRMV", + "type": "arrow" + } + ], + "updated": 1728377800529, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 805, + "versionNonce": 1470764080, + "index": "bfa", + "isDeleted": false, + "id": "YblOkvWU42Ux83gg6xu6l", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2568.1074345441275, + "y": 5822.844851219633, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 272.2718811035156, + "height": 70, + "seed": 1122810928, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "šŸ“® Tokens Recipient\nAddress", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Xs_5j2iXwJr6ocM2AJ23t", + "originalText": "šŸ“® Tokens Recipient\nAddress", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1156, + "versionNonce": 1018326224, + "index": "bfb", + "isDeleted": false, + "id": "rk3r3iu9sOyP4IqJaPRMV", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2970.4043175126726, + "y": 5860.672867216299, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 335.24803476893885, + "height": 0.34968444702280976, + "seed": 1214625328, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": { + "elementId": "sHR13uObvzhSDjq_krx6a", + "focus": -0.9041428098993307, + "gap": 1, + "fixedPoint": null + }, + "endBinding": { + "elementId": "Xs_5j2iXwJr6ocM2AJ23t", + "focus": -0.06383314101221657, + "gap": 21.84167459521632, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 335.24803476893885, + 0.34968444702280976 + ] + ], + "elbowed": false + }, + { + "type": "arrow", + "version": 870, + "versionNonce": 1871939632, + "index": "bfc", + "isDeleted": false, + "id": "ZWwuiMQzQi0_sV_BvZ8da", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3194.7732188132586, + "y": 5932.560159384642, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 931.5804564093892, + "height": 621.3785618823913, + "seed": 1756327984, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "JTHbs6ZMBme709i2nin2O", + "focus": -0.8830908699254634, + "gap": 10.50590233862522, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 680.9626394192851, + 136.26985242623323 + ], + [ + 931.5804564093892, + 621.3785618823913 + ] + ], + "elbowed": false + }, + { + "type": "rectangle", + "version": 946, + "versionNonce": 728519376, + "index": "bfd", + "isDeleted": false, + "id": "-R0pzbF_OyRdD_kjKs_Bo", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3936.687454598129, + "y": 6255.349361627788, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 922.1432738286609, + "height": 105.82467122203292, + "seed": 196209200, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "PF7Y36TEH8YjjCj9zSYJ2" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 954, + "versionNonce": 196611632, + "index": "bfe", + "isDeleted": false, + "id": "PF7Y36TEH8YjjCj9zSYJ2", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3658.7637669025485, + "y": 6290.761697238805, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 366.2958984375, + "height": 35, + "seed": 1549083696, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "šŸŖ™ Legacy Token SC (LGT)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "-R0pzbF_OyRdD_kjKs_Bo", + "originalText": "šŸŖ™ Legacy Token SC (LGT)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 1670, + "versionNonce": 905541840, + "index": "bff", + "isDeleted": false, + "id": "3IepVvGqeStvBVs9KuIW8", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3924.1985634980856, + "y": 6388.440746600132, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1452.10737991333, + "height": 560, + "seed": 1753292336, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "PK1nma1rY9IJHYswjdT5t", + "type": "arrow" + }, + { + "id": "-M5379M-9S2K50ICJE_1x", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "- ERC20 + Ownable contract\n\n- mint(address, amount): transfer an amount of tokens\nfrom address 0x0 to the specified address (!= 0x0).\n=> This function can only be called by the deployer.\n\n- delegateToNewContract(DelegateERC20): set the delegate contract.\n=> This function can only be called by the deployer.\n=> The delegate contract should be the DoubleEntryPoint Token SC (DET).\n\n- transfer(address, value): if the delegate is not set, use ERC20\ntransfer, otherwise, use DelegateERC20 transfer.\n - ERC20 transfer: move an amount of LGT token from the caller address to the specified address.\n - DelegateERC20 transfer: call to the DET, set by the owner.\n\n=> This function can be called by anyone.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- ERC20 + Ownable contract\n\n- mint(address, amount): transfer an amount of tokens\nfrom address 0x0 to the specified address (!= 0x0).\n=> This function can only be called by the deployer.\n\n- delegateToNewContract(DelegateERC20): set the delegate contract.\n=> This function can only be called by the deployer.\n=> The delegate contract should be the DoubleEntryPoint Token SC (DET).\n\n- transfer(address, value): if the delegate is not set, use ERC20\ntransfer, otherwise, use DelegateERC20 transfer.\n - ERC20 transfer: move an amount of LGT token from the caller address to the specified address.\n - DelegateERC20 transfer: call to the DET, set by the owner.\n\n=> This function can be called by anyone.", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 973, + "versionNonce": 1527987248, + "index": "bfg", + "isDeleted": false, + "id": "PK1nma1rY9IJHYswjdT5t", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3954.640178319646, + "y": 6487.936452163622, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 151.22551673325802, + "height": 664.4783853935267, + "seed": 1092474928, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "LzRFJRCkRq_g3oC9iH8q5" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": { + "elementId": "3IepVvGqeStvBVs9KuIW8", + "focus": -0.6402781493179066, + "gap": 30.441614821561416, + "fixedPoint": null + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -131.46613901821343, + -162.19510703228502 + ], + [ + 19.759377715044593, + -664.4783853935267 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 43, + "versionNonce": 1073137712, + "index": "bfh", + "isDeleted": false, + "id": "LzRFJRCkRq_g3oC9iH8q5", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -4180.970288869251, + "y": 6308.241345131337, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 189.7279430627823, + "height": 35, + "seed": 437473840, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "mint 100 LGT", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "PK1nma1rY9IJHYswjdT5t", + "originalText": "mint 100 LGT", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 172, + "versionNonce": 398210608, + "index": "bfi", + "isDeleted": false, + "id": "zsinc7-7Lpaf2tpniFqIN", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2943.6739478046757, + "y": 6616.024883389524, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 652.7820129252268, + "height": 0, + "seed": 1783204912, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 652.7820129252268, + 0 + ] + ], + "elbowed": false + }, + { + "type": "arrow", + "version": 1133, + "versionNonce": 2045609168, + "index": "bfj", + "isDeleted": false, + "id": "7hPKcr7ZyAwufE6Krjex9", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2994.1730920831587, + "y": 6865.716759782468, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 709.195553601638, + "height": 156.89606092496524, + "seed": 195593776, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "wkt6pk5IssL-iOR_4AKdi" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 397.4092355119283, + 94.06008062164528 + ], + [ + 709.195553601638, + 156.89606092496524 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 137, + "versionNonce": 810435120, + "index": "bfk", + "isDeleted": false, + "id": "wkt6pk5IssL-iOR_4AKdi", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2827.6517655067773, + "y": 6924.7768404041135, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 461.77581787109375, + "height": 70, + "seed": 1528315952, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "2. delegateTransfer(CryptoVault, \namount, tokensRecipient)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "7hPKcr7ZyAwufE6Krjex9", + "originalText": "2. delegateTransfer(CryptoVault, amount, tokensRecipient)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 1063, + "versionNonce": 1099197136, + "index": "bfl", + "isDeleted": false, + "id": "JTHbs6ZMBme709i2nin2O", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2260.193106319578, + "y": 6564.444623605657, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 922.1432738286609, + "height": 105.82467122203292, + "seed": 1484831280, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "ln2DNFnrqmAu1a5YHrzhj" + }, + { + "id": "ZWwuiMQzQi0_sV_BvZ8da", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1088, + "versionNonce": 1537637424, + "index": "bfm", + "isDeleted": false, + "id": "ln2DNFnrqmAu1a5YHrzhj", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2022.7573946982163, + "y": 6599.856959216674, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 447.2718505859375, + "height": 35, + "seed": 1187640368, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "šŸŖ™ DoubleEntry Token SC (DET)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "JTHbs6ZMBme709i2nin2O", + "originalText": "šŸŖ™ DoubleEntry Token SC (DET)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 1562, + "versionNonce": 21926096, + "index": "bfn", + "isDeleted": false, + "id": "UAzBqkfKfLj3lh0La6sgE", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2238.594406392961, + "y": 6696.101770606712, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1053.0797294974327, + "height": 490, + "seed": 1272704560, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "soJgNdyqHU80aQ9kSKC4t", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "- ERC20 + DelegateERC20 + Ownable\n\n- constructor(address, address, address, address): set many addresses\nsuch as:\n - the Legacy Token address, also called delegated from\n - the CryptoVault address\n - the Forta address\n - the player address\n\n- delegateTransfer(address, value, address): move an amount of DET tokens\nfrom the first address (!= 0x0) to the second address (!= 0x0).\n=> The function always returns true.\n=> The function can only be called by the legacy token.\n=> Let's avoid the fortaNotify modifier for now.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- ERC20 + DelegateERC20 + Ownable\n\n- constructor(address, address, address, address): set many addresses\nsuch as:\n - the Legacy Token address, also called delegated from\n - the CryptoVault address\n - the Forta address\n - the player address\n\n- delegateTransfer(address, value, address): move an amount of DET tokens\nfrom the first address (!= 0x0) to the second address (!= 0x0).\n=> The function always returns true.\n=> The function can only be called by the legacy token.\n=> Let's avoid the fortaNotify modifier for now.", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 426, + "versionNonce": 1832625200, + "index": "bfo", + "isDeleted": false, + "id": "z_SzqkwP_77zPg7OyYMMO", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2275.5406340550153, + "y": 6786.975449405452, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 765.0006705280566, + "height": 986.4653479665867, + "seed": 278833200, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "GWphkDuh6asF4aQBmUDQO" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "rP_idqv8cApLA9hD977zM", + "focus": -0.8832506943093104, + "gap": 15.39389841643515, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -765.0006705280566, + -986.4653479665867 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 20, + "versionNonce": 602371632, + "index": "bfp", + "isDeleted": false, + "id": "GWphkDuh6asF4aQBmUDQO", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2755.2009384700636, + "y": 6276.242775422159, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 194.3199383020401, + "height": 35, + "seed": 1627005488, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "mint 100 DET", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "z_SzqkwP_77zPg7OyYMMO", + "originalText": "mint 100 DET", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 464, + "versionNonce": 412589616, + "index": "bfq", + "isDeleted": false, + "id": "-M5379M-9S2K50ICJE_1x", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2274.398005532131, + "y": 6873.64755155738, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 191.93454888185033, + "height": 142.53493975903893, + "seed": 1272339504, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "3IepVvGqeStvBVs9KuIW8", + "focus": -0.5869115883517928, + "gap": 5.758629170775748, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -191.93454888185033, + -142.53493975903893 + ] + ], + "elbowed": false + }, + { + "type": "rectangle", + "version": 1742, + "versionNonce": 350002384, + "index": "bfr", + "isDeleted": false, + "id": "y3EusiMCvePFDZwcdnTKM", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -128.4738323017309, + "y": 6256.851678660103, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 826.888206455522, + "height": 105.82467122203292, + "seed": 1125865008, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "SLb2N2OgdvQOoTpewwbNM" + }, + { + "id": "vpUuKCd8JTZwE-Z3MzbYv", + "type": "arrow" + }, + { + "id": "bvMsV3jd3FKGP5rK497__", + "type": "arrow" + }, + { + "id": "hEsL9la-mGAxGH_bjcIve", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1763, + "versionNonce": 1845047344, + "index": "bfs", + "isDeleted": false, + "id": "SLb2N2OgdvQOoTpewwbNM", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 203.02829247144024, + "y": 6292.26401427112, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 163.8839569091797, + "height": 35, + "seed": 1001459760, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "šŸšØ Forta SC", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "y3EusiMCvePFDZwcdnTKM", + "originalText": "šŸšØ Forta SC", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1409, + "versionNonce": 1590723280, + "index": "bft", + "isDeleted": false, + "id": "vpUuKCd8JTZwE-Z3MzbYv", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1916.018863185941, + "y": 6921.015368277094, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 1762.8084922978624, + "height": 591.4462649085408, + "seed": 1300204080, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "y3EusiMCvePFDZwcdnTKM", + "focus": 0.7883541915847945, + "gap": 24.736538586347706, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 846.9437184560802, + -89.46184631909455 + ], + [ + 1762.8084922978624, + -591.4462649085408 + ] + ], + "elbowed": false + }, + { + "type": "rectangle", + "version": 1067, + "versionNonce": 378130992, + "index": "bfu", + "isDeleted": false, + "id": "653H4gbSch6sH8TUDU8Qz", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1001.5594216630207, + "y": 6883.158170411934, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 362.6862283122974, + "height": 105.82467122203292, + "seed": 1399768112, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "pcw_IPYPLcEBnr4AFN_7y" + }, + { + "id": "soJgNdyqHU80aQ9kSKC4t", + "type": "arrow" + }, + { + "id": "egtIWLpSp04qKJGZMrjxx", + "type": "arrow" + }, + { + "id": "bvMsV3jd3FKGP5rK497__", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 1086, + "versionNonce": 1103342128, + "index": "bfv", + "isDeleted": false, + "id": "pcw_IPYPLcEBnr4AFN_7y", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -881.8682888606318, + "y": 6918.570506022951, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 123.30396270751953, + "height": 35, + "seed": 191282736, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "šŸ‘© Player", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "653H4gbSch6sH8TUDU8Qz", + "originalText": "šŸ‘© Player", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1087, + "versionNonce": 334498864, + "index": "bfw", + "isDeleted": false, + "id": "soJgNdyqHU80aQ9kSKC4t", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -1918.394640939082, + "y": 6961.716022201597, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 894.4588205753917, + "height": 32.59229556344599, + "seed": 911177776, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "653H4gbSch6sH8TUDU8Qz", + "focus": 0.13128846618807297, + "gap": 22.376398700669597, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 447.774506259223, + -32.59229556344599 + ], + [ + 894.4588205753917, + -32.59229556344599 + ] + ], + "elbowed": false + }, + { + "type": "diamond", + "version": 748, + "versionNonce": 1812598480, + "index": "bfx", + "isDeleted": false, + "id": "Isy5gd5p9c0ov2PVBuBYZ", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3820.859295240317, + "y": 5515.586803405635, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 196.6017280359921, + "height": 160, + "seed": 940634672, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "htd7HF4WrhhjgLjQp92cq" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 684, + "versionNonce": 1781457968, + "index": "bfy", + "isDeleted": false, + "id": "htd7HF4WrhhjgLjQp92cq", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3753.340858283418, + "y": 5560.586803405635, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 61.263990104198456, + "height": 70, + "seed": 932391984, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "100 \nLGT", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Isy5gd5p9c0ov2PVBuBYZ", + "originalText": "100 LGT", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "diamond", + "version": 2396, + "versionNonce": 327136464, + "index": "bfz", + "isDeleted": false, + "id": "GjJO5cHzO7-b28_9q1NjK", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2235.18655336989, + "y": 5780.099513315659, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 196.6017280359921, + "height": 160, + "seed": 1052649008, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "FNXHZjm1dFzNB2E8gmL-u" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2343, + "versionNonce": 354606640, + "index": "bg0", + "isDeleted": false, + "id": "FNXHZjm1dFzNB2E8gmL-u", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2169.964115939969, + "y": 5825.099513315659, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 65.85598915815353, + "height": 70, + "seed": 1517936688, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "100\nDET", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "GjJO5cHzO7-b28_9q1NjK", + "originalText": "100\nDET", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 292, + "versionNonce": 1230019280, + "index": "bg1", + "isDeleted": false, + "id": "hDGqtfO_8cd2Qnc01MK6Q", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3911.971190366058, + "y": 6007.353649439452, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 915.0560414039398, + "height": 164.6631929848473, + "seed": 984504880, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "_N8dT6gMoGX2CcHdBeTHk", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 557, + "versionNonce": 632764976, + "index": "bg2", + "isDeleted": false, + "id": "2IX7loakYOAJZBIZTi9Xk", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3954.712465213911, + "y": 6724.75764507297, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 1442.5810345024595, + "height": 158.04323261644205, + "seed": 1380160560, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "_N8dT6gMoGX2CcHdBeTHk", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "rectangle", + "version": 745, + "versionNonce": 538230992, + "index": "bg3", + "isDeleted": false, + "id": "6hRIenb11AUHcqn3dX2Nw", + "fillStyle": "hachure", + "strokeWidth": 4, + "strokeStyle": "dashed", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2258.7155327538885, + "y": 6994.265290062831, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 1092.5672516169193, + "height": 214.50890574762212, + "seed": 1998687792, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "id": "KuGV3wTaAEHe7KNskJ909", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "arrow", + "version": 858, + "versionNonce": 691041328, + "index": "bg4", + "isDeleted": false, + "id": "_N8dT6gMoGX2CcHdBeTHk", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2975.627723330529, + "y": 6163.281453379512, + "strokeColor": "#e03131", + "backgroundColor": "#fcc2d7", + "width": 248.0571569413055, + "height": 543.7248018648747, + "seed": 36718640, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "RFJS4RxnGGfoXY3ockCyv" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": { + "elementId": "hDGqtfO_8cd2Qnc01MK6Q", + "focus": -0.8093697242712792, + "gap": 21.287425631587666, + "fixedPoint": null + }, + "endBinding": { + "elementId": "2IX7loakYOAJZBIZTi9Xk", + "focus": 0.7141793833078073, + "gap": 17.751389828583797, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 175.00706878794426, + 226.17849315704416 + ], + [ + 248.0571569413055, + 543.7248018648747 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 130, + "versionNonce": 1938266160, + "index": "bg5", + "isDeleted": false, + "id": "RFJS4RxnGGfoXY3ockCyv", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -2938.5765871597723, + "y": 6371.959946536555, + "strokeColor": "#e03131", + "backgroundColor": "#fcc2d7", + "width": 275.911865234375, + "height": 35, + "seed": 1868022320, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "1. sweepToken(LGT)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "_N8dT6gMoGX2CcHdBeTHk", + "originalText": "1. sweepToken(LGT)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "diamond", + "version": 2494, + "versionNonce": 494038576, + "index": "bg6", + "isDeleted": false, + "id": "L3lmIIulY2CV_n_PBAvTu", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3605.311753035344, + "y": 5505.904303201241, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 196.6017280359921, + "height": 160, + "seed": 2105539632, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "YTzjJNGKjzEn1AVQH7GPI" + }, + { + "id": "T31d609h6m-hQSg9XYXg3", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 2441, + "versionNonce": 1497698864, + "index": "bg7", + "isDeleted": false, + "id": "YTzjJNGKjzEn1AVQH7GPI", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3540.0893156054226, + "y": 5550.904303201241, + "strokeColor": "#2f9e44", + "backgroundColor": "transparent", + "width": 65.85598915815353, + "height": 70, + "seed": 1318123056, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "100\nDET", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "L3lmIIulY2CV_n_PBAvTu", + "originalText": "100\nDET", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 362, + "versionNonce": 1801791536, + "index": "bg8", + "isDeleted": false, + "id": "T31d609h6m-hQSg9XYXg3", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3394.1220752243344, + "y": 5588.197765652512, + "strokeColor": "#f08c00", + "backgroundColor": "#fcc2d7", + "width": 1183.8676424090304, + "height": 201.19504130762198, + "seed": 124956720, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "8pfb189-t9YLIHq4CkbV6" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": { + "elementId": "L3lmIIulY2CV_n_PBAvTu", + "focus": -0.020563469583827026, + "gap": 10.986933280538473, + "fixedPoint": null + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 608.0998495699923, + 21.215791999095927 + ], + [ + 1183.8676424090304, + 201.19504130762198 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 151, + "versionNonce": 1564691504, + "index": "bg9", + "isDeleted": false, + "id": "8pfb189-t9YLIHq4CkbV6", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3056.0401699902795, + "y": 5556.913557651608, + "strokeColor": "#f08c00", + "backgroundColor": "#fcc2d7", + "width": 540.035888671875, + "height": 105, + "seed": 1879910960, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "3. move 100 DET (and not LGT)\nfrom the CryptoVault SC to the tokens\nrecipient address (= drain)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "T31d609h6m-hQSg9XYXg3", + "originalText": "3. move 100 DET (and not LGT)\nfrom the CryptoVault SC to the tokens\nrecipient address (= drain)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 611, + "versionNonce": 519086640, + "index": "bgA", + "isDeleted": false, + "id": "Gja0MX1o2W22Iqs2FjoKS", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -120.32875695582425, + "y": 6875.484569814227, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 819.1057401575265, + "height": 110.58917406609544, + "seed": 323511344, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [ + { + "type": "text", + "id": "NukRE_cYcKUtTg8VYFWKS" + }, + { + "id": "hEsL9la-mGAxGH_bjcIve", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "text", + "version": 565, + "versionNonce": 2037007920, + "index": "bgB", + "isDeleted": false, + "id": "NukRE_cYcKUtTg8VYFWKS", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 169.55215419959916, + "y": 6913.279156847275, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 239.3439178466797, + "height": 35, + "seed": 1594646064, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "šŸ¤– Detection Bot", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "Gja0MX1o2W22Iqs2FjoKS", + "originalText": "šŸ¤– Detection Bot", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 2209, + "versionNonce": 117234736, + "index": "bgC", + "isDeleted": false, + "id": "3P2Rk-Wgk8NtiKf651Lq8", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -106.48030744502921, + "y": 7008.64895970838, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 722.3436889648438, + "height": 280, + "seed": 1145116720, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "- handleTransaction(address, bytes calldata): check\nif the call should be authorized or not.\n\nTo prevent the CryptoVault DET from being drained:\n1. check that the signature is different from the\ndelegaTransfer(address, value, address) signature\n2. check that the third parameter, origSender is\ndifferent from the CryptoVault SC address", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- handleTransaction(address, bytes calldata): check\nif the call should be authorized or not.\n\nTo prevent the CryptoVault DET from being drained:\n1. check that the signature is different from the\ndelegaTransfer(address, value, address) signature\n2. check that the third parameter, origSender is\ndifferent from the CryptoVault SC address", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1354, + "versionNonce": 418528976, + "index": "bgD", + "isDeleted": false, + "id": "smeNTZ99n5TQtWSEVxiNO", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 954.1626490135359, + "y": 6559.06026448125, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 220.40207503164584, + "height": 58.015252367355515, + "seed": 1456378416, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "WbbN8EBYmvQXL7_4X6IdV" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 220.40207503164584, + 58.015252367355515 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 106, + "versionNonce": 715259952, + "index": "bgE", + "isDeleted": false, + "id": "WbbN8EBYmvQXL7_4X6IdV", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 915.529763189515, + "y": 6535.567890664927, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 297.6678466796875, + "height": 105, + "seed": 1838992432, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "3. raiseAlert(address)\nwhen the call\nlooks suspicious", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "smeNTZ99n5TQtWSEVxiNO", + "originalText": "3. raiseAlert(address)\nwhen the call\nlooks suspicious", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 1457, + "versionNonce": 1407313104, + "index": "bgF", + "isDeleted": false, + "id": "KuGV3wTaAEHe7KNskJ909", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -159.26335593782005, + "y": 6304.668303829374, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 1109.4048748361379, + "height": 673.8228641410178, + "seed": 1116980784, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "srFyi37CkJENDaRdLrd15" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": { + "elementId": "6hRIenb11AUHcqn3dX2Nw", + "focus": 0.5338616167323716, + "gap": 15.774122092437665, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + -668.996568378529, + 151.87854880798022 + ], + [ + -1109.4048748361379, + 673.8228641410178 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 46, + "versionNonce": 282229296, + "index": "bgG", + "isDeleted": false, + "id": "srFyi37CkJENDaRdLrd15", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -965.5298828124428, + "y": 6421.546852637354, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 274.5399169921875, + "height": 70, + "seed": 312180784, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800532, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "4. revert to prevent\nan exploit", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "KuGV3wTaAEHe7KNskJ909", + "originalText": "4. revert to prevent\nan exploit", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 440, + "versionNonce": 502057680, + "index": "bgH", + "isDeleted": false, + "id": "nV60sHWbj3aybFzzgjh3w", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -678.2023575773392, + "y": 5554.0906121484495, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 191.81323228077554, + "height": 0, + "seed": 1033632304, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 191.81323228077554, + 0 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 518, + "versionNonce": 473303600, + "index": "bgI", + "isDeleted": false, + "id": "ZsUvxetrUZtnVyJUxMAew", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -460.7346698770307, + "y": 5538.180745075471, + "strokeColor": "#e03131", + "backgroundColor": "transparent", + "width": 96.79592895507812, + "height": 35, + "seed": 1719481392, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "Exploit", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Exploit", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 401, + "versionNonce": 1907028176, + "index": "bgJ", + "isDeleted": false, + "id": "EtAzT5GeqGlC32zXqFQrk", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -675.6163884710522, + "y": 5627.778417539086, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 192.32221667629938, + "height": 0, + "seed": 321334832, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 192.32221667629938, + 0 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 678, + "versionNonce": 123028528, + "index": "bgK", + "isDeleted": false, + "id": "svWJfn36lXruPs-BFB7Ps", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -458.1931476735408, + "y": 5607.405118532099, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 291.36783742904663, + "height": 35, + "seed": 1283582000, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "Result of the exploit", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Result of the exploit", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 461, + "versionNonce": 917788368, + "index": "bgL", + "isDeleted": false, + "id": "KjK6fTeK3fba1gUz5Ky32", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -668.635302696036, + "y": 5703.784816144704, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 192.32221667629938, + "height": 0, + "seed": 1047880240, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 192.32221667629938, + 0 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 627, + "versionNonce": 2122137136, + "index": "bgM", + "isDeleted": false, + "id": "1i8l9LC-pPBBufqSmaVLZ", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -451.4860468486586, + "y": 5689.942684864258, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 426.1038134098053, + "height": 35, + "seed": 1474927664, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "Prevent the exploit with Forta", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Prevent the exploit with Forta", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "text", + "version": 373, + "versionNonce": 564198608, + "index": "bgN", + "isDeleted": false, + "id": "Me39P32xb8Kwi2fDP_1sO", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -635.3984117989539, + "y": 5457.736582305544, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 92.48396301269531, + "height": 35, + "seed": 1791399472, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "Legend", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "Legend", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "rectangle", + "version": 456, + "versionNonce": 43382832, + "index": "bgO", + "isDeleted": false, + "id": "bFNMEoOKDSW0DrKaoA9rd", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "dotted", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -715.3335901733562, + "y": 5433.06725797412, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 750.6452608627119, + "height": 338.2119213996739, + "seed": 1274302512, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 3 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false + }, + { + "type": "line", + "version": 201, + "versionNonce": 336126672, + "index": "bgP", + "isDeleted": false, + "id": "TulA22p8OcJy1sVyqekRt", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3579.9182708371472, + "y": 5647.393042028003, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 145.2329506646347, + "height": 119.78373088845683, + "seed": 1935329840, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 145.2329506646347, + -119.78373088845683 + ] + ] + }, + { + "type": "line", + "version": 181, + "versionNonce": 931623472, + "index": "bgQ", + "isDeleted": false, + "id": "un7KZn4Fo-mkYbfHM4Zng", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -3595.384828920477, + "y": 5513.185350124466, + "strokeColor": "#f08c00", + "backgroundColor": "transparent", + "width": 190.66391267798053, + "height": 138.78855146324804, + "seed": 467797040, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": null, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": null, + "points": [ + [ + 0, + 0 + ], + [ + 190.66391267798053, + 138.78855146324804 + ] + ] + }, + { + "type": "text", + "version": 2761, + "versionNonce": 2089027792, + "index": "bgR", + "isDeleted": false, + "id": "Y9sLVsHO4ucOWhN6ScPvb", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -120.52993962157052, + "y": 6394.425932053266, + "strokeColor": "#1e1e1e", + "backgroundColor": "transparent", + "width": 833.979692876339, + "height": 280, + "seed": 1668415024, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [ + { + "id": "vpUuKCd8JTZwE-Z3MzbYv", + "type": "arrow" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "- setDetectionBot(address): assign a bot to the msg.sender\n\n- notify(address, bytes calldata): call the handleTransaction\nmethod of the bot of the user (first address) with the\nparameters: address and bytes calldata.\n\n- raiseAlert(address): checks if the sender is the bot of the\naddress, if yes, increase the number of alerts raised.", + "textAlign": "left", + "verticalAlign": "top", + "containerId": null, + "originalText": "- setDetectionBot(address): assign a bot to the msg.sender\n\n- notify(address, bytes calldata): call the handleTransaction\nmethod of the bot of the user (first address) with the\nparameters: address and bytes calldata.\n\n- raiseAlert(address): checks if the sender is the bot of the\naddress, if yes, increase the number of alerts raised.", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 78, + "versionNonce": 70888496, + "index": "bgS", + "isDeleted": false, + "id": "egtIWLpSp04qKJGZMrjxx", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -618.4996492588989, + "y": 6933.9671159388745, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 477.9296912681557, + "height": 0, + "seed": 1794291760, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "111OecMqxMA_jrswaWtiW" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": { + "elementId": "653H4gbSch6sH8TUDU8Qz", + "focus": -0.03975235755113571, + "gap": 20.373544091822623, + "fixedPoint": null + }, + "endBinding": null, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 477.9296912681557, + 0 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 19, + "versionNonce": 249803824, + "index": "bgT", + "isDeleted": false, + "id": "111OecMqxMA_jrswaWtiW", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -466.93675827569996, + "y": 6916.4671159388745, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 174.8039093017578, + "height": 35, + "seed": 1984705072, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800533, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "1. deploy bot", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "egtIWLpSp04qKJGZMrjxx", + "originalText": "1. deploy bot", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 105, + "versionNonce": 848602672, + "index": "bgU", + "isDeleted": false, + "id": "bvMsV3jd3FKGP5rK497__", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -617.5721032192205, + "y": 6910.755276296, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 469.6745315150465, + "height": 555.1015217691347, + "seed": 208038960, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [ + { + "type": "text", + "id": "Rd_PJZv1yFh_kgwR7UhTB" + } + ], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": { + "elementId": "653H4gbSch6sH8TUDU8Qz", + "focus": 0.801480738119244, + "gap": 21.30109013150104, + "fixedPoint": null + }, + "endBinding": { + "elementId": "y3EusiMCvePFDZwcdnTKM", + "focus": 0.8599490178285263, + "gap": 19.42373940244397, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 469.6745315150465, + -555.1015217691347 + ] + ], + "elbowed": false + }, + { + "type": "text", + "version": 44, + "versionNonce": 1790815792, + "index": "bgV", + "isDeleted": false, + "id": "Rd_PJZv1yFh_kgwR7UhTB", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": -539.6187791120879, + "y": 6580.704515411433, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 313.76788330078125, + "height": 105, + "seed": 388589104, + "groupIds": [], + "frameId": null, + "roundness": null, + "boundElements": [], + "updated": 1728377800533, + "link": null, + "locked": false, + "fontSize": 28, + "fontFamily": 5, + "text": "2. \nsetDetectionBot(botA\nddress)", + "textAlign": "center", + "verticalAlign": "middle", + "containerId": "bvMsV3jd3FKGP5rK497__", + "originalText": "2. setDetectionBot(botAddress)", + "autoResize": true, + "lineHeight": 1.25 + }, + { + "type": "arrow", + "version": 208, + "versionNonce": 260071472, + "index": "bgW", + "isDeleted": false, + "id": "hEsL9la-mGAxGH_bjcIve", + "fillStyle": "hachure", + "strokeWidth": 2, + "strokeStyle": "solid", + "roughness": 1, + "opacity": 100, + "angle": 0, + "x": 716.9637471772448, + "y": 6938.077304327186, + "strokeColor": "#1971c2", + "backgroundColor": "transparent", + "width": 176.72070920912256, + "height": 614.5804115633086, + "seed": 58531888, + "groupIds": [], + "frameId": null, + "roundness": { + "type": 2 + }, + "boundElements": [], + "updated": 1728377800530, + "link": null, + "locked": false, + "startBinding": { + "elementId": "Gja0MX1o2W22Iqs2FjoKS", + "focus": 0.9899134851543925, + "gap": 18.1867639755435, + "fixedPoint": null + }, + "endBinding": { + "elementId": "y3EusiMCvePFDZwcdnTKM", + "focus": -0.9382012841666914, + "gap": 20.74070054218737, + "fixedPoint": null + }, + "lastCommittedPoint": null, + "startArrowhead": null, + "endArrowhead": "arrow", + "points": [ + [ + 0, + 0 + ], + [ + 176.72070920912256, + -375.63875458022267 + ], + [ + 2.1913275187330328, + -614.5804115633086 + ] + ], + "elbowed": false + } + ], + "appState": { + "gridSize": 20, + "gridStep": 5, + "gridModeEnabled": false, + "viewBackgroundColor": "#ffffff" + }, + "files": {} +} \ No newline at end of file diff --git a/docs/ethernaut-ctf/architecture-diagrams/ethernaut-lvl-26.png b/docs/ethernaut-ctf/architecture-diagrams/ethernaut-lvl-26.png new file mode 100644 index 0000000..e111314 Binary files /dev/null and b/docs/ethernaut-ctf/architecture-diagrams/ethernaut-lvl-26.png differ diff --git a/docs/ethernaut.png b/docs/ethernaut-ctf/ethernaut.png similarity index 100% rename from docs/ethernaut.png rename to docs/ethernaut-ctf/ethernaut.png diff --git a/test/EthernautCTF/DoubleEntryExploit.t.sol b/test/EthernautCTF/DoubleEntryExploit.t.sol new file mode 100644 index 0000000..29af26e --- /dev/null +++ b/test/EthernautCTF/DoubleEntryExploit.t.sol @@ -0,0 +1,150 @@ +// SPDX-License-Identifier: GPL-3.0 +pragma solidity ^0.8.0; + +import '../../src/EthernautCTF/DoubleEntry.sol'; +import '@forge-std/Test.sol'; +import '@forge-std/console.sol'; + +contract Bot is IDetectionBot { + address vaultAddress; + Forta forta; + address playerAddress; + + constructor( + address _vaultAddress, + address _fortaAddress, + address _playerAddress + ) { + vaultAddress = _vaultAddress; + forta = Forta(_fortaAddress); + playerAddress = _playerAddress; + } + + function handleTransaction(address user, bytes calldata msgData) external { + // We monitor calls made to the DET contract. + // We should make sure that the CryptoVault does not call this function. + (, , address origSender) = abi.decode( + msgData[4:], + (address, uint256, address) + ); + + // $ cast sig "delegateTransfer(address,uint256,address)" + // 0x9cd1a121 + bytes4 delegateTransferSig = bytes4(0x9cd1a121); + bytes4 sig = bytes4(msgData); + + if (sig == delegateTransferSig && origSender == vaultAddress) { + // Check that the signature is the delegateTransfer method signature. + // Also check that the origin sender is the vault address. + console.log('Exploit detected by the bot!'); + forta.raiseAlert(playerAddress); + } + } +} + +contract DoubleEntryExploit is Test { + CryptoVault target; + DoubleEntryPoint doubleEntryToken; + LegacyToken legacyToken; + Forta forta; + + address deployerAddress = makeAddr('deployer'); + address sweptTokenRecipientAddress = makeAddr('sweptTokenRecipient'); + address playerAddress = makeAddr('player'); + address exploiterAddress = makeAddr('exploiter'); + + function setUp() public { + vm.startPrank(deployerAddress); + target = new CryptoVault(sweptTokenRecipientAddress); + console.log('Target contract deployed'); + + legacyToken = new LegacyToken(); + legacyToken.mint(address(target), 100 ether); + console.log('Legacy token contract deployed'); + + forta = new Forta(); + console.log('Forta contract deployed'); + + doubleEntryToken = new DoubleEntryPoint( + address(legacyToken), + address(target), + address(forta), + playerAddress + ); + console.log('DoubleEntry token contract deployed'); + + legacyToken.delegateToNewContract(doubleEntryToken); + console.log('Legacy token delegate to DoubleEntry token'); + + target.setUnderlying(address(doubleEntryToken)); + console.log('CryptoVault underlying token set to the DoubleEntry token'); + + vm.stopPrank(); + } + + function testExploit() public { + console.log(); // break line + + uint256 vaultDETBalance = doubleEntryToken.balanceOf(address(target)); + console.log('Vault DET balance: %d units', vaultDETBalance / 1 ether); + assertEq(vaultDETBalance, 100 ether); + + uint256 vaultLGTBalance = legacyToken.balanceOf(address(target)); + console.log('Vault LGT balance: %d units', vaultLGTBalance / 1 ether); + assertEq(vaultLGTBalance, 100 ether); + + vm.startPrank(exploiterAddress); + // The goal of this exploit is to drain all the DET tokens from the CryptoVault. + console.log( + 'Sweep the LGT tokens which in fact will sweep the DET tokens from the CryptoVault' + ); + target.sweepToken(legacyToken); + console.log('The CryptoVault DET tokens have been drained'); + vm.stopPrank(); + + vaultDETBalance = doubleEntryToken.balanceOf(address(target)); + console.log('Vault DET balance: %d units', vaultDETBalance / 1 ether); + assertEq(vaultDETBalance, 0); + + vaultLGTBalance = legacyToken.balanceOf(address(target)); + console.log('Vault LGT balance: %d units', vaultLGTBalance / 1 ether); + assertEq(vaultLGTBalance, 100 ether); + } + + function testPreventExploitWithForta() public { + console.log(); // break line + + // Set up Forta bot. + vm.startPrank(playerAddress); + Bot bot = new Bot(address(target), address(forta), playerAddress); + forta.setDetectionBot(address(bot)); + vm.stopPrank(); + + uint256 vaultDETBalance = doubleEntryToken.balanceOf(address(target)); + console.log('Vault DET balance: %d units', vaultDETBalance / 1 ether); + assertEq(vaultDETBalance, 100 ether); + + uint256 vaultLGTBalance = legacyToken.balanceOf(address(target)); + console.log('Vault LGT balance: %d units', vaultLGTBalance / 1 ether); + assertEq(vaultLGTBalance, 100 ether); + + vm.startPrank(exploiterAddress); + // The goal of this exploit is to drain all the DET tokens from the CryptoVault. + console.log( + 'Sweep the LGT tokens which in fact will sweep the DET tokens from the CryptoVault' + ); + vm.expectRevert('Alert has been triggered, reverting'); + target.sweepToken(legacyToken); + console.log('The exploit call has been reverted by Forta'); + console.log('The CryptoVault DET tokens have not been drained'); + vm.stopPrank(); + + vaultDETBalance = doubleEntryToken.balanceOf(address(target)); + console.log('Vault DET balance: %d units', vaultDETBalance / 1 ether); + assertEq(vaultDETBalance, 100 ether); + + vaultLGTBalance = legacyToken.balanceOf(address(target)); + console.log('Vault LGT balance: %d units', vaultLGTBalance / 1 ether); + assertEq(vaultLGTBalance, 100 ether); + } +}