diff --git a/packages/contracts/contracts/optimistic-ethereum/bridge/CrossDomainMessenger.interface.sol b/packages/contracts/contracts/optimistic-ethereum/bridge/CrossDomainMessenger.interface.sol new file mode 100644 index 00000000000..8dc382732d9 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/bridge/CrossDomainMessenger.interface.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.5.0; + +contract ICrossDomainMessenger { + address public crossDomainMsgSender; + + /** + * Relays a message to a given target contract. + * @param _target Address of the target contract. + * @param _sender Address of the message sender. + * @param _message Calldata to relay. + */ + function relayMessage( + address _target, + address _sender, + bytes memory _message + ) public; + + /** + * Sends a message to another cross domain messenger to be relayed. + * @param _target Address of the target contract. + * @param _message Calldata to relay. + */ + function sendMessage( + address _target, + bytes memory _message + ) public; +} \ No newline at end of file diff --git a/packages/contracts/contracts/optimistic-ethereum/bridge/MockCrossDomainMessenger.sol b/packages/contracts/contracts/optimistic-ethereum/bridge/MockCrossDomainMessenger.sol new file mode 100644 index 00000000000..310c8e09482 --- /dev/null +++ b/packages/contracts/contracts/optimistic-ethereum/bridge/MockCrossDomainMessenger.sol @@ -0,0 +1,70 @@ +pragma solidity ^0.5.0; + +/* Interface Imports */ +import { ICrossDomainMessenger } from "./CrossDomainMessenger.interface.sol"; + +/** + * @title MockCrossDomainMessenger + */ +contract MockCrossDomainMessenger is ICrossDomainMessenger { + /* + * Contract Variables + */ + + ICrossDomainMessenger targetMessenger; + address public crossDomainMsgSender; + + /* + * Public Functions + */ + + /** + * Relays a message to a target contract. + * .inheritdoc ICrossDomainMessenger + */ + function relayMessage( + address _target, + address _sender, + bytes memory _message + ) + public + { + crossDomainMsgSender = _sender; + (bool success,) = _target.call(_message); + require(success, "Received message reverted during execution."); + } + + /** + * Sends a message to the target messenger. + * .inheritdoc ICrossDomainMessenger + */ + function sendMessage( + address _target, + bytes memory _message + ) + public + { + require( + address(targetMessenger) != address(0), + "Cannot send a message without setting the target messenger." + ); + + targetMessenger.relayMessage( + _target, + msg.sender, + _message + ); + } + + /** + * Sets the target messenger. + * @param _messenger Target messenger address. + */ + function setTargetMessenger( + address _messenger + ) + public + { + targetMessenger = ICrossDomainMessenger(_messenger); + } +} diff --git a/packages/contracts/contracts/test-helpers/CrossDomainSimpleStorage.sol b/packages/contracts/contracts/test-helpers/CrossDomainSimpleStorage.sol new file mode 100644 index 00000000000..5cb60e2c7da --- /dev/null +++ b/packages/contracts/contracts/test-helpers/CrossDomainSimpleStorage.sol @@ -0,0 +1,18 @@ +pragma solidity ^0.5.0; + +import { ICrossDomainMessenger} from "../optimistic-ethereum/bridge/CrossDomainMessenger.interface.sol"; +import { SimpleStorage } from "./SimpleStorage.sol"; + +contract CrossDomainSimpleStorage is SimpleStorage { + ICrossDomainMessenger crossDomainMessenger; + address public crossDomainMsgSender; + + function setMessenger(address _crossDomainMessengerAddress) public { + crossDomainMessenger = ICrossDomainMessenger(_crossDomainMessengerAddress); + } + + function crossDomainSetStorage(bytes32 key, bytes32 value) public { + crossDomainMsgSender = crossDomainMessenger.crossDomainMsgSender(); + setStorage(key, value); + } +} \ No newline at end of file diff --git a/packages/contracts/package.json b/packages/contracts/package.json index 9152a3b4b95..e1aa352efcd 100644 --- a/packages/contracts/package.json +++ b/packages/contracts/package.json @@ -26,9 +26,11 @@ "test:contracts": "cross-env SOLPP_FLAGS=\"FLAG_IS_TEST,FLAG_IS_DEBUG\" buidler test --show-stack-traces", "coverage": "yarn run coverage:contracts", "coverage:contracts": "cross-env SOLPP_FLAGS=\"FLAG_IS_TEST\" buidler coverage --network coverage --show-stack-traces --testfiles \"test/contracts/**/*.spec.ts\"", - "build": "yarn run build:contracts && yarn run build:typescript", + "build": "yarn run build:contracts && yarn run build:typescript && yarn run build:copy", "build:contracts": "buidler compile", "build:typescript": "tsc -p .", + "build:copy": "yarn run build:copy:contracts", + "build:copy:contracts": "copyfiles -u 2 \"contracts/optimistic-ethereum/**/*.sol\" \"build/contracts\"", "clean": "rm -rf ./artifacts ./build ./cache", "lint": "yarn run lint:typescript", "lint:typescript": "tslint --format stylish --project .", @@ -61,6 +63,7 @@ "typescript": "^3.9.5" }, "devDependencies": { + "copyfiles": "^2.3.0", "cross-env": "^7.0.2", "glob": "^7.1.6", "solidity-coverage": "^0.7.9" diff --git a/packages/contracts/test/contracts/bridge/MockCrossDomainMessenger.spec.ts b/packages/contracts/test/contracts/bridge/MockCrossDomainMessenger.spec.ts new file mode 100644 index 00000000000..58bdbac6c90 --- /dev/null +++ b/packages/contracts/test/contracts/bridge/MockCrossDomainMessenger.spec.ts @@ -0,0 +1,126 @@ +import { expect } from '../../setup' + +/* External Imports */ +import { ethers } from '@nomiclabs/buidler' +import { ContractFactory, Contract, Signer } from 'ethers' +import { NULL_ADDRESS } from '@eth-optimism/core-utils' + +describe('MockCrossDomainMessenger', () => { + let wallet: Signer + before(async () => { + ;[wallet] = await ethers.getSigners() + }) + + let MockCrossDomainMessengerFactory: ContractFactory + let CrossDomainSimpleStorageFactory: ContractFactory + before(async () => { + MockCrossDomainMessengerFactory = await ethers.getContractFactory( + 'MockCrossDomainMessenger' + ) + CrossDomainSimpleStorageFactory = await ethers.getContractFactory( + 'CrossDomainSimpleStorage' + ) + }) + + let L1MockCrossDomainMessenger: Contract + let L2MockCrossDomainMessenger: Contract + beforeEach(async () => { + L1MockCrossDomainMessenger = await MockCrossDomainMessengerFactory.deploy() + L2MockCrossDomainMessenger = await MockCrossDomainMessengerFactory.deploy() + + await L1MockCrossDomainMessenger.setTargetMessenger( + L2MockCrossDomainMessenger.address + ) + await L2MockCrossDomainMessenger.setTargetMessenger( + L1MockCrossDomainMessenger.address + ) + }) + + let L2SimpleStorage: Contract + beforeEach(async () => { + L2SimpleStorage = await CrossDomainSimpleStorageFactory.deploy() + + await L2SimpleStorage.setMessenger(L2MockCrossDomainMessenger.address) + }) + describe('relayMessage', () => { + it('should successfully relay a message to the target receiver', async () => { + const expectedStorageKey = ethers.utils.keccak256('0x1234') + const expectedStorageValue = ethers.utils.keccak256('0x5678') + + const calldata = L2SimpleStorage.interface.encodeFunctionData( + 'crossDomainSetStorage', + [expectedStorageKey, expectedStorageValue] + ) + + const expectedMessage = [await wallet.getAddress(), calldata] + + await L2MockCrossDomainMessenger.relayMessage( + L2SimpleStorage.address, + ...expectedMessage + ) + + const actualStorageValue = await L2SimpleStorage.getStorage( + expectedStorageKey + ) + expect(actualStorageValue).to.equal(expectedStorageValue) + expect(await L2SimpleStorage.crossDomainMsgSender()).to.equal( + await wallet.getAddress() + ) + }) + }) + + describe('sendMessage', () => { + it('should successfully send a message to another messenger', async () => { + const expectedStorageKey = ethers.utils.keccak256('0x1234') + const expectedStorageValue = ethers.utils.keccak256('0x5678') + + const calldata = L2SimpleStorage.interface.encodeFunctionData( + 'crossDomainSetStorage', + [expectedStorageKey, expectedStorageValue] + ) + + await L1MockCrossDomainMessenger.sendMessage( + L2SimpleStorage.address, + calldata, + { + from: await wallet.getAddress(), + } + ) + + const currentBlock = await ethers.provider.getBlock('latest') + const expectedMessage = [await wallet.getAddress(), calldata] + + const actualStorageValue = await L2SimpleStorage.getStorage( + expectedStorageKey + ) + expect(actualStorageValue).to.equal(expectedStorageValue) + expect(await L2SimpleStorage.crossDomainMsgSender()).to.equal( + await wallet.getAddress() + ) + }) + + it('should revert if its target messenger is not set', async () => { + const expectedStorageKey = ethers.utils.keccak256('0x1234') + const expectedStorageValue = ethers.utils.keccak256('0x5678') + + const calldata = L2SimpleStorage.interface.encodeFunctionData( + 'setStorage', + [expectedStorageKey, expectedStorageValue] + ) + + await L1MockCrossDomainMessenger.setTargetMessenger(NULL_ADDRESS) + + await expect( + L1MockCrossDomainMessenger.sendMessage( + L2SimpleStorage.address, + calldata, + { + from: await wallet.getAddress(), + } + ) + ).to.be.revertedWith( + 'Cannot send a message without setting the target messenger.' + ) + }) + }) +}) diff --git a/yarn.lock b/yarn.lock index a29504eef20..1b2e82093ae 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1752,6 +1752,11 @@ resolved "https://registry.yarnpkg.com/@types/chai/-/chai-4.2.12.tgz#6160ae454cd89dae05adc3bb97997f488b608201" integrity sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ== +"@types/color-name@^1.1.1": + version "1.1.1" + resolved "https://registry.yarnpkg.com/@types/color-name/-/color-name-1.1.1.tgz#1c1261bbeaa10a8055bbc5d8ab84b7b2afc846a0" + integrity sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ== + "@types/debug@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.5.tgz#b14efa8852b7768d898906613c23f688713e02cd" @@ -2117,6 +2122,11 @@ ansi-regex@^4.1.0: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997" integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg== +ansi-regex@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75" + integrity sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg== + ansi-styles@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" @@ -2129,6 +2139,14 @@ ansi-styles@^3.2.0, ansi-styles@^3.2.1: dependencies: color-convert "^1.9.0" +ansi-styles@^4.0.0: + version "4.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.2.1.tgz#90ae75c424d008d2624c5bf29ead3177ebfcf359" + integrity sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA== + dependencies: + "@types/color-name" "^1.1.1" + color-convert "^2.0.1" + ansi-wrap@0.1.0, ansi-wrap@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/ansi-wrap/-/ansi-wrap-0.1.0.tgz#a82250ddb0015e9a27ca82e82ea603bbfa45efaf" @@ -3769,6 +3787,15 @@ cliui@^5.0.0: strip-ansi "^5.2.0" wrap-ansi "^5.1.0" +cliui@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" + integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== + dependencies: + string-width "^4.2.0" + strip-ansi "^6.0.0" + wrap-ansi "^6.2.0" + clone-buffer@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/clone-buffer/-/clone-buffer-1.0.0.tgz#e3e25b207ac4e701af721e2cb5a16792cac3dc58" @@ -3843,11 +3870,23 @@ color-convert@^1.9.0: dependencies: color-name "1.1.3" +color-convert@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" + integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== + dependencies: + color-name "~1.1.4" + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" integrity sha1-p9BVi9icQveV3UIyj3QIMcpTvCU= +color-name@~1.1.4: + version "1.1.4" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" + integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== + color-support@^1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-support/-/color-support-1.1.3.tgz#93834379a1cc9a0c61f82f52f0d04322251bd5a2" @@ -4090,6 +4129,18 @@ copy-props@^2.0.1: each-props "^1.3.0" is-plain-object "^2.0.1" +copyfiles@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/copyfiles/-/copyfiles-2.3.0.tgz#1c26ebbe3d46bba2d309a3fd8e3aaccf53af8c76" + integrity sha512-73v7KFuDFJ/ofkQjZBMjMBFWGgkS76DzXvBMUh7djsMOE5EELWtAO/hRB6Wr5Vj5Zg+YozvoHemv0vnXpqxmOQ== + dependencies: + glob "^7.0.5" + minimatch "^3.0.3" + mkdirp "^1.0.4" + noms "0.0.0" + through2 "^2.0.1" + yargs "^15.3.1" + core-js-pure@^3.0.1: version "3.6.5" resolved "https://registry.yarnpkg.com/core-js-pure/-/core-js-pure-3.6.5.tgz#c79e75f5e38dbc85a662d91eea52b8256d53b813" @@ -4734,6 +4785,11 @@ emoji-regex@^7.0.1: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-7.0.3.tgz#933a04052860c85e83c122479c4748a8e4c72156" integrity sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA== +emoji-regex@^8.0.0: + version "8.0.0" + resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" + integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== + encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -6640,7 +6696,7 @@ glob@7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@7.1.6, glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6: +glob@7.1.6, glob@^7.0.0, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@~7.1.6: version "7.1.6" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6" integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA== @@ -7480,6 +7536,11 @@ is-fullwidth-code-point@^2.0.0: resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" integrity sha1-o7MKXE8ZkYMWeqq5O+764937ZU8= +is-fullwidth-code-point@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" + integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== + is-function@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/is-function/-/is-function-1.0.2.tgz#4f097f30abf6efadac9833b17ca5dc03f8144e08" @@ -8859,7 +8920,7 @@ minimalistic-crypto-utils@^1.0.0, minimalistic-crypto-utils@^1.0.1: resolved "https://registry.yarnpkg.com/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz#f6c00c1c0b082246e5c4d99dfb8c7c083b2b582a" integrity sha1-9sAMHAsIIkblxNmd+4x8CDsrWCo= -"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.4: +"minimatch@2 || 3", minimatch@3.0.4, minimatch@^3.0.3, minimatch@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" integrity sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA== @@ -8934,7 +8995,7 @@ mkdirp-promise@^5.0.1: dependencies: mkdirp "*" -mkdirp@*: +mkdirp@*, mkdirp@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== @@ -9298,6 +9359,14 @@ node-gyp@^5.0.2: tar "^4.4.12" which "^1.3.1" +noms@0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859" + integrity sha1-2o69nzr51nYJGbJ9nNyAkqczKFk= + dependencies: + inherits "^2.0.1" + readable-stream "~1.0.31" + nopt@3.x: version "3.0.6" resolved "https://registry.yarnpkg.com/nopt/-/nopt-3.0.6.tgz#c6465dbf08abcd4db359317f79ac68a646b28ff9" @@ -10589,7 +10658,7 @@ readable-stream@^1.0.33: isarray "0.0.1" string_decoder "~0.10.x" -readable-stream@~1.0.15: +readable-stream@~1.0.15, readable-stream@~1.0.31: version "1.0.34" resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-1.0.34.tgz#125820e34bc842d2f2aaafafe4c2916ee32c157c" integrity sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw= @@ -11711,6 +11780,15 @@ string-width@^3.0.0, string-width@^3.1.0: is-fullwidth-code-point "^2.0.0" strip-ansi "^5.1.0" +string-width@^4.1.0, string-width@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.0.tgz#952182c46cc7b2c313d1596e623992bd163b72b5" + integrity sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.0" + string.prototype.trim@~1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.1.tgz#141233dff32c82bfad80684d7e5f0869ee0fb782" @@ -11776,6 +11854,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0: + version "6.0.0" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.0.tgz#0b1571dd7669ccd4f3e06e14ef1eed26225ae532" + integrity sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w== + dependencies: + ansi-regex "^5.0.0" + strip-bom@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" @@ -12068,7 +12153,7 @@ through2-filter@^3.0.0: through2 "~2.0.0" xtend "~4.0.0" -through2@^2.0.0, through2@^2.0.2, through2@^2.0.3, through2@~2.0.0: +through2@^2.0.0, through2@^2.0.1, through2@^2.0.2, through2@^2.0.3, through2@~2.0.0: version "2.0.5" resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.5.tgz#01c1e39eb31d07cb7d03a96a70823260b23132cd" integrity sha512-/mrRod8xqpA+IHSLyGCQ2s8SPHiCDEeQJSep1jqLYeEUClOFG2Qsh+4FU6G9VeqpZnGW/Su8LQGc4YKni5rYSQ== @@ -14363,6 +14448,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" + integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" @@ -14552,7 +14646,7 @@ yargs-parser@^15.0.1: camelcase "^5.0.0" decamelize "^1.2.0" -yargs-parser@^18.1.3: +yargs-parser@^18.1.2, yargs-parser@^18.1.3: version "18.1.3" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== @@ -14652,6 +14746,23 @@ yargs@^14.2.2: y18n "^4.0.0" yargs-parser "^15.0.1" +yargs@^15.3.1: + version "15.4.1" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" + integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== + dependencies: + cliui "^6.0.0" + decamelize "^1.2.0" + find-up "^4.1.0" + get-caller-file "^2.0.1" + require-directory "^2.1.1" + require-main-filename "^2.0.0" + set-blocking "^2.0.0" + string-width "^4.2.0" + which-module "^2.0.0" + y18n "^4.0.0" + yargs-parser "^18.1.2" + yargs@^4.7.1: version "4.8.1" resolved "https://registry.yarnpkg.com/yargs/-/yargs-4.8.1.tgz#c0c42924ca4aaa6b0e6da1739dfb216439f9ddc0"