Skip to content

Commit

Permalink
Merge pull request #38 from mountainprotocol/chore/oz-platform
Browse files Browse the repository at this point in the history
Chore/oz defender platform
  • Loading branch information
mattiascaricato committed Aug 26, 2023
2 parents 920d520 + 24294ae commit 2f0e2f0
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 43 deletions.
20 changes: 20 additions & 0 deletions .editorconfig
@@ -0,0 +1,20 @@
# EditorConfig is awesome: https://EditorConfig.org

# top-most EditorConfig file
root = true

# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
charset = utf-8
indent_style = space
trim_trailing_whitespace = false

# 4 space indentation
[*.{sol}]
indent_size = 4

# Indentation override for all js, ts and json files
[*.{js,ts,json}]
indent_size = 2
2 changes: 1 addition & 1 deletion README.md
@@ -1,4 +1,4 @@
# Mountain Protocol USD
# Mountain Protocol USD [![codecov](https://codecov.io/github/mountainprotocol/tokens/branch/main/graph/badge.svg?token=YXQ2CTHYNA)](https://codecov.io/github/mountainprotocol/tokens)

This smart contract implements a custom rebasing ERC-20 token with additional features like pausing, block/unblock, access control, and upgradability. The contract aims to reflect the T-Bills APY into the token value through a reward multiplier mechanism. Users receive a proportional number of shares when they deposit tokens, and the number of tokens they can withdraw is calculated based on the current reward multiplier. The addRewardMultiplier function is called once a day to adjust the reward multiplier, ensuring accurate reflection of the yield from 3 months maturity T-Bills.

