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

Add set default registry function, tests and scripts #20

Merged
merged 3 commits into from
Nov 10, 2023
Merged
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
5 changes: 4 additions & 1 deletion addresses/mumbai/index.json
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
"address": "0x61c22C4e709c3027ecCCdd6ee562BB583Db9a17f"
},
"RolesRegistry": {
"address": "0xB2aD616e84e1eF7A9ED0fA3169AAF31Ee51EA824"
"address": "0xB1b599Ec67ad23AF7FAC240191319822e674571A"
},
"KMSDeployer": {
"address": "0x04c8c6c56dab836f8bd62cb6884371507e706806"
@@ -13,5 +13,8 @@
"operator": "0x61c22C4e709c3027ecCCdd6ee562BB583Db9a17f",
"implementation": "0x92Ea5e9D5341F10eb6291BB72F3C9e617090952C",
"proxyAdmin": "0x3033689fB46FDe57789A8c84AD1C9bD3A8217985"
},
"ImmutableOwnerCreate2Factory": {
"address": "0x066f91a9Aa4C33D4ea4c12aBee6f4cb4e919F71d"
}
}
5 changes: 4 additions & 1 deletion addresses/polygon/index.json
Original file line number Diff line number Diff line change
@@ -3,7 +3,7 @@
"address": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8"
},
"RolesRegistry": {
"address": "0xB2aD616e84e1eF7A9ED0fA3169AAF31Ee51EA824"
"address": "0xB1b599Ec67ad23AF7FAC240191319822e674571A"
},
"KMSDeployer": {
"address": "0x04c8c6c56dab836f8bd62cb6884371507e706806"
@@ -13,5 +13,8 @@
"operator": "0x359E1208DE02Af11461A37D72165Ef2dcD2Adfc8",
"implementation": "0x69C9457dC1FDf2E84C4e45402967f3b88Cc6FF00",
"proxyAdmin": "0x48c769f6a8de57d824f0e7330d7A27dee53a43cD"
},
"ImmutableOwnerCreate2Factory": {
"address": "0x066f91a9Aa4C33D4ea4c12aBee6f4cb4e919F71d"
}
}
9 changes: 9 additions & 0 deletions contracts/OriumMarketplace.sol
Original file line number Diff line number Diff line change
@@ -808,6 +808,15 @@ contract OriumMarketplace is Initializable, OwnableUpgradeable, PausableUpgradea
emit RolesRegistrySet(_tokenAddress, _rolesRegistry);
}

/**
* @notice Sets the default roles registry.
* @dev Only owner can set the default roles registry.
* @param _rolesRegistry The roles registry address.
*/
function setDefaultRolesRegistry(address _rolesRegistry) external onlyOwner {
defaultRolesRegistry = _rolesRegistry;
}

/** ######### Getters ########### **/

