Skip to content
Draft
Show file tree
Hide file tree
Changes from 3 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
eeee227
no more token distributor
hieuvubk Jan 21, 2025
b9b235c
port denom into precompile, no more tokenPair
hieuvubk Jan 21, 2025
c77a57c
basic mint/burn
hieuvubk Jan 21, 2025
8173a0b
basic test
hieuvubk Jan 31, 2025
566e811
update state.md
hieuvubk Jan 31, 2025
797ce54
remove distribution
hieuvubk Jan 31, 2025
67d9b00
update
hieuvubk Jan 31, 2025
492d5b0
remove allow new extension
hieuvubk Jan 31, 2025
8cd94d5
init supply
hieuvubk Jan 31, 2025
f2d7ba3
new image
hieuvubk Jan 31, 2025
d9e1986
update msgs.md
hieuvubk Jan 31, 2025
33a8b33
note
hieuvubk Jan 31, 2025
1b17d07
update the spec
catShaark Jan 31, 2025
b07b0f2
readme
hieuvubk Jan 31, 2025
409aa5c
Merge pull request #18 from decentrio/spec-update-hieu
hieuvubk Jan 31, 2025
fd8c617
base logic
hieuvubk Jan 31, 2025
8eb943d
precompiles
hieuvubk Jan 31, 2025
5cd7e5d
update spec
catShaark Feb 1, 2025
05875c4
Merge branch 'hieu/asset-module' into spec-update-2
catShaark Feb 1, 2025
b7965a4
Merge pull request #19 from decentrio/spec-update-2
catShaark Feb 1, 2025
d605b22
Update 02_state.md
catShaark Feb 1, 2025
9bc990c
Update 02_state.md
catShaark Feb 1, 2025
55fc974
Update 02_state.md
catShaark Feb 1, 2025
d38b45c
Update 04_msgs.md
catShaark Feb 1, 2025
383a9ce
Update README.md
catShaark Feb 1, 2025
0cf3400
setup test framework & test mint
hieuvubk Feb 4, 2025
cd74210
fix burn, burnFrom & test
hieuvubk Feb 6, 2025
8448f46
freeze logic
hieuvubk Feb 7, 2025
ca63777
clean up example chain
hieuvubk Feb 10, 2025
1b6c0ce
test transfer with freeze
hieuvubk Feb 10, 2025
c6c86fa
no need
hieuvubk Feb 10, 2025
6b39b76
remove distributor
hieuvubk Feb 10, 2025
984fdc2
using addr as []byte
hieuvubk Feb 12, 2025
6139a5c
access control
hieuvubk Feb 13, 2025
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
4 changes: 2 additions & 2 deletions app/mock_erc20.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,12 +30,12 @@ func NewEmptyMockErc20Keeper() MockErc20Keeper {

func (k MockErc20Keeper) GetERC20PrecompileInstance(ctx sdk.Context, addr common.Address) (contract vm.PrecompiledContract, found bool, err error) {
// Check if contract address in list
exist, err := k.assetKeeper.EVMContractExist(ctx, addr)
exist, denom, err := k.assetKeeper.EVMContractExist(ctx, addr)
if err != nil || !exist {
return nil, false, nil
}

precompile, err := erc20.NewPrecompile(addr, k.bankKeeper, k.authzKeeper, *k.transferKeeper, k.assetKeeper)
precompile, err := erc20.NewPrecompile(denom, addr, k.bankKeeper, k.authzKeeper, *k.transferKeeper, k.assetKeeper)
if err != nil {
return nil, false, nil
}
Expand Down
26 changes: 13 additions & 13 deletions precompiles/erc20/approve.go
Original file line number Diff line number Diff line change
Expand Up @@ -178,7 +178,7 @@ func (p Precompile) DecreaseAllowance(
}
// TODO: owner should be the owner of the contract

authorization, expiration, allowance, err := GetAuthzExpirationAndAllowance(p.AuthzKeeper, ctx, grantee, granter, p.tokenPair.Denom)
authorization, expiration, allowance, err := GetAuthzExpirationAndAllowance(p.AuthzKeeper, ctx, grantee, granter, p.denom)

// TODO: (@fedekunze) check if this is correct by comparing behavior with
// regular ERC-20
Expand All @@ -189,7 +189,7 @@ func (p Precompile) DecreaseAllowance(
err = ErrDecreaseNonPositiveValue
case err != nil:
// case 2. no authorization -> return error
err = sdkerrors.Wrap(err, fmt.Sprintf(ErrNoAllowanceForToken, p.tokenPair.Denom))
err = sdkerrors.Wrap(err, fmt.Sprintf(ErrNoAllowanceForToken, p.denom))
case subtractedValue != nil && subtractedValue.Cmp(allowance) < 0:
// case 3. subtractedValue positive and subtractedValue less than allowance -> update authorization
amount, err = p.decreaseAllowance(ctx, grantee, granter, subtractedValue, authorization, expiration)
Expand All @@ -199,10 +199,10 @@ func (p Precompile) DecreaseAllowance(
amount = common.Big0
case subtractedValue != nil && allowance.Sign() == 0:
// case 5. subtractedValue positive but no allowance for given denomination -> return error
err = fmt.Errorf(ErrNoAllowanceForToken, p.tokenPair.Denom)
err = fmt.Errorf(ErrNoAllowanceForToken, p.denom)
case subtractedValue != nil && subtractedValue.Cmp(allowance) > 0:
// case 6. subtractedValue positive and subtractedValue higher than allowance -> return error
err = ConvertErrToERC20Error(fmt.Errorf(ErrSubtractMoreThanAllowance, p.tokenPair.Denom, subtractedValue, allowance))
err = ConvertErrToERC20Error(fmt.Errorf(ErrSubtractMoreThanAllowance, p.denom, subtractedValue, allowance))
}

if err != nil {
Expand All @@ -222,7 +222,7 @@ func (p Precompile) createAuthorization(ctx sdk.Context, grantee, granter common
return fmt.Errorf(ErrIntegerOverflow, amount)
}

coins := sdk.Coins{{Denom: p.tokenPair.Denom, Amount: sdkmath.NewIntFromBigInt(amount)}}
coins := sdk.Coins{{Denom: p.denom, Amount: sdkmath.NewIntFromBigInt(amount)}}
expiration := ctx.BlockTime().Add(p.ApprovalExpiration)

// NOTE: we leave the allowed arg empty as all recipients are allowed (per ERC20 standard)
Expand All @@ -235,7 +235,7 @@ func (p Precompile) createAuthorization(ctx sdk.Context, grantee, granter common
}

func (p Precompile) updateAuthorization(ctx sdk.Context, grantee, granter common.Address, amount *big.Int, authorization *banktypes.SendAuthorization, expiration *time.Time) error {
authorization.SpendLimit = updateOrAddCoin(authorization.SpendLimit, sdk.Coin{Denom: p.tokenPair.Denom, Amount: sdkmath.NewIntFromBigInt(amount)})
authorization.SpendLimit = updateOrAddCoin(authorization.SpendLimit, sdk.Coin{Denom: p.denom, Amount: sdkmath.NewIntFromBigInt(amount)})
if err := authorization.ValidateBasic(); err != nil {
return err
}
Expand All @@ -252,16 +252,16 @@ func (p Precompile) removeSpendLimitOrDeleteAuthorization(ctx sdk.Context, grant
return authz.ErrUnknownAuthorizationType
}

found, denomCoins := sendAuthz.SpendLimit.Find(p.tokenPair.Denom)
found, denomCoins := sendAuthz.SpendLimit.Find(p.denom)
if !found {
return fmt.Errorf(ErrNoAllowanceForToken, p.tokenPair.Denom)
return fmt.Errorf(ErrNoAllowanceForToken, p.denom)
}

newSpendLimit, hasNeg := sendAuthz.SpendLimit.SafeSub(denomCoins)
// NOTE: safety check only, this should never happen since we only subtract what was found in the slice.
if hasNeg {
return ConvertErrToERC20Error(fmt.Errorf(ErrSubtractMoreThanAllowance,
p.tokenPair.Denom, denomCoins, sendAuthz.SpendLimit,
p.denom, denomCoins, sendAuthz.SpendLimit,
))
}

Expand All @@ -285,7 +285,7 @@ func (p Precompile) increaseAllowance(
return nil, authz.ErrUnknownAuthorizationType
}

allowance := sendAuthz.SpendLimit.AmountOfNoDenomValidation(p.tokenPair.Denom)
allowance := sendAuthz.SpendLimit.AmountOfNoDenomValidation(p.denom)
sdkAddedValue := sdkmath.NewIntFromBigInt(addedValue)
amount, overflow := cmn.SafeAdd(allowance, sdkAddedValue)
if overflow {
Expand All @@ -311,15 +311,15 @@ func (p Precompile) decreaseAllowance(
return nil, authz.ErrUnknownAuthorizationType
}

found, allowance := sendAuthz.SpendLimit.Find(p.tokenPair.Denom)
found, allowance := sendAuthz.SpendLimit.Find(p.denom)
if !found {
return nil, fmt.Errorf(ErrNoAllowanceForToken, p.tokenPair.Denom)
return nil, fmt.Errorf(ErrNoAllowanceForToken, p.denom)
}

amount = new(big.Int).Sub(allowance.Amount.BigInt(), subtractedValue)
// NOTE: Safety check only since this is checked in the DecreaseAllowance method already.
if amount.Sign() < 0 {
return nil, ConvertErrToERC20Error(fmt.Errorf(ErrSubtractMoreThanAllowance, p.tokenPair.Denom, subtractedValue, allowance.Amount))
return nil, ConvertErrToERC20Error(fmt.Errorf(ErrSubtractMoreThanAllowance, p.denom, subtractedValue, allowance.Amount))
}

if err := p.updateAuthorization(ctx, grantee, granter, amount, sendAuthz, expiration); err != nil {
Expand Down
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
20 changes: 10 additions & 10 deletions precompiles/erc20/query.go
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,12 @@ func (p Precompile) Name(
method *abi.Method,
_ []interface{},
) ([]byte, error) {
metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom)
metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.denom)
if found {
return method.Outputs.Pack(metadata.Name)
}

baseDenom, err := p.getBaseDenomFromIBCVoucher(ctx, p.tokenPair.Denom)
baseDenom, err := p.getBaseDenomFromIBCVoucher(ctx, p.denom)
if err != nil {
return nil, ConvertErrToERC20Error(err)
}
Expand All @@ -75,12 +75,12 @@ func (p Precompile) Symbol(
method *abi.Method,
_ []interface{},
) ([]byte, error) {
metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom)
metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.denom)
if found {
return method.Outputs.Pack(metadata.Symbol)
}

baseDenom, err := p.getBaseDenomFromIBCVoucher(ctx, p.tokenPair.Denom)
baseDenom, err := p.getBaseDenomFromIBCVoucher(ctx, p.denom)
if err != nil {
return nil, ConvertErrToERC20Error(err)
}
Expand All @@ -99,9 +99,9 @@ func (p Precompile) Decimals(
method *abi.Method,
_ []interface{},
) ([]byte, error) {
metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.tokenPair.Denom)
metadata, found := p.BankKeeper.GetDenomMetaData(ctx, p.denom)
if !found {
denomTrace, err := ibc.GetDenomTrace(p.transferKeeper, ctx, p.tokenPair.Denom)
denomTrace, err := ibc.GetDenomTrace(p.transferKeeper, ctx, p.denom)
if err != nil {
return nil, ConvertErrToERC20Error(err)
}
Expand Down Expand Up @@ -129,7 +129,7 @@ func (p Precompile) Decimals(
if !displayFound {
return nil, ConvertErrToERC20Error(fmt.Errorf(
"display denomination not found for denom: %q",
p.tokenPair.Denom,
p.denom,
))
}

Expand All @@ -152,7 +152,7 @@ func (p Precompile) TotalSupply(
method *abi.Method,
_ []interface{},
) ([]byte, error) {
supply := p.BankKeeper.GetSupply(ctx, p.tokenPair.Denom)
supply := p.BankKeeper.GetSupply(ctx, p.denom)

return method.Outputs.Pack(supply.Amount.BigInt())
}
Expand All @@ -171,7 +171,7 @@ func (p Precompile) BalanceOf(
return nil, err
}

balance := p.BankKeeper.GetBalance(ctx, account.Bytes(), p.tokenPair.Denom)
balance := p.BankKeeper.GetBalance(ctx, account.Bytes(), p.denom)

return method.Outputs.Pack(balance.Amount.BigInt())
}
Expand All @@ -196,7 +196,7 @@ func (p Precompile) Allowance(
return method.Outputs.Pack(abi.MaxUint256)
}

_, _, allowance, err := GetAuthzExpirationAndAllowance(p.AuthzKeeper, ctx, spender, owner, p.tokenPair.Denom)
_, _, allowance, err := GetAuthzExpirationAndAllowance(p.AuthzKeeper, ctx, spender, owner, p.denom)
if err != nil {
// NOTE: We are not returning the error here, because we want to align the behavior with
// standard ERC20 smart contracts, which return zero if an allowance is not found.
Expand Down
Loading
Loading