@@ -5,6 +5,7 @@ package evm
55
66import (
77 "context"
8+ "crypto/ecdsa"
89 "encoding/json"
910 "errors"
1011 "fmt"
@@ -31,12 +32,14 @@ import (
3132 "github.com/ava-labs/avalanchego/utils/crypto/secp256k1"
3233 "github.com/ava-labs/avalanchego/utils/set"
3334 "github.com/ava-labs/avalanchego/vms/components/chain"
35+ "github.com/ava-labs/avalanchego/vms/evm/acp176"
3436 "github.com/ava-labs/avalanchego/vms/evm/predicate"
3537 "github.com/ava-labs/avalanchego/vms/platformvm/warp/payload"
3638 "github.com/ava-labs/libevm/common"
3739 "github.com/ava-labs/libevm/common/math"
3840 "github.com/ava-labs/libevm/core/rawdb"
3941 "github.com/ava-labs/libevm/core/types"
42+ "github.com/ava-labs/libevm/crypto"
4043 "github.com/ava-labs/libevm/log"
4144 "github.com/ava-labs/libevm/trie"
4245 "github.com/stretchr/testify/assert"
@@ -71,6 +74,8 @@ import (
7174 warpcontract "github.com/ava-labs/subnet-evm/precompile/contracts/warp"
7275)
7376
77+ const delegateCallPrecompileCode = "6080604052348015600e575f5ffd5b506106608061001c5f395ff3fe608060405234801561000f575f5ffd5b506004361061003f575f3560e01c80638b336b5e14610043578063b771b3bc14610061578063e4246eec1461007f575b5f5ffd5b61004b61009d565b604051610058919061029e565b60405180910390f35b610069610256565b6040516100769190610331565b60405180910390f35b61008761026e565b604051610094919061036a565b60405180910390f35b5f5f6040516020016100ae906103dd565b60405160208183030381529060405290505f63ee5b48eb60e01b826040516024016100d9919061046b565b604051602081830303815290604052907bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090505f5f73020000000000000000000000000000000000000573ffffffffffffffffffffffffffffffffffffffff168360405161017391906104c5565b5f60405180830381855af49150503d805f81146101ab576040519150601f19603f3d011682016040523d82523d5f602084013e6101b0565b606091505b5091509150816101f5576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016101ec9061054b565b60405180910390fd5b808060200190518101906102099190610597565b94505f5f1b850361024f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016102469061060c565b60405180910390fd5b5050505090565b73020000000000000000000000000000000000000581565b73020000000000000000000000000000000000000581565b5f819050919050565b61029881610286565b82525050565b5f6020820190506102b15f83018461028f565b92915050565b5f73ffffffffffffffffffffffffffffffffffffffff82169050919050565b5f819050919050565b5f6102f96102f46102ef846102b7565b6102d6565b6102b7565b9050919050565b5f61030a826102df565b9050919050565b5f61031b82610300565b9050919050565b61032b81610311565b82525050565b5f6020820190506103445f830184610322565b92915050565b5f610354826102b7565b9050919050565b6103648161034a565b82525050565b5f60208201905061037d5f83018461035b565b92915050565b5f82825260208201905092915050565b7f68656c6c6f0000000000000000000000000000000000000000000000000000005f82015250565b5f6103c7600583610383565b91506103d282610393565b602082019050919050565b5f6020820190508181035f8301526103f4816103bb565b9050919050565b5f81519050919050565b5f82825260208201905092915050565b8281835e5f83830152505050565b5f601f19601f8301169050919050565b5f61043d826103fb565b6104478185610405565b9350610457818560208601610415565b61046081610423565b840191505092915050565b5f6020820190508181035f8301526104838184610433565b905092915050565b5f81905092915050565b5f61049f826103fb565b6104a9818561048b565b93506104b9818560208601610415565b80840191505092915050565b5f6104d08284610495565b915081905092915050565b7f44656c65676174652063616c6c20746f2073656e64576172704d6573736167655f8201527f206661696c656400000000000000000000000000000000000000000000000000602082015250565b5f610535602783610383565b9150610540826104db565b604082019050919050565b5f6020820190508181035f83015261056281610529565b9050919050565b5f5ffd5b61057681610286565b8114610580575f5ffd5b50565b5f815190506105918161056d565b92915050565b5f602082840312156105ac576105ab610569565b5b5f6105b984828501610583565b91505092915050565b7f4661696c656420746f2073656e642077617270206d65737361676500000000005f82015250565b5f6105f6601b83610383565b9150610601826105c2565b602082019050919050565b5f6020820190508181035f830152610623816105ea565b905091905056fea2646970667358221220192acba01cff6d70ce187c63c7ccac116d811f6c35e316fde721f14929ced12564736f6c634300081e0033"
78+
7479func TestMain (m * testing.M ) {
7580 RegisterAllLibEVMExtras ()
7681 os .Exit (m .Run ())
@@ -3972,3 +3977,184 @@ func TestBlockGasValidation(t *testing.T) {
39723977 })
39733978 }
39743979}
3980+
3981+ // newSignedLegacyTx builds a legacy transaction and signs it using the
3982+ // LatestSigner derived from the provided chain config.
3983+ func newSignedLegacyTx (
3984+ t * testing.T ,
3985+ cfg * params.ChainConfig ,
3986+ key * ecdsa.PrivateKey ,
3987+ nonce uint64 ,
3988+ to * common.Address ,
3989+ value * big.Int ,
3990+ gas uint64 ,
3991+ gasPrice * big.Int ,
3992+ data []byte ,
3993+ ) * types.Transaction {
3994+ t .Helper ()
3995+
3996+ tx := types .NewTx (& types.LegacyTx {
3997+ Nonce : nonce ,
3998+ To : to ,
3999+ Value : value ,
4000+ Gas : gas ,
4001+ GasPrice : gasPrice ,
4002+ Data : data ,
4003+ })
4004+ signedTx , err := types .SignTx (tx , types .LatestSigner (cfg ), key )
4005+ require .NoError (t , err )
4006+ return signedTx
4007+ }
4008+
4009+ // deployContract deploys the provided EVM bytecode using a prefunded test account
4010+ // and returns the created contract address. It is reusable for any contract code.
4011+ func deployContract (ctx context.Context , t * testing.T , vm * VM , gasPrice * big.Int , code []byte ) common.Address {
4012+ callerAddr := testEthAddrs [0 ]
4013+ callerKey := testKeys [0 ]
4014+
4015+ nonce := vm .txPool .Nonce (callerAddr )
4016+ signedTx := newSignedLegacyTx (t , vm .chainConfig , callerKey .ToECDSA (), nonce , nil , big .NewInt (0 ), 1000000 , gasPrice , code )
4017+
4018+ for _ , err := range vm .txPool .AddRemotesSync ([]* types.Transaction {signedTx }) {
4019+ require .NoError (t , err )
4020+ }
4021+
4022+ blk , err := vm .BuildBlock (ctx )
4023+ require .NoError (t , err )
4024+ require .NoError (t , blk .Verify (ctx ))
4025+ require .NoError (t , vm .SetPreference (ctx , blk .ID ()))
4026+ require .NoError (t , blk .Accept (ctx ))
4027+
4028+ ethBlock := blk .(* chain.BlockWrapper ).Block .(* wrappedBlock ).ethBlock
4029+ receipts := vm .blockChain .GetReceiptsByHash (ethBlock .Hash ())
4030+ require .Len (t , receipts , len (ethBlock .Transactions ()))
4031+
4032+ found := false
4033+ for i , btx := range ethBlock .Transactions () {
4034+ if btx .Hash () == signedTx .Hash () {
4035+ found = true
4036+ require .Equal (t , types .ReceiptStatusSuccessful , receipts [i ].Status )
4037+ break
4038+ }
4039+ }
4040+ require .True (t , found , "deployContract: expected deploy tx %s to be included in block %s (caller=%s, nonce=%d)" ,
4041+ signedTx .Hash ().Hex (),
4042+ ethBlock .Hash ().Hex (),
4043+ callerAddr .Hex (),
4044+ nonce ,
4045+ )
4046+
4047+ return crypto .CreateAddress (callerAddr , nonce )
4048+ }
4049+
4050+ func TestDelegatePrecompile_BehaviorAcrossUpgrades (t * testing.T ) {
4051+ ctx := context .Background ()
4052+ tests := []struct {
4053+ name string
4054+ fork upgradetest.Fork
4055+ deployGasPrice * big.Int
4056+ txGasPrice * big.Int
4057+ preDeployTime int64
4058+ setTime int64
4059+ refillCapacityFortuna bool
4060+ wantIncluded bool
4061+ wantReceiptStatus uint64
4062+ }{
4063+ {
4064+ name : "granite_should_revert" ,
4065+ fork : upgradetest .Granite ,
4066+ deployGasPrice : big .NewInt (testMinGasPrice ),
4067+ txGasPrice : big .NewInt (testMinGasPrice ),
4068+ // Time is irrelevant as only the fork dictates the logic
4069+ refillCapacityFortuna : false ,
4070+ wantIncluded : true ,
4071+ wantReceiptStatus : types .ReceiptStatusFailed ,
4072+ },
4073+ {
4074+ name : "fortuna_post_cutoff_should_invalidate" ,
4075+ fork : upgradetest .Fortuna ,
4076+ deployGasPrice : big .NewInt (testMinGasPrice ),
4077+ txGasPrice : big .NewInt (testMinGasPrice ),
4078+ setTime : params .InvalidateDelegateUnix + 1 ,
4079+ refillCapacityFortuna : true ,
4080+ wantIncluded : false ,
4081+ },
4082+ {
4083+ name : "fortuna_pre_cutoff_should_succeed" ,
4084+ fork : upgradetest .Fortuna ,
4085+ deployGasPrice : big .NewInt (testMinGasPrice ),
4086+ txGasPrice : big .NewInt (testMinGasPrice ),
4087+ preDeployTime : params .InvalidateDelegateUnix - acp176 .TimeToFillCapacity - 1 ,
4088+ refillCapacityFortuna : true ,
4089+ wantIncluded : true ,
4090+ wantReceiptStatus : types .ReceiptStatusSuccessful ,
4091+ },
4092+ }
4093+
4094+ for _ , tt := range tests {
4095+ t .Run (tt .name , func (t * testing.T ) {
4096+ genesis := & core.Genesis {}
4097+ require .NoError (t , genesis .UnmarshalJSON ([]byte (toGenesisJSON (paramstest .ForkToChainConfig [tt .fork ]))))
4098+ params .GetExtra (genesis .Config ).GenesisPrecompiles = extras.Precompiles {
4099+ warpcontract .ConfigKey : warpcontract .NewDefaultConfig (utils .TimeToNewUint64 (upgrade .InitiallyActiveTime )),
4100+ }
4101+ genesisJSON , err := genesis .MarshalJSON ()
4102+ require .NoError (t , err )
4103+
4104+ vm := newVM (t , testVMConfig {
4105+ genesisJSON : string (genesisJSON ),
4106+ fork : & tt .fork ,
4107+ }).vm
4108+ defer vm .Shutdown (ctx )
4109+
4110+ if tt .preDeployTime != 0 {
4111+ vm .clock .Set (time .Unix (tt .preDeployTime , 0 ))
4112+ }
4113+
4114+ contractAddr := deployContract (ctx , t , vm , tt .deployGasPrice , common .FromHex (delegateCallPrecompileCode ))
4115+
4116+ if tt .setTime != 0 {
4117+ vm .clock .Set (time .Unix (tt .setTime , 0 ))
4118+ }
4119+
4120+ if tt .refillCapacityFortuna {
4121+ // Ensure gas capacity is refilled relative to the parent block's timestamp
4122+ parent := vm .blockChain .CurrentBlock ()
4123+ parentTime := time .Unix (int64 (parent .Time ), 0 )
4124+ minRefillTime := parentTime .Add (acp176 .TimeToFillCapacity * time .Second )
4125+ if vm .clock .Time ().Before (minRefillTime ) {
4126+ vm .clock .Set (minRefillTime )
4127+ }
4128+ }
4129+
4130+ data := crypto .Keccak256 ([]byte ("delegateSendHello()" ))[:4 ]
4131+ nonce := vm .txPool .Nonce (testEthAddrs [0 ])
4132+ signedTx := newSignedLegacyTx (t , vm .chainConfig , testKeys [0 ].ToECDSA (), nonce , & contractAddr , big .NewInt (0 ), 100000 , tt .txGasPrice , data )
4133+ for _ , err := range vm .txPool .AddRemotesSync ([]* types.Transaction {signedTx }) {
4134+ require .NoError (t , err )
4135+ }
4136+
4137+ blk , err := vm .BuildBlock (ctx )
4138+
4139+ if ! tt .wantIncluded {
4140+ // On subnet-evm, InvalidateExecution causes the transaction to be excluded from the block.
4141+ // BuildBlock will create a block but it will fail verification because it's empty
4142+ // and subnet-evm doesn't allow empty blocks.
4143+ require .Error (t , err , "BuildBlock should fail because it would create an empty block" )
4144+ require .ErrorContains (t , err , "empty block" , "Should fail with empty block error" )
4145+ return
4146+ }
4147+
4148+ require .NoError (t , err )
4149+ require .NoError (t , blk .Verify (ctx ))
4150+ require .NoError (t , vm .SetPreference (ctx , blk .ID ()))
4151+ require .NoError (t , blk .Accept (ctx ))
4152+
4153+ ethBlock := blk .(* chain.BlockWrapper ).Block .(* wrappedBlock ).ethBlock
4154+ require .Len (t , ethBlock .Transactions (), 1 )
4155+ receipts := vm .blockChain .GetReceiptsByHash (ethBlock .Hash ())
4156+ require .Len (t , receipts , 1 )
4157+ require .Equal (t , tt .wantReceiptStatus , receipts [0 ].Status )
4158+ })
4159+ }
4160+ }
0 commit comments