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

[Flow EVM] smart contract deployment for COAs #5269

Merged
merged 53 commits into from
Feb 5, 2024
Merged
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
53 commits
Select commit Hold shift + click to select a range
7a1f18c
add deploy at functionality
ramtinms Jan 18, 2024
2129f81
update deployedAt
ramtinms Jan 18, 2024
09f3e22
add more tests
ramtinms Jan 18, 2024
7e0ad0e
add DeployACOAAccount method
ramtinms Jan 18, 2024
86c6603
add COA contract ABI
ramtinms Jan 18, 2024
6e8b064
add test
ramtinms Jan 19, 2024
72e557e
.
ramtinms Jan 19, 2024
5a662b2
update address allocation to shuffle
ramtinms Jan 23, 2024
45583ca
move things around
ramtinms Jan 23, 2024
b9d7a8e
clean up
ramtinms Jan 23, 2024
b8e6017
cleanups
ramtinms Jan 23, 2024
6e92abc
rename bridged to COA
ramtinms Jan 23, 2024
5c265a5
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Jan 23, 2024
32a624f
cleanup
ramtinms Jan 23, 2024
631228e
Merge branch 'master' into ramtin/5179-update-coa-address-allocation
ramtinms Jan 23, 2024
5321f97
fix tests
ramtinms Jan 24, 2024
a89f0bd
bug fix
ramtinms Jan 24, 2024
9170c64
update factory address
ramtinms Jan 24, 2024
98aad4d
update test
ramtinms Jan 24, 2024
76b8b38
apply pr feedback
ramtinms Jan 24, 2024
06cc7a5
set nonce only for direct calls
ramtinms Jan 24, 2024
8c73df2
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Jan 24, 2024
e1442cb
update runDirect to add origin
ramtinms Jan 24, 2024
1794665
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Jan 24, 2024
806b692
Merge branch 'master' into ramtin/5179-update-coa-address-allocation
ramtinms Jan 24, 2024
2a79155
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Jan 25, 2024
d05ec15
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Jan 26, 2024
d2379a9
Merge branch 'master' into ramtin/5179-update-coa-address-allocation
ramtinms Jan 26, 2024
6a93bea
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Jan 29, 2024
3a71c54
fix bugs due to merge
ramtinms Jan 29, 2024
65567ed
Merge branch 'ramtin/5226-coa-smart-contracts' into ramtin/5179-updat…
ramtinms Jan 29, 2024
72bf78c
use resource id as the seed for address allocation
ramtinms Jan 29, 2024
5445289
Merge pull request #5276 from onflow/ramtin/5179-update-coa-address-a…
ramtinms Jan 30, 2024
a0761f7
improve coa contract
ramtinms Jan 30, 2024
4591f8f
clean up
ramtinms Jan 30, 2024
5ec74c6
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Feb 1, 2024
38d833b
apply PR feedback
ramtinms Feb 1, 2024
f9fac5a
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Feb 1, 2024
0a9e40b
clarify security consideration about prefix length
Feb 1, 2024
c81674d
simplify makePrefixedAddress
Feb 1, 2024
c8bd1da
explain mapAddressIndex logic
Feb 1, 2024
47d5b2c
add requirement on constant
Feb 1, 2024
88de167
typos and formulations
Feb 1, 2024
4fa6fda
more clarity about the choice of the multplpier constant
Feb 2, 2024
5c410f9
Merge pull request #5344 from onflow/tarak/address-suggestions
ramtinms Feb 2, 2024
78f2cbb
apply pr feedback
ramtinms Feb 2, 2024
b327986
Merge branch 'master' into ramtin/5226-coa-smart-contracts
ramtinms Feb 2, 2024
326471d
improve coa solidity code doc
ramtinms Feb 2, 2024
6202743
small improvements
ramtinms Feb 2, 2024
6d7e364
clean up
ramtinms Feb 2, 2024
99747b1
improve bridge account address allocation
ramtinms Feb 2, 2024
bb58529
fix test
ramtinms Feb 2, 2024
75081ff
fix test
ramtinms Feb 2, 2024
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
147 changes: 134 additions & 13 deletions fvm/evm/emulator/emulator.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import (
gethTypes "github.com/ethereum/go-ethereum/core/types"
gethVM "github.com/ethereum/go-ethereum/core/vm"
gethCrypto "github.com/ethereum/go-ethereum/crypto"
gethParams "github.com/ethereum/go-ethereum/params"
"github.com/onflow/atree"

"github.com/onflow/flow-go/fvm/evm/emulator/state"
Expand Down Expand Up @@ -73,14 +74,19 @@ func (bv *ReadOnlyBlockView) BalanceOf(address types.Address) (*big.Int, error)
return bv.state.GetBalance(address.ToCommon()), nil
}

// NonceOf returns the nonce of the given address
func (bv *ReadOnlyBlockView) NonceOf(address types.Address) (uint64, error) {
return bv.state.GetNonce(address.ToCommon()), nil
}

