Skip to content

Commit

Permalink
feat(ucs01): denom metadata update packet
Browse files Browse the repository at this point in the history
  • Loading branch information
hussein-aitlahcen committed Jun 26, 2024
1 parent ef0d35a commit 021ec8a
Show file tree
Hide file tree
Showing 6 changed files with 642 additions and 297 deletions.
47 changes: 46 additions & 1 deletion evm/contracts/apps/ucs/01-relay/ERC20Denom.sol
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,12 @@ contract ERC20Denom is ERC20, IERC20Denom {

address public admin;

constructor(string memory name) ERC20(name, name) {
// Metadata updated via UCS01 governance
string private _name;
string private _symbol;
uint8 private _decimals;

constructor(string memory denomName) ERC20(denomName, denomName) {
admin = msg.sender;
}

Expand All @@ -25,4 +30,44 @@ contract ERC20Denom is ERC20, IERC20Denom {
}
_burn(from, amount);
}

function update(
string calldata newName,
string calldata newSymbol,
uint8 newDecimals
) external {
if (msg.sender != admin) {
revert ERC20Unauthorized();
}
_name = newName;
_symbol = newSymbol;
_decimals = newDecimals;
}

function name()
public
view
override(ERC20, IERC20Metadata)
returns (string memory)
{
return _name;
}

function symbol()
public
view
override(ERC20, IERC20Metadata)
returns (string memory)
{
return _symbol;
}

function decimals()
public
view
override(ERC20, IERC20Metadata)
returns (uint8)
{
return _decimals;
}
}
9 changes: 8 additions & 1 deletion evm/contracts/apps/ucs/01-relay/IERC20Denom.sol
Original file line number Diff line number Diff line change
@@ -1,9 +1,16 @@
pragma solidity ^0.8.23;

import "@openzeppelin/token/ERC20/IERC20.sol";
import "@openzeppelin/token/ERC20/extensions/IERC20Metadata.sol";

