Skip to content
Closed
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
59 changes: 59 additions & 0 deletions contracts/ERC20WithMint.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

interface IERC20 {
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);

function totalSupply() external view returns (uint256);
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
function allowance(address owner, address spender) external view returns (uint256);
function approve(address spender, uint256 value) external returns (bool);
function transferFrom(address from, address to, uint256 value) external returns (bool);
}

contract ERC20WithMint is IERC20 {
string public name = "TestToken";
string public symbol = "TEST";
uint8 public decimals = 18;
uint256 private _totalSupply;

mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;

constructor() {
// Mint 1 million tokens to deployer
_totalSupply = 1000000 * 10 ** 18;
balanceOf[msg.sender] = _totalSupply;
emit Transfer(address(0), msg.sender, _totalSupply);
}

function totalSupply() external view override returns (uint256) {
return _totalSupply;
}

function transfer(address to, uint256 value) external override returns (bool) {
require(balanceOf[msg.sender] >= value, "ERC20: transfer amount exceeds balance");
balanceOf[msg.sender] -= value;
balanceOf[to] += value;
emit Transfer(msg.sender, to, value);
return true;
}

function approve(address spender, uint256 value) external override returns (bool) {
allowance[msg.sender][spender] = value;
emit Approval(msg.sender, spender, value);
return true;
}

function transferFrom(address from, address to, uint256 value) external override returns (bool) {
require(balanceOf[from] >= value, "ERC20: transfer amount exceeds balance");
require(allowance[from][msg.sender] >= value, "ERC20: transfer amount exceeds allowance");
balanceOf[from] -= value;
balanceOf[to] += value;
allowance[from][msg.sender] -= value;
emit Transfer(from, to, value);
return true;
}
}
34 changes: 34 additions & 0 deletions contracts/EVMCaller.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

// Interface for Wasmd Precompile
interface IWasmd {
function execute(
string memory contractAddress,
bytes memory msg,
bytes memory coins
) external returns (bytes memory);
}

