Skip to content

Commit

Permalink
basic mint/burn
Browse files Browse the repository at this point in the history
  • Loading branch information
hieuvubk committed Jan 21, 2025
1 parent b9b235c commit c77a57c
Show file tree
Hide file tree
Showing 4 changed files with 298 additions and 6 deletions.
28 changes: 25 additions & 3 deletions precompiles/erc20/erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ package erc20
import (
"embed"
"fmt"
"slices"

storetypes "cosmossdk.io/store/types"
sdk "github.com/cosmos/cosmos-sdk/types"
Expand All @@ -15,7 +16,6 @@ import (
"github.com/ethereum/go-ethereum/common"
auth "github.com/evmos/os/precompiles/authorization"
cmn "github.com/evmos/os/precompiles/common"
erc20types "github.com/evmos/os/x/erc20/types"
"github.com/evmos/os/x/evm/core/vm"
transferkeeper "github.com/evmos/os/x/ibc/transfer/keeper"
assetkeeper "github.com/realiotech/realio-network/x/asset/keeper"
Expand Down Expand Up @@ -47,7 +47,7 @@ var _ vm.PrecompiledContract = &Precompile{}
// Precompile defines the precompiled contract for ERC-20.
type Precompile struct {
cmn.Precompile
tokenPair erc20types.TokenPair
denom string
transferKeeper transferkeeper.Keeper
// BankKeeper is a public field so that the werc20 precompile can use it.
BankKeeper bankkeeper.Keeper
Expand All @@ -57,6 +57,7 @@ type Precompile struct {
// NewPrecompile creates a new ERC-20 Precompile instance as a
// PrecompiledContract interface.
func NewPrecompile(
denom string,
address common.Address,
bankKeeper bankkeeper.Keeper,
authzKeeper authzkeeper.Keeper,
Expand All @@ -78,7 +79,8 @@ func NewPrecompile(
},
BankKeeper: bankKeeper,
transferKeeper: transferKeeper,
assetKeep: assetKeeper,
assetKeep: assetKeeper,
denom: denom,
}
// Address defines the address of the ERC-20 precompile contract.
p.SetAddress(address)
Expand Down Expand Up @@ -188,12 +190,32 @@ func (p *Precompile) HandleMethod(
method *abi.Method,
args []interface{},
) (bz []byte, err error) {
params, err := p.assetKeep.GetParams(ctx)
allowedMethods := params.AllowExtensions
if err != nil {
return nil, err
}
switch method.Name {
// ERC-20 transactions
case TransferMethod:
bz, err = p.Transfer(ctx, contract, stateDB, method, args)
case TransferFromMethod:
bz, err = p.TransferFrom(ctx, contract, stateDB, method, args)
case MintMethod:
if !slices.Contains(allowedMethods, MintMethod) {
return nil, fmt.Errorf("method %s is not supported", MintMethod)
}
bz, err = p.Mint(ctx, contract, stateDB, method, args)
case BurnMethod:
if !slices.Contains(allowedMethods, BurnMethod) {
return nil, fmt.Errorf("method %s is not supported", BurnMethod)
}
bz, err = p.Burn(ctx, contract, stateDB, method, args)
case BurnFromMethod:
if !slices.Contains(allowedMethods, BurnFromMethod) {
return nil, fmt.Errorf("method %s is not supported", BurnFromMethod)
}
bz, err = p.BurnFrom(ctx, contract, stateDB, method, args)
case auth.ApproveMethod:
bz, err = p.Approve(ctx, contract, stateDB, method, args)
case auth.IncreaseAllowanceMethod:
Expand Down
62 changes: 62 additions & 0 deletions precompiles/erc20/events.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ import (
const (
// EventTypeTransfer defines the event type for the ERC-20 Transfer and TransferFrom transactions.
EventTypeTransfer = "Transfer"
EventTypeMint = "Mint"
EventTypeBurn = "Burn"
)

// EmitTransferEvent creates a new Transfer event emitted on transfer and transferFrom transactions.
Expand Down Expand Up @@ -58,6 +60,66 @@ func (p Precompile) EmitTransferEvent(ctx sdk.Context, stateDB vm.StateDB, from,
return nil
}

func (p Precompile) EmitMintEvent(ctx sdk.Context, stateDB vm.StateDB, to common.Address, value *big.Int) error {
// Prepare the event topics
event := p.ABI.Events[EventTypeMint]
topics := make([]common.Hash, 2)

// The first topic is always the signature of the event.
topics[0] = event.ID

var err error
topics[1], err = cmn.MakeTopic(to)
if err != nil {
return err
}

arguments := abi.Arguments{event.Inputs[1]}
packed, err := arguments.Pack(value)
if err != nil {
return err
}

stateDB.AddLog(&ethtypes.Log{
Address: p.Address(),
Topics: topics,
Data: packed,
BlockNumber: uint64(ctx.BlockHeight()), //nolint:gosec // G115 // block height won't exceed uint64
})

return nil
}

func (p Precompile) EmitBurnEvent(ctx sdk.Context, stateDB vm.StateDB, from common.Address, value *big.Int) error {
// Prepare the event topics
event := p.ABI.Events[EventTypeBurn]
topics := make([]common.Hash, 2)

// The first topic is always the signature of the event.
topics[0] = event.ID

var err error
topics[1], err = cmn.MakeTopic(from)
if err != nil {
return err
}

arguments := abi.Arguments{event.Inputs[1]}
packed, err := arguments.Pack(value)
if err != nil {
return err
}

stateDB.AddLog(&ethtypes.Log{
Address: p.Address(),
Topics: topics,
Data: packed,
BlockNumber: uint64(ctx.BlockHeight()), //nolint:gosec // G115 // block height won't exceed uint64
})

return nil
}

// EmitApprovalEvent creates a new approval event emitted on Approve, IncreaseAllowance
// and DecreaseAllowance transactions.
func (p Precompile) EmitApprovalEvent(ctx sdk.Context, stateDB vm.StateDB, owner, spender common.Address, value *big.Int) error {
Expand Down
159 changes: 156 additions & 3 deletions precompiles/erc20/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
package erc20

import (
"fmt"
"math/big"

errorsmod "cosmossdk.io/errors"
Expand All @@ -17,6 +18,7 @@ import (
cmn "github.com/evmos/os/precompiles/common"
"github.com/evmos/os/x/evm/core/vm"
evmtypes "github.com/evmos/os/x/evm/types"
assettypes "github.com/realiotech/realio-network/x/asset/types"
)

const (
Expand All @@ -26,6 +28,12 @@ const (
// TransferFromMethod defines the ABI method name for the ERC-20 transferFrom
// transaction.
TransferFromMethod = "transferFrom"

BurnMethod = "burn"

BurnFromMethod = "burnFrom"

MintMethod = "mint"
)

// SendMsgURL defines the authorization type for MsgSend
Expand Down Expand Up @@ -77,7 +85,7 @@ func (p *Precompile) transfer(
from, to common.Address,
amount *big.Int,
) (data []byte, err error) {
coins := sdk.Coins{{Denom: p.tokenPair.Denom, Amount: math.NewIntFromBigInt(amount)}}
coins := sdk.Coins{{Denom: p.denom, Amount: math.NewIntFromBigInt(amount)}}

msg := banktypes.NewMsgSend(from.Bytes(), to.Bytes(), coins)

Expand All @@ -96,7 +104,7 @@ func (p *Precompile) transfer(
msgSrv := bankkeeper.NewMsgServerImpl(p.BankKeeper)
_, err = msgSrv.Send(ctx, msg)
} else {
_, _, prevAllowance, err = GetAuthzExpirationAndAllowance(p.AuthzKeeper, ctx, spenderAddr, from, p.tokenPair.Denom)
_, _, prevAllowance, err = GetAuthzExpirationAndAllowance(p.AuthzKeeper, ctx, spenderAddr, from, p.denom)
if err != nil {
return nil, ConvertErrToERC20Error(errorsmod.Wrap(err, authz.ErrNoAuthorizationFound.Error()))
}
Expand All @@ -111,7 +119,7 @@ func (p *Precompile) transfer(
}

evmDenom := evmtypes.GetEVMCoinDenom()
if p.tokenPair.Denom == evmDenom {
if p.denom == evmDenom {
convertedAmount := evmtypes.ConvertAmountTo18DecimalsBigInt(amount)
p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(from, convertedAmount, cmn.Sub),
cmn.NewBalanceChangeEntry(to, convertedAmount, cmn.Add))
Expand Down Expand Up @@ -142,3 +150,148 @@ func (p *Precompile) transfer(

return method.Outputs.Pack(true)
}

func (p *Precompile) Mint(
ctx sdk.Context,
contract *vm.Contract,
stateDB vm.StateDB,
method *abi.Method,
args []interface{},
) ([]byte, error) {
to, amount, err := ParseMintArgs(args)
if err != nil {
return nil, err
}

return p.mint(ctx, contract, stateDB, method, to, amount)
}

func (p *Precompile) mint(
ctx sdk.Context,
contract *vm.Contract,
stateDB vm.StateDB,
method *abi.Method,
to common.Address,
amount *big.Int,
) (data []byte, err error) {

minter := contract.CallerAddress
havePerm, err := p.assetKeep.IsTokenManager(ctx, p.denom, minter)
if err != nil || !havePerm {
return nil, err
}

mintToAddr := sdk.AccAddress(to.Bytes())

coins := sdk.Coins{{Denom: p.denom, Amount: math.NewIntFromBigInt(amount)}}

// Check if new supply exceed max supply

tm, err := p.assetKeep.GetTokenManager(ctx, p.denom)
if err != nil {
return nil, err
}
maxSupply := tm.MaxSupply
currentSupply := p.BankKeeper.GetSupply(ctx, p.denom).Amount
newSupply := currentSupply.Add(math.NewIntFromBigInt(amount))
if newSupply.GT(maxSupply) {
return nil, ConvertErrToERC20Error(fmt.Errorf("Exceed max supply, expected: %d, got: %d", maxSupply, newSupply))
}

// Mint coins to asset module then transfer to minter addr
err = p.BankKeeper.MintCoins(ctx, assettypes.ModuleName, coins)
if err != nil {
return nil, ConvertErrToERC20Error(err)
}

err = p.BankKeeper.SendCoinsFromModuleToAccount(ctx, assettypes.ModuleName, mintToAddr, coins)
if err != nil {
return nil, ConvertErrToERC20Error(err)
}

evmDenom := evmtypes.GetEVMCoinDenom()
if p.denom == evmDenom {
convertedAmount := evmtypes.ConvertAmountTo18DecimalsBigInt(amount)
p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(to, convertedAmount, cmn.Add))
}

if err = p.EmitMintEvent(ctx, stateDB, to, amount); err != nil {
return nil, err
}

return method.Outputs.Pack(true)
}

func (p *Precompile) Burn(
ctx sdk.Context,
contract *vm.Contract,
stateDB vm.StateDB,
method *abi.Method,
args []interface{},
) ([]byte, error) {
from := contract.CallerAddress
amount, err := ParseBurnArgs(args)
if err != nil {
return nil, err
}

return p.burn(ctx, contract, stateDB, method, from, amount)
}

func (p *Precompile) BurnFrom(
ctx sdk.Context,
contract *vm.Contract,
stateDB vm.StateDB,
method *abi.Method,
args []interface{},
) ([]byte, error) {
from, amount, err := ParseBurnFromArgs(args)
if err != nil {
return nil, err
}

return p.burn(ctx, contract, stateDB, method, from, amount)
}

func (p *Precompile) burn(
ctx sdk.Context,
contract *vm.Contract,
stateDB vm.StateDB,
method *abi.Method,
from common.Address,
amount *big.Int,
) (data []byte, err error) {

minter := contract.CallerAddress
havePerm, err := p.assetKeep.IsTokenManager(ctx, p.denom, minter)
if err != nil || !havePerm {
return nil, err
}

burnFromAddr := sdk.AccAddress(from.Bytes())

coins := sdk.Coins{{Denom: p.denom, Amount: math.NewIntFromBigInt(amount)}}

// Transfer to asset module then burn
err = p.BankKeeper.SendCoinsFromAccountToModule(ctx, burnFromAddr, assettypes.ModuleName, coins)
if err != nil {
return nil, ConvertErrToERC20Error(err)
}

err = p.BankKeeper.BurnCoins(ctx, assettypes.ModuleName, coins)
if err != nil {
return nil, ConvertErrToERC20Error(err)
}

evmDenom := evmtypes.GetEVMCoinDenom()
if p.denom == evmDenom {
convertedAmount := evmtypes.ConvertAmountTo18DecimalsBigInt(amount)
p.SetBalanceChangeEntries(cmn.NewBalanceChangeEntry(from, convertedAmount, cmn.Add))
}

if err = p.EmitBurnEvent(ctx, stateDB, from, amount); err != nil {
return nil, err
}

return method.Outputs.Pack(true)
}
Loading

0 comments on commit c77a57c

Please sign in to comment.