diff --git a/generator/common.ts b/generator/common.ts
index bad4f0d96..c7010334c 100644
--- a/generator/common.ts
+++ b/generator/common.ts
@@ -106,3 +106,7 @@ export const CHAIN_TO_CHAIN_OBJECT = {
Bsc: bsc,
Gnosis: gnosis,
};
+
+export function flagAsRequired(message: string, required?: boolean) {
+ return required ? `${message}*` : message;
+}
diff --git a/generator/features/__snapshots__/assetListing.spec.ts.snap b/generator/features/__snapshots__/assetListing.spec.ts.snap
index fa297e7e4..d713c080a 100644
--- a/generator/features/__snapshots__/assetListing.spec.ts.snap
+++ b/generator/features/__snapshots__/assetListing.spec.ts.snap
@@ -50,23 +50,23 @@ Copyright and related rights waived via [CC0](https://creativecommons.org/public
\\"liqBonus\\": \\"5_00\\",
\\"debtCeiling\\": \\"100_000\\",
\\"liqProtocolFee\\": \\"20_00\\",
- \\"enabledToBorrow\\": \\"ENABLED\\",
- \\"flashloanable\\": \\"ENABLED\\",
- \\"stableRateModeEnabled\\": \\"DISABLED\\",
- \\"borrowableInIsolation\\": \\"DISABLED\\",
- \\"withSiloedBorrowing\\": \\"DISABLED\\",
+ \\"enabledToBorrow\\": \\"EngineFlags.ENABLED\\",
+ \\"flashloanable\\": \\"EngineFlags.ENABLED\\",
+ \\"stableRateModeEnabled\\": \\"EngineFlags.DISABLED\\",
+ \\"borrowableInIsolation\\": \\"EngineFlags.DISABLED\\",
+ \\"withSiloedBorrowing\\": \\"EngineFlags.DISABLED\\",
\\"reserveFactor\\": \\"20_00\\",
\\"supplyCap\\": \\"10_000\\",
\\"borrowCap\\": \\"5_000\\",
\\"rateStrategyParams\\": {
\\"optimalUtilizationRate\\": \\"_bpsToRay(80_00)\\",
- \\"baseVariableBorrowRate\\": \\"_bpsToRay(0_00)\\",
+ \\"baseVariableBorrowRate\\": \\"_bpsToRay(0)\\",
\\"variableRateSlope1\\": \\"_bpsToRay(10_00)\\",
\\"variableRateSlope2\\": \\"_bpsToRay(100_00)\\",
\\"stableRateSlope1\\": \\"_bpsToRay(10_00)\\",
\\"stableRateSlope2\\": \\"_bpsToRay(100_00)\\",
\\"baseStableRateOffset\\": \\"_bpsToRay(1_00)\\",
- \\"stableRateExcessOffset\\": \\"_bpsToRay()\\",
+ \\"stableRateExcessOffset\\": \\"_bpsToRay(0)\\",
\\"optimalStableToTotalDebtRatio\\": \\"_bpsToRay(10_00)\\"
},
\\"eModeCategory\\": \\"AaveV3EthereumEModes.NONE\\",
@@ -88,6 +88,7 @@ pragma solidity ^0.8.0;
import {AaveV3Ethereum, AaveV3EthereumEModes} from 'aave-address-book/AaveV3Ethereum.sol';
import {AaveV3PayloadEthereum} from 'aave-helpers/v3-config-engine/AaveV3PayloadEthereum.sol';
+import {EngineFlags} from 'aave-helpers/v3-config-engine/EngineFlags.sol';
import {IAaveV3ConfigEngine} from 'aave-helpers/v3-config-engine/IAaveV3ConfigEngine.sol';
import {IV3RateStrategyFactory} from 'aave-helpers/v3-config-engine/IV3RateStrategyFactory.sol';
import {IERC20} from 'solidity-utils/contracts/oz-common/interfaces/IERC20.sol';
@@ -118,11 +119,11 @@ contract AaveV3Ethereum_Test_20231023 is AaveV3PayloadEthereum {
assetSymbol: 'PSP',
priceFeed: 0x72AFAECF99C9d9C8215fF44C77B94B99C28741e8,
eModeCategory: AaveV3EthereumEModes.NONE,
- enabledToBorrow: ENABLED,
- stableRateModeEnabled: DISABLED,
- borrowableInIsolation: DISABLED,
- withSiloedBorrowing: DISABLED,
- flashloanable: ENABLED,
+ enabledToBorrow: EngineFlags.ENABLED,
+ stableRateModeEnabled: EngineFlags.DISABLED,
+ borrowableInIsolation: EngineFlags.DISABLED,
+ withSiloedBorrowing: EngineFlags.DISABLED,
+ flashloanable: EngineFlags.ENABLED,
ltv: 40_00,
liqThreshold: 50_00,
liqBonus: 5_00,
@@ -133,13 +134,13 @@ contract AaveV3Ethereum_Test_20231023 is AaveV3PayloadEthereum {
liqProtocolFee: 20_00,
rateStrategyParams: IV3RateStrategyFactory.RateStrategyParams({
optimalUsageRatio: _bpsToRay(80_00),
- baseVariableBorrowRate: _bpsToRay(0_00),
+ baseVariableBorrowRate: _bpsToRay(0),
variableRateSlope1: _bpsToRay(10_00),
variableRateSlope2: _bpsToRay(100_00),
stableRateSlope1: _bpsToRay(10_00),
stableRateSlope2: _bpsToRay(100_00),
baseStableRateOffset: _bpsToRay(1_00),
- stableRateExcessOffset: _bpsToRay(),
+ stableRateExcessOffset: _bpsToRay(0),
optimalStableToTotalDebtRatio: _bpsToRay(10_00)
})
});
@@ -167,7 +168,7 @@ contract AaveV3Ethereum_Test_20231023_Test is ProtocolV3TestBase {
AaveV3Ethereum_Test_20231023 internal proposal;
function setUp() public {
- vm.createSelectFork(vm.rpcUrl('mainnet'), 18484119);
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 18487480);
proposal = new AaveV3Ethereum_Test_20231023();
}
@@ -262,11 +263,11 @@ exports[`feature: assetListing > should return reasonable code 1`] = `
assetSymbol: \\"PSP\\",
priceFeed: 0x72AFAECF99C9d9C8215fF44C77B94B99C28741e8,
eModeCategory: AaveV3EthereumEModes.NONE,
- enabledToBorrow: ENABLED,
- stableRateModeEnabled: DISABLED,
- borrowableInIsolation: DISABLED,
- withSiloedBorrowing: DISABLED,
- flashloanable: ENABLED,
+ enabledToBorrow: EngineFlags.ENABLED,
+ stableRateModeEnabled: EngineFlags.DISABLED,
+ borrowableInIsolation: EngineFlags.DISABLED,
+ withSiloedBorrowing: EngineFlags.DISABLED,
+ flashloanable: EngineFlags.ENABLED,
ltv: 40_00,
liqThreshold: 50_00,
liqBonus: 5_00,
@@ -277,13 +278,13 @@ exports[`feature: assetListing > should return reasonable code 1`] = `
liqProtocolFee: 20_00,
rateStrategyParams: IV3RateStrategyFactory.RateStrategyParams({
optimalUsageRatio: _bpsToRay(80_00),
- baseVariableBorrowRate: _bpsToRay(0_00),
+ baseVariableBorrowRate: _bpsToRay(0),
variableRateSlope1: _bpsToRay(10_00),
variableRateSlope2: _bpsToRay(100_00),
stableRateSlope1: _bpsToRay(10_00),
stableRateSlope2: _bpsToRay(100_00),
baseStableRateOffset: _bpsToRay(1_00),
- stableRateExcessOffset: _bpsToRay(),
+ stableRateExcessOffset: _bpsToRay(0),
optimalStableToTotalDebtRatio: _bpsToRay(10_00)
})
});
diff --git a/generator/features/__snapshots__/priceFeedsUpdate.spec.ts.snap b/generator/features/__snapshots__/priceFeedsUpdate.spec.ts.snap
new file mode 100644
index 000000000..7787856d3
--- /dev/null
+++ b/generator/features/__snapshots__/priceFeedsUpdate.spec.ts.snap
@@ -0,0 +1,192 @@
+// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
+
+exports[`feature: priceFeedsUpdates > should properly generate files 1`] = `
+{
+ "aip": "---
+title: \\"test\\"
+author: \\"test\\"
+discussions: \\"test\\"
+---
+
+## Simple Summary
+
+## Motivation
+
+## Specification
+
+## References
+
+- Implementation: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20231023_AaveV3Ethereum_Test/AaveV3Ethereum_Test_20231023.sol)
+- Tests: [AaveV3Ethereum](https://github.com/bgd-labs/aave-proposals-v3/blob/main/src/20231023_AaveV3Ethereum_Test/AaveV3Ethereum_Test_20231023.t.sol)
+- [Snapshot](test)
+- [Discussion](test)
+
+## Copyright
+
+Copyright and related rights waived via [CC0](https://creativecommons.org/publicdomain/zero/1.0/).
+",
+ "jsonConfig": "{
+ \\"rootOptions\\": {
+ \\"pools\\": [
+ \\"AaveV3Ethereum\\"
+ ],
+ \\"title\\": \\"test\\",
+ \\"shortName\\": \\"Test\\",
+ \\"date\\": \\"20231023\\",
+ \\"author\\": \\"test\\",
+ \\"discussion\\": \\"test\\",
+ \\"snapshot\\": \\"test\\"
+ },
+ \\"poolOptions\\": {
+ \\"AaveV3Ethereum\\": {
+ \\"configs\\": {
+ \\"PRICE_FEEDS_UPDATE\\": [
+ {
+ \\"asset\\": \\"DAI\\",
+ \\"priceFeed\\": \\"0xae7ab96520de3a18e5e111b5eaab095312d7fe84\\"
+ }
+ ]
+ },
+ \\"features\\": [
+ \\"PRICE_FEEDS_UPDATE\\"
+ ]
+ }
+ }
+}",
+ "payloads": [
+ {
+ "contractName": "AaveV3Ethereum_Test_20231023",
+ "payload": "// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3EthereumAssets} from 'aave-address-book/AaveV3Ethereum.sol';
+import {AaveV3PayloadEthereum} from 'aave-helpers/v3-config-engine/AaveV3PayloadEthereum.sol';
+import {IAaveV3ConfigEngine} from 'aave-helpers/v3-config-engine/IAaveV3ConfigEngine.sol';
+
+/**
+ * @title test
+ * @author test
+ * - Snapshot: test
+ * - Discussion: test
+ */
+contract AaveV3Ethereum_Test_20231023 is AaveV3PayloadEthereum {
+ function priceFeedsUpdates()
+ public
+ pure
+ override
+ returns (IAaveV3ConfigEngine.PriceFeedUpdate[] memory)
+ {
+ IAaveV3ConfigEngine.PriceFeedUpdate[]
+ memory priceFeedUpdates = new IAaveV3ConfigEngine.PriceFeedUpdate[](1);
+
+ priceFeedUpdates[0] = IAaveV3ConfigEngine.PriceFeedUpdate({
+ asset: AaveV3EthereumAssets.DAI_UNDERLYING,
+ priceFeed: 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84
+ });
+
+ return priceFeedUpdates;
+ }
+}
+",
+ "test": "// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {AaveV3Ethereum} from 'aave-address-book/AaveV3Ethereum.sol';
+
+import 'forge-std/Test.sol';
+import {ProtocolV3TestBase, ReserveConfig} from 'aave-helpers/ProtocolV3TestBase.sol';
+import {AaveV3Ethereum_Test_20231023} from './AaveV3Ethereum_Test_20231023.sol';
+
+/**
+ * @dev Test for AaveV3Ethereum_Test_20231023
+ * command: make test-contract filter=AaveV3Ethereum_Test_20231023
+ */
+contract AaveV3Ethereum_Test_20231023_Test is ProtocolV3TestBase {
+ AaveV3Ethereum_Test_20231023 internal proposal;
+
+ function setUp() public {
+ vm.createSelectFork(vm.rpcUrl('mainnet'), 18487480);
+ proposal = new AaveV3Ethereum_Test_20231023();
+ }
+
+ /**
+ * @dev executes the generic test suite including e2e and config snapshots
+ */
+ function test_defaultProposalExecution() public {
+ defaultTest('AaveV3Ethereum_Test_20231023', AaveV3Ethereum.POOL, address(proposal));
+ }
+}
+",
+ },
+ ],
+ "script": "// SPDX-License-Identifier: MIT
+pragma solidity ^0.8.0;
+
+import {GovV3Helpers, IPayloadsControllerCore, PayloadsControllerUtils} from 'aave-helpers/GovV3Helpers.sol';
+import {EthereumScript} from 'aave-helpers/ScriptUtils.sol';
+import {AaveV3Ethereum_Test_20231023} from './AaveV3Ethereum_Test_20231023.sol';
+
+/**
+ * @dev Deploy Ethereum
+ * command: make deploy-ledger contract=src/20231023_AaveV3Ethereum_Test/Test_20231023.s.sol:DeployEthereum chain=mainnet
+ */
+contract DeployEthereum is EthereumScript {
+ function run() external broadcast {
+ // deploy payloads
+ AaveV3Ethereum_Test_20231023 payload0 = new AaveV3Ethereum_Test_20231023();
+
+ // compose action
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actions = new IPayloadsControllerCore.ExecutionAction[](1);
+ actions[0] = GovV3Helpers.buildAction(address(payload0));
+
+ // register action at payloadsController
+ GovV3Helpers.createPayload(actions);
+ }
+}
+
+/**
+ * @dev Create Proposal
+ * command: make deploy-ledger contract=src/20231023_AaveV3Ethereum_Test/Test_20231023.s.sol:CreateProposal chain=mainnet
+ */
+contract CreateProposal is EthereumScript {
+ function run() external {
+ // create payloads
+ PayloadsControllerUtils.Payload[] memory payloads = new PayloadsControllerUtils.Payload[](1);
+
+ // compose actions for validation
+ IPayloadsControllerCore.ExecutionAction[]
+ memory actionsEthereum = new IPayloadsControllerCore.ExecutionAction[](1);
+ actionsEthereum[0] = GovV3Helpers.buildAction(address(0));
+ payloads[0] = GovV3Helpers.buildMainnetPayload(vm, actionsEthereum);
+
+ // create proposal
+ vm.startBroadcast();
+ GovV3Helpers.createProposal2_5(
+ payloads,
+ GovV3Helpers.ipfsHashFile(vm, 'src/20231023_AaveV3Ethereum_Test/Test.md')
+ );
+ }
+}
+",
+}
+`;
+
+exports[`feature: priceFeedsUpdates > should return reasonable code 1`] = `
+{
+ "code": {
+ "fn": [
+ "function priceFeedsUpdates() public pure override returns (IAaveV3ConfigEngine.PriceFeedUpdate[] memory) {
+ IAaveV3ConfigEngine.PriceFeedUpdate[] memory priceFeedUpdates = new IAaveV3ConfigEngine.PriceFeedUpdate[](1);
+
+ priceFeedUpdates[0] = IAaveV3ConfigEngine.PriceFeedUpdate({
+ asset: AaveV3EthereumAssets.DAI_UNDERLYING,
+ priceFeed: 0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84
+ });
+
+ return priceFeedUpdates;
+ }",
+ ],
+ },
+}
+`;
diff --git a/generator/features/assetListing.ts b/generator/features/assetListing.ts
index fbf352f34..960a72394 100644
--- a/generator/features/assetListing.ts
+++ b/generator/features/assetListing.ts
@@ -1,5 +1,5 @@
import {CodeArtifact, FEATURE, FeatureModule, PoolIdentifier} from '../types';
-import {addressInput, eModeSelect, stringInput} from '../prompts';
+import {eModeSelect} from '../prompts';
import {fetchBorrowUpdate} from './borrowsUpdates';
import {fetchRateStrategyParamsV3} from './rateUpdates';
import {fetchCollateralUpdate} from './collateralsUpdates';
@@ -9,11 +9,13 @@ import {CHAIN_TO_CHAIN_OBJECT, getPoolChain} from '../common';
import {createPublicClient, getContract, http} from 'viem';
import {confirm} from '@inquirer/prompts';
import {TEST_EXECUTE_PROPOSAL} from '../utils/constants';
+import {addressPrompt, translateJsAddressToSol} from '../prompts/addressPrompt';
+import {stringPrompt} from '../prompts/stringPrompt';
async function fetchListing(pool: PoolIdentifier): Promise
{
- const asset = await addressInput({
+ const asset = await addressPrompt({
message: 'Enter the address of the asset you want to list',
- disableKeepCurrent: true,
+ required: true,
});
const chain = getPoolChain(pool);
@@ -56,13 +58,13 @@ async function fetchListing(pool: PoolIdentifier): Promise {
const decimals = await erc20.read.decimals();
return {
- assetSymbol: await stringInput({
+ assetSymbol: await stringPrompt({
message: 'Enter the asset symbol',
- disableKeepCurrent: true,
+ required: true,
defaultValue: symbol,
}),
decimals,
- priceFeed: await addressInput({message: 'PriceFeed address', disableKeepCurrent: true}),
+ priceFeed: await addressPrompt({message: 'PriceFeed address', required: true}),
...(await fetchCollateralUpdate(pool, true)),
...(await fetchBorrowUpdate(true)),
...(await fetchCapsUpdate(true)),
@@ -78,9 +80,9 @@ async function fetchListing(pool: PoolIdentifier): Promise {
async function fetchCustomImpl(): Promise {
return {
- aToken: await addressInput({message: 'aToken implementation', disableKeepCurrent: true}),
- vToken: await addressInput({message: 'vToken implementation', disableKeepCurrent: true}),
- sToken: await addressInput({message: 'sToken implementation', disableKeepCurrent: true}),
+ aToken: await addressPrompt({message: 'aToken implementation', required: true}),
+ vToken: await addressPrompt({message: 'vToken implementation', required: true}),
+ sToken: await addressPrompt({message: 'sToken implementation', required: true}),
};
}
@@ -102,7 +104,7 @@ export const assetListing: FeatureModule = {
code: {
constants: cfg
.map((cfg) => [
- `address public constant ${cfg.assetSymbol} = ${cfg.asset};`,
+ `address public constant ${cfg.assetSymbol} = ${translateJsAddressToSol(cfg.asset)};`,
`uint256 internal constant ${cfg.assetSymbol}_SEED_AMOUNT = 10 ** ${cfg.decimals};`,
])
.flat(),
@@ -122,7 +124,7 @@ export const assetListing: FeatureModule = {
(cfg, ix) => `listings[${ix}] = IAaveV3ConfigEngine.Listing({
asset: ${cfg.assetSymbol},
assetSymbol: "${cfg.assetSymbol}",
- priceFeed: ${cfg.priceFeed},
+ priceFeed: ${translateJsAddressToSol(cfg.priceFeed)},
eModeCategory: ${cfg.eModeCategory},
enabledToBorrow: ${cfg.enabledToBorrow},
stableRateModeEnabled: ${cfg.stableRateModeEnabled},
@@ -146,7 +148,9 @@ export const assetListing: FeatureModule = {
stableRateSlope2: ${cfg.rateStrategyParams.stableRateSlope2},
baseStableRateOffset: ${cfg.rateStrategyParams.baseStableRateOffset},
stableRateExcessOffset: ${cfg.rateStrategyParams.stableRateExcessOffset},
- optimalStableToTotalDebtRatio: ${cfg.rateStrategyParams.optimalStableToTotalDebtRatio}
+ optimalStableToTotalDebtRatio: ${
+ cfg.rateStrategyParams.optimalStableToTotalDebtRatio
+ }
})
});`
)
@@ -186,7 +190,10 @@ export const assetListingCustom: FeatureModule = {
const response: CodeArtifact = {
code: {
constants: cfg.map(
- (cfg) => `address public constant ${cfg.base.assetSymbol} = ${cfg.base.asset};`
+ (cfg) =>
+ `address public constant ${cfg.base.assetSymbol} = ${translateJsAddressToSol(
+ cfg.base.asset
+ )};`
),
execute: cfg.map(
(cfg) =>
@@ -205,7 +212,7 @@ export const assetListingCustom: FeatureModule = {
IAaveV3ConfigEngine.Listing({
asset: ${cfg.base.assetSymbol},
assetSymbol: "${cfg.base.assetSymbol}",
- priceFeed: ${cfg.base.priceFeed},
+ priceFeed: ${translateJsAddressToSol(cfg.base.priceFeed)},
eModeCategory: ${cfg.base.eModeCategory},
enabledToBorrow: ${cfg.base.enabledToBorrow},
stableRateModeEnabled: ${cfg.base.stableRateModeEnabled},
@@ -229,13 +236,15 @@ export const assetListingCustom: FeatureModule = {
stableRateSlope2: ${cfg.base.rateStrategyParams.stableRateSlope2},
baseStableRateOffset: ${cfg.base.rateStrategyParams.baseStableRateOffset},
stableRateExcessOffset: ${cfg.base.rateStrategyParams.stableRateExcessOffset},
- optimalStableToTotalDebtRatio: ${cfg.base.rateStrategyParams.optimalStableToTotalDebtRatio}
+ optimalStableToTotalDebtRatio: ${
+ cfg.base.rateStrategyParams.optimalStableToTotalDebtRatio
+ }
})
}),
IAaveV3ConfigEngine.TokenImplementations({
- aToken: ${cfg.implementations.aToken},
- vToken: ${cfg.implementations.vToken},
- sToken: ${cfg.implementations.sToken}
+ aToken: ${translateJsAddressToSol(cfg.implementations.aToken)},
+ vToken: ${translateJsAddressToSol(cfg.implementations.vToken)},
+ sToken: ${translateJsAddressToSol(cfg.implementations.sToken)}
})
);`
)
diff --git a/generator/features/borrowsUpdates.ts b/generator/features/borrowsUpdates.ts
index 029751679..cc7af61c8 100644
--- a/generator/features/borrowsUpdates.ts
+++ b/generator/features/borrowsUpdates.ts
@@ -1,6 +1,10 @@
import {CodeArtifact, ENGINE_FLAGS, FEATURE, FeatureModule} from '../types';
-import {assetsSelect, booleanSelect, percentInput} from '../prompts';
+import {booleanSelect, percentInput} from '../prompts';
import {BorrowUpdate} from './types';
+import {
+ assetsSelectPrompt,
+ translateAssetToAssetLibUnderlying,
+} from '../prompts/assetsSelectPrompt';
export async function fetchBorrowUpdate(disableKeepCurrent?: T) {
return {
@@ -41,7 +45,7 @@ export const borrowsUpdates: FeatureModule = {
description:
'BorrowsUpdates (enabledToBorrow, flashloanable, stableRateModeEnabled, borrowableInIsolation, withSiloedBorrowing, reserveFactor)',
async cli(opt, pool) {
- const assets = await assetsSelect({
+ const assets = await assetsSelectPrompt({
message: 'Select the assets you want to amend',
pool,
});
@@ -64,7 +68,7 @@ export const borrowsUpdates: FeatureModule = {
${cfg
.map(
(cfg, ix) => `borrowUpdates[${ix}] = IAaveV3ConfigEngine.BorrowUpdate({
- asset: ${cfg.asset},
+ asset: ${translateAssetToAssetLibUnderlying(cfg.asset, pool)},
enabledToBorrow: ${cfg.enabledToBorrow},
flashloanable: ${cfg.flashloanable},
stableRateModeEnabled: ${cfg.stableRateModeEnabled},
diff --git a/generator/features/capsUpdates.ts b/generator/features/capsUpdates.ts
index f172f07f6..1b64a0c24 100644
--- a/generator/features/capsUpdates.ts
+++ b/generator/features/capsUpdates.ts
@@ -1,6 +1,10 @@
-import {CodeArtifact, FEATURE, FeatureModule, PoolIdentifier} from '../types';
-import {assetsSelect, numberInput} from '../prompts';
+import {CodeArtifact, FEATURE, FeatureModule} from '../types';
+import {numberInput} from '../prompts';
import {CapsUpdate, CapsUpdatePartial} from './types';
+import {
+ assetsSelectPrompt,
+ translateAssetToAssetLibUnderlying,
+} from '../prompts/assetsSelectPrompt';
export async function fetchCapsUpdate(disableKeepCurrent?: boolean): Promise {
return {
@@ -22,7 +26,7 @@ export const capsUpdates: FeatureModule = {
description: 'CapsUpdates (supplyCap, borrowCap)',
async cli(opt, pool) {
console.log(`Fetching information for CapsUpdates on ${pool}`);
- const assets = await assetsSelect({
+ const assets = await assetsSelectPrompt({
message: 'Select the assets you want to amend',
pool,
});
@@ -46,7 +50,7 @@ export const capsUpdates: FeatureModule = {
${cfg
.map(
(cfg, ix) => `capsUpdate[${ix}] = IAaveV3ConfigEngine.CapsUpdate({
- asset: ${cfg.asset},
+ asset: ${translateAssetToAssetLibUnderlying(cfg.asset, pool)},
supplyCap: ${cfg.supplyCap},
borrowCap: ${cfg.borrowCap}
});`
diff --git a/generator/features/collateralsUpdates.ts b/generator/features/collateralsUpdates.ts
index 6989d1343..f967c12a0 100644
--- a/generator/features/collateralsUpdates.ts
+++ b/generator/features/collateralsUpdates.ts
@@ -1,6 +1,10 @@
-import {CodeArtifact, ENGINE_FLAGS, FEATURE, FeatureModule, PoolIdentifier} from '../types';
-import {assetsSelect, eModeSelect, numberInput, percentInput} from '../prompts';
+import {CodeArtifact, FEATURE, FeatureModule, PoolIdentifier} from '../types';
+import {numberInput, percentInput} from '../prompts';
import {CollateralUpdate, CollateralUpdatePartial} from './types';
+import {
+ assetsSelectPrompt,
+ translateAssetToAssetLibUnderlying,
+} from '../prompts/assetsSelectPrompt';
export async function fetchCollateralUpdate(
pool: PoolIdentifier,
@@ -39,7 +43,7 @@ export const collateralsUpdates: FeatureModule = {
console.log(`Fetching information for Collateral Updates on ${pool}`);
const response: CollateralUpdates = [];
- const assets = await assetsSelect({
+ const assets = await assetsSelectPrompt({
message: 'Select the assets you want to amend',
pool,
});
@@ -62,7 +66,7 @@ export const collateralsUpdates: FeatureModule = {
${cfg
.map(
(cfg, ix) => `collateralUpdate[${ix}] = IAaveV3ConfigEngine.CollateralUpdate({
- asset: ${cfg.asset},
+ asset: ${translateAssetToAssetLibUnderlying(cfg.asset, pool)},
ltv: ${cfg.ltv},
liqThreshold: ${cfg.liqThreshold},
liqBonus: ${cfg.liqBonus},
diff --git a/generator/features/eModesAssets.ts b/generator/features/eModesAssets.ts
index 6ec33d3d3..74be7fd90 100644
--- a/generator/features/eModesAssets.ts
+++ b/generator/features/eModesAssets.ts
@@ -1,10 +1,14 @@
import {CodeArtifact, FEATURE, FeatureModule, PoolIdentifier} from '../types';
-import {assetsSelect, eModeSelect} from '../prompts';
+import {eModeSelect} from '../prompts';
import {AssetEModeUpdate} from './types';
+import {
+ assetsSelectPrompt,
+ translateAssetToAssetLibUnderlying,
+} from '../prompts/assetsSelectPrompt';
async function subCli(pool: PoolIdentifier) {
console.log(`Fetching information for Emode assets on ${pool}`);
- const assets = await assetsSelect({
+ const assets = await assetsSelectPrompt({
message: 'Select the assets you want to amend eMode for',
pool,
});
@@ -44,7 +48,7 @@ export const eModeAssets: FeatureModule = {
${cfg
.map(
(cfg, ix) => `assetEModeUpdates[${ix}] = IAaveV3ConfigEngine.AssetEModeUpdate({
- asset: ${cfg.asset},
+ asset: ${translateAssetToAssetLibUnderlying(cfg.asset, pool)},
eModeCategory: ${cfg.eModeCategory}
});`
)
diff --git a/generator/features/eModesUpdates.ts b/generator/features/eModesUpdates.ts
index af37cc582..51e852a7d 100644
--- a/generator/features/eModesUpdates.ts
+++ b/generator/features/eModesUpdates.ts
@@ -1,15 +1,18 @@
import {CodeArtifact, FEATURE, FeatureModule, PoolIdentifier} from '../types';
-import {addressInput, eModesSelect, percentInput, stringInput} from '../prompts';
+import {eModesSelect, percentInput} from '../prompts';
import {EModeCategoryUpdate} from './types';
import {confirm} from '@inquirer/prompts';
+import {addressPrompt, translateJsAddressToSol} from '../prompts/addressPrompt';
+import {stringOrKeepCurrent, stringPrompt} from '../prompts/stringPrompt';
+import {zeroAddress} from 'viem';
+import {getEModes} from '../common';
async function fetchEmodeCategoryUpdate(
- disableKeepCurrent?: T,
- eModeCategory?: string
+ eModeCategory: string | number,
+ disableKeepCurrent?: T
): Promise {
return {
- eModeCategory:
- eModeCategory ?? (await stringInput({message: 'eModeCategory', disableKeepCurrent})),
+ eModeCategory,
ltv: await percentInput({
message: 'ltv',
disableKeepCurrent,
@@ -22,13 +25,14 @@ async function fetchEmodeCategoryUpdate(
message: 'liqBonus',
disableKeepCurrent,
}),
- priceSource: await addressInput({
+ priceSource: await addressPrompt({
message: 'Price Source',
- disableKeepCurrent,
+ required: disableKeepCurrent,
+ defaultValue: disableKeepCurrent ? zeroAddress : '',
}),
- label: await stringInput({
+ label: await stringPrompt({
message: 'label',
- disableKeepCurrent,
+ required: disableKeepCurrent,
}),
};
}
@@ -42,8 +46,11 @@ async function subCli(pool: PoolIdentifier) {
});
if (shouldAddNewCategory) {
let more: boolean = true;
+ const eModes = getEModes(pool as any);
+ let highestEmode = Object.values(eModes).length > 0 ? Math.max(...Object.values(eModes)) : 0;
+
while (more) {
- answers.push(await fetchEmodeCategoryUpdate(true));
+ answers.push(await fetchEmodeCategoryUpdate(++highestEmode, true));
more = await confirm({message: 'Do you want to add another emode category?', default: false});
}
}
@@ -61,7 +68,7 @@ async function subCli(pool: PoolIdentifier) {
if (eModeCategories) {
for (const eModeCategory of eModeCategories) {
console.log(`collecting info for ${eModeCategory}`);
- answers.push(await fetchEmodeCategoryUpdate(false, eModeCategory));
+ answers.push(await fetchEmodeCategoryUpdate(eModeCategory));
}
}
}
@@ -94,10 +101,8 @@ export const eModeUpdates: FeatureModule = {
ltv: ${cfg.ltv},
liqThreshold: ${cfg.liqThreshold},
liqBonus: ${cfg.liqBonus},
- priceSource: ${cfg.priceSource},
- label: ${
- cfg.label == 'EngineFlags.KEEP_CURRENT_STRING' ? cfg.label : `"${cfg.label}"`
- }
+ priceSource: ${translateJsAddressToSol(cfg.priceSource)},
+ label: ${stringOrKeepCurrent(cfg.label)}
});`
)
.join('\n')}
diff --git a/generator/features/flashBorrower.ts b/generator/features/flashBorrower.ts
index cbf837c32..75818a13f 100644
--- a/generator/features/flashBorrower.ts
+++ b/generator/features/flashBorrower.ts
@@ -1,7 +1,7 @@
import {CodeArtifact, FEATURE, FeatureModule} from '../types';
-import {addressInput} from '../prompts';
import {Hex} from 'viem';
import {TEST_EXECUTE_PROPOSAL} from '../utils/constants';
+import {addressPrompt, translateJsAddressToSol} from '../prompts/addressPrompt';
type FlashBorrower = {
address: Hex;
@@ -13,9 +13,9 @@ export const flashBorrower: FeatureModule = {
async cli(opt, pool) {
console.log(`Fetching information for FlashBorrower on ${pool}`);
const response: FlashBorrower = {
- address: await addressInput({
+ address: await addressPrompt({
message: 'Who do you want to grant the flashBorrower role',
- disableKeepCurrent: true,
+ required: true,
}),
};
return response;
@@ -23,7 +23,9 @@ export const flashBorrower: FeatureModule = {
build(opt, pool, cfg) {
const response: CodeArtifact = {
code: {
- constants: [`address public constant NEW_FLASH_BORROWER = address(${cfg.address});`],
+ constants: [
+ `address public constant NEW_FLASH_BORROWER = ${translateJsAddressToSol(cfg.address)};`,
+ ],
execute: [`${pool}.ACL_MANAGER.addFlashBorrower(NEW_FLASH_BORROWER);`],
},
test: {
diff --git a/generator/features/mocks/configs.ts b/generator/features/mocks/configs.ts
index eb0f1ccce..007a1658b 100644
--- a/generator/features/mocks/configs.ts
+++ b/generator/features/mocks/configs.ts
@@ -1,5 +1,5 @@
import {Options} from '../../types';
-import {CapsUpdate, Listing} from '../types';
+import {EModeCategoryUpdate, Listing, PriceFeedUpdate} from '../types';
export const MOCK_OPTIONS: Options = {
pools: ['AaveV3Ethereum'],
@@ -21,26 +21,52 @@ export const assetListingConfig: Listing[] = [
liqBonus: '5_00',
debtCeiling: '100_000',
liqProtocolFee: '20_00',
- enabledToBorrow: 'ENABLED',
- flashloanable: 'ENABLED',
- stableRateModeEnabled: 'DISABLED',
- borrowableInIsolation: 'DISABLED',
- withSiloedBorrowing: 'DISABLED',
+ enabledToBorrow: 'EngineFlags.ENABLED',
+ flashloanable: 'EngineFlags.ENABLED',
+ stableRateModeEnabled: 'EngineFlags.DISABLED',
+ borrowableInIsolation: 'EngineFlags.DISABLED',
+ withSiloedBorrowing: 'EngineFlags.DISABLED',
reserveFactor: '20_00',
supplyCap: '10_000',
borrowCap: '5_000',
rateStrategyParams: {
optimalUtilizationRate: '_bpsToRay(80_00)',
- baseVariableBorrowRate: '_bpsToRay(0_00)',
+ baseVariableBorrowRate: '_bpsToRay(0)',
variableRateSlope1: '_bpsToRay(10_00)',
variableRateSlope2: '_bpsToRay(100_00)',
stableRateSlope1: '_bpsToRay(10_00)',
stableRateSlope2: '_bpsToRay(100_00)',
baseStableRateOffset: '_bpsToRay(1_00)',
- stableRateExcessOffset: '_bpsToRay()',
+ stableRateExcessOffset: '_bpsToRay(0)',
optimalStableToTotalDebtRatio: '_bpsToRay(10_00)',
},
eModeCategory: 'AaveV3EthereumEModes.NONE',
asset: '0xcAfE001067cDEF266AfB7Eb5A286dCFD277f3dE5',
},
];
+
+export const priceFeedsUpdateConfig: PriceFeedUpdate[] = [
+ {
+ asset: 'DAI',
+ priceFeed: '0xae7ab96520de3a18e5e111b5eaab095312d7fe84',
+ },
+];
+
+export const emodeUpdates: EModeCategoryUpdate[] = [
+ {
+ eModeCategory: 2,
+ ltv: '20_00',
+ liqThreshold: '30_00',
+ liqBonus: '5_00',
+ priceSource: '0x0000000000000000000000000000000000000000',
+ label: 'label',
+ },
+ {
+ eModeCategory: 'AaveV3EthereumEModes.ETH_CORRELATED',
+ ltv: 'EngineFlags.KEEP_CURRENT',
+ liqThreshold: '50_00',
+ liqBonus: 'EngineFlags.KEEP_CURRENT',
+ priceSource: '',
+ label: '',
+ },
+];
diff --git a/generator/features/priceFeedsUpdate.spec.ts b/generator/features/priceFeedsUpdate.spec.ts
new file mode 100644
index 000000000..e18604920
--- /dev/null
+++ b/generator/features/priceFeedsUpdate.spec.ts
@@ -0,0 +1,28 @@
+// sum.test.js
+import {expect, describe, it} from 'vitest';
+import {MOCK_OPTIONS, priceFeedsUpdateConfig} from './mocks/configs';
+import {generateFiles} from '../generator';
+import {FEATURE, PoolConfigs} from '../types';
+import {priceFeedsUpdates} from './priceFeedsUpdates';
+
+describe('feature: priceFeedsUpdates', () => {
+ it('should return reasonable code', () => {
+ const output = priceFeedsUpdates.build(MOCK_OPTIONS, 'AaveV3Ethereum', priceFeedsUpdateConfig);
+ expect(output).toMatchSnapshot();
+ });
+
+ it('should properly generate files', async () => {
+ const poolConfigs: PoolConfigs = {
+ [MOCK_OPTIONS.pools[0]]: {
+ pool: MOCK_OPTIONS.pools[0],
+ features: [FEATURE.PRICE_FEEDS_UPDATE],
+ artifacts: [
+ priceFeedsUpdates.build(MOCK_OPTIONS, 'AaveV3Ethereum', priceFeedsUpdateConfig),
+ ],
+ configs: {[FEATURE.PRICE_FEEDS_UPDATE]: priceFeedsUpdateConfig},
+ },
+ };
+ const files = await generateFiles(MOCK_OPTIONS, poolConfigs);
+ expect(files).toMatchSnapshot();
+ });
+});
diff --git a/generator/features/priceFeedsUpdates.ts b/generator/features/priceFeedsUpdates.ts
index 397c88d5d..2e9e6ec72 100644
--- a/generator/features/priceFeedsUpdates.ts
+++ b/generator/features/priceFeedsUpdates.ts
@@ -1,12 +1,16 @@
-import {CodeArtifact, FEATURE, FeatureModule, PoolIdentifier} from '../types';
-import {addressInput, assetsSelect} from '../prompts';
+import {CodeArtifact, FEATURE, FeatureModule} from '../types';
import {PriceFeedUpdate, PriceFeedUpdatePartial} from './types';
+import {addressPrompt, translateJsAddressToSol} from '../prompts/addressPrompt';
+import {
+ assetsSelectPrompt,
+ translateAssetToAssetLibUnderlying,
+} from '../prompts/assetsSelectPrompt';
async function fetchPriceFeedUpdate(): Promise {
return {
- priceFeed: await addressInput({
+ priceFeed: await addressPrompt({
message: 'New price feed address',
- disableKeepCurrent: true,
+ required: true,
}),
};
}
@@ -16,7 +20,7 @@ export const priceFeedsUpdates: FeatureModule = {
description: 'PriceFeedsUpdates (replacing priceFeeds)',
async cli(opt, pool) {
const response: PriceFeedUpdate[] = [];
- const assets = await assetsSelect({
+ const assets = await assetsSelectPrompt({
message: 'Select the assets you want to amend',
pool,
});
@@ -38,8 +42,8 @@ export const priceFeedsUpdates: FeatureModule = {
${cfg
.map(
(cfg, ix) => `priceFeedUpdates[${ix}] = IAaveV3ConfigEngine.PriceFeedUpdate({
- asset: ${cfg.asset},
- priceFeed: ${cfg.priceFeed}
+ asset: ${translateAssetToAssetLibUnderlying(cfg.asset, pool)},
+ priceFeed: ${translateJsAddressToSol(cfg.priceFeed)}
});`
)
.join('\n')}
diff --git a/generator/features/rateUpdates.ts b/generator/features/rateUpdates.ts
index a46717a3f..00e858fe0 100644
--- a/generator/features/rateUpdates.ts
+++ b/generator/features/rateUpdates.ts
@@ -1,6 +1,10 @@
-import {CodeArtifact, FEATURE, FeatureModule, PoolIdentifier} from '../types';
-import {assetsSelect, percentInput} from '../prompts';
+import {CodeArtifact, FEATURE, FeatureModule} from '../types';
+import {percentInput} from '../prompts';
import {RateStrategyParams, RateStrategyUpdate} from './types';
+import {
+ assetsSelectPrompt,
+ translateAssetToAssetLibUnderlying,
+} from '../prompts/assetsSelectPrompt';
export async function fetchRateStrategyParamsV2(
disableKeepCurrent?: boolean
@@ -66,7 +70,7 @@ export const rateUpdatesV2: FeatureModule = {
description: 'RateStrategiesUpdates',
async cli(opt, pool) {
console.log(`Fetching information for RatesUpdate on ${pool}`);
- const assets = await assetsSelect({
+ const assets = await assetsSelectPrompt({
message: 'Select the assets you want to amend',
pool,
});
@@ -93,7 +97,7 @@ export const rateUpdatesV2: FeatureModule = {
${cfg
.map(
(cfg, ix) => `rateStrategies[${ix}] = IAaveV2ConfigEngine.RateStrategyUpdate({
- asset: ${cfg.asset},
+ asset: ${translateAssetToAssetLibUnderlying(cfg.asset, pool)},
params: IV2RateStrategyFactory.RateStrategyParams({
optimalUtilizationRate: ${cfg.params.optimalUtilizationRate},
baseVariableBorrowRate: ${cfg.params.baseVariableBorrowRate},
@@ -121,7 +125,7 @@ export const rateUpdatesV3: FeatureModule = {
description: 'RateStrategiesUpdates',
async cli(opt, pool) {
console.log(`Fetching information for RatesUpdate on ${pool}`);
- const assets = await assetsSelect({
+ const assets = await assetsSelectPrompt({
message: 'Select the assets you want to amend',
pool,
});
@@ -148,7 +152,7 @@ export const rateUpdatesV3: FeatureModule = {
${cfg
.map(
(cfg, ix) => `rateStrategies[${ix}] = IAaveV3ConfigEngine.RateStrategyUpdate({
- asset: ${cfg.asset},
+ asset: ${translateAssetToAssetLibUnderlying(cfg.asset, pool)},
params: IV3RateStrategyFactory.RateStrategyParams({
optimalUsageRatio: ${cfg.params.optimalUtilizationRate},
baseVariableBorrowRate: ${cfg.params.baseVariableBorrowRate},
diff --git a/generator/features/types.ts b/generator/features/types.ts
index 94f565e23..caf89be92 100644
--- a/generator/features/types.ts
+++ b/generator/features/types.ts
@@ -1,10 +1,5 @@
import {Hex} from 'viem';
-import {
- AddressInputValues,
- BooleanSelectValues,
- NumberInputValues,
- PercentInputValues,
-} from '../prompts';
+import {BooleanSelectValues, NumberInputValues, PercentInputValues} from '../prompts';
export interface AssetSelector {
asset: string;
@@ -57,11 +52,12 @@ export interface AssetEModeUpdatePartial {
export interface AssetEModeUpdate extends AssetEModeUpdatePartial, AssetSelector {}
export interface EModeCategoryUpdate {
- eModeCategory: string;
+ // library accessor or new id
+ eModeCategory: string | number;
ltv: NumberInputValues;
liqThreshold: NumberInputValues;
liqBonus: NumberInputValues;
- priceSource: AddressInputValues;
+ priceSource?: Hex | '';
label: string;
}
diff --git a/generator/prompts.ts b/generator/prompts.ts
index 129bed84c..97e002036 100644
--- a/generator/prompts.ts
+++ b/generator/prompts.ts
@@ -1,7 +1,6 @@
import {checkbox, input, select} from '@inquirer/prompts';
import {ENGINE_FLAGS, PoolIdentifier} from './types';
import {getAssets, getEModes} from './common';
-import {Hex, getAddress, isAddress} from 'viem';
import {advancedInput} from './prompts/advancedInput';
// VALIDATION
@@ -14,11 +13,6 @@ function isNumberOrKeepCurrent(value: string) {
return 'Must be number or KEEP_CURRENT';
}
-function isAddressOrKeepCurrent(value: string) {
- if (value == ENGINE_FLAGS.KEEP_CURRENT_ADDRESS || isAddress(value)) return true;
- return 'Must be a valid address';
-}
-
// TRANSFORMS
export function transformNumberToPercent(value: string) {
if (value && isNumber(value)) {
@@ -57,11 +51,6 @@ export function translateJsNumberToSol(value: string) {
return String(value).replace(/\B(?=(\d{3})+(?!\d))/g, '_');
}
-function translateJsAddressToSol(value: string) {
- if (value === ENGINE_FLAGS.KEEP_CURRENT_ADDRESS) return `EngineFlags.KEEP_CURRENT_ADDRESS`;
- return getAddress(value);
-}
-
function translateJsBoolToSol(value: string) {
switch (value) {
case ENGINE_FLAGS.ENABLED:
@@ -75,20 +64,11 @@ function translateJsBoolToSol(value: string) {
}
}
-function translateJsStringToSol(value: string) {
- if (value === ENGINE_FLAGS.KEEP_CURRENT_STRING) return `EngineFlags.KEEP_CURRENT_STRING`;
- return value;
-}
-
function translateEModeToEModeLib(value: string, pool: PoolIdentifier) {
if (value === ENGINE_FLAGS.KEEP_CURRENT) return `EngineFlags.KEEP_CURRENT`;
return `${pool}EModes.${value}`;
}
-function translateAssetToAssetLibUnderlying(value: string, pool: PoolIdentifier) {
- return `${pool}Assets.${value}_UNDERLYING`;
-}
-
// PROMPTS
interface GenericPrompt {
message: string;
@@ -131,7 +111,7 @@ export type PercentInputValues = typeof ENGINE_FLAGS.KEEP_CURRENT | string;
export async function percentInput(
{message, disableKeepCurrent, toRay}: PercentInputPrompt,
- opts
+ opts?
): Promise> {
const value = await advancedInput(
{
@@ -149,7 +129,7 @@ export async function percentInput(
export type NumberInputValues = typeof ENGINE_FLAGS.KEEP_CURRENT | string;
-export async function numberInput({message, disableKeepCurrent}: GenericPrompt, opts) {
+export async function numberInput({message, disableKeepCurrent}: GenericPrompt, opts?) {
const value = await advancedInput(
{
message,
@@ -164,37 +144,6 @@ export async function numberInput({message, disableKeepCurrent}: GenericPrompt,
return translateJsNumberToSol(value);
}
-export type AddressInputValues = Hex | typeof ENGINE_FLAGS.KEEP_CURRENT_ADDRESS;
-
-export async function addressInput({
- message,
- disableKeepCurrent,
-}: GenericPrompt): Promise {
- const value = await input({
- message,
- validate: disableKeepCurrent ? isAddress : isAddressOrKeepCurrent,
- ...(disableKeepCurrent ? {} : {default: ENGINE_FLAGS.KEEP_CURRENT_ADDRESS}),
- });
- return translateJsAddressToSol(value) as T extends true ? Hex : AddressInputValues;
-}
-
-interface AssetsSelectPrompt extends Exclude {
- pool: PoolIdentifier;
-}
-
-/**
- * allows selecting multiple assets
- * @param param0
- * @returns
- */
-export async function assetsSelect({pool, message}: AssetsSelectPrompt) {
- const values = await checkbox({
- message,
- choices: getAssets(pool).map((asset) => ({name: asset, value: asset})),
- });
- return values.map((v) => translateAssetToAssetLibUnderlying(v, pool));
-}
-
interface EModeSelectPrompt extends GenericPrompt {
pool: PoolIdentifier;
}
@@ -236,16 +185,3 @@ export async function eModesSelect({message, pool}: EModeSele
console.log('No e-mode category active on the current pool');
}
}
-
-export async function stringInput({
- message,
- defaultValue,
- disableKeepCurrent,
-}: GenericPrompt) {
- const value = await input({
- message,
- default: defaultValue,
- ...(disableKeepCurrent ? {} : {default: ENGINE_FLAGS.KEEP_CURRENT_STRING}),
- });
- return translateJsStringToSol(value);
-}
diff --git a/generator/prompts/addressPrompt.spec.ts b/generator/prompts/addressPrompt.spec.ts
new file mode 100644
index 000000000..55e1bcf02
--- /dev/null
+++ b/generator/prompts/addressPrompt.spec.ts
@@ -0,0 +1,54 @@
+import {expect, describe, it} from 'vitest';
+import {render} from '@inquirer/testing';
+import {addressPrompt, translateJsAddressToSol} from './addressPrompt';
+
+describe('addresses', () => {
+ describe('addressPrompt', () => {
+ it('handles "required"', async () => {
+ const {answer, events, getScreen} = await render(addressPrompt, {
+ message: 'Enter address?',
+ required: true,
+ });
+
+ expect(getScreen()).toMatchInlineSnapshot('"? Enter address?*"');
+
+ events.keypress('enter');
+ await Promise.resolve();
+ expect(getScreen()).toMatchInlineSnapshot(
+ '"? Enter address?*\n> You must provide a valid value"'
+ );
+
+ events.type('XX0xXXae7ab96520de3a18e5e111b5eaab095312d7fe84');
+ expect(getScreen()).toMatchInlineSnapshot(
+ '"? Enter address?* 0xae7ab96520de3a18e5e111b5eaab095312d7fe84"'
+ );
+
+ events.keypress('enter');
+ await expect(answer).resolves.toEqual('0xae7ab96520de3a18e5e111b5eaab095312d7fe84');
+ });
+
+ it('handles "optional"', async () => {
+ const {answer, events, getScreen} = await render(addressPrompt, {
+ message: 'Enter address?',
+ });
+
+ expect(getScreen()).toMatchInlineSnapshot('"? Enter address?"');
+
+ events.keypress('enter');
+ await Promise.resolve();
+ await expect(answer).resolves.toEqual('');
+ });
+ });
+
+ /**
+ * Translates, translate the js input value to solidity
+ */
+ describe('translate', () => {
+ it('translateJsAddressToSol: should properly translate values to addresses', () => {
+ expect(translateJsAddressToSol('')).toBe('EngineFlags.KEEP_CURRENT_ADDRESS');
+ expect(translateJsAddressToSol('0xae7ab96520de3a18e5e111b5eaab095312d7fe84')).toBe(
+ '0xae7ab96520DE3A18E5e111B5EaAb095312D7fE84'
+ );
+ });
+ });
+});
diff --git a/generator/prompts/addressPrompt.ts b/generator/prompts/addressPrompt.ts
new file mode 100644
index 000000000..3ec771dc0
--- /dev/null
+++ b/generator/prompts/addressPrompt.ts
@@ -0,0 +1,24 @@
+import {Hex, getAddress, isAddress} from 'viem';
+import {GenericPrompt} from './types';
+import {advancedInput} from './advancedInput';
+import {flagAsRequired} from '../common';
+
+export async function addressPrompt(
+ {message, required}: GenericPrompt,
+ opts?
+): Promise {
+ const value = await advancedInput(
+ {
+ message: flagAsRequired(message, required),
+ validate: (v) => (required ? isAddress(v) : isAddress(v) || v === ''),
+ pattern: /^(0|0x|0x[A-Fa-f0-9]{0,40})?$/,
+ },
+ opts
+ );
+ return value as Hex;
+}
+
+export function translateJsAddressToSol(value?: string) {
+ if (!value) return `EngineFlags.KEEP_CURRENT_ADDRESS`;
+ return getAddress(value);
+}
diff --git a/generator/prompts/advancedInput.ts b/generator/prompts/advancedInput.ts
index 70b242508..ed875706b 100644
--- a/generator/prompts/advancedInput.ts
+++ b/generator/prompts/advancedInput.ts
@@ -10,7 +10,7 @@ import {
import type {} from '@inquirer/type';
import chalk from 'chalk';
-type InputConfig = PromptConfig<{
+export type InputConfig = PromptConfig<{
default?: string;
transformer?: (value: string, {isFinal}: {isFinal: boolean}) => string;
validate?: (value: string) => boolean | string | Promise;
@@ -38,33 +38,31 @@ export const advancedInput = createPrompt((config, done) =>
return;
}
- if (!pattern || pattern?.test(rl.line)) {
- if (isEnterKey(key)) {
- const answer = value || defaultValue;
- setStatus('loading');
- const isValid = await validate(answer);
- if (isValid === true) {
- setValue(answer);
- setStatus('done');
- done(answer);
- } else {
- // Reset the readline line value to the previous value. On line event, the value
- // get cleared, forcing the user to re-enter the value instead of fixing it.
- rl.write(value);
- setError(isValid || 'You must provide a valid value');
- setStatus('pending');
- }
- } else if (isBackspaceKey(key) && !value) {
- setDefaultValue(undefined);
- } else if (key.name === 'tab' && !value) {
- setDefaultValue(undefined);
- rl.clearLine(0); // Remove the tab character.
- rl.write(defaultValue);
- setValue(defaultValue);
+ if (isEnterKey(key)) {
+ const answer = value || defaultValue;
+ setStatus('loading');
+ const isValid = await validate(answer);
+ if (isValid === true) {
+ setValue(answer);
+ setStatus('done');
+ done(answer);
} else {
- setValue(rl.line);
- setError(undefined);
+ // Reset the readline line value to the previous value. On line event, the value
+ // get cleared, forcing the user to re-enter the value instead of fixing it.
+ rl.write(value);
+ setError(isValid || 'You must provide a valid value');
+ setStatus('pending');
}
+ } else if (isBackspaceKey(key) && !value) {
+ setDefaultValue(undefined);
+ } else if (key.name === 'tab' && !value) {
+ setDefaultValue(undefined);
+ rl.clearLine(0); // Remove the tab character.
+ rl.write(defaultValue);
+ setValue(defaultValue);
+ } else if (!pattern || pattern?.test(rl.line)) {
+ setValue(rl.line);
+ setError(undefined);
} else {
const line = rl.line;
rl.clearLine(0);
diff --git a/generator/prompts/assetsSelectPrompt.ts b/generator/prompts/assetsSelectPrompt.ts
new file mode 100644
index 000000000..01a2fcaf1
--- /dev/null
+++ b/generator/prompts/assetsSelectPrompt.ts
@@ -0,0 +1,21 @@
+import {checkbox} from '@inquirer/prompts';
+import {GenericPoolPrompt} from './types';
+import {getAssets} from '../common';
+import {PoolIdentifier} from '../types';
+
+/**
+ * allows selecting multiple assets
+ * TODO: enforce selection of at least one asset (next version of inquirer ships with required)
+ * @param param0
+ * @returns
+ */
+export async function assetsSelectPrompt({pool, message}: GenericPoolPrompt) {
+ return await checkbox({
+ message,
+ choices: getAssets(pool).map((asset) => ({name: asset, value: asset})),
+ });
+}
+
+export function translateAssetToAssetLibUnderlying(value: string, pool: PoolIdentifier) {
+ return `${pool}Assets.${value}_UNDERLYING`;
+}
diff --git a/generator/prompts/stringPrompt.ts b/generator/prompts/stringPrompt.ts
new file mode 100644
index 000000000..8ccdbd430
--- /dev/null
+++ b/generator/prompts/stringPrompt.ts
@@ -0,0 +1,22 @@
+import {flagAsRequired} from '../common';
+import {advancedInput} from './advancedInput';
+import {GenericPrompt} from './types';
+
+export async function stringPrompt(
+ {message, defaultValue, required}: GenericPrompt,
+ opts?
+) {
+ return advancedInput(
+ {
+ message: flagAsRequired(message, required),
+ default: defaultValue,
+ validate: (v) => (required ? v.trim().length != 0 : true),
+ },
+ opts
+ );
+}
+
+export function stringOrKeepCurrent(value: string) {
+ if (!value) return `EngineFlags.KEEP_CURRENT_STRING`;
+ return `"${value}"`;
+}
diff --git a/generator/prompts/types.ts b/generator/prompts/types.ts
new file mode 100644
index 000000000..bf96220a4
--- /dev/null
+++ b/generator/prompts/types.ts
@@ -0,0 +1,12 @@
+import {PoolIdentifier} from '../types';
+
+export interface GenericPrompt {
+ message: string;
+ required?: T;
+ transform?: (value: string) => string;
+ defaultValue?: string;
+}
+
+export interface GenericPoolPrompt extends GenericPrompt {
+ pool: PoolIdentifier;
+}
diff --git a/generator/templates/aip.template.ts b/generator/templates/aip.template.ts
index 9401c82f8..3c1ac00a6 100644
--- a/generator/templates/aip.template.ts
+++ b/generator/templates/aip.template.ts
@@ -15,10 +15,7 @@ discussions: ${`"${options.discussion}"` || 'TODO'}
## Specification
${Object.keys(configs).map((pool) => {
- let template = `On ${pool} the following steps are performed:\n`;
- template += configs[pool].artifacts
- .filter((artifact) => artifact.aip)
- .map((artifact) => artifact.aip);
+ return configs[pool].artifacts.filter((artifact) => artifact.aip).map((artifact) => artifact.aip);
})}
## References