hardhat#upgrades TypeScript Examples

The following examples show how to use hardhat#upgrades. You can vote up the ones you like or vote down the ones you don't like, and go to the original project or source file by following the links above each example. You may check out the related API usage on the sidebar.
Example #1
Source File: OzContractDeployer.ts    From perpetual-protocol with GNU General Public License v3.0 6 votes vote down vote up
async deploy(contractFullyQualifiedName: string, args: any[]): Promise<string> {
        // deploy contract by open zeppelin upgrade plugin
        const contract = await ethers.getContractFactory(contractFullyQualifiedName)
        const proxyInstance = await upgrades.deployProxy(contract, args, {
            initializer: this.ozInitMethodName,
        })

        await this.syncNonce(proxyInstance.deployTransaction.hash)

        const impAddr = await getImplementation(proxyInstance.address)
        console.log(
            `deploy: contractFullyQualifiedName=${contractFullyQualifiedName}, proxy=${proxyInstance.address}, implementation=${impAddr}`,
        )
        await initImplementation(impAddr, contractFullyQualifiedName, this.confirmations, args)
        return proxyInstance.address
    }
Example #2
Source File: OzContractDeployer.ts    From perpetual-protocol with GNU General Public License v3.0 6 votes vote down vote up
// different from deploy() and upgrade(), this function returns the "implementation" address
    async prepareUpgrade(proxy: string, contractFullyQualifiedName: string, args: any[]): Promise<string> {
        const factory = await ethers.getContractFactory(contractFullyQualifiedName)
        const impAddr = await upgrades.prepareUpgrade(proxy, factory, { unsafeAllowCustomTypes: false })
        console.log(
            `prepareUpgrade: contractFullyQualifiedName=${contractFullyQualifiedName}, proxy=${proxy}, implementation=${impAddr}`,
        )

        await initImplementation(impAddr, contractFullyQualifiedName, this.confirmations, args)
        // proxyInstance.deployTransaction only exists in deployProxy() and does not exist in prepareUpgrade()
        return impAddr
    }
Example #3
Source File: OzContractDeployer.ts    From perpetual-protocol with GNU General Public License v3.0 6 votes vote down vote up
// only admin
    async upgrade(proxy: string, contractFullyQualifiedName: string, args: any[]): Promise<void> {
        const contract = await ethers.getContractFactory(contractFullyQualifiedName)
        const proxyInstance = await upgrades.upgradeProxy(proxy, contract)
        const impAddr = await getImplementation(proxyInstance.address)
        console.log(
            `upgrade: contractFullyQualifiedName=${contractFullyQualifiedName}, proxy=${proxy}, implementation=${impAddr}`,
        )

        await initImplementation(impAddr, contractFullyQualifiedName, this.confirmations, args)
        // proxyInstance.deployTransaction only exists in deployProxy() and does not exist in upgradeProxy()
        // await this.syncNonce(proxyInstance.deployTransaction.hash)
    }
Example #4
Source File: OzContractDeployer.ts    From perpetual-protocol with GNU General Public License v3.0 5 votes vote down vote up
static async transferProxyAdminOwnership(newAdmin: string): Promise<void> {
        // TODO this is a hack due to @openzeppelin/hardhat-upgrades doesn't expose "admin" in type-extensions.d.ts
        await (upgrades as any).admin.transferProxyAdminOwnership(newAdmin)
    }
Example #5
Source File: OzContractDeployer.ts    From perpetual-protocol with GNU General Public License v3.0 5 votes vote down vote up
async prepareUpgradeLegacy(proxy: string, contractFullyQualifiedName: string): Promise<string> {
        const factory = await ethers.getContractFactory(contractFullyQualifiedName)
        const impAddr = await upgrades.prepareUpgrade(proxy, factory, { unsafeAllowCustomTypes: false })
        console.log(
            `prepareUpgradeLegacy proxy=${proxy}, contractFullyQualifiedName=${contractFullyQualifiedName}, address=${impAddr}`,
        )
        return impAddr
    }
