Skip to content

Commit

Permalink
Merge pull request #4897 from onflow/bastian/evm-deploy
Browse files Browse the repository at this point in the history
[FVM] beyond EVM part 6.3 - Implement EVM.BridgedAccount.deploy
  • Loading branch information
turbolent authored Nov 15, 2023
2 parents b05bb9c + 972fe11 commit ac2fa03
Show file tree
Hide file tree
Showing 4 changed files with 290 additions and 9 deletions.
56 changes: 56 additions & 0 deletions fvm/evm/evm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -205,3 +205,59 @@ func TestBridgedAccountWithdraw(t *testing.T) {
})
})
}

// TODO: provide proper contract code
// TODO: fund created bridged account with Flow tokens
// TODO: deposit non-zero amount
func TestBridgedAccountDeploy(t *testing.T) {

// TODO:
t.Skip("TODO")

t.Parallel()

RunWithTestBackend(t, func(backend types.Backend) {
RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
RunWithDeployedContract(t, backend, rootAddr, func(testContract *TestContract) {
RunWithEOATestAccount(t, backend, rootAddr, func(testAccount *EOATestAccount) {
chain := flow.Emulator.Chain()
RunWithNewTestVM(t, chain, func(ctx fvm.Context, vm fvm.VM, snapshot snapshot.SnapshotTree) {

code := []byte(fmt.Sprintf(
`
import EVM from %s
import FlowToken from %s
access(all)
fun main(): [UInt8; 20] {
let bridgedAccount <- EVM.createBridgedAccount()
let address = bridgedAccount.deploy(
code: [],
gasLimit: 9999,
value: EVM.Balance(flow: 0.0)
)
destroy bridgedAccount
return address.bytes
}
`,
chain.ServiceAddress().HexWithPrefix(),
fvm.FlowTokenAddress(chain).HexWithPrefix(),
))

script := fvm.Script(code)

executionSnapshot, output, err := vm.Run(
ctx,
script,
snapshot)
require.NoError(t, err)
require.NoError(t, output.Err)

// TODO:
_ = executionSnapshot
})
})
})
})
})
}
25 changes: 16 additions & 9 deletions fvm/evm/stdlib/contract.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -80,15 +80,22 @@ contract EVM {
return <-vault
}

// TODO:
// /// Deploys a contract to the EVM environment.
// /// Returns the address of the newly deployed contract
// access(all)
// fun deploy(
// code: [UInt8],
// gasLimit: UInt64,
// value: Balance
// ): EVMAddress
/// Deploys a contract to the EVM environment.
/// Returns the address of the newly deployed contract
access(all)
fun deploy(
code: [UInt8],
gasLimit: UInt64,
value: Balance
): EVMAddress {
let addressBytes = InternalEVM.deploy(
from: self.addressBytes,
code: code,
gasLimit: gasLimit,
value: value.flow
)
return EVMAddress(bytes: addressBytes)
}

/// Calls a function with the given data.
/// The execution is limited by the given amount of gas
Expand Down
95 changes: 95 additions & 0 deletions fvm/evm/stdlib/contract.go
Original file line number Diff line number Diff line change
Expand Up @@ -418,6 +418,94 @@ func newInternalEVMTypeWithdrawFunction(
)
}

const internalEVMTypeDeployFunctionName = "deploy"

var internalEVMTypeDeployFunctionType = &sema.FunctionType{
Parameters: []sema.Parameter{
{
Label: "from",
TypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType),
},
{
Label: "code",
TypeAnnotation: sema.NewTypeAnnotation(sema.ByteArrayType),
},
{
Label: "gasLimit",
TypeAnnotation: sema.NewTypeAnnotation(sema.UInt64Type),
},
{
Label: "value",
TypeAnnotation: sema.NewTypeAnnotation(sema.UFix64Type),
},
},
ReturnTypeAnnotation: sema.NewTypeAnnotation(evmAddressBytesType),
}

func newInternalEVMTypeDeployFunction(
gauge common.MemoryGauge,
handler types.ContractHandler,
) *interpreter.HostFunctionValue {
return interpreter.NewHostFunctionValue(
gauge,
internalEVMTypeCallFunctionType,
func(invocation interpreter.Invocation) interpreter.Value {
inter := invocation.Interpreter
locationRange := invocation.LocationRange

// Get from address

fromAddressValue, ok := invocation.Arguments[0].(*interpreter.ArrayValue)
if !ok {
panic(errors.NewUnreachableError())
}

fromAddress, err := AddressBytesArrayValueToEVMAddress(inter, locationRange, fromAddressValue)
if err != nil {
panic(err)
}

// Get code

codeValue, ok := invocation.Arguments[1].(*interpreter.ArrayValue)
if !ok {
panic(errors.NewUnreachableError())
}

code, err := interpreter.ByteArrayValueToByteSlice(inter, codeValue, locationRange)
if err != nil {
panic(err)
}

// Get gas limit

gasLimitValue, ok := invocation.Arguments[2].(interpreter.UInt64Value)
if !ok {
panic(errors.NewUnreachableError())
}

gasLimit := types.GasLimit(gasLimitValue)

// Get value

amountValue, ok := invocation.Arguments[3].(interpreter.UFix64Value)
if !ok {
panic(errors.NewUnreachableError())
}

amount := types.Balance(amountValue)

// Deploy

const isAuthorized = true
account := handler.AccountByAddress(fromAddress, isAuthorized)
address := account.Deploy(code, gasLimit, amount)

return EVMAddressToAddressBytesArrayValue(inter, address)
},
)
}

