Skip to content

Commit

Permalink
Merge pull request #5469 from onflow/ramtin/5468-part1-use-transfer-f…
Browse files Browse the repository at this point in the history
…or-native-token-bridging

[Flow EVM] use transfer from/to a fixed native token bridge address for deposit/withdraw calls
  • Loading branch information
ramtinms authored Mar 5, 2024
2 parents 53aaae3 + 55feff3 commit d3d48b1
Show file tree
Hide file tree
Showing 6 changed files with 99 additions and 72 deletions.
91 changes: 41 additions & 50 deletions fvm/evm/emulator/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,9 @@ func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) {
}
switch call.SubType {
case types.DepositCallSubType:
return proc.mintTo(call.To, call.Value, txHash)
return proc.mintTo(call, txHash)
case types.WithdrawCallSubType:
return proc.withdrawFrom(call.From, call.Value, txHash)
return proc.withdrawFrom(call, txHash)
case types.DeployCallSubType:
if !call.EmptyToField() {
return proc.deployAt(call.From, call.To, call.Data, call.GasLimit, call.Value, txHash)
Expand All @@ -128,7 +128,7 @@ func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) {
default:
// TODO: when we support mutiple calls per block, we need
// to update the value zero here for tx index
return proc.runDirect(call.Message(), txHash, 0, types.DirectCallTxType)
return proc.runDirect(call.Message(), txHash, 0)
}
}

Expand Down Expand Up @@ -159,8 +159,8 @@ func (bl *BlockView) RunTransaction(
if err != nil {
return nil, err
}
res.TxHash = txHash
return res, err
// all commmit errors (StateDB errors) has to be returned
return res, proc.commit()
}

