Skip to content

Commit

Permalink
improve factory pattern tests
Browse files Browse the repository at this point in the history
  • Loading branch information
ramtinms committed Aug 6, 2024
1 parent e599782 commit 1a5b953
Show file tree
Hide file tree
Showing 6 changed files with 313 additions and 71 deletions.
291 changes: 226 additions & 65 deletions fvm/evm/emulator/emulator_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/onflow/flow-go/fvm/evm/debug"
"github.com/onflow/flow-go/fvm/evm/emulator"
"github.com/onflow/flow-go/fvm/evm/testutils"
"github.com/onflow/flow-go/fvm/evm/testutils/contracts"
"github.com/onflow/flow-go/fvm/evm/types"
"github.com/onflow/flow-go/model/flow"

Expand Down Expand Up @@ -746,26 +747,25 @@ func TestSelfdestruct(t *testing.T) {
})
}

// test factory pattern
func TestFactory(t *testing.T) {
// test factory patterns
func TestFactoryPatterns(t *testing.T) {
testutils.RunWithTestBackend(t, func(backend *testutils.TestBackend) {
testutils.RunWithTestFlowEVMRootAddress(t, backend, func(rootAddr flow.Address) {
testutils.RunWithEOATestAccount(t, backend, rootAddr, func(testAccount *testutils.EOATestAccount) {

var factoryAddress types.Address
factoryContract := testutils.GetFactoryTestContract(t)
factoryDeployer := types.NewAddressFromString("factoryDeployer")
factoryDeployerBalance := big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(gethParams.Ether))
factoryBalance := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether))
var factoryAddress types.Address
factoryContract := testutils.GetFactoryTestContract(t)
factoryDeployer := types.NewAddressFromString("factoryDeployer")
factoryDeployerBalance := big.NewInt(0).Mul(big.NewInt(1000), big.NewInt(gethParams.Ether))
factoryBalance := big.NewInt(0).Mul(big.NewInt(100), big.NewInt(gethParams.Ether))

// setup the test with funded account and deploying a factory contract.
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
// setup the test with funded account and deploying a factory contract.
RunWithNewEmulator(t, backend, rootAddr, func(env *emulator.Emulator) {
t.Run("test deploying factory", func(t *testing.T) {
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(types.NewDepositCall(types.EmptyAddress, factoryDeployer, factoryDeployerBalance, 0))
require.NoError(t, err)
requireSuccessfulExecution(t, err, res)
})

RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewDeployCall(
Expand All @@ -779,68 +779,229 @@ func TestFactory(t *testing.T) {
require.NotNil(t, res.DeployedContractAddress)
factoryAddress = *res.DeployedContractAddress
})
})

RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{1}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "deploy", salt),
200_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
})
t.Run("test self-destruct to a contract that is already deployed",
func(t *testing.T) {
// first test call deploy and try self destruct later
var deployed types.Address
RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{1}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "deploy", salt),
250_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)

// decode address, data is left padded
deployed = types.Address(gethCommon.BytesToAddress(res.ReturnedData[12:]))
})

RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{2}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "deployAndDestroy", salt),
200_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
// deposit money into the contract
depositedBalance := big.NewInt(200)
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(types.NewDepositCall(
types.EmptyAddress,
deployed,
depositedBalance, 1))
require.NoError(t, err)
requireSuccessfulExecution(t, err, res)
})
// check balance of contract
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
bal, err := blk.BalanceOf(deployed)
require.NoError(t, err)
require.Equal(t, depositedBalance, bal)
})

// set storage on deployed contract
storedValue := big.NewInt(12)
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
types.Address(deployed),
testutils.MakeCallData(t,
contracts.FactoryDeployableContractABIJSON,
"set",
storedValue),
120_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
})

// call self-destruct on the deployed
refundAddress := testutils.RandomAddress(t)
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
types.Address(deployed),
testutils.MakeCallData(t,
contracts.FactoryDeployableContractABIJSON,
"destroy",
refundAddress),
120_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
})

// check balance of the refund address and the contract
// balance should be transferred to the refund address
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
bal, err := blk.BalanceOf(refundAddress)
require.NoError(t, err)
require.Equal(t, depositedBalance, bal)

bal, err = blk.BalanceOf(deployed)
require.NoError(t, err)
require.True(t, types.BalancesAreEqual(big.NewInt(0), bal))
})

// data should still be there
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
types.Address(deployed),
testutils.MakeCallData(t,
contracts.FactoryDeployableContractABIJSON,
"get"),
120_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
require.Equal(t, storedValue, new(big.Int).SetBytes(res.ReturnedData))
})
})

RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{3}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "depositAndDeploy", salt, big.NewInt(100)),
250_000,
big.NewInt(0),
1,
),
)
requireSuccessfulExecution(t, err, res)
t.Run("test deploy and destroy in a single call",
func(t *testing.T) {
var originalFactoryBalance types.Balance
var err error
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
originalFactoryBalance, err = blk.BalanceOf(factoryAddress)
require.NoError(t, err)
})

