Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

chore: refactor using a unique symbol per token #115

Merged
merged 4 commits into from
Nov 22, 2023
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
131 changes: 67 additions & 64 deletions contracts/examples/ERC20Messaging.sol
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ contract ERC20Messaging is IERC20Messaging, ToposMessaging {
function deployToken(bytes calldata params) external {
(string memory name, string memory symbol, uint256 cap, uint256 dailyMintLimit, uint256 initialSupply) = abi
.decode(params, (string, string, uint256, uint256, uint256));

bytes32 salt = keccak256(abi.encodePacked(msg.sender, symbol));
// Note: this does not stop deployment of the same symbol to other subnets. Do not use in a production system.
bytes32 salt = keccak256(abi.encodePacked(symbol));
JDawg287 marked this conversation as resolved.
Show resolved Hide resolved
// Deploy the token contract
// The tx will revert if the token already exists because the salt will be the same
address tokenAddress = ITokenDeployer(_tokenDeployerAddr).deployToken(
Expand All @@ -55,30 +55,31 @@ contract ERC20Messaging is IERC20Messaging, ToposMessaging {
salt
);

_setTokenType(tokenAddress, TokenType.InternalBurnableFrom);
_setTokenType(symbol, TokenType.InternalBurnableFrom);
_setTokenAddress(symbol, tokenAddress);
_setTokenDailyMintLimit(dailyMintLimit, tokenAddress);
_setTokenDailyMintLimit(dailyMintLimit, symbol);

emit TokenDeployed(symbol, tokenAddress);
}

/// @notice Entry point for sending a cross-subnet asset transfer
/// @dev The input data is sent to the target subnet externally
/// @param targetSubnetId Target subnet ID
/// @param tokenAddress Address of target token contract
/// @param symbol Symbol of token
/// @param receiver Receiver's address
/// @param amount Amount of token to send
function sendToken(SubnetId targetSubnetId, address tokenAddress, address receiver, uint256 amount) external {
function sendToken(SubnetId targetSubnetId, string calldata symbol, address receiver, uint256 amount) external {
if (_toposCoreAddr.code.length == uint256(0)) revert InvalidToposCore();
_burnTokenFrom(msg.sender, tokenAddress, amount);
emit TokenSent(targetSubnetId, tokenAddress, receiver, amount);
Token memory token = getTokenBySymbol(symbol);
JDawg287 marked this conversation as resolved.
Show resolved Hide resolved
_burnTokenFrom(msg.sender, symbol, amount);
emit TokenSent(targetSubnetId, symbol, token.addr, receiver, amount);
_emitMessageSentEvent(targetSubnetId);
}

/// @notice Gets the token by address
/// @param tokenAddress Address of token contract
function getTokenByAddress(address tokenAddress) public view returns (Token memory token) {
bytes32 tokenKey = _getTokenKey(tokenAddress);
/// @notice Gets the token by symbol
/// @param symbol Symbol of token
function getTokenBySymbol(string calldata symbol) public view returns (Token memory token) {
bytes32 tokenKey = _getTokenKey(symbol);
token = tokens[tokenKey];
}

Expand All @@ -94,15 +95,15 @@ contract ERC20Messaging is IERC20Messaging, ToposMessaging {
}

/// @notice Get the token daily mint amount
/// @param tokenAddress Address of token contract
function tokenDailyMintAmount(address tokenAddress) public view returns (uint256) {
return getUint(_getTokenDailyMintAmountKey(tokenAddress, block.timestamp / 1 days));
/// @param symbol Symbol of token
function tokenDailyMintAmount(string memory symbol) public view returns (uint256) {
return getUint(_getTokenDailyMintAmountKey(symbol, block.timestamp / 1 days));
}

/// @notice Get the token daily mint limit
/// @param tokenAddress Address of token contract
function tokenDailyMintLimit(address tokenAddress) public view returns (uint256) {
return getUint(_getTokenDailyMintLimitKey(tokenAddress));
/// @param symbol Symbol of token
function tokenDailyMintLimit(string memory symbol) public view returns (uint256) {
return getUint(_getTokenDailyMintLimitKey(symbol));
}

/// @notice Get the address of token deployer contract
Expand Down Expand Up @@ -131,33 +132,34 @@ contract ERC20Messaging is IERC20Messaging, ToposMessaging {
bytes32 targetSubnetId = logsTopics[tokenSentEventIndex][1];
if (SubnetId.unwrap(networkSubnetId) != targetSubnetId) revert InvalidSubnetId();

(address tokenAddress, address receiver, uint256 amount) = abi.decode(
(string memory symbol, , address receiver, uint256 amount) = abi.decode(
logsData[tokenSentEventIndex],
(address, address, uint256)
(string, address, address, uint256)
);
_mintToken(tokenAddress, receiver, amount);
_mintToken(symbol, receiver, amount);
}

/// @notice Burn token internally
/// @param sender Account to burn token from
/// @param tokenAddress Address of target token contract
/// @param sender Sender of token
/// @param symbol Symbol of token
/// @param amount Amount of token to burn
function _burnTokenFrom(address sender, address tokenAddress, uint256 amount) internal {
bytes32 tokenKey = _getTokenKey(tokenAddress);
if (!tokenSet.exists(tokenKey)) revert TokenDoesNotExist(tokenAddress);
function _burnTokenFrom(address sender, string calldata symbol, uint256 amount) internal {
bytes32 tokenKey = _getTokenKey(symbol);
if (!tokenSet.exists(tokenKey)) revert TokenDoesNotExist(symbol);
if (amount == 0) revert InvalidAmount();

TokenType tokenType = _getTokenType(tokenAddress);
TokenType tokenType = _getTokenType(symbol);
bool burnSuccess;

if (tokenType == TokenType.External) {
revert UnsupportedTokenType();
} else {
Token memory token = tokens[tokenKey];
burnSuccess = _callERC20Token(
tokenAddress,
token.addr,
abi.encodeWithSelector(IBurnableMintableCappedERC20.burnFrom.selector, sender, amount)
);
if (!burnSuccess) revert BurnFailed(tokenAddress);
if (!burnSuccess) revert BurnFailed(symbol);
}
}

Expand All @@ -172,27 +174,28 @@ contract ERC20Messaging is IERC20Messaging, ToposMessaging {
}

/// @notice Mint token internally
/// @param tokenAddress Address of token contract
/// @param symbol Symbol of token
/// @param account Account to mint token to
/// @param amount Amount of token to mint
function _mintToken(address tokenAddress, address account, uint256 amount) internal {
bytes32 tokenKey = _getTokenKey(tokenAddress);
if (!tokenSet.exists(tokenKey)) revert TokenDoesNotExist(tokenAddress);
function _mintToken(string memory symbol, address account, uint256 amount) internal {
bytes32 tokenKey = _getTokenKey(symbol);
if (!tokenSet.exists(tokenKey)) revert TokenDoesNotExist(symbol);

_setTokenDailyMintAmount(tokenAddress, tokenDailyMintAmount(tokenAddress) + amount);
_setTokenDailyMintAmount(symbol, tokenDailyMintAmount(symbol) + amount);

if (_getTokenType(tokenAddress) == TokenType.External) {
if (_getTokenType(symbol) == TokenType.External) {
revert UnsupportedTokenType();
} else {
IBurnableMintableCappedERC20(tokenAddress).mint(account, amount);
Token memory token = tokens[tokenKey];
IBurnableMintableCappedERC20(token.addr).mint(account, amount);
}
}

/// @notice Store the token address for the specified symbol
/// @param symbol Symbol of token
/// @param tokenAddress Address of token contract
function _setTokenAddress(string memory symbol, address tokenAddress) internal {
bytes32 tokenKey = _getTokenKey(tokenAddress);
bytes32 tokenKey = _getTokenKey(symbol);
tokenSet.insert(tokenKey);
Token storage token = tokens[tokenKey];
token.symbol = symbol;
Expand All @@ -201,58 +204,58 @@ contract ERC20Messaging is IERC20Messaging, ToposMessaging {

/// @notice Set the token daily mint limit for a token address
/// @param limit Daily mint limit of token
/// @param tokenAddress Address of token contract
function _setTokenDailyMintLimit(uint256 limit, address tokenAddress) internal {
_setUint(_getTokenDailyMintLimitKey(tokenAddress), limit);
/// @param symbol Symbol of token
function _setTokenDailyMintLimit(uint256 limit, string memory symbol) internal {
_setUint(_getTokenDailyMintLimitKey(symbol), limit);

emit TokenDailyMintLimitUpdated(tokenAddress, limit);
emit TokenDailyMintLimitUpdated(symbol, limit);
}

/// @notice Set the token daily mint amount for a token address
/// @param tokenAddress Address of token contract
/// @param symbol Symbol of token
/// @param amount Daily mint amount of token
function _setTokenDailyMintAmount(address tokenAddress, uint256 amount) internal {
uint256 limit = tokenDailyMintLimit(tokenAddress);
if (limit > 0 && amount > limit) revert ExceedDailyMintLimit(tokenAddress);
function _setTokenDailyMintAmount(string memory symbol, uint256 amount) internal {
uint256 limit = tokenDailyMintLimit(symbol);
if (limit > 0 && amount > limit) revert ExceedDailyMintLimit(symbol);

_setUint(_getTokenDailyMintAmountKey(tokenAddress, block.timestamp / 1 days), amount);
_setUint(_getTokenDailyMintAmountKey(symbol, block.timestamp / 1 days), amount);
}

/// @notice Set the token type for a token address
/// @param tokenAddress Address of token contract
/// @param symbol Symbol of token
/// @param tokenType Type of token (external/internal)
function _setTokenType(address tokenAddress, TokenType tokenType) internal {
_setUint(_getTokenTypeKey(tokenAddress), uint256(tokenType));
function _setTokenType(string memory symbol, TokenType tokenType) internal {
_setUint(_getTokenTypeKey(symbol), uint256(tokenType));
}

/// @notice Get the token type for a token address
/// @param tokenAddress Address of token contract
function _getTokenType(address tokenAddress) internal view returns (TokenType) {
return TokenType(getUint(_getTokenTypeKey(tokenAddress)));
/// @param symbol Symbol of token
function _getTokenType(string memory symbol) internal view returns (TokenType) {
return TokenType(getUint(_getTokenTypeKey(symbol)));
}

/// @notice Get the key for the token daily mint limit
/// @param tokenAddress Address of token contract
function _getTokenDailyMintLimitKey(address tokenAddress) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_DAILY_MINT_LIMIT, tokenAddress));
/// @param symbol Symbol of token
function _getTokenDailyMintLimitKey(string memory symbol) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_DAILY_MINT_LIMIT, symbol));
}

/// @notice Get the key for the token daily mint amount
/// @param tokenAddress Address of token contract
/// @param symbol Symbol of token
/// @param day Day of token daily mint amount
function _getTokenDailyMintAmountKey(address tokenAddress, uint256 day) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_DAILY_MINT_AMOUNT, tokenAddress, day));
function _getTokenDailyMintAmountKey(string memory symbol, uint256 day) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_DAILY_MINT_AMOUNT, symbol, day));
}

/// @notice Get the key for the token type
/// @param tokenAddress Address of token contract
function _getTokenTypeKey(address tokenAddress) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_TYPE, tokenAddress));
/// @param symbol Symbol of token
function _getTokenTypeKey(string memory symbol) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_TYPE, symbol));
}

/// @notice Get the key for the token
/// @param tokenAddress Address of token contract
function _getTokenKey(address tokenAddress) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_KEY, tokenAddress));
/// @param symbol Symbol of token
function _getTokenKey(string memory symbol) internal pure returns (bytes32) {
return keccak256(abi.encodePacked(PREFIX_TOKEN_KEY, symbol));
}
}
26 changes: 16 additions & 10 deletions contracts/interfaces/IERC20Messaging.sol
Original file line number Diff line number Diff line change
Expand Up @@ -14,38 +14,44 @@ interface IERC20Messaging is IToposMessaging {
External // Not supported yet
}