interface IERC20Denom is IERC20 {
interface IERC20Denom is IERC20, IERC20Metadata {
function mint(address to, uint256 amount) external;

function burn(address from, uint256 amount) external;

function update(
string calldata name,
string calldata symbol,
uint8 decimals
) external;
}
199 changes: 179 additions & 20 deletions evm/contracts/apps/ucs/01-relay/Relay.sol
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,27 @@ struct Token {
}

struct RelayPacket {
// This is a convenient addition to avoid having to shift the body by 1 byte to eliminate the tag.
uint8 tag;
bytes sender;
bytes receiver;
Token[] tokens;
string extension;
}

struct TokenMetadata {
string denom;
string name;
string symbol;
uint8 decimals;
}

struct UpdateMetadataPacket {
// This is a convenient addition to avoid having to shift the body by 1 byte to eliminate the tag.
uint8 tag;
TokenMetadata[] tokensMetadata;
}

interface IRelay is IIBCModule {
function getDenomAddress(
string memory sourceChannel,
Expand Down Expand Up @@ -67,6 +82,9 @@ library RelayLib {
error ErrInvalidProtocolVersion();
error ErrInvalidProtocolOrdering();
error ErrInvalidCounterpartyProtocolVersion();
error ErrDenomNotFound();
error ErrUnknownPacketTag();
error ErrInvalidMetadataTimeout();
error ErrUnstoppable();

IbcCoreChannelV1GlobalEnums.Order public constant ORDER =
Expand All @@ -76,6 +94,9 @@ library RelayLib {
bytes1 public constant ACK_FAILURE = 0x00;
uint256 public constant ACK_LENGTH = 1;

uint8 public constant PACKET_TAG_RELAY = 0;
uint8 public constant PACKET_TAG_UPDATE_METADATA = 1;

event DenomCreated(
uint64 indexed packetSequence,
string channelId,
Expand Down Expand Up @@ -110,6 +131,26 @@ library RelayLib {
uint256 amount
);

event TokenMetadataUpdate(
uint64 packetSequence,
string channelId,
address indexed token,
string indexed denom,
string name,
string symbol,
uint8 decimals
);

event TokenMetadataUpdated(
uint64 packetSequence,
string channelId,
address indexed token,
string indexed denom,
string name,
string symbol,
uint8 decimals
);

function isValidVersion(string memory version)
internal
pure
Expand Down Expand Up @@ -158,7 +199,11 @@ library RelayPacketLib {
returns (bytes memory)
{
return abi.encode(
packet.sender, packet.receiver, packet.tokens, packet.extension
packet.tag,
packet.sender,
packet.receiver,
packet.tokens,
packet.extension
);
}

Expand All @@ -175,6 +220,28 @@ library RelayPacketLib {
}
}

library UpdateMetadataPacketLib {
function encode(UpdateMetadataPacket memory packet)
internal
pure
returns (bytes memory)
{
return abi.encode(packet.tag, packet.tokensMetadata);
}

function decode(bytes calldata stream)
internal
pure
returns (UpdateMetadataPacket calldata)
{
UpdateMetadataPacket calldata packet;
assembly {
packet := stream.offset
}
return packet;
}
}

contract UCS01Relay is
IBCAppBase,
IRelay,
Expand All @@ -184,17 +251,19 @@ contract UCS01Relay is
PausableUpgradeable
{
using RelayPacketLib for RelayPacket;
using UpdateMetadataPacketLib for UpdateMetadataPacket;
using LibString for *;
using strings for *;

IIBCPacket private ibcHandler;

// A mapping from remote denom to local ERC20 wrapper.
mapping(string => mapping(string => address)) private denomToAddress;
// A mapping from a local ERC20 wrapper to the remote denom.
// Required to determine whether an ERC20 token is originating from a remote chain.
mapping(string => mapping(address => string)) private addressToDenom;
mapping(string => mapping(address => uint256)) private outstanding;
// Max number of a seconds for a metadata update packet to timeout.
uint64 updateMetadataMaxTimeout;

constructor() {
_disableInitializers();
Expand All @@ -206,6 +275,11 @@ contract UCS01Relay is
) public initializer {
__Ownable_init(admin);
ibcHandler = _ibcHandler;
updateMetadataMaxTimeout = 3600;
}

function setUpdateMetadataMaxTimeout(uint64 _updateMetadataMaxTimeout) public onlyOwner {
updateMetadataMaxTimeout = _updateMetadataMaxTimeout;
}

function ibcAddress() public view virtual override returns (address) {
Expand Down Expand Up @@ -248,6 +322,47 @@ contract UCS01Relay is
outstanding[sourceChannel][token] -= amount;
}

function updateMetadata(
string calldata sourceChannel,
address[] calldata tokens,
uint64 timeoutTimestamp
) public {
if (timeoutTimestamp - block.timestamp > updateMetadataMaxTimeout) {
revert RelayLib.ErrInvalidMetadataTimeout();
}
uint256 tokensLength = tokens.length;
TokenMetadata[] memory tokensMetadata =
new TokenMetadata[](tokens.length);
for (uint256 i; i < tokensLength; i++) {
IERC20Denom token = IERC20Denom(tokens[i]);
tokensMetadata[i].denom = address(token).toHexString();
tokensMetadata[i].name = token.name();
tokensMetadata[i].symbol = token.symbol();
tokensMetadata[i].decimals = token.decimals();
}
UpdateMetadataPacket memory packet = UpdateMetadataPacket({
tag: RelayLib.PACKET_TAG_UPDATE_METADATA,
tokensMetadata: tokensMetadata
});
uint64 packetSequence = ibcHandler.sendPacket(
sourceChannel,
IbcCoreClientV1Height.Data({revision_number: 0, revision_height: 0}),
timeoutTimestamp,
packet.encode()
);
for (uint256 i; i < tokensLength; i++) {
emit RelayLib.TokenMetadataUpdate(
packetSequence,
sourceChannel,
tokens[i],
tokensMetadata[i].denom,
tokensMetadata[i].name,
tokensMetadata[i].symbol,
tokensMetadata[i].decimals
);
}
}

// Internal function
// Send the given token over the specified channel.
// If token is native, we increase the oustanding amount and escrow it. Otherwise, we burn the amount.
Expand Down Expand Up @@ -295,6 +410,7 @@ contract UCS01Relay is
normalizedTokens[i].amount = localToken.amount;
}
RelayPacket memory packet = RelayPacket({
tag: RelayLib.PACKET_TAG_RELAY,
sender: abi.encodePacked(msg.sender),
receiver: receiver,
tokens: normalizedTokens,
Expand Down Expand Up @@ -355,13 +471,33 @@ contract UCS01Relay is
}
}

function onRecvPacketProcessing(
IbcCoreChannelV1Packet.Data calldata ibcPacket,
address
) public {
if (msg.sender != address(this)) {
revert RelayLib.ErrUnauthorized();
function onRecvUpdateMetadataPacket(
IbcCoreChannelV1Packet.Data calldata ibcPacket
) internal {
UpdateMetadataPacket calldata packet =
UpdateMetadataPacketLib.decode(ibcPacket.data);
uint256 packetTokensMetadataLength = packet.tokensMetadata.length;
for (uint256 i; i < packetTokensMetadataLength; i++) {
TokenMetadata calldata tokenMetadata = packet.tokensMetadata[i];
string memory denom = RelayLib.makeForeignDenom(
ibcPacket.destination_port,
ibcPacket.destination_channel,
tokenMetadata.denom
);
address denomAddress =
denomToAddress[ibcPacket.destination_channel][denom];
if (denomAddress == address(0)) {
revert RelayLib.ErrDenomNotFound();
}
IERC20Denom(denomAddress).update(
tokenMetadata.name, tokenMetadata.symbol, tokenMetadata.decimals
);
}
}

function onRecvRelayPacket(IbcCoreChannelV1Packet.Data calldata ibcPacket)
internal
{
RelayPacket calldata packet = RelayPacketLib.decode(ibcPacket.data);
string memory prefix = RelayLib.makeDenomPrefix(
ibcPacket.source_port, ibcPacket.source_channel
Expand Down Expand Up @@ -399,7 +535,7 @@ contract UCS01Relay is
denomAddress =
denomToAddress[ibcPacket.destination_channel][denom];
if (denomAddress == address(0)) {
denomAddress = address(new ERC20Denom(token.denom));
denomAddress = address(new ERC20Denom(denom));
denomToAddress[ibcPacket.destination_channel][denom] =
denomAddress;
addressToDenom[ibcPacket.destination_channel][denomAddress]
Expand All @@ -425,6 +561,23 @@ contract UCS01Relay is
}
}

function onRecvPacketProcessing(
IbcCoreChannelV1Packet.Data calldata ibcPacket,
address
) public {
if (msg.sender != address(this)) {
revert RelayLib.ErrUnauthorized();
}
uint8 tag = abi.decode(ibcPacket.data, (uint8));
if (tag == RelayLib.PACKET_TAG_RELAY) {
onRecvRelayPacket(ibcPacket);
} else if (tag == RelayLib.PACKET_TAG_UPDATE_METADATA) {
onRecvUpdateMetadataPacket(ibcPacket);
} else {
revert RelayLib.ErrUnknownPacketTag();
}
}

function onRecvPacket(
IbcCoreChannelV1Packet.Data calldata ibcPacket,
address relayer
Expand Down Expand Up @@ -463,25 +616,31 @@ contract UCS01Relay is
) {
revert RelayLib.ErrInvalidAcknowledgement();
}
// Counterparty failed to execute the transfer, we refund.
if (acknowledgement[0] == RelayLib.ACK_FAILURE) {
refundTokens(
ibcPacket.sequence,
ibcPacket.source_channel,
RelayPacketLib.decode(ibcPacket.data)
);
uint8 tag = abi.decode(ibcPacket.data, (uint8));
// Counterparty failed to execute the transfer, we refund.
if (tag == RelayLib.PACKET_TAG_RELAY) {
refundTokens(
ibcPacket.sequence,
ibcPacket.source_channel,
RelayPacketLib.decode(ibcPacket.data)
);
}
}
}

function onTimeoutPacket(
IbcCoreChannelV1Packet.Data calldata ibcPacket,
address
) external override(IBCAppBase, IIBCModule) onlyIBC {
refundTokens(
ibcPacket.sequence,
ibcPacket.source_channel,
RelayPacketLib.decode(ibcPacket.data)
);
uint8 tag = abi.decode(ibcPacket.data, (uint8));
if (tag == RelayLib.PACKET_TAG_RELAY) {
refundTokens(
ibcPacket.sequence,
ibcPacket.source_channel,
RelayPacketLib.decode(ibcPacket.data)
);
}
}

function onChanOpenInit(
Expand Down
Loading

0 comments on commit 021ec8a

Please sign in to comment.