From 9c306ac8a9af02065a136e409509cfb117222a60 Mon Sep 17 00:00:00 2001 From: 0xchin Date: Tue, 30 Jul 2024 13:01:58 -0300 Subject: [PATCH] feat: pay function and aave integration --- package.json | 4 ++ remappings.txt | 2 + script/Deploy.sol | 17 +++-- src/contracts/Grateful.sol | 48 +++++++++++++ src/contracts/Greeter.sol | 59 ---------------- src/interfaces/IGrateful.sol | 63 +++++++++++++++++ src/interfaces/IGreeter.sol | 74 ------------------- test/integration/Grateful.t.sol | 30 ++++++++ test/integration/Greeter.t.sol | 16 ----- test/integration/IntegrationBase.sol | 14 ++-- test/invariants/fuzz/Greeter.t.sol | 37 ---------- test/invariants/symbolic/Greeter.t.sol | 58 --------------- test/unit/Greeter.t.sol | 98 -------------------------- yarn.lock | 10 +++ 14 files changed, 177 insertions(+), 353 deletions(-) create mode 100644 src/contracts/Grateful.sol delete mode 100644 src/contracts/Greeter.sol create mode 100644 src/interfaces/IGrateful.sol delete mode 100644 src/interfaces/IGreeter.sol create mode 100644 test/integration/Grateful.t.sol delete mode 100644 test/integration/Greeter.t.sol delete mode 100644 test/invariants/fuzz/Greeter.t.sol delete mode 100644 test/invariants/symbolic/Greeter.t.sol diff --git a/package.json b/package.json index 0ae14e9..d3f7e2a 100644 --- a/package.json +++ b/package.json @@ -34,10 +34,14 @@ "test/**/*.sol": "yarn lint:sol-tests", "package.json": "sort-package-json" }, + "dependencies": { + "@aave/core-v3": "^1.19.3" + }, "devDependencies": { "@commitlint/cli": "19.3.0", "@commitlint/config-conventional": "19.2.2", "@defi-wonderland/natspec-smells": "1.1.1", + "@openzeppelin/contracts": "^5.0.2", "forge-std": "github:foundry-rs/forge-std#1.8.2", "halmos-cheatcodes": "github:a16z/halmos-cheatcodes#c0d8655", "husky": ">=8", diff --git a/remappings.txt b/remappings.txt index 0ba2eec..572e6a8 100644 --- a/remappings.txt +++ b/remappings.txt @@ -1,5 +1,7 @@ forge-std/=node_modules/forge-std/src halmos-cheatcodes=node_modules/halmos-cheatcodes +openzeppelin-contracts/=node_modules/@openzeppelin/contracts +aave/core-v3/=node_modules/@aave/core-v3/contracts contracts/=src/contracts interfaces/=src/interfaces diff --git a/script/Deploy.sol b/script/Deploy.sol index 7ce78e9..f77d371 100644 --- a/script/Deploy.sol +++ b/script/Deploy.sol @@ -1,33 +1,36 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; -import {Greeter} from 'contracts/Greeter.sol'; +import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; +import {Grateful} from 'contracts/Grateful.sol'; import {Script} from 'forge-std/Script.sol'; import {IERC20} from 'forge-std/interfaces/IERC20.sol'; contract Deploy is Script { struct DeploymentParams { - string greeting; - IERC20 token; + address[] tokens; + IPool aavePool; } /// @notice Deployment parameters for each chain mapping(uint256 _chainId => DeploymentParams _params) internal _deploymentParams; function setUp() public { + address[] memory _tokens = new address[](1); + _tokens[0] = address(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2); + // Mainnet - _deploymentParams[1] = DeploymentParams('Hello, Mainnet!', IERC20(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2)); + _deploymentParams[1] = DeploymentParams(_tokens, IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2)); // Sepolia - _deploymentParams[11_155_111] = - DeploymentParams('Hello, Sepolia!', IERC20(0xB4FBF271143F4FBf7B91A5ded31805e42b2208d6)); + _deploymentParams[11_155_111] = DeploymentParams(_tokens, IPool(0x6Ae43d3271ff6888e7Fc43Fd7321a503ff738951)); } function run() public { DeploymentParams memory _params = _deploymentParams[block.chainid]; vm.startBroadcast(); - new Greeter(_params.greeting, _params.token); + new Grateful(_params.tokens, _params.aavePool); vm.stopBroadcast(); } } diff --git a/src/contracts/Grateful.sol b/src/contracts/Grateful.sol new file mode 100644 index 0000000..b7f23a5 --- /dev/null +++ b/src/contracts/Grateful.sol @@ -0,0 +1,48 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; +import {Ownable} from '@openzeppelin/contracts/access/Ownable.sol'; +import {Ownable2Step} from '@openzeppelin/contracts/access/Ownable2Step.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; +import {IGrateful} from 'interfaces/IGrateful.sol'; + +contract Grateful is IGrateful, Ownable2Step { + // @inheritdoc IGrateful + IPool public aavePool; + + // inheritdoc IGrateful + mapping(address => bool) public tokensWhitelisted; + + // @inheritdoc IGrateful + mapping(address => bool) public yieldingFunds; + + constructor(address[] memory _tokens, IPool _aavePool) Ownable(msg.sender) { + aavePool = _aavePool; + for (uint256 i = 0; i < _tokens.length; i++) { + tokensWhitelisted[_tokens[i]] = true; + IERC20(_tokens[i]).approve(address(_aavePool), type(uint256).max); + } + } + + // @inheritdoc IGrateful + function pay(address _merchant, address _token, uint256 _amount) external { + if (!tokensWhitelisted[_token]) { + revert Grateful_TokenNotWhitelisted(); + } + + if (yieldingFunds[_merchant]) { + IERC20(_token).transferFrom(msg.sender, address(this), _amount); + aavePool.supply(_token, _amount, address(this), 0); + } else { + if (!IERC20(_token).transferFrom(msg.sender, _merchant, _amount)) { + revert Grateful_TransferFailed(); + } + } + } + + // @inheritdoc IGrateful + function switchYieldingFunds() external { + yieldingFunds[msg.sender] = !yieldingFunds[msg.sender]; + } +} diff --git a/src/contracts/Greeter.sol b/src/contracts/Greeter.sol deleted file mode 100644 index 7167b5c..0000000 --- a/src/contracts/Greeter.sol +++ /dev/null @@ -1,59 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -import {IERC20} from 'forge-std/interfaces/IERC20.sol'; -import {IGreeter} from 'interfaces/IGreeter.sol'; - -contract Greeter is IGreeter { - /** - * @notice Empty string for revert checks - * @dev result of doing keccak256(bytes('')) - */ - bytes32 internal constant _EMPTY_STRING = 0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470; - - /// @inheritdoc IGreeter - address public immutable OWNER; - - /// @inheritdoc IGreeter - string public greeting; - - /// @inheritdoc IGreeter - IERC20 public token; - - /** - * @notice Reverts in case the function was not called by the owner of the contract - */ - modifier onlyOwner() { - if (msg.sender != OWNER) { - revert Greeter_OnlyOwner(); - } - _; - } - - /** - * @notice Defines the owner to the msg.sender and sets the initial greeting - * @param _greeting Initial greeting - * @param _token Initial token - */ - constructor(string memory _greeting, IERC20 _token) { - OWNER = msg.sender; - token = _token; - setGreeting(_greeting); - } - - /// @inheritdoc IGreeter - function greet() external view returns (string memory _greeting, uint256 _balance) { - _greeting = greeting; - _balance = token.balanceOf(msg.sender); - } - - /// @inheritdoc IGreeter - function setGreeting(string memory _greeting) public onlyOwner { - if (keccak256(bytes(_greeting)) == _EMPTY_STRING) { - revert Greeter_InvalidGreeting(); - } - - greeting = _greeting; - emit GreetingSet(_greeting); - } -} diff --git a/src/interfaces/IGrateful.sol b/src/interfaces/IGrateful.sol new file mode 100644 index 0000000..b6dfafb --- /dev/null +++ b/src/interfaces/IGrateful.sol @@ -0,0 +1,63 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; +import {IERC20} from 'forge-std/interfaces/IERC20.sol'; + +/** + * @title Grateful Contract + * @author Chin + * @notice Contract for allowing payments in whitelisted tokens. Payments can be done using Uniswap's Permit2. Merchants can choose to yield their payments in AAVE and withdraw them at any time. Recurring payments are enabled by using Chainlink Keepers + */ +interface IGrateful { + /*/////////////////////////////////////////////////////////////// + EVENTS + //////////////////////////////////////////////////////////////*/ + + /*/////////////////////////////////////////////////////////////// + ERRORS + //////////////////////////////////////////////////////////////*/ + /** + * @notice Throws if the token is not whitelisted + */ + error Grateful_TokenNotWhitelisted(); + + /** + * @notice Throws if the transfer failed + */ + error Grateful_TransferFailed(); + + /*/////////////////////////////////////////////////////////////// + VARIABLES + //////////////////////////////////////////////////////////////*/ + /** + * @notice Aave pool for yielding merchants funds + * @return _aavePool Aave pool + */ + function aavePool() external view returns (IPool _aavePool); + + /** + * @notice Whitelist of tokens that can be used to pay + * @return _isWhitelisted True if the token is whitelisted + */ + function tokensWhitelisted(address _token) external view returns (bool _isWhitelisted); + + /** + * @notice Returns the status of the merchant + * @return _isYieldingFunds True if the merchant is yielding funds + */ + function yieldingFunds(address _merchant) external view returns (bool _isYieldingFunds); + + /*/////////////////////////////////////////////////////////////// + LOGIC + //////////////////////////////////////////////////////////////*/ + /** + * @notice Makes a payment to merchant + */ + function pay(address _merchant, address _token, uint256 _amount) external; + + /** + * @notice Switch the preference of the merchant to yield funds or not + */ + function switchYieldingFunds() external; +} diff --git a/src/interfaces/IGreeter.sol b/src/interfaces/IGreeter.sol deleted file mode 100644 index 53b4a64..0000000 --- a/src/interfaces/IGreeter.sol +++ /dev/null @@ -1,74 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -import {IERC20} from 'forge-std/interfaces/IERC20.sol'; - -/** - * @title Greeter Contract - * @author Wonderland - * @notice This is a basic contract created in order to portray some - * best practices and foundry functionality. - */ -interface IGreeter { - /*/////////////////////////////////////////////////////////////// - EVENTS - //////////////////////////////////////////////////////////////*/ - /** - * @notice Greeting has changed - * @param _greeting The new greeting - */ - event GreetingSet(string _greeting); - - /*/////////////////////////////////////////////////////////////// - ERRORS - //////////////////////////////////////////////////////////////*/ - /** - * @notice Throws if the function was called by someone else than the owner - */ - error Greeter_OnlyOwner(); - - /** - * @notice Throws if the greeting set is invalid - * @dev Empty string is an invalid greeting - */ - error Greeter_InvalidGreeting(); - - /*/////////////////////////////////////////////////////////////// - VARIABLES - //////////////////////////////////////////////////////////////*/ - /** - * @notice Returns the owner of the contract - * @dev The owner will always be the deployer of the contract - * @return _owner The owner of the contract - */ - function OWNER() external view returns (address _owner); - - /** - * @notice Returns the previously set greeting - * @return _greet The greeting - */ - function greeting() external view returns (string memory _greet); - - /** - * @notice Returns the token used to greet callers - * @return _token The address of the token - */ - function token() external view returns (IERC20 _token); - - /*/////////////////////////////////////////////////////////////// - LOGIC - //////////////////////////////////////////////////////////////*/ - /** - * @notice Sets a new greeting - * @dev Only callable by the owner - * @param _newGreeting The new greeting to be set - */ - function setGreeting(string memory _newGreeting) external; - - /** - * @notice Greets the caller - * @return _greeting The greeting - * @return _balance Current token balance of the caller - */ - function greet() external view returns (string memory _greeting, uint256 _balance); -} diff --git a/test/integration/Grateful.t.sol b/test/integration/Grateful.t.sol new file mode 100644 index 0000000..dbf262e --- /dev/null +++ b/test/integration/Grateful.t.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.23; + +import {console} from 'forge-std/console.sol'; +import {IntegrationBase} from 'test/integration/IntegrationBase.sol'; + +contract IntegrationGreeter is IntegrationBase { + function test_Payment() public { + vm.startPrank(_daiWhale); + _dai.approve(address(_grateful), 100); + _grateful.pay(_merchant, address(_dai), 100); + vm.stopPrank(); + + assertEq(_dai.balanceOf(_merchant), 100); + } + + function test_PaymentYieldingFunds() public { + assertEq(_grateful.yieldingFunds(_merchant), false); + + vm.prank(_merchant); + _grateful.switchYieldingFunds(); + + assertEq(_grateful.yieldingFunds(_merchant), true); + + vm.startPrank(_daiWhale); + _dai.approve(address(_grateful), 100); + _grateful.pay(_merchant, address(_dai), 100); + vm.stopPrank(); + } +} diff --git a/test/integration/Greeter.t.sol b/test/integration/Greeter.t.sol deleted file mode 100644 index b0e3799..0000000 --- a/test/integration/Greeter.t.sol +++ /dev/null @@ -1,16 +0,0 @@ -// SPDX-License-Identifier: MIT -pragma solidity 0.8.23; - -import {IntegrationBase} from 'test/integration/IntegrationBase.sol'; - -contract IntegrationGreeter is IntegrationBase { - function test_Greet() public { - uint256 _whaleBalance = _dai.balanceOf(_daiWhale); - - vm.prank(_daiWhale); - (string memory _greeting, uint256 _balance) = _greeter.greet(); - - assertEq(_whaleBalance, _balance); - assertEq(_initialGreeting, _greeting); - } -} diff --git a/test/integration/IntegrationBase.sol b/test/integration/IntegrationBase.sol index b043f49..2dab78e 100644 --- a/test/integration/IntegrationBase.sol +++ b/test/integration/IntegrationBase.sol @@ -1,7 +1,8 @@ // SPDX-License-Identifier: MIT pragma solidity 0.8.23; -import {Greeter, IGreeter} from 'contracts/Greeter.sol'; +import {IPool} from '@aave/core-v3/contracts/interfaces/IPool.sol'; +import {Grateful, IGrateful} from 'contracts/Grateful.sol'; import {Test} from 'forge-std/Test.sol'; import {IERC20} from 'forge-std/interfaces/IERC20.sol'; @@ -10,14 +11,19 @@ contract IntegrationBase is Test { string internal _initialGreeting = 'hola'; address internal _user = makeAddr('user'); + address internal _merchant = makeAddr('merchant'); address internal _owner = makeAddr('owner'); - address internal _daiWhale = 0x42f8CA49E88A8fd8F0bfA2C739e648468b8f9dec; + address internal _daiWhale = 0xbf702ea18BB1AB2A710394993a576eC61476cCf3; + address[] internal _tokens; IERC20 internal _dai = IERC20(0x6B175474E89094C44Da98b954EedeAC495271d0F); - IGreeter internal _greeter; + IPool internal _aavePool = IPool(0x87870Bca3F3fD6335C3F4ce8392D69350B4fA4E2); + IGrateful internal _grateful; function setUp() public { vm.createSelectFork(vm.rpcUrl('mainnet'), _FORK_BLOCK); vm.prank(_owner); - _greeter = new Greeter(_initialGreeting, _dai); + _tokens = new address[](1); + _tokens[0] = address(_dai); + _grateful = new Grateful(_tokens, _aavePool); } } diff --git a/test/invariants/fuzz/Greeter.t.sol b/test/invariants/fuzz/Greeter.t.sol deleted file mode 100644 index 96e2b1e..0000000 --- a/test/invariants/fuzz/Greeter.t.sol +++ /dev/null @@ -1,37 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; - -import {Greeter, IERC20} from 'contracts/Greeter.sol'; - -interface IHevm { - function prank(address) external; -} - -contract InvariantGreeter { - IHevm internal _hevm = IHevm(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D); - - Greeter internal _targetContract; - - constructor() { - _targetContract = new Greeter('a', IERC20(address(1))); - } - - function checkGreeterNeverEmpty(string memory _newGreeting) public { - // Execution - (bool _success,) = address(_targetContract).call(abi.encodeCall(Greeter.setGreeting, _newGreeting)); - - // Check output condition - assert((_success && keccak256(bytes(_targetContract.greeting())) != keccak256(bytes(''))) || !_success); - } - - function checkOnlyOwnerSetsGreeting(address _caller) public { - // Input conditions - _hevm.prank(_caller); - - // Execution - (bool _success,) = address(this).call(abi.encodeCall(Greeter.setGreeting, 'hello')); - - // Check output condition - assert((_success && msg.sender == _targetContract.OWNER()) || (!_success && msg.sender != _targetContract.OWNER())); - } -} diff --git a/test/invariants/symbolic/Greeter.t.sol b/test/invariants/symbolic/Greeter.t.sol deleted file mode 100644 index 16a7edd..0000000 --- a/test/invariants/symbolic/Greeter.t.sol +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; - -import {Greeter, IERC20} from 'contracts/Greeter.sol'; - -import {Test} from 'forge-std/Test.sol'; -import {SymTest} from 'halmos-cheatcodes/src/SymTest.sol'; // See https://github.com/a16z/halmos-cheatcodes?tab=readme-ov-file - -contract SymbolicGreeter is SymTest, Test { - Greeter public targetContract; - - function setUp() public { - string memory _initialGreeting = svm.createString(64, 'initial greeting'); - address _token = svm.createAddress('token'); - - targetContract = new Greeter(_initialGreeting, IERC20(_token)); - } - - function check_validState_greeterNeverEmpty(address _caller) public { - // Input conditions: any caller - vm.prank(_caller); - - // Execution: Halmos cannot use a dynamic-sized array, iterate over multiple string lengths - bool _success; - for (uint256 i = 1; i < 3; i++) { - string memory greeting = svm.createString(i, 'greeting'); - (_success,) = address(targetContract).call(abi.encodeCall(Greeter.setGreeting, (greeting))); - - // Output condition check - vm.assume(_success); // discard failing calls - assert(keccak256(bytes(targetContract.greeting())) != keccak256(bytes(''))); - } - - // Add the empty string (bypass the non-empty check of svm.createString) - (_success,) = address(targetContract).call(abi.encodeCall(Greeter.setGreeting, (''))); - - // Output condition check - vm.assume(_success); // discard failing calls - assert(keccak256(bytes(targetContract.greeting())) != keccak256(bytes(''))); - } - - function check_setGreeting_onlyOwnerSetsGreeting(address _caller) public { - // Input conditions - string memory _newGreeting = svm.createString(64, 'new greeting'); - - // Execution - vm.prank(_caller); - (bool _success,) = address(targetContract).call(abi.encodeCall(Greeter.setGreeting, (_newGreeting))); - - // Output condition check - if (_success) { - assert(_caller == targetContract.OWNER()); - assert(keccak256(bytes(targetContract.greeting())) == keccak256(bytes(_newGreeting))); - } else { - assert(_caller != targetContract.OWNER() || keccak256(bytes(_newGreeting)) == keccak256(bytes(''))); - } - } -} diff --git a/test/unit/Greeter.t.sol b/test/unit/Greeter.t.sol index 82f59c9..8b13789 100644 --- a/test/unit/Greeter.t.sol +++ b/test/unit/Greeter.t.sol @@ -1,99 +1 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity 0.8.23; -import {Greeter, IGreeter} from 'contracts/Greeter.sol'; -import {Test} from 'forge-std/Test.sol'; -import {IERC20} from 'forge-std/interfaces/IERC20.sol'; - -contract UnitGreeter is Test { - address internal _owner = makeAddr('owner'); - IERC20 internal _token = IERC20(makeAddr('token')); - uint256 internal _initialBalance = 100; - string internal _initialGreeting = 'hola'; - - Greeter internal _greeter; - - event GreetingSet(string _greeting); - - function setUp() external { - vm.prank(_owner); - _greeter = new Greeter(_initialGreeting, _token); - - vm.etch(address(_token), new bytes(0x1)); - } - - function test_EmptyTestExample() external { - // it does nothing - vm.skip(true); - } - - function test_ConstructorWhenPassingValidGreetingString() external { - vm.prank(_owner); - - // it deploys - _greeter = new Greeter(_initialGreeting, _token); - - // it sets the greeting string - assertEq(_greeter.greeting(), _initialGreeting); - - // it sets the owner as sender - assertEq(_greeter.OWNER(), _owner); - - // it sets the token used - assertEq(address(_greeter.token()), address(_token)); - } - - function test_ConstructorWhenPassingAnEmptyGreetingString() external { - vm.prank(_owner); - - // it reverts - vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector); - _greeter = new Greeter('', _token); - } - - function test_GreetWhenCalled() external { - vm.mockCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector), abi.encode(_initialBalance)); - vm.expectCall(address(_token), abi.encodeWithSelector(IERC20.balanceOf.selector)); - (string memory _greet, uint256 _balance) = _greeter.greet(); - - // it returns the greeting string - assertEq(_greet, _initialGreeting); - - // it returns the token balance of the contract - assertEq(_balance, _initialBalance); - } - - modifier whenCalledByTheOwner() { - vm.startPrank(_owner); - _; - vm.stopPrank(); - } - - function test_SetGreetingWhenPassingAValidGreetingString() external whenCalledByTheOwner { - string memory _newGreeting = 'hello'; - - // it emit GreetingSet - vm.expectEmit(true, true, true, true, address(_greeter)); - emit GreetingSet(_newGreeting); - - _greeter.setGreeting(_newGreeting); - - // it sets the greeting string - assertEq(_greeter.greeting(), _newGreeting); - } - - function test_SetGreetingWhenPassingAnEmptyGreetingString() external whenCalledByTheOwner { - // it reverts - vm.expectRevert(IGreeter.Greeter_InvalidGreeting.selector); - _greeter.setGreeting(''); - } - - function test_SetGreetingWhenCalledByANon_owner(address _caller) external { - vm.assume(_caller != _owner); - vm.prank(_caller); - - // it reverts - vm.expectRevert(IGreeter.Greeter_OnlyOwner.selector); - _greeter.setGreeting('new greeting'); - } -} diff --git a/yarn.lock b/yarn.lock index 7cdad64..c8604b7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,11 @@ # yarn lockfile v1 +"@aave/core-v3@^1.19.3": + version "1.19.3" + resolved "https://registry.yarnpkg.com/@aave/core-v3/-/core-v3-1.19.3.tgz#513e886b37a8d84d9821a4041dceb5f014919669" + integrity sha512-Xr7+VcoU5b4mPwM4IUCnskw3lciwrnL4Xloyad8GOddYeixnri2lZYobNmErm1LwE50vqjI0O+9QGNKY2TDkeQ== + "@babel/code-frame@^7.0.0": version "7.24.2" resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.24.2.tgz#718b4b19841809a58b29b68cde80bc5e1aa6d9ae" @@ -223,6 +228,11 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@openzeppelin/contracts@^5.0.2": + version "5.0.2" + resolved "https://registry.yarnpkg.com/@openzeppelin/contracts/-/contracts-5.0.2.tgz#b1d03075e49290d06570b2fd42154d76c2a5d210" + integrity sha512-ytPc6eLGcHHnapAZ9S+5qsdomhjo6QBHTDRRBFfTxXIpsicMhVPouPgmUPebZZZGX7vt9USA+Z+0M0dSVtSUEA== + "@scure/base@~1.1.4": version "1.1.6" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.6.tgz#8ce5d304b436e4c84f896e0550c83e4d88cb917d"