func NewInternalEVMContractValue(
gauge common.MemoryGauge,
handler types.ContractHandler,
Expand All @@ -433,6 +521,7 @@ func NewInternalEVMContractValue(
internalEVMTypeCallFunctionName: newInternalEVMTypeCallFunction(gauge, handler),
internalEVMTypeDepositFunctionName: newInternalEVMTypeDepositFunction(gauge, handler),
internalEVMTypeWithdrawFunctionName: newInternalEVMTypeWithdrawFunction(gauge, handler),
internalEVMTypeDeployFunctionName: newInternalEVMTypeDeployFunction(gauge, handler),
},
nil,
nil,
Expand Down Expand Up @@ -479,6 +568,12 @@ var InternalEVMContractType = func() *sema.CompositeType {
internalEVMTypeWithdrawFunctionType,
"",
),
sema.NewUnmeteredPublicFunctionMember(
ty,
internalEVMTypeDeployFunctionName,
internalEVMTypeDeployFunctionType,
"",
),
})
return ty
}()
Expand Down
123 changes: 123 additions & 0 deletions fvm/evm/stdlib/contract_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -911,3 +911,126 @@ func TestBridgedAccountWithdraw(t *testing.T) {

require.True(t, withdrew)
}

func TestBridgedAccountDeploy(t *testing.T) {

t.Parallel()

var deployed bool

contractsAddress := flow.BytesToAddress([]byte{0x1})

expectedBalance, err := cadence.NewUFix64FromParts(1, 23000000)
require.NoError(t, err)

var handler *testContractHandler
handler = &testContractHandler{
flowTokenAddress: common.Address(contractsAddress),
accountByAddress: func(fromAddress types.Address, isAuthorized bool) types.Account {
assert.Equal(t, types.Address{1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}, fromAddress)
assert.True(t, isAuthorized)

return &testFlowAccount{
address: fromAddress,
deploy: func(code types.Code, limit types.GasLimit, balance types.Balance) types.Address {
deployed = true
assert.Equal(t, types.Code{4, 5, 6}, code)
assert.Equal(t, types.GasLimit(9999), limit)
assert.Equal(t, types.Balance(expectedBalance), balance)

return handler.AllocateAddress()
},
}
},
}

env := runtime.NewBaseInterpreterEnvironment(runtime.Config{})

stdlib.SetupEnvironment(env, handler, contractsAddress)

rt := runtime.NewInterpreterRuntime(runtime.Config{})

script := []byte(`
import EVM from 0x1
import FlowToken from 0x1
access(all)
fun main(): [UInt8; 20] {
let bridgedAccount <- EVM.createBridgedAccount()
let address = bridgedAccount.deploy(
code: [4, 5, 6],
gasLimit: 9999,
value: EVM.Balance(flow: 1.23)
)
destroy bridgedAccount
return address.bytes
}
`)

accountCodes := map[common.Location][]byte{}
var events []cadence.Event

runtimeInterface := &TestRuntimeInterface{
Storage: NewTestLedger(nil, nil),
OnGetSigningAccounts: func() ([]runtime.Address, error) {
return []runtime.Address{runtime.Address(contractsAddress)}, nil
},
OnResolveLocation: SingleIdentifierLocationResolver(t),
OnUpdateAccountContractCode: func(location common.AddressLocation, code []byte) error {
accountCodes[location] = code
return nil
},
OnGetAccountContractCode: func(location common.AddressLocation) (code []byte, err error) {
code = accountCodes[location]
return code, nil
},
OnEmitEvent: func(event cadence.Event) error {
events = append(events, event)
return nil
},
OnDecodeArgument: func(b []byte, t cadence.Type) (cadence.Value, error) {
return json.Decode(nil, b)
},
}

nextTransactionLocation := NewTransactionLocationGenerator()
nextScriptLocation := NewScriptLocationGenerator()

// Deploy contracts

deployContracts(t, rt, contractsAddress, runtimeInterface, env, nextTransactionLocation)

// Run script

actual, err := rt.ExecuteScript(
runtime.Script{
Source: script,
},
runtime.Context{
Interface: runtimeInterface,
Environment: env,
Location: nextScriptLocation(),
},
)
require.NoError(t, err)

expected := cadence.NewArray([]cadence.Value{
cadence.UInt8(2), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
cadence.UInt8(0), cadence.UInt8(0),
}).WithType(cadence.NewConstantSizedArrayType(
types.AddressLength,
cadence.UInt8Type{},
))

require.Equal(t, expected, actual)

require.True(t, deployed)
}

0 comments on commit ac2fa03

Please sign in to comment.