From e918c7e26339188749109aee661c7e045703774e Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 14:53:22 +0200 Subject: [PATCH 01/10] Chainlink oracle draft --- src/ERC7540Vault.sol | 4 +- src/factories/AggregatorV3OracleFactory.sol | 60 +++++++++++++++++++++ src/interfaces/IERC7540.sol | 6 +++ src/interfaces/factories/IAggregatorV3.sol | 20 +++++++ 4 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 src/factories/AggregatorV3OracleFactory.sol create mode 100644 src/interfaces/factories/IAggregatorV3.sol diff --git a/src/ERC7540Vault.sol b/src/ERC7540Vault.sol index 44c6d3f5..6e42622b 100644 --- a/src/ERC7540Vault.sol +++ b/src/ERC7540Vault.sol @@ -409,12 +409,12 @@ contract ERC7540Vault is Auth, IERC7540Vault { } // --- Helpers --- - /// @notice Price of 1 unit of share, quoted in the decimals of the asset. + /// @inheritdoc IERC7540Vault function pricePerShare() external view returns (uint256) { return convertToAssets(10 ** _shareDecimals); } - /// @notice Returns timestamp of the last share price update. + /// @inheritdoc IERC7540Vault function priceLastUpdated() external view returns (uint64) { return manager.priceLastUpdated(address(this)); } diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol new file mode 100644 index 00000000..f9313a52 --- /dev/null +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -0,0 +1,60 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity ^0.8.20; + +import {IAggregatorV3} from "src/interfaces/factories/IAggregatorV3.sol"; +import {Auth} from "src/Auth.sol"; +import {IERC20Metadata} from "src/interfaces/IERC20.sol"; +import {IERC7540Vault} from "src/interfaces/IERC7540.sol"; + +contract VaultOracle is Auth, IAggregatorV3 { + uint8 public constant PRICE_DECIMALS = 36; + uint80 public constant ROUND_ID = 0; + + uint256 public immutable override version = 1; + + IERC7540Vault public vault; + uint8 public override decimals; + string public override description; + + // --- Events --- + event File(bytes32 indexed what, address data); + + constructor(address vault_) Auth(msg.sender) { + _updateVault(vault_); + + wards[msg.sender] = 1; + emit Rely(msg.sender); + } + + // --- Administration --- + function file(bytes32 what, address data) public auth { + if (what == "vault") { + _updateVault(data); + emit File(what, data); + } else { + revert("VaultOracle/file-unrecognized-param"); + } + } + + function _updateVault(address vault_) internal { + vault = IERC7540Vault(vault_); + decimals = IERC20Metadata(vault.share()).decimals(); + + string memory assetSymbol = IERC20Metadata(vault.asset()).symbol(); + string memory shareSymbol = IERC20Metadata(vault.share()).symbol(); + description = string.concat(assetSymbol, " / ", shareSymbol); + } + + // --- Price computation --- + function latestRoundData() public view override returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { + uint256 priceLastUpdated = vault.priceLastUpdated(); + return (ROUND_ID, int256(vault.pricePerShare()), priceLastUpdated, priceLastUpdated, ROUND_ID); + } + + function getRoundData( + uint80 /* roundId */ + ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { + return latestRoundData(); + } + +} \ No newline at end of file diff --git a/src/interfaces/IERC7540.sol b/src/interfaces/IERC7540.sol index 7d7f5094..abf2d9cc 100644 --- a/src/interfaces/IERC7540.sol +++ b/src/interfaces/IERC7540.sol @@ -308,6 +308,12 @@ interface IERC7540Vault is /// @dev MUST be called by endorsed sender function setEndorsedOperator(address owner, bool approved) external; + /// @notice Price of 1 unit of share, quoted in the decimals of the asset. + function pricePerShare() external view returns (uint256); + + /// @notice Returns timestamp of the last share price update. + function priceLastUpdated() external view returns (uint64); + /// @notice Callback when a redeem Request is triggered externally; function onRedeemRequest(address controller, address owner, uint256 shares) external; diff --git a/src/interfaces/factories/IAggregatorV3.sol b/src/interfaces/factories/IAggregatorV3.sol new file mode 100644 index 00000000..30d7c292 --- /dev/null +++ b/src/interfaces/factories/IAggregatorV3.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: AGPL-3.0-only +pragma solidity >=0.5.0; + +interface IAggregatorV3 { + function decimals() external view returns (uint8); + + function description() external view returns (string memory); + + function version() external view returns (uint256); + + function getRoundData(uint80 _roundId) + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); + + function latestRoundData() + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound); +} From de55805be6885e1594fcb4097efd0d87262dba9f Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 14:56:48 +0200 Subject: [PATCH 02/10] Fix --- src/factories/AggregatorV3OracleFactory.sol | 1 - 1 file changed, 1 deletion(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index f9313a52..52bcfade 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -7,7 +7,6 @@ import {IERC20Metadata} from "src/interfaces/IERC20.sol"; import {IERC7540Vault} from "src/interfaces/IERC7540.sol"; contract VaultOracle is Auth, IAggregatorV3 { - uint8 public constant PRICE_DECIMALS = 36; uint80 public constant ROUND_ID = 0; uint256 public immutable override version = 1; From 9c949365ddea55e3f9e52ec67562da568a174f8c Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 14:56:54 +0200 Subject: [PATCH 03/10] Format --- src/factories/AggregatorV3OracleFactory.sol | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index 52bcfade..e659461b 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -45,15 +45,21 @@ contract VaultOracle is Auth, IAggregatorV3 { } // --- Price computation --- - function latestRoundData() public view override returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { + function latestRoundData() + public + view + override + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) + { uint256 priceLastUpdated = vault.priceLastUpdated(); return (ROUND_ID, int256(vault.pricePerShare()), priceLastUpdated, priceLastUpdated, ROUND_ID); } - function getRoundData( - uint80 /* roundId */ - ) external view returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) { + function getRoundData(uint80 /* roundId */ ) + external + view + returns (uint80 roundId, int256 answer, uint256 startedAt, uint256 updatedAt, uint80 answeredInRound) + { return latestRoundData(); } - -} \ No newline at end of file +} From 3c5eef8ecb1bab5713e070ac957de41161c8df3e Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 15:10:23 +0200 Subject: [PATCH 04/10] Update file --- src/factories/AggregatorV3OracleFactory.sol | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index e659461b..6a1c0421 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -19,7 +19,12 @@ contract VaultOracle is Auth, IAggregatorV3 { event File(bytes32 indexed what, address data); constructor(address vault_) Auth(msg.sender) { - _updateVault(vault_); + IERC7540Vault newVault = IERC7540Vault(vault_); + decimals = IERC20Metadata(newVault.share()).decimals(); + + string memory assetSymbol = IERC20Metadata(newVault.asset()).symbol(); + string memory shareSymbol = IERC20Metadata(newVault.share()).symbol(); + description = string.concat(assetSymbol, " / ", shareSymbol); wards[msg.sender] = 1; emit Rely(msg.sender); @@ -36,12 +41,13 @@ contract VaultOracle is Auth, IAggregatorV3 { } function _updateVault(address vault_) internal { - vault = IERC7540Vault(vault_); - decimals = IERC20Metadata(vault.share()).decimals(); + IERC7540Vault newVault = IERC7540Vault(vault_); + require( + address(vault) == address(0) || (vault.share() == newVault.share() && vault.asset() == newVault.asset()), + "VaultOracle/mismatching-asset-or-share" + ); - string memory assetSymbol = IERC20Metadata(vault.asset()).symbol(); - string memory shareSymbol = IERC20Metadata(vault.share()).symbol(); - description = string.concat(assetSymbol, " / ", shareSymbol); + vault = newVault; } // --- Price computation --- From 3ccba56ab1abb3772f18cf8ce842492740853d12 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 15:14:31 +0200 Subject: [PATCH 05/10] Allow renaming symbols --- src/factories/AggregatorV3OracleFactory.sol | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index 6a1c0421..e94e0081 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -10,10 +10,9 @@ contract VaultOracle is Auth, IAggregatorV3 { uint80 public constant ROUND_ID = 0; uint256 public immutable override version = 1; + uint8 public immutable override decimals; IERC7540Vault public vault; - uint8 public override decimals; - string public override description; // --- Events --- event File(bytes32 indexed what, address data); @@ -22,10 +21,6 @@ contract VaultOracle is Auth, IAggregatorV3 { IERC7540Vault newVault = IERC7540Vault(vault_); decimals = IERC20Metadata(newVault.share()).decimals(); - string memory assetSymbol = IERC20Metadata(newVault.asset()).symbol(); - string memory shareSymbol = IERC20Metadata(newVault.share()).symbol(); - description = string.concat(assetSymbol, " / ", shareSymbol); - wards[msg.sender] = 1; emit Rely(msg.sender); } @@ -68,4 +63,11 @@ contract VaultOracle is Auth, IAggregatorV3 { { return latestRoundData(); } + + // --- View methods --- + function description() external view returns (string memory) { + string memory assetSymbol = IERC20Metadata(vault.asset()).symbol(); + string memory shareSymbol = IERC20Metadata(vault.share()).symbol(); + return string.concat(assetSymbol, " / ", shareSymbol); + } } From 9193a3dc1624471b86e73a9d6716b41474590eba Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 15:17:17 +0200 Subject: [PATCH 06/10] Clean up file --- src/factories/AggregatorV3OracleFactory.sol | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index e94e0081..07ea79b7 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -28,23 +28,18 @@ contract VaultOracle is Auth, IAggregatorV3 { // --- Administration --- function file(bytes32 what, address data) public auth { if (what == "vault") { - _updateVault(data); + require( + address(vault) == address(0) + || (IERC7540Vault(data).share() == vault.share() && IERC7540Vault(data).asset() == vault.asset()), + "VaultOracle/mismatching-asset-or-share" + ); + vault = IERC7540Vault(data); emit File(what, data); } else { revert("VaultOracle/file-unrecognized-param"); } } - function _updateVault(address vault_) internal { - IERC7540Vault newVault = IERC7540Vault(vault_); - require( - address(vault) == address(0) || (vault.share() == newVault.share() && vault.asset() == newVault.asset()), - "VaultOracle/mismatching-asset-or-share" - ); - - vault = newVault; - } - // --- Price computation --- function latestRoundData() public From dae6d984827709dad54cc27189a726c2f31c6f48 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 15:20:03 +0200 Subject: [PATCH 07/10] Fix decimals --- src/factories/AggregatorV3OracleFactory.sol | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index 07ea79b7..c9188562 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -19,7 +19,7 @@ contract VaultOracle is Auth, IAggregatorV3 { constructor(address vault_) Auth(msg.sender) { IERC7540Vault newVault = IERC7540Vault(vault_); - decimals = IERC20Metadata(newVault.share()).decimals(); + decimals = IERC20Metadata(newVault.asset()).decimals(); wards[msg.sender] = 1; emit Rely(msg.sender); From bf80c9bca7b067eaf40a1726328249e6c111ccaf Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 15:20:59 +0200 Subject: [PATCH 08/10] Cleanup --- src/factories/AggregatorV3OracleFactory.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index c9188562..c47e902b 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -18,8 +18,8 @@ contract VaultOracle is Auth, IAggregatorV3 { event File(bytes32 indexed what, address data); constructor(address vault_) Auth(msg.sender) { - IERC7540Vault newVault = IERC7540Vault(vault_); - decimals = IERC20Metadata(newVault.asset()).decimals(); + vault = IERC7540Vault(vault_); + decimals = IERC20Metadata(vault.asset()).decimals(); wards[msg.sender] = 1; emit Rely(msg.sender); From fab76518ec96af4c463225479c131e8da42703d2 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 15:21:09 +0200 Subject: [PATCH 09/10] Remove old code --- src/factories/AggregatorV3OracleFactory.sol | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index c47e902b..2247e1b1 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -20,9 +20,6 @@ contract VaultOracle is Auth, IAggregatorV3 { constructor(address vault_) Auth(msg.sender) { vault = IERC7540Vault(vault_); decimals = IERC20Metadata(vault.asset()).decimals(); - - wards[msg.sender] = 1; - emit Rely(msg.sender); } // --- Administration --- From d9bd950bdc016f7bdcdc5f85d7a7eff91d4b1319 Mon Sep 17 00:00:00 2001 From: Jeroen Offerijns Date: Tue, 13 Aug 2024 15:31:53 +0200 Subject: [PATCH 10/10] Fix description --- src/factories/AggregatorV3OracleFactory.sol | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/factories/AggregatorV3OracleFactory.sol b/src/factories/AggregatorV3OracleFactory.sol index 2247e1b1..1cd5d982 100644 --- a/src/factories/AggregatorV3OracleFactory.sol +++ b/src/factories/AggregatorV3OracleFactory.sol @@ -58,8 +58,8 @@ contract VaultOracle is Auth, IAggregatorV3 { // --- View methods --- function description() external view returns (string memory) { - string memory assetSymbol = IERC20Metadata(vault.asset()).symbol(); string memory shareSymbol = IERC20Metadata(vault.share()).symbol(); - return string.concat(assetSymbol, " / ", shareSymbol); + string memory assetSymbol = IERC20Metadata(vault.asset()).symbol(); + return string.concat(shareSymbol, " / ", assetSymbol); } }