func (bl *BlockView) newProcedure() (*procedure, error) {
Expand Down Expand Up @@ -204,64 +204,53 @@ func (proc *procedure) commit() error {
}

func (proc *procedure) mintTo(
address types.Address,
amount *big.Int,
call *types.DirectCall,
txHash gethCommon.Hash,
) (*types.Result, error) {
addr := address.ToCommon()
res := &types.Result{
GasConsumed: proc.config.DirectCallBaseGasUsage,
TxType: types.DirectCallTxType,
TxHash: txHash,
}
bridge := call.From.ToCommon()

// create account if not exist
if !proc.state.Exist(addr) {
proc.state.CreateAccount(addr)
// create bridge account if not exist
if !proc.state.Exist(bridge) {
proc.state.CreateAccount(bridge)
}

// add balance
proc.state.AddBalance(addr, amount)
// add balance to the bridge account before transfer
proc.state.AddBalance(bridge, call.Value)

// we don't need to increment any nonce, given the origin doesn't exist
msg := call.Message()
proc.evm.TxContext.Origin = msg.From
// withdraw the amount and move it to the bridge account
res, err := proc.run(msg, txHash, 0, types.DirectCallTxType)
if err != nil {
return res, err
}
// all commmit errors (StateDB errors) has to be returned
return res, proc.commit()
}

func (proc *procedure) withdrawFrom(
address types.Address,
amount *big.Int,
call *types.DirectCall,
txHash gethCommon.Hash,
) (*types.Result, error) {

addr := address.ToCommon()
res := &types.Result{
GasConsumed: proc.config.DirectCallBaseGasUsage,
TxType: types.DirectCallTxType,
TxHash: txHash,
}
bridge := call.To.ToCommon()

// check if account exists
// while this method is only called for COAs
// it might be the case that someone creates a COA
// and never transfer tokens to and call for withdraw
if !proc.state.Exist(addr) {
proc.state.CreateAccount(addr)
// create bridge account if not exist
if !proc.state.Exist(bridge) {
proc.state.CreateAccount(bridge)
}

// check the source account balance
// if balance is lower than amount needed for withdrawal, error out
if proc.state.GetBalance(addr).Cmp(amount) < 0 {
return res, gethCore.ErrInsufficientFundsForTransfer
// withdraw the amount and move it to the bridge account
msg := call.Message()
proc.evm.TxContext.Origin = msg.From
res, err := proc.run(msg, txHash, 0, types.DirectCallTxType)
if err != nil {
return res, err
}

// sub balance
proc.state.SubBalance(addr, amount)

// we increment the nonce for source account cause
// withdraw counts as a transaction
nonce := proc.state.GetNonce(addr)
proc.state.SetNonce(addr, nonce+1)

// now deduct the balance from the bridge
proc.state.SubBalance(bridge, call.Value)
// all commmit errors (StateDB errors) has to be returned
return res, proc.commit()
}

Expand Down Expand Up @@ -385,12 +374,16 @@ func (proc *procedure) runDirect(
msg *gethCore.Message,
txHash gethCommon.Hash,
txIndex uint,
txType uint8,
) (*types.Result, error) {
// set the nonce for the message (needed for some opeartions like deployment)
msg.Nonce = proc.state.GetNonce(msg.From)
proc.evm.TxContext.Origin = msg.From
return proc.run(msg, txHash, txIndex, types.DirectCallTxType)
res, err := proc.run(msg, txHash, txIndex, types.DirectCallTxType)
if err != nil {
return nil, err
}
// all commmit errors (StateDB errors) has to be returned
return res, proc.commit()
}

func (proc *procedure) run(
Expand Down Expand Up @@ -442,9 +435,7 @@ func (proc *procedure) run(
res.VMError = execResult.Err
}
}
// all commmit errors (StateDB errors) has to be returned
// TODO: maybe handle them (if there are happy errors)
return &res, proc.commit()
return &res, nil
}

type precompileUpdater struct {
Expand Down
55 changes: 41 additions & 14 deletions fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,13 @@ func TestNativeTokenBridging(t *testing.T) {
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
originalBalance := big.NewInt(10000)
testAccount := types.NewAddressFromString("test")
bridgeAccount := types.NewAddressFromString("bridge")
nonce := uint64(0)

t.Run("mint tokens to the first account", func(t *testing.T) {
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
call := types.NewDepositCall(testAccount, originalBalance, nonce)
call := types.NewDepositCall(bridgeAccount, testAccount, originalBalance, nonce)
res, err := blk.DirectCall(call)
require.NoError(t, err)
require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed)
Expand All @@ -58,6 +59,18 @@ func TestNativeTokenBridging(t *testing.T) {
nonce += 1
})
})
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
retBalance, err := blk.BalanceOf(testAccount)
require.NoError(t, err)
require.Equal(t, originalBalance, retBalance)
// check balance of bridgeAccount to be zero

retBalance, err = blk.BalanceOf(bridgeAccount)
require.NoError(t, err)
require.Equal(t, big.NewInt(0), retBalance)
})
})
})
t.Run("tokens withdraw", func(t *testing.T) {
amount := big.NewInt(1000)
Expand All @@ -70,7 +83,7 @@ func TestNativeTokenBridging(t *testing.T) {
})
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
call := types.NewWithdrawCall(testAccount, amount, nonce)
call := types.NewWithdrawCall(bridgeAccount, testAccount, amount, nonce)
res, err := blk.DirectCall(call)
require.NoError(t, err)
require.Equal(t, defaultCtx.DirectCallBaseGasUsage, res.GasConsumed)
Expand All @@ -85,6 +98,11 @@ func TestNativeTokenBridging(t *testing.T) {
retBalance, err := blk.BalanceOf(testAccount)
require.NoError(t, err)
require.Equal(t, amount.Sub(originalBalance, amount), retBalance)
// check balance of bridgeAccount to be zero

retBalance, err = blk.BalanceOf(bridgeAccount)
require.NoError(t, err)
require.Equal(t, big.NewInt(0), retBalance)
})
})
})
Expand All @@ -100,6 +118,7 @@ func TestContractInteraction(t *testing.T) {
testContract := testutils.GetStorageTestContract(t)

testAccount := types.NewAddressFromString("test")
bridgeAccount := types.NewAddressFromString("bridge")
nonce := uint64(0)

amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether))
Expand All @@ -108,7 +127,7 @@ func TestContractInteraction(t *testing.T) {
// fund test account
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount, nonce))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, testAccount, amount, nonce))
require.NoError(t, err)
nonce += 1
})
Expand Down Expand Up @@ -238,7 +257,7 @@ func TestContractInteraction(t *testing.T) {
fAddr := account.Address()
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount, account.Nonce()))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, fAddr, amount, account.Nonce()))
require.NoError(t, err)
})
})
Expand All @@ -249,7 +268,7 @@ func TestContractInteraction(t *testing.T) {
coinbaseOrgBalance := gethCommon.Big1
// small amount of money to create account
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(ctx.GasFeeCollector, coinbaseOrgBalance, 0))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, ctx.GasFeeCollector, coinbaseOrgBalance, 0))
require.NoError(t, err)
})

Expand Down Expand Up @@ -288,7 +307,7 @@ func TestContractInteraction(t *testing.T) {
fAddr := account.Address()
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount, account.Nonce()))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, fAddr, amount, account.Nonce()))
require.NoError(t, err)
})
})
Expand All @@ -300,7 +319,7 @@ func TestContractInteraction(t *testing.T) {
coinbaseOrgBalance := gethCommon.Big1
// small amount of money to create account
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(ctx.GasFeeCollector, coinbaseOrgBalance, 1))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, ctx.GasFeeCollector, coinbaseOrgBalance, 1))
require.NoError(t, err)
})

