Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Legacy oracle bug&tests #668

Merged
merged 8 commits into from
Mar 9, 2023
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions contracts/0.4.24/oracle/LegacyOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,11 @@ contract LegacyOracle is Versioned, AragonApp {
* Returns the epoch calculated from current timestamp
*/
function getCurrentEpochId() external view returns (uint256 epochId) {
Jeday marked this conversation as resolved.
Show resolved Hide resolved
(epochId, ,) = _getCurrentFrameFromAccountingOracle();
ChainSpec memory spec = _getChainSpec();
IHashConsensus consensus = _getAccountingConsensusContract();
uint256 refSlot;
(refSlot,) = consensus.getCurrentFrame();
epochId = (refSlot + 1) / spec.slotsPerEpoch;
}

/**
Expand Down Expand Up @@ -369,12 +373,13 @@ contract LegacyOracle is Versioned, AragonApp {
ChainSpec memory spec = _getChainSpec();
IHashConsensus consensus = _getAccountingConsensusContract();
uint256 refSlot;
(refSlot, frameEndTime) = consensus.getCurrentFrame();
// new accounting oracle's frame ends at the timestamp of the frame's last slot; old oracle's frame
// ended a second before the timestamp of the first slot of the next frame
frameEndTime += spec.secondsPerSlot - 1;
(refSlot,) = consensus.getCurrentFrame();

// new accounting oracle's ref. slot is the last slot of the epoch preceding the one the frame starts at
frameStartTime = spec.genesisTime + (refSlot + 1) * spec.secondsPerSlot;
// new accounting oracle's frame ends at the timestamp of the frame's last slot; old oracle's frame
// ended a second before the timestamp of the first slot of the next frame
frameEndTime = frameStartTime + spec.secondsPerSlot*spec.slotsPerEpoch*spec.epochsPerFrame - 1;
frameEpochId = (refSlot + 1) / spec.slotsPerEpoch;
}

Expand Down
44 changes: 22 additions & 22 deletions contracts/0.4.24/test_helpers/MockLegacyOracle.sol
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,29 @@ contract MockLegacyOracle is ILegacyOracle, LegacyOracle {

HandleConsensusLayerReportCallData public lastCall__handleConsensusLayerReport;

uint64 internal _epochsPerFrame;
uint64 internal _slotsPerEpoch;
uint64 internal _secondsPerSlot;
uint64 internal _genesisTime;
uint256 internal _lastCompletedEpochId;


function getBeaconSpec() external view returns (
uint64 epochsPerFrame,
uint64 slotsPerEpoch,
uint64 secondsPerSlot,
uint64 genesisTime
) {
return (
_epochsPerFrame,
_slotsPerEpoch,
_secondsPerSlot,
_genesisTime
);

ChainSpec memory spec = _getChainSpec();
epochsPerFrame = spec.epochsPerFrame;
slotsPerEpoch = spec.slotsPerEpoch;
secondsPerSlot = spec.secondsPerSlot;
genesisTime = spec.genesisTime;
}

function setBeaconSpec( uint64 epochsPerFrame,
uint64 slotsPerEpoch,
uint64 secondsPerSlot,
uint64 genesisTime) external {
_setChainSpec(ChainSpec(epochsPerFrame,slotsPerEpoch,secondsPerSlot,genesisTime));
}


function handleConsensusLayerReport(uint256 refSlot, uint256 clBalance, uint256 clValidators)
external
{
Expand All @@ -65,18 +67,16 @@ contract MockLegacyOracle is ILegacyOracle, LegacyOracle {
uint64 genesisTime,
uint256 lastCompletedEpochId
) external {
_epochsPerFrame = epochsPerFrame;
_slotsPerEpoch = slotsPerEpoch;
_secondsPerSlot = secondsPerSlot;
_genesisTime = genesisTime;
_lastCompletedEpochId = lastCompletedEpochId;

_setChainSpec(ChainSpec(epochsPerFrame,slotsPerEpoch,secondsPerSlot,genesisTime));
LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(lastCompletedEpochId);
}
function getLastCompletedEpochId() external view returns (uint256) {
return _lastCompletedEpochId;

function setLastCompletedEpochId(uint256 lastCompletedEpochId) external {
LAST_COMPLETED_EPOCH_ID_POSITION.setStorageUint256(lastCompletedEpochId);
}

function setLastCompletedEpochId(uint256 lastCompletedEpochId) external {
_lastCompletedEpochId = lastCompletedEpochId;
function initializeAsV3() external {
CONTRACT_VERSION_POSITION_DEPRECATED.setStorageUint256(3);
}

}
220 changes: 220 additions & 0 deletions test/0.4.24/legacy-oracle.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,220 @@
const { contract, ethers, artifacts, web3 } = require('hardhat')
const { assert } = require('../helpers/assert')
const { impersonate } = require('../helpers/blockchain')
const { e9, e18, e27 } = require('../helpers/utils')
const { legacyOracleFactory } = require('../helpers/factories')

const OssifiableProxy = artifacts.require('OssifiableProxy')
const LegacyOracle = artifacts.require('LegacyOracle')
const MockLegacyOracle = artifacts.require('MockLegacyOracle')

const LegacyOracleAbi = require('../../lib/abi/LegacyOracle.json')

const {
deployAccountingOracleSetup,
initAccountingOracle,
EPOCHS_PER_FRAME,
SLOTS_PER_EPOCH,
SECONDS_PER_SLOT,
GENESIS_TIME,
calcAccountingReportDataHash,
getAccountingReportDataItems,
computeTimestampAtSlot,
ZERO_HASH,
CONSENSUS_VERSION,
computeTimestampAtEpoch,
} = require('../0.8.9/oracle/accounting-oracle-deploy.test')

const getReportFields = (override = {}) => ({
consensusVersion: CONSENSUS_VERSION,
numValidators: 10,
clBalanceGwei: e9(320),
stakingModuleIdsWithNewlyExitedValidators: [1],
numExitedValidatorsByStakingModule: [3],
withdrawalVaultBalance: e18(1),
elRewardsVaultBalance: e18(2),
sharesRequestedToBurn: e18(3),
lastFinalizableWithdrawalRequestId: 1,
simulatedShareRate: e27(1),
isBunkerMode: true,
extraDataFormat: 0,
extraDataHash: ZERO_HASH,
extraDataItemsCount: 0,
...override,
})

async function deployLegacyOracleWithAccountingOracle({ admin, initialEpoch = 1, lastProcessingRefSlot = 31 }) {
const legacyOracle = await legacyOracleFactory({ appManager: { address: admin } })
const { locatorAddr, consensus, oracle, lido } = await deployAccountingOracleSetup(admin, {
initialEpoch,
legacyOracleAddrArg: legacyOracle.address,
getLegacyOracle: () => {
return legacyOracle
},
})
await legacyOracle.initialize(locatorAddr, consensus.address)
await initAccountingOracle({ admin, oracle, consensus, shouldMigrateLegacyOracle: false, lastProcessingRefSlot })
return { legacyOracle, consensus, accountingOracle: oracle, lido }
}

module.exports = {
deployLegacyOracleWithAccountingOracle,
}

contract('LegacyOracle', ([admin, stranger]) => {
context('Fresh deploy and puppet methods checks', () => {
let legacyOracle, accountingOracle, lido, consensus
before('deploy', async () => {
const deployed = await deployLegacyOracleWithAccountingOracle({ admin })
legacyOracle = deployed.legacyOracle
accountingOracle = deployed.accountingOracle
lido = deployed.lido
consensus = deployed.consensus
})

it('initial state is correct', async () => {
assert.equals(await legacyOracle.getVersion(), 4)
assert.equals(await legacyOracle.getAccountingOracle(), accountingOracle.address)
assert.equals(await legacyOracle.getLido(), lido.address)
const spec = await legacyOracle.getBeaconSpec()
assert.equals(spec.epochsPerFrame, EPOCHS_PER_FRAME)
assert.equals(spec.slotsPerEpoch, SLOTS_PER_EPOCH)
assert.equals(spec.secondsPerSlot, SECONDS_PER_SLOT)
assert.equals(spec.genesisTime, GENESIS_TIME)
const frame = await consensus.getCurrentFrame()
const epochId = frame.refSlot.addn(1).divn(SLOTS_PER_EPOCH)
assert.equals(await legacyOracle.getCurrentEpochId(), epochId)
assert.equals(await legacyOracle.getLastCompletedEpochId(), 0)
})

it('handlePostTokenRebase performs AC, emits event and changes state', async () => {
await impersonate(ethers.provider, lido.address)
await assert.reverts(
legacyOracle.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7, { from: stranger }),
'SENDER_NOT_ALLOWED'
)
const tx = await legacyOracle.handlePostTokenRebase(1, 2, 3, 4, 5, 6, 7, { from: lido.address })
assert.emits(tx, 'PostTotalShares', {
postTotalPooledEther: 6,
preTotalPooledEther: 4,
timeElapsed: 2,
totalShares: 5,
})
const delta = await legacyOracle.getLastCompletedReportDelta()
assert.equals(delta.postTotalPooledEther, 6)
assert.equals(delta.preTotalPooledEther, 4)
assert.equals(delta.timeElapsed, 2)
})

it('handleConsensusLayerReport performs AC, emits event and changes state', async () => {
const refSlot = 3000
await impersonate(ethers.provider, accountingOracle.address)
await assert.reverts(
legacyOracle.handleConsensusLayerReport(refSlot, 2, 3, { from: stranger }),
'SENDER_NOT_ALLOWED'
)
const tx = await legacyOracle.handleConsensusLayerReport(refSlot, 2, 3, { from: accountingOracle.address })
const epochId = Math.floor((refSlot + 1) / SLOTS_PER_EPOCH)
assert.emits(tx, 'Completed', {
epochId,
beaconBalance: 2,
beaconValidators: 3,
})
const completedEpoch = await legacyOracle.getLastCompletedEpochId()
assert.equals(completedEpoch, epochId)
})
})

context('Migration from old contract', () => {
const lastCompletedEpoch = 10
let oldImplementation
let newImplementation
let proxy
let proxyAsOldImplementation
let proxyAsNewImplementation
let deployedInfra

before('deploy old implementation and set as proxy', async () => {
oldImplementation = await MockLegacyOracle.new({ from: admin })
newImplementation = await LegacyOracle.new({ from: admin })
proxy = await OssifiableProxy.new(oldImplementation.address, admin, '0x')
proxyAsOldImplementation = await MockLegacyOracle.at(proxy.address)
})

it('implementations are petrified', async () => {
await assert.reverts(oldImplementation.initialize(stranger, stranger), 'INIT_ALREADY_INITIALIZED')
await assert.reverts(newImplementation.initialize(stranger, stranger), 'INIT_ALREADY_INITIALIZED')
})

it('set state to mimic legacy oracle', async () => {
await proxyAsOldImplementation.initializeAsV3()
await proxyAsOldImplementation.setParams(
EPOCHS_PER_FRAME,
SLOTS_PER_EPOCH,
SECONDS_PER_SLOT,
GENESIS_TIME,
lastCompletedEpoch
)
})

it('deploy&initialize all contracts', async () => {
deployedInfra = await deployAccountingOracleSetup(admin, {
legacyOracleAddrArg: proxy.address,
getLegacyOracle: () => {
return proxyAsOldImplementation
},
dataSubmitter: admin,
})
const { consensus, oracle } = deployedInfra
await initAccountingOracle({ admin, oracle, consensus, shouldMigrateLegacyOracle: true })
})

it('upgrade implementation', async () => {
await proxy.proxy__upgradeTo(newImplementation.address)
proxyAsNewImplementation = await LegacyOracle.at(proxy.address)
await proxyAsNewImplementation.finalizeUpgrade_v4(deployedInfra.oracle.address)
})

it('submit report', async () => {
await deployedInfra.consensus.advanceTimeToNextFrameStart()
const { refSlot } = await deployedInfra.consensus.getCurrentFrame()
const reportFields = getReportFields({
refSlot: +refSlot,
})
const reportItems = getAccountingReportDataItems(reportFields)
const reportHash = calcAccountingReportDataHash(reportItems)
await deployedInfra.consensus.addMember(admin, 1, { from: admin })
await deployedInfra.consensus.submitReport(refSlot, reportHash, CONSENSUS_VERSION, { from: admin })
const oracleVersion = +(await deployedInfra.oracle.getContractVersion())
const tx = await deployedInfra.oracle.submitReportData(reportItems, oracleVersion, { from: admin })

const epochId = Math.floor((+refSlot + 1) / SLOTS_PER_EPOCH)
assert.emits(
tx,
'Completed',
{
epochId,
beaconBalance: web3.utils.toWei(reportFields.clBalanceGwei, 'gwei'),
beaconValidators: reportFields.numValidators,
},
{ abi: LegacyOracleAbi }
)
const completedEpoch = await proxyAsNewImplementation.getLastCompletedEpochId()
assert.equals(completedEpoch, epochId)
})

it('time in sync with consensus', async () => {
await deployedInfra.consensus.advanceTimeToNextFrameStart()
const epochId = await proxyAsNewImplementation.getCurrentEpochId()
const { frameEpochId, frameStartTime, frameEndTime } = await proxyAsNewImplementation.getCurrentFrame()
assert.equals(epochId, frameEpochId)
const consensusFrame = await deployedInfra.consensus.getCurrentFrame()
const refSlot = consensusFrame.refSlot.toNumber()
assert.equals(epochId, Math.floor((refSlot + 1) / SLOTS_PER_EPOCH))
assert.equals(frameStartTime, computeTimestampAtSlot(refSlot + 1))
assert.equals(frameEndTime, computeTimestampAtEpoch(+epochId + EPOCHS_PER_FRAME) - 1)
})

it.skip('handlePostTokenRebase from lido')
})
})
5 changes: 0 additions & 5 deletions test/0.4.24/legacyoracle.test.js

This file was deleted.

Loading