Example #6
Source File: DeployUtil.ts    From perpetual-protocol with GNU General Public License v3.0 5 votes vote down vote up
export async function getImplementation(proxyAddr: string) {
    const proxyAdmin = await upgrades.admin.getInstance()
    // const proxyAdminAbi = ["function getProxyImplementation(address proxy) view returns (address)"]
    // const proxyAdminAddr = (await upgrades.admin.getInstance()).address
    // const proxyAdmin = await ethers.getContractAt(proxyAdminAbi, proxyAdminAddr)

    return proxyAdmin.getProxyImplementation(proxyAddr)
}
Example #7
Source File: deploy-reward-vesting.ts    From perpetual-protocol with GNU General Public License v3.0 5 votes vote down vote up
async function main() {
    const perpAddress = "0xaFfB148304D38947193785D194972a7d0d9b7F68"
    const Vesting = await ethers.getContractFactory(ContractFullyQualifiedName.PerpRewardVesting)
    const instance = await upgrades.deployProxy(Vesting, [perpAddress])
    await instance.deployed()
}
Example #8
Source File: auction.test.ts    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
describe('NounsAuctionHouse', () => {
  let nounsAuctionHouse: NounsAuctionHouse;
  let nounsToken: NounsToken;
  let weth: WETH;
  let deployer: SignerWithAddress;
  let noundersDAO: SignerWithAddress;
  let bidderA: SignerWithAddress;
  let bidderB: SignerWithAddress;
  let snapshotId: number;

  const TIME_BUFFER = 15 * 60;
  const RESERVE_PRICE = 2;
  const MIN_INCREMENT_BID_PERCENTAGE = 5;
  const DURATION = 60 * 60 * 24;

  async function deploy(deployer?: SignerWithAddress) {
    const auctionHouseFactory = await ethers.getContractFactory('NounsAuctionHouse', deployer);
    return upgrades.deployProxy(auctionHouseFactory, [
      nounsToken.address,
      weth.address,
      TIME_BUFFER,
      RESERVE_PRICE,
      MIN_INCREMENT_BID_PERCENTAGE,
      DURATION,
    ]) as Promise<NounsAuctionHouse>;
  }

  before(async () => {
    [deployer, noundersDAO, bidderA, bidderB] = await ethers.getSigners();

    nounsToken = await deployNounsToken(deployer, noundersDAO.address, deployer.address);
    weth = await deployWeth(deployer);
    nounsAuctionHouse = await deploy(deployer);

    const descriptor = await nounsToken.descriptor();

    await populateDescriptor(NounsDescriptorFactory.connect(descriptor, deployer));

    await nounsToken.setMinter(nounsAuctionHouse.address);
  });

  beforeEach(async () => {
    snapshotId = await ethers.provider.send('evm_snapshot', []);
  });

  afterEach(async () => {
    await ethers.provider.send('evm_revert', [snapshotId]);
  });

  it('should revert if a second initialization is attempted', async () => {
    const tx = nounsAuctionHouse.initialize(
      nounsToken.address,
      weth.address,
      TIME_BUFFER,
      RESERVE_PRICE,
      MIN_INCREMENT_BID_PERCENTAGE,
      DURATION,
    );
    await expect(tx).to.be.revertedWith('Initializable: contract is already initialized');
  });

  it('should allow the noundersDAO to unpause the contract and create the first auction', async () => {
    const tx = await nounsAuctionHouse.unpause();
    await tx.wait();

    const auction = await nounsAuctionHouse.auction();
    expect(auction.startTime.toNumber()).to.be.greaterThan(0);
  });

  it('should revert if a user creates a bid for an inactive auction', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();
    const tx = nounsAuctionHouse.connect(bidderA).createBid(nounId.add(1), {
      value: RESERVE_PRICE,
    });

    await expect(tx).to.be.revertedWith('Noun not up for auction');
  });

  it('should revert if a user creates a bid for an expired auction', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    await ethers.provider.send('evm_increaseTime', [60 * 60 * 25]); // Add 25 hours

    const { nounId } = await nounsAuctionHouse.auction();
    const tx = nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE,
    });

    await expect(tx).to.be.revertedWith('Auction expired');
  });

  it('should revert if a user creates a bid with an amount below the reserve price', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();
    const tx = nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE - 1,
    });

    await expect(tx).to.be.revertedWith('Must send at least reservePrice');
  });

  it('should revert if a user creates a bid less than the min bid increment percentage', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();
    await nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE * 50,
    });
    const tx = nounsAuctionHouse.connect(bidderB).createBid(nounId, {
      value: RESERVE_PRICE * 51,
    });

    await expect(tx).to.be.revertedWith(
      'Must send more than last bid by minBidIncrementPercentage amount',
    );
  });

  it('should refund the previous bidder when the following user creates a bid', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();
    await nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE,
    });

    const bidderAPostBidBalance = await bidderA.getBalance();
    await nounsAuctionHouse.connect(bidderB).createBid(nounId, {
      value: RESERVE_PRICE * 2,
    });
    const bidderAPostRefundBalance = await bidderA.getBalance();

    expect(bidderAPostRefundBalance).to.equal(bidderAPostBidBalance.add(RESERVE_PRICE));
  });

  it('should cap the maximum bid griefing cost at 30K gas + the cost to wrap and transfer WETH', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();

    const maliciousBidderFactory = new MaliciousBidderFactory(bidderA);
    const maliciousBidder = await maliciousBidderFactory.deploy();

    const maliciousBid = await maliciousBidder
      .connect(bidderA)
      .bid(nounsAuctionHouse.address, nounId, {
        value: RESERVE_PRICE,
      });
    await maliciousBid.wait();

    const tx = await nounsAuctionHouse.connect(bidderB).createBid(nounId, {
      value: RESERVE_PRICE * 2,
      gasLimit: 1_000_000,
    });
    const result = await tx.wait();

    expect(result.gasUsed.toNumber()).to.be.lessThan(200_000);
    expect(await weth.balanceOf(maliciousBidder.address)).to.equal(RESERVE_PRICE);
  });

  it('should emit an `AuctionBid` event on a successful bid', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();
    const tx = nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE,
    });

    await expect(tx)
      .to.emit(nounsAuctionHouse, 'AuctionBid')
      .withArgs(nounId, bidderA.address, RESERVE_PRICE, false);
  });

  it('should emit an `AuctionExtended` event if the auction end time is within the time buffer', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId, endTime } = await nounsAuctionHouse.auction();

    await ethers.provider.send('evm_setNextBlockTimestamp', [endTime.sub(60 * 5).toNumber()]); // Subtract 5 mins from current end time

    const tx = nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE,
    });

    await expect(tx)
      .to.emit(nounsAuctionHouse, 'AuctionExtended')
      .withArgs(nounId, endTime.add(60 * 10));
  });

  it('should revert if auction settlement is attempted while the auction is still active', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();

    await nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE,
    });
    const tx = nounsAuctionHouse.connect(bidderA).settleCurrentAndCreateNewAuction();

    await expect(tx).to.be.revertedWith("Auction hasn't completed");
  });

  it('should emit `AuctionSettled` and `AuctionCreated` events if all conditions are met', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();

    await nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE,
    });

    await ethers.provider.send('evm_increaseTime', [60 * 60 * 25]); // Add 25 hours
    const tx = await nounsAuctionHouse.connect(bidderA).settleCurrentAndCreateNewAuction();

    const receipt = await tx.wait();
    const { timestamp } = await ethers.provider.getBlock(receipt.blockHash);

    const settledEvent = receipt.events?.find(e => e.event === 'AuctionSettled');
    const createdEvent = receipt.events?.find(e => e.event === 'AuctionCreated');

    expect(settledEvent?.args?.nounId).to.equal(nounId);
    expect(settledEvent?.args?.winner).to.equal(bidderA.address);
    expect(settledEvent?.args?.amount).to.equal(RESERVE_PRICE);

    expect(createdEvent?.args?.nounId).to.equal(nounId.add(1));
    expect(createdEvent?.args?.startTime).to.equal(timestamp);
    expect(createdEvent?.args?.endTime).to.equal(timestamp + DURATION);
  });

  it('should not create a new auction if the auction house is paused and unpaused while an auction is ongoing', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    await (await nounsAuctionHouse.pause()).wait();

    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();

    expect(nounId).to.equal(1);
  });

  it('should create a new auction if the auction house is paused and unpaused after an auction is settled', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();

    await nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE,
    });

    await ethers.provider.send('evm_increaseTime', [60 * 60 * 25]); // Add 25 hours

    await (await nounsAuctionHouse.pause()).wait();

    const settleTx = nounsAuctionHouse.connect(bidderA).settleAuction();

    await expect(settleTx)
      .to.emit(nounsAuctionHouse, 'AuctionSettled')
      .withArgs(nounId, bidderA.address, RESERVE_PRICE);

    const unpauseTx = await nounsAuctionHouse.unpause();
    const receipt = await unpauseTx.wait();
    const { timestamp } = await ethers.provider.getBlock(receipt.blockHash);

    const createdEvent = receipt.events?.find(e => e.event === 'AuctionCreated');

    expect(createdEvent?.args?.nounId).to.equal(nounId.add(1));
    expect(createdEvent?.args?.startTime).to.equal(timestamp);
    expect(createdEvent?.args?.endTime).to.equal(timestamp + DURATION);
  });

  it('should settle the current auction and pause the contract if the minter is updated while the auction house is unpaused', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();

    await nounsAuctionHouse.connect(bidderA).createBid(nounId, {
      value: RESERVE_PRICE,
    });

    await nounsToken.setMinter(constants.AddressZero);

    await ethers.provider.send('evm_increaseTime', [60 * 60 * 25]); // Add 25 hours

    const settleTx = nounsAuctionHouse.connect(bidderA).settleCurrentAndCreateNewAuction();

    await expect(settleTx)
      .to.emit(nounsAuctionHouse, 'AuctionSettled')
      .withArgs(nounId, bidderA.address, RESERVE_PRICE);

    const paused = await nounsAuctionHouse.paused();

    expect(paused).to.equal(true);
  });

  it('should burn a Noun on auction settlement if no bids are received', async () => {
    await (await nounsAuctionHouse.unpause()).wait();

    const { nounId } = await nounsAuctionHouse.auction();

    await ethers.provider.send('evm_increaseTime', [60 * 60 * 25]); // Add 25 hours

    const tx = nounsAuctionHouse.connect(bidderA).settleCurrentAndCreateNewAuction();

    await expect(tx)
      .to.emit(nounsAuctionHouse, 'AuctionSettled')
      .withArgs(nounId, '0x0000000000000000000000000000000000000000', 0);
  });
});
Example #9
Source File: end2end.test.ts    From nouns-monorepo with GNU General Public License v3.0 4 votes vote down vote up
async function deploy() {
  [deployer, bidderA, wethDeployer, noundersDAO] = await ethers.getSigners();

  // Deployed by another account to simulate real network

  weth = await deployWeth(wethDeployer);

  // nonce 2: Deploy AuctionHouse
  // nonce 3: Deploy nftDescriptorLibraryFactory
  // nonce 4: Deploy NounsDescriptor
  // nonce 5: Deploy NounsSeeder
  // nonce 6: Deploy NounsToken
  // nonce 0: Deploy NounsDAOExecutor
  // nonce 1: Deploy NounsDAOLogicV1
  // nonce 7: Deploy NounsDAOProxy
  // nonce ++: populate Descriptor
  // nonce ++: set ownable contracts owner to timelock

  // 1. DEPLOY Nouns token
  nounsToken = await deployNounsToken(
    deployer,
    noundersDAO.address,
    deployer.address, // do not know minter/auction house yet
  );

  // 2a. DEPLOY AuctionHouse
  const auctionHouseFactory = await ethers.getContractFactory('NounsAuctionHouse', deployer);
  const nounsAuctionHouseProxy = await upgrades.deployProxy(auctionHouseFactory, [
    nounsToken.address,
    weth.address,
    TIME_BUFFER,
    RESERVE_PRICE,
    MIN_INCREMENT_BID_PERCENTAGE,
    DURATION,
  ]);

  // 2b. CAST proxy as AuctionHouse
  nounsAuctionHouse = NounsAuctionHouseFactory.connect(nounsAuctionHouseProxy.address, deployer);

  // 3. SET MINTER
  await nounsToken.setMinter(nounsAuctionHouse.address);

  // 4. POPULATE body parts
  descriptor = NounsDescriptorFactory.connect(await nounsToken.descriptor(), deployer);

  await populateDescriptor(descriptor);

  // 5a. CALCULATE Gov Delegate, takes place after 2 transactions
  const calculatedGovDelegatorAddress = ethers.utils.getContractAddress({
    from: deployer.address,
    nonce: (await deployer.getTransactionCount()) + 2,
  });

  // 5b. DEPLOY NounsDAOExecutor with pre-computed Delegator address
  timelock = await new NounsDaoExecutorFactory(deployer).deploy(
    calculatedGovDelegatorAddress,
    TIME_LOCK_DELAY,
  );

  // 6. DEPLOY Delegate
  const govDelegate = await new NounsDaoLogicV1Factory(deployer).deploy();

  // 7a. DEPLOY Delegator
  const nounsDAOProxy = await new NounsDaoProxyFactory(deployer).deploy(
    timelock.address,
    nounsToken.address,
    noundersDAO.address, // NoundersDAO is vetoer
    timelock.address,
    govDelegate.address,
    VOTING_PERIOD,
    VOTING_DELAY,
    PROPOSAL_THRESHOLD_BPS,
    QUORUM_VOTES_BPS,
  );

  expect(calculatedGovDelegatorAddress).to.equal(nounsDAOProxy.address);

  // 7b. CAST Delegator as Delegate
  gov = NounsDaoLogicV1Factory.connect(nounsDAOProxy.address, deployer);

  // 8. SET Nouns owner to NounsDAOExecutor
  await nounsToken.transferOwnership(timelock.address);
  // 9. SET Descriptor owner to NounsDAOExecutor
  await descriptor.transferOwnership(timelock.address);

  // 10. UNPAUSE auction and kick off first mint
  await nounsAuctionHouse.unpause();

  // 11. SET Auction House owner to NounsDAOExecutor
  await nounsAuctionHouse.transferOwnership(timelock.address);
}
Example #10
Source File: 0001-layer2-clearingHouse.ts    From perpetual-protocol with GNU General Public License v3.0 4 votes vote down vote up
migration: MigrationDefinition = {
    configPath: "hardhat.flatten.clearinghouse.config.ts",

    // deploy the flattened clearingHouse and init it just in case
    getTasks: (context: MigrationContext) => {
        let arbitrageur: string
        let initMarginRatio: string
        let oldImpAddr: string
        let insuranceFund: string
        let BTC: string
        let arbitrageurBTCPositionSize: string
        let openInterestNotional: string
        let newImplContractAddr: string
        return [
            async (): Promise<void> => {
                console.log("verifying state variables...")

                // have to first flatten contracts for creating instances
                const filename = `${ContractName.ClearingHouse}.sol`
                await flatten(SRC_DIR, hre.config.paths.sources, filename)
                // after flatten sol file we must re-compile again
                await hre.run(TASK_COMPILE)

                const clearingHouseContract = await context.factory
                    .create<ClearingHouse>(ContractFullyQualifiedName.FlattenClearingHouse)
                    .instance()

                oldImpAddr = await getImplementation(clearingHouseContract.address)
                initMarginRatio = (await clearingHouseContract.initMarginRatio()).toString()
                insuranceFund = await clearingHouseContract.insuranceFund()
                BTC = context.systemMetadataDao.getContractMetadata(context.layer, AmmInstanceName.BTCUSDC).address
                openInterestNotional = (await clearingHouseContract.openInterestNotionalMap(BTC)).toString()

                arbitrageur = context.externalContract.arbitrageur!
                arbitrageurBTCPositionSize = (
                    await clearingHouseContract.getUnadjustedPosition(BTC, arbitrageur)
                ).size.toString()
            },
            async (): Promise<void> => {
                console.log("prepare upgrading...")
                // deploy clearing house implementation
                const clearingHouseContract = await context.factory.create<ClearingHouse>(
                    ContractFullyQualifiedName.FlattenClearingHouse,
                )
                newImplContractAddr = await clearingHouseContract.prepareUpgradeContractLegacy()
            },
            async (): Promise<void> => {
                console.info("do upgrade")
                // create an impersonated signer
                const govAddr = context.externalContract.foundationGovernance
                await hre.network.provider.request({
                    method: "hardhat_impersonateAccount",
                    params: [govAddr],
                })
                const govSigner = ethers.provider.getSigner(govAddr)

                // prepare information for upgrading
                const contractName = ContractFullyQualifiedName.FlattenClearingHouse
                const proxyAddr = context.factory.create<ClearingHouse>(contractName).address!

                const proxyAdmin = await upgrades.admin.getInstance()
                await proxyAdmin.connect(govSigner).upgrade(proxyAddr, newImplContractAddr)

                console.log(
                    `upgrade: contractFullyQualifiedName=${contractName}, proxy=${proxyAddr}, implementation=${newImplContractAddr}`,
                )
            },
            async (): Promise<void> => {
                const clearingHouseContract = await context.factory
                    .create<ClearingHouse>(ContractFullyQualifiedName.FlattenClearingHouse)
                    .instance()

                // for comparing with the new implementation address
                console.log("old implementation address: ", oldImpAddr)
                console.log("new implementation address: ", await getImplementation(clearingHouseContract.address))

                const newInsuranceFund = await clearingHouseContract.insuranceFund()
                console.log("insuranceFund address (shouldn't be zero address): ", newInsuranceFund)
                expect(newInsuranceFund).to.eq(insuranceFund)
                console.log("insuranceFund verified!")

                expect((await clearingHouseContract.initMarginRatio()).toString()).to.eq(initMarginRatio)
                console.log("initMarginRatio verified!")
                expect((await clearingHouseContract.openInterestNotionalMap(BTC)).toString()).to.eq(
                    openInterestNotional,
                )
                console.log("openInterestNotional verified!")
                expect((await clearingHouseContract.getUnadjustedPosition(BTC, arbitrageur)).size.toString()).to.eq(
                    arbitrageurBTCPositionSize,
                )
                console.log("arbitrageurBTCPositionSize verified!")
            },
        ]
    },
}
Example #11
Source File: 0004-layer2-clearingHouse-amm-openPos.ts    From perpetual-protocol with GNU General Public License v3.0 4 votes vote down vote up
migration: MigrationDefinition = {
    configPath: "hardhat.flatten.clearinghouse.config.ts",

    // deploy the flattened clearingHouse and init it just in case
    getTasks: (context: MigrationContext) => {
        let arbitrageur: string
        let oldClearingHouseImpAddr: string
        let oldAmmImpAddr: string
        let newClearingHouseImplAddr: string
        let newAmmImplAddr: string
        let ammETHAddr: string
        let arbitrageurPosition: any
        let oldQuoteAssetReserve: BigNumber
        let quoteAssetAddr: string
        return [
            async (): Promise<void> => {
                console.log("get state variables for verification later...")

                // flat clearingHouse
                const fileClearingHouse = `${ContractName.ClearingHouse}.sol`
                await flatten(SRC_DIR, hre.config.paths.sources, fileClearingHouse)
                await hre.run(TASK_COMPILE)

                // flat Amm
                const fileAmm = `${ContractName.Amm}.sol`
                await flatten(SRC_DIR, hre.config.paths.sources, fileAmm)
                await hre.run(TASK_COMPILE)

                const clearingHouseContract = await context.factory
                    .create<ClearingHouse>(ContractFullyQualifiedName.FlattenClearingHouse)
                    .instance()
                const ammContract = await context.factory
                    .createAmm(AmmInstanceName.ETHUSDC, ContractFullyQualifiedName.FlattenAmmUnderClearingHouse)
                    .instance()

                oldClearingHouseImpAddr = await getImplementation(clearingHouseContract.address)
                oldAmmImpAddr = await getImplementation(ammContract.address)
                ammETHAddr = ammContract.address

                arbitrageur = context.externalContract.arbitrageur!
                arbitrageurPosition = await clearingHouseContract.getPosition(ammETHAddr, arbitrageur)
                oldQuoteAssetReserve = await ammContract.quoteAssetReserve()
                quoteAssetAddr = await ammContract.quoteAsset()
            },
            async (): Promise<void> => {
                console.log("prepare upgrading...")

                // deploy clearingHouse implementation
                const clearingHouseContract = await context.factory.create<ClearingHouse>(
                    ContractFullyQualifiedName.FlattenClearingHouse,
                )
                newClearingHouseImplAddr = await clearingHouseContract.prepareUpgradeContractLegacy()

                // deploy Amm implementation
                const ammContract = await context.factory.createAmm(
                    AmmInstanceName.ETHUSDC,
                    ContractFullyQualifiedName.FlattenAmmUnderClearingHouse,
                )
                newAmmImplAddr = await ammContract.prepareUpgradeContractLegacy()
            },
            async (): Promise<void> => {
                console.info("upgrading...")

                // create an impersonated signer
                const govSigner = await impersonateAccount(context.externalContract.foundationGovernance!)

                // prepare information for upgrading
                const contractNameClearingHouse = ContractFullyQualifiedName.FlattenClearingHouse
                const proxyClearingHouseAddr = context.factory.create<ClearingHouse>(contractNameClearingHouse).address!

                const proxyAdmin = await upgrades.admin.getInstance()
                await proxyAdmin.connect(govSigner).upgrade(proxyClearingHouseAddr, newClearingHouseImplAddr)
                console.log(
                    `upgrade: contractFullyQualifiedName=${contractNameClearingHouse}, proxy=${proxyClearingHouseAddr}, implementation=${newClearingHouseImplAddr}`,
                )

                await proxyAdmin.connect(govSigner).upgrade(ammETHAddr, newAmmImplAddr)
                console.log(
                    `upgrade: contractFullyQualifiedName=${contractNameClearingHouse}, proxy=${ammETHAddr}, implementation=${newAmmImplAddr}`,
                )
            },
            // verify can openPosition
            async (): Promise<void> => {
                const clearingHouseContract = await context.factory
                    .create<ClearingHouse>(ContractFullyQualifiedName.FlattenClearingHouse)
                    .instance()

                const gov = context.externalContract.foundationGovernance!
                const govSigner = await impersonateAccount(gov)
                const arbitrageurSigner = await impersonateAccount(arbitrageur)

                const usdcAddr = context.externalContract.usdc!
                const USDCArtifact = await artifacts.readArtifact(ContractFullyQualifiedName.FlattenIERC20)
                const usdcInstance = (await ethers.getContractAt(USDCArtifact.abi, usdcAddr)) as ERC20

                // send eth and usdc to gov account
                const txETH = await arbitrageurSigner.sendTransaction({
                    to: gov,
                    value: ethers.utils.parseEther("0.1"),
                })
                await txETH.wait()
                const txUSDC = await usdcInstance.connect(arbitrageurSigner).transfer(gov, parseUnits("1000", 6))
                await txUSDC.wait()
                const txApprove = await usdcInstance
                    .connect(govSigner)
                    .approve(clearingHouseContract.address, parseUnits("1000", 6))
                await txApprove.wait()

                // open position
                const receipt = await clearingHouseContract
                    .connect(govSigner)
                    .openPosition(
                        ammETHAddr,
                        Side.BUY,
                        { d: parseEther("600") },
                        { d: parseEther("1") },
                        { d: parseEther("0") },
                    )

                const ownerPosition = await clearingHouseContract.getPosition(ammETHAddr, gov)
                expect(ownerPosition.margin.d).to.eq(parseEther("600"))
                expect(ownerPosition.blockNumber).to.eq(receipt.blockNumber)
            },
            // verify arbitrageur's position on ETH market and Amm's reserve
            async (): Promise<void> => {
                const clearingHouseContract = await context.factory
                    .create<ClearingHouse>(ContractFullyQualifiedName.FlattenClearingHouse)
                    .instance()
                const ammContract = await context.factory
                    .createAmm(AmmInstanceName.ETHUSDC, ContractFullyQualifiedName.FlattenAmmUnderClearingHouse)
                    .instance()

                // for comparing with the new implementation address
                console.log("old implementation address of ClearingHouse: ", oldClearingHouseImpAddr)
                console.log(
                    "new implementation address of ClearingHouse: ",
                    await getImplementation(clearingHouseContract.address),
                )
                console.log("old implementation address of Amm: ", oldAmmImpAddr)
                console.log("new implementation address of Amm: ", await getImplementation(ammContract.address))

                console.log("arbitrageur position: ")
                const arbitrageurPositionNow = await clearingHouseContract.getPosition(ammETHAddr, arbitrageur)
                console.log("size: ", arbitrageurPositionNow.size.d.toString())
                console.log("margin: ", arbitrageurPositionNow.margin.d.toString())
                console.log("last updated blockNumber: ", arbitrageurPositionNow.blockNumber.toString())
                expect(arbitrageurPosition.size.d).to.eq(arbitrageurPositionNow.size.d)
                expect(arbitrageurPosition.margin.d).to.eq(arbitrageurPositionNow.margin.d)
                expect(arbitrageurPosition.blockNumber).to.eq(arbitrageurPositionNow.blockNumber)

                console.log("amm states: ")
                const newQuoteAssetReserve = await ammContract.quoteAssetReserve()
                console.log("quote asset reserve: ", oldQuoteAssetReserve.toString())
                console.log("USDC addr: ", quoteAssetAddr.toString())
                expect(newQuoteAssetReserve).to.eq(oldQuoteAssetReserve.add(parseEther("600")))
                expect(await ammContract.quoteAsset()).to.eq(quoteAssetAddr)
            },
        ]
    },
}
Example #12
Source File: OzContractDeployerSpec.ts    From perpetual-protocol with GNU General Public License v3.0 4 votes vote down vote up
// conflict with hardhat-gas-reporter without proxyResolver
describe("OzContractDeployer Spec", () => {
    const [wallet] = new MockProvider().getWallets()
    const ozContractDeployer: OzContractDeployer = new OzContractDeployer()
    const contractNameV1 = "src/mock/UpgradableContractV1.sol:UpgradableContractV1"
    const contractNameV2 = "src/mock/UpgradableContractV2.sol:UpgradableContractV2"
    // the following two are proxys
    let v1: UpgradableContractV1
    let v2: UpgradableContractV2
    let factoryV2: ContractFactory
    let proxyAddr: string

    async function getImplementation(proxyAddr: string) {
        const proxyAdmin = await upgrades.admin.getInstance()
        return proxyAdmin.getProxyImplementation(proxyAddr)
    }

    beforeEach(async () => {
        factoryV2 = await ethers.getContractFactory(contractNameV2)
        proxyAddr = await ozContractDeployer.deploy(contractNameV1, [])
        v1 = (await ethers.getContractAt(contractNameV1, proxyAddr)) as UpgradableContractV1
    })

    it("retrieve version that's initialized", async () => {
        expect((await v1.version()).toString()).eq("1")
    })

    it("doesn't have increaseVersion function", async () => {
        const wrongV2 = factoryV2.attach(proxyAddr) as UpgradableContractV2
        await expect(wrongV2.increaseVersion()).to.be.reverted
    })

    it("force error, initialization is included in ozContractDeployer.deploy()", async () => {
        const v1ImplAddr = await getImplementation(proxyAddr)
        const v1Impl = (await ethers.getContractAt(contractNameV1, v1ImplAddr)) as UpgradableContractV1
        await expectRevert(v1Impl.initialize(), "Contract instance has already been initialized")
    })

    describe("upgrade to v2", () => {
        beforeEach(async () => {
            await ozContractDeployer.upgrade(proxyAddr, contractNameV2, [])
            v2 = (await ethers.getContractAt(contractNameV2, proxyAddr)) as UpgradableContractV2
        })

        it("won't change the proxy address", async () => {
            expect(v2.address).eq(proxyAddr)
        })

        it("won't change state", async () => {
            expect((await v2.version()).toString()).eq("1")
        })

        it("has a new function", async () => {
            await v2.increaseVersion()
            expect((await v1.version()).toString()).eq("2")
        })

        it("force error, initialization is included in ozContractDeployer.upgrade()", async () => {
            const v2ImplAddr = await getImplementation(v2.address)
            const v2Impl = (await ethers.getContractAt(contractNameV2, v2ImplAddr)) as UpgradableContractV2
            await expectRevert(v2Impl.initialize(), "Contract instance has already been initialized")
        })
    })

    describe("prepareUpgrade to v2", () => {
        let v2ImplAddr: string

        beforeEach(async () => {
            v2ImplAddr = await ozContractDeployer.prepareUpgrade(proxyAddr, contractNameV2, [])
        })

        it("ozContractDeployer.prepareUpgrade() returns the implementation address; will be different from proxy address", async () => {
            expect(v2ImplAddr).not.eq(proxyAddr)
        })

        it("won't change state", async () => {
            expect((await v1.version()).toString()).eq("1")
        })

        it("proxy still has no new function", async () => {
            const wrongV2 = factoryV2.attach(proxyAddr) as UpgradableContractV2
            await expect(wrongV2.increaseVersion()).to.be.reverted
        })

        it("force error, initialization is included in ozContractDeployer.prepareUpgrade()", async () => {
            const v2Impl = (await ethers.getContractAt(contractNameV2, v2ImplAddr)) as UpgradableContractV2
            await expectRevert(v2Impl.initialize(), "Contract instance has already been initialized")
        })
    })

    describe("transferProxyAdminOwnership to others", () => {
        it("can't transfer to empty address", async () => {
            await expect(OzContractDeployer.transferProxyAdminOwnership("0x0000000000000000000000000000000000000000"))
                .to.be.reverted
        })

        it("can't transfer and upgrade once transfer admin to others, but can deploy new and prepareUpgrade", async () => {
            await OzContractDeployer.transferProxyAdminOwnership(wallet.address)
            await expect(OzContractDeployer.transferProxyAdminOwnership(wallet.address)).to.be.reverted
            await expect(ozContractDeployer.upgrade(proxyAddr, contractNameV2, [])).to.be.reverted
            await upgrades.prepareUpgrade(v1.address, factoryV2)
            const newProxy = await ozContractDeployer.deploy(contractNameV2, [])
            await expect(ozContractDeployer.upgrade(newProxy, contractNameV1, [])).to.be.reverted
        })

        // once transferProxyAdminOwnership has been called, every admin-only tx won't be able to test
    })
})