Expand Down
11 changes: 7 additions & 4 deletions contracts/USDM.sol
Expand Up @@ -10,6 +10,10 @@ import {AccessControlUpgradeable} from "@openzeppelin/contracts-upgradeable/acce
import {IERC20MetadataUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20MetadataUpgradeable.sol";
import {IERC20PermitUpgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/extensions/IERC20PermitUpgradeable.sol";

/**
* @title Mountain Protocol USD Contract
* @custom:security-contact security@mountainprotocol.com
*/
contract USDM is
IERC20MetadataUpgradeable,
AccessControlUpgradeable,
Expand Down Expand Up @@ -86,9 +90,9 @@ contract USDM is
* @notice Initializes the contract.
* @param name_ The name of the token.
* @param symbol_ The symbol of the token.
* @param initialSupply The initial amount of tokens for the contract creator.
* @param owner Owner address.
*/
function initialize(string memory name_, string memory symbol_, uint256 initialSupply) external initializer {
function initialize(string memory name_, string memory symbol_, address owner) external initializer {
_name = name_;
_symbol = symbol_;
_setRewardMultiplier(_BASE);
Expand All @@ -98,8 +102,7 @@ contract USDM is
__UUPSUpgradeable_init();
__EIP712_init(name_, "1");

_grantRole(DEFAULT_ADMIN_ROLE, _msgSender());
_mint(_msgSender(), initialSupply);
_grantRole(DEFAULT_ADMIN_ROLE, owner);
}

/// @custom:oz-upgrades-unsafe-allow constructor
Expand Down
31 changes: 22 additions & 9 deletions hardhat.config.ts
Expand Up @@ -11,10 +11,11 @@ const {
REPORT_GAS,
ETHERSCAN_API_KEY,
COIN_MARKETCAP_API_KEY,
GOERLI_PRIVATE_KEY,
ALCHEMY_MAINNET_API_KEY,
ALCHEMY_GOERLI_API_KEY,
// MAINNET_PRIVATE_KEY,
// ALCHEMY_MAINNET_API_KEY,
ALCHEMY_SEPOLIA_API_KEY,
OZ_PLATFORM_KEY,
OZ_PLATFORM_SECRET,
} = process.env;

const isTestEnv = NODE_ENV === 'test';
Expand Down Expand Up @@ -47,19 +48,31 @@ const config: HardhatUserConfig = {
},
},
},
platform: {
apiKey: OZ_PLATFORM_KEY as string,
apiSecret: OZ_PLATFORM_SECRET as string,
},
etherscan: ETHERSCAN_API_KEY ? etherscanConfig : {},
defaultNetwork: 'goerli',
networks: {
goerli: {
url: `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_GOERLI_API_KEY}`,
chainId: 5,
// Only add account if the PK is provided
// ...(GOERLI_PRIVATE_KEY ? { accounts: [GOERLI_PRIVATE_KEY] } : {}),
},
sepolia: {
url: `https://eth-goerli.alchemyapi.io/v2/${ALCHEMY_SEPOLIA_API_KEY}`,
chainId: 11155111,
// Only add account if the PK is provided
// ...(SEPOLIA_PRIVATE_KEY ? { accounts: [SEPOLIA_PRIVATE_KEY] } : {}),
},
mainnet: {
url: `https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_MAINNET_API_KEY}`,
chainId: 1,
// Only add account if the PK is provided
...(GOERLI_PRIVATE_KEY ? { accounts: [GOERLI_PRIVATE_KEY] } : {}),
// ...(MAINNET_PRIVATE_KEY ? { accounts: [MAINNET_PRIVATE_KEY] } : {}),
},
// mainnet: {
// url: `https://eth-mainnet.g.alchemy.com/v2/${ALCHEMY_MAINNET_API_KEY}`,
// Only add account if the PK is provided
// ...(MAINNET_PRIVATE_KEY ? { accounts: [MAINNET_PRIVATE_KEY] } : {}),
// },
hardhat: {
chainId: 1337, // We set 1337 to make interacting with MetaMask simpler
},
Expand Down
77 changes: 69 additions & 8 deletions package-lock.json

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

29 changes: 15 additions & 14 deletions scripts/deploy.ts
@@ -1,30 +1,31 @@
import { ethers, upgrades } from 'hardhat';
import { ethers, platform } from 'hardhat';
import dotenv from 'dotenv';

const deploy = async () => {
const [deployer] = await ethers.getSigners();

console.log('Deployer: %s', await deployer.getAddress());
dotenv.config();

console.log('Account balance: %s', ethers.utils.formatEther(await deployer.getBalance()));
const { OWNER_ADDRESS } = process.env;

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const deploy = async () => {
const USDM = await ethers.getContractFactory('USDM');
const usdm = await upgrades.deployProxy(USDM, ['Mountain Protocol USD', 'USDM', ethers.utils.parseUnits('1')], {
const contract = await platform.deployProxy(USDM, ['Mountain Protocol USD', 'USDM', OWNER_ADDRESS], {
initializer: 'initialize',
kind: 'uups',
salt: '1337',
});
await usdm.deployed();
await contract.deployed();

console.log('Contract address: %s', usdm.address);
console.log('Contract address: %s', contract.address);
};

// eslint-disable-next-line @typescript-eslint/no-unused-vars
const upgrade = async () => {
const PROXY_ADDRESS = '0xe31Cf614fC1C5d3781d9E09bdb2e04134CDebb89';
const newContract = await ethers.getContractFactory('USDM');
const PROXY_ADDRESS = '';
const newUSDM = await ethers.getContractFactory('USDM');
console.log('Upgrading contract... %s', PROXY_ADDRESS);
// It's no necessary to specify proxy's kind since it's inferred from the proxy address
await upgrades.upgradeProxy(PROXY_ADDRESS, newContract);
console.log('Contract upgraded');
const proposal = await platform.proposeUpgrade(PROXY_ADDRESS, newUSDM);

console.log(`Upgrade proposal URL: ${proposal.url}`);
};

// We recommend this pattern to be able to use async/await everywhere
Expand Down
20 changes: 13 additions & 7 deletions test/USDM.ts
Expand Up @@ -31,7 +31,13 @@ describe('USDM', () => {
const [owner, acc1, acc2] = await ethers.getSigners();

const USDM = await ethers.getContractFactory('USDM');
const contract = await upgrades.deployProxy(USDM, [name, symbol, totalShares], { initializer: 'initialize' });
const contract = await upgrades.deployProxy(USDM, [name, symbol, owner.address], {
initializer: 'initialize',
});

await contract.grantRole(roles.MINTER, owner.address);
await contract.mint(owner.address, totalShares);
await contract.revokeRole(roles.MINTER, owner.address);

return { contract, owner, acc1, acc2 };
};
Expand All @@ -55,7 +61,7 @@ describe('USDM', () => {
expect(await contract.decimals()).to.be.equal(18);
});

it('grants admin role to deployer', async () => {
it('grants admin role to the address passed to the initializer', async () => {
const { contract, owner } = await loadFixture(deployUSDMFixture);

expect(await contract.hasRole(await contract.DEFAULT_ADMIN_ROLE(), owner.address)).to.equal(true);
Expand All @@ -74,28 +80,28 @@ describe('USDM', () => {
expect(await contract.totalSupply()).to.equal(totalShares);
});

it('assigns the initial total shares to deployer', async () => {
it('assigns the total shares to the owner', async () => {
const { contract, owner } = await loadFixture(deployUSDMFixture);

expect(await contract.sharesOf(owner.address)).to.equal(totalShares);
});

it('assigns the initial balance to the deployer', async () => {
it('assigns the balance to the the owner', async () => {
const { contract, owner } = await loadFixture(deployUSDMFixture);

expect(await contract.balanceOf(owner.address)).to.equal(totalShares);
});

it('sets initial reward multiplier to 100%', async () => {
it('sets reward multiplier to 100%', async () => {
const { contract } = await loadFixture(deployUSDMFixture);

expect(await contract.rewardMultiplier()).to.equal(parseUnits('1')); // 1 equals to 100%
});

it('fails if initialize is called again after initialization', async () => {
const { contract } = await loadFixture(deployUSDMFixture);
const { contract, owner } = await loadFixture(deployUSDMFixture);

await expect(contract.initialize(name, symbol, totalShares)).to.be.revertedWith(
await expect(contract.initialize(name, symbol, owner.address)).to.be.revertedWith(
'Initializable: contract is already initialized',
);
});
Expand Down

0 comments on commit 2f0e2f0

Please sign in to comment.