Skip to content
Merged
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
27 changes: 27 additions & 0 deletions scripts/helpers/upgradeToV4.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { ethers } from "hardhat";
import fs from "fs";

async function main() {
const PROXY_ADMIN = process.env.PROXY_ADMIN!;
const TOKEN_PROXY = process.env.TOKEN_PROXY!;

type RawUpdate = { previousMultiplier: string; newMultiplier: string; activationTime: number };
const pastUpdates: RawUpdate[] = JSON.parse(fs.readFileSync(process.env.PAST_UPDATES_FILE!, "utf8"));

const v4Impl = await (
await ethers.getContractFactory("BackedAutoFeeTokenImplementation")
).deploy();
await v4Impl.deployed();

const proxyAdmin = await ethers.getContractAt("ProxyAdmin", PROXY_ADMIN);

const tx = await proxyAdmin.upgradeAndCall(
TOKEN_PROXY,
v4Impl.address,
v4Impl.interface.encodeFunctionData("initialize_v4", [pastUpdates])
);
const receipt = await tx.wait();
console.log(`Upgrade + initialize_v4 mined in block ${receipt.blockNumber} (tx: ${tx.hash})`);
}

main().catch((e) => { console.error(e); process.exit(1); });
110 changes: 110 additions & 0 deletions test/Upgradability.ts
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,113 @@ describe("Upgrade from v1.1.0 to auto fee", () => {
expect(await tokenV2.balanceOf(tmpAccount.address)).to.equal(150);
});
});

describe("Upgrade to v4 (multiplier history migration)", () => {
let v4Impl: BackedAutoFeeTokenImplementation;
let token: BackedAutoFeeTokenImplementation;
let tokenV1: BackedTokenImplementation;
let v1Factory: BackedFactory;

let owner: SignerWithAddress;
let minter: SignerWithAddress;
let burner: SignerWithAddress;
let pauser: SignerWithAddress;
let blacklister: SignerWithAddress;
let attacker: SignerWithAddress;
let sanctionsList: SanctionsListMock;

const tokenName = "Wrapped Apple";
const tokenSymbol = "WAAPL";

const past = [
{ previousMultiplier: "1000000000000000000", newMultiplier: "999000000000000000", activationTime: 1700000000 },
{ previousMultiplier: "999000000000000000", newMultiplier: "998000000000000000", activationTime: 1700086400 },
];

beforeEach(async () => {
owner = await getSigner(0);
minter = await getSigner(1);
burner = await getSigner(2);
pauser = await getSigner(3);
blacklister = await getSigner(4);
attacker = await getSigner(5);

// Deploy a v1.1.0 token proxy (its multiplierUpdates array is empty,
// which is the precondition initialize_v4 requires).
v1Factory = await (
await ethers.getContractFactory("BackedFactory")
).deploy(owner.address);

sanctionsList = await (
await ethers.getContractFactory("SanctionsListMock", blacklister.signer)
).deploy();

const receipt = await (
await v1Factory.deployToken(
tokenName,
tokenSymbol,
owner.address,
minter.address,
burner.address,
pauser.address,
sanctionsList.address
)
).wait();

const deployedTokenAddress = receipt.events?.find(
(event: any) => event.event === "NewToken"
)?.args?.newToken;

tokenV1 = await ethers.getContractAt(
"BackedTokenImplementation",
deployedTokenAddress
);

v4Impl = await (
await ethers.getContractFactory("BackedAutoFeeTokenImplementation")
).deploy();
await v4Impl.deployed();
});

it("atomic upgradeAndCall backfills history and is one-shot", async () => {
const proxyAdmin = await ethers.getContractAt(
"ProxyAdmin",
await v1Factory.proxyAdmin()
);

// Atomic: flip implementation AND run initialize_v4 in a single tx.
await proxyAdmin.upgradeAndCall(
tokenV1.address,
v4Impl.address,
v4Impl.interface.encodeFunctionData('initialize_v4', [past])
);

token = await ethers.getContractAt(
"BackedAutoFeeTokenImplementation",
tokenV1.address
);

// Genesis sentinel at index 0 + the two backfilled entries.
expect(await token.multiplierUpdatesLength()).to.equal(3);

const sentinel = await token.multiplierUpdates(0);
expect(sentinel.previousMultiplier).to.equal("1000000000000000000");
expect(sentinel.newMultiplier).to.equal("1000000000000000000");
expect(sentinel.activationTime).to.equal(0);

const first = await token.multiplierUpdates(1);
expect(first.previousMultiplier).to.equal(past[0].previousMultiplier);
expect(first.newMultiplier).to.equal(past[0].newMultiplier);
expect(first.activationTime).to.equal(past[0].activationTime);

const second = await token.multiplierUpdates(2);
expect(second.previousMultiplier).to.equal(past[1].previousMultiplier);
expect(second.newMultiplier).to.equal(past[1].newMultiplier);
expect(second.activationTime).to.equal(past[1].activationTime);

// One-shot: any later call (any caller) reverts.
await expect(token.initialize_v4([])).to.be.revertedWith(
"BackedAutoFeeTokenImplementation v4 already initialized"
);
});
});
Loading