diff --git a/contracts/interfaces/IMeTokenRegistry.sol b/contracts/interfaces/IMeTokenRegistry.sol index 59f8e412..ebb07503 100644 --- a/contracts/interfaces/IMeTokenRegistry.sol +++ b/contracts/interfaces/IMeTokenRegistry.sol @@ -28,6 +28,9 @@ interface IMeTokenRegistry { address _to, address _meToken ); + event CancelTransferMeTokenOwnership(address _from, address _meToken); + event ClaimMeTokenOwnership(address _from, address _to, address _meToken); + event UpdateBalancePooled(bool add, address _meToken, uint256 _amount); event UpdateBalanceLocked(bool add, address _meToken, uint256 _amount); @@ -95,6 +98,13 @@ interface IMeTokenRegistry { /// @param _newOwner TODO function transferMeTokenOwnership(address _newOwner) external; + /// @notice TODO + function cancelTransferMeTokenOwnership() external; + + /// @notice TODO + /// @param _oldOwner TODO + function claimMeTokenOwnership(address _oldOwner) external; + /// @notice TODO /// @param _owner TODO /// @return TODO diff --git a/contracts/registries/MeTokenRegistry.sol b/contracts/registries/MeTokenRegistry.sol index d97b6875..13b41563 100644 --- a/contracts/registries/MeTokenRegistry.sol +++ b/contracts/registries/MeTokenRegistry.sol @@ -31,7 +31,9 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { IMigrationRegistry public migrationRegistry; mapping(address => Details.MeToken) private _meTokens; // key pair: ERC20 address + // TODO: this mapping will break when multiple addresses revoke ownership to 0 address mapping(address => address) private _owners; // key: address of owner, value: address of meToken + mapping(address => address) private _pendingOwners; // key: address of owner, value: address of pending owner constructor( address _foundry, @@ -272,18 +274,48 @@ contract MeTokenRegistry is Ownable, IMeTokenRegistry { /// @inheritdoc IMeTokenRegistry function transferMeTokenOwnership(address _newOwner) external override { + require( + _pendingOwners[msg.sender] == address(0), + "transfer ownership already pending" + ); require(!isOwner(_newOwner), "_newOwner already owns a meToken"); + require(_newOwner != address(0), "Cannot transfer to 0 address"); + address meToken_ = _owners[msg.sender]; + require(meToken_ != address(0), "meToken does not exist"); + _pendingOwners[msg.sender] = _newOwner; + + emit TransferMeTokenOwnership(msg.sender, _newOwner, meToken_); + } + + /// @inheritdoc IMeTokenRegistry + function cancelTransferMeTokenOwnership() external override { + require( + _pendingOwners[msg.sender] != address(0), + "transferMeTokenOwnership() not initiated" + ); - // TODO: what happens if multiple people want to revoke ownership to 0 address? address _meToken = _owners[msg.sender]; + require(_meToken != address(0), "meToken does not exist"); - require(_meToken != address(0), "!meToken"); + delete _pendingOwners[msg.sender]; + emit CancelTransferMeTokenOwnership(msg.sender, _meToken); + } + + /// @inheritdoc IMeTokenRegistry + function claimMeTokenOwnership(address _oldOwner) external override { + require(!isOwner(msg.sender), "Already owns a meToken"); + require(msg.sender == _pendingOwners[_oldOwner], "!_pendingOwner"); + + address _meToken = _owners[_oldOwner]; Details.MeToken storage meToken_ = _meTokens[_meToken]; - meToken_.owner = _newOwner; - _owners[msg.sender] = address(0); - _owners[_newOwner] = _meToken; - emit TransferMeTokenOwnership(msg.sender, _newOwner, _meToken); + meToken_.owner = msg.sender; + _owners[msg.sender] = _meToken; + + delete _owners[_oldOwner]; + delete _pendingOwners[_oldOwner]; + + emit ClaimMeTokenOwnership(_oldOwner, msg.sender, _meToken); } function setWarmup(uint256 warmup_) external onlyOwner { diff --git a/test/contracts/registries/MeTokenRegistry.ts b/test/contracts/registries/MeTokenRegistry.ts index 83588680..47fe8ad0 100644 --- a/test/contracts/registries/MeTokenRegistry.ts +++ b/test/contracts/registries/MeTokenRegistry.ts @@ -107,7 +107,6 @@ describe("MeTokenRegistry.sol", () => { toETHNumber(baseY), reserveWeight / MAX_WEIGHT ); - console.log(` calculatedRes:${calculatedRes}`); expect(toETHNumber(await meToken.totalSupply())).to.equal(calculatedRes); }); }); @@ -121,7 +120,7 @@ describe("MeTokenRegistry.sol", () => { meTokenRegistry .connect(account3) .transferMeTokenOwnership(account2.address) - ).to.revertedWith("!meToken"); + ).to.revertedWith("meToken does not exist"); await expect( meTokenRegistry.transferMeTokenOwnership(account1.address) @@ -131,14 +130,15 @@ describe("MeTokenRegistry.sol", () => { const meTokenAddr = await meTokenRegistry.getOwnerMeToken( account1.address ); - const tx = await meTokenRegistry .connect(account1) .transferMeTokenOwnership(account2.address); - - /* await expect(tx) - .to.emit(meTokenRegistry, "TransferOwnership") - .withArgs(account1.address, account2.address, meTokenAddr); */ + const meTokenAddrAfter = await meTokenRegistry.getOwnerMeToken( + account1.address + ); + await expect(tx) + .to.emit(meTokenRegistry, "TransferMeTokenOwnership") + .withArgs(account1.address, account2.address, meTokenAddr); }); }); @@ -147,6 +147,24 @@ describe("MeTokenRegistry.sol", () => { expect(await meTokenRegistry.isOwner(ethers.constants.AddressZero)).to.be .false; }); + it("Revert if ownership is not claimed", async () => { + expect(await meTokenRegistry.isOwner(account2.address)).to.be.false; + }); + it("Claim ownership should work", async () => { + const meTokenAddr = await meTokenRegistry.getOwnerMeToken( + account1.address + ); + const tx = await meTokenRegistry + .connect(account2) + .claimMeTokenOwnership(account1.address); + const meTokenAddrAfter = await meTokenRegistry.getOwnerMeToken( + account2.address + ); + expect(meTokenAddr).to.equal(meTokenAddrAfter); + await expect(tx) + .to.emit(meTokenRegistry, "ClaimMeTokenOwnership") + .withArgs(account1.address, account2.address, meTokenAddr); + }); it("Returns true for a meToken issuer", async () => { expect(await meTokenRegistry.isOwner(account2.address)).to.be.true; });