Skip to content

Commit

Permalink
Problem: not possible to cancel SendToEthereum transactions (fixes #389
Browse files Browse the repository at this point in the history
…) (#532)
  • Loading branch information
thomas-nguy authored Jun 9, 2022
1 parent c90f4f5 commit 2ae1533
Show file tree
Hide file tree
Showing 17 changed files with 1,323 additions and 95 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,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.

### Bug Fixes

Expand Down
3 changes: 2 additions & 1 deletion app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -488,7 +488,8 @@ func New(

app.EvmKeeper.SetHooks(cronoskeeper.NewLogProcessEvmHook(
cronoskeeper.NewSendToAccountHandler(app.BankKeeper, app.CronosKeeper),
cronoskeeper.NewSendToEthereumHandler(gravitySrv, app.CronosKeeper),
cronoskeeper.NewSendToChainHandler(gravitySrv, app.BankKeeper, app.CronosKeeper),
cronoskeeper.NewCancelSendToChainHandler(gravitySrv, app.CronosKeeper, app.GravityKeeper),
cronoskeeper.NewSendToIbcHandler(app.BankKeeper, app.CronosKeeper),
cronoskeeper.NewSendCroToIbcHandler(app.BankKeeper, app.CronosKeeper),
))
Expand Down
6 changes: 2 additions & 4 deletions client/docs/statik/statik.go

Large diffs are not rendered by default.

34 changes: 34 additions & 0 deletions contracts/src/ModuleCRC21.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
pragma solidity ^0.6.8;

import "./ModuleCRC20.sol";

contract ModuleCRC21 is ModuleCRC20 {

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 {
decimals = decimals_;
denom = denom_;
}

// 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);
}

// 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));
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);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
pragma solidity ^0.6.6;

contract CronosGravityCancellation {

event __CronosCancelSendToChain(address sender, uint256 id);

// Cancel a send to chain transaction considering if it hasnt been batched yet.
function cancelTransaction(uint256 id) public {
emit __CronosCancelSendToChain(msg.sender, id);
}
}
148 changes: 121 additions & 27 deletions integration_tests/test_gravity.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@
Account.enable_unaudited_hdwallet_features()


def cronos_crc20_abi():
path = CONTRACTS["ModuleCRC20"]
def cronos_crc21_abi():
path = CONTRACTS["ModuleCRC21"]
return json.load(path.open())["abi"]


Expand Down Expand Up @@ -210,33 +210,31 @@ def test_gravity_transfer(gravity):

denom = f"gravity{erc20.address}"

crc20_contract = None
crc21_contract = None

def check_auto_deployment():
"check crc20 contract auto deployed, and the crc20 balance"
nonlocal crc20_contract
nonlocal crc21_contract
try:
rsp = cli.query_contract_by_denom(denom)
except AssertionError:
# not deployed yet
return False
assert len(rsp["auto_contract"]) > 0
crc20_contract = cronos_w3.eth.contract(
address=rsp["auto_contract"], abi=cronos_crc20_abi()
crc21_contract = cronos_w3.eth.contract(
address=rsp["auto_contract"], abi=cronos_crc21_abi()
)
return crc20_contract.caller.balanceOf(recipient) == amount
return crc21_contract.caller.balanceOf(recipient) == amount

def check_gravity_native_tokens():
"check the balance of gravity native token"
return cli.balance(eth_to_bech32(recipient), denom=denom) == amount

def get_id_from_receipt(receipt):
"check the id after sendToEthereum call"
"check the id after sendToChain call"
for _, log in enumerate(receipt.logs):
if log.topics[0] == HexBytes(
abi.event_signature_to_log_topic(
"__CronosSendToEthereumResponse(uint256)"
)
abi.event_signature_to_log_topic("__CronosSendToChainResponse(uint256)")
):
return log.data
return "0x0000000000000000000000000000000000000000000000000000000000000000"
Expand All @@ -245,14 +243,14 @@ def get_id_from_receipt(receipt):
wait_for_fn("send-to-crc20", check_auto_deployment)

# send it back to erc20
tx = crc20_contract.functions.send_to_ethereum(
ADDRS["validator"], amount, 0
tx = crc21_contract.functions.send_to_chain(
ADDRS["validator"], amount, 0, 1
).buildTransaction({"from": ADDRS["community"]})
txreceipt = send_transaction(cronos_w3, tx, KEYS["community"])
# CRC20 emit 3 logs for send_to_ethereum:
# CRC20 emit 3 logs for send_to_chain:
# burn
# __CronosSendToEthereum
# __CronosSendToEthereumResponse
# __CronosSendToChain
# __CronosSendToChainResponse
assert len(txreceipt.logs) == 3
assert (
get_id_from_receipt(txreceipt)
Expand Down Expand Up @@ -292,19 +290,19 @@ def test_gov_token_mapping(gravity):
CONTRACTS["TestERC20A"],
)
print("erc20 contract", erc20.address)
crc20 = deploy_contract(
crc21 = deploy_contract(
cronos_w3,
CONTRACTS["TestERC20Utility"],
)
print("crc20 contract", crc20.address)
print("crc21 contract", crc21.address)
denom = f"gravity{erc20.address}"

print("check the contract mapping not exists yet")
with pytest.raises(AssertionError):
cli.query_contract_by_denom(denom)

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

Expand Down Expand Up @@ -333,7 +331,7 @@ def test_gov_token_mapping(gravity):
print("check the contract mapping exists now")
rsp = cli.query_contract_by_denom(denom)
print("contract", rsp)
assert rsp["contract"] == crc20.address
assert rsp["contract"] == crc21.address

print("try to send token from ethereum to cronos")
txreceipt = send_to_cosmos(
Expand All @@ -342,7 +340,7 @@ def test_gov_token_mapping(gravity):
assert txreceipt.status == 1

def check():
balance = crc20.caller.balanceOf(ADDRS["community"])
balance = crc21.caller.balanceOf(ADDRS["community"])
print("crc20 balance", balance)
return balance == 10

Expand All @@ -367,28 +365,28 @@ def test_direct_token_mapping(gravity):
CONTRACTS["TestERC20A"],
)
print("erc20 contract", erc20.address)
crc20 = deploy_contract(
crc21 = deploy_contract(
cronos_w3,
CONTRACTS["TestERC20Utility"],
)
print("crc20 contract", crc20.address)
print("crc21 contract", crc21.address)
denom = f"gravity{erc20.address}"

print("check the contract mapping not exists yet")
with pytest.raises(AssertionError):
cli.query_contract_by_denom(denom)

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

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

print("check the contract mapping exists now")
rsp = cli.query_contract_by_denom(denom)
print("contract", rsp)
assert rsp["contract"] == crc20.address
assert rsp["contract"] == crc21.address

print("try to send token from ethereum to cronos")
txreceipt = send_to_cosmos(
Expand All @@ -397,8 +395,104 @@ def test_direct_token_mapping(gravity):
assert txreceipt.status == 1

def check():
balance = crc20.caller.balanceOf(ADDRS["community"])
balance = crc21.caller.balanceOf(ADDRS["community"])
print("crc20 balance", balance)
return balance == 10

wait_for_fn("check balance on cronos", check)


def test_gravity_cancel_transfer(gravity):
if gravity.cronos.enable_auto_deployment:
geth = gravity.geth
cli = gravity.cronos.cosmos_cli()
cronos_w3 = gravity.cronos.w3

# deploy test erc20 contract
erc20 = deploy_contract(
geth,
CONTRACTS["TestERC20A"],
)

# deploy gravity cancellation contract
cancel_contract = deploy_contract(
cronos_w3,
CONTRACTS["CronosGravityCancellation"],
)

balance = erc20.caller.balanceOf(ADDRS["validator"])
assert balance == 100000000000000000000000000
amount = 1000

print("send to cronos crc21")
recipient = HexBytes(ADDRS["community"])
send_to_cosmos(gravity.contract, erc20, recipient, amount, KEYS["validator"])
assert erc20.caller.balanceOf(ADDRS["validator"]) == balance - amount

denom = f"gravity{erc20.address}"

crc21_contract = None

def check_auto_deployment():
"check crc21 contract auto deployed, and the crc21 balance"
nonlocal crc21_contract
try:
rsp = cli.query_contract_by_denom(denom)
except AssertionError:
# not deployed yet
return False
assert len(rsp["auto_contract"]) > 0
crc21_contract = cronos_w3.eth.contract(
address=rsp["auto_contract"], abi=cronos_crc21_abi()
)
return crc21_contract.caller.balanceOf(recipient) == amount

def get_id_from_receipt(receipt):
"check the id after sendToChain call"
for _, log in enumerate(receipt.logs):
if log.topics[0] == HexBytes(
abi.event_signature_to_log_topic(
"__CronosSendToChainResponse(uint256)"
)
):
return log.data
return "0x0000000000000000000000000000000000000000000000000000000000000000"

wait_for_fn("send-to-crc20", check_auto_deployment)

def check_fund():
v = crc21_contract.caller.balanceOf(ADDRS["community"])
return v == amount

wait_for_fn("send-to-ethereum", check_fund)

# send it back to erc20
tx = crc21_contract.functions.send_to_chain(
ADDRS["validator"], amount, 0, 1
).buildTransaction({"from": ADDRS["community"]})
txreceipt = send_transaction(cronos_w3, tx, KEYS["community"])
# CRC20 emit 3 logs for send_to_chain:
# burn
# __CronosSendToChain
# __CronosSendToChainResponse
assert len(txreceipt.logs) == 3
tx_id = get_id_from_receipt(txreceipt)
assert txreceipt.status == 1, "should success"

# Check_deduction
balance_after_send = crc21_contract.caller.balanceOf(ADDRS["community"])
assert balance_after_send == 0

# Cancel the send_to_chain
canceltx = cancel_contract.functions.cancelTransaction(
int(tx_id, base=16)
).buildTransaction({"from": ADDRS["community"]})
canceltxreceipt = send_transaction(cronos_w3, canceltx, KEYS["community"])
print("canceltxreceipt", canceltxreceipt)
assert canceltxreceipt.status == 1, "should success"

def check_refund():
v = crc21_contract.caller.balanceOf(ADDRS["community"])
return v == amount

wait_for_fn("cancel-send-to-ethereum", check_refund)
3 changes: 3 additions & 0 deletions integration_tests/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"TestERC20Utility": "TestERC20Utility.sol",
"TestMessageCall": "TestMessageCall.sol",
"CroBridge": "CroBridge.sol",
"CronosGravityCancellation": "CronosGravityCancellation.sol",
}


Expand All @@ -55,6 +56,8 @@ def contract_path(name, filename):
CONTRACTS = {
"ModuleCRC20": Path(__file__).parent.parent
/ "x/cronos/types/contracts/ModuleCRC20.json",
"ModuleCRC21": Path(__file__).parent.parent
/ "x/cronos/types/contracts/ModuleCRC21.json",
**{
name: contract_path(name, filename) for name, filename in TEST_CONTRACTS.items()
},
Expand Down
2 changes: 2 additions & 0 deletions scripts/cronos-experimental-devnet.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ cronos_777-1:
block:
max_bytes: "1048576"
max_gas: "81500000"
# increase block time to reduce the likelihood that tx is immediately included in batch for integration tests and making the cancellation impossible
time_iota_ms: "2000"
app_state:
evm:
params:
Expand Down
4 changes: 4 additions & 0 deletions scripts/gen-cronos-contracts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@ cat contracts/out/dapp.sol.json | \
jq '.contracts."src/ModuleCRC20.sol".ModuleCRC20' | \
jq '{abi, bin: .evm.bytecode.object}' \
> x/cronos/types/contracts/ModuleCRC20.json
cat contracts/out/dapp.sol.json | \
jq '.contracts."src/ModuleCRC21.sol".ModuleCRC21' | \
jq '{abi, bin: .evm.bytecode.object}' \
> x/cronos/types/contracts/ModuleCRC21.json
10 changes: 5 additions & 5 deletions x/cronos/keeper/evm.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,13 +54,13 @@ func (k Keeper) CallModuleCRC20(ctx sdk.Context, contract common.Address, method
return res.Ret, nil
}

// DeployModuleCRC20 deploy an embed erc20 contract
func (k Keeper) DeployModuleCRC20(ctx sdk.Context, denom string) (common.Address, error) {
ctor, err := types.ModuleCRC20Contract.ABI.Pack("", denom, uint8(0))
// DeployModuleCRC21 deploy an embed crc21 contract
func (k Keeper) DeployModuleCRC21(ctx sdk.Context, denom string) (common.Address, error) {
ctor, err := types.ModuleCRC21Contract.ABI.Pack("", denom, uint8(0))
if err != nil {
return common.Address{}, err
}
data := types.ModuleCRC20Contract.Bin
data := types.ModuleCRC21Contract.Bin
data = append(data, ctor...)

msg, res, err := k.CallEVM(ctx, nil, data, big.NewInt(0))
Expand All @@ -87,7 +87,7 @@ func (k Keeper) ConvertCoinFromNativeToCRC20(ctx sdk.Context, sender common.Addr
if !autoDeploy {
return fmt.Errorf("no contract found for the denom %s", coin.Denom)
}
contract, err = k.DeployModuleCRC20(ctx, coin.Denom)
contract, err = k.DeployModuleCRC21(ctx, coin.Denom)
if err != nil {
return err
}
Expand Down
Loading

0 comments on commit 2ae1533

Please sign in to comment.