From 5bf2c715ba53e19159fd068e8d3bd26caf3f2977 Mon Sep 17 00:00:00 2001 From: pr0toshi <46229158+pr0toshi@users.noreply.github.com> Date: Tue, 5 Sep 2023 06:06:38 +0000 Subject: [PATCH 1/3] Scry Metamorph support --- protocol/oracle-manager/contracts/modules/NodeModule.sol | 7 ++++++- .../oracle-manager/contracts/storage/NodeDefinition.sol | 3 ++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/protocol/oracle-manager/contracts/modules/NodeModule.sol b/protocol/oracle-manager/contracts/modules/NodeModule.sol index c09e096f19..8dbe2d8213 100644 --- a/protocol/oracle-manager/contracts/modules/NodeModule.sol +++ b/protocol/oracle-manager/contracts/modules/NodeModule.sol @@ -6,6 +6,7 @@ import "../nodes/ReducerNode.sol"; import "../nodes/ExternalNode.sol"; import "../nodes/PythNode.sol"; import "../nodes/ChainlinkNode.sol"; +import "../nodes/ScryMetaMorphNode.sol.sol"; import "../nodes/PriceDeviationCircuitBreakerNode.sol"; import "../nodes/StalenessCircuitBreakerNode.sol"; import "../nodes/UniswapNode.sol"; @@ -179,7 +180,9 @@ contract NodeModule is INodeModule { } else if (nodeDefinition.nodeType == NodeDefinition.NodeType.CHAINLINK) { return ChainlinkNode.process(nodeDefinition.parameters); } else if (nodeDefinition.nodeType == NodeDefinition.NodeType.UNISWAP) { - return UniswapNode.process(nodeDefinition.parameters); + return UniswapNode.process(nodeDefinition.parameters); + else if (nodeDefinition.nodeType == NodeDefinition.NodeType.SCRY) { + return ScryMetaMorph.process(nodeDefinition.parameters); } else if (nodeDefinition.nodeType == NodeDefinition.NodeType.PYTH) { return PythNode.process(nodeDefinition.parameters); } else if ( @@ -225,6 +228,8 @@ contract NodeModule is INodeModule { return ChainlinkNode.isValid(nodeDefinition); } else if (nodeDefinition.nodeType == NodeDefinition.NodeType.UNISWAP) { return UniswapNode.isValid(nodeDefinition); + } else if (nodeDefinition.nodeType == NodeDefinition.NodeType.SCRY) { + return ScryMetaMorph.isValid(nodeDefinition); } else if (nodeDefinition.nodeType == NodeDefinition.NodeType.PYTH) { return PythNode.isValid(nodeDefinition); } else if ( diff --git a/protocol/oracle-manager/contracts/storage/NodeDefinition.sol b/protocol/oracle-manager/contracts/storage/NodeDefinition.sol index b20b98ade2..78f403853d 100644 --- a/protocol/oracle-manager/contracts/storage/NodeDefinition.sol +++ b/protocol/oracle-manager/contracts/storage/NodeDefinition.sol @@ -11,7 +11,8 @@ library NodeDefinition { PYTH, PRICE_DEVIATION_CIRCUIT_BREAKER, STALENESS_CIRCUIT_BREAKER, - CONSTANT + CONSTANT, + SCRY } struct Data { From f70b54258ab7f1502686f3e63cd5f7b216c2aba5 Mon Sep 17 00:00:00 2001 From: pr0toshi <46229158+pr0toshi@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:52:27 +0000 Subject: [PATCH 2/3] test --- protocol/oracle-manager/test/integration/mixins/Node.types.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/protocol/oracle-manager/test/integration/mixins/Node.types.ts b/protocol/oracle-manager/test/integration/mixins/Node.types.ts index 20df9bd174..3284ecb027 100644 --- a/protocol/oracle-manager/test/integration/mixins/Node.types.ts +++ b/protocol/oracle-manager/test/integration/mixins/Node.types.ts @@ -8,4 +8,5 @@ export default { PRICE_DEVIATION_CIRCUIT_BREAKER: 6, STALENESS_CIRCUIT_BREAKER: 7, CONSTANT: 8, + SCRY:9 }; From 01903273e0fa4c5243a08b5978e72bf2bcef9a1f Mon Sep 17 00:00:00 2001 From: pr0toshi <46229158+pr0toshi@users.noreply.github.com> Date: Tue, 5 Sep 2023 10:54:30 +0000 Subject: [PATCH 3/3] test --- .../interfaces/external/IScryMetaMorph.sol | 20 ++++++ .../contracts/mocks/MockScryMetamorph.sol | 25 ++++++++ .../contracts/nodes/ScryMetaMorphNode.sol | 56 ++++++++++++++++ .../integration/nodes/ScryMetamorph.test.ts | 64 +++++++++++++++++++ 4 files changed, 165 insertions(+) create mode 100644 protocol/oracle-manager/contracts/interfaces/external/IScryMetaMorph.sol create mode 100644 protocol/oracle-manager/contracts/mocks/MockScryMetamorph.sol create mode 100644 protocol/oracle-manager/contracts/nodes/ScryMetaMorphNode.sol create mode 100644 protocol/oracle-manager/test/integration/nodes/ScryMetamorph.test.ts diff --git a/protocol/oracle-manager/contracts/interfaces/external/IScryMetaMorph.sol b/protocol/oracle-manager/contracts/interfaces/external/IScryMetaMorph.sol new file mode 100644 index 0000000000..93f9d32b28 --- /dev/null +++ b/protocol/oracle-manager/contracts/interfaces/external/IScryMetaMorph.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.11 <0.9.0; + +/// @title Interface an aggregator needs to adhere. +interface IScryMetaMorph { + + function getFeedPortal( + uint256 ID + ) + external + view + returns ( + uint256 value, + uint256 decimals, + string memory valStr, + bytes memory valBytes, + uint timestamp + ); + +} diff --git a/protocol/oracle-manager/contracts/mocks/MockScryMetamorph.sol b/protocol/oracle-manager/contracts/mocks/MockScryMetamorph.sol new file mode 100644 index 0000000000..9ae2988431 --- /dev/null +++ b/protocol/oracle-manager/contracts/mocks/MockScryMetamorph.sol @@ -0,0 +1,25 @@ +// SPDX-License-Identifier: SCRY +pragma solidity 0.8.6; + +contract MockMetaMorph { + function getFeedPortal( + uint256 ID + ) + external + view + returns ( + uint256 value, + uint decimals, + string memory valStr, + bytes memory valBytes, + uint timestamp + ) + {if (ID==0){ + value=100*10*18; + decimals=18; + timestamp=block.timestamp();} + else{value=100*10*2; + decimals=2; + timestamp=block.timestamp();} + } +} diff --git a/protocol/oracle-manager/contracts/nodes/ScryMetaMorphNode.sol b/protocol/oracle-manager/contracts/nodes/ScryMetaMorphNode.sol new file mode 100644 index 0000000000..655f426043 --- /dev/null +++ b/protocol/oracle-manager/contracts/nodes/ScryMetaMorphNode.sol @@ -0,0 +1,56 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.8.11 <0.9.0; + +import "@synthetixio/core-contracts/contracts/utils/SafeCast.sol"; +import "@synthetixio/core-contracts/contracts/utils/DecimalMath.sol"; + +import "../storage/NodeDefinition.sol"; +import "../storage/NodeOutput.sol"; +import "../interfaces/external/IScryMetaMorph.sol"; + +library ScryMetaMorphNode { + using SafeCastU256 for uint256; + using SafeCastI256 for int256; + using DecimalMath for int256; + + uint256 public constant PRECISION = 18; + + function process( + bytes memory parameters + ) internal view returns (NodeOutput.Data memory nodeOutput) { + (address addrs, uint256 ID) = abi.decode( + parameters, + (address, uint256) + ); + IScryMetaMorph metamorph = IScryMetaMorph(addrs); + (int256 price, uint256 decimals,,,uint256 timestamp) = metamorph.getFeedPortal(ID); + + price = decimals > PRECISION + ? price / 10 ** (decimals-PRECISION) + : price * 10 ** (PRECISION-decimals); + + return NodeOutput.Data(price, timestamp, 0, 0); + } + + function isValid(NodeDefinition.Data memory nodeDefinition) internal view returns (bool valid) { + // Must have no parents + if (nodeDefinition.parents.length > 0) { + return false; + } + + // Must have correct length of parameters data + if (nodeDefinition.parameters.length != 64) { + return false; + } + + (address addrs, uint256 ID) = abi.decode( + nodeDefinition.parameters, + (address, uint256) + ); + IScryMetaMorph metamorph = IScryMetaMorph(addrs); + // Must successfully execute getFeedPortal, the custom portal enforces the users desired quorums among custom oracle sets and data already + (int256 price, uint256 decimals,,,uint256 timestamp) = metamorph.getFeedPortal(ID); + + return true; + } +} diff --git a/protocol/oracle-manager/test/integration/nodes/ScryMetamorph.test.ts b/protocol/oracle-manager/test/integration/nodes/ScryMetamorph.test.ts new file mode 100644 index 0000000000..0d41843f23 --- /dev/null +++ b/protocol/oracle-manager/test/integration/nodes/ScryMetamorph.test.ts @@ -0,0 +1,64 @@ +import assertBn from '@synthetixio/core-utils/utils/assertions/assert-bignumber'; +import { BigNumber, ethers, utils } from 'ethers'; +import hre from 'hardhat'; +import { bootstrap } from '../bootstrap'; +import NodeTypes from '../mixins/Node.types'; + +describe('ScryMetaMorphNode', () => { + const { getSigners, getContract } = bootstrap(); + + let metamorph: ethers.Contract; + + const abi = utils.defaultAbiCoder; + + let owner: ethers.Signer; + + let NodeModule: ethers.Contract; + + before('prepare environment', async () => { + NodeModule = getContract('NodeModule'); + }); + + before('identify owner', async () => { + [owner] = await getSigners(); + }); + + before('deploy mock MM', async () => { + const factory = await hre.ethers.getContractFactory('MockMetaMorph'); + metamorph = await factory.connect(owner).deploy(); + }); + + describe('process()', () => { + describe('check value = 100', async () => { + it('returns latest price', async () => { + const encodedParams = abi.encode( + ['address', 'uint256'], + [metamorph.address, 0] + ); + + const nodeId = await registerNode(encodedParams); + const [price] = await NodeModule.process(nodeId); + assertBn.equal(price, ethers.utils.parseUnits('100',18)); + }); + }); + + describe('when upscale is needed', async () => { + it('returns price with 18 decimals', async () => { + const encodedParams = abi.encode( + ['address', 'uint256'], + [metamorph.address, 1] + ); + + const nodeId = await registerNode(encodedParams); + const [price] = await NodeModule.process(nodeId); + assertBn.equal(price, ethers.utils.parseUnits('100', 18)); + }); + }); + }); + + const registerNode = async (params: string) => { + const tx = await NodeModule.registerNode(NodeTypes.SCRY, params, []); + await tx.wait(); + return await NodeModule.getNodeId(NodeTypes.SCRY, params, []); + }; +});