event TokenDailyMintLimitUpdated(address tokenAddress, uint256 limit);
event TokenDailyMintLimitUpdated(string symbol, uint256 limit);

event TokenDeployed(string symbol, address tokenAddress);

event TokenSent(SubnetId indexed targetSubnetId, address tokenAddress, address receiver, uint256 amount);
event TokenSent(
SubnetId indexed targetSubnetId,
string symbol,
address tokenAddress,
address receiver,
uint256 amount
);

error BurnFailed(address tokenAddress);
error ExceedDailyMintLimit(address tokenAddress);
error BurnFailed(string symbol);
error ExceedDailyMintLimit(string symbol);
error InvalidAmount();
error InvalidOriginAddress();
error InvalidSetDailyMintLimitsParams();
error InvalidSubnetId();
error InvalidTokenDeployer();
error TokenAlreadyExists(address tokenAddress);
error TokenDoesNotExist(address tokenAddress);
error TokenAlreadyExists(string symbol);
error TokenDoesNotExist(string symbol);
error UnsupportedTokenType();

function deployToken(bytes calldata params) external;

function sendToken(SubnetId targetSubnetId, address tokenAddress, address receiver, uint256 amount) external;
function sendToken(SubnetId targetSubnetId, string calldata symbol, address receiver, uint256 amount) external;

function getTokenByAddress(address tokenAddress) external view returns (Token memory token);
function getTokenBySymbol(string calldata symbol) external view returns (Token memory token);

function getTokenCount() external view returns (uint256);

function getTokenKeyAtIndex(uint256 index) external view returns (bytes32);

function tokens(bytes32 tokenKey) external view returns (string memory, address);

function tokenDailyMintAmount(address tokenAddress) external view returns (uint256);
function tokenDailyMintAmount(string memory symbol) external view returns (uint256);

function tokenDailyMintLimit(address tokenAddress) external view returns (uint256);
function tokenDailyMintLimit(string memory symbol) external view returns (uint256);

function tokenDeployer() external view returns (address);
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@topos-protocol/topos-smart-contracts",
"version": "2.0.0",
"version": "3.0.0-rc1",
"description": "Topos Smart Contracts",
"repository": {
"type": "git",
Expand Down
Loading
Loading