diff --git a/contracts/interfaces/IMeTokenRegistry.sol b/contracts/interfaces/IMeTokenRegistry.sol index ebb07503..2726ce5b 100644 --- a/contracts/interfaces/IMeTokenRegistry.sol +++ b/contracts/interfaces/IMeTokenRegistry.sol @@ -110,6 +110,11 @@ interface IMeTokenRegistry { /// @return TODO function getOwnerMeToken(address _owner) external view returns (address); + /// @notice TODO + /// @param _oldOwner TODO + /// @return TODO + function getPendingOwner(address _oldOwner) external view returns (address); + /// @notice TODO /// @param meToken Address of meToken queried /// @return meToken_ details of the meToken diff --git a/contracts/registries/MeTokenRegistry.sol b/contracts/registries/MeTokenRegistry.sol index 13b41563..2c9064a0 100644 --- a/contracts/registries/MeTokenRegistry.sol +++ b/contracts/registries/MeTokenRegistry.sol @@ -289,14 +289,14 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { /// @inheritdoc IMeTokenRegistry function cancelTransferMeTokenOwnership() external override { + address _meToken = _owners[msg.sender]; + require(_meToken != address(0), "meToken does not exist"); + require( _pendingOwners[msg.sender] != address(0), "transferMeTokenOwnership() not initiated" ); - address _meToken = _owners[msg.sender]; - require(_meToken != address(0), "meToken does not exist"); - delete _pendingOwners[msg.sender]; emit CancelTransferMeTokenOwnership(msg.sender, _meToken); } @@ -345,6 +345,16 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { return _owners[_owner]; } + /// @inheritdoc IMeTokenRegistry + function getPendingOwner(address _oldOwner) + external + view + override + returns (address) + { + return _pendingOwners[_oldOwner]; + } + /// @inheritdoc IMeTokenRegistry function getDetails(address _meToken) external diff --git a/test/contracts/registries/MeTokenRegistry.ts b/test/contracts/registries/MeTokenRegistry.ts index 65942ca9..ea1e9a13 100644 --- a/test/contracts/registries/MeTokenRegistry.ts +++ b/test/contracts/registries/MeTokenRegistry.ts @@ -12,10 +12,13 @@ import { } from "../../utils/helpers"; import { SignerWithAddress } from "@nomiclabs/hardhat-ethers/signers"; import { hubSetup } from "../../utils/hubSetup"; -import { BigNumber } from "ethers"; +import { BigNumber, ContractTransaction } from "ethers"; import { expect } from "chai"; describe("MeTokenRegistry.sol", () => { + let meTokenAddr0: string; + let meTokenAddr1: string; + let tx: ContractTransaction; let meTokenRegistry: MeTokenRegistry; let hub: Hub; @@ -55,15 +58,13 @@ describe("MeTokenRegistry.sol", () => { const tx = await meTokenRegistry .connect(account0) .subscribe(name, "CARL", hubId, 0); - const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account0.address - ); + meTokenAddr0 = await meTokenRegistry.getOwnerMeToken(account0.address); /* expect(tx) .to.emit(meTokenRegistry, "Register") .withArgs(meTokenAddr, account0.address, name, symbol, hubId); */ // assert token infos - const meToken = await getContractAt("MeToken", meTokenAddr); + const meToken = await getContractAt("MeToken", meTokenAddr0); expect(await meToken.name()).to.equal(name); expect(await meToken.symbol()).to.equal(symbol); expect(await meToken.decimals()).to.equal(18); @@ -91,10 +92,8 @@ describe("MeTokenRegistry.sol", () => { const balVault = await token.balanceOf(hubDetail.vault); expect(balVault).equal(amount); // assert token infos - const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account1.address - ); - const meToken = await getContractAt("MeToken", meTokenAddr); + meTokenAddr1 = await meTokenRegistry.getOwnerMeToken(account1.address); + const meToken = await getContractAt("MeToken", meTokenAddr1); // should be greater than 0 const calculatedRes = calculateTokenReturnedFromZero( @@ -102,75 +101,145 @@ describe("MeTokenRegistry.sol", () => { toETHNumber(baseY), reserveWeight / MAX_WEIGHT ); + console.log(` calculatedRes:${calculatedRes}`); expect(toETHNumber(await meToken.totalSupply())).to.equal(calculatedRes); }); }); - describe("transferOwnership()", () => { - it("Fails if not owner", async () => { - const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account1.address - ); + describe("transferMeTokenOwnership()", () => { + it("Fails if not a meToken owner", async () => { await expect( meTokenRegistry .connect(account3) .transferMeTokenOwnership(account2.address) ).to.revertedWith("meToken does not exist"); - + }); + it("Fails if recipient already owns a meToken", async () => { await expect( meTokenRegistry.transferMeTokenOwnership(account1.address) ).to.revertedWith("_newOwner already owns a meToken"); }); - it("Emits TransferOwnership()", async () => { - const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account1.address + it("Fails if _newOwner is address(0)", async () => { + await expect( + meTokenRegistry.transferMeTokenOwnership(ethers.constants.AddressZero) + ).to.be.revertedWith("Cannot transfer to 0 address"); + }); + it("Successfully queues a recipient to claim ownership", async () => { + expect(await meTokenRegistry.getPendingOwner(account1.address)).to.equal( + ethers.constants.AddressZero ); - const tx = await meTokenRegistry + tx = await meTokenRegistry .connect(account1) .transferMeTokenOwnership(account2.address); - const meTokenAddrAfter = await meTokenRegistry.getOwnerMeToken( - account1.address + expect(await meTokenRegistry.getPendingOwner(account1.address)).to.equal( + account2.address ); - await expect(tx) + }); + it("Emits TransferOwnership()", async () => { + expect(tx) .to.emit(meTokenRegistry, "TransferMeTokenOwnership") - .withArgs(account1.address, account2.address, meTokenAddr); + .withArgs(account1.address, account2.address, meTokenAddr1); }); }); - describe("isOwner()", () => { - it("Returns false for address(0)", async () => { - expect(await meTokenRegistry.isOwner(ethers.constants.AddressZero)).to.be - .false; + describe("cancelTransferMeTokenOwnership()", () => { + it("Fails if owner has never called transferMeTokenOwnership()", async () => { + await expect( + meTokenRegistry.connect(account0).cancelTransferMeTokenOwnership() + ).to.be.revertedWith("transferMeTokenOwnership() not initiated"); }); - it("Revert if ownership is not claimed", async () => { - expect(await meTokenRegistry.isOwner(account2.address)).to.be.false; + it("Fails if owner does not own a meToken", async () => { + await expect( + meTokenRegistry.connect(account2).cancelTransferMeTokenOwnership() + ).to.be.revertedWith("meToken does not exist"); }); - it("Claim ownership should work", async () => { - const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account1.address + it("Succesfully cancels transfer and removes from _pendingOwners", async () => { + tx = await meTokenRegistry + .connect(account1) + .cancelTransferMeTokenOwnership(); + expect(await meTokenRegistry.getPendingOwner(account1.address)).to.equal( + ethers.constants.AddressZero ); - const tx = await meTokenRegistry + }); + it("Emits CancelTransferMeTokenOwnership()", async () => { + expect(tx) + .to.emit(meTokenRegistry, "CancelTransferMeTokenOwnership") + .withArgs(account1.address, meTokenAddr1); + }); + }); + + describe("claimMeTokenOwnership()", () => { + it("Fails if claimer already owns a meToken", async () => { + // scenario 1: already owns a meToken, not a pending owner + await expect( + meTokenRegistry + .connect(account0) + .claimMeTokenOwnership(ethers.constants.AddressZero) + ).to.be.revertedWith("Already owns a meToken"); + // Scenario 2: doesn't own a meToken and becomes pending owner for 2 meTokens, + // claims ownership to the first, then tries claiming ownership to the second + await meTokenRegistry + .connect(account0) + .transferMeTokenOwnership(account2.address); + await meTokenRegistry + .connect(account1) + .transferMeTokenOwnership(account2.address); + tx = await meTokenRegistry .connect(account2) - .claimMeTokenOwnership(account1.address); - const meTokenAddrAfter = await meTokenRegistry.getOwnerMeToken( - account2.address + .claimMeTokenOwnership(account0.address); + await expect( + meTokenRegistry + .connect(account2) + .claimMeTokenOwnership(account1.address) + ).to.be.revertedWith("Already owns a meToken"); + }); + it("Fails if not claimer not pending owner from oldOwner", async () => { + await expect( + meTokenRegistry + .connect(account3) + .claimMeTokenOwnership(account1.address) + ).to.be.revertedWith("!_pendingOwner"); + }); + it("Successfully completes claim and updates meToken struct, deletes old mappings", async () => { + expect(await meTokenRegistry.getOwnerMeToken(account2.address)).to.equal( + meTokenAddr0 + ); + const details = await meTokenRegistry.getDetails(meTokenAddr0); + expect(details.owner).to.equal(account2.address); + expect(await meTokenRegistry.getPendingOwner(account0.address)).to.equal( + ethers.constants.AddressZero ); - expect(meTokenAddr).to.equal(meTokenAddrAfter); - await expect(tx) + expect(await meTokenRegistry.getOwnerMeToken(account0.address)).to.equal( + ethers.constants.AddressZero + ); + }); + it("Emits ClaimMeTokenOwnership()", async () => { + expect(tx) .to.emit(meTokenRegistry, "ClaimMeTokenOwnership") - .withArgs(account1.address, account2.address, meTokenAddr); + .withArgs(account0.address, account2.address, meTokenAddr0); + }); + }); + + describe("isOwner()", () => { + it("Returns false for address(0)", async () => { + expect(await meTokenRegistry.isOwner(ethers.constants.AddressZero)).to.be + .false; + }); + it("Returns false for if address not an owner", async () => { + expect(await meTokenRegistry.isOwner(account3.address)).to.be.false; }); it("Returns true for a meToken issuer", async () => { - expect(await meTokenRegistry.isOwner(account2.address)).to.be.true; + expect(await meTokenRegistry.isOwner(account1.address)).to.be.true; }); }); describe("balancePool", () => { it("Fails if not foundry", async () => { - const meTokenAddr = await meTokenRegistry.getOwnerMeToken( - account1.address - ); await expect( - meTokenRegistry.updateBalancePooled(true, meTokenAddr, account2.address) + meTokenRegistry.updateBalancePooled( + true, + meTokenAddr1, + account2.address + ) ).to.revertedWith("!foundry"); }); it("updateBalancePooled()", async () => {