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

fix(werc20): WERC20 refactors and handling of fallback and receive functions #2057

Merged
merged 9 commits into from
Nov 20, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ Ref: https://keepachangelog.com/en/1.0.0/
- (bank) [#2045](https://github.com/evmos/evmos/pull/2045) Add unit tests for `supplyOf` query.
- (osmosis-outpost) [#2042](https://github.com/evmos/evmos/pull/2042) Add Osmosis's wasm hook validation functions to test
- (make) [#2052](https://github.com/evmos/evmos/pull/2052) Fix Makefile targets to compile ERC20 contracts.
- (werc20) [#2057](https://github.com/evmos/evmos/pull/2057) WERC20 refactors and handling of fallback and receive functions.

### Bug Fixes

Expand Down
34 changes: 19 additions & 15 deletions precompiles/common/precompile.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,23 +51,27 @@
}
ctx = stateDB.GetContext()

methodID := contract.Input[:4]
// NOTE: this function iterates over the method map and returns
// the method with the given ID
method, err = p.MethodById(methodID)
if err != nil {
return sdk.Context{}, nil, nil, uint64(0), nil, err
}
// NOTE: This is a special case where the calling transaction does not specify a function name.
// In this case we default to a `fallback` or `receive` function on the contract.
if len(contract.Input) != 0 {
Vvaradinov marked this conversation as resolved.
Show resolved Hide resolved
methodID := contract.Input[:4]
// NOTE: this function iterates over the method map and returns
// the method with the given ID
method, err = p.MethodById(methodID)
if err != nil {
return sdk.Context{}, nil, nil, uint64(0), nil, err
}

Check warning on line 63 in precompiles/common/precompile.go

View check run for this annotation

Codecov / codecov/patch

precompiles/common/precompile.go#L54-L63

Added lines #L54 - L63 were not covered by tests

// return error if trying to write to state during a read-only call
if readOnly && isTransaction(method.Name) {
return sdk.Context{}, nil, nil, uint64(0), nil, vm.ErrWriteProtection
}
// return error if trying to write to state during a read-only call
if readOnly && isTransaction(method.Name) {
return sdk.Context{}, nil, nil, uint64(0), nil, vm.ErrWriteProtection
}

Check warning on line 68 in precompiles/common/precompile.go

View check run for this annotation

Codecov / codecov/patch

precompiles/common/precompile.go#L66-L68

Added lines #L66 - L68 were not covered by tests

argsBz := contract.Input[4:]
args, err = method.Inputs.Unpack(argsBz)
if err != nil {
return sdk.Context{}, nil, nil, uint64(0), nil, err
argsBz := contract.Input[4:]
args, err = method.Inputs.Unpack(argsBz)
if err != nil {
return sdk.Context{}, nil, nil, uint64(0), nil, err
}

Check warning on line 74 in precompiles/common/precompile.go

View check run for this annotation

Codecov / codecov/patch

precompiles/common/precompile.go#L70-L74

Added lines #L70 - L74 were not covered by tests
}

initialGas := ctx.GasMeter().GasConsumed()
Expand Down
4 changes: 2 additions & 2 deletions precompiles/werc20/IWERC20.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import "./../erc20/IERC20MetadataAllowance.sol";
* @dev Interface for representing the native EVM token as ERC20 standard.
*/
interface IWERC20 is IERC20MetadataAllowance {
/// @dev Emitted when the native tokens are deposited in exchange for the wrapped ERC20.
/// @dev Emitted when the native tokens are deposited in exchange for the wrapped ERC20.
/// @param dst The account for which the deposit is made.
/// @param wad The amount of native tokens deposited.
event Deposit(address indexed dst, uint256 wad);
Expand All @@ -19,7 +19,7 @@ interface IWERC20 is IERC20MetadataAllowance {
/// @param wad The amount of native tokens withdrawn.
event Withdrawal(address indexed src, uint256 wad);

/// @dev Default fallback payable function. Calls the deposit method.
/// @dev Default fallback payable function. Calls the deposit method.
fallback() external payable;

/// @dev Default receive payable function. Calls the deposit method.
Expand Down
10 changes: 5 additions & 5 deletions precompiles/werc20/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,8 @@ import (
const (
// EventTypeDeposit defines the event type for the Deposit transaction.
EventTypeDeposit = "Deposit"
// EventTypeWithdraw defines the event type for the Withdraw transaction.
EventTypeWithdraw = "Withdraw"
// EventTypeWithdrawal defines the event type for the Withdraw transaction.
EventTypeWithdrawal = "Withdrawal"
)

// EmitDepositEvent creates a new Deposit event emitted on a Deposit transaction.
Expand All @@ -28,9 +28,9 @@ func (p Precompile) EmitDepositEvent(ctx sdk.Context, stateDB vm.StateDB, dst co
return p.createWERC20Event(ctx, stateDB, event, dst, amount)
}

// EmitDepositEvent creates a new Withdraw event emitted on Withdraw transaction.
// EmitWithdrawEvent creates a new Withdrawal event emitted on Withdraw transaction.
func (p Precompile) EmitWithdrawEvent(ctx sdk.Context, stateDB vm.StateDB, src common.Address, amount *big.Int) error {
Vvaradinov marked this conversation as resolved.
Show resolved Hide resolved
event := p.ABI.Events[EventTypeWithdraw]
event := p.ABI.Events[EventTypeWithdrawal]
return p.createWERC20Event(ctx, stateDB, event, src, amount)
}

Expand All @@ -53,7 +53,7 @@ func (p Precompile) createWERC20Event(
return err
}

arguments := abi.Arguments{event.Inputs[2]}
arguments := abi.Arguments{event.Inputs[1]}
packed, err := arguments.Pack(amount)
if err != nil {
return err
Expand Down
16 changes: 14 additions & 2 deletions precompiles/werc20/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@
package werc20

import (
"fmt"
"math/big"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/core/vm"
Expand Down Expand Up @@ -35,6 +38,12 @@ func (p Precompile) Deposit(
return nil, err
}

// NOTE: To avoid triggering the minting/burning mechanism we override the
// balances of the contract and the sender manually to perform a no-op so
// the balances are kept in sync.
stateDB.AddBalance(dst, amount)
stateDB.SubBalance(contract.Address(), amount)

return nil, nil
}

Expand All @@ -46,10 +55,13 @@ func (p Precompile) Withdraw(
contract *vm.Contract,
stateDB vm.StateDB,
_ *abi.Method,
_ []interface{},
args []interface{},
) ([]byte, error) {
src := contract.Caller()
amount := contract.Value()
amount, ok := args[0].(*big.Int)
if !ok {
return nil, fmt.Errorf("invalid argument type: %T", args[0])
}

if err := p.EmitWithdrawEvent(ctx, stateDB, src, amount); err != nil {
Vvaradinov marked this conversation as resolved.
Show resolved Hide resolved
return nil, err
Expand Down
38 changes: 27 additions & 11 deletions precompiles/werc20/werc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ type Precompile struct {
*erc20.Precompile
}

const (
// DepositRequiredGas defines the gas required for the Deposit transaction.
DepositRequiredGas uint64 = 28_799
// WithdrawRequiredGas defines the gas required for the Withdraw transaction.
WithdrawRequiredGas uint64 = 35_960
)

// NewPrecompile creates a new WERC20 Precompile instance as a
// PrecompiledContract interface.
func NewPrecompile(
Expand Down Expand Up @@ -65,20 +72,26 @@ func (p Precompile) Address() common.Address {

// RequiredGas calculates the contract gas use.
func (p Precompile) RequiredGas(input []byte) uint64 {
// TODO: these values were obtained from Remix using the WEVMOS9.sol.
// We should execute the transactions from Evmos testnet
// to ensure parity in the values.

// If there is no method ID, then it's the fallback or receive case
if len(input) == 0 {
return DepositRequiredGas
}

methodID := input[:4]
method, err := p.MethodById(methodID)
if err != nil {
return 0
}

// TODO: these values were obtained from Remix using the WEVMOS9.sol.
// We should execute the transactions from Evmos testnet
// to ensure parity in the values.
switch method.Name {
case cmn.FallbackMethod, cmn.ReceiveMethod, DepositMethod:
Vvaradinov marked this conversation as resolved.
Show resolved Hide resolved
return 28_799
case DepositMethod:
return DepositRequiredGas
case WithdrawMethod:
return 3_000_000
return WithdrawRequiredGas
}

return p.Precompile.RequiredGas(input)
Expand All @@ -95,13 +108,16 @@ func (p Precompile) Run(evm *vm.EVM, contract *vm.Contract, readOnly bool) (bz [
// It avoids panics and returns the out of gas error so the EVM can continue gracefully.
defer cmn.HandleGasError(ctx, contract, initialGas, &err)()

switch method.Name {
// WERC20 transactions
case cmn.FallbackMethod, cmn.ReceiveMethod, DepositMethod:
switch {
case method == nil:
// If there is no method specified, then it's the fallback or receive case
bz, err = p.Deposit(ctx, contract, stateDB, method, args)
case WithdrawMethod:
case method.Name == cmn.FallbackMethod, method.Name == cmn.ReceiveMethod, method.Name == DepositMethod:
Vvaradinov marked this conversation as resolved.
Show resolved Hide resolved
Vvaradinov marked this conversation as resolved.
Show resolved Hide resolved
// WERC20 transactions
bz, err = p.Deposit(ctx, contract, stateDB, method, args)
case method.Name == WithdrawMethod:
// Withdraw Method
bz, err = p.Withdraw(ctx, contract, stateDB, method, args)

default:
// ERC20 transactions and queries
bz, err = p.Precompile.HandleMethod(ctx, contract, stateDB, method, args)
Expand Down