Expand Down Expand Up @@ -332,7 +351,7 @@ func TestContractInteraction(t *testing.T) {
fAddr := account.Address()
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(fAddr, amount, account.Nonce()))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, fAddr, amount, account.Nonce()))
require.NoError(t, err)
})
})
Expand Down Expand Up @@ -387,13 +406,15 @@ func TestDeployAtFunctionality(t *testing.T) {
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
testContract := testutils.GetStorageTestContract(t)
testAccount := types.NewAddressFromString("test")
bridgeAccount := types.NewAddressFromString("bridge")

amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether))
amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether))

// fund test account
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount, 0))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, testAccount, amount, 0))
require.NoError(t, err)
})
})
Expand Down Expand Up @@ -475,14 +496,16 @@ func TestSelfdestruct(t *testing.T) {

testContract := testutils.GetStorageTestContract(t)
testAddress := types.NewAddressFromString("testaddr")
bridgeAccount := types.NewAddressFromString("bridge")

startBalance := big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(gethParams.Ether))
deployBalance := big.NewInt(0).Mul(big.NewInt(10), big.NewInt(gethParams.Ether))
var contractAddr types.Address

// setup the test with funded account and deploying a selfdestruct contract.
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAddress, startBalance, 0))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, testAddress, startBalance, 0))
require.NoError(t, err)
})

Expand Down Expand Up @@ -554,13 +577,14 @@ func TestTransfers(t *testing.T) {

testAccount1 := types.NewAddressFromString("test1")
testAccount2 := types.NewAddressFromString("test2")
bridgeAccount := types.NewAddressFromString("bridge")

amount := big.NewInt(0).Mul(big.NewInt(1337), big.NewInt(gethParams.Ether))
amountToBeTransfered := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether))

RunWithNewEmulator(t, backend, rootAddr, func(em *emulator.Emulator) {
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAccount1, amount, 0))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, testAccount1, amount, 0))
require.NoError(t, err)
})
})
Expand Down Expand Up @@ -593,15 +617,17 @@ func TestStorageNoSideEffect(t *testing.T) {
var err error
em := emulator.NewEmulator(backend, flowEVMRoot)
testAccount := types.NewAddressFromString("test")
bridgeAccount := types.NewAddressFromString("bridge")

amount := big.NewInt(10)
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err = blk.DirectCall(types.NewDepositCall(testAccount, amount, 0))
_, err = blk.DirectCall(types.NewDepositCall(bridgeAccount, testAccount, amount, 0))
require.NoError(t, err)
})

orgSize := backend.TotalStorageSize()
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err = blk.DirectCall(types.NewDepositCall(testAccount, amount, 0))
_, err = blk.DirectCall(types.NewDepositCall(bridgeAccount, testAccount, amount, 0))
require.NoError(t, err)
})
require.Equal(t, orgSize, backend.TotalStorageSize())
Expand All @@ -615,9 +641,10 @@ func TestCallingExtraPrecompiles(t *testing.T) {
RunWithNewEmulator(t, backend, flowEVMRoot, func(em *emulator.Emulator) {

testAccount := types.NewAddressFromString("test")
bridgeAccount := types.NewAddressFromString("bridge")
amount := big.NewInt(10_000_000)
RunWithNewBlockView(t, em, func(blk types.BlockView) {
_, err := blk.DirectCall(types.NewDepositCall(testAccount, amount, 0))
_, err := blk.DirectCall(types.NewDepositCall(bridgeAccount, testAccount, amount, 0))
require.NoError(t, err)
})

Expand Down
2 changes: 2 additions & 0 deletions fvm/evm/handler/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -476,6 +476,7 @@ func (a *Account) deposit(v *types.FLOWTokenVault) error {
bridgeAccount := a.fch.AccountByAddress(bridge, false)

call := types.NewDepositCall(
bridge,
a.address,
v.Balance(),
bridgeAccount.Nonce(),
Expand All @@ -498,6 +499,7 @@ func (a *Account) Withdraw(b types.Balance) *types.FLOWTokenVault {

func (a *Account) withdraw(b types.Balance) (*types.FLOWTokenVault, error) {
call := types.NewWithdrawCall(
a.fch.addressAllocator.NativeTokenBridgeAddress(),
a.address,
b,
a.Nonce(),
Expand Down
1 change: 1 addition & 0 deletions fvm/evm/testutils/accounts.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ func FundAndGetEOATestAccount(t testing.TB, led atree.Ledger, flowEVMRootAddress

_, err = blk.DirectCall(
types.NewDepositCall(
RandomAddress(t), // any random non-empty address works here
account.Address(),
new(big.Int).Mul(big.NewInt(1e18), big.NewInt(1000)),
account.nonce,
Expand Down
Loading

0 comments on commit d3d48b1

Please sign in to comment.