Skip to content

Commit

Permalink
Problem: bi-directional token conversion is not implemented (#600)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomas-nguy authored Aug 2, 2022
1 parent 85163e4 commit ca819e5
Show file tree
Hide file tree
Showing 35 changed files with 1,008 additions and 254 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### State Machine Breaking
- [cronos#429](https://github.com/crypto-org-chain/cronos/pull/429) Update ethermint to main, ibc-go to v3.0.0, cosmos sdk to v0.45.4 and gravity to latest, remove v0.7.0 related upgradeHandler.
- [cronos#532](https://github.com/crypto-org-chain/cronos/pull/532) Add SendtoChain and CancelSendToChain support from evm call.
- [cronos#600](https://github.com/crypto-org-chain/cronos/pull/600) Implement bidirectional token mapping.

### Bug Fixes

Expand Down
94 changes: 82 additions & 12 deletions contracts/src/ModuleCRC21.sol
Original file line number Diff line number Diff line change
@@ -1,34 +1,104 @@
pragma solidity ^0.6.8;
pragma solidity ^0.6.1;

import "./ModuleCRC20.sol";
import "ds-token/token.sol";

contract ModuleCRC21 is ModuleCRC20 {
contract ModuleCRC21 is DSToken {
// sha256('cronos-evm')[:20]
address constant module_address = 0x89A7EF2F08B1c018D5Cc88836249b84Dd5392905;
string denom;
bool isSource;

event __CronosSendToIbc(address sender, string recipient, uint256 amount);
event __CronosSendToChain(address sender, address recipient, uint256 amount, uint256 bridge_fee, uint256 chain_id);
event __CronosCancelSendToChain(address sender, uint256 id);

constructor(string memory denom_, uint8 decimals_) ModuleCRC20(denom_, decimals_) public {
constructor(string memory denom_, uint8 decimals_, bool isSource_) DSToken(denom_) public {
decimals = decimals_;
denom = denom_;
isSource = isSource_;
}

// make unsafe_burn internal
function unsafe_burn_internal(address addr, uint amount) internal {
// Deduct user's balance without approval
require(balanceOf[addr] >= amount, "ds-token-insufficient-balance");
balanceOf[addr] = sub(balanceOf[addr], amount);
totalSupply = sub(totalSupply, amount);
emit Burn(addr, amount);
/**
views
**/
function native_denom() public view returns (string memory) {
return denom;
}

function is_source() public view returns (bool) {
return isSource;
}


/**
Internal functions to be called by cronos module
**/
function mint_by_cronos_module(address addr, uint amount) public {
require(msg.sender == module_address);
mint(addr, amount);
}

function burn_by_cronos_module(address addr, uint amount) public {
require(msg.sender == module_address);
unsafe_burn(addr, amount);
}

function transfer_by_cronos_module(address addr, uint amount) public {
require(msg.sender == module_address);
unsafe_transfer(addr, module_address, amount);
}

function transfer_from_cronos_module(address addr, uint amount) public {
transferFrom(module_address, addr, amount);
}

/**
Evm hooks functions
**/

// send an "amount" of the contract token to recipient through IBC
function send_to_ibc(string memory recipient, uint amount) public {
if (isSource) {
transferFrom(msg.sender, module_address, amount);
} else {
unsafe_burn(msg.sender, amount);
}
emit __CronosSendToIbc(msg.sender, recipient, amount);
}

// send to another chain through gravity bridge
function send_to_chain(address recipient, uint amount, uint bridge_fee, uint chain_id) external {
unsafe_burn_internal(msg.sender, add(amount, bridge_fee));
if (isSource) {
transferFrom(msg.sender, module_address, add(amount, bridge_fee));
} else {
unsafe_burn(msg.sender, add(amount, bridge_fee));
}
emit __CronosSendToChain(msg.sender, recipient, amount, bridge_fee, chain_id);
}

// cancel a send to chain transaction considering if it hasnt been batched yet.
function cancel_send_to_chain(uint256 id) external {
emit __CronosCancelSendToChain(msg.sender, id);
}

/**
Internal functions
**/

// unsafe_burn burn tokens without user's approval and authentication, used internally
function unsafe_burn(address addr, uint amount) internal {
// Deduct user's balance without approval
require(balanceOf[addr] >= amount, "ds-token-insufficient-balance");
balanceOf[addr] = sub(balanceOf[addr], amount);
totalSupply = sub(totalSupply, amount);
emit Burn(addr, amount);
}

// unsafe_transfer transfer tokens without user's approval and authentication, used internally
function unsafe_transfer(address src, address dst, uint amount) internal {
require(balanceOf[src] >= amount, "ds-token-insufficient-balance");
balanceOf[src] = sub(balanceOf[src], amount);
balanceOf[dst] = add(balanceOf[dst], amount);
emit Transfer(src, dst, amount);
}
}
4 changes: 4 additions & 0 deletions docs/api/proto-docs.md
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ TokenMappingChangeProposal defines a proposal to change one token mapping.
| `description` | [string](#string) | | |
| `denom` | [string](#string) | | |
| `contract` | [string](#string) | | |
| `symbol` | [string](#string) | | only when updating cronos (source) tokens |
| `decimal` | [uint32](#uint32) | | |



Expand Down Expand Up @@ -352,6 +354,8 @@ MsgUpdateTokenMapping defines the request type
| `sender` | [string](#string) | | |
| `denom` | [string](#string) | | |
| `contract` | [string](#string) | | |
| `symbol` | [string](#string) | | only when updating cronos (source) tokens |
| `decimal` | [uint32](#uint32) | | |



Expand Down
5 changes: 3 additions & 2 deletions docs/architecture/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,13 @@ To suggest an ADR, please make use of the [ADR template](./adr-template.md) prov

## Table of Contents

| ADR \# | Description | Status |
| ------ | ----------- | ------ |
| ADR \# | Description | Status |
|---------------------| ----------- |----------|
| [001](./adr-001.md) | Disable Gravity Bridge at Genesis | Accepted |
| [002](./adr-002.md) | Use a custom fork of ibc-go | Accepted |
| [003](./adr-003.md) | Add Fee Market Module | Accepted |
| [004](./adr-004.md) | Tokens conversion in Cronos | Accepted |
| [005](./adr-005.md) | Cross-chain Validation for Gravity Bridge | Rejected |
| [006](./adr-006.md) | Migrating CRC20 contract to CRC21 standard | Rejected |
| [007](./adr-007.md) | Generic event format for evm-hook actions | Proposed |
| [008](./adr-008.md) | Denom and Contract Mapping Enhancement for Bi-Directional Token Conversion | Accepted |
9 changes: 5 additions & 4 deletions docs/architecture/adr-008.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## Changelog
* 2022-06-15: first draft
* 2022-07-20: change status to accepted

## Context

Expand Down Expand Up @@ -102,10 +103,10 @@ Bridge hooks are to be updated according. Upon receiving bridge transfer in, the

The denom naming of the native token with Cronos EVM as source is
```
evm0x{smartContractAddress}
cronos0x{smartContractAddress}
```

The chain must avoid naming any native asset with the prefix `evm0x` in the future.
The chain must avoid naming any native asset with the prefix `cronos0x` in the future.

### 4. Keeper Methods Changes

Expand All @@ -129,7 +130,7 @@ GetContractByDenom(ctx sdk.Context, denom string)

`source` is added in the returned value to denote the source of the denom to smart contract address mapping

##### ConvertCoinFromNativeToCRC20
##### ConvertCoinFromNativeToCRC21

In the current design, the method permits only the denom is one of the IBC and Gravity Bridge denoms before doing any conversion. In this ADR, we want to loosen this validation check. If such a requirement exists, it should be performed in the IBC hooks.

Expand All @@ -150,7 +151,7 @@ The working mechanism is as follows:

## Status

Proposed
Accepted

## Consequences

Expand Down
14 changes: 12 additions & 2 deletions integration_tests/cosmoscli.py
Original file line number Diff line number Diff line change
Expand Up @@ -992,7 +992,9 @@ def query_contract_by_denom(self, denom: str):
)
)

def gov_propose_token_mapping_change(self, denom, contract, **kwargs):
def gov_propose_token_mapping_change(
self, denom, contract, symbol, decimal, **kwargs
):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
return json.loads(
self.raw(
Expand All @@ -1002,13 +1004,17 @@ def gov_propose_token_mapping_change(self, denom, contract, **kwargs):
"token-mapping-change",
denom,
contract,
"--symbol",
symbol,
"--decimals",
decimal,
"-y",
home=self.data_dir,
**kwargs,
)
)

def update_token_mapping(self, denom, contract, **kwargs):
def update_token_mapping(self, denom, contract, symbol, decimals, **kwargs):
kwargs.setdefault("gas_prices", DEFAULT_GAS_PRICE)
return json.loads(
self.raw(
Expand All @@ -1017,6 +1023,10 @@ def update_token_mapping(self, denom, contract, **kwargs):
"update-token-mapping",
denom,
contract,
"--symbol",
symbol,
"--decimals",
decimals,
"-y",
home=self.data_dir,
**kwargs,
Expand Down
6 changes: 3 additions & 3 deletions integration_tests/test_gravity.py
Original file line number Diff line number Diff line change
Expand Up @@ -329,7 +329,7 @@ def test_gov_token_mapping(gravity):
cli.query_contract_by_denom(denom)

rsp = cli.gov_propose_token_mapping_change(
denom, crc21.address, from_="community", deposit="1basetcro"
denom, crc21.address, "", 0, from_="community", deposit="1basetcro"
)
assert rsp["code"] == 0, rsp["raw_log"]

Expand Down Expand Up @@ -403,10 +403,10 @@ def test_direct_token_mapping(gravity):
with pytest.raises(AssertionError):
cli.query_contract_by_denom(denom)

rsp = cli.update_token_mapping(denom, crc21.address, from_="community")
rsp = cli.update_token_mapping(denom, crc21.address, "", 0, from_="community")
assert rsp["code"] != 0, "should not have the permission"

rsp = cli.update_token_mapping(denom, crc21.address, from_="validator")
rsp = cli.update_token_mapping(denom, crc21.address, "", 0, from_="validator")
assert rsp["code"] == 0, rsp["raw_log"]
wait_for_new_blocks(cli, 1)

Expand Down
3 changes: 3 additions & 0 deletions proto/cronos/cronos.proto
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ message TokenMappingChangeProposal {
string description = 2;
string denom = 3;
string contract = 4;
// only when updating cronos (source) tokens
string symbol = 5;
uint32 decimal = 6;
}

// TokenMapping defines a mapping between native denom and contract
Expand Down
3 changes: 3 additions & 0 deletions proto/cronos/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ message MsgUpdateTokenMapping {
string sender = 1;
string denom = 2;
string contract = 3;
// only when updating cronos (source) tokens
string symbol = 4;
uint32 decimal = 5;
}

// MsgUpdateTokenMappingResponse defines the response type
Expand Down
52 changes: 50 additions & 2 deletions x/cronos/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ func CmdSendToCryptoOrg() *cobra.Command {
return cmd
}

// TokenMappingChangeProposalTxCmd flags
const (
FlagSymbol = "symbol"
FlagDecimals = "decimals"
)

// NewSubmitTokenMappingChangeProposalTxCmd returns a CLI command handler for creating
// a token mapping change proposal governance transaction.
func NewSubmitTokenMappingChangeProposalTxCmd() *cobra.Command {
Expand Down Expand Up @@ -145,8 +151,27 @@ $ %s tx gov submit-proposal token-mapping-change gravity0x0000...0000 0x0000...0
contract = &addr
}

denom := args[0]
if !types.IsValidCoinDenom(denom) {
return fmt.Errorf("invalid coin denom: %s", denom)
}

symbol := ""
decimal := uint(0)
if types.IsSourceCoin(denom) {
symbol, err = cmd.Flags().GetString(FlagSymbol)
if err != nil {
return err
}

decimal, err = cmd.Flags().GetUint(FlagDecimals)
if err != nil {
return err
}
}

content := types.NewTokenMappingChangeProposal(
title, description, args[0], contract,
title, description, args[0], symbol, uint32(decimal), contract,
)

from := clientCtx.GetFromAddress()
Expand All @@ -172,6 +197,8 @@ $ %s tx gov submit-proposal token-mapping-change gravity0x0000...0000 0x0000...0
cmd.Flags().String(govcli.FlagTitle, "", "The proposal title")
cmd.Flags().String(govcli.FlagDescription, "", "The proposal description")
cmd.Flags().String(govcli.FlagDeposit, "", "The proposal deposit")
cmd.Flags().String(FlagSymbol, "", "The coin symbol")
cmd.Flags().Uint(FlagDecimals, 0, "The coin decimal")

return cmd
}
Expand All @@ -188,7 +215,26 @@ func CmdUpdateTokenMapping() *cobra.Command {
return err
}

msg := types.NewMsgUpdateTokenMapping(clientCtx.GetFromAddress().String(), args[0], args[1])
denom := args[0]
if !types.IsValidCoinDenom(denom) {
return fmt.Errorf("invalid coin denom: %s", denom)
}

symbol := ""
decimal := uint(0)
if types.IsSourceCoin(denom) {
symbol, err = cmd.Flags().GetString(FlagSymbol)
if err != nil {
return err
}

decimal, err = cmd.Flags().GetUint(FlagDecimals)
if err != nil {
return err
}
}

msg := types.NewMsgUpdateTokenMapping(clientCtx.GetFromAddress().String(), denom, args[1], symbol, uint32(decimal))
if err := msg.ValidateBasic(); err != nil {
return err
}
Expand All @@ -197,6 +243,8 @@ func CmdUpdateTokenMapping() *cobra.Command {
}

flags.AddTxFlagsToCmd(cmd)
cmd.Flags().String(FlagSymbol, "", "The coin symbol")
cmd.Flags().Uint(FlagDecimals, 0, "The coin decimal")

return cmd
}
4 changes: 3 additions & 1 deletion x/cronos/client/rest/rest.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ type (
Description string `json:"description" yaml:"description"`
Denom string `json:"denom" yaml:"denom"`
Contract string `json:"contract" yaml:"contract"`
Symbol string `json:"symbol" yaml:"symbol"`
Decimal uint32 `json:"decimal" yaml:"decimal"`
Proposer sdk.AccAddress `json:"proposer" yaml:"proposer"`
Deposit sdk.Coins `json:"deposit" yaml:"deposit"`
}
Expand Down Expand Up @@ -55,7 +57,7 @@ func postProposalHandlerFn(clientCtx client.Context) http.HandlerFunc {
contract = &addr
}

content := types.NewTokenMappingChangeProposal(req.Title, req.Description, req.Denom, contract)
content := types.NewTokenMappingChangeProposal(req.Title, req.Description, req.Denom, req.Symbol, req.Decimal, contract)

msg, err := govtypes.NewMsgSubmitProposal(content, req.Deposit, req.Proposer)
if rest.CheckBadRequestError(w, err) {
Expand Down
Loading

0 comments on commit ca819e5

Please sign in to comment.