/**
9 changes: 9 additions & 0 deletions contracts/interfaces/IImmutableOwnerCreate2Factory.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// SPDX-License-Identifier: CC0-1.0

pragma solidity 0.8.9;


interface IImmutableOwnerCreate2Factory {
function deploy(bytes32 salt, bytes memory bytecode) external returns (address);
function computeAddress(bytes32 salt, bytes32 bytecodeHash) external view returns (address);
}
47 changes: 41 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -44,6 +44,7 @@
"@typechain/ethers-v5": "^10.1.0",
"@typechain/hardhat": "^6.1.3",
"@types/mocha": "^10.0.1",
"@types/node": "^20.9.0",
"chai": "^4.3.6",
"ethers": "^5.7.1",
"hardhat": "2.11.2",
28 changes: 28 additions & 0 deletions scripts/03-set-default-roles-registry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { ethers, network as hardhatNetwork } from 'hardhat'
import config, { Network } from '../addresses'
import { colors, print, confirmOrDie } from '../utils/misc'

async function main() {
const NETWORK = hardhatNetwork.name as Network
const CONTRACT_NAME = 'OriumMarketplace'
const CONTRACT_ADDRESS = config[NETWORK][CONTRACT_NAME].address

await confirmOrDie(
`Are you sure you want to set default roles registry ${config[NETWORK].RolesRegistry.address} for ${CONTRACT_NAME} on ${NETWORK} network?`,
)

const contract = await ethers.getContractAt(CONTRACT_NAME, CONTRACT_ADDRESS)
const tx = await contract.setDefaultRolesRegistry(config[NETWORK].RolesRegistry.address)

print(colors.highlight, `Transaction hash: ${tx.hash}`)
print(colors.success, `Default roles registry set for ${CONTRACT_NAME} on ${NETWORK} network!`)
}

main()
.then(() => {
console.log('Done!')
})
.catch(error => {
console.error(error)
process.exitCode = 1
})
157 changes: 157 additions & 0 deletions scripts/04-create2-deploy.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
import hre, { ethers, network, upgrades } from 'hardhat'
import { AwsKmsSigner } from '@govtechsg/ethers-aws-kms-signer'
import { confirmOrDie, print, colors } from '../utils/misc'
import addresses, { Network } from '../addresses'
import { THREE_MONTHS } from '../utils/constants'
import config from '../addresses'
import { keccak256 } from 'ethers/lib/utils'
import TransparentUpgradeableProxy from '@openzeppelin/upgrades-core/artifacts/@openzeppelin/contracts/proxy/transparent/TransparentUpgradeableProxy.sol/TransparentUpgradeableProxy.json'
import { updateJsonFile } from '../utils/json'

const kmsCredentials = {
accessKeyId: process.env.AWS_ACCESS_KEY_ID || 'AKIAxxxxxxxxxxxxxxxx', // credentials for your IAM user with KMS access
secretAccessKey: process.env.AWS_ACCESS_KEY_SECRET || 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', // credentials for your IAM user with KMS access
region: 'us-east-1', // region of your KMS key
keyId: process.env.AWS_KMS_KEY_ID || 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx', // KMS key id
}

const NETWORK = network.name as Network
const { Multisig, RolesRegistry } = addresses[NETWORK]

const CONTRACT_NAME = 'OriumMarketplace'
const OPERATOR_ADDRESS = Multisig.address
const MAX_DEADLINE = THREE_MONTHS.toString()
const INITIALIZER_ARGUMENTS: string[] = [OPERATOR_ADDRESS, RolesRegistry.address, MAX_DEADLINE]

const networkConfig: any = network.config
const provider = new ethers.providers.JsonRpcProvider(networkConfig.url || '')
const deployer = new AwsKmsSigner(kmsCredentials).connect(provider)

async function main() {
const deployerAddress = await deployer.getAddress()
await confirmOrDie(
`Are you sure you want to deploy ${CONTRACT_NAME} to ${NETWORK} using ${deployerAddress} for deployer and ${OPERATOR_ADDRESS} as operator?`,
)

print(colors.highlight, `Deploying ProxyAdmin to ${NETWORK}...`)
// Deploy ProxyAdmin and update network files, if there is a ProxyAdmin already deployed it will use it
const proxyAdminAddress = await upgrades.deployProxyAdmin(deployer)
print(colors.success, `ProxyAdmin deployed to ${NETWORK} at ${proxyAdminAddress}`)

print(colors.highlight, `Deploying implementation to ${NETWORK}...`)
const ImplementationFactory = await ethers.getContractFactory(CONTRACT_NAME, deployer)
// We are not using upgrades.deployImplementation here because it will only update the storage layout of the implementation
// And it will clash when we try to forceImport the proxy network files
// This way we can have both (implementation and proxy) imported in the network files
const implementation = await ImplementationFactory.deploy()
await implementation.deployed()
print(colors.success, `Implementation deployed to ${NETWORK} at ${implementation.address}`)

print(colors.highlight, `Deploying proxy to ${NETWORK} with CREATE2...`)

const create2Factory = await ethers.getContractAt(
'IImmutableOwnerCreate2Factory',
config[NETWORK].ImmutableOwnerCreate2Factory.address,
deployer,
)

// encoding the implementation initialize function call with the arguments
const implementationInitData = ImplementationFactory.interface.encodeFunctionData('initialize', INITIALIZER_ARGUMENTS)
// encoding the TransparentUpgradeableProxy constructor call with the implementation address, proxy admin address and implementation initialize function call
// this is the bytecode that will be deployed with CREATE2
const bytecode = ethers.utils.concat([
TransparentUpgradeableProxy.bytecode,
ethers.utils.defaultAbiCoder.encode(
['address', 'address', 'bytes'],
// Here we are passing the implementation address, proxy admin address that will be set for TransparentUpgradeableProxy
// and initialize data that will be called on implementation with delegatecall (implementationInitData)
[implementation.address, proxyAdminAddress, implementationInitData],
),
])
const salt = '0x00000000000000000000000000000000000000008b99e5a778edb02572010000'

// computing the proxy address that will be deployed with CREATE2
const proxyContractAddress = await create2Factory.computeAddress(salt, keccak256(bytecode))
print(colors.highlight, `Proxy will be deployed to ${proxyContractAddress}, deploying...`)

// deploying the proxy with CREATE2, this will deploy the bytecode we computed above
const tx = await create2Factory.deploy(salt, bytecode)
print(colors.highlight, `Waiting for transaction to be mined..., tx: ${tx.hash}`)
await tx.wait()
print(colors.success, `Proxy deployed to ${proxyContractAddress}`)

// We need this to import to the network files the implementation WITH proxy address
try {
print(colors.highlight, `Fetching proxy network files...`)
await upgrades.forceImport(proxyContractAddress, ImplementationFactory, { kind: 'transparent' })
print(colors.success, `Proxy network files fetched`)
} catch (e) {
print(colors.error, `Proxy network files not found`)
console.error(e)
}

// it will fail to verify proxy because transaction hash is different than the one used to verify
// but it will still verify the implementation and link the proxy to the implementation
try {
print(colors.highlight, `Verifying proxy...`)
await hre.run('verify:verify', {
address: proxyContractAddress,
constructorArguments: [],
})
print(colors.success, `Proxy verified!`)
} catch (e) {
/* print(colors.error, `Proxy not verified`)
console.error(e) */
}