contract EVMCaller {
address constant WASMD_PRECOMPILE = 0x0000000000000000000000000000000000001002;

event CalledCW(string contractAddress, bytes response);

// This function calls a CosmWasm contract via the wasmd precompile
// The CosmWasm contract will then try to call an EVM contract
// This should trigger: EVM -> CW -> EVM error
function callCosmWasm(
string memory cwContractAddress,
bytes memory executeMsg
) external returns (bytes memory) {
IWasmd wasmd = IWasmd(WASMD_PRECOMPILE);

// Call the CosmWasm contract with empty coins
bytes memory emptyCoins = abi.encodePacked("[]");
bytes memory response = wasmd.execute(cwContractAddress, executeMsg, emptyCoins);

emit CalledCW(cwContractAddress, response);
return response;
}
}
9 changes: 5 additions & 4 deletions example/cosmwasm/cw20/src/contract.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,7 @@ pub fn execute_increase_allowance(

// Send the message to approve the new amount
let payload = querier.erc20_approve_payload(spender.clone(), new_allowance)?;
let msg = EvmMsg::DelegateCallEvm { to: erc_addr, data: payload.encoded_payload };
let msg = EvmMsg::CallEvm { to: erc_addr, data: payload.encoded_payload, value: "0".to_string() };

let res = Response::new()
.add_attribute("action", "increase_allowance")
Expand Down Expand Up @@ -175,7 +175,7 @@ pub fn execute_decrease_allowance(

// Send the message to approve the new amount.
let payload = querier.erc20_approve_payload(spender.clone(), new_allowance)?;
let msg = EvmMsg::DelegateCallEvm { to: erc_addr, data: payload.encoded_payload };
let msg = EvmMsg::CallEvm { to: erc_addr, data: payload.encoded_payload, value: "0".to_string() };

let res = Response::new()
.add_attribute("action", "decrease_allowance")
Expand Down Expand Up @@ -237,7 +237,8 @@ fn transfer(

let querier = EvmQuerier::new(&deps.querier);
let payload = querier.erc20_transfer_payload(recipient.clone(), amount)?;
let msg = EvmMsg::DelegateCallEvm { to: erc_addr, data: payload.encoded_payload };
// Use CallEvm instead of DelegateCallEvm to trigger EVM->CW->EVM error at line 78-79
let msg = EvmMsg::CallEvm { to: erc_addr, data: payload.encoded_payload, value: "0".to_string() };
let res = Response::new()
.add_attribute("from", info.sender)
.add_attribute("to", recipient)
Expand All @@ -262,7 +263,7 @@ pub fn transfer_from(

let querier = EvmQuerier::new(&deps.querier);
let payload = querier.erc20_transfer_from_payload(owner.clone(), recipient.clone(), amount)?;
let msg = EvmMsg::DelegateCallEvm { to: erc_addr, data: payload.encoded_payload };
let msg = EvmMsg::CallEvm { to: erc_addr, data: payload.encoded_payload, value: "0".to_string() };
let res = Response::new()
.add_attribute("from", owner)
.add_attribute("to", recipient)
Expand Down
5 changes: 5 additions & 0 deletions example/cosmwasm/cw20/src/msg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,11 @@ impl From<EvmMsg> for CosmosMsg<EvmMsg> {
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)]
#[serde(rename_all = "snake_case")]
pub enum EvmMsg {
CallEvm {
to: String,
data: String, // base64 encoded
value: String, // amount to send
},
DelegateCallEvm {
to: String,
data: String, // base64 encoded
Expand Down
127 changes: 127 additions & 0 deletions scripts/README_EVM_CW_EVM.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# EVM->CW->EVM Call Pattern Error Reproduction

This directory contains a script to reproduce the `EVM->CW->EVM call pattern` error.

## Error Location
- **File**: `x/evm/keeper/evm.go`
- **Line**: 78
- **Error**: "sei does not support EVM->CW->EVM call pattern"

## The Pattern

The error occurs when:
1. An EVM contract calls a CosmWasm contract via the Wasmd precompile (EVM -> CW)
2. The CosmWasm contract attempts to call back to an EVM contract (CW -> EVM)
3. This creates an EVM->CW->EVM call pattern which is not supported

## The Correct Setup: CW20->ERC20 Pointer

To trigger this error, you need a **CW20->ERC20 pointer** (not ERC20->CW20). Here's why:

### Pointer Direction Matters
- **CW20->ERC20 Pointer**: An ERC20 contract that points to a CW20 contract
- Calls from EVM to this pointer use `delegatecall` to the Wasmd precompile
- If the CW20 tries to call back to EVM → **triggers the error**

- **ERC20->CW20 Pointer**: A CW20 contract that points to an ERC20 contract
- This pattern is different and doesn't trigger the same error

## How It Works

The script `reproduce_evm_cw_evm_error.sh` does the following:

### Step 1: Deploy ERC20 Token
Deploys an ERC20 token that the CW20 contract will call back to

### Step 2: Deploy CW20 Wrapper Contract
Deploys the CW20 wrapper contract (`example/cosmwasm/cw20/artifacts/cwerc20.wasm`) which wraps the ERC20 token.
This CW20 uses `DelegateCallEvm` to interact with the ERC20 contract.

### Step 3: Register CW20->ERC20 Pointer
Creates an ERC20 pointer contract for the CW20 using `seid tx evm register-pointer ERC20 <cw20-address>`.
This deploys a `CW20ERC20Pointer.sol` contract that delegates to the CW20.

### Step 4: Call the Pointer's transfer() Function
Calls `transfer()` on the ERC20 pointer from an EVM address.
This triggers the flow:
1. **EVM**: transfer() called on ERC20 pointer
2. **Pointer → CW**: delegatecall to Wasmd precompile → executes CW20's transfer
3. **CW → EVM**: CW20 tries to call ERC20 via `DelegateCallEvm`
4. **ERROR**: "sei does not support EVM->CW->EVM call pattern"

## Usage

```bash
# Ensure local chain is running
./build/seid start

# In another terminal, run the script
./scripts/reproduce_evm_cw_evm_error.sh
```

## Debugging

To debug this with a breakpoint:

### Using Delve (Go debugger)
```bash
# Start chain with debugger
dlv exec ./build/seid -- start

# Set breakpoint
(dlv) break x/evm/keeper/evm.go:79

# Continue
(dlv) continue

# In another terminal, run the script
./scripts/reproduce_evm_cw_evm_error.sh
```

### Using VS Code / GoLand
1. Set a breakpoint at `x/evm/keeper/evm.go:79`
2. Start debugging session with `seid start`
3. Run the script in another terminal
4. Debugger will pause at the breakpoint

## The Check

The error is triggered by this condition in `x/evm/keeper/evm.go:78`:

```go
if ctx.IsEVM() && !ctx.EVMEntryViaWasmdPrecompile() {
return nil, errors.New("sei does not support EVM->CW->EVM call pattern")
}
```

### Context State
When the error occurs at `x/evm/keeper/evm.go:78`:
- `ctx.IsEVM()` = `true` (we're in an EVM transaction context)
- `ctx.EVMEntryViaWasmdPrecompile()` = `false` (was reset when CW->EVM call started)

## Related Code

- **Pointer Registration**: `x/evm/keeper/msg_server.go:268-345` - Registers CW20->ERC20 pointers
- **Pointer Contract**: `contracts/src/CW20ERC20Pointer.sol` - ERC20 contract that delegates to CW20
- **Wasmd Precompile**: `precompiles/wasmd/wasmd.go:354-361` - Handles delegatecall from pointers
- **Error Check**: `x/evm/keeper/evm.go:78-79` - Detects and blocks EVM->CW->EVM pattern
- **CW20 Contract**: `example/cosmwasm/cw20/src/contract.rs` - Uses `DelegateCallEvm` to call ERC20

## Why This Error Exists

The EVM->CW->EVM pattern is blocked because:
1. It creates complex reentrancy scenarios
2. Gas accounting becomes difficult across context switches
3. State management is complicated when bouncing between EVM and CW

The allowed pattern is: **EVM -> CW** (one-way via Wasmd precompile)

## Cleaning Up

The script creates:
- An ERC20 contract
- A CW20 wrapper contract
- An ERC20 pointer contract (CW20->ERC20)
- Transaction log at `/tmp/evm_cw_evm_error.log`

To clean up, reset your local chain.
Loading
Loading