feat: RecoveryManager role on GovernanceRouter (#353)
* RecoveryManager role Fix inequality * Require recovery activated to exit recovery * Add RecoveryManager tests * Move utils & pass lint * Test transfer RecoveryManager role: * Refactor RecoveryManager tests * Change block.number -> timestamp * ran prettier * Add recovery manager to typescript * Fix comment * Re-arrange inRecovery check Co-authored-by: Conner Swann <me@connerswann.me>buddies-main-deployment
parent
5b2e48a358
commit
d20438434c
@ -0,0 +1,467 @@ |
|||||||
|
const { provider } = waffle; |
||||||
|
const { expect } = require('chai'); |
||||||
|
const testUtils = require('../utils'); |
||||||
|
const { domainsToTestConfigs } = require('./generateTestChainConfigs'); |
||||||
|
const { formatCall, sendFromSigner } = require('./crossChainTestUtils'); |
||||||
|
const { |
||||||
|
deployMultipleChains, |
||||||
|
getHome, |
||||||
|
getGovernanceRouter, |
||||||
|
getUpdaterManager, |
||||||
|
} = require('./deployCrossChainTest'); |
||||||
|
|
||||||
|
async function expectNotInRecovery( |
||||||
|
updaterManager, |
||||||
|
recoveryManager, |
||||||
|
randomSigner, |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
home, |
||||||
|
) { |
||||||
|
expect(await governanceRouter.inRecovery()).to.be.false; |
||||||
|
|
||||||
|
// Format optics call message
|
||||||
|
const call = await formatCall(updaterManager, 'setUpdater', [ |
||||||
|
randomSigner.address, |
||||||
|
]); |
||||||
|
|
||||||
|
// Expect that Governor *CAN* Call Local & Call Remote
|
||||||
|
// dispatch call on local governorRouter
|
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'callLocal', [[call]]), |
||||||
|
) |
||||||
|
.to.emit(home, 'NewUpdater') |
||||||
|
.withArgs(randomSigner.address); |
||||||
|
|
||||||
|
// dispatch call on local governorRouter
|
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'callRemote', [2000, [call]]), |
||||||
|
).to.emit(home, 'Dispatch'); |
||||||
|
|
||||||
|
// set xApp Connection Manager
|
||||||
|
const xAppConnectionManager = await governanceRouter.xAppConnectionManager(); |
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'setXAppConnectionManager', [ |
||||||
|
randomSigner.address, |
||||||
|
]), |
||||||
|
).to.not.be.reverted; |
||||||
|
// reset xApp Connection Manager to actual contract
|
||||||
|
await sendFromSigner(governor, governanceRouter, 'setXAppConnectionManager', [ |
||||||
|
xAppConnectionManager, |
||||||
|
]); |
||||||
|
|
||||||
|
// set Router Locally
|
||||||
|
const otherDomain = 2000; |
||||||
|
const previousRouter = await governanceRouter.routers(otherDomain); |
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'setRouterLocal', [ |
||||||
|
2000, |
||||||
|
optics.ethersAddressToBytes32(randomSigner.address), |
||||||
|
]), |
||||||
|
) |
||||||
|
.to.emit(governanceRouter, 'SetRouter') |
||||||
|
.withArgs( |
||||||
|
otherDomain, |
||||||
|
previousRouter, |
||||||
|
optics.ethersAddressToBytes32(randomSigner.address), |
||||||
|
); |
||||||
|
|
||||||
|
// Expect that Recovery Manager CANNOT Call Local OR Call Remote
|
||||||
|
// cannot dispatch call on local governorRouter
|
||||||
|
await expect( |
||||||
|
sendFromSigner(recoveryManager, governanceRouter, 'callLocal', [[call]]), |
||||||
|
).to.be.revertedWith('! called by governor'); |
||||||
|
|
||||||
|
// cannot dispatch call to remote governorRouter
|
||||||
|
await expect( |
||||||
|
sendFromSigner(recoveryManager, governanceRouter, 'callRemote', [ |
||||||
|
2000, |
||||||
|
[call], |
||||||
|
]), |
||||||
|
).to.be.revertedWith('! called by governor'); |
||||||
|
|
||||||
|
// cannot set xAppConnectionManager
|
||||||
|
await expect( |
||||||
|
sendFromSigner( |
||||||
|
recoveryManager, |
||||||
|
governanceRouter, |
||||||
|
'setXAppConnectionManager', |
||||||
|
[randomSigner.address], |
||||||
|
), |
||||||
|
).to.be.revertedWith('! called by governor'); |
||||||
|
|
||||||
|
// cannot set Router
|
||||||
|
await expect( |
||||||
|
sendFromSigner(recoveryManager, governanceRouter, 'setRouterLocal', [ |
||||||
|
2000, |
||||||
|
optics.ethersAddressToBytes32(randomSigner.address), |
||||||
|
]), |
||||||
|
).to.be.revertedWith('! called by governor'); |
||||||
|
} |
||||||
|
|
||||||
|
async function expectOnlyRecoveryManagerCanTransferRole( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
) { |
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'transferRecoveryManager', [ |
||||||
|
randomSigner.address, |
||||||
|
]), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
|
||||||
|
await expect( |
||||||
|
sendFromSigner(randomSigner, governanceRouter, 'transferRecoveryManager', [ |
||||||
|
randomSigner.address, |
||||||
|
]), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
|
||||||
|
await expect( |
||||||
|
sendFromSigner( |
||||||
|
recoveryManager, |
||||||
|
governanceRouter, |
||||||
|
'transferRecoveryManager', |
||||||
|
[randomSigner.address], |
||||||
|
), |
||||||
|
) |
||||||
|
.to.emit(governanceRouter, 'TransferRecoveryManager') |
||||||
|
.withArgs(recoveryManager.address, randomSigner.address); |
||||||
|
|
||||||
|
await expect( |
||||||
|
sendFromSigner(randomSigner, governanceRouter, 'transferRecoveryManager', [ |
||||||
|
recoveryManager.address, |
||||||
|
]), |
||||||
|
) |
||||||
|
.to.emit(governanceRouter, 'TransferRecoveryManager') |
||||||
|
.withArgs(randomSigner.address, recoveryManager.address); |
||||||
|
} |
||||||
|
|
||||||
|
async function expectOnlyRecoveryManagerCanExitRecovery( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
) { |
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'exitRecovery', []), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
|
||||||
|
await expect( |
||||||
|
sendFromSigner(randomSigner, governanceRouter, 'exitRecovery', []), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
|
||||||
|
await expect( |
||||||
|
sendFromSigner(recoveryManager, governanceRouter, 'exitRecovery', []), |
||||||
|
) |
||||||
|
.to.emit(governanceRouter, 'ExitRecovery') |
||||||
|
.withArgs(recoveryManager.address); |
||||||
|
} |
||||||
|
|
||||||
|
async function expectOnlyRecoveryManagerCanInitiateRecovery( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
) { |
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'initiateRecoveryTimelock', []), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
|
||||||
|
await expect( |
||||||
|
sendFromSigner( |
||||||
|
randomSigner, |
||||||
|
governanceRouter, |
||||||
|
'initiateRecoveryTimelock', |
||||||
|
[], |
||||||
|
), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
|
||||||
|
expect(await governanceRouter.recoveryActiveAt()).to.equal(0); |
||||||
|
|
||||||
|
await expect( |
||||||
|
sendFromSigner( |
||||||
|
recoveryManager, |
||||||
|
governanceRouter, |
||||||
|
'initiateRecoveryTimelock', |
||||||
|
[], |
||||||
|
), |
||||||
|
).to.emit(governanceRouter, 'InitiateRecovery'); |
||||||
|
|
||||||
|
expect(await governanceRouter.recoveryActiveAt()).to.not.equal(0); |
||||||
|
} |
||||||
|
|
||||||
|
/* |
||||||
|
* Deploy the full Optics suite on two chains |
||||||
|
*/ |
||||||
|
describe('RecoveryManager', async () => { |
||||||
|
const domains = [1000, 2000]; |
||||||
|
const domain = 1000; |
||||||
|
const walletProvider = new testUtils.WalletProvider(provider); |
||||||
|
const [ |
||||||
|
governor, |
||||||
|
recoveryManager, |
||||||
|
randomSigner, |
||||||
|
] = walletProvider.getWalletsPersistent(5); |
||||||
|
|
||||||
|
let governanceRouter, home, updaterManager, chainDetails; |
||||||
|
|
||||||
|
before(async () => { |
||||||
|
// generate TestChainConfigs for the given domains
|
||||||
|
const configs = await domainsToTestConfigs( |
||||||
|
domains, |
||||||
|
recoveryManager.address, |
||||||
|
); |
||||||
|
|
||||||
|
// deploy the entire Optics suite on each chain
|
||||||
|
chainDetails = await deployMultipleChains(configs); |
||||||
|
|
||||||
|
// get the governance router
|
||||||
|
governanceRouter = getGovernanceRouter(chainDetails, domain); |
||||||
|
// transfer governorship to the governor signer
|
||||||
|
await governanceRouter.transferGovernor(domain, governor.address); |
||||||
|
|
||||||
|
home = getHome(chainDetails, domain); |
||||||
|
|
||||||
|
updaterManager = getUpdaterManager(chainDetails, domain); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Initiated: Timelock has not been set', async () => { |
||||||
|
expect(await governanceRouter.recoveryActiveAt()).to.equal(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Initiated: Cannot Exit Recovery yet', async () => { |
||||||
|
await expect( |
||||||
|
sendFromSigner(recoveryManager, governanceRouter, 'exitRecovery', []), |
||||||
|
).to.be.revertedWith('recovery not initiated'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Initiated: Not in Recovery (Governor CAN Call Local & Remote; Recovery Manager CANNOT Call either)', async () => { |
||||||
|
await expectNotInRecovery( |
||||||
|
updaterManager, |
||||||
|
recoveryManager, |
||||||
|
randomSigner, |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
home, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Initiated: ONLY RecoveryManager can transfer RecoveryManager role', async () => { |
||||||
|
await expectOnlyRecoveryManagerCanTransferRole( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Initiated: ONLY RecoveryManager can Initiate Recovery', async () => { |
||||||
|
await expectOnlyRecoveryManagerCanInitiateRecovery( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Active: CANNOT Initiate Recovery Twice', async () => { |
||||||
|
await expect( |
||||||
|
sendFromSigner( |
||||||
|
recoveryManager, |
||||||
|
governanceRouter, |
||||||
|
'initiateRecoveryTimelock', |
||||||
|
[], |
||||||
|
), |
||||||
|
).to.be.revertedWith('recovery already initiated'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Active: Not in Recovery (Governor CAN Call Local & Remote; Recovery Manager CANNOT Call either)', async () => { |
||||||
|
await expectNotInRecovery( |
||||||
|
updaterManager, |
||||||
|
recoveryManager, |
||||||
|
randomSigner, |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
home, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Active: ONLY RecoveryManager can transfer RecoveryManager role', async () => { |
||||||
|
await expectOnlyRecoveryManagerCanTransferRole( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Active: ONLY RecoveryManager can Exit Recovery', async () => { |
||||||
|
await expectOnlyRecoveryManagerCanExitRecovery( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Before Recovery Active: ONLY RecoveryManager can Initiate Recovery (CAN initiate a second time)', async () => { |
||||||
|
await expectOnlyRecoveryManagerCanInitiateRecovery( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: inRecovery becomes true when timelock expires', async () => { |
||||||
|
// increase timestamp on-chain
|
||||||
|
const timelock = await governanceRouter.recoveryTimelock(); |
||||||
|
await testUtils.increaseTimestampBy(provider, timelock.toNumber()); |
||||||
|
expect(await governanceRouter.inRecovery()).to.be.true; |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: RecoveryManager CAN call local', async () => { |
||||||
|
// Format optics call message
|
||||||
|
const call = await formatCall(updaterManager, 'setUpdater', [ |
||||||
|
randomSigner.address, |
||||||
|
]); |
||||||
|
|
||||||
|
// dispatch call on local governorRouter
|
||||||
|
await expect( |
||||||
|
sendFromSigner(recoveryManager, governanceRouter, 'callLocal', [[call]]), |
||||||
|
) |
||||||
|
.to.emit(home, 'NewUpdater') |
||||||
|
.withArgs(randomSigner.address); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: RecoveryManager CANNOT call remote', async () => { |
||||||
|
// Format optics call message
|
||||||
|
const call = await formatCall(updaterManager, 'setUpdater', [ |
||||||
|
randomSigner.address, |
||||||
|
]); |
||||||
|
|
||||||
|
// dispatch call on local governorRouter
|
||||||
|
await expect( |
||||||
|
sendFromSigner(recoveryManager, governanceRouter, 'callRemote', [ |
||||||
|
2000, |
||||||
|
[call], |
||||||
|
]), |
||||||
|
).to.be.revertedWith('! called by governor'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: RecoveryManager CAN set xAppConnectionManager', async () => { |
||||||
|
// set xApp Connection Manager
|
||||||
|
const xAppConnectionManager = await governanceRouter.xAppConnectionManager(); |
||||||
|
await expect( |
||||||
|
sendFromSigner( |
||||||
|
recoveryManager, |
||||||
|
governanceRouter, |
||||||
|
'setXAppConnectionManager', |
||||||
|
[randomSigner.address], |
||||||
|
), |
||||||
|
).to.not.be.reverted; |
||||||
|
// reset xApp Connection Manager to actual contract
|
||||||
|
await sendFromSigner( |
||||||
|
recoveryManager, |
||||||
|
governanceRouter, |
||||||
|
'setXAppConnectionManager', |
||||||
|
[xAppConnectionManager], |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: RecoveryManager CAN set Router locally', async () => { |
||||||
|
const otherDomain = 2000; |
||||||
|
const previousRouter = await governanceRouter.routers(otherDomain); |
||||||
|
await expect( |
||||||
|
sendFromSigner(recoveryManager, governanceRouter, 'setRouterLocal', [ |
||||||
|
2000, |
||||||
|
optics.ethersAddressToBytes32(randomSigner.address), |
||||||
|
]), |
||||||
|
) |
||||||
|
.to.emit(governanceRouter, 'SetRouter') |
||||||
|
.withArgs( |
||||||
|
otherDomain, |
||||||
|
previousRouter, |
||||||
|
optics.ethersAddressToBytes32(randomSigner.address), |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: Governor CANNOT call local OR remote', async () => { |
||||||
|
// Format optics call message
|
||||||
|
const call = await formatCall(updaterManager, 'setUpdater', [ |
||||||
|
randomSigner.address, |
||||||
|
]); |
||||||
|
|
||||||
|
// dispatch call on local governorRouter
|
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'callLocal', [[call]]), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
|
||||||
|
// dispatch call on local governorRouter
|
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'callRemote', [2000, [call]]), |
||||||
|
).to.be.revertedWith('in recovery'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: Governor CANNOT set xAppConnectionManager', async () => { |
||||||
|
// cannot set xAppConnectionManager
|
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'setXAppConnectionManager', [ |
||||||
|
randomSigner.address, |
||||||
|
]), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: Governor CANNOT set Router locally', async () => { |
||||||
|
// cannot set Router
|
||||||
|
await expect( |
||||||
|
sendFromSigner(governor, governanceRouter, 'setRouterLocal', [ |
||||||
|
2000, |
||||||
|
optics.ethersAddressToBytes32(randomSigner.address), |
||||||
|
]), |
||||||
|
).to.be.revertedWith('! called by recovery manager'); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: ONLY RecoveryManager can transfer RecoveryManager role', async () => { |
||||||
|
await expectOnlyRecoveryManagerCanTransferRole( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Recovery Active: ONLY RecoveryManager can Exit Recovery', async () => { |
||||||
|
await expectOnlyRecoveryManagerCanExitRecovery( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Exited Recovery: Timelock is deleted', async () => { |
||||||
|
expect(await governanceRouter.recoveryActiveAt()).to.equal(0); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Exited Recovery: Not in Recovery (Governor CAN Call Local & Remote; Recovery Manager CANNOT Call either)', async () => { |
||||||
|
await expectNotInRecovery( |
||||||
|
updaterManager, |
||||||
|
recoveryManager, |
||||||
|
randomSigner, |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
home, |
||||||
|
); |
||||||
|
}); |
||||||
|
|
||||||
|
it('Exited Recovery: ONLY RecoveryManager can transfer RecoveryManager role', async () => { |
||||||
|
await expectOnlyRecoveryManagerCanTransferRole( |
||||||
|
governor, |
||||||
|
governanceRouter, |
||||||
|
randomSigner, |
||||||
|
recoveryManager, |
||||||
|
); |
||||||
|
}); |
||||||
|
}); |
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Loading…
Reference in new issue