// THE FOLLOWING CODE IS THE SAME AS THE 01-deploy.ts (previous deploy script)

print(colors.highlight, 'Updating config files...')
const deploymentInfo = {
[CONTRACT_NAME]: {
address: proxyContractAddress,
operator: OPERATOR_ADDRESS,
implementation: implementation.address,
proxyAdmin: proxyAdminAddress,
},
}

console.log(deploymentInfo)

updateJsonFile(`addresses/${NETWORK}/index.json`, deploymentInfo)

print(colors.success, 'Config files updated!')

try {
print(colors.highlight, 'Transferring proxy admin ownership...')
const abi = [
{
inputs: [
{
internalType: 'address',
name: 'newOwner',
type: 'address',
},
],
name: 'transferOwnership',
outputs: [],
stateMutability: 'nonpayable',
type: 'function',
},
]
const proxyAdminContract = new ethers.Contract(deploymentInfo[CONTRACT_NAME].proxyAdmin, abi, deployer)
await proxyAdminContract.transferOwnership(OPERATOR_ADDRESS)
print(colors.success, `Proxy admin ownership transferred to: ${OPERATOR_ADDRESS}`)
} catch (e) {
print(colors.error, `Error transferring proxy admin ownership: ${e}`)
}
}

main()
.then(async () => {
print(colors.bigSuccess, 'All done!')
})
.catch(error => {
console.error(error)
process.exitCode = 1
})
11 changes: 11 additions & 0 deletions test/OriumMarketplace.test.ts
Original file line number Diff line number Diff line change
@@ -737,6 +737,17 @@ describe('OriumMarketplace', () => {
).to.be.revertedWith('Ownable: caller is not the owner')
})
})

describe('Default Roles Registry', async () => {
it('Should set the default roles registry for a collection', async () => {
await expect(marketplace.connect(operator).setDefaultRolesRegistry(rolesRegistry.address)).to.not.be.reverted
})
it('Should NOT set the default roles registry if caller is not the operator', async () => {
await expect(
marketplace.connect(notOperator).setDefaultRolesRegistry(rolesRegistry.address),
).to.be.revertedWith('Ownable: caller is not the owner')
})
})
})
})
})