diff --git a/bindings/ccip/receiver_registry/receiver_registry.go b/bindings/ccip/receiver_registry/receiver_registry.go index e1253ff5e..f0b0d87da 100644 --- a/bindings/ccip/receiver_registry/receiver_registry.go +++ b/bindings/ccip/receiver_registry/receiver_registry.go @@ -24,6 +24,7 @@ var ( type ReceiverRegistryInterface interface { TypeAndVersion(opts *bind.CallOpts) (string, error) IsRegisteredReceiver(opts *bind.CallOpts, receiverAddress aptos.AccountAddress) (bool, error) + IsRegisteredReceiverV2(opts *bind.CallOpts, receiverAddress aptos.AccountAddress) (bool, error) // Encoder returns the encoder implementation of this module. Encoder() ReceiverRegistryEncoder @@ -32,6 +33,7 @@ type ReceiverRegistryInterface interface { type ReceiverRegistryEncoder interface { TypeAndVersion() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) IsRegisteredReceiver(receiverAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + IsRegisteredReceiverV2(receiverAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) FinishReceive(receiverAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) } @@ -54,6 +56,7 @@ const ( E_NON_EMPTY_INPUT uint64 = 5 E_PROOF_TYPE_ACCOUNT_MISMATCH uint64 = 6 E_PROOF_TYPE_MODULE_MISMATCH uint64 = 7 + E_UNAUTHORIZED uint64 = 8 ) // Structs @@ -61,15 +64,25 @@ const ( type ReceiverRegistryState struct { } +type ReceiverRegistryEventsV2 struct { +} + type CCIPReceiverRegistration struct { DispatchMetadata bind.StdObject `move:"aptos_framework::object::Object"` } +type CCIPReceiverRegistrationV2 struct { +} + type ReceiverRegistered struct { ReceiverAddress aptos.AccountAddress `move:"address"` ReceiverModuleName []byte `move:"vector"` } +type ReceiverRegisteredV2 struct { + ReceiverAddress aptos.AccountAddress `move:"address"` +} + type ReceiverRegistryContract struct { *bind.BoundContract receiverRegistryEncoder @@ -125,6 +138,27 @@ func (c ReceiverRegistryContract) IsRegisteredReceiver(opts *bind.CallOpts, rece return r0, nil } +func (c ReceiverRegistryContract) IsRegisteredReceiverV2(opts *bind.CallOpts, receiverAddress aptos.AccountAddress) (bool, error) { + module, function, typeTags, args, err := c.receiverRegistryEncoder.IsRegisteredReceiverV2(receiverAddress) + if err != nil { + return *new(bool), err + } + + callData, err := c.Call(opts, module, function, typeTags, args) + if err != nil { + return *new(bool), err + } + + var ( + r0 bool + ) + + if err := codec.DecodeAptosJsonArray(callData, &r0); err != nil { + return *new(bool), err + } + return r0, nil +} + // Entry Functions // Encoder @@ -144,6 +178,14 @@ func (c receiverRegistryEncoder) IsRegisteredReceiver(receiverAddress aptos.Acco }) } +func (c receiverRegistryEncoder) IsRegisteredReceiverV2(receiverAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("is_registered_receiver_v2", nil, []string{ + "address", + }, []any{ + receiverAddress, + }) +} + func (c receiverRegistryEncoder) FinishReceive(receiverAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { return c.BoundContract.Encode("finish_receive", nil, []string{ "address", diff --git a/bindings/ccip/token_admin_registry/token_admin_registry.go b/bindings/ccip/token_admin_registry/token_admin_registry.go index 6974b4b14..caaef27de 100644 --- a/bindings/ccip/token_admin_registry/token_admin_registry.go +++ b/bindings/ccip/token_admin_registry/token_admin_registry.go @@ -26,6 +26,8 @@ type TokenAdminRegistryInterface interface { GetPools(opts *bind.CallOpts, localTokens []aptos.AccountAddress) ([]aptos.AccountAddress, error) GetPool(opts *bind.CallOpts, localToken aptos.AccountAddress) (aptos.AccountAddress, error) GetPoolLocalToken(opts *bind.CallOpts, tokenPoolAddress aptos.AccountAddress) (aptos.AccountAddress, error) + GetPoolLocalTokenV2(opts *bind.CallOpts, tokenPoolAddress aptos.AccountAddress) (aptos.AccountAddress, error) + HasTokenPoolRegistrationV2(opts *bind.CallOpts, tokenPoolAddress aptos.AccountAddress) (bool, error) GetTokenConfig(opts *bind.CallOpts, localToken aptos.AccountAddress) (aptos.AccountAddress, aptos.AccountAddress, aptos.AccountAddress, error) GetAllConfiguredTokens(opts *bind.CallOpts, startKey aptos.AccountAddress, maxCount uint64) ([]aptos.AccountAddress, aptos.AccountAddress, bool, error) IsAdministrator(opts *bind.CallOpts, localToken aptos.AccountAddress, administrator aptos.AccountAddress) (bool, error) @@ -45,6 +47,8 @@ type TokenAdminRegistryEncoder interface { GetPools(localTokens []aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) GetPool(localToken aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) GetPoolLocalToken(tokenPoolAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + GetPoolLocalTokenV2(tokenPoolAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + HasTokenPoolRegistrationV2(tokenPoolAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) GetTokenConfig(localToken aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) GetAllConfiguredTokens(startKey aptos.AccountAddress, maxCount uint64) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) IsAdministrator(localToken aptos.AccountAddress, administrator aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) @@ -101,6 +105,8 @@ const ( E_ADMIN_NOT_SET_FOR_TOKEN uint64 = 27 E_ADMIN_ALREADY_SET_FOR_TOKEN uint64 = 28 E_ZERO_ADDRESS uint64 = 29 + E_POOL_NOT_REGISTERED uint64 = 30 + E_TOKEN_MISMATCH uint64 = 31 ) // Structs @@ -150,6 +156,14 @@ type ReleaseOrMintOutputV1 struct { DestinationAmount uint64 `move:"u64"` } +type TokenPoolCallbacks struct { +} + +type TokenPoolRegistrationV2 struct { + Callbacks TokenPoolCallbacks `move:"TokenPoolCallbacks"` + LocalToken aptos.AccountAddress `move:"address"` +} + type PoolSet struct { LocalToken aptos.AccountAddress `move:"address"` PreviousPoolAddress aptos.AccountAddress `move:"address"` @@ -272,6 +286,48 @@ func (c TokenAdminRegistryContract) GetPoolLocalToken(opts *bind.CallOpts, token return r0, nil } +func (c TokenAdminRegistryContract) GetPoolLocalTokenV2(opts *bind.CallOpts, tokenPoolAddress aptos.AccountAddress) (aptos.AccountAddress, error) { + module, function, typeTags, args, err := c.tokenAdminRegistryEncoder.GetPoolLocalTokenV2(tokenPoolAddress) + if err != nil { + return *new(aptos.AccountAddress), err + } + + callData, err := c.Call(opts, module, function, typeTags, args) + if err != nil { + return *new(aptos.AccountAddress), err + } + + var ( + r0 aptos.AccountAddress + ) + + if err := codec.DecodeAptosJsonArray(callData, &r0); err != nil { + return *new(aptos.AccountAddress), err + } + return r0, nil +} + +func (c TokenAdminRegistryContract) HasTokenPoolRegistrationV2(opts *bind.CallOpts, tokenPoolAddress aptos.AccountAddress) (bool, error) { + module, function, typeTags, args, err := c.tokenAdminRegistryEncoder.HasTokenPoolRegistrationV2(tokenPoolAddress) + if err != nil { + return *new(bool), err + } + + callData, err := c.Call(opts, module, function, typeTags, args) + if err != nil { + return *new(bool), err + } + + var ( + r0 bool + ) + + if err := codec.DecodeAptosJsonArray(callData, &r0); err != nil { + return *new(bool), err + } + return r0, nil +} + func (c TokenAdminRegistryContract) GetTokenConfig(opts *bind.CallOpts, localToken aptos.AccountAddress) (aptos.AccountAddress, aptos.AccountAddress, aptos.AccountAddress, error) { module, function, typeTags, args, err := c.tokenAdminRegistryEncoder.GetTokenConfig(localToken) if err != nil { @@ -419,6 +475,22 @@ func (c tokenAdminRegistryEncoder) GetPoolLocalToken(tokenPoolAddress aptos.Acco }) } +func (c tokenAdminRegistryEncoder) GetPoolLocalTokenV2(tokenPoolAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("get_pool_local_token_v2", nil, []string{ + "address", + }, []any{ + tokenPoolAddress, + }) +} + +func (c tokenAdminRegistryEncoder) HasTokenPoolRegistrationV2(tokenPoolAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("has_token_pool_registration_v2", nil, []string{ + "address", + }, []any{ + tokenPoolAddress, + }) +} + func (c tokenAdminRegistryEncoder) GetTokenConfig(localToken aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { return c.BoundContract.Encode("get_token_config", nil, []string{ "address", diff --git a/bindings/ccip_dummy_receiver/ccip_dummy_receiver.go b/bindings/ccip_dummy_receiver/ccip_dummy_receiver.go index 58647a553..8cb21f000 100644 --- a/bindings/ccip_dummy_receiver/ccip_dummy_receiver.go +++ b/bindings/ccip_dummy_receiver/ccip_dummy_receiver.go @@ -6,6 +6,7 @@ import ( "github.com/smartcontractkit/chainlink-aptos/bindings/bind" module_dummy_receiver "github.com/smartcontractkit/chainlink-aptos/bindings/ccip_dummy_receiver/dummy_receiver" + module_ptt_dummy_receiver "github.com/smartcontractkit/chainlink-aptos/bindings/ccip_dummy_receiver/ptt_dummy_receiver" "github.com/smartcontractkit/chainlink-aptos/bindings/compile" "github.com/smartcontractkit/chainlink-aptos/contracts" ) @@ -14,6 +15,7 @@ type CCIPDummyReceiver interface { Address() aptos.AccountAddress DummyReceiver() module_dummy_receiver.DummyReceiverInterface + PTTDummyReceiver() module_ptt_dummy_receiver.PttDummyReceiverInterface } var _ CCIPDummyReceiver = CCIPDummyReceiverContract{} @@ -21,7 +23,8 @@ var _ CCIPDummyReceiver = CCIPDummyReceiverContract{} type CCIPDummyReceiverContract struct { address aptos.AccountAddress - dummyReceiver module_dummy_receiver.DummyReceiverInterface + dummyReceiver module_dummy_receiver.DummyReceiverInterface + pttDummyReceiver module_ptt_dummy_receiver.PttDummyReceiverInterface } func (C CCIPDummyReceiverContract) Address() aptos.AccountAddress { @@ -32,16 +35,22 @@ func (C CCIPDummyReceiverContract) DummyReceiver() module_dummy_receiver.DummyRe return C.dummyReceiver } +func (C CCIPDummyReceiverContract) PTTDummyReceiver() module_ptt_dummy_receiver.PttDummyReceiverInterface { + return C.pttDummyReceiver +} + var FunctionInfo = bind.MustParseFunctionInfo( module_dummy_receiver.FunctionInfo, + module_ptt_dummy_receiver.FunctionInfo, ) -func Compile(address aptos.AccountAddress, ccipAddress aptos.AccountAddress, mcmsAddress aptos.AccountAddress) (compile.CompiledPackage, error) { +func Compile(address aptos.AccountAddress, ccipAddress aptos.AccountAddress, mcmsAddress aptos.AccountAddress, deployer aptos.AccountAddress) (compile.CompiledPackage, error) { namedAddresses := map[string]aptos.AccountAddress{ "ccip_dummy_receiver": address, "ccip": ccipAddress, "mcms": mcmsAddress, "mcms_register_entrypoints": aptos.AccountZero, + "deployer": deployer, } // Compile using CLI return compile.CompilePackage(contracts.CCIPDummyReceiver, namedAddresses) @@ -49,13 +58,21 @@ func Compile(address aptos.AccountAddress, ccipAddress aptos.AccountAddress, mcm func Bind(address aptos.AccountAddress, client aptos.AptosRpcClient) CCIPDummyReceiver { return CCIPDummyReceiverContract{ - address: address, - dummyReceiver: module_dummy_receiver.NewDummyReceiver(address, client), + address: address, + dummyReceiver: module_dummy_receiver.NewDummyReceiver(address, client), + pttDummyReceiver: module_ptt_dummy_receiver.NewPttDummyReceiver(address, client), } } +const ( + DefaultSeed = "chainlink_ccip_dummy_receiver" +) + // DeployToObject deploys the dummmy receiver contract to a new named object. // The resulting address will be calculated using the deployer's account address and the next sequence number +// +// NOTE: This deployment method will NOT work with ptt_dummy_receiver module as it requires resource account. +// Use DeployToResourceAccount if you need PTT functionality. func DeployToObject( auth aptos.TransactionSigner, client aptos.AptosRpcClient, @@ -66,6 +83,7 @@ func DeployToObject( "ccip": ccipAddress, "mcms": mcmsAddress, "mcms_register_entrypoints": aptos.AccountZero, + "deployer": auth.AccountAddress(), // Required for compilation, but ptt_dummy_receiver won't work with object deployment } address, tx, err := bind.DeployPackageToObject(auth, client, contracts.CCIPDummyReceiver, namedAddresses) if err != nil { @@ -73,3 +91,30 @@ func DeployToObject( } return address, tx, Bind(address, client), nil } + +// DeployToResourceAccount deploys the dummy receiver contract to a new resource account. +// The address of that resource account is determined by the deployer account + an optional seed. +// If no seed is provided, the default seed DefaultSeed is used. +func DeployToResourceAccount( + auth aptos.TransactionSigner, + client aptos.AptosRpcClient, + ccipAddress, + mcmsAddress aptos.AccountAddress, + seed ...string, +) (aptos.AccountAddress, *api.PendingTransaction, CCIPDummyReceiver, error) { + dummyReceiverSeed := DefaultSeed + if len(seed) > 0 { + dummyReceiverSeed = seed[0] + } + namedAddresses := map[string]aptos.AccountAddress{ + "ccip": ccipAddress, + "mcms": mcmsAddress, + "mcms_register_entrypoints": aptos.AccountZero, + "deployer": auth.AccountAddress(), // Origin account where Container is stored + } + address, tx, err := bind.DeployPackageToResourceAccount(auth, client, contracts.CCIPDummyReceiver, dummyReceiverSeed, namedAddresses) + if err != nil { + return aptos.AccountAddress{}, nil, nil, err + } + return address, tx, Bind(address, client), nil +} diff --git a/bindings/ccip_dummy_receiver/ccip_dummy_receiver_test.go b/bindings/ccip_dummy_receiver/ccip_dummy_receiver_test.go index 1e3cd326e..f07e3b285 100644 --- a/bindings/ccip_dummy_receiver/ccip_dummy_receiver_test.go +++ b/bindings/ccip_dummy_receiver/ccip_dummy_receiver_test.go @@ -9,7 +9,7 @@ import ( func TestCompile(t *testing.T) { t.Parallel() - output, err := Compile(aptos.AccountOne, aptos.AccountOne, aptos.AccountThree) + output, err := Compile(aptos.AccountOne, aptos.AccountOne, aptos.AccountThree, aptos.AccountFour) require.NoError(t, err) require.NotZero(t, output.Metadata, "Compilation resulted in no metadata") require.NotZero(t, output.Bytecode, "Compilation resulted in no bytecode") diff --git a/bindings/ccip_dummy_receiver/dummy_receiver/dummy_receiver.go b/bindings/ccip_dummy_receiver/dummy_receiver/dummy_receiver.go index 65c0d3c25..0989c083a 100644 --- a/bindings/ccip_dummy_receiver/dummy_receiver/dummy_receiver.go +++ b/bindings/ccip_dummy_receiver/dummy_receiver/dummy_receiver.go @@ -43,6 +43,11 @@ func NewDummyReceiver(address aptos.AccountAddress, client aptos.AptosRpcClient) } } +// Constants +const ( + E_TEST_ABORT uint64 = 1 +) + // Structs type ReceivedMessage struct { diff --git a/bindings/ccip_dummy_receiver/ptt_dummy_receiver/ptt_dummy_receiver.go b/bindings/ccip_dummy_receiver/ptt_dummy_receiver/ptt_dummy_receiver.go new file mode 100644 index 000000000..be352beee --- /dev/null +++ b/bindings/ccip_dummy_receiver/ptt_dummy_receiver/ptt_dummy_receiver.go @@ -0,0 +1,162 @@ +// Code generated - DO NOT EDIT. +// This file is a generated binding and any manual changes will be lost. + +package module_ptt_dummy_receiver + +import ( + "math/big" + + "github.com/aptos-labs/aptos-go-sdk" + "github.com/aptos-labs/aptos-go-sdk/api" + + "github.com/smartcontractkit/chainlink-aptos/bindings/bind" + "github.com/smartcontractkit/chainlink-aptos/relayer/codec" +) + +var ( + _ = aptos.AccountAddress{} + _ = api.PendingTransaction{} + _ = big.NewInt + _ = bind.NewBoundContract + _ = codec.DecodeAptosJsonValue +) + +type PttDummyReceiverInterface interface { + TypeAndVersion(opts *bind.CallOpts) (string, error) + GetStateAddress(opts *bind.CallOpts) (aptos.AccountAddress, error) + + WithdrawToken(opts *bind.TransactOpts, recipient aptos.AccountAddress, tokenAddress aptos.AccountAddress) (*api.PendingTransaction, error) + + // Encoder returns the encoder implementation of this module. + Encoder() PttDummyReceiverEncoder +} + +type PttDummyReceiverEncoder interface { + TypeAndVersion() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + GetStateAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + WithdrawToken(recipient aptos.AccountAddress, tokenAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) +} + +const FunctionInfo = `[{"package":"ccip_dummy_receiver","module":"ptt_dummy_receiver","name":"withdraw_token","parameters":[{"name":"recipient","type":"address"},{"name":"token_address","type":"address"}]}]` + +func NewPttDummyReceiver(address aptos.AccountAddress, client aptos.AptosRpcClient) PttDummyReceiverInterface { + contract := bind.NewBoundContract(address, "ccip_dummy_receiver", "ptt_dummy_receiver", client) + return PttDummyReceiverContract{ + BoundContract: contract, + pttDummyReceiverEncoder: pttDummyReceiverEncoder{BoundContract: contract}, + } +} + +// Constants +const ( + E_RESOURCE_NOT_FOUND_ON_ACCOUNT uint64 = 1 + E_UNAUTHORIZED uint64 = 2 + E_NO_TOKENS_AVAILABLE_TO_WITHDRAW uint64 = 3 + E_TEST_ABORT uint64 = 4 +) + +// Structs + +type ReceivedMessage struct { + Data []byte `move:"vector"` +} + +type ForwardedTokens struct { + FinalRecipient aptos.AccountAddress `move:"address"` +} + +type ReceivedTokensOnly struct { + TokenCount uint64 `move:"u64"` +} + +type CCIPReceiverState struct { +} + +type PttDummyReceiverContract struct { + *bind.BoundContract + pttDummyReceiverEncoder +} + +var _ PttDummyReceiverInterface = PttDummyReceiverContract{} + +func (c PttDummyReceiverContract) Encoder() PttDummyReceiverEncoder { + return c.pttDummyReceiverEncoder +} + +// View Functions + +func (c PttDummyReceiverContract) TypeAndVersion(opts *bind.CallOpts) (string, error) { + module, function, typeTags, args, err := c.pttDummyReceiverEncoder.TypeAndVersion() + if err != nil { + return *new(string), err + } + + callData, err := c.Call(opts, module, function, typeTags, args) + if err != nil { + return *new(string), err + } + + var ( + r0 string + ) + + if err := codec.DecodeAptosJsonArray(callData, &r0); err != nil { + return *new(string), err + } + return r0, nil +} + +func (c PttDummyReceiverContract) GetStateAddress(opts *bind.CallOpts) (aptos.AccountAddress, error) { + module, function, typeTags, args, err := c.pttDummyReceiverEncoder.GetStateAddress() + if err != nil { + return *new(aptos.AccountAddress), err + } + + callData, err := c.Call(opts, module, function, typeTags, args) + if err != nil { + return *new(aptos.AccountAddress), err + } + + var ( + r0 aptos.AccountAddress + ) + + if err := codec.DecodeAptosJsonArray(callData, &r0); err != nil { + return *new(aptos.AccountAddress), err + } + return r0, nil +} + +// Entry Functions + +func (c PttDummyReceiverContract) WithdrawToken(opts *bind.TransactOpts, recipient aptos.AccountAddress, tokenAddress aptos.AccountAddress) (*api.PendingTransaction, error) { + module, function, typeTags, args, err := c.pttDummyReceiverEncoder.WithdrawToken(recipient, tokenAddress) + if err != nil { + return nil, err + } + + return c.BoundContract.Transact(opts, module, function, typeTags, args) +} + +// Encoder +type pttDummyReceiverEncoder struct { + *bind.BoundContract +} + +func (c pttDummyReceiverEncoder) TypeAndVersion() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("type_and_version", nil, []string{}, []any{}) +} + +func (c pttDummyReceiverEncoder) GetStateAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("get_state_address", nil, []string{}, []any{}) +} + +func (c pttDummyReceiverEncoder) WithdrawToken(recipient aptos.AccountAddress, tokenAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("withdraw_token", nil, []string{ + "address", + "address", + }, []any{ + recipient, + tokenAddress, + }) +} diff --git a/bindings/ccip_token_pools/burn_mint_token_pool/burn_mint_token_pool/burn_mint_token_pool.go b/bindings/ccip_token_pools/burn_mint_token_pool/burn_mint_token_pool/burn_mint_token_pool.go index ee8f84a24..c2401fdb7 100644 --- a/bindings/ccip_token_pools/burn_mint_token_pool/burn_mint_token_pool/burn_mint_token_pool.go +++ b/bindings/ccip_token_pools/burn_mint_token_pool/burn_mint_token_pool/burn_mint_token_pool.go @@ -88,13 +88,14 @@ type BurnMintTokenPoolEncoder interface { TransferOwnership(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) AcceptOwnership() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) ExecuteOwnershipTransfer(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) StoreAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) AssertCanInitialize(callerAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) MCMSEntrypoint(Metadata aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) RegisterMCMSEntrypoint(moduleName []byte) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) } -const FunctionInfo = `[{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"accept_ownership","parameters":null},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"assert_can_initialize","parameters":[{"name":"caller_address","type":"address"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"store_address","parameters":null},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]}]` +const FunctionInfo = `[{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"accept_ownership","parameters":null},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"assert_can_initialize","parameters":[{"name":"caller_address","type":"address"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"register_v2_callbacks","parameters":null},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"store_address","parameters":null},{"package":"burn_mint_token_pool","module":"burn_mint_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]}]` func NewBurnMintTokenPool(address aptos.AccountAddress, client aptos.AptosRpcClient) BurnMintTokenPoolInterface { contract := bind.NewBoundContract(address, "burn_mint_token_pool", "burn_mint_token_pool", client) @@ -854,6 +855,10 @@ func (c burnMintTokenPoolEncoder) ExecuteOwnershipTransfer(to aptos.AccountAddre }) } +func (c burnMintTokenPoolEncoder) RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("register_v2_callbacks", nil, []string{}, []any{}) +} + func (c burnMintTokenPoolEncoder) StoreAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { return c.BoundContract.Encode("store_address", nil, []string{}, []any{}) } diff --git a/bindings/ccip_token_pools/lock_release_token_pool/lock_release_token_pool/lock_release_token_pool.go b/bindings/ccip_token_pools/lock_release_token_pool/lock_release_token_pool/lock_release_token_pool.go index 413b73b8b..daebaede5 100644 --- a/bindings/ccip_token_pools/lock_release_token_pool/lock_release_token_pool/lock_release_token_pool.go +++ b/bindings/ccip_token_pools/lock_release_token_pool/lock_release_token_pool/lock_release_token_pool.go @@ -102,13 +102,14 @@ type LockReleaseTokenPoolEncoder interface { TransferOwnership(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) AcceptOwnership() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) ExecuteOwnershipTransfer(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) StoreAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) AssertCanInitialize(callerAddress aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) MCMSEntrypoint(Metadata aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) RegisterMCMSEntrypoint(moduleName []byte) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) } -const FunctionInfo = `[{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"accept_ownership","parameters":null},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"assert_can_initialize","parameters":[{"name":"caller_address","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"provide_liquidity","parameters":[{"name":"amount","type":"u64"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"set_rebalancer","parameters":[{"name":"rebalancer","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"store_address","parameters":null},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"withdraw_liquidity","parameters":[{"name":"amount","type":"u64"}]}]` +const FunctionInfo = `[{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"accept_ownership","parameters":null},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"assert_can_initialize","parameters":[{"name":"caller_address","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"provide_liquidity","parameters":[{"name":"amount","type":"u64"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"register_v2_callbacks","parameters":null},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"set_rebalancer","parameters":[{"name":"rebalancer","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"store_address","parameters":null},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]},{"package":"lock_release_token_pool","module":"lock_release_token_pool","name":"withdraw_liquidity","parameters":[{"name":"amount","type":"u64"}]}]` func NewLockReleaseTokenPool(address aptos.AccountAddress, client aptos.AptosRpcClient) LockReleaseTokenPoolInterface { contract := bind.NewBoundContract(address, "lock_release_token_pool", "lock_release_token_pool", client) @@ -1022,6 +1023,10 @@ func (c lockReleaseTokenPoolEncoder) ExecuteOwnershipTransfer(to aptos.AccountAd }) } +func (c lockReleaseTokenPoolEncoder) RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("register_v2_callbacks", nil, []string{}, []any{}) +} + func (c lockReleaseTokenPoolEncoder) StoreAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { return c.BoundContract.Encode("store_address", nil, []string{}, []any{}) } diff --git a/bindings/ccip_token_pools/managed_token_pool/managed_token_pool/managed_token_pool.go b/bindings/ccip_token_pools/managed_token_pool/managed_token_pool/managed_token_pool.go index 585473a26..ddb9fded9 100644 --- a/bindings/ccip_token_pools/managed_token_pool/managed_token_pool/managed_token_pool.go +++ b/bindings/ccip_token_pools/managed_token_pool/managed_token_pool/managed_token_pool.go @@ -88,12 +88,13 @@ type ManagedTokenPoolEncoder interface { TransferOwnership(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) AcceptOwnership() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) ExecuteOwnershipTransfer(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) StoreAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) MCMSEntrypoint(Metadata aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) RegisterMCMSEntrypoint(moduleName []byte) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) } -const FunctionInfo = `[{"package":"managed_token_pool","module":"managed_token_pool","name":"accept_ownership","parameters":null},{"package":"managed_token_pool","module":"managed_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"store_address","parameters":null},{"package":"managed_token_pool","module":"managed_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]}]` +const FunctionInfo = `[{"package":"managed_token_pool","module":"managed_token_pool","name":"accept_ownership","parameters":null},{"package":"managed_token_pool","module":"managed_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"register_v2_callbacks","parameters":null},{"package":"managed_token_pool","module":"managed_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"managed_token_pool","module":"managed_token_pool","name":"store_address","parameters":null},{"package":"managed_token_pool","module":"managed_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]}]` func NewManagedTokenPool(address aptos.AccountAddress, client aptos.AptosRpcClient) ManagedTokenPoolInterface { contract := bind.NewBoundContract(address, "managed_token_pool", "managed_token_pool", client) @@ -105,12 +106,9 @@ func NewManagedTokenPool(address aptos.AccountAddress, client aptos.AptosRpcClie // Constants const ( - E_NOT_PUBLISHER uint64 = 1 - E_ALREADY_INITIALIZED uint64 = 2 - E_INVALID_FUNGIBLE_ASSET uint64 = 3 - E_LOCAL_TOKEN_MISMATCH uint64 = 4 - E_INVALID_ARGUMENTS uint64 = 5 - E_UNKNOWN_FUNCTION uint64 = 6 + E_INVALID_ARGUMENTS uint64 = 1 + E_UNKNOWN_FUNCTION uint64 = 2 + E_NOT_PUBLISHER uint64 = 3 ) // Structs @@ -848,6 +846,10 @@ func (c managedTokenPoolEncoder) ExecuteOwnershipTransfer(to aptos.AccountAddres }) } +func (c managedTokenPoolEncoder) RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("register_v2_callbacks", nil, []string{}, []any{}) +} + func (c managedTokenPoolEncoder) StoreAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { return c.BoundContract.Encode("store_address", nil, []string{}, []any{}) } diff --git a/bindings/ccip_token_pools/regulated_token_pool/regulated_token_pool/regulated_token_pool.go b/bindings/ccip_token_pools/regulated_token_pool/regulated_token_pool/regulated_token_pool.go index 854def7f9..59afd2e16 100644 --- a/bindings/ccip_token_pools/regulated_token_pool/regulated_token_pool/regulated_token_pool.go +++ b/bindings/ccip_token_pools/regulated_token_pool/regulated_token_pool/regulated_token_pool.go @@ -88,12 +88,13 @@ type RegulatedTokenPoolEncoder interface { TransferOwnership(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) AcceptOwnership() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) ExecuteOwnershipTransfer(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) StoreAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) MCMSEntrypoint(Metadata aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) RegisterMCMSEntrypoint(moduleName []byte) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) } -const FunctionInfo = `[{"package":"regulated_token_pool","module":"regulated_token_pool","name":"accept_ownership","parameters":null},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"store_address","parameters":null},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]}]` +const FunctionInfo = `[{"package":"regulated_token_pool","module":"regulated_token_pool","name":"accept_ownership","parameters":null},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"register_v2_callbacks","parameters":null},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"store_address","parameters":null},{"package":"regulated_token_pool","module":"regulated_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]}]` func NewRegulatedTokenPool(address aptos.AccountAddress, client aptos.AptosRpcClient) RegulatedTokenPoolInterface { contract := bind.NewBoundContract(address, "regulated_token_pool", "regulated_token_pool", client) @@ -105,12 +106,9 @@ func NewRegulatedTokenPool(address aptos.AccountAddress, client aptos.AptosRpcCl // Constants const ( - E_NOT_PUBLISHER uint64 = 1 - E_ALREADY_INITIALIZED uint64 = 2 - E_INVALID_FUNGIBLE_ASSET uint64 = 3 - E_LOCAL_TOKEN_MISMATCH uint64 = 4 - E_INVALID_ARGUMENTS uint64 = 5 - E_UNKNOWN_FUNCTION uint64 = 6 + E_INVALID_ARGUMENTS uint64 = 1 + E_UNKNOWN_FUNCTION uint64 = 2 + E_NOT_PUBLISHER uint64 = 3 ) // Structs @@ -848,6 +846,10 @@ func (c regulatedTokenPoolEncoder) ExecuteOwnershipTransfer(to aptos.AccountAddr }) } +func (c regulatedTokenPoolEncoder) RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("register_v2_callbacks", nil, []string{}, []any{}) +} + func (c regulatedTokenPoolEncoder) StoreAddress() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { return c.BoundContract.Encode("store_address", nil, []string{}, []any{}) } diff --git a/bindings/ccip_token_pools/usdc_token_pool/usdc_token_pool/usdc_token_pool.go b/bindings/ccip_token_pools/usdc_token_pool/usdc_token_pool/usdc_token_pool.go index 609e0f985..fa3c97628 100644 --- a/bindings/ccip_token_pools/usdc_token_pool/usdc_token_pool/usdc_token_pool.go +++ b/bindings/ccip_token_pools/usdc_token_pool/usdc_token_pool/usdc_token_pool.go @@ -90,6 +90,7 @@ type USDCTokenPoolEncoder interface { TransferOwnership(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) AcceptOwnership() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) ExecuteOwnershipTransfer(to aptos.AccountAddress) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) + RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) Initialize() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) ParseMessageAndAttestation(payload []byte) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) EncodeDestPoolData(localDomainIdentifier uint32, nonce uint64) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) @@ -101,7 +102,7 @@ type USDCTokenPoolEncoder interface { RegisterMCMSEntrypoint(moduleName []byte) (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) } -const FunctionInfo = `[{"package":"usdc_token_pool","module":"usdc_token_pool","name":"accept_ownership","parameters":null},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"assert_can_initialize","parameters":[{"name":"caller_address","type":"address"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"decode_dest_pool_data","parameters":[{"name":"dest_pool_data","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"encode_dest_pool_data","parameters":[{"name":"local_domain_identifier","type":"u32"},{"name":"nonce","type":"u64"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"initialize","parameters":null},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"parse_message_and_attestation","parameters":[{"name":"payload","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"set_domains","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"remote_domain_identifiers","type":"vector\u003cu32\u003e"},{"name":"allowed_remote_callers","type":"vector\u003cvector\u003cu8\u003e\u003e"},{"name":"enableds","type":"vector\u003cbool\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"store_address","parameters":null},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]}]` +const FunctionInfo = `[{"package":"usdc_token_pool","module":"usdc_token_pool","name":"accept_ownership","parameters":null},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"add_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"apply_allowlist_updates","parameters":[{"name":"removes","type":"vector\u003caddress\u003e"},{"name":"adds","type":"vector\u003caddress\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"apply_chain_updates","parameters":[{"name":"remote_chain_selectors_to_remove","type":"vector\u003cu64\u003e"},{"name":"remote_chain_selectors_to_add","type":"vector\u003cu64\u003e"},{"name":"remote_pool_addresses_to_add","type":"vector\u003cvector\u003cvector\u003cu8\u003e\u003e\u003e"},{"name":"remote_token_addresses_to_add","type":"vector\u003cvector\u003cu8\u003e\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"assert_can_initialize","parameters":[{"name":"caller_address","type":"address"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"decode_dest_pool_data","parameters":[{"name":"dest_pool_data","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"encode_dest_pool_data","parameters":[{"name":"local_domain_identifier","type":"u32"},{"name":"nonce","type":"u64"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"execute_ownership_transfer","parameters":[{"name":"to","type":"address"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"initialize","parameters":null},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"mcms_entrypoint","parameters":[{"name":"_metadata","type":"address"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"parse_message_and_attestation","parameters":[{"name":"payload","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"register_mcms_entrypoint","parameters":[{"name":"module_name","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"register_v2_callbacks","parameters":null},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"remove_remote_pool","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"remote_pool_address","type":"vector\u003cu8\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"set_allowlist_enabled","parameters":[{"name":"enabled","type":"bool"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"set_chain_rate_limiter_config","parameters":[{"name":"remote_chain_selector","type":"u64"},{"name":"outbound_is_enabled","type":"bool"},{"name":"outbound_capacity","type":"u64"},{"name":"outbound_rate","type":"u64"},{"name":"inbound_is_enabled","type":"bool"},{"name":"inbound_capacity","type":"u64"},{"name":"inbound_rate","type":"u64"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"set_chain_rate_limiter_configs","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"outbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"outbound_capacities","type":"vector\u003cu64\u003e"},{"name":"outbound_rates","type":"vector\u003cu64\u003e"},{"name":"inbound_is_enableds","type":"vector\u003cbool\u003e"},{"name":"inbound_capacities","type":"vector\u003cu64\u003e"},{"name":"inbound_rates","type":"vector\u003cu64\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"set_domains","parameters":[{"name":"remote_chain_selectors","type":"vector\u003cu64\u003e"},{"name":"remote_domain_identifiers","type":"vector\u003cu32\u003e"},{"name":"allowed_remote_callers","type":"vector\u003cvector\u003cu8\u003e\u003e"},{"name":"enableds","type":"vector\u003cbool\u003e"}]},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"store_address","parameters":null},{"package":"usdc_token_pool","module":"usdc_token_pool","name":"transfer_ownership","parameters":[{"name":"to","type":"address"}]}]` func NewUSDCTokenPool(address aptos.AccountAddress, client aptos.AptosRpcClient) USDCTokenPoolInterface { contract := bind.NewBoundContract(address, "usdc_token_pool", "usdc_token_pool", client) @@ -912,6 +913,10 @@ func (c usdcTokenPoolEncoder) ExecuteOwnershipTransfer(to aptos.AccountAddress) }) } +func (c usdcTokenPoolEncoder) RegisterV2Callbacks() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { + return c.BoundContract.Encode("register_v2_callbacks", nil, []string{}, []any{}) +} + func (c usdcTokenPoolEncoder) Initialize() (bind.ModuleInformation, string, []aptos.TypeTag, [][]byte, error) { return c.BoundContract.Encode("initialize", nil, []string{}, []any{}) } diff --git a/contracts/ccip/ccip/sources/receiver_dispatcher.move b/contracts/ccip/ccip/sources/receiver_dispatcher.move index ac5b0cb1b..1b5c41014 100644 --- a/contracts/ccip/ccip/sources/receiver_dispatcher.move +++ b/contracts/ccip/ccip/sources/receiver_dispatcher.move @@ -11,9 +11,13 @@ module ccip::receiver_dispatcher { ) { auth::assert_is_allowed_offramp(signer::address_of(caller)); - let dispatch_metadata = - receiver_registry::start_receive(receiver_address, message); - dispatchable_fungible_asset::derived_supply(dispatch_metadata); - receiver_registry::finish_receive(receiver_address); + if (receiver_registry::is_registered_receiver_v2(receiver_address)) { + receiver_registry::invoke_ccip_receive_v2(receiver_address, message); + } else { + let dispatch_metadata = + receiver_registry::start_receive(receiver_address, message); + dispatchable_fungible_asset::derived_supply(dispatch_metadata); + receiver_registry::finish_receive(receiver_address); + } } } diff --git a/contracts/ccip/ccip/sources/receiver_registry.move b/contracts/ccip/ccip/sources/receiver_registry.move index f90b9e96d..93af3d1e7 100644 --- a/contracts/ccip/ccip/sources/receiver_registry.move +++ b/contracts/ccip/ccip/sources/receiver_registry.move @@ -23,6 +23,10 @@ module ccip::receiver_registry { receiver_registered_events: EventHandle } + struct ReceiverRegistryEventsV2 has key { + receiver_registered_v2_events: EventHandle + } + struct CCIPReceiverRegistration has key { ccip_receive_function: FunctionInfo, proof_typeinfo: TypeInfo, @@ -32,12 +36,22 @@ module ccip::receiver_registry { executing_input: Option } + struct CCIPReceiverRegistrationV2 has key { + callback: |client::Any2AptosMessage| has copy + drop + store + } + #[event] struct ReceiverRegistered has store, drop { receiver_address: address, receiver_module_name: vector } + #[event] + struct ReceiverRegisteredV2 has drop, store { + receiver_address: address, + callback: |client::Any2AptosMessage| has copy + drop + store + } + const E_ALREADY_REGISTERED: u64 = 1; const E_UNKNOWN_RECEIVER: u64 = 2; const E_UNKNOWN_PROOF_TYPE: u64 = 3; @@ -45,6 +59,7 @@ module ccip::receiver_registry { const E_NON_EMPTY_INPUT: u64 = 5; const E_PROOF_TYPE_ACCOUNT_MISMATCH: u64 = 6; const E_PROOF_TYPE_MODULE_MISMATCH: u64 = 7; + const E_UNAUTHORIZED: u64 = 8; #[view] public fun type_and_version(): String { @@ -72,7 +87,8 @@ module ccip::receiver_registry { ) acquires ReceiverRegistryState { let receiver_address = signer::address_of(receiver_account); assert!( - !exists(receiver_address), + !exists(receiver_address) + && !exists(receiver_address), error::invalid_argument(E_ALREADY_REGISTERED) ); @@ -138,9 +154,54 @@ module ccip::receiver_registry { ); } + /// Registers a V2 CCIP receiver using a function-value callback (closure). + /// + /// Upgrade path: existing legacy receivers can upgrade to V2 by calling this function, + /// which supersedes the legacy registration without requiring unregistration. + /// New receivers should use V2 directly. Once V2 is registered, legacy registration + /// via `register_receiver()` is rejected. + /// + /// SECURITY: The callback MUST wrap a private `#[persistent]` function. Exposing the + /// receive function as `public fun` allows any caller to construct an `Any2AptosMessage` + /// and invoke the receiver directly, + /// + /// Correct pattern: + /// ``` + /// #[persistent] + /// fun ccip_receive_v2(message: client::Any2AptosMessage) { ... } + /// + /// fun init_module(publisher: &signer) { + /// receiver_registry::register_receiver_v2( + /// publisher, |message| ccip_receive_v2(message) + /// ); + /// } + /// ``` + public fun register_receiver_v2( + receiver_account: &signer, callback: |client::Any2AptosMessage| has copy + drop + store + ) { + let receiver_address = signer::address_of(receiver_account); + assert!( + !exists(receiver_address), + error::invalid_argument(E_ALREADY_REGISTERED) + ); + + move_to(receiver_account, CCIPReceiverRegistrationV2 { callback }); + + event::emit_event( + &mut borrow_events_v2_mut().receiver_registered_v2_events, + ReceiverRegisteredV2 { receiver_address, callback } + ); + } + #[view] public fun is_registered_receiver(receiver_address: address): bool { exists(receiver_address) + || exists(receiver_address) + } + + #[view] + public fun is_registered_receiver_v2(receiver_address: address): bool { + exists(receiver_address) } public fun get_receiver_input( @@ -185,6 +246,18 @@ module ccip::receiver_registry { ); } + public(friend) fun invoke_ccip_receive_v2( + receiver_address: address, message: client::Any2AptosMessage + ) acquires CCIPReceiverRegistrationV2 { + assert!( + exists(receiver_address), + error::invalid_argument(E_UNKNOWN_RECEIVER) + ); + + let registration = borrow_global(receiver_address); + (registration.callback) (message); + } + inline fun borrow_state(): &ReceiverRegistryState { borrow_global(state_object::object_address()) } @@ -202,6 +275,22 @@ module ccip::receiver_registry { borrow_global_mut(receiver_address) } + inline fun borrow_events_v2_mut(): &mut ReceiverRegistryEventsV2 { + let state_signer = &state_object::object_signer(); + let state_address = state_object::object_address(); + + if (!exists(state_address)) { + move_to( + state_signer, + ReceiverRegistryEventsV2 { + receiver_registered_v2_events: account::new_event_handle(state_signer) + } + ); + }; + + borrow_global_mut(state_address) + } + #[test_only] public fun init_module_for_testing(publisher: &signer) { init_module(publisher); diff --git a/contracts/ccip/ccip/sources/token_admin_dispatcher.move b/contracts/ccip/ccip/sources/token_admin_dispatcher.move index 7645fbfec..c3e17f8cb 100644 --- a/contracts/ccip/ccip/sources/token_admin_dispatcher.move +++ b/contracts/ccip/ccip/sources/token_admin_dispatcher.move @@ -16,17 +16,27 @@ module ccip::token_admin_dispatcher { ): (vector, vector) { auth::assert_is_allowed_onramp(signer::address_of(caller)); - let dispatch_fungible_store = - token_admin_registry::start_lock_or_burn( + if (token_admin_registry::has_token_pool_registration_v2(token_pool_address)) { + token_admin_registry::lock_or_burn_v2( token_pool_address, + fa, sender, remote_chain_selector, receiver - ); + ) + } else { + let dispatch_fungible_store = + token_admin_registry::start_lock_or_burn( + token_pool_address, + sender, + remote_chain_selector, + receiver + ); - dispatchable_fungible_asset::deposit(dispatch_fungible_store, fa); + dispatchable_fungible_asset::deposit(dispatch_fungible_store, fa); - token_admin_registry::finish_lock_or_burn(token_pool_address) + token_admin_registry::finish_lock_or_burn(token_pool_address) + } } public fun dispatch_release_or_mint( @@ -43,8 +53,8 @@ module ccip::token_admin_dispatcher { ): (FungibleAsset, u64) { auth::assert_is_allowed_offramp(signer::address_of(caller)); - let (dispatch_owner, dispatch_fungible_store) = - token_admin_registry::start_release_or_mint( + if (token_admin_registry::has_token_pool_registration_v2(token_pool_address)) { + token_admin_registry::release_or_mint_v2( token_pool_address, sender, receiver, @@ -54,16 +64,30 @@ module ccip::token_admin_dispatcher { source_pool_address, source_pool_data, offchain_token_data - ); + ) + } else { + let (dispatch_owner, dispatch_fungible_store) = + token_admin_registry::start_release_or_mint( + token_pool_address, + sender, + receiver, + source_amount, + local_token, + remote_chain_selector, + source_pool_address, + source_pool_data, + offchain_token_data + ); - let fa = - dispatchable_fungible_asset::withdraw( - &dispatch_owner, dispatch_fungible_store, 0 - ); + let fa = + dispatchable_fungible_asset::withdraw( + &dispatch_owner, dispatch_fungible_store, 0 + ); - let destination_amount = - token_admin_registry::finish_release_or_mint(token_pool_address); + let destination_amount = + token_admin_registry::finish_release_or_mint(token_pool_address); - (fa, destination_amount) + (fa, destination_amount) + } } } diff --git a/contracts/ccip/ccip/sources/token_admin_registry.move b/contracts/ccip/ccip/sources/token_admin_registry.move index 51518445c..8b2903967 100644 --- a/contracts/ccip/ccip/sources/token_admin_registry.move +++ b/contracts/ccip/ccip/sources/token_admin_registry.move @@ -4,7 +4,7 @@ module ccip::token_admin_registry { use std::error; use std::event::{Self, EventHandle}; use std::function_info::{Self, FunctionInfo}; - use std::fungible_asset::{Self, Metadata, FungibleStore}; + use std::fungible_asset::{Self, Metadata, FungibleStore, FungibleAsset}; use std::object::{Self, Object, ExtendRef, TransferRef}; use std::option::{Self, Option}; use std::signer; @@ -87,6 +87,16 @@ module ccip::token_admin_registry { destination_amount: u64 } + struct TokenPoolCallbacks has copy, drop, store { + lock_or_burn: |FungibleAsset, LockOrBurnInputV1| (vector, vector), + release_or_mint: |ReleaseOrMintInputV1| (FungibleAsset, u64) + } + + struct TokenPoolRegistrationV2 has key { + callbacks: TokenPoolCallbacks, + local_token: address + } + #[event] struct PoolSet has store, drop { local_token: address, @@ -142,6 +152,8 @@ module ccip::token_admin_registry { const E_ADMIN_NOT_SET_FOR_TOKEN: u64 = 27; const E_ADMIN_ALREADY_SET_FOR_TOKEN: u64 = 28; const E_ZERO_ADDRESS: u64 = 29; + const E_POOL_NOT_REGISTERED: u64 = 30; + const E_TOKEN_MISMATCH: u64 = 31; #[view] public fun type_and_version(): String { @@ -214,11 +226,33 @@ module ccip::token_admin_registry { } #[view] - /// Returns the local token address for the token pool. + /// Returns the local token address for the token pool (supports both V1 and V2). public fun get_pool_local_token( token_pool_address: address - ): address acquires TokenPoolRegistration { - get_registration(token_pool_address).local_token + ): address acquires TokenPoolRegistration, TokenPoolRegistrationV2 { + if (exists(token_pool_address)) { + TokenPoolRegistrationV2[token_pool_address].local_token + } else if (exists(token_pool_address)) { + get_registration(token_pool_address).local_token + } else { + abort error::invalid_argument(E_POOL_NOT_REGISTERED) + } + } + + #[view] + /// Returns the local token address for the token pool. + public fun get_pool_local_token_v2( + token_pool_address: address + ): address acquires TokenPoolRegistrationV2 { + TokenPoolRegistrationV2[token_pool_address].local_token + } + + #[view] + /// Returns true if token pool has TokenPoolRegistrationV2 resource + public fun has_token_pool_registration_v2( + token_pool_address: address + ): bool { + exists(token_pool_address) } #[view] @@ -291,7 +325,9 @@ module ccip::token_admin_registry { // ================================================================ // | Register Pool | // ================================================================ - + #[deprecated] + /// @deprecated: Use `register_pool_v2()` instead. + /// /// Registers pool with `TokenPoolRegistration` and sets up dynamic dispatch for a token pool /// Registry token config mapping must be done separately via `set_pool()` /// by token owner or ccip owner. @@ -303,7 +339,8 @@ module ccip::token_admin_registry { ) acquires TokenAdminRegistryState { let token_pool_address = signer::address_of(token_pool_account); assert!( - !exists(token_pool_address), + !exists(token_pool_address) + && !exists(token_pool_address), error::invalid_argument(E_ALREADY_REGISTERED) ); assert!( @@ -393,9 +430,47 @@ module ccip::token_admin_registry { ); } + /// Registers a V2 token pool using function-value callbacks (closures). + /// + /// Upgrade path: existing legacy pools can upgrade to V2 by calling this function, + /// which supersedes the legacy registration without requiring `unregister_pool()`. + /// New pools should use V2 directly. Once V2 is registered, legacy registration + /// via `register_pool()` is rejected. + public fun register_pool_v2( + token_pool_account: &signer, + local_token: address, + lock_or_burn: |FungibleAsset, LockOrBurnInputV1| (vector, vector) has copy + + drop + store, + release_or_mint: |ReleaseOrMintInputV1| (FungibleAsset, u64) has copy + drop + store + ) { + let token_pool_address = signer::address_of(token_pool_account); + assert!( + !exists(token_pool_address), + error::invalid_argument(E_ALREADY_REGISTERED) + ); + assert!( + object::object_exists(local_token), + error::invalid_argument(E_INVALID_FUNGIBLE_ASSET) + ); + if (exists(token_pool_address)) { + assert!( + get_registration(token_pool_address).local_token == local_token, + error::invalid_argument(E_TOKEN_MISMATCH) + ); + }; + + move_to( + token_pool_account, + TokenPoolRegistrationV2 { + callbacks: TokenPoolCallbacks { lock_or_burn, release_or_mint }, + local_token + } + ); + } + public entry fun unregister_pool( caller: &signer, local_token: address - ) acquires TokenAdminRegistryState, TokenPoolRegistration { + ) acquires TokenAdminRegistryState, TokenPoolRegistration, TokenPoolRegistrationV2 { let state = borrow_state_mut(); assert!( state.token_configs.contains(&local_token), @@ -428,6 +503,11 @@ module ccip::token_admin_registry { } = move_from(previous_pool_address); }; + if (exists(previous_pool_address)) { + let TokenPoolRegistrationV2 { callbacks: _, local_token: _ } = + move_from(previous_pool_address); + }; + event::emit_event( &mut state.token_unregistered_events, TokenUnregistered { @@ -439,7 +519,7 @@ module ccip::token_admin_registry { public entry fun set_pool( caller: &signer, local_token: address, token_pool_address: address - ) acquires TokenAdminRegistryState, TokenPoolRegistration { + ) acquires TokenAdminRegistryState, TokenPoolRegistration, TokenPoolRegistrationV2 { assert!( object::object_exists(local_token), error::invalid_argument(E_INVALID_FUNGIBLE_ASSET) @@ -447,8 +527,17 @@ module ccip::token_admin_registry { let caller_addr = signer::address_of(caller); + let pool_local_token = + if (exists(token_pool_address)) { + get_pool_local_token_v2(token_pool_address) + } else if (exists(token_pool_address)) { + get_registration(token_pool_address).local_token + } else { + abort error::invalid_argument(E_POOL_NOT_REGISTERED) + }; + assert!( - get_registration(token_pool_address).local_token == local_token, + pool_local_token == local_token, error::invalid_argument(E_INVALID_TOKEN_FOR_POOL) ); @@ -994,6 +1083,48 @@ module ccip::token_admin_registry { output.destination_amount } + public(friend) fun lock_or_burn_v2( + token_pool_address: address, + fa: fungible_asset::FungibleAsset, + sender: address, + remote_chain_selector: u64, + receiver: vector + ): (vector, vector) acquires TokenPoolRegistrationV2 { + let pool_config = &TokenPoolRegistrationV2[token_pool_address]; + let input = LockOrBurnInputV1 { sender, remote_chain_selector, receiver }; + + (pool_config.callbacks.lock_or_burn) + (fa, input) + } + + public(friend) fun release_or_mint_v2( + token_pool_address: address, + sender: vector, + receiver: address, + source_amount: u256, + local_token: address, + remote_chain_selector: u64, + source_pool_address: vector, + source_pool_data: vector, + offchain_token_data: vector + ): (FungibleAsset, u64) acquires TokenPoolRegistrationV2 { + let pool_config = &TokenPoolRegistrationV2[token_pool_address]; + let input = + ReleaseOrMintInputV1 { + sender, + receiver, + source_amount, + local_token, + remote_chain_selector, + source_pool_address, + source_pool_data, + offchain_token_data + }; + + (pool_config.callbacks.release_or_mint) + (input) + } + inline fun borrow_state(): &TokenAdminRegistryState { borrow_global(state_object::object_address()) } @@ -1022,7 +1153,7 @@ module ccip::token_admin_registry { public fun mcms_entrypoint( _metadata: Object - ): option::Option acquires TokenAdminRegistryState, TokenPoolRegistration { + ): option::Option acquires TokenAdminRegistryState, TokenPoolRegistration, TokenPoolRegistrationV2 { let (caller, function, data) = mcms_registry::get_callback_params(@ccip, McmsCallback {}); diff --git a/contracts/ccip/ccip_dummy_receiver/Move.toml b/contracts/ccip/ccip_dummy_receiver/Move.toml index 560a20543..5ccbe1fa7 100644 --- a/contracts/ccip/ccip_dummy_receiver/Move.toml +++ b/contracts/ccip/ccip_dummy_receiver/Move.toml @@ -8,12 +8,14 @@ ccip = "_" ccip_dummy_receiver = "_" mcms = "_" mcms_register_entrypoints = "_" +deployer = "_" [dev-addresses] ccip = "0x85d5520ea6a8b1501502651040c6f61238b281142ccbafdc205d40258f0bec10" ccip_dummy_receiver = "0x3010" mcms = "0x4000" mcms_register_entrypoints = "0x4001" +deployer = "0x9910" [dependencies] AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "16beac69835f3a71564c96164a606a23f259099a", subdir = "aptos-move/framework/aptos-framework" } diff --git a/contracts/ccip/ccip_dummy_receiver/sources/dummy_receiver.move b/contracts/ccip/ccip_dummy_receiver/sources/dummy_receiver.move index 51dca3224..b3a111ea5 100644 --- a/contracts/ccip/ccip_dummy_receiver/sources/dummy_receiver.move +++ b/contracts/ccip/ccip_dummy_receiver/sources/dummy_receiver.move @@ -9,6 +9,8 @@ module ccip_dummy_receiver::dummy_receiver { use ccip::client; use ccip::receiver_registry; + const E_TEST_ABORT: u64 = 1; + #[event] struct ReceivedMessage has store, drop { data: vector @@ -46,7 +48,9 @@ module ccip_dummy_receiver::dummy_receiver { @ccip_dummy_receiver, DummyReceiverProof {} ); let data = client::get_data(&message); - if (data == b"abort") { abort 1 }; + if (data == b"abort") { + abort E_TEST_ABORT + }; let state = borrow_state_mut(); diff --git a/contracts/ccip/ccip_dummy_receiver/sources/ptt_dummy_receiver.move b/contracts/ccip/ccip_dummy_receiver/sources/ptt_dummy_receiver.move new file mode 100644 index 000000000..87022892c --- /dev/null +++ b/contracts/ccip/ccip_dummy_receiver/sources/ptt_dummy_receiver.move @@ -0,0 +1,162 @@ +module ccip_dummy_receiver::ptt_dummy_receiver { + use std::account; + use std::event; + use std::object::{Self}; + use std::string::{Self, String}; + use std::fungible_asset::{Metadata}; + use std::resource_account; + use std::primary_fungible_store; + use std::from_bcs; + use std::signer; + + use ccip::client; + use ccip::receiver_registry; + + #[event] + struct ReceivedMessage has store, drop { + data: vector + } + + #[event] + struct ForwardedTokens has store, drop { + final_recipient: address + } + + #[event] + struct ReceivedTokensOnly has store, drop { + token_count: u64 + } + + struct CCIPReceiverState has key { + signer_cap: account::SignerCapability, + received_message_handle: event::EventHandle, + forwarded_tokens_handle: event::EventHandle, + received_tokens_only_handle: event::EventHandle + } + + const E_RESOURCE_NOT_FOUND_ON_ACCOUNT: u64 = 1; + const E_UNAUTHORIZED: u64 = 2; + const E_NO_TOKENS_AVAILABLE_TO_WITHDRAW: u64 = 3; + const E_TEST_ABORT: u64 = 4; + + #[view] + public fun type_and_version(): String { + string::utf8(b"PTTDummyReceiver 1.6.0") + } + + fun init_module(publisher: &signer) { + let signer_cap = + resource_account::retrieve_resource_account_cap(publisher, @deployer); + + let received_message_handle = + account::new_event_handle(publisher); + let forwarded_tokens_handle = + account::new_event_handle(publisher); + let received_tokens_only_handle = + account::new_event_handle(publisher); + + move_to( + publisher, + CCIPReceiverState { + signer_cap, + received_message_handle, + forwarded_tokens_handle, + received_tokens_only_handle + } + ); + + // Default to V2 registration + receiver_registry::register_receiver_v2( + publisher, |message| ccip_receive_v2(message) + ); + } + + #[view] + public fun get_state_address(): address acquires CCIPReceiverState { + let state = borrow_global(@ccip_dummy_receiver); + let state_signer = account::create_signer_with_capability(&state.signer_cap); + signer::address_of(&state_signer) + } + + /// This function MUST remain private (not `public fun`). The `#[persistent]` + /// attribute allows it to be stored as a closure without exposing it to external callers. + /// Only the authorized offramp can invoke this via the closure registered with + /// `receiver_registry::register_receiver_v2()`. Making this public would allow anyone to + /// construct an `Any2AptosMessage` and execute arbitrary token transfers. + #[persistent] + fun ccip_receive_v2(message: client::Any2AptosMessage) acquires CCIPReceiverState { + /* load state and rebuild a signer for the resource account */ + let state = borrow_global_mut(@ccip_dummy_receiver); + let state_signer = account::create_signer_with_capability(&state.signer_cap); + + let data = client::get_data(&message); + + let dest_token_amounts = client::get_dest_token_amounts(&message); + + if (dest_token_amounts.length() != 0 && data.length() != 0) { + let final_recipient = from_bcs::to_address(data); + + for (i in 0..dest_token_amounts.length()) { + let token_amount_ref = &dest_token_amounts[i]; + let token_addr = client::get_token(token_amount_ref); + let amount = client::get_amount(token_amount_ref); + + // Implement the token transfer logic here + + let fa_token = object::address_to_object(token_addr); + + // Must use primary_fungible_store::transfer as token may be dispatchable + primary_fungible_store::transfer( + &state_signer, + fa_token, + final_recipient, + amount + ); + }; + + event::emit(ForwardedTokens { final_recipient }); + event::emit_event( + &mut state.forwarded_tokens_handle, + ForwardedTokens { final_recipient } + ); + } else if (data.length() != 0) { + event::emit(ReceivedMessage { data }); + event::emit_event(&mut state.received_message_handle, ReceivedMessage { data }); + + } else if (dest_token_amounts.length() != 0) { + // Tokens only (no forwarding data) - keep them at receiver + // Emit event to prove receiver was called + let token_count = dest_token_amounts.length(); + event::emit(ReceivedTokensOnly { token_count }); + event::emit_event( + &mut state.received_tokens_only_handle, + ReceivedTokensOnly { token_count } + ); + }; + + // Simple abort condition for testing + if (data == b"abort") { + abort E_TEST_ABORT + }; + } + + public entry fun withdraw_token( + sender: &signer, recipient: address, token_address: address + ) acquires CCIPReceiverState { + assert!( + exists(@ccip_dummy_receiver), + E_RESOURCE_NOT_FOUND_ON_ACCOUNT + ); + assert!(signer::address_of(sender) == @ccip_dummy_receiver, E_UNAUTHORIZED); + + let state = borrow_global_mut(@ccip_dummy_receiver); + let state_signer = account::create_signer_with_capability(&state.signer_cap); + + let fa_token = object::address_to_object(token_address); + let balance = primary_fungible_store::balance(@ccip_dummy_receiver, fa_token); + + assert!(balance > 0, E_NO_TOKENS_AVAILABLE_TO_WITHDRAW); + + primary_fungible_store::transfer(&state_signer, fa_token, recipient, balance); + } +} diff --git a/contracts/ccip/ccip_offramp/Move.toml b/contracts/ccip/ccip_offramp/Move.toml index 98f51424a..af90b91e4 100644 --- a/contracts/ccip/ccip_offramp/Move.toml +++ b/contracts/ccip/ccip_offramp/Move.toml @@ -18,8 +18,13 @@ mcms_register_entrypoints = "0x4001" ccip_token_pool = "0x8d62e11f76e6e92563c59e7a5e842a540f8c6c3a4ed8a32f40a5ad3425b55f86" burn_mint_token_pool = "0x8e03eb21315649c06acb9a860a72c2b8cef5bd36775402008b89af397756dad7" lock_release_token_pool = "0x2e1f4cc8fbc2c7ccd2c67ff453e00526919098304d70708ef504af944d6fede8" +managed_token_pool = "0xc0a1d7586bddbd46c9b3445cf9ad82c020dcb82818e6092c9683b51bfecc6633" +regulated_token_pool = "0xb761d8e8425c11486ecb38444aab3493baf7c45381ce973a0bc99618c35021d5" burn_mint_local_token = "0x15c084d10b071a4b180c8d050e421a533bd07d13f9d9386335709da567183768" lock_release_local_token = "0x6d1c246126d36fea774b12486de0a3737997f1b9b23806c67ca5ce72859ff5fa" +managed_token = "0xfda529bc9c3e1cd8bd3df66bd96bb20a36553ed454c909a96e6a0dadb728e896" +regulated_token = "0xb3cec8e3442cafe0c378411012bbcae6787bfc0fbdd528ee9a00aaaf0c88d1b6" +admin = "0x100" [dependencies] AptosFramework = { git = "https://github.com/aptos-labs/aptos-core.git", rev = "16beac69835f3a71564c96164a606a23f259099a", subdir = "aptos-move/framework/aptos-framework" } @@ -28,3 +33,5 @@ ChainlinkCCIP = { local = "../ccip" } [dev-dependencies] BurnMintTokenPool = { local = "../ccip_token_pools/burn_mint_token_pool" } LockReleaseTokenPool = { local = "../ccip_token_pools/lock_release_token_pool" } +ManagedTokenPool = { local = "../ccip_token_pools/managed_token_pool" } +RegulatedTokenPool = { local = "../ccip_token_pools/regulated_token_pool" } diff --git a/contracts/ccip/ccip_offramp/sources/offramp.move b/contracts/ccip/ccip_offramp/sources/offramp.move index 9281c62e5..b0dc2f74b 100644 --- a/contracts/ccip/ccip_offramp/sources/offramp.move +++ b/contracts/ccip/ccip_offramp/sources/offramp.move @@ -390,8 +390,9 @@ module ccip_offramp::offramp { ); let source_chain_execution_states = state.execution_states.borrow(source_chain_selector); - let execution_state = source_chain_execution_states.borrow(sequence_number); - *execution_state + *source_chain_execution_states.borrow_with_default( + sequence_number, &EXECUTION_STATE_UNTOUCHED + ) } fun execute_single_report( @@ -1812,4 +1813,9 @@ module ccip_offramp::offramp { public fun merkle_root_merkle_root(root: &MerkleRoot): vector { root.merkle_root } + + #[test_only] + public fun source_chain_config_on_ramp(config: &SourceChainConfig): vector { + config.on_ramp + } } diff --git a/contracts/ccip/ccip_offramp/tests/mock/mock_ccip_receiver.move b/contracts/ccip/ccip_offramp/tests/mock/mock_ccip_receiver.move new file mode 100644 index 000000000..920acbf99 --- /dev/null +++ b/contracts/ccip/ccip_offramp/tests/mock/mock_ccip_receiver.move @@ -0,0 +1,322 @@ +#[test_only] +/// Compatible with dispatchable and non-dispatchable tokens +/// When transferring tokens, use `primary_fungible_store::transfer` as this triggers the dispatchable fungible asset hook +module ccip_offramp::mock_ccip_receiver { + use std::account; + use std::event; + use std::object::{Self, Object}; + use std::string::{Self, String}; + use std::fungible_asset::{Self, Metadata}; + use std::option::{Self, Option}; + use std::primary_fungible_store; + use std::from_bcs; + use std::signer; + + use ccip::client; + use ccip::receiver_registry; + + #[event] + struct ReceivedMessage has store, drop { + message: String + } + + #[event] + struct ForwardedTokens has store, drop { + final_recipient: address + } + + #[event] + struct ReceivedTokensOnly has store, drop { + token_count: u64 + } + + struct CCIPReceiverState has key { + signer_cap: account::SignerCapability, + received_message_handle: event::EventHandle, + forwarded_tokens_handle: event::EventHandle, + received_tokens_only_handle: event::EventHandle + } + + const E_RESOURCE_NOT_FOUND_ON_ACCOUNT: u64 = 1; + const E_UNAUTHORIZED: u64 = 2; + const E_INVALID_TOKEN_ADDRESS: u64 = 3; + const E_NO_TOKENS_AVAILABLE_TO_WITHDRAW: u64 = 4; + /// Test-only abort triggered when message data equals "abort". + const E_TEST_ABORT: u64 = 5; + + #[view] + public fun type_and_version(): String { + string::utf8(b"MockCCIPReceiver 1.6.0") + } + + const MODULE_NAME: vector = b"mock_ccip_receiver"; + + fun init_module(publisher: &signer) { + // Create a signer capability for the receiver account + let signer_cap = account::create_test_signer_cap(signer::address_of(publisher)); + + // Create a unique handle for each event type + let received_message_handle = + account::new_event_handle(publisher); + let forwarded_tokens_handle = + account::new_event_handle(publisher); + let received_tokens_only_handle = + account::new_event_handle(publisher); + + // Move all state into the single resource struct + move_to( + publisher, + CCIPReceiverState { + signer_cap, + received_message_handle, + forwarded_tokens_handle, + received_tokens_only_handle + } + ); + + // Default to V2 registration + receiver_registry::register_receiver_v2( + publisher, |message| ccip_receive_v2(message) + ); + } + + /// Register this receiver as V1 (dispatchable fungible asset mode) + /// This is used for testing V1 compatibility + public fun register_as_v1(publisher: &signer) { + receiver_registry::register_receiver(publisher, MODULE_NAME, CCIPReceiverProof {}); + } + + /// Migrate from V1 to V2 registration + /// This demonstrates the upgrade path from dispatchable FA to closures + public fun migrate_to_v2(publisher: &signer) { + // V2 registration will coexist with V1 + // The dispatcher will prefer V2 when both exist + receiver_registry::register_receiver_v2( + publisher, |message| ccip_receive_v2(message) + ); + } + + #[view] + public fun get_state_address(): address acquires CCIPReceiverState { + let state = borrow_global(@ccip_offramp); + let state_signer = account::create_signer_with_capability(&state.signer_cap); + signer::address_of(&state_signer) + } + + struct CCIPReceiverProof has drop {} + + /// This function MUST remain private (not `public fun`). The `#[persistent]` + /// attribute allows it to be stored as a closure without exposing it to external callers. + /// Only the authorized offramp can invoke this via the closure registered with + /// `receiver_registry::register_receiver_v2()`. Making this public would allow anyone to + /// construct an `Any2AptosMessage` and execute arbitrary token transfers. + #[persistent] + fun ccip_receive_v2(message: client::Any2AptosMessage) acquires CCIPReceiverState { + /* load state and rebuild a signer for the resource account */ + let state = borrow_global_mut(@ccip_offramp); + let state_signer = account::create_signer_with_capability(&state.signer_cap); + + let data = client::get_data(&message); + + let dest_token_amounts = client::get_dest_token_amounts(&message); + + if (dest_token_amounts.length() != 0 && data.length() != 0) { + let final_recipient = from_bcs::to_address(data); + + for (i in 0..dest_token_amounts.length()) { + let token_amount_ref = &dest_token_amounts[i]; + let token_addr = client::get_token(token_amount_ref); + let amount = client::get_amount(token_amount_ref); + + // Implement the token transfer logic here + + let fa_token = object::address_to_object(token_addr); + + // Must use primary_fungible_store::transfer as token may be dispatchable + primary_fungible_store::transfer( + &state_signer, + fa_token, + final_recipient, + amount + ); + }; + + event::emit(ForwardedTokens { final_recipient }); + event::emit_event( + &mut state.forwarded_tokens_handle, + ForwardedTokens { final_recipient } + ); + } else if (data.length() != 0) { + // Convert the vector to a string + let message = string::utf8(data); + + event::emit(ReceivedMessage { message }); + event::emit_event( + &mut state.received_message_handle, ReceivedMessage { message } + ); + + } else if (dest_token_amounts.length() != 0) { + // Tokens only (no forwarding data) - keep them at receiver + // Emit event to prove receiver was called + let token_count = dest_token_amounts.length(); + event::emit(ReceivedTokensOnly { token_count }); + event::emit_event( + &mut state.received_tokens_only_handle, + ReceivedTokensOnly { token_count } + ); + }; + + // Simple abort condition for testing + if (data == b"abort") { + abort E_TEST_ABORT + }; + } + + #[deprecated] + /// Legacy V1 receive function, use ccip_receive_v2 as this supports dispatchable tokens + /// Only switch to v2 once TokenPools are migrated to V2 + public fun ccip_receive(_metadata: Object): Option acquires CCIPReceiverState { + /* load state and rebuild a signer for the resource account */ + let state = borrow_global_mut(@ccip_offramp); + let state_signer = account::create_signer_with_capability(&state.signer_cap); + + let message = + receiver_registry::get_receiver_input(@ccip_offramp, CCIPReceiverProof {}); + + let data = client::get_data(&message); + + let dest_token_amounts = client::get_dest_token_amounts(&message); + + if (dest_token_amounts.length() != 0 && data.length() != 0) { + let final_recipient = from_bcs::to_address(data); + + for (i in 0..dest_token_amounts.length()) { + let token_amount_ref = &dest_token_amounts[i]; + let token_addr = client::get_token(token_amount_ref); + let amount = client::get_amount(token_amount_ref); + + // Implement the token transfer logic here + + let fa_token = object::address_to_object(token_addr); + let fa_store_sender = + primary_fungible_store::ensure_primary_store_exists( + @ccip_offramp, fa_token + ); + let fa_store_receiver = + primary_fungible_store::ensure_primary_store_exists( + final_recipient, fa_token + ); + + fungible_asset::transfer( + &state_signer, + fa_store_sender, + fa_store_receiver, + amount + ); + }; + + event::emit(ForwardedTokens { final_recipient }); + event::emit_event( + &mut state.forwarded_tokens_handle, + ForwardedTokens { final_recipient } + ); + + } else if (data.length() != 0) { + + // Convert the vector to a string + let message = string::utf8(data); + + event::emit(ReceivedMessage { message }); + event::emit_event( + &mut state.received_message_handle, ReceivedMessage { message } + ); + + } else if (dest_token_amounts.length() != 0) { + // Tokens only (no forwarding data) - keep them at receiver + // Emit event to prove receiver was called + let token_count = dest_token_amounts.length(); + event::emit(ReceivedTokensOnly { token_count }); + event::emit_event( + &mut state.received_tokens_only_handle, + ReceivedTokensOnly { token_count } + ); + }; + + // Simple abort condition for testing + if (data == b"abort") { + abort E_TEST_ABORT + }; + + option::none() + } + + public entry fun withdraw_token( + sender: &signer, recipient: address, token_address: address + ) acquires CCIPReceiverState { + assert!( + exists(@ccip_offramp), + E_RESOURCE_NOT_FOUND_ON_ACCOUNT + ); + assert!(signer::address_of(sender) == @ccip_offramp, E_UNAUTHORIZED); + + let state = borrow_global_mut(@ccip_offramp); + let state_signer = account::create_signer_with_capability(&state.signer_cap); + + let fa_token = object::address_to_object(token_address); + let balance = primary_fungible_store::balance(@ccip_offramp, fa_token); + + assert!(balance > 0, E_NO_TOKENS_AVAILABLE_TO_WITHDRAW); + + primary_fungible_store::transfer(&state_signer, fa_token, recipient, balance); + } + + public fun test_init_module(publisher: &signer) { + init_module(publisher); + } + + /// Initialize without auto-registering (for testing V1/V2 manually) + public fun test_init_state_only(publisher: &signer) { + // Create a signer capability for the receiver account + let signer_cap = account::create_test_signer_cap(signer::address_of(publisher)); + + // Create a unique handle for each event type + let received_message_handle = + account::new_event_handle(publisher); + let forwarded_tokens_handle = + account::new_event_handle(publisher); + let received_tokens_only_handle = + account::new_event_handle(publisher); + + // Move all state into the single resource struct + move_to( + publisher, + CCIPReceiverState { + signer_cap, + received_message_handle, + forwarded_tokens_handle, + received_tokens_only_handle + } + ); + } + + public fun get_received_message_events(): vector acquires CCIPReceiverState { + let state = borrow_global(@ccip_offramp); + event::emitted_events_by_handle(&state.received_message_handle) + } + + public fun get_forwarded_tokens_events(): vector acquires CCIPReceiverState { + let state = borrow_global(@ccip_offramp); + event::emitted_events_by_handle(&state.forwarded_tokens_handle) + } + + public fun get_received_tokens_only_events(): vector acquires CCIPReceiverState { + let state = borrow_global(@ccip_offramp); + event::emitted_events_by_handle( + &state.received_tokens_only_handle + ) + } + + public fun received_message_get_message(event: &ReceivedMessage): String { + event.message + } +} diff --git a/contracts/ccip/ccip_offramp/tests/mock/mock_token.move b/contracts/ccip/ccip_offramp/tests/mock/mock_token.move new file mode 100644 index 000000000..082933183 --- /dev/null +++ b/contracts/ccip/ccip_offramp/tests/mock/mock_token.move @@ -0,0 +1,44 @@ +#[test_only] +module ccip_offramp::mock_token { + use std::fungible_asset::{Self, FungibleAsset, TransferRef}; + use std::object::{Object, ConstructorRef}; + use std::string::{Self}; + use std::option::{Self}; + use std::function_info; + use std::dispatchable_fungible_asset; + + public fun add_dynamic_dispatch_function( + ccip_onramp_signer: &signer, constructor_ref: &ConstructorRef + ) { + let deposit = + function_info::new_function_info( + ccip_onramp_signer, + string::utf8(b"mock_token"), + string::utf8(b"deposit") + ); + let withdraw = + function_info::new_function_info( + ccip_onramp_signer, + string::utf8(b"mock_token"), + string::utf8(b"withdraw") + ); + dispatchable_fungible_asset::register_dispatch_functions( + constructor_ref, + option::some(withdraw), + option::some(deposit), + option::none() + ); + } + + public fun deposit( + store: Object, fa: FungibleAsset, transfer_ref: &TransferRef + ) { + fungible_asset::deposit_with_ref(transfer_ref, store, fa); + } + + public fun withdraw( + store: Object, amount: u64, transfer_ref: &TransferRef + ): FungibleAsset { + fungible_asset::withdraw_with_ref(transfer_ref, store, amount) + } +} diff --git a/contracts/ccip/ccip_offramp/tests/offramp_burn_mint_receiver_test.move b/contracts/ccip/ccip_offramp/tests/offramp_burn_mint_receiver_test.move new file mode 100644 index 000000000..d6e0e698a --- /dev/null +++ b/contracts/ccip/ccip_offramp/tests/offramp_burn_mint_receiver_test.move @@ -0,0 +1,569 @@ +#[test_only] +module ccip_offramp::offramp_burn_mint_receiver_test { + use std::account; + use std::fungible_asset; + use std::object; + use std::primary_fungible_store; + use std::signer; + use std::string; + use std::bcs; + use std::timestamp; + + use ccip_offramp::offramp_test; + use ccip_offramp::offramp; + use ccip_offramp::mock_ccip_receiver; + use ccip::receiver_registry; + + const BURN_MINT_TOKEN_POOL: u8 = 0; + const BURN_MINT_TOKEN_SEED: vector = b"TestToken"; + const EVM_SOURCE_CHAIN_SELECTOR: u64 = 909606746561742123; + const MOCK_EVM_ADDRESS_VECTOR: vector = x"4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97"; + const EVM_SENDER: vector = x"d87929a32cf0cbdc9e2d07ffc7c33344079de727"; + const GAS_LIMIT: u64 = 1000000; + + fun setup_mock_ccip_receiver(owner: &signer, ccip_offramp: &signer) { + account::create_account_if_does_not_exist(signer::address_of(ccip_offramp)); + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_module(ccip_offramp); + } + + struct TestMessage has drop { + message: offramp::Any2AptosRampMessage, + merkle_root: vector, + proofs: vector> + } + + fun create_and_commit_message( + message_id: vector, + sequence_number: u64, + receiver: address, + data: vector, + token_amounts: vector + ): TestMessage { + let static_config = offramp::get_static_config(); + let dest_chain_selector = offramp::chain_selector(&static_config); + + let header = + offramp::test_create_ramp_message_header( + message_id, + EVM_SOURCE_CHAIN_SELECTOR, + dest_chain_selector, + sequence_number, + 0 + ); + + let message = + offramp::test_create_any2aptos_ramp_message( + header, + EVM_SENDER, + data, + receiver, + (GAS_LIMIT as u256), + token_amounts + ); + + let source_chain_config = + offramp::get_source_chain_config(EVM_SOURCE_CHAIN_SELECTOR); + let on_ramp = offramp::source_chain_config_on_ramp(&source_chain_config); + + let metadata_hash = + offramp::test_calculate_metadata_hash( + EVM_SOURCE_CHAIN_SELECTOR, dest_chain_selector, on_ramp + ); + + let message_hash = offramp::test_calculate_message_hash(&message, metadata_hash); + let merkle_root = message_hash; + let proofs = vector[]; + + offramp::test_add_root(merkle_root, timestamp::now_seconds() - 3700); + + TestMessage { message, merkle_root, proofs } + } + + fun execute_message_and_verify_success( + sequence_number: u64, + test_message: TestMessage, + offchain_token_data: vector> + ) { + let TestMessage { message, merkle_root: _, proofs } = test_message; + + let report = + offramp::test_create_execution_report( + EVM_SOURCE_CHAIN_SELECTOR, + message, + offchain_token_data, + proofs + ); + + offramp::test_execute_single_report(report); + + let execution_state = + offramp::get_execution_state(EVM_SOURCE_CHAIN_SELECTOR, sequence_number); + assert!(execution_state == 2); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_non_dispatchable_token_transfer_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // token is non-dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 100000 + ); + + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000001", + 1, + @ccip_offramp, // receiver + vector[], + vector[token_amounts] + ); + + execute_message_and_verify_success(1, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 100000); + + let tokens_only_events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(tokens_only_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_non_dispatchable_message_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, _token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // token is non-dispatchable + false // use_v1_init + ); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let test_data = b"Hello from EVM chain!"; + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000002", + 2, + @ccip_offramp, // receiver + test_data, + vector[] + ); + + execute_message_and_verify_success(2, test_message, vector[]); + + let received_events = mock_ccip_receiver::get_received_message_events(); + assert!(received_events.length() == 1); + + let event = received_events.borrow(0); + let event_message = mock_ccip_receiver::received_message_get_message(event); + assert!(event_message == string::utf8(test_data)); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + recipient = @0x999, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_receiver_non_dispatchable_tokens_with_forwarding( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + recipient: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // token is non-dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let recipient_addr = signer::address_of(recipient); + account::create_account_for_test(recipient_addr); + + // Sending 200,000 tokens to receiver contract first + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 200000 + ); + + let test_data = bcs::to_bytes(&recipient_addr); + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000003", + 3, // sequence number + @ccip_offramp, // receiver + test_data, + vector[token_amounts] + ); + + execute_message_and_verify_success(3, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 0); + + let recipient_store = + primary_fungible_store::primary_store(recipient_addr, token_obj); + let recipient_balance = fungible_asset::balance(recipient_store); + assert!(recipient_balance == 200000); + + let forwarded_events = mock_ccip_receiver::get_forwarded_tokens_events(); + assert!(forwarded_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_dispatchable_token_transfer_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + true, // token is dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 100000 + ); + + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000004", + 4, // sequence number + @ccip_offramp, // receiver + vector[], + vector[token_amounts] + ); + + execute_message_and_verify_success(4, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 100000); + + let tokens_only_events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(tokens_only_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_dispatchable_message_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, _token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + true, // token is dispatchable + false // use_v1_init + ); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let test_data = b"Hello from EVM chain!"; + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000005", + 5, // sequence number + @ccip_offramp, // receiver + test_data, + vector[] + ); + + execute_message_and_verify_success(5, test_message, vector[]); + + let received_events = mock_ccip_receiver::get_received_message_events(); + assert!(received_events.length() == 1); + + let event = received_events.borrow(0); + let event_message = mock_ccip_receiver::received_message_get_message(event); + assert!(event_message == string::utf8(test_data)); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + recipient = @0x999, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_receiver_dispatchable_tokens_with_forwarding( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + recipient: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + true, // token is dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let recipient_addr = signer::address_of(recipient); + account::create_account_for_test(recipient_addr); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 200000 + ); + + let test_data = bcs::to_bytes(&recipient_addr); + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000006", + 6, // sequence number + @ccip_offramp, // receiver + test_data, + vector[token_amounts] + ); + + execute_message_and_verify_success(6, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 0); + + let recipient_store = + primary_fungible_store::primary_store(recipient_addr, token_obj); + let recipient_balance = fungible_asset::balance(recipient_store); + assert!(recipient_balance == 200000); + + let forwarded_events = mock_ccip_receiver::get_forwarded_tokens_events(); + assert!(forwarded_events.length() == 1); + } +} diff --git a/contracts/ccip/ccip_offramp/tests/offramp_lock_release_receiver_test.move b/contracts/ccip/ccip_offramp/tests/offramp_lock_release_receiver_test.move new file mode 100644 index 000000000..1f6edb0ec --- /dev/null +++ b/contracts/ccip/ccip_offramp/tests/offramp_lock_release_receiver_test.move @@ -0,0 +1,569 @@ +#[test_only] +module ccip_offramp::offramp_lock_release_receiver_test { + use std::account; + use std::fungible_asset; + use std::object; + use std::primary_fungible_store; + use std::signer; + use std::string; + use std::bcs; + use std::timestamp; + + use ccip_offramp::offramp_test; + use ccip_offramp::offramp; + use ccip_offramp::mock_ccip_receiver; + use ccip::receiver_registry; + + const LOCK_RELEASE_TOKEN_POOL: u8 = 1; + const LOCK_RELEASE_TOKEN_SEED: vector = b"LockReleaseToken"; + const EVM_SOURCE_CHAIN_SELECTOR: u64 = 909606746561742123; + const MOCK_EVM_ADDRESS_VECTOR: vector = x"4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97"; + const EVM_SENDER: vector = x"d87929a32cf0cbdc9e2d07ffc7c33344079de727"; + const GAS_LIMIT: u64 = 1000000; + + fun setup_mock_ccip_receiver(owner: &signer, ccip_offramp: &signer) { + account::create_account_if_does_not_exist(signer::address_of(ccip_offramp)); + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_module(ccip_offramp); + } + + struct TestMessage has drop { + message: offramp::Any2AptosRampMessage, + merkle_root: vector, + proofs: vector> + } + + fun create_and_commit_message( + message_id: vector, + sequence_number: u64, + receiver: address, + data: vector, + token_amounts: vector + ): TestMessage { + let static_config = offramp::get_static_config(); + let dest_chain_selector = offramp::chain_selector(&static_config); + + let header = + offramp::test_create_ramp_message_header( + message_id, + EVM_SOURCE_CHAIN_SELECTOR, + dest_chain_selector, + sequence_number, + 0 + ); + + let message = + offramp::test_create_any2aptos_ramp_message( + header, + EVM_SENDER, + data, + receiver, + (GAS_LIMIT as u256), + token_amounts + ); + + let source_chain_config = + offramp::get_source_chain_config(EVM_SOURCE_CHAIN_SELECTOR); + let on_ramp = offramp::source_chain_config_on_ramp(&source_chain_config); + + let metadata_hash = + offramp::test_calculate_metadata_hash( + EVM_SOURCE_CHAIN_SELECTOR, dest_chain_selector, on_ramp + ); + + let message_hash = offramp::test_calculate_message_hash(&message, metadata_hash); + let merkle_root = message_hash; + let proofs = vector[]; + + offramp::test_add_root(merkle_root, timestamp::now_seconds() - 3700); + + TestMessage { message, merkle_root, proofs } + } + + fun execute_message_and_verify_success( + sequence_number: u64, + test_message: TestMessage, + offchain_token_data: vector> + ) { + let TestMessage { message, merkle_root: _, proofs } = test_message; + + let report = + offramp::test_create_execution_report( + EVM_SOURCE_CHAIN_SELECTOR, + message, + offchain_token_data, + proofs + ); + + offramp::test_execute_single_report(report); + + let execution_state = + offramp::get_execution_state(EVM_SOURCE_CHAIN_SELECTOR, sequence_number); + assert!(execution_state == 2); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_non_dispatchable_token_transfer_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 100000 + ); + + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000001", + 1, + @ccip_offramp, // receiver + vector[], + vector[token_amounts] + ); + + execute_message_and_verify_success(1, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 100000); + + let tokens_only_events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(tokens_only_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_non_dispatchable_message_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, _token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + false, // token is non-dispatchable + false // use_v1_init + ); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let test_data = b"Hello from EVM chain!"; + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000002", + 2, + @ccip_offramp, // receiver + test_data, + vector[] + ); + + execute_message_and_verify_success(2, test_message, vector[]); + + let received_events = mock_ccip_receiver::get_received_message_events(); + assert!(received_events.length() == 1); + + let event = received_events.borrow(0); + let event_message = mock_ccip_receiver::received_message_get_message(event); + assert!(event_message == string::utf8(test_data)); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + recipient = @0x999, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_receiver_non_dispatchable_tokens_with_forwarding( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + recipient: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let recipient_addr = signer::address_of(recipient); + account::create_account_for_test(recipient_addr); + + // Sending 200,000 tokens to receiver contract first + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 200000 + ); + + let test_data = bcs::to_bytes(&recipient_addr); + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000003", + 3, // sequence number + @ccip_offramp, // receiver + test_data, + vector[token_amounts] + ); + + execute_message_and_verify_success(3, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 0); + + let recipient_store = + primary_fungible_store::primary_store(recipient_addr, token_obj); + let recipient_balance = fungible_asset::balance(recipient_store); + assert!(recipient_balance == 200000); + + let forwarded_events = mock_ccip_receiver::get_forwarded_tokens_events(); + assert!(forwarded_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_dispatchable_token_transfer_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + true, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 100000 + ); + + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000004", + 4, // sequence number + @ccip_offramp, // receiver + vector[], + vector[token_amounts] + ); + + execute_message_and_verify_success(4, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 100000); + + let tokens_only_events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(tokens_only_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_dispatchable_message_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, _token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + true, // is_dispatchable + false // use_v1_init + ); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let test_data = b"Hello from EVM chain!"; + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000005", + 5, // sequence number + @ccip_offramp, // receiver + test_data, + vector[] + ); + + execute_message_and_verify_success(5, test_message, vector[]); + + let received_events = mock_ccip_receiver::get_received_message_events(); + assert!(received_events.length() == 1); + + let event = received_events.borrow(0); + let event_message = mock_ccip_receiver::received_message_get_message(event); + assert!(event_message == string::utf8(test_data)); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + recipient = @0x999, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_receiver_dispatchable_tokens_with_forwarding( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + recipient: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + true, // token is dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let recipient_addr = signer::address_of(recipient); + account::create_account_for_test(recipient_addr); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 200000 + ); + + let test_data = bcs::to_bytes(&recipient_addr); + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000006", + 6, // sequence number + @ccip_offramp, // receiver + test_data, + vector[token_amounts] + ); + + execute_message_and_verify_success(6, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 0); + + let recipient_store = + primary_fungible_store::primary_store(recipient_addr, token_obj); + let recipient_balance = fungible_asset::balance(recipient_store); + assert!(recipient_balance == 200000); + + let forwarded_events = mock_ccip_receiver::get_forwarded_tokens_events(); + assert!(forwarded_events.length() == 1); + } +} diff --git a/contracts/ccip/ccip_offramp/tests/offramp_managed_receiver_test.move b/contracts/ccip/ccip_offramp/tests/offramp_managed_receiver_test.move new file mode 100644 index 000000000..c35607125 --- /dev/null +++ b/contracts/ccip/ccip_offramp/tests/offramp_managed_receiver_test.move @@ -0,0 +1,644 @@ +#[test_only] +module ccip_offramp::offramp_managed_receiver_test { + use std::account; + use std::fungible_asset; + use std::object; + use std::primary_fungible_store; + use std::signer; + use std::bcs; + use std::string; + use std::timestamp; + + use ccip_offramp::offramp_test; + use ccip_offramp::offramp; + use ccip_offramp::mock_ccip_receiver; + use ccip::receiver_registry; + use managed_token::managed_token; + use managed_token_pool::managed_token_pool; + + const MANAGED_TOKEN_POOL: u8 = 2; + const MANAGED_TOKEN_SEED: vector = b"ManagedToken"; + + const EVM_SOURCE_CHAIN_SELECTOR: u64 = 909606746561742123; + const MOCK_EVM_ADDRESS_VECTOR: vector = x"4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97"; + const EVM_SENDER: vector = x"d87929a32cf0cbdc9e2d07ffc7c33344079de727"; + const GAS_LIMIT: u64 = 1000000; + + fun setup_mock_ccip_receiver(owner: &signer, ccip_offramp: &signer) { + account::create_account_if_does_not_exist(signer::address_of(ccip_offramp)); + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_module(ccip_offramp); + } + + struct TestMessage has drop { + message: offramp::Any2AptosRampMessage, + merkle_root: vector, + proofs: vector> + } + + fun create_and_commit_message( + message_id: vector, + sequence_number: u64, + receiver: address, + data: vector, + token_amounts: vector + ): TestMessage { + let static_config = offramp::get_static_config(); + let dest_chain_selector = offramp::chain_selector(&static_config); + + let header = + offramp::test_create_ramp_message_header( + message_id, + EVM_SOURCE_CHAIN_SELECTOR, + dest_chain_selector, + sequence_number, + 0 + ); + + let message = + offramp::test_create_any2aptos_ramp_message( + header, + EVM_SENDER, + data, + receiver, + (GAS_LIMIT as u256), + token_amounts + ); + + let source_chain_config = + offramp::get_source_chain_config(EVM_SOURCE_CHAIN_SELECTOR); + let on_ramp = offramp::source_chain_config_on_ramp(&source_chain_config); + + let metadata_hash = + offramp::test_calculate_metadata_hash( + EVM_SOURCE_CHAIN_SELECTOR, dest_chain_selector, on_ramp + ); + + let message_hash = offramp::test_calculate_message_hash(&message, metadata_hash); + let merkle_root = message_hash; + let proofs = vector[]; + + offramp::test_add_root(merkle_root, timestamp::now_seconds() - 3700); + + TestMessage { message, merkle_root, proofs } + } + + fun execute_message_and_verify_success( + sequence_number: u64, + test_message: TestMessage, + offchain_token_data: vector> + ) { + let TestMessage { message, merkle_root: _, proofs } = test_message; + + let report = + offramp::test_create_execution_report( + EVM_SOURCE_CHAIN_SELECTOR, + message, + offchain_token_data, + proofs + ); + + offramp::test_execute_single_report(report); + + let execution_state = + offramp::get_execution_state(EVM_SOURCE_CHAIN_SELECTOR, sequence_number); + assert!(execution_state == 2); + } + + // ======================== NON DISPATCHABLE TESTS ======================== + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @admin, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_non_dispatchable_token_transfer_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + MANAGED_TOKEN_POOL, + MANAGED_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + // Add to allowlist for release_or_mint + let pool_address = managed_token_pool::get_store_address(); + managed_token::apply_allowed_minter_updates( + owner, vector[], vector[pool_address] + ); + + // Add to allowlist for lock_or_burn + let pool_address = managed_token_pool::get_store_address(); + managed_token::apply_allowed_burner_updates( + owner, vector[], vector[pool_address] + ); + + // // Grant receiver state signer minter and burner role for transfer during forwarding + // let state_address = managed_dispatchable_receiver::get_state_address(); + // managed_token::apply_allowed_minter_updates(owner, vector[], vector[state_address]); + // managed_token::apply_allowed_burner_updates(owner, vector[], vector[state_address]); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 100000 + ); + + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000004", + 4, // sequence number + @ccip_offramp, // receiver + vector[], + vector[token_amounts] + ); + + execute_message_and_verify_success(4, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 100000); + + let tokens_only_events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(tokens_only_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_non_dispatchable_message_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, _token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + MANAGED_TOKEN_POOL, + MANAGED_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init + ); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + // Add to allowlist for lock_or_burn/release_or_mint + let pool_address = managed_token_pool::get_store_address(); + managed_token::apply_allowed_burner_updates( + owner, vector[], vector[pool_address] + ); + managed_token::apply_allowed_minter_updates( + owner, vector[], vector[pool_address] + ); + + let test_data = b"Hello from EVM chain!"; + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000002", + 2, + @ccip_offramp, // receiver + test_data, + vector[] + ); + + execute_message_and_verify_success(2, test_message, vector[]); + + let received_events = mock_ccip_receiver::get_received_message_events(); + assert!(received_events.length() == 1); + + let event = received_events.borrow(0); + let event_message = mock_ccip_receiver::received_message_get_message(event); + assert!(event_message == string::utf8(test_data)); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + recipient = @0x999, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_receiver_non_dispatchable_tokens_with_forwarding( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + recipient: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + MANAGED_TOKEN_POOL, + MANAGED_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + // Add to allowlist for lock_or_burn/release_or_mint + let pool_address = managed_token_pool::get_store_address(); + managed_token::apply_allowed_burner_updates( + owner, vector[], vector[pool_address] + ); + managed_token::apply_allowed_minter_updates( + owner, vector[], vector[pool_address] + ); + + let recipient_addr = signer::address_of(recipient); + account::create_account_for_test(recipient_addr); + + // Sending 200,000 tokens to receiver contract first + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 200000 + ); + + let test_data = bcs::to_bytes(&recipient_addr); + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000003", + 3, // sequence number + @ccip_offramp, // receiver + test_data, + vector[token_amounts] + ); + + execute_message_and_verify_success(3, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 0); + + let recipient_store = + primary_fungible_store::primary_store(recipient_addr, token_obj); + let recipient_balance = fungible_asset::balance(recipient_store); + assert!(recipient_balance == 200000); + + let forwarded_events = mock_ccip_receiver::get_forwarded_tokens_events(); + assert!(forwarded_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @admin, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_dispatchable_token_transfer_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + MANAGED_TOKEN_POOL, + MANAGED_TOKEN_SEED, + true, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + // Add to allowlist for lock_or_burn/release_or_mint + let pool_address = managed_token_pool::get_store_address(); + managed_token::apply_allowed_minter_updates( + owner, vector[], vector[pool_address] + ); + managed_token::apply_allowed_burner_updates( + owner, vector[], vector[pool_address] + ); + + // Grant receiver state signer minter and burner role for transfer during forwarding + let state_address = mock_ccip_receiver::get_state_address(); + managed_token::apply_allowed_minter_updates( + owner, vector[], vector[state_address] + ); + managed_token::apply_allowed_burner_updates( + owner, vector[], vector[state_address] + ); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 100000 + ); + + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000004", + 4, // sequence number + @ccip_offramp, // receiver + vector[], + vector[token_amounts] + ); + + execute_message_and_verify_success(4, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 100000); + + let tokens_only_events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(tokens_only_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @admin, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_dispatchable_message_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, _token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + MANAGED_TOKEN_POOL, + MANAGED_TOKEN_SEED, + true, // is_dispatchable + false // use_v1_init + ); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let test_data = b"Hello from EVM chain!"; + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000005", + 5, // sequence number + @ccip_offramp, // receiver + test_data, + vector[] + ); + + execute_message_and_verify_success(5, test_message, vector[]); + + let received_events = mock_ccip_receiver::get_received_message_events(); + assert!(received_events.length() == 1); + + let event = received_events.borrow(0); + let event_message = mock_ccip_receiver::received_message_get_message(event); + assert!(event_message == string::utf8(test_data)); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @admin, + recipient = @0x999, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_receiver_dispatchable_tokens_with_forwarding( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + recipient: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + MANAGED_TOKEN_POOL, + MANAGED_TOKEN_SEED, + true, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + // Add to allowlist for lock_or_burn/release_or_mint + let pool_address = managed_token_pool::get_store_address(); + managed_token::apply_allowed_minter_updates( + owner, vector[], vector[pool_address] + ); + managed_token::apply_allowed_burner_updates( + owner, vector[], vector[pool_address] + ); + + // Grant receiver state signer BRIDGE_MINTER_OR_BURNER_ROLE (role 6) for transfer during forwarding + let state_address = mock_ccip_receiver::get_state_address(); + managed_token::apply_allowed_minter_updates( + owner, vector[], vector[state_address] + ); + managed_token::apply_allowed_burner_updates( + owner, vector[], vector[state_address] + ); + + let recipient_addr = signer::address_of(recipient); + account::create_account_for_test(recipient_addr); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 200000 + ); + + let test_data = bcs::to_bytes(&recipient_addr); + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000006", + 6, // sequence number + @ccip_offramp, // receiver + test_data, + vector[token_amounts] + ); + + execute_message_and_verify_success(6, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 0); + + let recipient_store = + primary_fungible_store::primary_store(recipient_addr, token_obj); + let recipient_balance = fungible_asset::balance(recipient_store); + assert!(recipient_balance == 200000); + + let forwarded_events = mock_ccip_receiver::get_forwarded_tokens_events(); + assert!(forwarded_events.length() == 1); + } +} diff --git a/contracts/ccip/ccip_offramp/tests/offramp_regulated_receiver_test.move b/contracts/ccip/ccip_offramp/tests/offramp_regulated_receiver_test.move new file mode 100644 index 000000000..9fee9c21f --- /dev/null +++ b/contracts/ccip/ccip_offramp/tests/offramp_regulated_receiver_test.move @@ -0,0 +1,357 @@ +#[test_only] +/// Regulated token is always dispatchable, therefore we test the dispatchable token transfer only +module ccip_offramp::offramp_regulated_receiver_test { + use std::account; + use std::fungible_asset; + use std::object; + use std::primary_fungible_store; + use std::signer; + use std::timestamp; + + use ccip_offramp::offramp_test; + use ccip_offramp::offramp; + use ccip_offramp::mock_ccip_receiver; + use ccip::receiver_registry; + use regulated_token::regulated_token; + use regulated_token_pool::regulated_token_pool; + + const REGULATED_TOKEN_POOL: u8 = 3; + const REGULATED_TOKEN_SEED: vector = b"RegulatedToken"; + + const EVM_SOURCE_CHAIN_SELECTOR: u64 = 909606746561742123; + const MOCK_EVM_ADDRESS_VECTOR: vector = x"4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97"; + const EVM_SENDER: vector = x"d87929a32cf0cbdc9e2d07ffc7c33344079de727"; + const GAS_LIMIT: u64 = 1000000; + + fun setup_mock_ccip_receiver(owner: &signer, ccip_offramp: &signer) { + account::create_account_if_does_not_exist(signer::address_of(ccip_offramp)); + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_module(ccip_offramp); + } + + struct TestMessage has drop { + message: offramp::Any2AptosRampMessage, + merkle_root: vector, + proofs: vector> + } + + fun create_and_commit_message( + message_id: vector, + sequence_number: u64, + receiver: address, + data: vector, + token_amounts: vector + ): TestMessage { + let static_config = offramp::get_static_config(); + let dest_chain_selector = offramp::chain_selector(&static_config); + + let header = + offramp::test_create_ramp_message_header( + message_id, + EVM_SOURCE_CHAIN_SELECTOR, + dest_chain_selector, + sequence_number, + 0 + ); + + let message = + offramp::test_create_any2aptos_ramp_message( + header, + EVM_SENDER, + data, + receiver, + (GAS_LIMIT as u256), + token_amounts + ); + + let source_chain_config = + offramp::get_source_chain_config(EVM_SOURCE_CHAIN_SELECTOR); + let on_ramp = offramp::source_chain_config_on_ramp(&source_chain_config); + + let metadata_hash = + offramp::test_calculate_metadata_hash( + EVM_SOURCE_CHAIN_SELECTOR, dest_chain_selector, on_ramp + ); + + let message_hash = offramp::test_calculate_message_hash(&message, metadata_hash); + let merkle_root = message_hash; + let proofs = vector[]; + + offramp::test_add_root(merkle_root, timestamp::now_seconds() - 3700); + + TestMessage { message, merkle_root, proofs } + } + + fun execute_message_and_verify_success( + sequence_number: u64, + test_message: TestMessage, + offchain_token_data: vector> + ) { + let TestMessage { message, merkle_root: _, proofs } = test_message; + + let report = + offramp::test_create_execution_report( + EVM_SOURCE_CHAIN_SELECTOR, + message, + offchain_token_data, + proofs + ); + + offramp::test_execute_single_report(report); + + let execution_state = + offramp::get_execution_state(EVM_SOURCE_CHAIN_SELECTOR, sequence_number); + assert!(execution_state == 2); + } + + // ======================== DISPATCHABLE TESTS ======================== // + + // Regulated token is always dispatchable, therefore we test the dispatchable token transfer only + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @admin, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_dispatchable_token_transfer_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + REGULATED_TOKEN_POOL, + REGULATED_TOKEN_SEED, + true, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + // Grant pool signer BRIDGE_MINTER_OR_BURNER_ROLE (role 6) for release_or_mint + let pool_address = regulated_token_pool::get_store_address(); + regulated_token::grant_role(owner, 6, pool_address); + + // Grant receiver state signer BRIDGE_MINTER_OR_BURNER_ROLE (role 6) for transfer during forwarding + let state_signer_address = mock_ccip_receiver::get_state_address(); + regulated_token::grant_role(owner, 6, state_signer_address); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 100000 + ); + + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000004", + 4, // sequence number + @ccip_offramp, // receiver + vector[], + vector[token_amounts] + ); + + execute_message_and_verify_success(4, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 100000); + + let tokens_only_events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(tokens_only_events.length() == 1); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @admin, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_execute_dispatchable_message_only( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, _token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + REGULATED_TOKEN_POOL, + REGULATED_TOKEN_SEED, + true, // is_dispatchable + false // use_v1_init + ); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + let test_data = b"Hello from EVM chain!"; + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000005", + 5, // sequence number + @ccip_offramp, // receiver + test_data, + vector[] + ); + + execute_message_and_verify_success(5, test_message, vector[]); + + let received_events = mock_ccip_receiver::get_received_message_events(); + assert!(received_events.length() == 1); + + let event = received_events.borrow(0); + let event_message = mock_ccip_receiver::received_message_get_message(event); + assert!(event_message == std::string::utf8(test_data)); + } + + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @admin, + recipient = @0x999, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_receiver_dispatchable_tokens_with_forwarding( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + recipient: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + REGULATED_TOKEN_POOL, + REGULATED_TOKEN_SEED, + true, // is_dispatchable + false // use_v1_init + ); + let token_addr = object::object_address(&token_obj); + + setup_mock_ccip_receiver(owner, ccip_offramp); + + // Grant pool signer BRIDGE_MINTER_OR_BURNER_ROLE (role 6) for release_or_mint + let pool_address = regulated_token_pool::get_store_address(); + regulated_token::grant_role(owner, 6, pool_address); + + // Grant receiver state signer BRIDGE_MINTER_OR_BURNER_ROLE (role 6) for transfer during forwarding + let state_signer_address = mock_ccip_receiver::get_state_address(); + regulated_token::grant_role(owner, 6, state_signer_address); + + let recipient_addr = signer::address_of(recipient); + account::create_account_for_test(recipient_addr); + + let token_amounts = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + 200000 + ); + + let test_data = std::bcs::to_bytes(&recipient_addr); + let test_message = + create_and_commit_message( + x"0000000000000000000000000000000000000000000000000000000000000006", + 6, // sequence number + @ccip_offramp, // receiver + test_data, + vector[token_amounts] + ); + + execute_message_and_verify_success(6, test_message, vector[vector[]]); + + let token_obj = object::address_to_object(token_addr); + + let receiver_store = + primary_fungible_store::primary_store(@ccip_offramp, token_obj); + let receiver_balance = fungible_asset::balance(receiver_store); + assert!(receiver_balance == 0); + + let recipient_store = + primary_fungible_store::primary_store(recipient_addr, token_obj); + let recipient_balance = fungible_asset::balance(recipient_store); + assert!(recipient_balance == 200000); + + let forwarded_events = mock_ccip_receiver::get_forwarded_tokens_events(); + assert!(forwarded_events.length() == 1); + } +} diff --git a/contracts/ccip/ccip_offramp/tests/offramp_test.move b/contracts/ccip/ccip_offramp/tests/offramp_test.move index a4d0ba8b3..8a22cb1bb 100644 --- a/contracts/ccip/ccip_offramp/tests/offramp_test.move +++ b/contracts/ccip/ccip_offramp/tests/offramp_test.move @@ -10,6 +10,7 @@ module ccip_offramp::offramp_test { use aptos_framework::primary_fungible_store; use aptos_framework::timestamp; use ccip_offramp::bcs_helper; + use ccip_offramp::mock_token; use ccip::merkle_proof; use ccip::token_admin_registry; @@ -23,6 +24,10 @@ module ccip_offramp::offramp_test { use burn_mint_token_pool::burn_mint_token_pool; use lock_release_token_pool::lock_release_token_pool; + use managed_token::managed_token; + use managed_token_pool::managed_token_pool; + use regulated_token_pool::regulated_token_pool; + use regulated_token::regulated_token; const CHAIN_ID: u8 = 100; const EVM_SOURCE_CHAIN_SELECTOR: u64 = 909606746561742123; @@ -41,9 +46,13 @@ module ccip_offramp::offramp_test { const BURN_MINT_TOKEN_POOL: u8 = 0; const LOCK_RELEASE_TOKEN_POOL: u8 = 1; + const MANAGED_TOKEN_POOL: u8 = 2; + const REGULATED_TOKEN_POOL: u8 = 3; const BURN_MINT_TOKEN_SEED: vector = b"TestToken"; const LOCK_RELEASE_TOKEN_SEED: vector = b"LockReleaseToken"; + const MANAGED_TOKEN_SEED: vector = b"ManagedToken"; + const REGULATED_TOKEN_SEED: vector = b"RegulatedToken"; const MOCK_EVM_ADDRESS: address = @0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97; const MOCK_EVM_ADDRESS_VECTOR: vector = x"4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97"; @@ -61,15 +70,23 @@ module ccip_offramp::offramp_test { timestamp::update_global_time_for_test_secs(timestamp_seconds); } - fun setup( + /// use_v1_init: if true, uses test_init_v1 for token pools (V1 compatibility mode) + /// if false, uses test_init_module for token pools (V2 mode, default) + public fun setup( aptos_framework: &signer, ccip: &signer, ccip_offramp: &signer, owner: &signer, burn_mint_token_pool: &signer, lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer, pool_type: u8, - seed: vector + seed: vector, + is_dispatchable: bool, + use_v1_init: bool ): (address, Object) { let owner_addr = signer::address_of(owner); account::create_account_for_test(signer::address_of(burn_mint_token_pool)); @@ -123,10 +140,17 @@ module ccip_offramp::offramp_test { let (token_obj, token_addr) = create_test_token_and_pool( owner, + ccip_offramp, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, pool_type, - seed + seed, + is_dispatchable, + use_v1_init ); // Initialize fee quoter @@ -152,10 +176,17 @@ module ccip_offramp::offramp_test { fun create_test_token_and_pool( owner: &signer, + ccip_offramp_signer: &signer, burn_mint_token_pool: &signer, lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer, pool_type: u8, - seed: vector + seed: vector, + is_dispatchable: bool, + use_v1_init: bool ): (Object, address) { let constructor_ref = object::create_named_object(owner, seed); @@ -177,10 +208,21 @@ module ccip_offramp::offramp_test { let burn_ref = fungible_asset::generate_burn_ref(&constructor_ref); let transfer_ref = fungible_asset::generate_transfer_ref(&constructor_ref); + // Add dynamic dispatch function to the token if it is dispatchable + if (is_dispatchable) { + mock_token::add_dynamic_dispatch_function( + ccip_offramp_signer, &constructor_ref + ); + }; + let token_addr = object::object_address(&metadata); if (pool_type == BURN_MINT_TOKEN_POOL) { - burn_mint_token_pool::test_init_module(burn_mint_token_pool); + if (use_v1_init) { + burn_mint_token_pool::test_init_v1(burn_mint_token_pool); + } else { + burn_mint_token_pool::test_init_module(burn_mint_token_pool); + }; burn_mint_token_pool::initialize(owner, burn_ref, mint_ref); burn_mint_token_pool::apply_chain_updates( owner, @@ -207,8 +249,12 @@ module ccip_offramp::offramp_test { token_admin_registry::set_pool( owner, token_addr, signer::address_of(burn_mint_token_pool) ); - } else { - lock_release_token_pool::test_init_module(lock_release_token_pool); + } else if (pool_type == LOCK_RELEASE_TOKEN_POOL) { + if (use_v1_init) { + lock_release_token_pool::test_init_v1(lock_release_token_pool); + } else { + lock_release_token_pool::test_init_module(lock_release_token_pool); + }; lock_release_token_pool::initialize( owner, option::some(transfer_ref), signer::address_of(owner) ); @@ -237,15 +283,141 @@ module ccip_offramp::offramp_test { token_admin_registry::set_pool( owner, token_addr, signer::address_of(lock_release_token_pool) ); + } else if (pool_type == MANAGED_TOKEN_POOL) { + let seed = b"MT"; + let _constructor_ref = &object::create_named_object(owner, seed); + let _managed_token_pool_constructor_ref = + &object::create_named_object(owner, b"ManagedTokenPool"); + + managed_token::init_module_for_testing(managed_token); + managed_token::initialize( + owner, + option::none(), + string::utf8(b"Managed Token"), + string::utf8(seed), + 6, + string::utf8(b"https://managedtoken.com/images/pic.png"), + string::utf8(b"https://managedtoken.com") + ); + + managed_token_pool::test_init_module(managed_token_pool); + managed_token_pool::apply_chain_updates( + owner, + vector[], + vector[EVM_SOURCE_CHAIN_SELECTOR], + vector[vector[MOCK_EVM_ADDRESS_VECTOR]], + vector[MOCK_EVM_ADDRESS_VECTOR] + ); + managed_token_pool::set_chain_rate_limiter_config( + owner, + EVM_SOURCE_CHAIN_SELECTOR, + true, + OUTBOUND_CAPACITY, + OUTBOUND_RATE, + true, + INBOUND_CAPACITY, + INBOUND_RATE + ); + + token_addr = managed_token::token_metadata(); + metadata = object::address_to_object(token_addr); + + // Set admin for token + token_admin_registry::propose_administrator( + owner, token_addr, signer::address_of(owner) + ); + token_admin_registry::accept_admin_role(owner, token_addr); + token_admin_registry::set_pool( + owner, token_addr, signer::address_of(managed_token_pool) + ); + // Fund managed token pool + primary_fungible_store::mint( + &mint_ref, managed_token_pool::get_store_address(), 1000 + ); + } else if (pool_type == REGULATED_TOKEN_POOL) { + account::create_account_for_test(signer::address_of(owner)); + account::create_account_for_test(signer::address_of(regulated_token_pool)); + account::create_account_for_test(signer::address_of(regulated_token)); + + // Create an object at @regulated_token for the ownable functionality + let regulated_token_pool_constructor_ref = + object::create_named_object(owner, b"regulated_token_pool"); + account::create_account_for_test( + object::address_from_constructor_ref( + ®ulated_token_pool_constructor_ref + ) + ); + + // Setup regulated token first (use admin as the object creator) + let regulated_token_constructor_ref = + object::create_named_object(owner, b"regulated_token"); + account::create_account_for_test( + object::address_from_constructor_ref(®ulated_token_constructor_ref) + ); + + regulated_token::init_module_for_testing(regulated_token); + regulated_token::initialize( + owner, + option::none(), + string::utf8(b"Regulated Token"), + string::utf8(b"RT"), + 6, + string::utf8( + b"https://regulatedtoken.com/images/pic.png" + ), + string::utf8(b"https://regulatedtoken.com") + ); + + if (use_v1_init) { + regulated_token_pool::test_init_v1(regulated_token_pool); + } else { + regulated_token_pool::test_init_module(regulated_token_pool); + }; + regulated_token_pool::apply_chain_updates( + owner, + vector[], + vector[EVM_SOURCE_CHAIN_SELECTOR], + vector[vector[MOCK_EVM_ADDRESS_VECTOR]], + vector[MOCK_EVM_ADDRESS_VECTOR] + ); + regulated_token_pool::set_chain_rate_limiter_config( + owner, + EVM_SOURCE_CHAIN_SELECTOR, + true, + OUTBOUND_CAPACITY, + OUTBOUND_RATE, + true, + INBOUND_CAPACITY, + INBOUND_RATE + ); + + token_addr = regulated_token::token_address(); + metadata = regulated_token::token_metadata(); + + // Set admin for token + token_admin_registry::propose_administrator( + owner, token_addr, signer::address_of(owner) + ); + token_admin_registry::accept_admin_role(owner, token_addr); + token_admin_registry::set_pool( + owner, token_addr, signer::address_of(regulated_token_pool) + ); + + // Fund regulated token pool + primary_fungible_store::mint( + &mint_ref, regulated_token_pool::get_store_address(), 1000 + ); }; let mint_ref = fungible_asset::generate_mint_ref(&constructor_ref); let burn_ref = fungible_asset::generate_burn_ref(&constructor_ref); let transfer_ref = fungible_asset::generate_transfer_ref(&constructor_ref); - // Fund lock/release token pool + // Fund lock/release token pool with sufficient liquidity for tests primary_fungible_store::mint( - &mint_ref, lock_release_token_pool::get_store_address(), 1000 + &mint_ref, + lock_release_token_pool::get_store_address(), + 10000000 // 10M tokens for test liquidity ); move_to( @@ -262,7 +434,7 @@ module ccip_offramp::offramp_test { (metadata, token_addr) } - fun initialize_offramp(owner: &signer): address { + public fun initialize_offramp(owner: &signer): address { offramp::initialize( owner, DEST_CHAIN_SELECTOR, @@ -276,7 +448,7 @@ module ccip_offramp::offramp_test { offramp::get_state_address() } - fun setup_fee_quoter( + public fun setup_fee_quoter( owner: &signer, ccip_offramp: &signer, token_addr: address ) { fee_quoter::apply_fee_token_updates(owner, vector[], vector[token_addr]); @@ -351,7 +523,11 @@ module ccip_offramp::offramp_test { ccip_offramp = @ccip_offramp, owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, - lock_release_token_pool = @lock_release_token_pool + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token ) ] fun test_initialize( @@ -360,7 +536,11 @@ module ccip_offramp::offramp_test { ccip_offramp: &signer, owner: &signer, burn_mint_token_pool: &signer, - lock_release_token_pool: &signer + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer ) { setup( aptos_framework, @@ -369,8 +549,14 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, BURN_MINT_TOKEN_POOL, - BURN_MINT_TOKEN_SEED + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ); // Verify initialization was successful @@ -759,7 +945,11 @@ module ccip_offramp::offramp_test { ccip_offramp = @ccip_offramp, owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, - lock_release_token_pool = @lock_release_token_pool + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token ) ] fun test_commit_and_execute( @@ -768,7 +958,11 @@ module ccip_offramp::offramp_test { ccip_offramp: &signer, owner: &signer, burn_mint_token_pool: &signer, - lock_release_token_pool: &signer + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer ) { setup( aptos_framework, @@ -777,8 +971,14 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, BURN_MINT_TOKEN_POOL, - BURN_MINT_TOKEN_SEED + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ); let config_digest = x"000aed76a87f048dab766bc14ecdbb966f4253e309d742585062a75abfc16c38"; @@ -940,7 +1140,11 @@ module ccip_offramp::offramp_test { ccip_offramp = @ccip_offramp, owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, - lock_release_token_pool = @lock_release_token_pool + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token ) ] fun test_manually_execute( @@ -949,7 +1153,11 @@ module ccip_offramp::offramp_test { ccip_offramp: &signer, owner: &signer, burn_mint_token_pool: &signer, - lock_release_token_pool: &signer + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer ) { setup( aptos_framework, @@ -958,8 +1166,14 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, BURN_MINT_TOKEN_POOL, - BURN_MINT_TOKEN_SEED + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ); let merkle_root = @@ -982,6 +1196,10 @@ module ccip_offramp::offramp_test { owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token, receiver = @0xbed8 ) ] @@ -992,6 +1210,10 @@ module ccip_offramp::offramp_test { owner: &signer, burn_mint_token_pool: &signer, lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer, receiver: &signer ) { test_execute_single_report_with_token_transfer( @@ -1001,9 +1223,15 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, receiver, BURN_MINT_TOKEN_POOL, - BURN_MINT_TOKEN_SEED + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ) } @@ -1015,6 +1243,10 @@ module ccip_offramp::offramp_test { owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token, receiver = @0xbed8 ) ] @@ -1025,6 +1257,10 @@ module ccip_offramp::offramp_test { owner: &signer, burn_mint_token_pool: &signer, lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer, receiver: &signer ) { test_execute_single_report_with_token_transfer( @@ -1034,9 +1270,15 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, receiver, LOCK_RELEASE_TOKEN_POOL, - LOCK_RELEASE_TOKEN_SEED + LOCK_RELEASE_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ) } @@ -1049,9 +1291,15 @@ module ccip_offramp::offramp_test { owner: &signer, burn_mint_token_pool: &signer, lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer, receiver: &signer, pool_type: u8, - token_seed: vector + token_seed: vector, + is_dispatchable: bool, + use_v1_init: bool ) { let (_owner_addr, token_obj) = setup( @@ -1061,8 +1309,14 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, pool_type, - token_seed + token_seed, + is_dispatchable, + use_v1_init ); let token_addr = object::object_address(&token_obj); @@ -1162,7 +1416,11 @@ module ccip_offramp::offramp_test { ccip_offramp = @ccip_offramp, owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, - lock_release_token_pool = @lock_release_token_pool + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token ) ] fun test_transfer_ownership_flow( @@ -1171,7 +1429,11 @@ module ccip_offramp::offramp_test { ccip_offramp: &signer, owner: &signer, burn_mint_token_pool: &signer, - lock_release_token_pool: &signer + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer ) { setup( aptos_framework, @@ -1180,8 +1442,14 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, BURN_MINT_TOKEN_POOL, - BURN_MINT_TOKEN_SEED + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ); let new_owner = signer::address_of(aptos_framework); account::create_account_for_test(new_owner); @@ -1202,7 +1470,11 @@ module ccip_offramp::offramp_test { ccip_offramp = @ccip_offramp, owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, - lock_release_token_pool = @lock_release_token_pool + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token ) ] fun test_getters( @@ -1211,7 +1483,11 @@ module ccip_offramp::offramp_test { ccip_offramp: &signer, owner: &signer, burn_mint_token_pool: &signer, - lock_release_token_pool: &signer + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer ) { setup( aptos_framework, @@ -1220,8 +1496,14 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, BURN_MINT_TOKEN_POOL, - BURN_MINT_TOKEN_SEED + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ); let latest_price_sequence_number = offramp::get_latest_price_sequence_number(); @@ -1241,7 +1523,11 @@ module ccip_offramp::offramp_test { ccip_offramp = @ccip_offramp, owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, - lock_release_token_pool = @lock_release_token_pool + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token ) ] #[expected_failure(abort_code = 65540, location = ccip_offramp::offramp)] @@ -1251,7 +1537,11 @@ module ccip_offramp::offramp_test { ccip_offramp: &signer, owner: &signer, burn_mint_token_pool: &signer, - lock_release_token_pool: &signer + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer ) { setup( aptos_framework, @@ -1260,8 +1550,14 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, BURN_MINT_TOKEN_POOL, - BURN_MINT_TOKEN_SEED + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ); // E_UNKNOWN_SOURCE_CHAIN_SELECTOR @@ -1275,7 +1571,11 @@ module ccip_offramp::offramp_test { ccip_offramp = @ccip_offramp, owner = @0x100, burn_mint_token_pool = @burn_mint_token_pool, - lock_release_token_pool = @lock_release_token_pool + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token ) ] #[expected_failure(abort_code = 65550, location = ccip_offramp::offramp)] @@ -1285,7 +1585,11 @@ module ccip_offramp::offramp_test { ccip_offramp: &signer, owner: &signer, burn_mint_token_pool: &signer, - lock_release_token_pool: &signer + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer ) { setup( aptos_framework, @@ -1294,8 +1598,14 @@ module ccip_offramp::offramp_test { owner, burn_mint_token_pool, lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, BURN_MINT_TOKEN_POOL, - BURN_MINT_TOKEN_SEED + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init ); // E_INVALID_ROOT diff --git a/contracts/ccip/ccip_offramp/tests/offramp_v1_v2_compatibility_test.move b/contracts/ccip/ccip_offramp/tests/offramp_v1_v2_compatibility_test.move new file mode 100644 index 000000000..f143c69d3 --- /dev/null +++ b/contracts/ccip/ccip_offramp/tests/offramp_v1_v2_compatibility_test.move @@ -0,0 +1,682 @@ +#[test_only] +/// Verifies V1 → V2 migration path and that both can coexist +module ccip_offramp::offramp_v1_v2_compatibility_test { + use std::signer; + use std::object; + use std::primary_fungible_store; + use std::timestamp; + + use ccip_offramp::offramp; + use ccip_offramp::offramp_test; + use ccip_offramp::mock_ccip_receiver; + use ccip::receiver_registry; + use ccip::token_admin_registry; + use ccip::merkle_proof; + + use burn_mint_token_pool::upgrade_v2; + + const BURN_MINT_TOKEN_POOL: u8 = 0; + const LOCK_RELEASE_TOKEN_POOL: u8 = 1; + const BURN_MINT_TOKEN_SEED: vector = b"TestToken"; + const LOCK_RELEASE_TOKEN_SEED: vector = b"LockReleaseToken"; + const EVM_SOURCE_CHAIN_SELECTOR: u64 = 909606746561742123; + const DEST_CHAIN_SELECTOR: u64 = 743186221051783445; + const MOCK_EVM_ADDRESS_VECTOR: vector = x"4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97"; + const MOCK_EVM_ADDRESS_VECTOR_2: vector = x"1234567890abcdef1234567890abcdef12345678"; + const ONRAMP_ADDRESS: vector = x"47a1f0a819457f01153f35c6b6b0d42e2e16e91e"; + + // ============================================ + // Test Helper Functions + // ============================================ + fun create_and_execute_message( + message_id: vector, + sequence_number: u64, + receiver: address, + data: vector, + token_transfers: vector, + owner: &signer + ) { + // Configure source chain if first message + if (sequence_number == 0) { + offramp::apply_source_chain_config_updates( + owner, + vector[EVM_SOURCE_CHAIN_SELECTOR], + vector[true], // is_enabled + vector[true], // is_rmn_verification_disabled + vector[ONRAMP_ADDRESS] + ); + }; + + let nonce: u64 = 0; + let sender = x"d87929a32cf0cbdc9e2d07ffc7c33344079de727"; + let gas_limit: u256 = 100000; + + let header = + offramp::test_create_ramp_message_header( + message_id, + EVM_SOURCE_CHAIN_SELECTOR, + DEST_CHAIN_SELECTOR, + sequence_number, + nonce + ); + + // Create offchain_token_data: one empty vector per token transfer + let num_tokens = token_transfers.length(); + let offchain_token_data: vector> = vector[]; + let i = 0; + while (i < num_tokens) { + offchain_token_data.push_back(vector[]); + i = i + 1; + }; + + let message = + offramp::test_create_any2aptos_ramp_message( + header, + sender, + data, + receiver, + gas_limit, + token_transfers + ); + + let metadata_hash = + offramp::test_calculate_metadata_hash( + EVM_SOURCE_CHAIN_SELECTOR, DEST_CHAIN_SELECTOR, ONRAMP_ADDRESS + ); + + let hashed_leaf = offramp::test_calculate_message_hash(&message, metadata_hash); + let proofs = vector[]; + let root = merkle_proof::merkle_root(hashed_leaf, proofs); + + // Commit root (with timestamp in the past to allow execution) + offramp::test_add_root(root, timestamp::now_seconds() - 3700); + + let execution_report = + offramp::test_create_execution_report( + EVM_SOURCE_CHAIN_SELECTOR, + message, + offchain_token_data, + vector[] + ); + + offramp::test_execute_single_report(execution_report); + + // Verify execution state is SUCCESS (2) + let execution_state = + offramp::get_execution_state(EVM_SOURCE_CHAIN_SELECTOR, sequence_number); + assert!(execution_state == 2); + } + + // ============================================ + // Test 1: V1 Receiver Works (Baseline) + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_v1_receiver_works( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + // Setup with V1 pool (use_v1_init = true) + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + true // use_v1_init + ); + + // Setup receiver (need receiver_registry initialized) + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_state_only(ccip_offramp); + + // Register as V1 (dispatchable FA mode) + mock_ccip_receiver::register_as_v1(ccip_offramp); + + // Verify V1 receiver registered (not V2) + assert!( + !receiver_registry::is_registered_receiver_v2( + signer::address_of(ccip_offramp) + ) + ); + assert!( + receiver_registry::is_registered_receiver(signer::address_of(ccip_offramp)) + ); + + // Pool is V1-only (test_init_v1 was called in setup) + // No public function to verify V1 pool registration, but successful execution proves it works + assert!( + !token_admin_registry::has_token_pool_registration_v2( + signer::address_of(burn_mint_token_pool) + ) + ); + + let token_addr = object::object_address(&token_obj); + let token_amount = 1000; + + // Create token transfer + let token_transfer = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, // source_pool_address + token_addr, // dest_token_address + 1000000, // dest_gas_amount + vector[], // extra_data + (token_amount as u256) // amount + ); + + // Execute message with tokens + create_and_execute_message( + x"0001", + 0, + signer::address_of(ccip_offramp), + vector[], // no data, just tokens + vector[token_transfer], + owner + ); + + // Verify tokens received by V1 receiver + let receiver_balance = + primary_fungible_store::balance(signer::address_of(ccip_offramp), token_obj); + assert!(receiver_balance == token_amount); + + // Verify V1 receiver callback was invoked via dispatchable FA + let events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(events.length() == 1); + } + + // ============================================ + // Test 2: V1 → V2 Migration Works + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_v1_to_v2_migration( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + // Setup with V1 pool (use_v1_init = true) + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + true // use_v1_init + ); + + // Setup receiver (need receiver_registry initialized) + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_state_only(ccip_offramp); + + // STEP 1: Register as V1 + mock_ccip_receiver::register_as_v1(ccip_offramp); + assert!( + !receiver_registry::is_registered_receiver_v2( + signer::address_of(ccip_offramp) + ) + ); + assert!( + receiver_registry::is_registered_receiver(signer::address_of(ccip_offramp)) + ); + + let token_addr = object::object_address(&token_obj); + let token_amount = 1000; + + // Execute message with V1 registration + let token_transfer_v1 = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + (token_amount as u256) + ); + + create_and_execute_message( + x"0002", + 0, + signer::address_of(ccip_offramp), + vector[], + vector[token_transfer_v1], + owner + ); + + // Verify V1 worked + let balance_after_v1 = + primary_fungible_store::balance(signer::address_of(ccip_offramp), token_obj); + assert!(balance_after_v1 == token_amount); + + // STEP 2: Upgrade pool to V2 (realistic upgrade pattern) + upgrade_v2::test_init_module(burn_mint_token_pool); + + // STEP 3: Migrate receiver to V2 + mock_ccip_receiver::migrate_to_v2(ccip_offramp); + + // Verify V2 is now active (V2 registration exists) + assert!( + receiver_registry::is_registered_receiver_v2(signer::address_of(ccip_offramp)) + ); + + // Execute message with V2 registration (dispatcher should prefer V2) + let token_transfer_v2 = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + (token_amount as u256) + ); + + create_and_execute_message( + x"0003", + 1, + signer::address_of(ccip_offramp), + vector[], + vector[token_transfer_v2], + owner + ); + + // Verify V2 worked - should now have 2x token_amount + let balance_after_v2 = + primary_fungible_store::balance(signer::address_of(ccip_offramp), token_obj); + assert!(balance_after_v2 == token_amount * 2); + + // Verify both V1 and V2 callbacks were invoked + let events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(events.length() == 2); // 1 from V1 execution, 1 from V2 execution + } + + // ============================================ + // Test 3: Direct V2 Registration Works + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_v2_receiver_direct( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + // Setup with V1 pool initially (use_v1_init = true) + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + true // use_v1_init + ); + + // Upgrade pool to V2 immediately (realistic new deployment with V2) + upgrade_v2::test_init_module(burn_mint_token_pool); + + // Setup V2 receiver directly (default behavior) + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_module(ccip_offramp); + + // Verify V2 receiver registered + assert!( + receiver_registry::is_registered_receiver_v2(signer::address_of(ccip_offramp)) + ); + + // Verify V2 pool registered + assert!( + token_admin_registry::has_token_pool_registration_v2( + signer::address_of(burn_mint_token_pool) + ) + ); + + let token_addr = object::object_address(&token_obj); + let token_amount = 1000; + + // Create token transfer + let token_transfer = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + (token_amount as u256) + ); + + // Execute message with tokens + create_and_execute_message( + x"0004", + 0, + signer::address_of(ccip_offramp), + vector[], + vector[token_transfer], + owner + ); + + // Verify tokens received by V2 receiver + let receiver_balance = + primary_fungible_store::balance(signer::address_of(ccip_offramp), token_obj); + assert!(receiver_balance == token_amount); + + // Verify V2 receiver callback was invoked + let events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(events.length() == 1); + } + + // ============================================ + // Test 4: Dispatcher Routes Correctly + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_dispatcher_routing( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + // Setup with V1 pool initially (use_v1_init = true) + let (_owner_addr, token_obj) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + true // use_v1_init + ); + + // Upgrade pool to V2 before testing (mixed V1 receiver + V2 pool scenario) + upgrade_v2::test_init_module(burn_mint_token_pool); + + // Setup receiver (need receiver_registry initialized) + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_state_only(ccip_offramp); + + let token_addr = object::object_address(&token_obj); + let token_amount = 500; + + // Phase 1: Register V1, verify dispatcher uses V1 path + mock_ccip_receiver::register_as_v1(ccip_offramp); + assert!( + !receiver_registry::is_registered_receiver_v2( + signer::address_of(ccip_offramp) + ) + ); + + let token_transfer_1 = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + (token_amount as u256) + ); + + create_and_execute_message( + x"0005", + 0, + signer::address_of(ccip_offramp), + vector[], + vector[token_transfer_1], + owner + ); + + let balance_1 = + primary_fungible_store::balance(signer::address_of(ccip_offramp), token_obj); + assert!(balance_1 == token_amount); + + // Phase 2: Add V2 registration, verify dispatcher now uses V2 path + mock_ccip_receiver::migrate_to_v2(ccip_offramp); + assert!( + receiver_registry::is_registered_receiver_v2(signer::address_of(ccip_offramp)) + ); + + let token_transfer_2 = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, + token_addr, + 1000000, + vector[], + (token_amount as u256) + ); + + create_and_execute_message( + x"0006", + 1, + signer::address_of(ccip_offramp), + vector[], + vector[token_transfer_2], + owner + ); + + // Should have received both transfers + let balance_2 = + primary_fungible_store::balance(signer::address_of(ccip_offramp), token_obj); + assert!(balance_2 == token_amount * 2); + + // Both callbacks should have been invoked + let events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(events.length() == 2); + } + + // ============================================ + // Test 5: Multi-Transfer Message with V2 Receiver + // Tests V2 receiver handling multiple token transfers in a single message + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + ccip = @ccip, + ccip_offramp = @ccip_offramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + managed_token_pool = @managed_token_pool, + managed_token = @managed_token, + regulated_token_pool = @regulated_token_pool, + regulated_token = @regulated_token + ) + ] + fun test_multi_token_v2_receiver( + aptos_framework: &signer, + ccip: &signer, + ccip_offramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + managed_token_pool: &signer, + managed_token: &signer, + regulated_token_pool: &signer, + regulated_token: &signer + ) { + // Setup first pool: burn_mint with V2 + let (_owner_addr, token_obj_1) = + offramp_test::setup( + aptos_framework, + ccip, + ccip_offramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + managed_token_pool, + managed_token, + regulated_token_pool, + regulated_token, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v1_init = false (V2 from start) + ); + + let token_addr_1 = object::object_address(&token_obj_1); + + receiver_registry::init_module_for_testing(owner); + mock_ccip_receiver::test_init_module(ccip_offramp); + + assert!( + receiver_registry::is_registered_receiver_v2(signer::address_of(ccip_offramp)) + ); + + // Verify pool has V2 config + assert!( + token_admin_registry::has_token_pool_registration_v2( + signer::address_of(burn_mint_token_pool) + ) + ); + + // Create 2 token transfers of the same token + // This tests that V2 receiver can handle multiple transfers in one message + // and that pool closures can be invoked multiple times sequentially + let token_amount_1 = 1000; + let token_amount_2 = 2000; + + let token_transfer_1 = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, // source_pool_address + token_addr_1, // dest_token_address + 1000000, // dest_gas_amount + vector[], // extra_data + (token_amount_1 as u256) // amount + ); + + let token_transfer_2 = + offramp::test_create_any2aptos_token_transfer( + MOCK_EVM_ADDRESS_VECTOR, // same source_pool_address + token_addr_1, // dest_token_address (same token) + 1000000, // dest_gas_amount + vector[], // extra_data + (token_amount_2 as u256) // amount + ); + + // Execute message with 2 transfers + create_and_execute_message( + x"0007", // unique message_id + 0, // sequence_number + signer::address_of(ccip_offramp), // receiver + vector[], // no data, just tokens + vector[token_transfer_1, token_transfer_2], // 2 token transfers + owner + ); + + // Verify BOTH 2 transfers were received (total amount = amount_1 + amount_2) + let total_balance = + primary_fungible_store::balance( + signer::address_of(ccip_offramp), token_obj_1 + ); + assert!( + total_balance == token_amount_1 + token_amount_2 + ); + + // Verify V2 receiver callback was invoked once with multiple tokens + // The mock receiver's ccip_receive_v2 handles multiple tokens in a loop + // and emits a single ReceivedTokensOnly event + let events = mock_ccip_receiver::get_received_tokens_only_events(); + assert!(events.length() == 1); + } +} diff --git a/contracts/ccip/ccip_onramp/tests/mock_token.move b/contracts/ccip/ccip_onramp/tests/mock/mock_token.move similarity index 77% rename from contracts/ccip/ccip_onramp/tests/mock_token.move rename to contracts/ccip/ccip_onramp/tests/mock/mock_token.move index eded7cfcb..d22f1566e 100644 --- a/contracts/ccip/ccip_onramp/tests/mock_token.move +++ b/contracts/ccip/ccip_onramp/tests/mock/mock_token.move @@ -1,10 +1,9 @@ #[test_only] module ccip_onramp::mock_token { use std::fungible_asset::{Self, FungibleAsset, TransferRef}; - use std::object::{Self, Object, ConstructorRef}; + use std::object::{Object, ConstructorRef}; use std::string::{Self}; use std::option::{Self}; - use std::primary_fungible_store; public fun add_dynamic_dispatch_function( ccip_onramp_signer: &signer, constructor_ref: &ConstructorRef @@ -30,16 +29,14 @@ module ccip_onramp::mock_token { } public fun lock_or_burn( - store: Object, fa: FungibleAsset, _transfer_ref: &TransferRef + store: Object, fa: FungibleAsset, transfer_ref: &TransferRef ) { - fungible_asset::deposit(store, fa); + fungible_asset::deposit_with_ref(transfer_ref, store, fa); } public fun release_or_mint( store: Object, amount: u64, transfer_ref: &TransferRef ): FungibleAsset { - primary_fungible_store::withdraw_with_ref( - transfer_ref, object::owner(store), amount - ) + fungible_asset::withdraw_with_ref(transfer_ref, store, amount) } } diff --git a/contracts/ccip/ccip_onramp/tests/onramp_migration_test.move b/contracts/ccip/ccip_onramp/tests/onramp_migration_test.move index 68b4b5109..c6749bc1e 100644 --- a/contracts/ccip/ccip_onramp/tests/onramp_migration_test.move +++ b/contracts/ccip/ccip_onramp/tests/onramp_migration_test.move @@ -854,7 +854,8 @@ module ccip_onramp::onramp_migration_test { lock_release_token_pool, pool_type, seed, - is_dispatchable + is_dispatchable, + false // use_v1_init ); let one_e_18 = 1_000_000_000_000_000_000; diff --git a/contracts/ccip/ccip_onramp/tests/onramp_test.move b/contracts/ccip/ccip_onramp/tests/onramp_test.move index 1f4bca97e..5d491e901 100644 --- a/contracts/ccip/ccip_onramp/tests/onramp_test.move +++ b/contracts/ccip/ccip_onramp/tests/onramp_test.move @@ -66,6 +66,17 @@ module ccip_onramp::onramp_test { transfer_ref: TransferRef } + public fun mint_test_tokens( + token_addr: address, recipient: address, amount: u64 + ) acquires TestToken { + let token = borrow_global(token_addr); + let recipient_store = + primary_fungible_store::ensure_primary_store_exists( + recipient, token.metadata + ); + fungible_asset::mint_to(&token.mint_ref, recipient_store, amount); + } + fun init_timestamp(aptos_framework: &signer, timestamp_seconds: u64) { timestamp::set_time_has_started_for_testing(aptos_framework); timestamp::update_global_time_for_test_secs(timestamp_seconds); @@ -81,7 +92,8 @@ module ccip_onramp::onramp_test { lock_release_token_pool: &signer, pool_type: u8, // 0 for burn_mint, 1 for lock_release seed: vector, - is_dispatchable: bool + is_dispatchable: bool, + use_v1_init: bool ): (address, Object) { let owner_addr = signer::address_of(owner); account::create_account_for_test(signer::address_of(burn_mint_token_pool)); @@ -138,7 +150,8 @@ module ccip_onramp::onramp_test { lock_release_token_pool, pool_type, seed, - is_dispatchable + is_dispatchable, + use_v1_init ); let one_e_18 = 1_000_000_000_000_000_000; @@ -185,12 +198,26 @@ module ccip_onramp::onramp_test { vector[900_000_000_000_000_000] // premium_multiplier_wei_per_eth ); + // Configure token transfer fees (needed for token transfers) + fee_quoter::apply_token_transfer_fee_config_updates( + owner, + DEST_CHAIN_SELECTOR, + vector[token_addr], + vector[50], // min_fee_usd_cents + vector[500], // max_fee_usd_cents + vector[10], // deci_bps + vector[5000], // dest_gas_overhead + vector[64], // dest_bytes_overhead + vector[true], // is_enabled + vector[] + ); + // To be able to call token_admin_dispatcher::dispatch_lock_or_burn - // Need to register onramp signer as an allowed onramp + // Need to register onramp state address as an allowed onramp auth::apply_allowed_onramp_updates( owner, vector[], // onramps_to_remove - vector[signer::address_of(ccip_onramp)] // onramps_to_add + vector[onramp::get_state_address()] // onramps_to_add ); // To be able to call fee_quoter::update_prices, need to register as an allowed offramp @@ -218,7 +245,8 @@ module ccip_onramp::onramp_test { lock_release_token_pool: &signer, pool_type: u8, // 0 for burn_mint, 1 for lock_release seed: vector, - is_dispatchable: bool + is_dispatchable: bool, + use_v1_init: bool ): (Object, address) { let constructor_ref = object::create_named_object(owner, seed); @@ -252,7 +280,11 @@ module ccip_onramp::onramp_test { eth_abi::encode_address(&mut remote_token_address, MOCK_EVM_ADDRESS); if (pool_type == BURN_MINT_TOKEN_POOL) { - burn_mint_token_pool::test_init_module(burn_mint_token_pool); + if (use_v1_init) { + burn_mint_token_pool::test_init_v1(burn_mint_token_pool); + } else { + burn_mint_token_pool::test_init_module(burn_mint_token_pool); + }; burn_mint_token_pool::initialize(owner, burn_ref, mint_ref); burn_mint_token_pool::apply_chain_updates( owner, @@ -280,7 +312,11 @@ module ccip_onramp::onramp_test { owner, token_addr, signer::address_of(burn_mint_token_pool) ); } else { - lock_release_token_pool::test_init_module(lock_release_token_pool); + if (use_v1_init) { + lock_release_token_pool::test_init_v1(lock_release_token_pool); + } else { + lock_release_token_pool::test_init_module(lock_release_token_pool); + }; lock_release_token_pool::initialize( owner, transfer_ref, signer::address_of(owner) ); @@ -333,7 +369,7 @@ module ccip_onramp::onramp_test { (metadata, token_addr) } - fun initialize_onramp(owner: &signer, router: &signer): address { + public fun initialize_onramp(owner: &signer, router: &signer): address { onramp::initialize( owner, SOURCE_CHAIN_SELECTOR, @@ -387,6 +423,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -443,6 +480,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -498,6 +536,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -535,6 +574,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -615,6 +655,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -703,6 +744,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -819,6 +861,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, LOCK_RELEASE_TOKEN_POOL, b"LockReleaseToken", + false, false ); @@ -939,6 +982,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); initialize_onramp(owner, router); @@ -978,6 +1022,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1015,6 +1060,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1054,6 +1100,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1114,6 +1161,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1163,6 +1211,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1202,6 +1251,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1268,6 +1318,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1325,6 +1376,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1376,6 +1428,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); @@ -1454,6 +1507,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); setup_mcms(mcms); @@ -1521,6 +1575,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); setup_mcms(mcms); @@ -1596,6 +1651,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); setup_mcms(mcms); @@ -1679,6 +1735,7 @@ module ccip_onramp::onramp_test { lock_release_token_pool, BURN_MINT_TOKEN_POOL, b"TestToken", + false, false ); setup_mcms(mcms); diff --git a/contracts/ccip/ccip_onramp/tests/onramp_v1_v2_pool_compatibility_test.move b/contracts/ccip/ccip_onramp/tests/onramp_v1_v2_pool_compatibility_test.move new file mode 100644 index 000000000..e543f137f --- /dev/null +++ b/contracts/ccip/ccip_onramp/tests/onramp_v1_v2_pool_compatibility_test.move @@ -0,0 +1,586 @@ +#[test_only] +/// Verifies V1 → V2 migration path and that both can coexist +module ccip_onramp::onramp_v1_v2_pool_compatibility_test { + use std::signer; + use std::object; + use std::primary_fungible_store; + + use ccip::client; + use ccip::token_admin_registry; + use ccip::eth_abi; + use ccip_onramp::onramp; + + use burn_mint_token_pool::burn_mint_token_pool; + use burn_mint_token_pool::upgrade_v2 as burn_mint_upgrade_v2; + use lock_release_token_pool::lock_release_token_pool; + use lock_release_token_pool::upgrade_v2 as lock_release_upgrade_v2; + + use ccip_onramp::onramp_test; + + const DEST_CHAIN_SELECTOR: u64 = 5678; + const TOKEN_AMOUNT: u64 = 5000; + + const SENDER: address = @0x500; + + const BURN_MINT_TOKEN_POOL: u8 = 0; + const LOCK_RELEASE_TOKEN_POOL: u8 = 1; + + const BURN_MINT_TOKEN_SEED: vector = b"TestToken"; + const LOCK_RELEASE_TOKEN_SEED: vector = b"LockReleaseToken"; + + const MOCK_EVM_ADDRESS: address = @0x4838B106FCe9647Bdf1E7877BF73cE8B0BAD5f97; + + const GAS_LIMIT: u64 = 5000000; + const ALLOW_OUT_OF_ORDER_EXECUTION: bool = true; + + fun create_extra_args_v2(): vector { + client::encode_generic_extra_args_v2( + GAS_LIMIT as u256, ALLOW_OUT_OF_ORDER_EXECUTION + ) + } + + fun encode_receiver(): vector { + let receiver = vector[]; + eth_abi::encode_address(&mut receiver, MOCK_EVM_ADDRESS); + receiver + } + + /// Helper to calculate fee and mint enough tokens for sender + fun mint_tokens_for_transfer(token_addr: address, num_transfers: u64) { + let receiver = encode_receiver(); + let extra_args = create_extra_args_v2(); + + // Calculate fee for one transfer + let fee_amount = + onramp::get_fee( + DEST_CHAIN_SELECTOR, + receiver, + vector[], // data + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], + token_addr, + @0x0, + extra_args + ); + + // Mint enough for transfers + fees + let total_needed = (TOKEN_AMOUNT + fee_amount) * num_transfers; + onramp_test::mint_test_tokens(token_addr, SENDER, total_needed); + } + + // ============================================ + // Test 1: V1 Burn/Mint Pool Baseline + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + router = @0x200, + ccip = @ccip, + ccip_onramp = @ccip_onramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + sender = @0x500 + ) + ] + fun test_v1_burn_mint_pool_baseline( + aptos_framework: &signer, + router: &signer, + ccip: &signer, + ccip_onramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + sender: &signer + ) { + let (_owner_addr, token_obj) = + onramp_test::setup( + aptos_framework, + router, + ccip, + ccip_onramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + true // use_v1_init + ); + + let token_addr = object::object_address(&token_obj); + + // Fund sender with enough tokens for transfer + fees + mint_tokens_for_transfer(token_addr, 1); + + // Verify V1 pool registered (not V2) + assert!( + !token_admin_registry::has_token_pool_registration_v2( + signer::address_of(burn_mint_token_pool) + ) + ); + + // Send tokens via onramp + let sender_balance_before = primary_fungible_store::balance(SENDER, token_obj); + + onramp::ccip_send( + router, + sender, + DEST_CHAIN_SELECTOR, + encode_receiver(), + vector[], // data + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], // token_store_addresses - use primary store + token_addr, // fee_token + @0x0, // fee_token_store - use primary store + create_extra_args_v2() + ); + + // Verify tokens were burned from sender + let sender_balance_after = primary_fungible_store::balance(SENDER, token_obj); + assert!( + sender_balance_before - sender_balance_after >= TOKEN_AMOUNT + ); + + // Verify V1 callback worked - check events + let events = + burn_mint_token_pool::get_locked_or_burned_events( + burn_mint_token_pool::get_store_address() + ); + assert!(events.length() >= 1); + } + + // ============================================ + // Test 2: V1 → V2 Burn/Mint Migration + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + router = @0x200, + ccip = @ccip, + ccip_onramp = @ccip_onramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + sender = @0x500 + ) + ] + fun test_v1_to_v2_burn_mint_migration( + aptos_framework: &signer, + router: &signer, + ccip: &signer, + ccip_onramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + sender: &signer + ) { + let (_owner_addr, token_obj) = + onramp_test::setup( + aptos_framework, + router, + ccip, + ccip_onramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + true // use_v1_init + ); + + let token_addr = object::object_address(&token_obj); + + // Fund sender with enough tokens for 2 transfers + fees + mint_tokens_for_transfer(token_addr, 2); + + // STEP 1: Send with V1 pool + onramp::ccip_send( + router, + sender, + DEST_CHAIN_SELECTOR, + encode_receiver(), + vector[], + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], + token_addr, + @0x0, + create_extra_args_v2() + ); + + // Verify V1 callback worked + let events_before = + burn_mint_token_pool::get_locked_or_burned_events( + burn_mint_token_pool::get_store_address() + ); + assert!(events_before.length() == 1); + + // STEP 2: Upgrade to V2 + burn_mint_upgrade_v2::test_init_module(burn_mint_token_pool); + + // Verify V2 config now exists + assert!( + token_admin_registry::has_token_pool_registration_v2( + signer::address_of(burn_mint_token_pool) + ) + ); + + // STEP 3: Send with V2 pool + onramp::ccip_send( + router, + sender, + DEST_CHAIN_SELECTOR, + encode_receiver(), + vector[], + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], + token_addr, + @0x0, + create_extra_args_v2() + ); + + // Verify V2 callback worked + let events_after = + burn_mint_token_pool::get_locked_or_burned_events( + burn_mint_token_pool::get_store_address() + ); + assert!(events_after.length() == 2); + } + + // ============================================ + // Test 3: V2 Burn/Mint Direct (no migration) + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + router = @0x200, + ccip = @ccip, + ccip_onramp = @ccip_onramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + sender = @0x500 + ) + ] + fun test_v2_burn_mint_direct( + aptos_framework: &signer, + router: &signer, + ccip: &signer, + ccip_onramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + sender: &signer + ) { + let (_owner_addr, token_obj) = + onramp_test::setup( + aptos_framework, + router, + ccip, + ccip_onramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + BURN_MINT_TOKEN_POOL, + BURN_MINT_TOKEN_SEED, + false, // is_dispatchable + false // use_v2_init (V2) + ); + + let token_addr = object::object_address(&token_obj); + + // Fund sender with enough tokens for transfer + fees + mint_tokens_for_transfer(token_addr, 1); + + // Verify V2 config registered (already done by setup) + assert!( + token_admin_registry::has_token_pool_registration_v2( + signer::address_of(burn_mint_token_pool) + ) + ); + + // Send message using V2 pool + onramp::ccip_send( + router, + sender, + DEST_CHAIN_SELECTOR, + encode_receiver(), + vector[], + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], + token_addr, + @0x0, + create_extra_args_v2() + ); + + // Verify V2 callback worked + let events = + burn_mint_token_pool::get_locked_or_burned_events( + burn_mint_token_pool::get_store_address() + ); + assert!(events.length() == 1); + } + + // ============================================ + // Test 4: V1 Lock/Release Pool Baseline + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + router = @0x200, + ccip = @ccip, + ccip_onramp = @ccip_onramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + sender = @0x500 + ) + ] + fun test_v1_lock_release_pool_baseline( + aptos_framework: &signer, + router: &signer, + ccip: &signer, + ccip_onramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + sender: &signer + ) { + let (_owner_addr, token_obj) = + onramp_test::setup( + aptos_framework, + router, + ccip, + ccip_onramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + false, // is_dispatchable + true // use_v1_init + ); + + let token_addr = object::object_address(&token_obj); + + // Fund sender with enough tokens for transfer + fees + mint_tokens_for_transfer(token_addr, 1); + + // Verify V1 pool registered (not V2) + assert!( + !token_admin_registry::has_token_pool_registration_v2( + signer::address_of(lock_release_token_pool) + ) + ); + + // Send tokens via onramp + let sender_balance_before = primary_fungible_store::balance(SENDER, token_obj); + + onramp::ccip_send( + router, + sender, + DEST_CHAIN_SELECTOR, + encode_receiver(), + vector[], + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], + token_addr, + @0x0, + create_extra_args_v2() + ); + + // Verify tokens were locked from sender + let sender_balance_after = primary_fungible_store::balance(SENDER, token_obj); + assert!( + sender_balance_before - sender_balance_after >= TOKEN_AMOUNT + ); + + // Verify V1 callback worked - check events + let events = + lock_release_token_pool::get_locked_or_burned_events( + lock_release_token_pool::get_store_address() + ); + assert!(events.length() >= 1); + } + + // ============================================ + // Test 5: V1 → V2 Lock/Release Migration + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + router = @0x200, + ccip = @ccip, + ccip_onramp = @ccip_onramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + sender = @0x500 + ) + ] + fun test_v1_to_v2_lock_release_migration( + aptos_framework: &signer, + router: &signer, + ccip: &signer, + ccip_onramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + sender: &signer + ) { + let (_owner_addr, token_obj) = + onramp_test::setup( + aptos_framework, + router, + ccip, + ccip_onramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + false, // is_dispatchable + true // use_v1_init + ); + + let token_addr = object::object_address(&token_obj); + + // Fund sender with enough tokens for 2 transfers + fees + mint_tokens_for_transfer(token_addr, 2); + + // STEP 1: Send with V1 + onramp::ccip_send( + router, + sender, + DEST_CHAIN_SELECTOR, + encode_receiver(), + vector[], + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], + token_addr, + @0x0, + create_extra_args_v2() + ); + + // Verify V1 callback worked + let events_before = + lock_release_token_pool::get_locked_or_burned_events( + lock_release_token_pool::get_store_address() + ); + assert!(events_before.length() == 1); + + // STEP 2: Upgrade to V2 + lock_release_upgrade_v2::test_init_module(lock_release_token_pool); + + // Verify V2 config now exists + assert!( + token_admin_registry::has_token_pool_registration_v2( + signer::address_of(lock_release_token_pool) + ) + ); + + // STEP 3: Send with V2 pool + onramp::ccip_send( + router, + sender, + DEST_CHAIN_SELECTOR, + encode_receiver(), + vector[], + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], + token_addr, + @0x0, + create_extra_args_v2() + ); + + // Verify V2 callback worked + let events_after = + lock_release_token_pool::get_locked_or_burned_events( + lock_release_token_pool::get_store_address() + ); + assert!(events_after.length() == 2); + } + + // ============================================ + // Test 6: V2 Lock/Release Direct (no migration) + // ============================================ + #[ + test( + aptos_framework = @aptos_framework, + router = @0x200, + ccip = @ccip, + ccip_onramp = @ccip_onramp, + owner = @0x100, + burn_mint_token_pool = @burn_mint_token_pool, + lock_release_token_pool = @lock_release_token_pool, + sender = @0x500 + ) + ] + fun test_v2_lock_release_direct( + aptos_framework: &signer, + router: &signer, + ccip: &signer, + ccip_onramp: &signer, + owner: &signer, + burn_mint_token_pool: &signer, + lock_release_token_pool: &signer, + sender: &signer + ) { + let (_owner_addr, token_obj) = + onramp_test::setup( + aptos_framework, + router, + ccip, + ccip_onramp, + owner, + burn_mint_token_pool, + lock_release_token_pool, + LOCK_RELEASE_TOKEN_POOL, + LOCK_RELEASE_TOKEN_SEED, + false, // is_dispatchable + false // use_v2_init (V2) + ); + + let token_addr = object::object_address(&token_obj); + + // Fund sender with enough tokens for transfer + fees + mint_tokens_for_transfer(token_addr, 1); + + assert!( + token_admin_registry::has_token_pool_registration_v2( + signer::address_of(lock_release_token_pool) + ) + ); + + // Send message using V2 pool + onramp::ccip_send( + router, + sender, + DEST_CHAIN_SELECTOR, + encode_receiver(), + vector[], + vector[token_addr], + vector[TOKEN_AMOUNT], + vector[@0x0], + token_addr, + @0x0, + create_extra_args_v2() + ); + + // Verify V2 callback worked + let events = + lock_release_token_pool::get_locked_or_burned_events( + lock_release_token_pool::get_store_address() + ); + assert!(events.length() == 1); + } +} diff --git a/contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move b/contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move index 308e4e857..8a21728be 100644 --- a/contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move +++ b/contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move @@ -7,9 +7,9 @@ module burn_mint_token_pool::burn_mint_token_pool { use std::option::{Self, Option}; use std::signer; use std::string::{Self, String}; - use aptos_framework::fungible_asset::{BurnRef, MintRef}; + use std::fungible_asset::{BurnRef, MintRef}; - use ccip::token_admin_registry; + use ccip::token_admin_registry::{Self, ReleaseOrMintInputV1, LockOrBurnInputV1}; use ccip_token_pool::ownable; use ccip_token_pool::rate_limiter; use ccip_token_pool::token_pool; @@ -72,12 +72,8 @@ module burn_mint_token_pool::burn_mint_token_pool { register_mcms_entrypoint(publisher, token_pool_module_name); }; - token_admin_registry::register_pool( - publisher, - token_pool_module_name, - @burn_mint_local_token, - CallbackProof {} - ); + // Register V2 pool with closure-based callbacks + register_v2_callbacks(publisher); // create a resource account to be the owner of the primary FungibleStore we will use. let (store_signer, store_signer_cap) = @@ -140,6 +136,19 @@ module burn_mint_token_pool::burn_mint_token_pool { move_to(&store_signer, pool); } + public fun register_v2_callbacks(publisher: &signer) { + assert!( + signer::address_of(publisher) == @burn_mint_token_pool, + error::permission_denied(E_NOT_PUBLISHER) + ); + token_admin_registry::register_pool_v2( + publisher, + @burn_mint_local_token, + lock_or_burn_v2, + release_or_mint_v2 + ); + } + // ================================================================ // | Exposing token_pool functions | // ================================================================ @@ -365,6 +374,66 @@ module burn_mint_token_pool::burn_mint_token_pool { fa } + #[persistent] + fun lock_or_burn_v2( + fa: FungibleAsset, input: LockOrBurnInputV1 + ): (vector, vector) acquires BurnMintTokenPoolState { + let pool = borrow_pool_mut(); + let fa_amount = fungible_asset::amount(&fa); + + let dest_token_address = + token_pool::validate_lock_or_burn( + &mut pool.token_pool_state, + &fa, + &input, + fa_amount + ); + + // Burn the token + assert!(pool.burn_ref.is_some(), E_BURN_REF_NOT_SET); + fungible_asset::burn(pool.burn_ref.borrow(), fa); + + // Emit event + let remote_chain_selector = + token_admin_registry::get_lock_or_burn_remote_chain_selector(&input); + + token_pool::emit_locked_or_burned( + &mut pool.token_pool_state, fa_amount, remote_chain_selector + ); + + (dest_token_address, token_pool::encode_local_decimals(&pool.token_pool_state)) + } + + #[persistent] + fun release_or_mint_v2( + input: ReleaseOrMintInputV1 + ): (FungibleAsset, u64) acquires BurnMintTokenPoolState { + let pool = borrow_pool_mut(); + let local_amount = + token_pool::calculate_release_or_mint_amount(&pool.token_pool_state, &input); + + token_pool::validate_release_or_mint( + &mut pool.token_pool_state, &input, local_amount + ); + + // Mint the amount for release + assert!(pool.mint_ref.is_some(), E_MINT_REF_NOT_SET); + let fa = fungible_asset::mint(pool.mint_ref.borrow(), local_amount); + + let recipient = token_admin_registry::get_release_or_mint_receiver(&input); + let remote_chain_selector = + token_admin_registry::get_release_or_mint_remote_chain_selector(&input); + + token_pool::emit_released_or_minted( + &mut pool.token_pool_state, + recipient, + local_amount, + remote_chain_selector + ); + + (fa, local_amount) + } + // ================================================================ // | Rate limit config | // ================================================================ @@ -727,4 +796,61 @@ module burn_mint_token_pool::burn_mint_token_pool { &borrow_global(state).token_pool_state ) } + + #[test_only] + /// Used for registering the pool with V2 closure-based callbacks. + public fun create_callback_proof(): CallbackProof { + CallbackProof {} + } + + #[test_only] + public fun test_init_v1(publisher: &signer) { + // register the pool on deployment, because in the case of object code deployment, + // this is the only time we have a signer ref to @ccip_burn_mint_pool. + assert!( + object::object_exists(@burn_mint_local_token), + error::invalid_argument(E_INVALID_FUNGIBLE_ASSET) + ); + let metadata = object::address_to_object(@burn_mint_local_token); + + // create an Account on the object for event handles. + account::create_account_if_does_not_exist(@burn_mint_token_pool); + + // the name of this module. if incorrect, callbacks will fail to be registered and + // register_pool will revert. + let token_pool_module_name = b"burn_mint_token_pool"; + + // Register the entrypoint with mcms + if (@mcms_register_entrypoints == @0x1) { + register_mcms_entrypoint(publisher, token_pool_module_name); + }; + + token_admin_registry::register_pool( + publisher, + token_pool_module_name, + @burn_mint_local_token, + CallbackProof {} + ); + + // create a resource account to be the owner of the primary FungibleStore we will use. + let (store_signer, store_signer_cap) = + account::create_resource_account(publisher, STORE_OBJECT_SEED); + + // make sure this is a valid fungible asset that is primary fungible store enabled, + // ie. created with primary_fungible_store::create_primary_store_enabled_fungible_asset + primary_fungible_store::ensure_primary_store_exists( + signer::address_of(&store_signer), metadata + ); + + move_to( + publisher, + BurnMintTokenPoolDeployment { + store_signer_cap, + ownable_state: ownable::new(&store_signer, @burn_mint_token_pool), + token_pool_state: token_pool::initialize( + &store_signer, @burn_mint_local_token, vector[] + ) + } + ); + } } diff --git a/contracts/ccip/ccip_token_pools/burn_mint_token_pool/tests/upgrade_v2.move b/contracts/ccip/ccip_token_pools/burn_mint_token_pool/tests/upgrade_v2.move new file mode 100644 index 000000000..6f442a05b --- /dev/null +++ b/contracts/ccip/ccip_token_pools/burn_mint_token_pool/tests/upgrade_v2.move @@ -0,0 +1,32 @@ +#[test_only] +module burn_mint_token_pool::upgrade_v2 { + use std::account::{Self}; + use std::error; + use std::fungible_asset::{Metadata}; + use std::object::{Self}; + + use burn_mint_token_pool::burn_mint_token_pool; + + const E_INVALID_FUNGIBLE_ASSET: u64 = 1; + + fun init_module(publisher: &signer) { + // register the pool on deployment, because in the case of object code deployment, + // this is the only time we have a signer ref to @burn_mint_token_pool. + assert!( + object::object_exists(@burn_mint_local_token), + error::invalid_argument(E_INVALID_FUNGIBLE_ASSET) + ); + + // create an Account on the object for event handles. + account::create_account_if_does_not_exist(@burn_mint_token_pool); + + // If the contract has already been deployed with V1 and needs to be upgraded to V2, + // create a new module and pass in `publisher` from `fun init_module(publisher: &signer)` + burn_mint_token_pool::register_v2_callbacks(publisher); + } + + #[test_only] + public fun test_init_module(publisher: &signer) { + init_module(publisher); + } +} diff --git a/contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move b/contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move index 212958122..5ceea6cf9 100644 --- a/contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move +++ b/contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move @@ -15,7 +15,7 @@ module lock_release_token_pool::lock_release_token_pool { use std::signer; use std::string::{Self, String}; - use ccip::token_admin_registry; + use ccip::token_admin_registry::{Self, LockOrBurnInputV1, ReleaseOrMintInputV1}; use ccip_token_pool::ownable; use ccip_token_pool::rate_limiter; use ccip_token_pool::token_pool; @@ -80,12 +80,8 @@ module lock_release_token_pool::lock_release_token_pool { register_mcms_entrypoint(publisher, token_pool_module_name); }; - token_admin_registry::register_pool( - publisher, - token_pool_module_name, - @lock_release_local_token, - CallbackProof {} - ); + // Register V2 pool with closure-based callbacks + register_v2_callbacks(publisher); // create a resource account to be the owner of the primary FungibleStore we will use. let (store_signer, store_signer_cap) = @@ -163,6 +159,19 @@ module lock_release_token_pool::lock_release_token_pool { move_to(&store_signer, pool); } + public fun register_v2_callbacks(publisher: &signer) { + assert!( + signer::address_of(publisher) == @lock_release_token_pool, + error::permission_denied(E_NOT_PUBLISHER) + ); + token_admin_registry::register_pool_v2( + publisher, + @lock_release_local_token, + lock_or_burn_v2, + release_or_mint_v2 + ); + } + // ================================================================ // | Exposing token_pool functions | // ================================================================ @@ -439,6 +448,66 @@ module lock_release_token_pool::lock_release_token_pool { fa } + #[persistent] + fun lock_or_burn_v2( + fa: FungibleAsset, input: LockOrBurnInputV1 + ): (vector, vector) acquires LockReleaseTokenPoolState { + let pool = borrow_pool_mut(); + let fa_amount = fungible_asset::amount(&fa); + + let dest_token_address = + token_pool::validate_lock_or_burn( + &mut pool.token_pool_state, + &fa, + &input, + fa_amount + ); + + // Lock the funds in the pool + primary_fungible_store::deposit(pool.store_signer_address, fa); + + let remote_chain_selector = + token_admin_registry::get_lock_or_burn_remote_chain_selector(&input); + + token_pool::emit_locked_or_burned( + &mut pool.token_pool_state, fa_amount, remote_chain_selector + ); + + (dest_token_address, token_pool::encode_local_decimals(&pool.token_pool_state)) + } + + #[persistent] + fun release_or_mint_v2( + input: ReleaseOrMintInputV1 + ): (FungibleAsset, u64) acquires LockReleaseTokenPoolState { + let pool = borrow_pool_mut(); + let local_amount = + token_pool::calculate_release_or_mint_amount(&pool.token_pool_state, &input); + + token_pool::validate_release_or_mint( + &mut pool.token_pool_state, &input, local_amount + ); + + let store_signer = account::create_signer_with_capability(&pool.store_signer_cap); + let metadata = token_pool::get_fa_metadata(&pool.token_pool_state); + + // Withdraw the amount from the store for release + let fa = primary_fungible_store::withdraw(&store_signer, metadata, local_amount); + + let recipient = token_admin_registry::get_release_or_mint_receiver(&input); + let remote_chain_selector = + token_admin_registry::get_release_or_mint_remote_chain_selector(&input); + + token_pool::emit_released_or_minted( + &mut pool.token_pool_state, + recipient, + local_amount, + remote_chain_selector + ); + + (fa, local_amount) + } + // ================================================================ // | Rate limit config | // ================================================================ @@ -895,6 +964,63 @@ module lock_release_token_pool::lock_release_token_pool { init_module(publisher); } + #[test_only] + /// Used for registering the pool with V2 closure-based callbacks. + public fun create_callback_proof(): CallbackProof { + CallbackProof {} + } + + #[test_only] + public fun test_init_v1(publisher: &signer) { + // register the pool on deployment, because in the case of object code deployment, + // this is the only time we have a signer ref to @ccip_lock_release_pool. + assert!( + object::object_exists(@lock_release_local_token), + error::invalid_argument(E_INVALID_FUNGIBLE_ASSET) + ); + let metadata = object::address_to_object(@lock_release_local_token); + + // create an Account on the object for event handles. + account::create_account_if_does_not_exist(@lock_release_token_pool); + + // the name of this module. if incorrect, callbacks will fail to be registered and + // register_pool will revert. + let token_pool_module_name = b"lock_release_token_pool"; + + // Register the entrypoint with mcms + if (@mcms_register_entrypoints == @0x1) { + register_mcms_entrypoint(publisher, token_pool_module_name); + }; + + token_admin_registry::register_pool( + publisher, + token_pool_module_name, + @lock_release_local_token, + CallbackProof {} + ); + + // create a resource account to be the owner of the primary FungibleStore we will use. + let (store_signer, store_signer_cap) = + account::create_resource_account(publisher, STORE_OBJECT_SEED); + + // make sure this is a valid fungible asset that is primary fungible store enabled, + // ie. created with primary_fungible_store::create_primary_store_enabled_fungible_asset + primary_fungible_store::ensure_primary_store_exists( + signer::address_of(&store_signer), metadata + ); + + move_to( + publisher, + LockReleaseTokenPoolDeployment { + store_signer_cap, + ownable_state: ownable::new(&store_signer, @lock_release_token_pool), + token_pool_state: token_pool::initialize( + &store_signer, @lock_release_local_token, vector[] + ) + } + ); + } + #[test_only] public fun get_locked_or_burned_events( state: address diff --git a/contracts/ccip/ccip_token_pools/lock_release_token_pool/tests/upgrade_v2.move b/contracts/ccip/ccip_token_pools/lock_release_token_pool/tests/upgrade_v2.move new file mode 100644 index 000000000..0fbd1d1f5 --- /dev/null +++ b/contracts/ccip/ccip_token_pools/lock_release_token_pool/tests/upgrade_v2.move @@ -0,0 +1,32 @@ +#[test_only] +module lock_release_token_pool::upgrade_v2 { + use std::account::{Self}; + use std::error; + use std::fungible_asset::{Metadata}; + use std::object::{Self}; + + use lock_release_token_pool::lock_release_token_pool; + + const E_INVALID_FUNGIBLE_ASSET: u64 = 1; + + fun init_module(publisher: &signer) { + // register the pool on deployment, because in the case of object code deployment, + // this is the only time we have a signer ref to @lock_release_token_pool. + assert!( + object::object_exists(@lock_release_local_token), + error::invalid_argument(E_INVALID_FUNGIBLE_ASSET) + ); + + // create an Account on the object for event handles. + account::create_account_if_does_not_exist(@lock_release_token_pool); + + // If the contract has already been deployed with V1 and needs to be upgraded to V2, + // create a new module and pass in `publisher` from `fun init_module(publisher: &signer)` + lock_release_token_pool::register_v2_callbacks(publisher); + } + + #[test_only] + public fun test_init_module(publisher: &signer) { + init_module(publisher); + } +} diff --git a/contracts/ccip/ccip_token_pools/managed_token_pool/sources/managed_token_pool.move b/contracts/ccip/ccip_token_pools/managed_token_pool/sources/managed_token_pool.move index b779de216..4b2193fa6 100644 --- a/contracts/ccip/ccip_token_pools/managed_token_pool/sources/managed_token_pool.move +++ b/contracts/ccip/ccip_token_pools/managed_token_pool/sources/managed_token_pool.move @@ -10,7 +10,7 @@ module managed_token_pool::managed_token_pool { use managed_token::managed_token; - use ccip::token_admin_registry; + use ccip::token_admin_registry::{Self, LockOrBurnInputV1, ReleaseOrMintInputV1}; use ccip_token_pool::ownable; use ccip_token_pool::rate_limiter; use ccip_token_pool::token_pool; @@ -27,12 +27,9 @@ module managed_token_pool::managed_token_pool { store_signer_address: address } - const E_NOT_PUBLISHER: u64 = 1; - const E_ALREADY_INITIALIZED: u64 = 2; - const E_INVALID_FUNGIBLE_ASSET: u64 = 3; - const E_LOCAL_TOKEN_MISMATCH: u64 = 4; - const E_INVALID_ARGUMENTS: u64 = 5; - const E_UNKNOWN_FUNCTION: u64 = 6; + const E_INVALID_ARGUMENTS: u64 = 1; + const E_UNKNOWN_FUNCTION: u64 = 2; + const E_NOT_PUBLISHER: u64 = 3; // ================================================================ // | Init | @@ -58,18 +55,14 @@ module managed_token_pool::managed_token_pool { register_mcms_entrypoint(publisher, token_pool_module_name); }; - let managed_token_address = managed_token::token_metadata(); - token_admin_registry::register_pool( - publisher, - token_pool_module_name, - managed_token_address, - CallbackProof {} - ); + // Register V2 pool with closure-based callbacks + register_v2_callbacks(publisher); // create a resource account to be the owner of the primary FungibleStore we will use. let (store_signer, store_signer_cap) = account::create_resource_account(publisher, STORE_OBJECT_SEED); + let managed_token_address = managed_token::token_metadata(); let metadata = object::address_to_object(managed_token_address); // make sure this is a valid fungible asset that is primary fungible store enabled, @@ -92,6 +85,20 @@ module managed_token_pool::managed_token_pool { move_to(&store_signer, pool); } + public fun register_v2_callbacks(publisher: &signer) { + assert!( + signer::address_of(publisher) == @managed_token_pool, + error::permission_denied(E_NOT_PUBLISHER) + ); + let managed_token_address = managed_token::token_metadata(); + token_admin_registry::register_pool_v2( + publisher, + managed_token_address, + lock_or_burn_v2, + release_or_mint_v2 + ); + } + // ================================================================ // | Exposing token_pool functions | // ================================================================ @@ -329,6 +336,77 @@ module managed_token_pool::managed_token_pool { fa } + #[persistent] + fun lock_or_burn_v2(fa: FungibleAsset, input: LockOrBurnInputV1) + : (vector, vector) { + let pool = borrow_pool_mut(); + let fa_amount = fungible_asset::amount(&fa); + + // This method validates various aspects of the lock or burn operation. If any of the + // validations fail, the transaction will abort. + let dest_token_address = + token_pool::validate_lock_or_burn( + &mut pool.token_pool_state, + &fa, + &input, + fa_amount + ); + + // Burn the funds + let store = + primary_fungible_store::ensure_primary_store_exists( + pool.store_signer_address, fungible_asset::asset_metadata(&fa) + ); + let signer = &account::create_signer_with_capability(&pool.store_signer_cap); + fungible_asset::deposit(store, fa); + managed_token::burn(signer, pool.store_signer_address, fa_amount); + + let remote_chain_selector = + token_admin_registry::get_lock_or_burn_remote_chain_selector(&input); + + token_pool::emit_locked_or_burned( + &mut pool.token_pool_state, fa_amount, remote_chain_selector + ); + + (dest_token_address, token_pool::encode_local_decimals(&pool.token_pool_state)) + } + + #[persistent] + fun release_or_mint_v2(input: ReleaseOrMintInputV1): (FungibleAsset, u64) { + let pool = borrow_pool_mut(); + let local_amount = + token_pool::calculate_release_or_mint_amount(&pool.token_pool_state, &input); + + token_pool::validate_release_or_mint( + &mut pool.token_pool_state, &input, local_amount + ); + + // Mint the amount for release. + let local_token = token_admin_registry::get_release_or_mint_local_token(&input); + let metadata = object::address_to_object(local_token); + let store = + primary_fungible_store::ensure_primary_store_exists( + pool.store_signer_address, metadata + ); + let signer = &account::create_signer_with_capability(&pool.store_signer_cap); + managed_token::mint(signer, pool.store_signer_address, local_amount); + + // Calling into `fungible_asset::withdraw` works as managed token is not dispatchable + let fa = fungible_asset::withdraw(signer, store, local_amount); + let recipient = token_admin_registry::get_release_or_mint_receiver(&input); + let remote_chain_selector = + token_admin_registry::get_release_or_mint_remote_chain_selector(&input); + + token_pool::emit_released_or_minted( + &mut pool.token_pool_state, + recipient, + local_amount, + remote_chain_selector + ); + + (fa, local_amount) + } + // ================================================================ // | Rate limit config | // ================================================================ @@ -639,4 +717,10 @@ module managed_token_pool::managed_token_pool { public entry fun test_init_module(owner: &signer) { init_module(owner); } + + #[test_only] + /// Used for registering the pool with V2 closure-based callbacks. + public fun create_callback_proof(): CallbackProof { + CallbackProof {} + } } diff --git a/contracts/ccip/ccip_token_pools/managed_token_pool/tests/upgrade_v2.move b/contracts/ccip/ccip_token_pools/managed_token_pool/tests/upgrade_v2.move new file mode 100644 index 000000000..9e8c413dc --- /dev/null +++ b/contracts/ccip/ccip_token_pools/managed_token_pool/tests/upgrade_v2.move @@ -0,0 +1,23 @@ +#[test_only] +module managed_token_pool::upgrade_v2 { + use std::account::{Self}; + + use managed_token_pool::managed_token_pool; + + fun init_module(publisher: &signer) { + // register the pool on deployment, because in the case of object code deployment, + // this is the only time we have a signer ref to @managed_token_pool. + + // create an Account on the object for event handles. + account::create_account_if_does_not_exist(@managed_token_pool); + + // If the contract has already been deployed with V1 and needs to be upgraded to V2, + // create a new module and pass in `publisher` from `fun init_module(publisher: &signer)` + managed_token_pool::register_v2_callbacks(publisher); + } + + #[test_only] + public fun test_init_module(publisher: &signer) { + init_module(publisher); + } +} diff --git a/contracts/ccip/ccip_token_pools/regulated_token_pool/sources/regulated_token_pool.move b/contracts/ccip/ccip_token_pools/regulated_token_pool/sources/regulated_token_pool.move index 455f66930..4d41d4def 100644 --- a/contracts/ccip/ccip_token_pools/regulated_token_pool/sources/regulated_token_pool.move +++ b/contracts/ccip/ccip_token_pools/regulated_token_pool/sources/regulated_token_pool.move @@ -10,7 +10,7 @@ module regulated_token_pool::regulated_token_pool { use regulated_token::regulated_token::{Self}; - use ccip::token_admin_registry; + use ccip::token_admin_registry::{Self, LockOrBurnInputV1, ReleaseOrMintInputV1}; use ccip_token_pool::ownable; use ccip_token_pool::rate_limiter; use ccip_token_pool::token_pool; @@ -27,12 +27,9 @@ module regulated_token_pool::regulated_token_pool { store_signer_address: address } - const E_NOT_PUBLISHER: u64 = 1; - const E_ALREADY_INITIALIZED: u64 = 2; - const E_INVALID_FUNGIBLE_ASSET: u64 = 3; - const E_LOCAL_TOKEN_MISMATCH: u64 = 4; - const E_INVALID_ARGUMENTS: u64 = 5; - const E_UNKNOWN_FUNCTION: u64 = 6; + const E_INVALID_ARGUMENTS: u64 = 1; + const E_UNKNOWN_FUNCTION: u64 = 2; + const E_NOT_PUBLISHER: u64 = 3; // ================================================================ // | Init | @@ -58,18 +55,14 @@ module regulated_token_pool::regulated_token_pool { register_mcms_entrypoint(publisher, token_pool_module_name); }; - let regulated_token_address = regulated_token::token_address(); - token_admin_registry::register_pool( - publisher, - token_pool_module_name, - regulated_token_address, - CallbackProof {} - ); + // Register V2 pool with closure-based callbacks + register_v2_callbacks(publisher); // create a resource account to be the owner of the primary FungibleStore we will use. let (store_signer, store_signer_cap) = account::create_resource_account(publisher, STORE_OBJECT_SEED); + let regulated_token_address = regulated_token::token_address(); let metadata = object::address_to_object(regulated_token_address); // make sure this is a valid fungible asset that is primary fungible store enabled, @@ -90,6 +83,20 @@ module regulated_token_pool::regulated_token_pool { move_to(&store_signer, pool); } + public fun register_v2_callbacks(publisher: &signer) { + assert!( + signer::address_of(publisher) == @regulated_token_pool, + error::permission_denied(E_NOT_PUBLISHER) + ); + let regulated_token_address = regulated_token::token_address(); + token_admin_registry::register_pool_v2( + publisher, + regulated_token_address, + lock_or_burn_v2, + release_or_mint_v2 + ); + } + // ================================================================ // | Exposing token_pool functions | // ================================================================ @@ -318,6 +325,65 @@ module regulated_token_pool::regulated_token_pool { fa } + #[persistent] + fun lock_or_burn_v2( + fa: FungibleAsset, input: LockOrBurnInputV1 + ): (vector, vector) acquires RegulatedTokenPoolState { + let pool = borrow_pool_mut(); + let fa_amount = fungible_asset::amount(&fa); + + let dest_token_address = + token_pool::validate_lock_or_burn( + &mut pool.token_pool_state, + &fa, + &input, + fa_amount + ); + + let pool_signer = &account::create_signer_with_capability(&pool.store_signer_cap); + let sender = token_admin_registry::get_lock_or_burn_sender(&input); + regulated_token::bridge_burn(pool_signer, sender, fa); + + let remote_chain_selector = + token_admin_registry::get_lock_or_burn_remote_chain_selector(&input); + + token_pool::emit_locked_or_burned( + &mut pool.token_pool_state, fa_amount, remote_chain_selector + ); + + (dest_token_address, token_pool::encode_local_decimals(&pool.token_pool_state)) + } + + #[persistent] + fun release_or_mint_v2( + input: ReleaseOrMintInputV1 + ): (FungibleAsset, u64) acquires RegulatedTokenPoolState { + let pool = borrow_pool_mut(); + let local_amount = + token_pool::calculate_release_or_mint_amount(&pool.token_pool_state, &input); + + token_pool::validate_release_or_mint( + &mut pool.token_pool_state, &input, local_amount + ); + + // Mint the amount for release using regulated token's bridge mint function + let pool_signer = &account::create_signer_with_capability(&pool.store_signer_cap); + let receiver = token_admin_registry::get_release_or_mint_receiver(&input); + let fa = regulated_token::bridge_mint(pool_signer, receiver, local_amount); + + let remote_chain_selector = + token_admin_registry::get_release_or_mint_remote_chain_selector(&input); + + token_pool::emit_released_or_minted( + &mut pool.token_pool_state, + receiver, + local_amount, + remote_chain_selector + ); + + (fa, local_amount) + } + // ================================================================ // | Rate limit config | // ================================================================ @@ -628,4 +694,59 @@ module regulated_token_pool::regulated_token_pool { public entry fun test_init_module(owner: &signer) { init_module(owner); } + + #[test_only] + /// Used for registering the pool with V2 closure-based callbacks. + public fun create_callback_proof(): CallbackProof { + CallbackProof {} + } + + #[test_only] + public fun test_init_v1(publisher: &signer) { + // register the pool on deployment, because in the case of object code deployment, + // this is the only time we have a signer ref to @regulated_token_pool. + + // create an Account on the object for event handles. + account::create_account_if_does_not_exist(@regulated_token_pool); + + // the name of this module. if incorrect, callbacks will fail to be registered and + // register_pool will revert. + let token_pool_module_name = b"regulated_token_pool"; + + // Register the entrypoint with mcms + if (@mcms_register_entrypoints == @0x1) { + register_mcms_entrypoint(publisher, token_pool_module_name); + }; + + let regulated_token_address = regulated_token::token_address(); + token_admin_registry::register_pool( + publisher, + token_pool_module_name, + regulated_token_address, + CallbackProof {} + ); + + // create a resource account to be the owner of the primary FungibleStore we will use. + let (store_signer, store_signer_cap) = + account::create_resource_account(publisher, STORE_OBJECT_SEED); + + let metadata = object::address_to_object(regulated_token_address); + + // make sure this is a valid fungible asset that is primary fungible store enabled, + // ie. created with primary_fungible_store::create_primary_store_enabled_fungible_asset + primary_fungible_store::ensure_primary_store_exists( + signer::address_of(&store_signer), metadata + ); + + let pool = RegulatedTokenPoolState { + ownable_state: ownable::new(&store_signer, @regulated_token_pool), + store_signer_address: signer::address_of(&store_signer), + store_signer_cap, + token_pool_state: token_pool::initialize( + &store_signer, regulated_token_address, vector[] + ) + }; + + move_to(&store_signer, pool); + } } diff --git a/contracts/ccip/ccip_token_pools/regulated_token_pool/tests/regulated_token_pool_ccip_test.move b/contracts/ccip/ccip_token_pools/regulated_token_pool/tests/regulated_token_pool_ccip_test.move index 3b4b7f043..2df11cf8a 100644 --- a/contracts/ccip/ccip_token_pools/regulated_token_pool/tests/regulated_token_pool_ccip_test.move +++ b/contracts/ccip/ccip_token_pools/regulated_token_pool/tests/regulated_token_pool_ccip_test.move @@ -393,9 +393,9 @@ module regulated_token_pool::regulated_token_pool_ccip_test { assert!(administrator != @0x0); assert!(pending_admin == @0x0); - // Verify pool local token matches + // Verify pool local token matches (using V2 since pools now only register with V2) let local_token = - token_admin_registry::get_pool_local_token(@regulated_token_pool); + token_admin_registry::get_pool_local_token_v2(@regulated_token_pool); assert!(local_token == regulated_token_address); // Test the basic registry functions work diff --git a/contracts/ccip/ccip_token_pools/regulated_token_pool/tests/upgrade_v2.move b/contracts/ccip/ccip_token_pools/regulated_token_pool/tests/upgrade_v2.move new file mode 100644 index 000000000..220edb7b4 --- /dev/null +++ b/contracts/ccip/ccip_token_pools/regulated_token_pool/tests/upgrade_v2.move @@ -0,0 +1,23 @@ +#[test_only] +module regulated_token_pool::upgrade_v2 { + use std::account::{Self}; + + use regulated_token_pool::regulated_token_pool; + + fun init_module(publisher: &signer) { + // register the pool on deployment, because in the case of object code deployment, + // this is the only time we have a signer ref to @regulated_token_pool. + + // create an Account on the object for event handles. + account::create_account_if_does_not_exist(@regulated_token_pool); + + // If the contract has already been deployed with V1 and needs to be upgraded to V2, + // create a new module and pass in `publisher` from `fun init_module(publisher: &signer)` + regulated_token_pool::register_v2_callbacks(publisher); + } + + #[test_only] + public fun test_init_module(publisher: &signer) { + init_module(publisher); + } +} diff --git a/contracts/ccip/ccip_token_pools/usdc_token_pool/sources/usdc_token_pool.move b/contracts/ccip/ccip_token_pools/usdc_token_pool/sources/usdc_token_pool.move index 9081c6f9f..15dc7e5c0 100644 --- a/contracts/ccip/ccip_token_pools/usdc_token_pool/sources/usdc_token_pool.move +++ b/contracts/ccip/ccip_token_pools/usdc_token_pool/sources/usdc_token_pool.move @@ -13,7 +13,7 @@ module usdc_token_pool::usdc_token_pool { use ccip::address; use ccip::eth_abi; - use ccip::token_admin_registry; + use ccip::token_admin_registry::{Self, LockOrBurnInputV1, ReleaseOrMintInputV1}; use ccip_token_pool::ownable; use ccip_token_pool::rate_limiter; use ccip_token_pool::token_pool; @@ -112,12 +112,8 @@ module usdc_token_pool::usdc_token_pool { register_mcms_entrypoint(publisher, token_pool_module_name); }; - token_admin_registry::register_pool( - publisher, - token_pool_module_name, - @local_token, - CallbackProof {} - ); + // Register V2 pool with closure-based callbacks + register_v2_callbacks(publisher); // create a resource account to be the owner of the primary FungibleStore we will use. let (store_signer, store_signer_cap) = @@ -142,6 +138,19 @@ module usdc_token_pool::usdc_token_pool { ); } + public fun register_v2_callbacks(publisher: &signer) { + assert!( + signer::address_of(publisher) == @usdc_token_pool, + error::permission_denied(E_NOT_PUBLISHER) + ); + token_admin_registry::register_pool_v2( + publisher, + @local_token, + lock_or_burn_v2, + release_or_mint_v2 + ); + } + public fun initialize(caller: &signer) acquires USDCTokenPoolDeployment { assert_can_initialize(signer::address_of(caller)); @@ -364,9 +373,6 @@ module usdc_token_pool::usdc_token_pool { dest_pool_data ); - let remote_chain_selector = - token_admin_registry::get_lock_or_burn_remote_chain_selector(&input); - token_pool::emit_locked_or_burned( &mut pool.token_pool_state, fa_amount, remote_chain_selector ); @@ -493,6 +499,109 @@ module usdc_token_pool::usdc_token_pool { ); } + #[persistent] + fun lock_or_burn_v2( + fa: FungibleAsset, input: LockOrBurnInputV1 + ): (vector, vector) acquires USDCTokenPoolState { + let pool = borrow_pool_mut(); + let fa_amount = fungible_asset::amount(&fa); + + let dest_token_address = + token_pool::validate_lock_or_burn( + &mut pool.token_pool_state, + &fa, + &input, + fa_amount + ); + + let store_signer = account::create_signer_with_capability(&pool.store_signer_cap); + + let remote_chain_selector = + token_admin_registry::get_lock_or_burn_remote_chain_selector(&input); + assert!( + pool.chain_to_domain.contains(remote_chain_selector), + error::invalid_argument(E_DOMAIN_NOT_FOUND) + ); + + let remote_domain_info = pool.chain_to_domain.borrow(remote_chain_selector); + + assert!(remote_domain_info.enabled, error::invalid_argument(E_DOMAIN_DISABLED)); + + let mint_recipient_bytes = + token_admin_registry::get_lock_or_burn_receiver(&input); + let mint_recipient = from_bcs::to_address(mint_recipient_bytes); + let nonce = + token_messenger::deposit_for_burn_with_caller( + &store_signer, + fa, + remote_domain_info.domain_identifier, + mint_recipient, + from_bcs::to_address(remote_domain_info.allowed_caller) + ); + + let dest_pool_data = encode_dest_pool_data(pool.local_domain_identifier, nonce); + + token_pool::emit_locked_or_burned( + &mut pool.token_pool_state, fa_amount, remote_chain_selector + ); + + (dest_token_address, dest_pool_data) + } + + #[persistent] + fun release_or_mint_v2( + input: ReleaseOrMintInputV1 + ): (FungibleAsset, u64) acquires USDCTokenPoolState { + let pool = borrow_pool_mut(); + let local_amount = + token_admin_registry::get_release_or_mint_source_amount(&input) as u64; + + token_pool::validate_release_or_mint( + &mut pool.token_pool_state, &input, local_amount + ); + + let store_signer = account::create_signer_with_capability(&pool.store_signer_cap); + + let (source_domain_identifier, nonce) = + decode_dest_pool_data( + token_admin_registry::get_release_or_mint_source_pool_data(&input) + ); + let offchain_token_data = + token_admin_registry::get_release_or_mint_offchain_token_data(&input); + + let (message_bytes, attestation) = + parse_message_and_attestation(offchain_token_data); + + validate_message( + &message_bytes, + source_domain_identifier, + nonce, + pool.local_domain_identifier + ); + + let receipt = + message_transmitter::receive_message( + &store_signer, &message_bytes, &attestation + ); + + assert!(token_messenger::handle_receive_message(receipt)); + + let recipient = token_admin_registry::get_release_or_mint_receiver(&input); + let remote_chain_selector = + token_admin_registry::get_release_or_mint_remote_chain_selector(&input); + + token_pool::emit_released_or_minted( + &mut pool.token_pool_state, + recipient, + local_amount, + remote_chain_selector + ); + + let fa_metadata = token_pool::get_fa_metadata(&pool.token_pool_state); + + (fungible_asset::zero(fa_metadata), local_amount) + } + // ================================================================ // | USDC Domains | // ================================================================ @@ -912,4 +1021,10 @@ module usdc_token_pool::usdc_token_pool { public fun test_init_module(publisher: &signer) { init_module(publisher); } + + #[test_only] + /// Used for registering the pool with V2 closure-based callbacks. + public fun create_callback_proof(): CallbackProof { + CallbackProof {} + } } diff --git a/contracts/ccip/ccip_token_pools/usdc_token_pool/tests/upgrade_v2.move b/contracts/ccip/ccip_token_pools/usdc_token_pool/tests/upgrade_v2.move new file mode 100644 index 000000000..296c1dce9 --- /dev/null +++ b/contracts/ccip/ccip_token_pools/usdc_token_pool/tests/upgrade_v2.move @@ -0,0 +1,32 @@ +#[test_only] +module usdc_token_pool::upgrade_v2 { + use std::account::{Self}; + use std::error; + use std::fungible_asset::{Metadata}; + use std::object::{Self}; + + use usdc_token_pool::usdc_token_pool; + + const E_INVALID_FUNGIBLE_ASSET: u64 = 1; + + fun init_module(publisher: &signer) { + // register the pool on deployment, because in the case of object code deployment, + // this is the only time we have a signer ref to @usdc_token_pool. + assert!( + object::object_exists(@local_token), + error::invalid_argument(E_INVALID_FUNGIBLE_ASSET) + ); + + // create an Account on the object for event handles. + account::create_account_if_does_not_exist(@usdc_token_pool); + + // If the contract has already been deployed with V1 and needs to be upgraded to V2, + // create a new module and pass in `publisher` from `fun init_module(publisher: &signer)` + usdc_token_pool::register_v2_callbacks(publisher); + } + + #[test_only] + public fun test_init_module(publisher: &signer) { + init_module(publisher); + } +} diff --git a/gen.go b/gen.go index 843884c7a..05bd3dde9 100644 --- a/gen.go +++ b/gen.go @@ -21,6 +21,7 @@ package aptos //go:generate go run ./cmd/bindgen --input ./contracts/ccip/ccip_router/sources/router.move --output ./bindings/ccip_router/router //go:generate go run ./cmd/bindgen --input ./contracts/ccip/ccip_dummy_receiver/sources/dummy_receiver.move --output ./bindings/ccip_dummy_receiver/dummy_receiver +//go:generate go run ./cmd/bindgen --input ./contracts/ccip/ccip_dummy_receiver/sources/ptt_dummy_receiver.move --output ./bindings/ccip_dummy_receiver/ptt_dummy_receiver //go:generate go run ./cmd/bindgen --input ./contracts/ccip/ccip_token_pools/burn_mint_token_pool/sources/burn_mint_token_pool.move --output ./bindings/ccip_token_pools/burn_mint_token_pool/burn_mint_token_pool --externalStructs ccip_token_pool::rate_limiter::TokenBucket=github.com/smartcontractkit/chainlink-aptos/bindings/ccip_token_pools/token_pool/rate_limiter //go:generate go run ./cmd/bindgen --input ./contracts/ccip/ccip_token_pools/lock_release_token_pool/sources/lock_release_token_pool.move --output ./bindings/ccip_token_pools/lock_release_token_pool/lock_release_token_pool --externalStructs ccip_token_pool::rate_limiter::TokenBucket=github.com/smartcontractkit/chainlink-aptos/bindings/ccip_token_pools/token_pool/rate_limiter