// CodeOf returns the code of the given address
func (bv *ReadOnlyBlockView) CodeOf(address types.Address) (types.Code, error) {
return bv.state.GetCode(address.ToCommon()), nil
}

// NonceOf returns the nonce of the given address
func (bv *ReadOnlyBlockView) NonceOf(address types.Address) (uint64, error) {
return bv.state.GetNonce(address.ToCommon()), nil
// CodeHashOf returns the code hash of the given address
func (bv *ReadOnlyBlockView) CodeHashOf(address types.Address) ([]byte, error) {
return bv.state.GetCodeHash(address.ToCommon()).Bytes(), nil
}

// BlockView allows mutation of the evm state as part of a block
Expand All @@ -99,16 +105,19 @@ func (bl *BlockView) DirectCall(call *types.DirectCall) (*types.Result, error) {
if err != nil {
return nil, err
}
var res *types.Result
switch call.SubType {
case types.DepositCallSubType:
res, err = proc.mintTo(call.To, call.Value)
return proc.mintTo(call.To, call.Value)
case types.WithdrawCallSubType:
res, err = proc.withdrawFrom(call.From, call.Value)
return proc.withdrawFrom(call.From, call.Value)
case types.DeployCallSubType:
if !call.EmptyToField() {
return proc.deployAt(call.From, call.To, call.Data, call.GasLimit, call.Value)
}
fallthrough
default:
res, err = proc.run(call.Message(), types.DirectCallTxType)
return proc.runDirect(call.Message(), types.DirectCallTxType)
}
return res, err
}

// RunTransaction runs an evm transaction
Expand Down Expand Up @@ -205,12 +214,9 @@ func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*ty
}

// check if account exists
// while this method is only called from bridged accounts
// it might be the case that someone creates a bridged account
// 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
// TODO: we might revisit this apporach and
// return res, types.ErrAccountDoesNotExist
// instead
if !proc.state.Exist(addr) {
proc.state.CreateAccount(addr)
}
Expand All @@ -232,6 +238,121 @@ func (proc *procedure) withdrawFrom(address types.Address, amount *big.Int) (*ty
return res, proc.commit()
}

// deployAt deploys a contract at the given target address
// behaviour should be similar to what evm.create internal method does with
// a few differences, don't need to check for previous forks given this
// functionality was not available to anyone, we don't need to
// follow snapshoting, given we do commit/revert style in this code base.
// in the future we might optimize this method accepting deploy-ready byte codes
// and skip interpreter call, gas calculations and many checks.
func (proc *procedure) deployAt(
caller types.Address,
to types.Address,
data types.Code,
gasLimit uint64,
value *big.Int,
) (*types.Result, error) {
res := &types.Result{
TxType: types.DirectCallTxType,
}
addr := to.ToCommon()

// precheck 1 - check balance of the source
if value.Sign() != 0 &&
!proc.evm.Context.CanTransfer(proc.state, caller.ToCommon(), value) {
return res, gethVM.ErrInsufficientBalance
}

// precheck 2 - ensure there's no existing contract is deployed at the address
contractHash := proc.state.GetCodeHash(addr)
if proc.state.GetNonce(addr) != 0 ||
(contractHash != (gethCommon.Hash{}) && contractHash != gethTypes.EmptyCodeHash) {
return res, gethVM.ErrContractAddressCollision
}

callerCommon := caller.ToCommon()
// setup caller if doesn't exist
if !proc.state.Exist(callerCommon) {
proc.state.CreateAccount(callerCommon)
}
// increment the nonce for the caller
proc.state.SetNonce(callerCommon, proc.state.GetNonce(callerCommon)+1)

// setup account
proc.state.CreateAccount(addr)
proc.state.SetNonce(addr, 1) // (EIP-158)
proc.evm.Context.Transfer( // transfer value
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
proc.state,
caller.ToCommon(),
addr,
value,
)

// run code through interpreter
// this would check for errors and computes the final bytes to be stored under account
var err error
inter := gethVM.NewEVMInterpreter(proc.evm)
contract := gethVM.NewContract(
gethVM.AccountRef(caller.ToCommon()),
gethVM.AccountRef(addr),
value,
gasLimit)
contract.Code = data
contract.CodeHash = gethCrypto.Keccak256Hash(data)
contract.CodeAddr = &addr
ramtinms marked this conversation as resolved.
Show resolved Hide resolved
// update access list (Berlin)
proc.state.AddAddressToAccessList(addr)

ret, err := inter.Run(contract, nil, false)
gasCost := uint64(len(ret)) * gethParams.CreateDataGas
res.GasConsumed = gasCost

// handle errors
if err != nil {
// for all errors except this one consume all the remaining gas (Homestead)
if err != gethVM.ErrExecutionReverted {
res.GasConsumed = gasLimit
}
res.Failed = true
return res, err
}

// update gas usage
if gasCost > gasLimit {
// consume all the remaining gas (Homestead)
res.GasConsumed = gasLimit
res.Failed = true
return res, gethVM.ErrCodeStoreOutOfGas
}

// check max code size (EIP-158)
if len(ret) > gethParams.MaxCodeSize {
// consume all the remaining gas (Homestead)
res.GasConsumed = gasLimit
res.Failed = true
return res, gethVM.ErrMaxCodeSizeExceeded
}

// reject code starting with 0xEF (EIP-3541)
if len(ret) >= 1 && ret[0] == 0xEF {
// consume all the remaining gas (Homestead)
res.GasConsumed = gasLimit
res.Failed = true
return res, gethVM.ErrInvalidCode
}

proc.state.SetCode(addr, ret)
res.DeployedContractAddress = to
return res, proc.commit()
}

