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

multisig facade #1206

Open
wants to merge 5 commits into
base: 4.0.0
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
4 changes: 4 additions & 0 deletions common/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ interface INetworkConfig {
AAVE_V3_POOL?: string
STARGATE_STAKING_CONTRACT?: string
CURVE_POOL_WETH_FRXETH?: string
DEV_MULTISIG?: string
}

export const networkConfig: { [key: string]: INetworkConfig } = {
Expand Down Expand Up @@ -306,6 +307,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
AAVE_V3_INCENTIVES_CONTROLLER: '0x8164Cc65827dcFe994AB23944CBC90e0aa80bFcb',
STARGATE_STAKING_CONTRACT: '0xB0D502E938ed5f4df2E681fE6E419ff29631d62b',
CURVE_POOL_WETH_FRXETH: '0x9c3b46c0ceb5b9e304fcd6d88fc50f7dd24b31bc',
DEV_MULTISIG: '0xe63AfB49529E135be5ab2747104fF1D3ceAE0EFf', // same on all networks
},
'3': {
name: 'tenderly',
Expand Down Expand Up @@ -539,6 +541,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
AAVE_V3_POOL: '0xA238Dd80C259a72e81d7e4664a9801593F98d1c5',
AAVE_V3_INCENTIVES_CONTROLLER: '0xf9cc4F0D883F1a1eb2c253bdb46c254Ca51E1F44',
STARGATE_STAKING_CONTRACT: '0x06Eb48763f117c7Be887296CDcdfad2E4092739C',
DEV_MULTISIG: '0xe63AfB49529E135be5ab2747104fF1D3ceAE0EFf', // same on all networks
},
'42161': {
name: 'arbitrum',
Expand Down Expand Up @@ -579,6 +582,7 @@ export const networkConfig: { [key: string]: INetworkConfig } = {
COMET_EXT: '0x1B2E88cC7365d90e7E81392432482925BD8437E9',
AAVE_V3_POOL: '0x794a61358D6845594F94dc1DB02A252b5b4814aD',
AAVE_V3_INCENTIVES_CONTROLLER: '0x929EC64c34a17401F460460D4B9390518E5B473e',
DEV_MULTISIG: '0xe63AfB49529E135be5ab2747104fF1D3ceAE0EFf', // same on all networks
},
'421614': {
name: 'arbitrum-sepolia',
Expand Down
4 changes: 3 additions & 1 deletion contracts/facade/Facade.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@
mapping(bytes4 => address) public facets;

// solhint-disable-next-line no-empty-blocks
constructor() Ownable() {}
constructor(address owner) Ownable() {
_transferOwnership(owner);
}

// Save new facets to the Facade, forcefully
function save(address facet, bytes4[] memory selectors) external onlyOwner {
Expand All @@ -27,12 +29,12 @@

// Find the facet for function that is called and execute the
// function if a facet is found and return any value.
fallback() external {

Check warning on line 32 in contracts/facade/Facade.sol

View workflow job for this annotation

GitHub Actions / Lint Checks

When fallback is not payable you will not be able to receive ether

Check warning on line 32 in contracts/facade/Facade.sol

View workflow job for this annotation

GitHub Actions / Lint Checks

Fallback function must be simple
address facet = facets[msg.sig];
require(facet != address(0), "facet does not exist");

// Execute external function from facet using delegatecall and return any value.
assembly {

Check warning on line 37 in contracts/facade/Facade.sol

View workflow job for this annotation

GitHub Actions / Lint Checks

Avoid to use inline assembly. It is acceptable only in rare cases
// copy function selector and any arguments
calldatacopy(0, 0, calldatasize())
// execute function call using the facet
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
"@openzeppelin/contracts": "4.9.6",
"@openzeppelin/contracts-upgradeable": "4.9.6",
"@openzeppelin/hardhat-upgrades": "^1.23.0",
"@safe-global/api-kit": "^2.4.5",
"@safe-global/protocol-kit": "^4.1.0",
"@tenderly/hardhat-tenderly": "^1.7.7",
"@typechain/ethers-v5": "^7.2.0",
"@typechain/hardhat": "^2.3.1",
Expand Down
2 changes: 1 addition & 1 deletion scripts/addresses/1-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"GNOSIS_EASY_AUCTION": "0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101"
},
"tradingLib": "0xa54544C6C36C0d776cc4F04EBB847e0BB3A11ea2",
"facade": "0x2C7ca56342177343A2954C250702Fd464f4d0613",
"facade": "0x9d49b363d492725412bf8350bBb525B7a80a7470",
"facets": {
"actFacet": "0xCAB3D3d0d5544145A6BCB47e58F61368BCcAe2dB",
"readFacet": "0x823110a13eB26cB09c4Bb118DBfE4ff5f96D5526",
Expand Down
2 changes: 1 addition & 1 deletion scripts/addresses/42161-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"tradingLib": "0x348644F24FA34c40a8E3C4Cf9aF14f8a96aD63fC",
"cvxMiningLib": "",
"facade": "0x387A0C36681A22F728ab54426356F4CAa6bB48a9",
"facade": "0xb48a407e225b4764fD3BCc2a2329fF7745af7e64",
"facets": {
"actFacet": "0xE774CCF1431c3DEe7Fa4c20f67534b61289CAa45",
"readFacet": "0x15175d35F3d88548B49600B4ee8067253A2e4e66",
Expand Down
2 changes: 1 addition & 1 deletion scripts/addresses/8453-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"GNOSIS_EASY_AUCTION": "0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02"
},
"tradingLib": "0x6419fe6cf428150e2d8ed38a3316b1bb468f79a7",
"facade": "0xEb2071e9B542555E90E6e4E1F83fa17423583991",
"facade": "0xE41416d8dC94ac1F6d12282d6D46B714F39a87d9",
"facets": {
"actFacet": "0x0eac15B9Fe585432E48Cf175571D75D111861F43",
"readFacet": "0x5Af543D6F95a98200Dd770f39A902Fe793BAeB27",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
},
"tradingLib": "0x348644F24FA34c40a8E3C4Cf9aF14f8a96aD63fC",
"cvxMiningLib": "",
"facade": "0x387A0C36681A22F728ab54426356F4CAa6bB48a9",
"facade": "0xb48a407e225b4764fD3BCc2a2329fF7745af7e64",
"facets": {
"actFacet": "0xE774CCF1431c3DEe7Fa4c20f67534b61289CAa45",
"readFacet": "0x15175d35F3d88548B49600B4ee8067253A2e4e66",
Expand Down
2 changes: 1 addition & 1 deletion scripts/addresses/base-3.4.0/8453-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"GNOSIS_EASY_AUCTION": "0xb1875Feaeea32Bbb02DE83D81772e07E37A40f02"
},
"tradingLib": "0x6419fe6cf428150e2d8ed38a3316b1bb468f79a7",
"facade": "0xEb2071e9B542555E90E6e4E1F83fa17423583991",
"facade": "0xE41416d8dC94ac1F6d12282d6D46B714F39a87d9",
"facets": {
"actFacet": "0x0eac15B9Fe585432E48Cf175571D75D111861F43",
"readFacet": "0x5Af543D6F95a98200Dd770f39A902Fe793BAeB27",
Expand Down
2 changes: 1 addition & 1 deletion scripts/addresses/mainnet-3.4.0/1-tmp-deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"GNOSIS_EASY_AUCTION": "0x0b7fFc1f4AD541A4Ed16b40D8c37f0929158D101"
},
"tradingLib": "0xa54544C6C36C0d776cc4F04EBB847e0BB3A11ea2",
"facade": "0x2C7ca56342177343A2954C250702Fd464f4d0613",
"facade": "0x9d49b363d492725412bf8350bBb525B7a80a7470",
"facets": {
"actFacet": "0xCAB3D3d0d5544145A6BCB47e58F61368BCcAe2dB",
"readFacet": "0x823110a13eB26cB09c4Bb118DBfE4ff5f96D5526",
Expand Down
3 changes: 2 additions & 1 deletion scripts/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ async function main() {
'phase1-facade/2_deploy_actFacet.ts',
'phase1-facade/3_deploy_maxIssuable.ts',
'phase1-facade/4_deploy_backingBufferFacet.ts',
'phase1-facade/5_deploy_revenueFacet.ts'
'phase1-facade/5_deploy_revenueFacet.ts',
'phase1-facade/save.ts'
)

// =============================================
Expand Down
6 changes: 5 additions & 1 deletion scripts/deployment/phase1-core/4_deploy_facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,14 @@ async function main() {
throw new Error(`RSR Asset contract not found in network ${hre.network.name}`)
}

if (!networkConfig[chainId].DEV_MULTISIG) {
throw new Error(`Missing DEV_MULTISIG address in network ${hre.network.name}`)
}

// ******************** Deploy Facade ****************************************/

const FacadeFactory = await ethers.getContractFactory('Facade')
facade = <Facade>await FacadeFactory.connect(burner).deploy()
facade = <Facade>await FacadeFactory.connect(burner).deploy(networkConfig[chainId].DEV_MULTISIG)
await facade.deployed()

// Write temporary deployments file
Expand Down
13 changes: 0 additions & 13 deletions scripts/deployment/phase1-facade/1_deploy_readFacet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,6 @@ async function main() {
console.log(`Deployed to ${hre.network.name} (${chainId})
ReadFacet: ${readFacet.address}
Deployment file: ${deploymentFilename}`)

// ******************** Save to Facade ****************************************/

console.log('Configuring with Facade...')

// Save ReadFacet to Facade
const facade = await ethers.getContractAt('Facade', deployments.facade)
await facade.save(
readFacet.address,
Object.entries(readFacet.functions).map(([fn]) => readFacet.interface.getSighash(fn))
)

console.log('Finished saving to Facade')
}

main().catch((error) => {
Expand Down
13 changes: 0 additions & 13 deletions scripts/deployment/phase1-facade/2_deploy_actFacet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,6 @@ async function main() {
console.log(`Deployed to ${hre.network.name} (${chainId})
ActFacet: ${actFacet.address}
Deployment file: ${deploymentFilename}`)

// ******************** Save to Facade ****************************************/

console.log('Configuring with Facade...')

// Save ReadFacet to Facade
const facade = await ethers.getContractAt('Facade', deployments.facade)
await facade.save(
actFacet.address,
Object.entries(actFacet.functions).map(([fn]) => actFacet.interface.getSighash(fn))
)

console.log('Finished saving to Facade')
}

main().catch((error) => {
Expand Down
15 changes: 0 additions & 15 deletions scripts/deployment/phase1-facade/3_deploy_maxIssuable.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,6 @@ async function main() {
console.log(`Deployed to ${hre.network.name} (${chainId})
MaxIssuableFacet: ${maxIssuableFacet.address}
Deployment file: ${deploymentFilename}`)

// ******************** Save to Facade ****************************************/

console.log('Configuring with Facade...')

// Save MaxIssuableFacet functions to Facade
const facade = await ethers.getContractAt('Facade', deployments.facade)
await facade.save(
maxIssuableFacet.address,
Object.entries(maxIssuableFacet.functions).map(([fn]) =>
maxIssuableFacet.interface.getSighash(fn)
)
)

console.log('Finished saving to Facade')
}

main().catch((error) => {
Expand Down
15 changes: 0 additions & 15 deletions scripts/deployment/phase1-facade/4_deploy_backingBufferFacet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,21 +44,6 @@ async function main() {
console.log(`Deployed to ${hre.network.name} (${chainId})
BackingBufferFacet: ${backingBufferFacet.address}
Deployment file: ${deploymentFilename}`)

// ******************** Save to Facade ****************************************/

console.log('Configuring with Facade...')

// Save BackingBufferFacet functions to Facade
const facade = await ethers.getContractAt('Facade', deployments.facade)
await facade.save(
backingBufferFacet.address,
Object.entries(backingBufferFacet.functions).map(([fn]) =>
backingBufferFacet.interface.getSighash(fn)
)
)

console.log('Finished saving to Facade')
}

main().catch((error) => {
Expand Down
13 changes: 0 additions & 13 deletions scripts/deployment/phase1-facade/5_deploy_revenueFacet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,6 @@ async function main() {
console.log(`Deployed to ${hre.network.name} (${chainId})
RevenueFacet: ${revenueFacet.address}
Deployment file: ${deploymentFilename}`)

// ******************** Save to Facade ****************************************/

console.log('Configuring with Facade...')

// Save RevenueFacet functions to Facade
const facade = await ethers.getContractAt('Facade', deployments.facade)
await facade.save(
revenueFacet.address,
Object.entries(revenueFacet.functions).map(([fn]) => revenueFacet.interface.getSighash(fn))
)

console.log('Finished saving to Facade')
}

main().catch((error) => {
Expand Down
60 changes: 60 additions & 0 deletions scripts/deployment/phase1-facade/save.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import hre, { ethers } from 'hardhat'

import { getChainId, isValidContract } from '../../../common/blockchain-utils'
import { getDeploymentFile, getDeploymentFilename, IDeployments } from '../common'
import { initiateMultisigTxs } from '../utils'
import { MetaTransactionData } from '@safe-global/safe-core-sdk-types'

async function main() {
// ==== Read Configuration ====
const [burner] = await hre.ethers.getSigners()
const chainId = await getChainId(hre)

console.log(`Saving Facets to Facade on network ${hre.network.name} (${chainId})
with burner account: ${burner.address}`)

const deploymentFilename = getDeploymentFilename(chainId)
const deployments = <IDeployments>getDeploymentFile(deploymentFilename)

// Check facade exists
if (!deployments.facade) {
throw new Error(`Missing deployed contracts in network ${hre.network.name}`)
} else if (!(await isValidContract(hre, deployments.facade))) {
throw new Error(`Facade contract not found in network ${hre.network.name}`)
}

// ******************** Save Facets to Facade ****************************************/

if (hre.network.name == 'localhost' || hre.network.name == 'hardhat') {
console.log('Skipping saving facets on localhost')
return
}

const facade = await ethers.getContractAt('Facade', deployments.facade)

const facets = [
await ethers.getContractAt('ReadFacet', deployments.facets.readFacet),
await ethers.getContractAt('ActFacet', deployments.facets.actFacet),
await ethers.getContractAt('MaxIssuableFacet', deployments.facets.maxIssuableFacet),
await ethers.getContractAt('BackingBufferFacet', deployments.facets.backingBufferFacet),
await ethers.getContractAt('RevenueFacet', deployments.facets.revenueFacet),
]

const txs = facets.map((facet): MetaTransactionData => {
return {
to: facade.address,
value: '0',
data: facade.interface.encodeFunctionData('save', [
facet.address,
Object.entries(facet.functions).map(([fn]) => facet.interface.getSighash(fn)),
]),
}
})

await initiateMultisigTxs(chainId, txs)
}

main().catch((error) => {
console.error(error)
process.exitCode = 1
})
57 changes: 57 additions & 0 deletions scripts/deployment/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ import { IComponents, arbitrumL2Chains, baseL2Chains } from '../../common/config
import { isValidContract } from '../../common/blockchain-utils'
import { IDeployments } from './common'
import { useEnv } from '#/utils/env'
import { networkConfig } from '../../common/configuration'

import Safe from '@safe-global/protocol-kit'
import SafeApiKit from '@safe-global/api-kit'
import { HttpNetworkConfig } from 'hardhat/types'
import { MetaTransactionData } from '@safe-global/safe-core-sdk-types'

export const priceTimeout = bn('604800') // 1 week

Expand Down Expand Up @@ -274,3 +280,54 @@ export const getUsdtOracleError = (network: string): BigNumber => {
return fp('0.0025') // 0.25% mainnet
}
}

export const initiateMultisigTx = async (
chainId: string,
tx: MetaTransactionData
): Promise<void> => {
return await initiateMultisigTxs(chainId, [tx])
}

// Note: may end up with conflicting nonces if used naively
export const initiateMultisigTxs = async (
chainId: string,
txs: MetaTransactionData[]
): Promise<void> => {
if (hre.network.name == 'localhost' || hre.network.name == 'hardhat') {
console.log('Skipping multisig tx on localhost')
return
}

const provider = (hre.config.networks[hre.network.name] as HttpNetworkConfig).url
const wallet = hre.ethers.Wallet.fromMnemonic(process.env.MNEMONIC!)
const safeAddress = networkConfig[chainId].DEV_MULTISIG!

const safe = await Safe.init({
provider,
signer: wallet.privateKey,
safeAddress,
})
const safeApi = new SafeApiKit({
chainId: parseInt(chainId) as unknown as bigint,
})
const safeTx = await safe.createTransaction({ transactions: txs })
const safeTxHash = await safe.getTransactionHash(safeTx)
const signature = await safe.signHash(safeTxHash)

// Propose transaction to the service
await safeApi.proposeTransaction({
safeAddress: await safe.getAddress(),
safeTransactionData: safeTx.data,
safeTxHash,
senderAddress: wallet.address,
senderSignature: signature.data,
})

let prefix = 'base'
if (hre.network.name == 'mainnet') prefix = 'eth'
else if (hre.network.name == 'arbitrum') prefix = 'arb'

const hyperlink = `https://app.safe.global/transactions/queue?safe=${prefix}:${networkConfig[chainId].DEV_MULTISIG}`

console.log(`Queued tx, requires confirmation: ${hyperlink}`)
}
7 changes: 6 additions & 1 deletion scripts/verification/4_verify_facade.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,12 @@ async function main() {
deployments = <IDeployments>getDeploymentFile(getDeploymentFilename(chainId))

/** ******************** Verify Facade ****************************************/
await verifyContract(chainId, deployments.facade, [], 'contracts/facade/Facade.sol:Facade')
await verifyContract(
chainId,
deployments.facade,
[networkConfig[chainId].DEV_MULTISIG],
'contracts/facade/Facade.sol:Facade'
)

/** ******************** Verify ReadFacet ****************************************/
await verifyContract(
Expand Down
7 changes: 6 additions & 1 deletion test/fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -740,7 +740,12 @@ const makeDefaultFixture = async (setBasket: boolean): Promise<DefaultFixture> =

// Deploy Facade
const FacadeFactory: ContractFactory = await ethers.getContractFactory('Facade')
const facade = await ethers.getContractAt('TestIFacade', (await FacadeFactory.deploy()).address)
const facade = await ethers.getContractAt(
'TestIFacade',
(
await FacadeFactory.deploy(owner.address)
).address
)

// Save ReadFacet to Facade
const ReadFacetFactory: ContractFactory = await ethers.getContractFactory('ReadFacet')
Expand Down
Loading
Loading