From 745ff0ba9b6de543d2af2f326fe1ae3c39fef59d Mon Sep 17 00:00:00 2001 From: Hendrik Hofstadt Date: Wed, 1 Sep 2021 13:13:51 +0200 Subject: [PATCH] Allow to update meta on ETH, new token naming This allows us to update the token name and symbol once solana adds the appropriate metadata. Change-Id: Ibcabf2a644bcc2b2962ff779fa7fb3cb1f2cc626 --- ethereum/contracts/bridge/Bridge.sol | 113 +++++++++++------- ethereum/contracts/bridge/BridgeGetters.sol | 1 + .../contracts/bridge/BridgeGovernance.sol | 10 +- ethereum/contracts/bridge/TokenBridge.sol | 8 +- ethereum/contracts/bridge/token/Token.sol | 1 + .../bridge/token/TokenImplementation.sol | 14 ++- .../contracts/bridge/token/TokenState.sol | 6 +- 7 files changed, 95 insertions(+), 58 deletions(-) diff --git a/ethereum/contracts/bridge/Bridge.sol b/ethereum/contracts/bridge/Bridge.sol index f9841e117..8b4a7ff0c 100644 --- a/ethereum/contracts/bridge/Bridge.sol +++ b/ethereum/contracts/bridge/Bridge.sol @@ -34,29 +34,29 @@ contract Bridge is BridgeGovernance { bytes32 symbol; bytes32 name; assembly { - // first 32 bytes hold string length + // first 32 bytes hold string length symbol := mload(add(symbolString, 32)) name := mload(add(nameString, 32)) } BridgeStructs.AssetMeta memory meta = BridgeStructs.AssetMeta({ - payloadID : 2, - // Address of the token. Left-zero-padded if shorter than 32 bytes - tokenAddress : bytes32(uint256(uint160(tokenAddress))), - // Chain ID of the token - tokenChain : chainId(), - // Number of decimals of the token (big-endian uint8) - decimals : decimals, - // Symbol of the token (UTF-8) - symbol : symbol, - // Name of the token (UTF-8) - name : name + payloadID : 2, + // Address of the token. Left-zero-padded if shorter than 32 bytes + tokenAddress : bytes32(uint256(uint160(tokenAddress))), + // Chain ID of the token + tokenChain : chainId(), + // Number of decimals of the token (big-endian uint8) + decimals : decimals, + // Symbol of the token (UTF-8) + symbol : symbol, + // Name of the token (UTF-8) + name : name }); bytes memory encoded = encodeAssetMeta(meta); sequence = wormhole().publishMessage{ - value : msg.value + value : msg.value }(nonce, encoded, 15); } @@ -69,18 +69,18 @@ contract Bridge is BridgeGovernance { require(arbiterFee <= amount, "fee is bigger than amount minus wormhole fee"); - uint normalizedAmount = amount / (10**10); - uint normalizedArbiterFee = arbiterFee / (10**10); + uint normalizedAmount = amount / (10 ** 10); + uint normalizedArbiterFee = arbiterFee / (10 ** 10); // refund dust - uint dust = amount - (normalizedAmount * (10**10)); + uint dust = amount - (normalizedAmount * (10 ** 10)); if (dust > 0) { - payable(msg.sender).transfer(dust); + payable(msg.sender).transfer(dust); } // deposit into WETH WETH().deposit{ - value : amount - dust + value : amount - dust }(); // track and check outstanding token amounts @@ -94,10 +94,10 @@ contract Bridge is BridgeGovernance { // determine token parameters uint16 tokenChain; bytes32 tokenAddress; - if(isWrappedAsset(token)){ + if (isWrappedAsset(token)) { tokenChain = TokenImplementation(token).chainId(); tokenAddress = TokenImplementation(token).nativeContract(); - }else{ + } else { tokenChain = chainId(); tokenAddress = bytes32(uint256(uint160(token))); } @@ -109,8 +109,8 @@ contract Bridge is BridgeGovernance { // adjust decimals uint256 normalizedAmount = amount; uint256 normalizedArbiterFee = arbiterFee; - if(decimals > 8) { - uint multiplier = 10**(decimals - 8); + if (decimals > 8) { + uint multiplier = 10 ** (decimals - 8); normalizedAmount /= multiplier; normalizedArbiterFee /= multiplier; @@ -119,7 +119,7 @@ contract Bridge is BridgeGovernance { amount = normalizedAmount * multiplier; } - if(tokenChain == chainId()){ + if (tokenChain == chainId()) { SafeERC20.safeTransferFrom(IERC20(token), msg.sender, address(this), amount); // track and check outstanding token amounts @@ -137,22 +137,42 @@ contract Bridge is BridgeGovernance { require(fee <= amount, "fee exceeds amount"); BridgeStructs.Transfer memory transfer = BridgeStructs.Transfer({ - payloadID : 1, - amount : amount, - tokenAddress : tokenAddress, - tokenChain : tokenChain, - to : recipient, - toChain : recipientChain, - fee : fee + payloadID : 1, + amount : amount, + tokenAddress : tokenAddress, + tokenChain : tokenChain, + to : recipient, + toChain : recipientChain, + fee : fee }); bytes memory encoded = encodeTransfer(transfer); sequence = wormhole().publishMessage{ - value : callValue + value : callValue }(nonce, encoded, 15); } + function updateWrapped(bytes memory encodedVm) external returns (address token) { + (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm); + + require(valid, reason); + require(verifyBridgeVM(vm), "invalid emitter"); + + BridgeStructs.AssetMeta memory meta = parseAssetMeta(vm.payload); + return _updateWrapped(meta, vm.sequence); + } + + function _updateWrapped(BridgeStructs.AssetMeta memory meta, uint64 sequence) internal returns (address token) { + address wrapped = wrappedAsset(meta.tokenChain, meta.tokenAddress); + require(wrapped != address(0), "wrapped asset does not exists"); + + // Update metadata + TokenImplementation(wrapped).updateDetails(bytes32ToString(meta.name), bytes32ToString(meta.symbol), sequence); + + return wrapped; + } + function createWrapped(bytes memory encodedVm) external returns (address token) { (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVm); @@ -160,11 +180,11 @@ contract Bridge is BridgeGovernance { require(verifyBridgeVM(vm), "invalid emitter"); BridgeStructs.AssetMeta memory meta = parseAssetMeta(vm.payload); - return _createWrapped(meta); + return _createWrapped(meta, vm.sequence); } - // Creates a wrapped asset using AssetMeta - function _createWrapped(BridgeStructs.AssetMeta memory meta) internal returns (address token) { + // Creates a wrapped asset using AssetMeta + function _createWrapped(BridgeStructs.AssetMeta memory meta, uint64 sequence) internal returns (address token) { require(meta.tokenChain != chainId(), "can only wrap tokens from foreign chains"); require(wrappedAsset(meta.tokenChain, meta.tokenAddress) == address(0), "wrapped asset already exists"); @@ -174,6 +194,7 @@ contract Bridge is BridgeGovernance { bytes32ToString(meta.name), bytes32ToString(meta.symbol), meta.decimals, + sequence, address(this), @@ -223,7 +244,7 @@ contract Bridge is BridgeGovernance { require(transfer.toChain == chainId(), "invalid target chain"); IERC20 transferToken; - if(transfer.tokenChain == chainId()){ + if (transfer.tokenChain == chainId()) { transferToken = IERC20(address(uint160(uint256(transfer.tokenAddress)))); // track outstanding token amounts @@ -244,14 +265,14 @@ contract Bridge is BridgeGovernance { // adjust decimals uint256 nativeAmount = transfer.amount; uint256 nativeFee = transfer.fee; - if(decimals > 8) { - uint multiplier = 10**(decimals - 8); + if (decimals > 8) { + uint multiplier = 10 ** (decimals - 8); nativeAmount *= multiplier; nativeFee *= multiplier; } // transfer fee to arbiter - if(nativeFee > 0) { + if (nativeFee > 0) { require(nativeFee <= nativeAmount, "fee higher than transferred amount"); if (unwrapWETH) { @@ -259,10 +280,10 @@ contract Bridge is BridgeGovernance { payable(msg.sender).transfer(nativeFee); } else { - if(transfer.tokenChain != chainId()) { + if (transfer.tokenChain != chainId()) { // mint wrapped asset TokenImplementation(address(transferToken)).mint(msg.sender, nativeFee); - }else{ + } else { SafeERC20.safeTransfer(transferToken, msg.sender, nativeFee); } } @@ -277,10 +298,10 @@ contract Bridge is BridgeGovernance { payable(transferRecipient).transfer(transferAmount); } else { - if(transfer.tokenChain != chainId()) { + if (transfer.tokenChain != chainId()) { // mint wrapped asset TokenImplementation(address(transferToken)).mint(transferRecipient, transferAmount); - }else{ + } else { SafeERC20.safeTransfer(transferToken, transferRecipient, transferAmount); } } @@ -304,7 +325,7 @@ contract Bridge is BridgeGovernance { return false; } - function encodeAssetMeta(BridgeStructs.AssetMeta memory meta) public pure returns(bytes memory encoded) { + function encodeAssetMeta(BridgeStructs.AssetMeta memory meta) public pure returns (bytes memory encoded) { encoded = abi.encodePacked( meta.payloadID, meta.tokenAddress, @@ -327,7 +348,7 @@ contract Bridge is BridgeGovernance { ); } - function parseAssetMeta(bytes memory encoded) public pure returns(BridgeStructs.AssetMeta memory meta) { + function parseAssetMeta(bytes memory encoded) public pure returns (BridgeStructs.AssetMeta memory meta) { uint index = 0; meta.payloadID = encoded.toUint8(index); @@ -353,7 +374,7 @@ contract Bridge is BridgeGovernance { require(encoded.length == index, "invalid AssetMeta"); } - function parseTransfer(bytes memory encoded) public pure returns(BridgeStructs.Transfer memory transfer) { + function parseTransfer(bytes memory encoded) public pure returns (BridgeStructs.Transfer memory transfer) { uint index = 0; transfer.payloadID = encoded.toUint8(index); @@ -384,7 +405,7 @@ contract Bridge is BridgeGovernance { function bytes32ToString(bytes32 input) internal pure returns (string memory) { uint256 i; - while(i < 32 && input[i] != 0) { + while (i < 32 && input[i] != 0) { i++; } bytes memory array = new bytes(i); diff --git a/ethereum/contracts/bridge/BridgeGetters.sol b/ethereum/contracts/bridge/BridgeGetters.sol index afc67ec77..c0ce13722 100644 --- a/ethereum/contracts/bridge/BridgeGetters.sol +++ b/ethereum/contracts/bridge/BridgeGetters.sol @@ -65,5 +65,6 @@ contract BridgeGetters is BridgeState { interface IWETH is IERC20 { function deposit() external payable; + function withdraw(uint amount) external; } \ No newline at end of file diff --git a/ethereum/contracts/bridge/BridgeGovernance.sol b/ethereum/contracts/bridge/BridgeGovernance.sol index 41f371b15..7373ae557 100644 --- a/ethereum/contracts/bridge/BridgeGovernance.sol +++ b/ethereum/contracts/bridge/BridgeGovernance.sol @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.0; + import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol"; import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Upgrade.sol"; @@ -54,7 +55,7 @@ contract BridgeGovernance is BridgeGetters, BridgeSetters, ERC1967Upgrade { function verifyGovernanceVM(bytes memory encodedVM) internal view returns (IWormhole.VM memory parsedVM, bool isValid, string memory invalidReason){ (IWormhole.VM memory vm, bool valid, string memory reason) = wormhole().parseAndVerifyVM(encodedVM); - if(!valid){ + if (!valid) { return (vm, valid, reason); } @@ -65,7 +66,7 @@ contract BridgeGovernance is BridgeGetters, BridgeSetters, ERC1967Upgrade { return (vm, false, "wrong governance contract"); } - if(governanceActionIsConsumed(vm.hash)){ + if (governanceActionIsConsumed(vm.hash)) { return (vm, false, "governance action already consumed"); } @@ -73,6 +74,7 @@ contract BridgeGovernance is BridgeGetters, BridgeSetters, ERC1967Upgrade { } event ContractUpgraded(address indexed oldContract, address indexed newContract); + function upgradeImplementation(address newImplementation) internal { address currentImplementation = _getImplementation(); @@ -86,7 +88,7 @@ contract BridgeGovernance is BridgeGetters, BridgeSetters, ERC1967Upgrade { emit ContractUpgraded(currentImplementation, newImplementation); } - function parseRegisterChain(bytes memory encoded) public pure returns(BridgeStructs.RegisterChain memory chain) { + function parseRegisterChain(bytes memory encoded) public pure returns (BridgeStructs.RegisterChain memory chain) { uint index = 0; // governance header @@ -113,7 +115,7 @@ contract BridgeGovernance is BridgeGetters, BridgeSetters, ERC1967Upgrade { require(encoded.length == index, "invalid RegisterChain: wrong length"); } - function parseUpgrade(bytes memory encoded) public pure returns(BridgeStructs.UpgradeContract memory chain) { + function parseUpgrade(bytes memory encoded) public pure returns (BridgeStructs.UpgradeContract memory chain) { uint index = 0; // governance header diff --git a/ethereum/contracts/bridge/TokenBridge.sol b/ethereum/contracts/bridge/TokenBridge.sol index dfa560dda..bc7e0ec00 100644 --- a/ethereum/contracts/bridge/TokenBridge.sol +++ b/ethereum/contracts/bridge/TokenBridge.sol @@ -7,9 +7,9 @@ import "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol"; contract TokenBridge is ERC1967Proxy { constructor (address implementation, bytes memory initData) - ERC1967Proxy( - implementation, - initData - ) + ERC1967Proxy( + implementation, + initData + ) {} } \ No newline at end of file diff --git a/ethereum/contracts/bridge/token/Token.sol b/ethereum/contracts/bridge/token/Token.sol index 8cdfba901..5f6488c07 100644 --- a/ethereum/contracts/bridge/token/Token.sol +++ b/ethereum/contracts/bridge/token/Token.sol @@ -2,6 +2,7 @@ // SPDX-License-Identifier: Apache 2 pragma solidity ^0.8.0; + import "@openzeppelin/contracts/proxy/beacon/BeaconProxy.sol"; contract BridgeToken is BeaconProxy { diff --git a/ethereum/contracts/bridge/token/TokenImplementation.sol b/ethereum/contracts/bridge/token/TokenImplementation.sol index 585dc5519..b8fb26c58 100644 --- a/ethereum/contracts/bridge/token/TokenImplementation.sol +++ b/ethereum/contracts/bridge/token/TokenImplementation.sol @@ -17,6 +17,7 @@ contract TokenImplementation is TokenState, Context { string memory name_, string memory symbol_, uint8 decimals_, + uint64 sequence_, address owner_, @@ -26,6 +27,7 @@ contract TokenImplementation is TokenState, Context { _state.name = name_; _state.symbol = symbol_; _state.decimals = decimals_; + _state.metaLastUpdatedSequence = sequence_; _state.owner = owner_; @@ -34,11 +36,11 @@ contract TokenImplementation is TokenState, Context { } function name() public view returns (string memory) { - return string(abi.encodePacked("Wormhole: ", _state.name)); + return string(abi.encodePacked(_state.name, " (Wormhole)")); } function symbol() public view returns (string memory) { - return string(abi.encodePacked("wh", _state.symbol)); + return _state.symbol; } function owner() public view returns (address) { @@ -149,6 +151,14 @@ contract TokenImplementation is TokenState, Context { emit Approval(owner_, spender_, amount_); } + function updateDetails(string memory name_, string memory symbol_, uint64 sequence_) public onlyOwner { + require(_state.metaLastUpdatedSequence < sequence_, "current metadata is up to date"); + + _state.name = name_; + _state.symbol = symbol_; + _state.metaLastUpdatedSequence = sequence_; + } + modifier onlyOwner() { require(owner() == _msgSender(), "caller is not the owner"); _; diff --git a/ethereum/contracts/bridge/token/TokenState.sol b/ethereum/contracts/bridge/token/TokenState.sol index b11adcee2..cd58ce54a 100644 --- a/ethereum/contracts/bridge/token/TokenState.sol +++ b/ethereum/contracts/bridge/token/TokenState.sol @@ -8,12 +8,14 @@ contract TokenStorage { string name; string symbol; + uint64 metaLastUpdatedSequence; + uint256 totalSupply; uint8 decimals; - mapping (address => uint256) balances; + mapping(address => uint256) balances; - mapping (address => mapping (address => uint256)) allowances; + mapping(address => mapping(address => uint256)) allowances; address owner;