func (proc *procedure) runDirect(msg *gethCore.Message, 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, types.DirectCallTxType)
}

func (proc *procedure) run(msg *gethCore.Message, txType uint8) (*types.Result, error) {
res := types.Result{
TxType: txType,
Expand Down
76 changes: 76 additions & 0 deletions fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package emulator_test

import (
"fmt"
"math"
"math/big"
"testing"

gethCommon "github.com/ethereum/go-ethereum/common"
gethTypes "github.com/ethereum/go-ethereum/core/types"
gethVM "github.com/ethereum/go-ethereum/core/vm"
gethParams "github.com/ethereum/go-ethereum/params"
"github.com/stretchr/testify/require"

Expand Down Expand Up @@ -285,7 +287,81 @@ func TestContractInteraction(t *testing.T) {
require.True(t, types.IsEVMValidationError(err))
})
})
})
})
}

func TestDeployAtFunctionality(t *testing.T) {
testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
testContract := testutils.GetStorageTestContract(t)
testAccount := types.NewAddressFromString("test")
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))
require.NoError(t, err)
})
})

t.Run("deploy contract at target address", func(t *testing.T) {
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
target := types.Address{1, 2, 3}
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewDeployCallWithTargetAddress(
testAccount,
target,
testContract.ByteCode,
math.MaxUint64,
amountToBeTransfered),
)
require.NoError(t, err)
require.Equal(t, target, res.DeployedContractAddress)
})
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
require.NotNil(t, target)
retCode, err := blk.CodeOf(target)
require.NoError(t, err)
require.NotEmpty(t, retCode)

retBalance, err := blk.BalanceOf(target)
require.NoError(t, err)
require.Equal(t, amountToBeTransfered, retBalance)

retBalance, err = blk.BalanceOf(testAccount)
require.NoError(t, err)
require.Equal(t, amount.Sub(amount, amountToBeTransfered), retBalance)
})
// test deployment to an address that is already exist
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(
types.NewDeployCallWithTargetAddress(
testAccount,
target,
testContract.ByteCode,
math.MaxUint64,
amountToBeTransfered),
)
require.Equal(t, gethVM.ErrContractAddressCollision, err)
})
// test deployment with not enough gas
RunWithNewBlockView(t, env, func(blk types.BlockView) {
_, err := blk.DirectCall(
types.NewDeployCallWithTargetAddress(
testAccount,
types.Address{3, 4, 5},
testContract.ByteCode,
100,
new(big.Int)),
)
require.Equal(t, fmt.Errorf("out of gas"), err)
})
})
})
})
})
}
Expand Down
2 changes: 1 addition & 1 deletion fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,7 @@ func TestEVMAddressDeposit(t *testing.T) {
})
}

func TestBridgedAccountWithdraw(t *testing.T) {
func TestCOAWithdraw(t *testing.T) {

t.Parallel()

Expand Down
4 changes: 4 additions & 0 deletions fvm/evm/handler/addressAllocator.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ func NewAddressAllocator(led atree.Ledger, flexAddress flow.Address) (*AddressAl
}, nil
}

func (aa *AddressAllocator) COAFactoryAddress() types.Address {
return MakeCOAAddress(0)
}

// AllocateCOAAddress allocates an address for COA
func (aa *AddressAllocator) AllocateCOAAddress() (types.Address, error) {
data, err := aa.led.GetValue(aa.flexAddress[:], []byte(ledgerAddressAllocatorKey))
Expand Down
8 changes: 5 additions & 3 deletions fvm/evm/handler/addressAllocator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,6 @@ import (
)

func TestAddressAllocator(t *testing.T) {

testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(root flow.Address) {
aa, err := handler.NewAddressAllocator(backend, root)
Expand All @@ -34,8 +33,11 @@ func TestAddressAllocator(t *testing.T) {
require.NoError(t, err)
expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000020000000000000002"))
require.Equal(t, expectedAddress, adr)
})

// factory
factory := aa.COAFactoryAddress()
expectedAddress = types.NewAddress(gethCommon.HexToAddress("0x0000000000000000000000020000000000000000"))
require.Equal(t, expectedAddress, factory)
})
})

}
Loading
Loading