storedValue := big.NewInt(100)
RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{2}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t,
"deployAndDestroy",
salt,
storedValue),
400_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
})

// no balance change on the caller
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
ret, err := blk.BalanceOf(factoryAddress)
require.NoError(t, err)
require.True(t, types.BalancesAreEqual(originalFactoryBalance, ret))
})
})
t.Run("test deposit first to an address and then deploy in a single call",
func(t *testing.T) {
storedValue := big.NewInt(120)
balance := big.NewInt(80)
var deployed types.Address
RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{3}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "depositAndDeploy", salt, balance, storedValue),
250_000,
big.NewInt(0),
1,
),
)
requireSuccessfulExecution(t, err, res)
// decode address, data is left padded
deployed = types.Address(gethCommon.BytesToAddress(res.ReturnedData[12:]))
})

RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{4}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "depositDeployAndDestroy", salt, big.NewInt(100)),
250_000,
big.NewInt(0),
1,
),
)
requireSuccessfulExecution(t, err, res)
// no balance change on the caller
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
ret, err := blk.BalanceOf(deployed)
require.NoError(t, err)
require.True(t, types.BalancesAreEqual(balance, ret))
})

// check stored data
RunWithNewBlockView(t, env, func(blk types.BlockView) {
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
types.Address(deployed),
testutils.MakeCallData(t,
contracts.FactoryDeployableContractABIJSON,
"get"),
120_000,
big.NewInt(0),
0,
),
)
requireSuccessfulExecution(t, err, res)
require.Equal(t, storedValue, new(big.Int).SetBytes(res.ReturnedData))
})
})

})
t.Run("test deposit, deploy, destroy in a single call",
func(t *testing.T) {
var originalFactoryBalance types.Balance
var err error
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
originalFactoryBalance, err = blk.BalanceOf(factoryAddress)
require.NoError(t, err)
})

RunWithNewBlockView(t, env, func(blk types.BlockView) {
salt := [32]byte{4}
res, err := blk.DirectCall(
types.NewContractCall(
factoryDeployer,
factoryAddress,
factoryContract.MakeCallData(t, "depositDeployAndDestroy", salt, big.NewInt(100), big.NewInt(10)),
250_000,
big.NewInt(0),
1,
),
)
requireSuccessfulExecution(t, err, res)
})
// no balance change on the caller
RunWithNewReadOnlyBlockView(t, env, func(blk types.ReadOnlyBlockView) {
ret, err := blk.BalanceOf(factoryAddress)
require.NoError(t, err)
require.True(t, types.BalancesAreEqual(originalFactoryBalance, ret))
})
})
})
})
})
Expand Down
3 changes: 3 additions & 0 deletions fvm/evm/testutils/contracts/contracts.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,6 @@ var FactoryContractBytes, _ = hex.DecodeString(factoryContractBytesInHex)

//go:embed factory_abi.json
var FactoryContractABIJSON string

//go:embed factory_deployable_abi.json
var FactoryDeployableContractABIJSON string
15 changes: 11 additions & 4 deletions fvm/evm/testutils/contracts/factory.sol
Original file line number Diff line number Diff line change
Expand Up @@ -10,29 +10,33 @@ contract Factory {
return _getCreate2Address(salt, keccak256(abi.encodePacked(type(Deployable).creationCode)));
}

function deployAndDestroy(bytes32 salt) public {
function deployAndDestroy(bytes32 salt, uint256 value) public {
Deployable dep = new Deployable{salt: salt}();
dep.set(value);
dep.destroy(address(this));
}

function depositAndDeploy(bytes32 salt, uint256 amount) public {
function depositAndDeploy(bytes32 salt, uint256 amount, uint256 stored) public returns (address) {
address addr = _getCreate2Address(salt, keccak256(abi.encodePacked(type(Deployable).creationCode)));
bool success;
assembly {
success := call(gas(), addr, amount, 0, 0, 0, 0)
}
require(success);
new Deployable{salt: salt}();
Deployable dep = new Deployable{salt: salt}();
dep.set(stored);
return _getCreate2Address(salt, keccak256(abi.encodePacked(type(Deployable).creationCode)));
}

function depositDeployAndDestroy(bytes32 salt, uint256 amount) public {
function depositDeployAndDestroy(bytes32 salt, uint256 amount, uint256 stored) public {
address addr = _getCreate2Address(salt, keccak256(abi.encodePacked(type(Deployable).creationCode)));
bool success;
assembly {
success := call(gas(), addr, amount, 0, 0, 0, 0)
}
require(success);
Deployable dep = new Deployable{salt: salt}();
dep.set(stored);
dep.destroy(address(this));
}

Expand All @@ -53,4 +57,7 @@ contract Deployable {
function destroy(address etherDestination) external {
selfdestruct(payable(etherDestination));
}

receive() external payable {
}
}
Loading

0 comments on commit 1a5b953

Please sign in to comment.